diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index c8eb19871e737..d4590349992f3 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -225,6 +225,8 @@ public: /// For polymorphic classes (that declare or inherit at least one virtual method), return the expected dynamic type /// of any user object. If the class is not polymorphic, return nullptr. const std::type_info *GetPolymorphicTypeInfo() const; + /// Return the TClass instance backing this field. + const TClass *GetClass() const { return fClass; } void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const final; }; @@ -279,6 +281,7 @@ public: size_t GetAlignment() const final; std::uint32_t GetTypeVersion() const final; std::uint32_t GetTypeChecksum() const final; + TClass *GetClass() const { return fClass; } void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const final; }; @@ -316,7 +319,7 @@ template class RField final : public RClassField { public: static std::string TypeName() { return ROOT::Internal::GetRenormalizedTypeName(typeid(T)); } - RField(std::string_view name) : RClassField(name, TypeName()) + RField(std::string_view name) : RClassField(name, Internal::GetDemangledTypeName(typeid(T))) { static_assert(std::is_class_v, "no I/O support for this basic C++ type"); } diff --git a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx index d997874b4ea5b..d988e72656f3a 100644 --- a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx +++ b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx @@ -1762,9 +1762,6 @@ public: /// - Logical columns of piece two /// - ... void ShiftAliasColumns(std::uint32_t offset); - - /// Get the streamer info records for custom classes. Currently requires the corresponding dictionaries to be loaded. - ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t BuildStreamerInfos() const; }; inline RNTupleDescriptor CloneDescriptorSchema(const RNTupleDescriptor &desc) diff --git a/tree/ntuple/inc/ROOT/RPageStorage.hxx b/tree/ntuple/inc/ROOT/RPageStorage.hxx index d1f4e4f99d870..0d3c7e6299d2f 100644 --- a/tree/ntuple/inc/ROOT/RPageStorage.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorage.hxx @@ -447,7 +447,7 @@ private: std::vector fOpenPageRanges; /// Union of the streamer info records that are sent from streamer fields to the sink before committing the dataset. - ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fStreamerInfos; + ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfStreamerFields; protected: /// Set of optional features supported by the persistent sink @@ -521,7 +521,7 @@ public: /// Updates the descriptor and calls InitImpl() that handles the backend-specific details (file, DAOS, etc.) void InitImpl(RNTupleModel &model) final; - void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) final; + void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) override; void UpdateExtraTypeInfo(const ROOT::RExtraTypeInfoDescriptor &extraTypeInfo) final; /// Initialize sink based on an existing descriptor and fill into the descriptor builder, optionally copying over diff --git a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx index 81adeed6b339b..bbef8c11b0efc 100644 --- a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx @@ -65,6 +65,10 @@ private: std::unique_ptr fWriter; /// Number of bytes committed to storage in the current cluster std::uint64_t fNBytesCurrentCluster = 0; + /// On UpdateSchema(), the new class fields register the corresponding streamer info here so that the + /// streamer info records in the file can be properly updated on dataset commit + ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfClassFields; + RPageSinkFile(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options); /// We pass bytesPacked so that TFile::ls() reports a reasonable value for the compression ratio of the corresponding @@ -98,6 +102,8 @@ public: RPageSinkFile(RPageSinkFile &&) = default; RPageSinkFile &operator=(RPageSinkFile &&) = default; ~RPageSinkFile() override; + + void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) final; }; // class RPageSinkFile // clang-format off diff --git a/tree/ntuple/src/RNTupleDescriptor.cxx b/tree/ntuple/src/RNTupleDescriptor.cxx index 464ffbb0d25c8..7cfd9a16761e9 100644 --- a/tree/ntuple/src/RNTupleDescriptor.cxx +++ b/tree/ntuple/src/RNTupleDescriptor.cxx @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -1412,48 +1411,6 @@ ROOT::Internal::RNTupleDescriptorBuilder::AddAttributeSet(Experimental::RNTupleA return RResult::Success(); } -RNTupleSerializer::StreamerInfoMap_t ROOT::Internal::RNTupleDescriptorBuilder::BuildStreamerInfos() const -{ - RNTupleSerializer::StreamerInfoMap_t streamerInfoMap; - const auto &desc = GetDescriptor(); - - std::function fnWalkFieldTree; - fnWalkFieldTree = [&desc, &streamerInfoMap, &fnWalkFieldTree](const RFieldDescriptor &fieldDesc) { - if (fieldDesc.IsCustomClass()) { - // Add streamer info for this class to streamerInfoMap - auto cl = TClass::GetClass(fieldDesc.GetTypeName().c_str()); - if (!cl) { - throw RException(R__FAIL(std::string("cannot get TClass for ") + fieldDesc.GetTypeName())); - } - auto streamerInfo = cl->GetStreamerInfo(fieldDesc.GetTypeVersion()); - if (!streamerInfo) { - throw RException(R__FAIL(std::string("cannot get streamerInfo for ") + fieldDesc.GetTypeName())); - } - streamerInfoMap[streamerInfo->GetNumber()] = streamerInfo; - } - - // Recursively traverse sub fields - for (const auto &subFieldDesc : desc.GetFieldIterable(fieldDesc)) { - fnWalkFieldTree(subFieldDesc); - } - }; - - fnWalkFieldTree(desc.GetFieldZero()); - - // Add the streamer info records from streamer fields: because of runtime polymorphism we may need to add additional - // types not covered by the type names stored in the field headers - for (const auto &extraTypeInfo : desc.GetExtraTypeInfoIterable()) { - if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) - continue; - // Ideally, we would avoid deserializing the streamer info records of the streamer fields that we just serialized. - // However, this happens only once at the end of writing and only when streamer fields are used, so the - // preference here is for code simplicity. - streamerInfoMap.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); - } - - return streamerInfoMap; -} - ROOT::RClusterDescriptor::RColumnRangeIterable ROOT::RClusterDescriptor::GetColumnRangeIterable() const { return RColumnRangeIterable(*this); diff --git a/tree/ntuple/src/RPageStorage.cxx b/tree/ntuple/src/RPageStorage.cxx index 362f903418bb1..c8498fe9c616b 100644 --- a/tree/ntuple/src/RPageStorage.cxx +++ b/tree/ntuple/src/RPageStorage.cxx @@ -927,7 +927,7 @@ void ROOT::Internal::RPagePersistentSink::UpdateExtraTypeInfo(const ROOT::RExtra if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()")); - fStreamerInfos.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); + fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); } void ROOT::Internal::RPagePersistentSink::InitImpl(ROOT::RNTupleModel &model) @@ -1255,7 +1255,7 @@ void ROOT::Internal::RPagePersistentSink::CommitClusterGroup() void ROOT::Internal::RPagePersistentSink::CommitDatasetImpl() { - if (!fStreamerInfos.empty()) { + if (!fInfosOfStreamerFields.empty()) { // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging. for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) { @@ -1265,13 +1265,13 @@ void ROOT::Internal::RPagePersistentSink::CommitDatasetImpl() R__ASSERT(etDesc.GetTypeName().empty()); R__ASSERT(etDesc.GetTypeVersion() == 0); auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap(); - fStreamerInfos.merge(etInfo); + fInfosOfStreamerFields.merge(etInfo); } } RExtraTypeInfoDescriptorBuilder extraInfoBuilder; extraInfoBuilder.ContentId(EExtraTypeInfoIds::kStreamerInfo) - .Content(RNTupleSerializer::SerializeStreamerInfos(fStreamerInfos)); + .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields)); fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap()); } diff --git a/tree/ntuple/src/RPageStorageFile.cxx b/tree/ntuple/src/RPageStorageFile.cxx index 0222d717d8ce8..5bd280d55f787 100644 --- a/tree/ntuple/src/RPageStorageFile.cxx +++ b/tree/ntuple/src/RPageStorageFile.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,37 @@ void ROOT::Internal::RPageSinkFile::InitImpl(unsigned char *serializedHeader, st fWriter->WriteNTupleHeader(zipBuffer.get(), szZipHeader, length); } +void ROOT::Internal::RPageSinkFile::UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, + ROOT::NTupleSize_t firstEntry) +{ + RPagePersistentSink::UpdateSchema(changeset, firstEntry); + + auto fnAddStreamerInfo = [this](const ROOT::RFieldBase *field) { + const TClass *cl = nullptr; + if (auto classField = dynamic_cast(field)) { + cl = classField->GetClass(); + } else if (auto streamerField = dynamic_cast(field)) { + cl = streamerField->GetClass(); + } + if (!cl) + return; + + auto streamerInfo = cl->GetStreamerInfo(field->GetTypeVersion()); + if (!streamerInfo) { + throw RException(R__FAIL(std::string("cannot get streamerInfo for ") + cl->GetName() + " [" + + std::to_string(field->GetTypeVersion()) + "]")); + } + fInfosOfClassFields[streamerInfo->GetNumber()] = streamerInfo; + }; + + for (const auto field : changeset.fAddedFields) { + fnAddStreamerInfo(field); + for (const auto &subField : *field) { + fnAddStreamerInfo(&subField); + } + } +} + inline ROOT::RNTupleLocator ROOT::Internal::RPageSinkFile::WriteSealedPage(const RPageStorage::RSealedPage &sealedPage, std::size_t bytesPacked) { @@ -244,7 +276,18 @@ ROOT::Internal::RPageSinkFile::CommitClusterGroupImpl(unsigned char *serializedP void ROOT::Internal::RPageSinkFile::CommitDatasetImpl(unsigned char *serializedFooter, std::uint32_t length) { - fWriter->UpdateStreamerInfos(fDescriptorBuilder.BuildStreamerInfos()); + // Add the streamer info records from streamer fields: because of runtime polymorphism we may need to add additional + // types not covered by the type names of the class fields + for (const auto &extraTypeInfo : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) { + if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) + continue; + // Ideally, we would avoid deserializing the streamer info records of the streamer fields that we just serialized. + // However, this happens only once at the end of writing and only when streamer fields are used, so the + // preference here is for code simplicity. + fInfosOfClassFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); + } + fWriter->UpdateStreamerInfos(fInfosOfClassFields); + auto bufFooterZip = MakeUninitArray(length); auto szFooterZip = RNTupleCompressor::Zip(serializedFooter, length, GetWriteOptions().GetCompression(), bufFooterZip.get()); diff --git a/tree/ntuple/test/CMakeLists.txt b/tree/ntuple/test/CMakeLists.txt index fbbbb849a26ba..1df3d41dc0ef5 100644 --- a/tree/ntuple/test/CMakeLists.txt +++ b/tree/ntuple/test/CMakeLists.txt @@ -30,11 +30,6 @@ ROOT_GENERATE_DICTIONARY(RXTupleDict ${CMAKE_CURRENT_SOURCE_DIR}/RXTuple.hxx LINKDEF RXTupleLinkDef.h DEPENDENCIES RIO) ROOT_ADD_GTEST(ntuple_descriptor ntuple_descriptor.cxx LIBRARIES ROOTNTuple) -ROOT_GENERATE_DICTIONARY(RNTupleDescriptorDict ${CMAKE_CURRENT_SOURCE_DIR}/RNTupleDescriptorDict.hxx - MODULE ntuple_descriptor - LINKDEF RNTupleDescriptorLinkDef.h - OPTIONS -inlineInputHeader - DEPENDENCIES RIO) ROOT_ADD_GTEST(ntuple_endian ntuple_endian.cxx LIBRARIES ROOTNTuple) ROOT_ADD_GTEST(ntuple_evolution_type ntuple_evolution_type.cxx LIBRARIES ROOTNTuple) diff --git a/tree/ntuple/test/CustomStruct.hxx b/tree/ntuple/test/CustomStruct.hxx index 634ab2b901d38..135fdfae150ce 100644 --- a/tree/ntuple/test/CustomStruct.hxx +++ b/tree/ntuple/test/CustomStruct.hxx @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,12 @@ public: T fMember; }; +class EdmContainer { +public: + // Used to test that the streamer info for fWrapper will use long long + EdmWrapper fWrapper; +}; + template struct EdmHashTrait { using value_type = T; diff --git a/tree/ntuple/test/CustomStructLinkDef.h b/tree/ntuple/test/CustomStructLinkDef.h index d8a6f2224a662..dc7d0f6e7ed65 100644 --- a/tree/ntuple/test/CustomStructLinkDef.h +++ b/tree/ntuple/test/CustomStructLinkDef.h @@ -27,8 +27,13 @@ #pragma link C++ class IOConstructor+; #pragma link C++ class LowPrecisionFloats+; +#pragma link C++ class std::map+ ; +#pragma link C++ class std::map+ ; + #pragma link C++ class EdmWrapper +; #pragma link C++ class EdmHash < 1> + ; +#pragma link C++ class EdmWrapper+; +#pragma link C++ class EdmContainer; #pragma link C++ class DataVector < int, double> + ; #pragma link C++ class DataVector < int, float> + ; diff --git a/tree/ntuple/test/RNTupleDescriptorDict.hxx b/tree/ntuple/test/RNTupleDescriptorDict.hxx deleted file mode 100644 index 5992255dae874..0000000000000 --- a/tree/ntuple/test/RNTupleDescriptorDict.hxx +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef ROOT_RNTuple_Test_RNTupleDescriptorDict -#define ROOT_RNTuple_Test_RNTupleDescriptorDict - -#include "CustomStruct.hxx" - -#include -#include - -#endif // ROOT_RNTuple_Test_RNTupleDescriptorDict diff --git a/tree/ntuple/test/RNTupleDescriptorLinkDef.h b/tree/ntuple/test/RNTupleDescriptorLinkDef.h deleted file mode 100644 index bd8d72f485a35..0000000000000 --- a/tree/ntuple/test/RNTupleDescriptorLinkDef.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifdef __CLING__ - -#pragma link C++ class std::map < int, CustomStruct> + ; -#pragma link C++ class std::map < int, float> + ; -#pragma link C++ class std::unordered_map < int, float> + ; - -#endif diff --git a/tree/ntuple/test/ntuple_descriptor.cxx b/tree/ntuple/test/ntuple_descriptor.cxx index b99e794abe7d8..72590a9b5f30d 100644 --- a/tree/ntuple/test/ntuple_descriptor.cxx +++ b/tree/ntuple/test/ntuple_descriptor.cxx @@ -609,88 +609,6 @@ TEST(RNTupleDescriptor, Clone) EXPECT_EQ(desc, clone); } -TEST(RNTupleDescriptor, BuildStreamerInfos) -{ - auto fnBuildStreamerInfosOf = [](const RFieldBase &field) -> RNTupleSerializer::StreamerInfoMap_t { - RNTupleDescriptorBuilder descBuilder; - descBuilder.SetNTuple("test", ""); - descBuilder.AddField( - RFieldDescriptorBuilder().FieldId(0).Structure(ROOT::ENTupleStructure::kRecord).MakeDescriptor().Unwrap()); - auto fieldBuilder = RFieldDescriptorBuilder::FromField(field); - descBuilder.AddField(fieldBuilder.FieldId(1).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(0, 1); - int i = 2; - // In this test, we only support field hierarchies up to 2 levels - for (const auto &child : field.GetConstSubfields()) { - fieldBuilder = RFieldDescriptorBuilder::FromField(*child); - descBuilder.AddField(fieldBuilder.FieldId(i).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(1, i); - const auto childId = i; - i++; - for (const auto &grandChild : child->GetConstSubfields()) { - fieldBuilder = RFieldDescriptorBuilder::FromField(*grandChild); - descBuilder.AddField(fieldBuilder.FieldId(i).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(childId, i); - i++; - } - } - return descBuilder.BuildStreamerInfos(); - }; - - RNTupleSerializer::StreamerInfoMap_t streamerInfoMap; - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "float").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::vector").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::pair").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::map").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::unordered_map").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - std::vector> itemFields; - streamerInfoMap = fnBuildStreamerInfosOf(ROOT::RRecordField("f", std::move(itemFields))); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "CustomStruct").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::vector").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::map").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "DerivedA").Unwrap()); - EXPECT_EQ(2u, streamerInfoMap.size()); - std::vector typeNames; - for (const auto &[_, si] : streamerInfoMap) { - typeNames.emplace_back(si->GetName()); - } - std::sort(typeNames.begin(), typeNames.end()); - EXPECT_STREQ("CustomStruct", typeNames[0].c_str()); - EXPECT_STREQ("DerivedA", typeNames[1].c_str()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::pair").Unwrap()); - EXPECT_EQ(2u, streamerInfoMap.size()); - typeNames.clear(); - for (const auto &[_, si] : streamerInfoMap) { - typeNames.emplace_back(si->GetName()); - } - std::sort(typeNames.begin(), typeNames.end()); - EXPECT_STREQ("CustomStruct", typeNames[0].c_str()); - EXPECT_STREQ("DerivedA", typeNames[1].c_str()); -} - TEST(RNTupleDescriptor, CloneSchema) { FileRaii fileGuard("test_ntuple_desc_cloneschema.root"); diff --git a/tree/ntuple/test/ntuple_emulated.cxx b/tree/ntuple/test/ntuple_emulated.cxx index 893cca7bd3b67..adee6f6cad1b9 100644 --- a/tree/ntuple/test/ntuple_emulated.cxx +++ b/tree/ntuple/test/ntuple_emulated.cxx @@ -32,6 +32,12 @@ TEST(RNTupleEmulated, EmulatedFields_Simple) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "Outer_Simple").Unwrap()); + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -42,13 +48,6 @@ TEST(RNTupleEmulated, EmulatedFields_Simple) ProcessLine("ptrOuter->fInner.fInt2 = 82;"); ProcessLine("ptrOuter->fInt1 = 93;"); 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()); @@ -145,6 +144,12 @@ TEST(RNTupleEmulated, EmulatedFields_Vecs) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("outers", "std::vector").Unwrap()); + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -155,13 +160,6 @@ TEST(RNTupleEmulated, EmulatedFields_Vecs) ProcessLine("(*ptrOuters)[0].fInners.push_back(Inner_Vecs{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()); @@ -318,15 +316,14 @@ TEST(RNTupleEmulated, EmulatedFields_EmptyStruct) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "Outer_EmptyStruct").Unwrap()); - auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); - 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 writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + writer->Fill(); }); auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); diff --git a/tree/ntuple/test/ntuple_evolution_shape.cxx b/tree/ntuple/test/ntuple_evolution_shape.cxx index 6e8bb76b85054..4798893d8df64 100644 --- a/tree/ntuple/test/ntuple_evolution_shape.cxx +++ b/tree/ntuple/test/ntuple_evolution_shape.cxx @@ -150,6 +150,12 @@ struct AddedMemberObject { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedMemberObject").Unwrap()); + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -158,16 +164,6 @@ struct AddedMemberObject { ProcessLine("ptrAddedMemberObject->fMember1.fInt = 71;"); ProcessLine("ptrAddedMemberObject->fMember3.fInt = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -396,18 +392,14 @@ struct RenamedMemberClass { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedMemberClass").Unwrap()); + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -491,6 +483,12 @@ struct AddedBaseDerived : public AddedBaseIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -499,16 +497,6 @@ struct AddedBaseDerived : public AddedBaseIntermediate { ProcessLine("ptrAddedBaseDerived->fIntermediate = 82;"); ProcessLine("ptrAddedBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -560,6 +548,12 @@ struct AddedSecondBaseDerived : public AddedSecondBaseFirst { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -568,16 +562,6 @@ struct AddedSecondBaseDerived : public AddedSecondBaseFirst { ProcessLine("ptrAddedSecondBaseDerived->fFirstBase = 71;"); ProcessLine("ptrAddedSecondBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -623,18 +607,14 @@ struct PrependSecondBaseDerived : public PrependSecondBaseFirst { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "PrependSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -678,18 +658,14 @@ struct AddedIntermediateDerived : public AddedIntermediateBase { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -736,6 +712,12 @@ struct RemovedBaseDerived : public RemovedBaseIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -745,16 +727,6 @@ struct RemovedBaseDerived : public RemovedBaseIntermediate { ProcessLine("ptrRemovedBaseDerived->fIntermediate = 82;"); ProcessLine("ptrRemovedBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -804,18 +776,14 @@ struct RemovedIntermediateDerived : public RemovedIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -859,18 +827,14 @@ struct RemovedSecondBaseDerived : public RemovedSecondBaseFirst, public RemovedS auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -912,18 +876,14 @@ struct RenamedBaseDerived : public RenamedBase1 { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -968,18 +928,14 @@ struct RenamedIntermediateDerived : public RenamedIntermediate1 { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // 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); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // 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(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( diff --git a/tree/ntuple/test/rfield_class.cxx b/tree/ntuple/test/rfield_class.cxx index 05c53e52d8909..cdb13f62b273d 100644 --- a/tree/ntuple/test/rfield_class.cxx +++ b/tree/ntuple/test/rfield_class.cxx @@ -4,9 +4,14 @@ #include #include #include +#include #include #include +#include +#include +#include +#include namespace { class RNoDictionary {}; @@ -371,3 +376,65 @@ TEST(RNTuple, PolymorphicPointer) EXPECT_THROW(writer->Fill(), ROOT::RException); } } + +TEST(RNTuple, TClassMetaName) +{ + auto f1 = ROOT::RClassField("f", "EdmWrapper"); + EXPECT_STREQ("EdmWrapper", f1.GetClass()->GetName()); + + auto f2 = std::make_unique>>("f"); + EXPECT_STREQ("EdmWrapper", static_cast(f2.get())->GetClass()->GetName()); + + auto f3 = RFieldBase::Create("f", "EdmWrapper").Unwrap(); + EXPECT_STREQ("EdmWrapper", static_cast(f3.get())->GetClass()->GetName()); + + auto f4 = RFieldBase::Create("f", "EdmContainer").Unwrap(); + EXPECT_STREQ("EdmWrapper", + static_cast(f4->GetConstSubfields()[0])->GetClass()->GetName()); +} + +TEST(RNTuple, StreamerInfoRecords) +{ + // Every testee consists of the type stored on disk and the expected streamer info records + std::vector>> testees{ + {"float", {}}, + {"std::vector", {}}, + {"std::pair", {}}, + {"std::map", {}}, + {"CustomStruct", {"CustomStruct"}}, + {"std::vector", {"CustomStruct"}}, + {"std::map", {"CustomStruct"}}, + {"DerivedA", {"DerivedA", "CustomStruct"}}, + {"std::pair", {"DerivedA", "CustomStruct"}}, + {"EdmWrapper", {"EdmWrapper"}}, + {"TRotation", {"TRotation"}}}; + + for (const auto &t : testees) { + FileRaii fileGuard("test_ntuple_streamer_info_records.root"); + + { + auto model = ROOT::RNTupleModel::Create(); + if (t.first == "TRotation") { + model->AddField(std::make_unique("f", t.first)); + } else { + model->AddField(ROOT::RFieldBase::Create("f", t.first).Unwrap()); + } + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + } + + auto f = std::unique_ptr(TFile::Open(fileGuard.GetPath().c_str())); + ASSERT_TRUE(f && !f->IsZombie()); + + std::unordered_set expectedInfos{t.second.begin(), t.second.end()}; + expectedInfos.insert("ROOT::RNTuple"); + for (const auto info : TRangeDynCast(*f->GetStreamerInfoList())) { + auto itr = expectedInfos.find(info->GetName()); + if (itr == expectedInfos.end()) { + FAIL() << "unexpected streamer info: " << info->GetName(); + continue; + } + expectedInfos.erase(itr); + } + EXPECT_TRUE(expectedInfos.empty()); + } +}