From ec52000ab822e95b2f6b34dbfaa2b7bf7cbef23b Mon Sep 17 00:00:00 2001 From: matildamarjamaki Date: Wed, 17 Jun 2026 13:34:37 +0200 Subject: [PATCH 1/6] 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/6] 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/6] 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{ From a9d2dccfeebdff8ce6ecd0e8fc7fe59861fcd87c Mon Sep 17 00:00:00 2001 From: matildamarjamaki Date: Fri, 19 Jun 2026 16:29:42 +0200 Subject: [PATCH 4/6] Use join terminology in RNTupleProcessor test --- tree/ntuple/test/ntuple_processor.cxx | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tree/ntuple/test/ntuple_processor.cxx b/tree/ntuple/test/ntuple_processor.cxx index 42ffc6dc69d10..d38dfb58ee672 100644 --- a/tree/ntuple/test/ntuple_processor.cxx +++ b/tree/ntuple/test/ntuple_processor.cxx @@ -810,10 +810,10 @@ class GH16805ProcessorTest : public testing::Test { "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::vector fJoinFiles{ + "gh16805_rntuple_join_0.root", + "gh16805_rntuple_join_1.root", + "gh16805_rntuple_join_2.root" }; const std::string fStepOneFile = "gh16805_rntuple_stepone.root"; @@ -848,14 +848,14 @@ class GH16805ProcessorTest : public testing::Test { } } - void WriteFriend(const std::string &fileName, int begin, int end) + void WriteJoin(const std::string &fileName, int begin, int end) { auto model = ROOT::RNTupleModel::Create(); - auto br1 = model->MakeField("friendBr1"); - auto br2 = model->MakeField("friendBr2"); + auto br1 = model->MakeField("joinBr1"); + auto br2 = model->MakeField("joinBr2"); - auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "topLevelFriend", fileName); + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "topLevelJoin", fileName); for (int i = begin; i < end; ++i) { *br1 = i; @@ -869,9 +869,9 @@ class GH16805ProcessorTest : public testing::Test { 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); + WriteJoin(fJoinFiles[0], 200, 207); + WriteJoin(fJoinFiles[1], 207, 214); + WriteJoin(fJoinFiles[2], 214, 220); WriteStepOne(fStepOneFile, 100, 120); } @@ -881,7 +881,7 @@ class GH16805ProcessorTest : public testing::Test { for (const auto &f : fStepZeroFiles) std::remove(f.c_str()); - for (const auto &f : fFriendFiles) + for (const auto &f : fJoinFiles) std::remove(f.c_str()); std::remove(fStepOneFile.c_str()); @@ -899,10 +899,10 @@ TEST_F(GH16805ProcessorTest, JoinReading) {"stepzero", fStepZeroFiles[1]} }; - std::vector friendSpecs{ - {"topLevelFriend", fFriendFiles[0]}, - {"topLevelFriend", fFriendFiles[1]}, - {"topLevelFriend", fFriendFiles[2]} + std::vector joinSpecs{ + {"topLevelJoin", fJoinFiles[0]}, + {"topLevelJoin", fJoinFiles[1]}, + {"topLevelJoin", fJoinFiles[2]} }; auto stepOneProc = @@ -911,26 +911,26 @@ TEST_F(GH16805ProcessorTest, JoinReading) auto stepZeroProc = RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); - auto friendProc = - RNTupleProcessor::CreateChain(friendSpecs, "topLevelFriend"); + auto joinProc = + RNTupleProcessor::CreateChain(joinSpecs, "topLevelJoin"); - auto joinedWithFriend = + auto joinedWithJoin = RNTupleProcessor::CreateJoin( std::move(stepOneProc), - std::move(friendProc), + std::move(joinProc), {} ); auto joinedAll = RNTupleProcessor::CreateJoin( - std::move(joinedWithFriend), + std::move(joinedWithJoin), std::move(stepZeroProc), {} ); auto stepOneBr1 = joinedAll->RequestField("stepOneBr1"); - auto friendBr1 = joinedAll->RequestField("topLevelFriend.friendBr1"); - auto friendBr2 = joinedAll->RequestField("topLevelFriend.friendBr2"); + auto joinBr1 = joinedAll->RequestField("topLevelJoin.joinBr1"); + auto joinBr2 = joinedAll->RequestField("topLevelJoin.joinBr2"); auto stepZeroBr1 = joinedAll->RequestField("stepzero.stepZeroBr1"); auto stepZeroBr2 = joinedAll->RequestField("stepzero.stepZeroBr2"); @@ -942,8 +942,8 @@ TEST_F(GH16805ProcessorTest, JoinReading) 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); + EXPECT_EQ(static_cast(200 + i), *joinBr1); + EXPECT_EQ(static_cast(2 * (200 + i)), *joinBr2); ++i; } From d705b37c20204e8a3f297aaaeb0962ef1754a7d1 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 24 Jun 2026 08:05:08 +0000 Subject: [PATCH 5/6] Add GH20033 RNTupleProcessor regression tests --- tree/ntuple/test/ntuple_processor.cxx | 157 ++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/tree/ntuple/test/ntuple_processor.cxx b/tree/ntuple/test/ntuple_processor.cxx index d38dfb58ee672..d2ef0e5bf3f0b 100644 --- a/tree/ntuple/test/ntuple_processor.cxx +++ b/tree/ntuple/test/ntuple_processor.cxx @@ -952,3 +952,160 @@ TEST_F(GH16805ProcessorTest, JoinReading) EXPECT_EQ(20u, joinedAll->GetNEntriesProcessed()); } +// Testiluokka GH20033-regressiotestille +class GH20033ProcessorTest : public testing::Test { +protected: + + // StepZero on ketjutettu kahdesta tiedostosta + const std::array fStepZeroFiles{ + "gh20033_rntuple_stepzero_0.root", + "gh20033_rntuple_stepzero_1.root" + }; + + // Muut analyysivaiheet omissa tiedostoissaan + const std::string fStepOneFile = "gh20033_rntuple_stepone.root"; + const std::string fStepTwoFile = "gh20033_rntuple_steptwo.root"; + const std::string fStepThreeFile = "gh20033_rntuple_stepthree.root"; + const std::string fStepFourFile = "gh20033_rntuple_stepfour.root"; + + // Luo stepZero-RNTuplen ja täyttää sen testidatalla + static void WriteStepZero(const std::string &fileName, int begin, int end) + { + auto model = RNTupleModel::Create(); + + // Kentät, joita testissä luetaan myöhemmin + auto stepZeroBr1 = model->MakeField("stepZeroBr1"); + auto stepZeroBr2 = model->MakeField("stepZeroBr2"); + auto value = model->MakeField("value"); + + auto writer = RNTupleWriter::Recreate(std::move(model), "stepzero", fileName); + + // Kirjoita rivit väliltä [begin,end) + for (int i = begin; i < end; ++i) { + *stepZeroBr1 = i; + *stepZeroBr2 = 2 * i; + *value = i; + writer->Fill(); + } + } + + // Yleinen apufunktio step1-step4 tiedostojen luontiin + static void WriteStepFile(const std::string &fileName, + std::string_view ntupleName, + std::string_view branchName, + int offset) + { + auto model = RNTupleModel::Create(); + + // Vaihekohtainen branch + auto branch = model->MakeField(std::string(branchName)); + + // Sama branch-nimi kaikissa vaiheissa SameBranchName-testiä varten + auto value = model->MakeField("value"); + + auto writer = RNTupleWriter::Recreate(std::move(model), ntupleName, fileName); + + // Luo 20 tapahtumaa + for (int i = 0; i < 20; ++i) { + *branch = offset + i; + *value = offset + i; + writer->Fill(); + } + } + + // Ajetaan ennen jokaista testiä + void SetUp() override + { + // StepZero ketjuna: 0-9 ja 10-19 + WriteStepZero(fStepZeroFiles[0], 0, 10); + WriteStepZero(fStepZeroFiles[1], 10, 20); + + // Muut vaiheet omilla offseteillaan + WriteStepFile(fStepOneFile, "stepone", "stepOneBr1", 100); + WriteStepFile(fStepTwoFile, "steptwo", "stepTwoBr1", 200); + WriteStepFile(fStepThreeFile, "stepthree", "stepThreeBr1", 300); + WriteStepFile(fStepFourFile, "stepfour", "stepFourBr1", 400); + } + + // Siivoa väliaikaiset ROOT-tiedostot testin jälkeen + void TearDown() override + { + ... + } + + // Luo sama join-rakenne kuin GH16805-testissä + std::unique_ptr CreateJoinedProcessor() + { + // Ketjutetaan stepZero:n kaksi tiedostoa yhdeksi prosessoriksi + std::vector stepZeroSpecs{ + {"stepzero", fStepZeroFiles[0]}, + {"stepzero", fStepZeroFiles[1]} + }; + + auto stepZeroProc = RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); + + // Yksittäiset prosessorit muille vaiheille + auto stepOneProc = RNTupleProcessor::Create({"stepone", fStepOneFile}, "stepone"); + auto stepTwoProc = RNTupleProcessor::Create({"steptwo", fStepTwoFile}, "steptwo"); + auto stepThreeProc = RNTupleProcessor::Create({"stepthree", fStepThreeFile}, "stepthree"); + auto stepFourProc = RNTupleProcessor::Create({"stepfour", fStepFourFile}, "stepfour"); + + // Rakennetaan join-puu vaihe vaiheelta + auto joined = RNTupleProcessor::CreateJoin(std::move(stepFourProc), std::move(stepThreeProc), {}); + joined = RNTupleProcessor::CreateJoin(std::move(joined), std::move(stepTwoProc), {}); + joined = RNTupleProcessor::CreateJoin(std::move(joined), std::move(stepOneProc), {}); + joined = RNTupleProcessor::CreateJoin(std::move(joined), std::move(stepZeroProc), {}); + + return joined; + } +}; + +// Tarkistaa että kaikki branchit sisältävät odotetut arvot +TEST_F(GH20033ProcessorTest, Regression) +{ + auto proc = CreateJoinedProcessor(); + + // Pyydä branchit prosessorilta + auto stepFourBr1 = proc->RequestField("stepFourBr1"); + auto stepThreeBr1 = proc->RequestField("stepthree.stepThreeBr1"); + ... + + std::size_t nEntries = 0; + + // Käy kaikki tapahtumat läpi + for (auto idx : *proc) { + + // Tarkista että indeksit etenevät oikein + EXPECT_EQ(nEntries, idx); + + // Tarkista jokaisen vaiheen arvot + EXPECT_EQ(static_cast(400 + idx), *stepFourBr1); + ... + EXPECT_EQ(static_cast(2 * idx), *stepZeroBr2); + + ++nEntries; + } + + // Varmista että kaikki 20 tapahtumaa käsiteltiin + EXPECT_EQ(20u, nEntries); + EXPECT_EQ(20u, proc->GetNEntriesProcessed()); +} + +// Tarkistaa tilanteen jossa kaikissa vaiheissa on branch nimeltä "value" +TEST_F(GH20033ProcessorTest, SameBranchName) +{ + auto proc = CreateJoinedProcessor(); + + // Sama branch-nimi eri prosessoreissa + auto stepFourValue = proc->RequestField("value"); + auto stepThreeValue = proc->RequestField("stepthree.value"); + ... + + // Tarkista että namespace-erottelu toimii oikein + for (auto idx : *proc) { + ... + } + + EXPECT_EQ(20u, nEntries); + EXPECT_EQ(20u, proc->GetNEntriesProcessed()); +} \ No newline at end of file From 265e7ff36eaf34ab1597c0db8e7d26256c4ec6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Marjam=C3=A4ki?= Date: Wed, 24 Jun 2026 16:06:58 +0000 Subject: [PATCH 6/6] Add GH20033 regression test for joined processors --- tree/ntuple/test/ntuple_processor.cxx | 111 ++++++++++++++++---------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/tree/ntuple/test/ntuple_processor.cxx b/tree/ntuple/test/ntuple_processor.cxx index d2ef0e5bf3f0b..56e370da70889 100644 --- a/tree/ntuple/test/ntuple_processor.cxx +++ b/tree/ntuple/test/ntuple_processor.cxx @@ -4,6 +4,12 @@ #include +#include + +#include + +#include + TEST(RNTupleProcessor, EmptyNTuple) { FileRaii fileGuard("test_ntuple_processor_empty.root"); @@ -952,11 +958,11 @@ TEST_F(GH16805ProcessorTest, JoinReading) EXPECT_EQ(20u, joinedAll->GetNEntriesProcessed()); } -// Testiluokka GH20033-regressiotestille -class GH20033ProcessorTest : public testing::Test { +using GH20033ProcessorConfig = std::tuple; + +class GH20033ProcessorTest : public testing::TestWithParam { protected: - // StepZero on ketjutettu kahdesta tiedostosta const std::array fStepZeroFiles{ "gh20033_rntuple_stepzero_0.root", "gh20033_rntuple_stepzero_1.root" @@ -973,14 +979,12 @@ class GH20033ProcessorTest : public testing::Test { { auto model = RNTupleModel::Create(); - // Kentät, joita testissä luetaan myöhemmin auto stepZeroBr1 = model->MakeField("stepZeroBr1"); auto stepZeroBr2 = model->MakeField("stepZeroBr2"); auto value = model->MakeField("value"); auto writer = RNTupleWriter::Recreate(std::move(model), "stepzero", fileName); - // Kirjoita rivit väliltä [begin,end) for (int i = begin; i < end; ++i) { *stepZeroBr1 = i; *stepZeroBr2 = 2 * i; @@ -989,54 +993,63 @@ class GH20033ProcessorTest : public testing::Test { } } - // Yleinen apufunktio step1-step4 tiedostojen luontiin static void WriteStepFile(const std::string &fileName, std::string_view ntupleName, - std::string_view branchName, + std::string_view fieldName, int offset) { auto model = RNTupleModel::Create(); - // Vaihekohtainen branch - auto branch = model->MakeField(std::string(branchName)); + auto field = model->MakeField(std::string(fieldName)); - // Sama branch-nimi kaikissa vaiheissa SameBranchName-testiä varten auto value = model->MakeField("value"); auto writer = RNTupleWriter::Recreate(std::move(model), ntupleName, fileName); - // Luo 20 tapahtumaa for (int i = 0; i < 20; ++i) { - *branch = offset + i; + *field = offset + i; *value = offset + i; writer->Fill(); } } - // Ajetaan ennen jokaista testiä void SetUp() override { - // StepZero ketjuna: 0-9 ja 10-19 + WriteStepZero(fStepZeroFiles[0], 0, 10); WriteStepZero(fStepZeroFiles[1], 10, 20); - // Muut vaiheet omilla offseteillaan WriteStepFile(fStepOneFile, "stepone", "stepOneBr1", 100); WriteStepFile(fStepTwoFile, "steptwo", "stepTwoBr1", 200); WriteStepFile(fStepThreeFile, "stepthree", "stepThreeBr1", 300); WriteStepFile(fStepFourFile, "stepfour", "stepFourBr1", 400); } - // Siivoa väliaikaiset ROOT-tiedostot testin jälkeen void TearDown() override { - ... + for (const auto &fileName : fStepZeroFiles) + std::remove(fileName.c_str()); + + std::remove(fStepOneFile.c_str()); + std::remove(fStepTwoFile.c_str()); + std::remove(fStepThreeFile.c_str()); + std::remove(fStepFourFile.c_str()); + } + + std::unique_ptr CreateStepProcessor(std::string_view ntupleName, + const std::string &fileName, + bool useChain) + { + if (useChain) { + std::vector specs{{std::string(ntupleName), fileName}}; + return RNTupleProcessor::CreateChain(specs, std::string(ntupleName)); + } + + return RNTupleProcessor::Create({std::string(ntupleName), fileName}, std::string(ntupleName)); } - // Luo sama join-rakenne kuin GH16805-testissä std::unique_ptr CreateJoinedProcessor() { - // Ketjutetaan stepZero:n kaksi tiedostoa yhdeksi prosessoriksi std::vector stepZeroSpecs{ {"stepzero", fStepZeroFiles[0]}, {"stepzero", fStepZeroFiles[1]} @@ -1044,13 +1057,13 @@ class GH20033ProcessorTest : public testing::Test { auto stepZeroProc = RNTupleProcessor::CreateChain(stepZeroSpecs, "stepzero"); - // Yksittäiset prosessorit muille vaiheille - auto stepOneProc = RNTupleProcessor::Create({"stepone", fStepOneFile}, "stepone"); - auto stepTwoProc = RNTupleProcessor::Create({"steptwo", fStepTwoFile}, "steptwo"); - auto stepThreeProc = RNTupleProcessor::Create({"stepthree", fStepThreeFile}, "stepthree"); - auto stepFourProc = RNTupleProcessor::Create({"stepfour", fStepFourFile}, "stepfour"); + const auto &[chainStepOne, chainStepTwo, chainStepThree, chainStepFour] = GetParam(); + + auto stepOneProc = CreateStepProcessor("stepone", fStepOneFile, chainStepOne); + auto stepTwoProc = CreateStepProcessor("steptwo", fStepTwoFile, chainStepTwo); + auto stepThreeProc = CreateStepProcessor("stepthree", fStepThreeFile, chainStepThree); + auto stepFourProc = CreateStepProcessor("stepfour", fStepFourFile, chainStepFour); - // Rakennetaan join-puu vaihe vaiheelta auto joined = RNTupleProcessor::CreateJoin(std::move(stepFourProc), std::move(stepThreeProc), {}); joined = RNTupleProcessor::CreateJoin(std::move(joined), std::move(stepTwoProc), {}); joined = RNTupleProcessor::CreateJoin(std::move(joined), std::move(stepOneProc), {}); @@ -1060,52 +1073,66 @@ class GH20033ProcessorTest : public testing::Test { } }; -// Tarkistaa että kaikki branchit sisältävät odotetut arvot -TEST_F(GH20033ProcessorTest, Regression) +TEST_P(GH20033ProcessorTest, Regression) { auto proc = CreateJoinedProcessor(); - // Pyydä branchit prosessorilta auto stepFourBr1 = proc->RequestField("stepFourBr1"); auto stepThreeBr1 = proc->RequestField("stepthree.stepThreeBr1"); - ... + auto stepTwoBr1 = proc->RequestField("steptwo.stepTwoBr1"); + auto stepOneBr1 = proc->RequestField("stepone.stepOneBr1"); + auto stepZeroBr1 = proc->RequestField("stepzero.stepZeroBr1"); + auto stepZeroBr2 = proc->RequestField("stepzero.stepZeroBr2"); std::size_t nEntries = 0; - // Käy kaikki tapahtumat läpi for (auto idx : *proc) { - - // Tarkista että indeksit etenevät oikein EXPECT_EQ(nEntries, idx); - // Tarkista jokaisen vaiheen arvot EXPECT_EQ(static_cast(400 + idx), *stepFourBr1); - ... + EXPECT_EQ(static_cast(300 + idx), *stepThreeBr1); + EXPECT_EQ(static_cast(200 + idx), *stepTwoBr1); + EXPECT_EQ(static_cast(100 + idx), *stepOneBr1); + EXPECT_EQ(static_cast(idx), *stepZeroBr1); EXPECT_EQ(static_cast(2 * idx), *stepZeroBr2); ++nEntries; } - // Varmista että kaikki 20 tapahtumaa käsiteltiin EXPECT_EQ(20u, nEntries); EXPECT_EQ(20u, proc->GetNEntriesProcessed()); } -// Tarkistaa tilanteen jossa kaikissa vaiheissa on branch nimeltä "value" -TEST_F(GH20033ProcessorTest, SameBranchName) +TEST_P(GH20033ProcessorTest, SameFieldName) { auto proc = CreateJoinedProcessor(); - // Sama branch-nimi eri prosessoreissa auto stepFourValue = proc->RequestField("value"); auto stepThreeValue = proc->RequestField("stepthree.value"); - ... + auto stepTwoValue = proc->RequestField("steptwo.value"); + auto stepOneValue = proc->RequestField("stepone.value"); + auto stepZeroValue = proc->RequestField("stepzero.value"); + + std::size_t nEntries = 0; - // Tarkista että namespace-erottelu toimii oikein for (auto idx : *proc) { - ... + EXPECT_EQ(nEntries, idx); + + EXPECT_EQ(static_cast(400 + idx), *stepFourValue); + EXPECT_EQ(static_cast(300 + idx), *stepThreeValue); + EXPECT_EQ(static_cast(200 + idx), *stepTwoValue); + EXPECT_EQ(static_cast(100 + idx), *stepOneValue); + EXPECT_EQ(static_cast(idx), *stepZeroValue); + + ++nEntries; } EXPECT_EQ(20u, nEntries); EXPECT_EQ(20u, proc->GetNEntriesProcessed()); -} \ No newline at end of file +} + +INSTANTIATE_TEST_SUITE_P( + CreateVsCreateChain, + GH20033ProcessorTest, + testing::Combine(testing::Bool(), testing::Bool(), testing::Bool(), testing::Bool()) +);