From ec52000ab822e95b2f6b34dbfaa2b7bf7cbef23b Mon Sep 17 00:00:00 2001 From: matildamarjamaki Date: Wed, 17 Jun 2026 13:34:37 +0200 Subject: [PATCH 1/3] Add GH16805 RNTupleProcessor regression test --- tree/ntuple/test/CMakeLists.txt | 3 + tree/ntuple/test/ntuple_processor_gh16805.cxx | 171 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 tree/ntuple/test/ntuple_processor_gh16805.cxx diff --git a/tree/ntuple/test/CMakeLists.txt b/tree/ntuple/test/CMakeLists.txt index 96b080a35f1ee..6a9f4af7dbc2e 100644 --- a/tree/ntuple/test/CMakeLists.txt +++ b/tree/ntuple/test/CMakeLists.txt @@ -185,4 +185,7 @@ endif() if(pyroot) ROOT_ADD_PYUNITTEST(ntuple_py_basics ntuple_basics.py) ROOT_ADD_PYUNITTEST(ntuple_py_model ntuple_model.py) + endif() + +ROOT_ADD_GTEST(ntuple_processor_gh16805 ntuple_processor_gh16805.cxx LIBRARIES ROOTNTuple) \ No newline at end of file diff --git a/tree/ntuple/test/ntuple_processor_gh16805.cxx b/tree/ntuple/test/ntuple_processor_gh16805.cxx new file mode 100644 index 0000000000000..4b2fc394ed2d7 --- /dev/null +++ b/tree/ntuple/test/ntuple_processor_gh16805.cxx @@ -0,0 +1,171 @@ + +#include "ntuple_test.hxx" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using ROOT::Experimental::RNTupleOpenSpec; +using ROOT::Experimental::RNTupleProcessor; + +class GH16805ProcessorTest : public testing::Test { +protected: + const std::vector fStepZeroFiles{ + "gh16805_rntuple_stepzero_0.root", + "gh16805_rntuple_stepzero_1.root" + }; + + const std::vector fFriendFiles{ + "gh16805_rntuple_friend_0.root", + "gh16805_rntuple_friend_1.root", + "gh16805_rntuple_friend_2.root" + }; + + const std::string fStepOneFile = "gh16805_rntuple_stepone.root"; + + void WriteStepZero(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("stepZeroBr1"); + auto br2 = model->MakeField("stepZeroBr2"); + + auto writer = + ROOT::RNTupleWriter::Recreate(std::move(model), "stepzero", fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + *br2 = 2 * i; + writer->Fill(); + } + } + + void WriteStepOne(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("stepOneBr1"); + + auto writer = + ROOT::RNTupleWriter::Recreate(std::move(model), "stepone", fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + writer->Fill(); + } + } + + void WriteFriend(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("friendBr1"); + auto br2 = model->MakeField("friendBr2"); + + auto writer = + ROOT::RNTupleWriter::Recreate(std::move(model), + "topLevelFriend", + fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + *br2 = 2 * i; + writer->Fill(); + } + } + + void SetUp() override + { + WriteStepZero(fStepZeroFiles[0], 0, 10); + WriteStepZero(fStepZeroFiles[1], 10, 20); + + WriteFriend(fFriendFiles[0], 200, 207); + WriteFriend(fFriendFiles[1], 207, 214); + WriteFriend(fFriendFiles[2], 214, 220); + + WriteStepOne(fStepOneFile, 100, 120); + } + + void TearDown() override + { + for (const auto &f : fStepZeroFiles) + std::remove(f.c_str()); + + for (const auto &f : fFriendFiles) + std::remove(f.c_str()); + + std::remove(fStepOneFile.c_str()); + } + +}; + +TEST_F(GH16805ProcessorTest, JoinReading) +{ + std::vector stepOneSpecs{ + {"stepone", fStepOneFile} + }; + + std::vector stepZeroSpecs{ + {"stepzero", fStepZeroFiles[0]}, + {"stepzero", fStepZeroFiles[1]} + }; + + std::vector friendSpecs{ + {"topLevelFriend", fFriendFiles[0]}, + {"topLevelFriend", fFriendFiles[1]}, + {"topLevelFriend", fFriendFiles[2]} + }; + + auto stepOneProc = + RNTupleProcessor::CreateChain(stepOneSpecs, "stepone"); + + auto stepZeroProc = + RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); + + auto friendProc = + RNTupleProcessor::CreateChain(friendSpecs, "topLevelFriend"); + + auto joinedWithFriend = + RNTupleProcessor::CreateJoin( + std::move(stepOneProc), + std::move(friendProc), + {} + ); + + auto joinedAll = + RNTupleProcessor::CreateJoin( + std::move(joinedWithFriend), + std::move(stepZeroProc), + {} + ); + + auto stepOneBr1 = joinedAll->RequestField("stepOneBr1"); + auto friendBr1 = joinedAll->RequestField("topLevelFriend.friendBr1"); + auto friendBr2 = joinedAll->RequestField("topLevelFriend.friendBr2"); + auto stepZeroBr1 = joinedAll->RequestField("stepzero.stepZeroBr1"); + auto stepZeroBr2 = joinedAll->RequestField("stepzero.stepZeroBr2"); + + std::size_t i = 0; + + for (auto idx : *joinedAll) { + EXPECT_EQ(i, idx); + + EXPECT_EQ(static_cast(i), *stepZeroBr1); + EXPECT_EQ(static_cast(2 * i), *stepZeroBr2); + EXPECT_EQ(static_cast(100 + i), *stepOneBr1); + EXPECT_EQ(static_cast(200 + i), *friendBr1); + EXPECT_EQ(static_cast(2 * (200 + i)), *friendBr2); + + ++i; + } + + EXPECT_EQ(20u, i); + EXPECT_EQ(20u, joinedAll->GetNEntriesProcessed()); +} From f54dc5f85965acf782ff1f7222866053dfac8e98 Mon Sep 17 00:00:00 2001 From: matildamarjamaki Date: Wed, 17 Jun 2026 17:25:33 +0200 Subject: [PATCH 2/3] Move GH16805 test into ntuple_processor.cxx --- tree/ntuple/test/CMakeLists.txt | 2 - tree/ntuple/test/ntuple_processor.cxx | 150 +++++++++++++++ tree/ntuple/test/ntuple_processor_gh16805.cxx | 171 ------------------ 3 files changed, 150 insertions(+), 173 deletions(-) delete mode 100644 tree/ntuple/test/ntuple_processor_gh16805.cxx diff --git a/tree/ntuple/test/CMakeLists.txt b/tree/ntuple/test/CMakeLists.txt index 6a9f4af7dbc2e..d69d67e42f463 100644 --- a/tree/ntuple/test/CMakeLists.txt +++ b/tree/ntuple/test/CMakeLists.txt @@ -187,5 +187,3 @@ if(pyroot) ROOT_ADD_PYUNITTEST(ntuple_py_model ntuple_model.py) endif() - -ROOT_ADD_GTEST(ntuple_processor_gh16805 ntuple_processor_gh16805.cxx LIBRARIES ROOTNTuple) \ No newline at end of file diff --git a/tree/ntuple/test/ntuple_processor.cxx b/tree/ntuple/test/ntuple_processor.cxx index e96c6a6b3f9c6..1c3657cc23209 100644 --- a/tree/ntuple/test/ntuple_processor.cxx +++ b/tree/ntuple/test/ntuple_processor.cxx @@ -798,3 +798,153 @@ TEST_F(RNTupleProcessorTest, PrintStructureJoinedChainAsymmetric) " +-----------------------------+\n"; EXPECT_EQ(exp2, os2.str()); } + +class GH16805ProcessorTest : public testing::Test { +protected: + const std::vector fStepZeroFiles{ + "gh16805_rntuple_stepzero_0.root", + "gh16805_rntuple_stepzero_1.root" + }; + + const std::vector fFriendFiles{ + "gh16805_rntuple_friend_0.root", + "gh16805_rntuple_friend_1.root", + "gh16805_rntuple_friend_2.root" + }; + + const std::string fStepOneFile = "gh16805_rntuple_stepone.root"; + + void WriteStepZero(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("stepZeroBr1"); + auto br2 = model->MakeField("stepZeroBr2"); + + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "stepzero", fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + *br2 = 2 * i; + writer->Fill(); + } + } + + void WriteStepOne(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("stepOneBr1"); + + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "stepone", fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + writer->Fill(); + } + } + + void WriteFriend(const std::string &fileName, int begin, int end) + { + auto model = ROOT::RNTupleModel::Create(); + + auto br1 = model->MakeField("friendBr1"); + auto br2 = model->MakeField("friendBr2"); + + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "topLevelFriend", fileName); + + for (int i = begin; i < end; ++i) { + *br1 = i; + *br2 = 2 * i; + writer->Fill(); + } + } + + void SetUp() override + { + WriteStepZero(fStepZeroFiles[0], 0, 10); + WriteStepZero(fStepZeroFiles[1], 10, 20); + + WriteFriend(fFriendFiles[0], 200, 207); + WriteFriend(fFriendFiles[1], 207, 214); + WriteFriend(fFriendFiles[2], 214, 220); + + WriteStepOne(fStepOneFile, 100, 120); + } + + void TearDown() override + { + for (const auto &f : fStepZeroFiles) + std::remove(f.c_str()); + + for (const auto &f : fFriendFiles) + std::remove(f.c_str()); + + std::remove(fStepOneFile.c_str()); + } +}; + +TEST_F(GH16805ProcessorTest, JoinReading) +{ + std::vector stepOneSpecs{ + {"stepone", fStepOneFile} + }; + + std::vector stepZeroSpecs{ + {"stepzero", fStepZeroFiles[0]}, + {"stepzero", fStepZeroFiles[1]} + }; + + std::vector friendSpecs{ + {"topLevelFriend", fFriendFiles[0]}, + {"topLevelFriend", fFriendFiles[1]}, + {"topLevelFriend", fFriendFiles[2]} + }; + + auto stepOneProc = + RNTupleProcessor::CreateChain(stepOneSpecs, "stepone"); + + auto stepZeroProc = + RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); + + auto friendProc = + RNTupleProcessor::CreateChain(friendSpecs, "topLevelFriend"); + + auto joinedWithFriend = + RNTupleProcessor::CreateJoin( + std::move(stepOneProc), + std::move(friendProc), + {} + ); + + auto joinedAll = + RNTupleProcessor::CreateJoin( + std::move(joinedWithFriend), + std::move(stepZeroProc), + {} + ); + + auto stepOneBr1 = joinedAll->RequestField("stepOneBr1"); + auto friendBr1 = joinedAll->RequestField("topLevelFriend.friendBr1"); + auto friendBr2 = joinedAll->RequestField("topLevelFriend.friendBr2"); + auto stepZeroBr1 = joinedAll->RequestField("stepzero.stepZeroBr1"); + auto stepZeroBr2 = joinedAll->RequestField("stepzero.stepZeroBr2"); + + std::size_t i = 0; + + for (auto idx : *joinedAll) { + EXPECT_EQ(i, idx); + + EXPECT_EQ(static_cast(i), *stepZeroBr1); + EXPECT_EQ(static_cast(2 * i), *stepZeroBr2); + EXPECT_EQ(static_cast(100 + i), *stepOneBr1); + EXPECT_EQ(static_cast(200 + i), *friendBr1); + EXPECT_EQ(static_cast(2 * (200 + i)), *friendBr2); + + ++i; + } + + EXPECT_EQ(20u, i); + EXPECT_EQ(20u, joinedAll->GetNEntriesProcessed()); +} + diff --git a/tree/ntuple/test/ntuple_processor_gh16805.cxx b/tree/ntuple/test/ntuple_processor_gh16805.cxx deleted file mode 100644 index 4b2fc394ed2d7..0000000000000 --- a/tree/ntuple/test/ntuple_processor_gh16805.cxx +++ /dev/null @@ -1,171 +0,0 @@ - -#include "ntuple_test.hxx" - -#include -#include -#include - -#include -#include -#include - -#include -#include - -using ROOT::Experimental::RNTupleOpenSpec; -using ROOT::Experimental::RNTupleProcessor; - -class GH16805ProcessorTest : public testing::Test { -protected: - const std::vector fStepZeroFiles{ - "gh16805_rntuple_stepzero_0.root", - "gh16805_rntuple_stepzero_1.root" - }; - - const std::vector fFriendFiles{ - "gh16805_rntuple_friend_0.root", - "gh16805_rntuple_friend_1.root", - "gh16805_rntuple_friend_2.root" - }; - - const std::string fStepOneFile = "gh16805_rntuple_stepone.root"; - - void WriteStepZero(const std::string &fileName, int begin, int end) - { - auto model = ROOT::RNTupleModel::Create(); - - auto br1 = model->MakeField("stepZeroBr1"); - auto br2 = model->MakeField("stepZeroBr2"); - - auto writer = - ROOT::RNTupleWriter::Recreate(std::move(model), "stepzero", fileName); - - for (int i = begin; i < end; ++i) { - *br1 = i; - *br2 = 2 * i; - writer->Fill(); - } - } - - void WriteStepOne(const std::string &fileName, int begin, int end) - { - auto model = ROOT::RNTupleModel::Create(); - - auto br1 = model->MakeField("stepOneBr1"); - - auto writer = - ROOT::RNTupleWriter::Recreate(std::move(model), "stepone", fileName); - - for (int i = begin; i < end; ++i) { - *br1 = i; - writer->Fill(); - } - } - - void WriteFriend(const std::string &fileName, int begin, int end) - { - auto model = ROOT::RNTupleModel::Create(); - - auto br1 = model->MakeField("friendBr1"); - auto br2 = model->MakeField("friendBr2"); - - auto writer = - ROOT::RNTupleWriter::Recreate(std::move(model), - "topLevelFriend", - fileName); - - for (int i = begin; i < end; ++i) { - *br1 = i; - *br2 = 2 * i; - writer->Fill(); - } - } - - void SetUp() override - { - WriteStepZero(fStepZeroFiles[0], 0, 10); - WriteStepZero(fStepZeroFiles[1], 10, 20); - - WriteFriend(fFriendFiles[0], 200, 207); - WriteFriend(fFriendFiles[1], 207, 214); - WriteFriend(fFriendFiles[2], 214, 220); - - WriteStepOne(fStepOneFile, 100, 120); - } - - void TearDown() override - { - for (const auto &f : fStepZeroFiles) - std::remove(f.c_str()); - - for (const auto &f : fFriendFiles) - std::remove(f.c_str()); - - std::remove(fStepOneFile.c_str()); - } - -}; - -TEST_F(GH16805ProcessorTest, JoinReading) -{ - std::vector stepOneSpecs{ - {"stepone", fStepOneFile} - }; - - std::vector stepZeroSpecs{ - {"stepzero", fStepZeroFiles[0]}, - {"stepzero", fStepZeroFiles[1]} - }; - - std::vector friendSpecs{ - {"topLevelFriend", fFriendFiles[0]}, - {"topLevelFriend", fFriendFiles[1]}, - {"topLevelFriend", fFriendFiles[2]} - }; - - auto stepOneProc = - RNTupleProcessor::CreateChain(stepOneSpecs, "stepone"); - - auto stepZeroProc = - RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); - - auto friendProc = - RNTupleProcessor::CreateChain(friendSpecs, "topLevelFriend"); - - auto joinedWithFriend = - RNTupleProcessor::CreateJoin( - std::move(stepOneProc), - std::move(friendProc), - {} - ); - - auto joinedAll = - RNTupleProcessor::CreateJoin( - std::move(joinedWithFriend), - std::move(stepZeroProc), - {} - ); - - auto stepOneBr1 = joinedAll->RequestField("stepOneBr1"); - auto friendBr1 = joinedAll->RequestField("topLevelFriend.friendBr1"); - auto friendBr2 = joinedAll->RequestField("topLevelFriend.friendBr2"); - auto stepZeroBr1 = joinedAll->RequestField("stepzero.stepZeroBr1"); - auto stepZeroBr2 = joinedAll->RequestField("stepzero.stepZeroBr2"); - - std::size_t i = 0; - - for (auto idx : *joinedAll) { - EXPECT_EQ(i, idx); - - EXPECT_EQ(static_cast(i), *stepZeroBr1); - EXPECT_EQ(static_cast(2 * i), *stepZeroBr2); - EXPECT_EQ(static_cast(100 + i), *stepOneBr1); - EXPECT_EQ(static_cast(200 + i), *friendBr1); - EXPECT_EQ(static_cast(2 * (200 + i)), *friendBr2); - - ++i; - } - - EXPECT_EQ(20u, i); - EXPECT_EQ(20u, joinedAll->GetNEntriesProcessed()); -} From c86bba1956f6f7174da92e4c85e5ad207efcea3c Mon Sep 17 00:00:00 2001 From: matildamarjamaki Date: Thu, 18 Jun 2026 15:51:22 +0200 Subject: [PATCH 3/3] Address review comments --- tree/ntuple/test/CMakeLists.txt | 1 - tree/ntuple/test/ntuple_processor.cxx | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tree/ntuple/test/CMakeLists.txt b/tree/ntuple/test/CMakeLists.txt index d69d67e42f463..96b080a35f1ee 100644 --- a/tree/ntuple/test/CMakeLists.txt +++ b/tree/ntuple/test/CMakeLists.txt @@ -185,5 +185,4 @@ endif() if(pyroot) ROOT_ADD_PYUNITTEST(ntuple_py_basics ntuple_basics.py) ROOT_ADD_PYUNITTEST(ntuple_py_model ntuple_model.py) - endif() diff --git a/tree/ntuple/test/ntuple_processor.cxx b/tree/ntuple/test/ntuple_processor.cxx index 1c3657cc23209..42ffc6dc69d10 100644 --- a/tree/ntuple/test/ntuple_processor.cxx +++ b/tree/ntuple/test/ntuple_processor.cxx @@ -799,6 +799,10 @@ TEST_F(RNTupleProcessorTest, PrintStructureJoinedChainAsymmetric) EXPECT_EQ(exp2, os2.str()); } +// This test is a translation using RNTupleProcessor of the test +// introduced by https://github.com/root-project/root/pull/19322, +// to ensure that the TTree friendship mechanism works equivalently +// with the RNTuple join mechanism. class GH16805ProcessorTest : public testing::Test { protected: const std::vector fStepZeroFiles{