From c8cfc0db390985269cc1b5b847034f660ebcae80 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 02:54:08 +0200 Subject: [PATCH 1/7] [ntuple] fix de-s11n of streamer info records --- tree/ntuple/src/RNTupleSerialize.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tree/ntuple/src/RNTupleSerialize.cxx b/tree/ntuple/src/RNTupleSerialize.cxx index 520fbc5c212cd..aab44d7c2a584 100644 --- a/tree/ntuple/src/RNTupleSerialize.cxx +++ b/tree/ntuple/src/RNTupleSerialize.cxx @@ -2146,13 +2146,12 @@ ROOT::Internal::RNTupleSerializer::DeserializeStreamerInfos(const std::string &e TBufferFile buffer(TBuffer::kRead, extraTypeInfoContent.length(), const_cast(extraTypeInfoContent.data()), false /* adopt */); auto infoList = reinterpret_cast(buffer.ReadObject(TList::Class())); - infoList->SetOwner(); // delete the TStreamerInfo items of the list TObjLink *lnk = infoList->FirstLink(); while (lnk) { auto info = reinterpret_cast(lnk->GetObject()); info->BuildCheck(); - infoMap[info->GetNumber()] = info->GetClass()->GetStreamerInfo(); + infoMap[info->GetNumber()] = info->GetClass()->GetStreamerInfo(info->GetClassVersion()); assert(info->GetNumber() == infoMap[info->GetNumber()]->GetNumber()); lnk = lnk->Next(); } From 4e388868cab8a851318102c362768b7e52b95d1e Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 02:55:58 +0200 Subject: [PATCH 2/7] [ntuple] add RPageSource::RegisterStreamerInfos() Co-authored-by: Jonas Hahnfeld --- tree/ntuple/inc/ROOT/RPageStorage.hxx | 5 +++++ tree/ntuple/src/RPageStorage.cxx | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tree/ntuple/inc/ROOT/RPageStorage.hxx b/tree/ntuple/inc/ROOT/RPageStorage.hxx index 039188f9c61bc..6fcf41f646128 100644 --- a/tree/ntuple/inc/ROOT/RPageStorage.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorage.hxx @@ -612,6 +612,7 @@ private: REntryRange fEntryRange; ///< Used by the cluster pool to prevent reading beyond the given range bool fHasStructure = false; ///< Set to true once `LoadStructure()` is called bool fIsAttached = false; ///< Set to true once `Attach()` is called + bool fHasStreamerInfosRegistered = false; ///< Set to true when RegisterStreamerInfos() is called. /// Remembers the last cluster id from which a page was requested ROOT::DescriptorId_t fLastUsedCluster = ROOT::kInvalidDescriptorId; @@ -817,6 +818,10 @@ public: // TODO(gparolini): for symmetry with SealPage(), we should either make this private or SealPage() public. RResult UnsealPage(const RSealedPage &sealedPage, const ROOT::Internal::RColumnElementBase &element); + + /// Builds the streamer info records from the descriptor's extra type info section. This is necessary when + /// connecting streamer fields so that emulated classes can be read. + void RegisterStreamerInfos(); }; // class RPageSource } // namespace Internal diff --git a/tree/ntuple/src/RPageStorage.cxx b/tree/ntuple/src/RPageStorage.cxx index 1652f7ca3f4f4..0300a7501d2a1 100644 --- a/tree/ntuple/src/RPageStorage.cxx +++ b/tree/ntuple/src/RPageStorage.cxx @@ -589,6 +589,22 @@ ROOT::RResult ROOT::Internal::RPageSource::UnsealPage(con return page; } +void ROOT::Internal::RPageSource::RegisterStreamerInfos() +{ + if (fHasStreamerInfosRegistered) + return; + + for (const auto &extraTypeInfo : fDescriptor.GetExtraTypeInfoIterable()) { + if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) + continue; + // We don't need the result, it's enough that during deserialization, BuildCheck() is called for every + // streamer info record. + RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap(); + } + + fHasStreamerInfosRegistered = true; +} + //------------------------------------------------------------------------------ bool ROOT::Internal::RWritePageMemoryManager::RColumnInfo::operator>(const RColumnInfo &other) const From 9a0278fb831a5f38e66f2dbb7e59d86afa0c740b Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 02:57:27 +0200 Subject: [PATCH 3/7] [ntuple] add RStreamerField::BeforeConnectPageSource() Register streamer infos of the extra type info when connecting streamer field to page source. --- tree/ntuple/inc/ROOT/RField.hxx | 2 ++ tree/ntuple/src/RFieldMeta.cxx | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index a826a3701dc5b..f878e914634c8 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -238,6 +238,8 @@ protected: // Returns the list of seen streamer infos ROOT::RExtraTypeInfoDescriptor GetExtraTypeInfo() const final; + void BeforeConnectPageSource(ROOT::Internal::RPageSource &pageSource) final; + public: RStreamerField(std::string_view fieldName, std::string_view className, std::string_view typeAlias = ""); RStreamerField(RStreamerField &&other) = default; diff --git a/tree/ntuple/src/RFieldMeta.cxx b/tree/ntuple/src/RFieldMeta.cxx index 1311841fe7c03..461551e7d74b1 100644 --- a/tree/ntuple/src/RFieldMeta.cxx +++ b/tree/ntuple/src/RFieldMeta.cxx @@ -834,6 +834,11 @@ ROOT::RStreamerField::RStreamerField(std::string_view fieldName, TClass *classp) fTraits |= kTraitTriviallyDestructible; } +void ROOT::RStreamerField::BeforeConnectPageSource(ROOT::Internal::RPageSource &pageSource) +{ + pageSource.RegisterStreamerInfos(); +} + std::unique_ptr ROOT::RStreamerField::CloneImpl(std::string_view newName) const { return std::unique_ptr(new RStreamerField(newName, GetTypeName(), GetTypeAlias())); From 2034c23f93bc3327e9a8ebd2149922cc26a9be70 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 02:59:02 +0200 Subject: [PATCH 4/7] [ntuple] test automatic schema evolution for streamer field --- tree/ntuple/test/ntuple_evolution.cxx | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tree/ntuple/test/ntuple_evolution.cxx b/tree/ntuple/test/ntuple_evolution.cxx index e59967e315fbc..e1307b079cfb1 100644 --- a/tree/ntuple/test/ntuple_evolution.cxx +++ b/tree/ntuple/test/ntuple_evolution.cxx @@ -863,3 +863,60 @@ struct RenamedIntermediateDerived : public RenamedIntermediate2 { EXPECT_THAT(err.what(), testing::HasSubstr("incompatible type name for field")); } } + +TEST(RNTupleEvolution, StreamerField) +{ + FileRaii fileGuard("test_ntuple_evolution_streamer_field.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 StreamerField { + int fInt = 1; + int fAnotherInt = 3; + + ClassDefNV(StreamerField, 2) +}; +)")); + + auto model = RNTupleModel::Create(); + model->AddField(std::make_unique("f", "StreamerField")); + + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + writer->Fill(); + + void *ptr = writer->GetModel().GetDefaultEntry().GetPtr("f").get(); + DeclarePointer("StreamerField", "ptrStreamerField", ptr); + ProcessLine("ptrStreamerField->fInt = 2;"); + writer->Fill(); + + // Reset / close the writer and flush the file. + writer.reset(); + }); + + ASSERT_TRUE(gInterpreter->Declare(R"( +struct StreamerField { + int fInt = 0; + int fAdded = 137; + // removed fAnotherInt + + ClassDefNV(StreamerField, 3) +}; +)")); + + auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); + ASSERT_EQ(2, reader->GetNEntries()); + + void *ptr = reader->GetModel().GetDefaultEntry().GetPtr("f").get(); + DeclarePointer("StreamerField", "ptrStreamerField", ptr); + + reader->LoadEntry(0); + EXPECT_EVALUATE_EQ("ptrStreamerField->fInt", 1); + EXPECT_EVALUATE_EQ("ptrStreamerField->fAdded", 137); + + reader->LoadEntry(1); + EXPECT_EVALUATE_EQ("ptrStreamerField->fInt", 2); + EXPECT_EVALUATE_EQ("ptrStreamerField->fAdded", 137); +} From fb3284d9cc530ee56c9e1198a9a91cb6ee24e27b Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 21:49:28 +0200 Subject: [PATCH 5/7] [ntuple] test I/O rules for streamer fields --- tree/ntuple/test/StreamerField.hxx | 10 ++++++++++ tree/ntuple/test/StreamerFieldLinkDef.h | 4 ++++ tree/ntuple/test/rfield_streamer.cxx | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/tree/ntuple/test/StreamerField.hxx b/tree/ntuple/test/StreamerField.hxx index a8c09eaffa086..271d9b98da432 100644 --- a/tree/ntuple/test/StreamerField.hxx +++ b/tree/ntuple/test/StreamerField.hxx @@ -54,4 +54,14 @@ struct PolyContainer { std::unique_ptr fPoly; }; +template +struct OldStreamerName { + T fValue; +}; + +template +struct NewStreamerName { + T fValue; +}; + #endif // ROOT_RNTuple_Test_StreamerField diff --git a/tree/ntuple/test/StreamerFieldLinkDef.h b/tree/ntuple/test/StreamerFieldLinkDef.h index e7d81303ec028..bd04e1512ec23 100644 --- a/tree/ntuple/test/StreamerFieldLinkDef.h +++ b/tree/ntuple/test/StreamerFieldLinkDef.h @@ -12,4 +12,8 @@ #pragma link C++ class PolyB + ; #pragma link C++ options = rntupleStreamerMode(true) class PolyContainer + ; +#pragma link C++ options = rntupleStreamerMode(true), version(3) class OldStreamerName < int> + ; +#pragma link C++ options = rntupleStreamerMode(true), version(3) class NewStreamerName < int> + ; +#pragma read sourceClass = "OldStreamerName" targetClass = "NewStreamerName" version = "[3]" + #endif diff --git a/tree/ntuple/test/rfield_streamer.cxx b/tree/ntuple/test/rfield_streamer.cxx index 57d34982a90c3..0308b54cadcdf 100644 --- a/tree/ntuple/test/rfield_streamer.cxx +++ b/tree/ntuple/test/rfield_streamer.cxx @@ -305,3 +305,25 @@ TEST(RField, StreamerMergeIncremental) EXPECT_TRUE(seenStreamerInfos[2]); EXPECT_TRUE(seenStreamerInfos[3]); } + +TEST(RField, StreamerSchemaEvolution) +{ + FileRaii fileGuard("test_ntuple_rfield_streamer_schema_evolution.root"); + { + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("f", "OldStreamerName").Unwrap()); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + auto ptrF = writer->GetModel().GetDefaultEntry().GetPtr>("f"); + ptrF->fValue = 137; + writer->Fill(); + } + + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("f", "NewStreamerName").Unwrap()); + auto reader = RNTupleReader::Open(std::move(model), "ntpl", fileGuard.GetPath()); + + ASSERT_EQ(1U, reader->GetNEntries()); + auto ptrF = reader->GetModel().GetDefaultEntry().GetPtr>("f"); + reader->LoadEntry(0); + EXPECT_EQ(137, ptrF->fValue); +} From a7f514a934ecd3b960d089cb50d948431a33f590 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 21 Apr 2025 22:01:26 +0200 Subject: [PATCH 6/7] [ntuple] test failure on streamer field type mismatch --- tree/ntuple/test/StreamerField.hxx | 8 +++++++ tree/ntuple/test/StreamerFieldLinkDef.h | 3 +++ tree/ntuple/test/rfield_streamer.cxx | 28 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/tree/ntuple/test/StreamerField.hxx b/tree/ntuple/test/StreamerField.hxx index 271d9b98da432..f25905c5cb9ca 100644 --- a/tree/ntuple/test/StreamerField.hxx +++ b/tree/ntuple/test/StreamerField.hxx @@ -64,4 +64,12 @@ struct NewStreamerName { T fValue; }; +struct TemperatureCelsius { + float fValue; +}; + +struct TemperatureKelvin { + float fValue; +}; + #endif // ROOT_RNTuple_Test_StreamerField diff --git a/tree/ntuple/test/StreamerFieldLinkDef.h b/tree/ntuple/test/StreamerFieldLinkDef.h index bd04e1512ec23..b01191c905ab3 100644 --- a/tree/ntuple/test/StreamerFieldLinkDef.h +++ b/tree/ntuple/test/StreamerFieldLinkDef.h @@ -16,4 +16,7 @@ #pragma link C++ options = rntupleStreamerMode(true), version(3) class NewStreamerName < int> + ; #pragma read sourceClass = "OldStreamerName" targetClass = "NewStreamerName" version = "[3]" +#pragma link C++ options = rntupleStreamerMode(true) class TemperatureCelsius + ; +#pragma link C++ options = rntupleStreamerMode(true) class TemperatureKelvin + ; + #endif diff --git a/tree/ntuple/test/rfield_streamer.cxx b/tree/ntuple/test/rfield_streamer.cxx index 0308b54cadcdf..7ee35bb2b9882 100644 --- a/tree/ntuple/test/rfield_streamer.cxx +++ b/tree/ntuple/test/rfield_streamer.cxx @@ -327,3 +327,31 @@ TEST(RField, StreamerSchemaEvolution) reader->LoadEntry(0); EXPECT_EQ(137, ptrF->fValue); } + +TEST(RField, StreamerClassMismatch) +{ + FileRaii fileGuard("test_ntuple_rfield_streamer_class_mismatch.root"); + { + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("f", "TemperatureCelsius").Unwrap()); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + auto ptrF = writer->GetModel().GetDefaultEntry().GetPtr("f"); + ptrF->fValue = 100.; + writer->Fill(); + } + + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("f", "TemperatureKelvin").Unwrap()); + auto reader = RNTupleReader::Open(std::move(model), "ntpl", fileGuard.GetPath()); + + ASSERT_EQ(1U, reader->GetNEntries()); + auto ptrF = reader->GetModel().GetDefaultEntry().GetPtr("f"); + + // TODO(jblomer): this should fail with an exception when we connect the page source + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.requiredDiag(kError, "TBufferFile::ReadVersion", "Could not find the StreamerInfo with a checksum of", + false /* matchFullMessage */); + diagRAII.requiredDiag(kError, "TBufferFile::CheckByteCount", "object of class TemperatureKelvin read too few bytes", + false /* matchFullMessage */); + reader->LoadEntry(0); +} From 9851b52650fcf1c5eacb0b4bca5806ff780bb9fc Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Wed, 30 Apr 2025 10:14:19 +0200 Subject: [PATCH 7/7] [ntuple] add integration test for streamer field evolution --- .../root/ntuple/streamerfield/CMakeLists.txt | 46 +++++++++++++++++++ .../root/ntuple/streamerfield/Event_v2.hxx | 36 +++++++++++++++ .../ntuple/streamerfield/Event_v2_LinkDef.h | 8 ++++ .../root/ntuple/streamerfield/Event_v3.hxx | 36 +++++++++++++++ .../ntuple/streamerfield/Event_v3_LinkDef.h | 12 +++++ .../root/ntuple/streamerfield/read_event.cxx | 34 ++++++++++++++ .../root/ntuple/streamerfield/write_event.cxx | 18 ++++++++ 7 files changed, 190 insertions(+) create mode 100644 roottest/root/ntuple/streamerfield/CMakeLists.txt create mode 100644 roottest/root/ntuple/streamerfield/Event_v2.hxx create mode 100644 roottest/root/ntuple/streamerfield/Event_v2_LinkDef.h create mode 100644 roottest/root/ntuple/streamerfield/Event_v3.hxx create mode 100644 roottest/root/ntuple/streamerfield/Event_v3_LinkDef.h create mode 100644 roottest/root/ntuple/streamerfield/read_event.cxx create mode 100644 roottest/root/ntuple/streamerfield/write_event.cxx diff --git a/roottest/root/ntuple/streamerfield/CMakeLists.txt b/roottest/root/ntuple/streamerfield/CMakeLists.txt new file mode 100644 index 0000000000000..b23c0f47b844d --- /dev/null +++ b/roottest/root/ntuple/streamerfield/CMakeLists.txt @@ -0,0 +1,46 @@ +ROOTTEST_GENERATE_DICTIONARY( + event_v2_dict + ${CMAKE_CURRENT_SOURCE_DIR}/Event_v2.hxx + LINKDEF ${CMAKE_CURRENT_SOURCE_DIR}/Event_v2_LinkDef.h + NO_ROOTMAP NO_CXXMODULE + FIXTURES_SETUP generated_event_v2_dictionary +) + +ROOTTEST_GENERATE_EXECUTABLE( + write_event + LIBRARIES Core RIO ROOTNTuple + FIXTURES_REQUIRED generated_event_v2_dictionary + FIXTURES_SETUP write_event_excutable) + +target_sources( + write_event + PRIVATE write_event.cxx event_v2_dict.cxx +) + +ROOTTEST_ADD_TEST(write_event + EXEC ./write_event + FIXTURES_REQUIRED write_event_excutable + FIXTURES_SETUP written_event) + +ROOTTEST_GENERATE_DICTIONARY( + event_v3_dict + ${CMAKE_CURRENT_SOURCE_DIR}/Event_v3.hxx + LINKDEF ${CMAKE_CURRENT_SOURCE_DIR}/Event_v3_LinkDef.h + NO_ROOTMAP NO_CXXMODULE + FIXTURES_SETUP generated_event_v3_dictionary +) + +ROOTTEST_GENERATE_EXECUTABLE( + read_event + LIBRARIES Core RIO ROOTNTuple + FIXTURES_REQUIRED generated_event_v3_dictionary + FIXTURES_SETUP read_event_executable) + +target_sources( + read_event + PRIVATE read_event.cxx event_v3_dict.cxx +) + +ROOTTEST_ADD_TEST(read_event + EXEC ./read_event + FIXTURES_REQUIRED read_event_executable written_event) diff --git a/roottest/root/ntuple/streamerfield/Event_v2.hxx b/roottest/root/ntuple/streamerfield/Event_v2.hxx new file mode 100644 index 0000000000000..a83bb08a14427 --- /dev/null +++ b/roottest/root/ntuple/streamerfield/Event_v2.hxx @@ -0,0 +1,36 @@ +#ifndef EVENT_V2_H +#define EVENT_V2_H + +#include + +#include + +struct StreamerBase { + int fBase = 0; + virtual ~StreamerBase() = default; + + ClassDef(StreamerBase, 2) +}; + +struct StreamerDerived : public StreamerBase { + int fFirst = 1; + int fSecond = 2; + virtual ~StreamerDerived() = default; + + ClassDef(StreamerDerived, 2) +}; + +struct StreamerContainer { + std::unique_ptr fPtr; + + ClassDefNV(StreamerContainer, 2) +}; + +struct Event { + int fX = 137; + StreamerContainer fField; + + ClassDefNV(Event, 2) +}; + +#endif // EVENT_V2_H diff --git a/roottest/root/ntuple/streamerfield/Event_v2_LinkDef.h b/roottest/root/ntuple/streamerfield/Event_v2_LinkDef.h new file mode 100644 index 0000000000000..4b408f267a25b --- /dev/null +++ b/roottest/root/ntuple/streamerfield/Event_v2_LinkDef.h @@ -0,0 +1,8 @@ +#ifdef __ROOTCLING__ + +#pragma link C++ class StreamerBase+; +#pragma link C++ class StreamerDerived+; +#pragma link C++ options = rntupleStreamerMode(true) class StreamerContainer+; +#pragma link C++ class Event+; + +#endif diff --git a/roottest/root/ntuple/streamerfield/Event_v3.hxx b/roottest/root/ntuple/streamerfield/Event_v3.hxx new file mode 100644 index 0000000000000..67730d351b728 --- /dev/null +++ b/roottest/root/ntuple/streamerfield/Event_v3.hxx @@ -0,0 +1,36 @@ +#ifndef EVENT_V3_H +#define EVENT_V3_H + +#include + +#include + +struct StreamerBase { + int fBase = 0; + virtual ~StreamerBase() = default; + + ClassDef(StreamerBase, 2) +}; + +struct StreamerDerived : public StreamerBase { + int fSecond = 2; + int fFirst = 1; + virtual ~StreamerDerived() = default; + + ClassDef(StreamerDerived, 3) +}; + +struct StreamerContainer { + std::unique_ptr fPtr; + + ClassDefNV(StreamerContainer, 2) +}; + +struct Event { + StreamerContainer fField; + int fY = 42; + + ClassDefNV(Event, 3) +}; + +#endif // EVENT_V3_H diff --git a/roottest/root/ntuple/streamerfield/Event_v3_LinkDef.h b/roottest/root/ntuple/streamerfield/Event_v3_LinkDef.h new file mode 100644 index 0000000000000..aa249d53b740e --- /dev/null +++ b/roottest/root/ntuple/streamerfield/Event_v3_LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __ROOTCLING__ + +#pragma link C++ class StreamerBase+; +#pragma link C++ class StreamerDerived+; +#pragma link C++ options = rntupleStreamerMode(true) class StreamerContainer+; +#pragma link C++ class Event+; + +#pragma read sourceClass = "StreamerBase" source = "int fBase;" version = "[2]" \ + targetClass = "StreamerBase" target = "fBase" \ + code = "{ fBase = onfile.fBase + 1000; }" + +#endif diff --git a/roottest/root/ntuple/streamerfield/read_event.cxx b/roottest/root/ntuple/streamerfield/read_event.cxx new file mode 100644 index 0000000000000..3f9d49eae2448 --- /dev/null +++ b/roottest/root/ntuple/streamerfield/read_event.cxx @@ -0,0 +1,34 @@ +#include + +#include "Event_v3.hxx" + +#include + +int main() +{ + auto reader = ROOT::RNTupleReader::Open("ntpl", "root_test_streamerfield.root"); + + auto ptrEvent = reader->GetModel().GetDefaultEntry().GetPtr("event"); + + reader->LoadEntry(0); + + if (!dynamic_cast(&reader->GetModel().GetConstField("event.fField"))) + return 10; + + if (!ptrEvent->fField.fPtr) + return 1; + auto derived = dynamic_cast(ptrEvent->fField.fPtr.get()); + if (!derived) + return 2; + if (derived->fBase != 1000) + return 3; + if (derived->fFirst != 1) + return 4; + if (derived->fSecond != 2) + return 5; + if (ptrEvent->fY != 42) + return 6; + + std::remove("root_test_streamerfield.root"); + return 0; +} diff --git a/roottest/root/ntuple/streamerfield/write_event.cxx b/roottest/root/ntuple/streamerfield/write_event.cxx new file mode 100644 index 0000000000000..f9e79d1d57a1d --- /dev/null +++ b/roottest/root/ntuple/streamerfield/write_event.cxx @@ -0,0 +1,18 @@ +#include +#include + +#include "Event_v2.hxx" + +#include + +int main() +{ + auto model = ROOT::RNTupleModel::Create(); + auto field = model->MakeField("event"); + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", "root_test_streamerfield.root"); + + field->fField.fPtr = std::unique_ptr(new StreamerDerived()); + writer->Fill(); + + return 0; +}