diff --git a/tree/ntuple/v7/test/ntuple_emulated.cxx b/tree/ntuple/v7/test/ntuple_emulated.cxx index e2e7ae192f388..7e92bc0cddab1 100644 --- a/tree/ntuple/v7/test/ntuple_emulated.cxx +++ b/tree/ntuple/v7/test/ntuple_emulated.cxx @@ -3,7 +3,7 @@ #define NTUPLE_FORK_MACROS #include "ntuple_fork.hxx" -TEST(RNTupleEmulated, EmulatedFields) +TEST(RNTupleEmulated, EmulatedFields_Simple) { // Write a user-defined class to RNTuple, then "forget" about it and try to load it as an emulated record field. // To achieve this "forgetting" we use fork() so that the parent process doesn't know about the class. @@ -94,3 +94,107 @@ struct Outer { reader = RNTupleReader::Open(cmOpts, "ntpl", fileGuard.GetPath()); reader->LoadEntry(0); } + +TEST(RNTupleEmulated, EmulatedFields_Vecs) +{ + // Write a user-defined class to RNTuple, then "forget" about it and try to load it as an emulated record field. + // To achieve this "forgetting" we use fork() so that the parent process doesn't know about the class. + + FileRaii fileGuard("test_ntuple_emulated_fields_vecs.root"); + + ExecInFork([&] { + // The child process writes the file and exits, but the file must be preserved to be read by the parent. + fileGuard.PreserveFile(); + + ASSERT_TRUE(gInterpreter->Declare(R"( +struct Inner { + float fFlt; + + ClassDefNV(Inner, 2); +}; + +struct Outer { + std::vector fInners; + Inner fInner; + int f; + + ClassDefNV(Outer, 2); +}; +)")); + + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("outers", "std::vector").Unwrap()); + + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + writer->Fill(); + + void *ptr = writer->GetModel().GetDefaultEntry().GetPtr("outers").get(); + DeclarePointer("std::vector", "ptrOuters", ptr); + + ProcessLine("ptrOuters->push_back(Outer{});"); + ProcessLine("(*ptrOuters)[0].fInners.push_back(Inner{42.f});"); + ProcessLine("(*ptrOuters)[0].fInner.fFlt = 84.f;"); + writer->Fill(); + + // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + writer.reset(); + }); + + auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); + const auto &desc = reader->GetDescriptor(); + + { + RNTupleDescriptor::RCreateModelOptions opts; + opts.fEmulateUnknownTypes = false; + try { + auto model = desc.CreateModel(opts); + FAIL() << "Creating a model without fEmulateUnknownTypes should fail"; + } catch (ROOT::RException ex) { + ASSERT_THAT(ex.GetError().GetReport(), testing::HasSubstr("unknown type")); + } + } + + { + RNTupleDescriptor::RCreateModelOptions opts; + opts.fEmulateUnknownTypes = true; + auto model = desc.CreateModel(opts); + ASSERT_NE(model, nullptr); + + const auto &outers = model->GetConstField("outers"); + ASSERT_EQ(outers.GetTypeName(), "std::vector"); + ASSERT_EQ(outers.GetStructure(), ENTupleStructure::kCollection); + ASSERT_EQ(outers.GetSubFields().size(), 1); + + const auto subfields = outers.GetSubFields(); + const auto *outer = subfields[0]; + ASSERT_EQ(outer->GetTypeName(), "Outer"); + ASSERT_EQ(outer->GetStructure(), ENTupleStructure::kRecord); + + const auto outersubfields = outer->GetSubFields(); + ASSERT_EQ(outersubfields.size(), 3); + + const auto *inners = outersubfields[0]; + ASSERT_EQ(inners->GetTypeName(), "std::vector"); + ASSERT_EQ(inners->GetFieldName(), "fInners"); + ASSERT_EQ(inners->GetStructure(), ENTupleStructure::kCollection); + ASSERT_EQ(inners->GetSubFields().size(), 1); + ASSERT_EQ(inners->GetSubFields()[0]->GetFieldName(), "_0"); + + const auto innersubfields = inners->GetSubFields(); + const auto *inner = innersubfields[0]; + ASSERT_EQ(inner->GetTypeName(), "Inner"); + ASSERT_EQ(inner->GetStructure(), ENTupleStructure::kRecord); + ASSERT_EQ(inner->GetSubFields().size(), 1); + ASSERT_EQ(inner->GetSubFields()[0]->GetFieldName(), "fFlt"); + } + + // Now test loading entries with a reader + RNTupleDescriptor::RCreateModelOptions cmOpts; + cmOpts.fEmulateUnknownTypes = true; + reader = RNTupleReader::Open(cmOpts, "ntpl", fileGuard.GetPath()); + reader->LoadEntry(0); +}