diff --git a/tree/tree/inc/TTree.h b/tree/tree/inc/TTree.h index 006b7b9fc7f3b..e0dc1427bfed9 100644 --- a/tree/tree/inc/TTree.h +++ b/tree/tree/inc/TTree.h @@ -218,6 +218,12 @@ class TTree : public TNamed, public TAttLine, public TAttFill, public TAttMarker virtual Int_t SetBranchAddress(const char *bname, void *add, TBranch **ptr, TClass *realClass, EDataType datatype, bool isptr, bool suppressMissingBranchError); + TBranch *FindBranchFromSelf(const char *branchName); + TBranch *FindBranchFromFriends(const char *branchName); + + TLeaf *GetLeafFromSelf(const char *branchName, const char *leafName); + TLeaf *GetLeafFromFriends(const char *branchName, const char *leafName); + class TFriendLock { // Helper class to prevent infinite recursion in the // usage of TTree Friends. Implemented in TTree.cxx. diff --git a/tree/tree/src/TChain.cxx b/tree/tree/src/TChain.cxx index 88da9fcc421c6..0bad1647000d3 100644 --- a/tree/tree/src/TChain.cxx +++ b/tree/tree/src/TChain.cxx @@ -1139,34 +1139,59 @@ TObjArray* TChain::GetListOfLeaves() Double_t TChain::GetMaximum(const char* columname) { - Double_t cmax = -DBL_MAX; - TLeaf *leaf = nullptr; - TBranch *branch = nullptr; - Int_t treenumber = -1; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) - break; - Long64_t localEntryNumber = this->LoadTree(entryNumber); - if (localEntryNumber < 0) - break; - if (treenumber != this->GetTreeNumber()) { - leaf = this->GetLeaf(columname); - if (leaf) - branch = leaf->GetBranch(); - } - treenumber = this->GetTreeNumber(); - if (!branch) - continue; - branch->GetEntry(localEntryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val > cmax) { - cmax = val; + constexpr auto errVal{std::numeric_limits::lowest()}; + + // If the requested column is in this TTree, compute the maximum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + TBranch *branch = nullptr; + Int_t treenumber = -1; + double cmax{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + Long64_t localEntryNumber = this->LoadTree(entryNumber); + if (localEntryNumber < 0) + break; + if (treenumber != this->GetTreeNumber()) { + leaf = GetLeafFromSelf(nullptr, columname); + if (leaf) + branch = leaf->GetBranch(); + } + if (!branch) + continue; + treenumber = this->GetTreeNumber(); + branch->GetEntry(localEntryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val > cmax) { + cmax = val; + } } } + return cmax; } - return cmax; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMaximum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// @@ -1174,34 +1199,59 @@ Double_t TChain::GetMaximum(const char* columname) Double_t TChain::GetMinimum(const char* columname) { - Double_t cmin = DBL_MAX; - TLeaf *leaf = nullptr; - TBranch *branch = nullptr; - Int_t treenumber = -1; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) - break; - Long64_t localEntryNumber = this->LoadTree(entryNumber); - if (localEntryNumber < 0) - break; - if (treenumber != this->GetTreeNumber()) { - leaf = this->GetLeaf(columname); - if (leaf) - branch = leaf->GetBranch(); - } - treenumber = this->GetTreeNumber(); - if (!branch) - continue; - branch->GetEntry(localEntryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val < cmin) { - cmin = val; + constexpr auto errVal{std::numeric_limits::max()}; + + // If the requested column is in this TTree, compute the minimum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + TBranch *branch = nullptr; + Int_t treenumber = -1; + double cmin{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + Long64_t localEntryNumber = this->LoadTree(entryNumber); + if (localEntryNumber < 0) + break; + if (treenumber != this->GetTreeNumber()) { + leaf = GetLeafFromSelf(nullptr, columname); + if (leaf) + branch = leaf->GetBranch(); + } + if (!branch) + continue; + treenumber = this->GetTreeNumber(); + branch->GetEntry(localEntryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val < cmin) { + cmin = val; + } } } + return cmin; } - return cmin; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMinimum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// diff --git a/tree/tree/src/TTree.cxx b/tree/tree/src/TTree.cxx index 8023b9b939bf3..a0f4e1a70f960 100644 --- a/tree/tree/src/TTree.cxx +++ b/tree/tree/src/TTree.cxx @@ -4882,64 +4882,47 @@ static TBranch *R__FindBranchHelper(TObjArray *list, const char *branchname) { return nullptr; } -//////////////////////////////////////////////////////////////////////////////// -/// Return the branch that correspond to the path 'branchname', which can -/// include the name of the tree or the omitted name of the parent branches. -/// In case of ambiguity, returns the first match. -/// \sa TTree::GetBranch - -TBranch* TTree::FindBranch(const char* branchname) +TBranch *TTree::FindBranchFromSelf(const char *branchName) { - // We already have been visited while recursively looking - // through the friends tree, let return - if (kFindBranch & fFriendLockStatus) { - return nullptr; - } - - if (!branchname) - return nullptr; - - TBranch* branch = nullptr; // If the first part of the name match the TTree name, look for the right part in the // list of branches. - // This will allow the branchname to be preceded by + // This will allow the branchName to be preceded by // the name of this tree. - if (strncmp(fName.Data(),branchname,fName.Length())==0 && branchname[fName.Length()]=='.') { - branch = R__FindBranchHelper( GetListOfBranches(), branchname + fName.Length() + 1); - if (branch) return branch; - } + if (strncmp(fName.Data(), branchName, fName.Length()) == 0 && branchName[fName.Length()] == '.') + if (auto *br = R__FindBranchHelper(GetListOfBranches(), branchName + fName.Length() + 1)) + return br; + // If we did not find it, let's try to find the full name in the list of branches. - branch = R__FindBranchHelper(GetListOfBranches(), branchname); - if (branch) return branch; + if (auto *br = R__FindBranchHelper(GetListOfBranches(), branchName)) + return br; - // If we still did not find, let's try to find it within each branch assuming it does not the branch name. - TIter next(GetListOfBranches()); - while ((branch = (TBranch*) next())) { - TBranch* nestedbranch = branch->FindBranch(branchname); - if (nestedbranch) { + // If we still did not find, let's try to find it within each branch assuming it does not contain the branch name. + for (auto *branch : ROOT::Detail::TRangeStaticCast(*GetListOfBranches())) + if (auto *nestedbranch = branch->FindBranch(branchName)) return nestedbranch; - } - } - // Search in list of friends. + return nullptr; +} + +TBranch *TTree::FindBranchFromFriends(const char *branchName) +{ if (!fFriends) { return nullptr; } + TFriendLock lock(this, kFindBranch); - TIter nextf(fFriends); - TFriendElement* fe = nullptr; - while ((fe = (TFriendElement*) nextf())) { - TTree* t = fe->GetTree(); + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) { + TTree *t = frEl->GetTree(); if (!t) { continue; } // If the alias is present replace it with the real name. - const char *subbranch = strstr(branchname, fe->GetName()); - if (subbranch != branchname) { + const char *subbranch = strstr(branchName, frEl->GetName()); + if (subbranch != branchName) { subbranch = nullptr; } if (subbranch) { - subbranch += strlen(fe->GetName()); + subbranch += strlen(frEl->GetName()); if (*subbranch != '.') { subbranch = nullptr; } else { @@ -4950,13 +4933,38 @@ TBranch* TTree::FindBranch(const char* branchname) if (subbranch) { name << t->GetName() << "." << subbranch; } else { - name << branchname; - } - branch = t->FindBranch(name.str().c_str()); - if (branch) { - return branch; + name << branchName; } + if (auto *br = t->FindBranch(name.str().c_str())) + return br; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return the branch that correspond to the path 'branchname', which can +/// include the name of the tree or the omitted name of the parent branches. +/// In case of ambiguity, returns the first match. +/// \sa TTree::GetBranch + +TBranch *TTree::FindBranch(const char *branchname) +{ + // We already have been visited while recursively looking + // through the friends tree, let return + if (kFindBranch & fFriendLockStatus) { + return nullptr; } + + if (!branchname) + return nullptr; + + if (auto *br = FindBranchFromSelf(branchname)) + return br; + + if (auto *br = FindBranchFromFriends(branchname)) + return br; + return nullptr; } @@ -6196,51 +6204,35 @@ TIterator* TTree::GetIteratorOnAllLeaves(bool dir) return new TTreeFriendLeafIter(this, dir); } -//////////////////////////////////////////////////////////////////////////////// -/// Return pointer to the 1st Leaf named name in any Branch of this -/// Tree or any branch in the list of friend trees. -/// -/// The leaf name can contain the name of a friend tree with the -/// syntax: friend_dir_and_tree.full_leaf_name -/// the friend_dir_and_tree can be of the form: -/// ~~~ {.cpp} -/// TDirectoryName/TreeName -/// ~~~ - -TLeaf* TTree::GetLeafImpl(const char* branchname, const char *leafname) +TLeaf *TTree::GetLeafFromSelf(const char *branchName, const char *leafName) { - TLeaf *leaf = nullptr; - if (branchname) { - TBranch *branch = FindBranch(branchname); - if (branch) { - leaf = branch->GetLeaf(leafname); - if (leaf) { + if (branchName) + if (auto *br = FindBranchFromSelf(branchName)) + if (auto *leaf = br->GetLeaf(leafName)) return leaf; - } - } - } - TIter nextl(GetListOfLeaves()); - while ((leaf = (TLeaf*)nextl())) { - if (strcmp(leaf->GetFullName(), leafname) != 0 && strcmp(leaf->GetName(), leafname) != 0) - continue; // leafname does not match GetName() nor GetFullName(), this is not the right leaf - if (branchname) { - // check the branchname is also a match + + for (auto *leaf : ROOT::Detail::TRangeStaticCast(*GetListOfLeaves())) { + if (strcmp(leaf->GetFullName(), leafName) != 0 && strcmp(leaf->GetName(), leafName) != 0) + continue; // leafName does not match GetName() nor GetFullName(), this is not the right leaf + if (branchName) { + // check the branchName is also a match TBranch *br = leaf->GetBranch(); // if a quick comparison with the branch full name is a match, we are done - if (!strcmp(br->GetFullName(), branchname)) + if (!strcmp(br->GetFullName(), branchName)) return leaf; - UInt_t nbch = strlen(branchname); + UInt_t nbch = strlen(branchName); const char* brname = br->GetName(); TBranch *mother = br->GetMother(); - if (strncmp(brname,branchname,nbch)) { + if (strncmp(brname, branchName, nbch)) { if (mother != br) { const char *mothername = mother->GetName(); UInt_t motherlen = strlen(mothername); - if (!strcmp(mothername, branchname)) { + if (!strcmp(mothername, branchName)) { return leaf; - } else if (nbch > motherlen && strncmp(mothername,branchname,motherlen)==0 && (mothername[motherlen-1]=='.' || branchname[motherlen]=='.')) { + } else if (nbch > motherlen && strncmp(mothername, branchName, motherlen) == 0 && + (mothername[motherlen - 1] == '.' || branchName[motherlen] == '.')) { // The left part of the requested name match the name of the mother, let's see if the right part match the name of the branch. - if (strncmp(brname,branchname+motherlen+1,nbch-motherlen-1)) { + if (strncmp(brname, branchName + motherlen + 1, nbch - motherlen - 1)) { // No it does not continue; } // else we have match so we can proceed. @@ -6262,35 +6254,65 @@ TLeaf* TTree::GetLeafImpl(const char* branchname, const char *leafname) } return leaf; } + + return nullptr; +} + +TLeaf *TTree::GetLeafFromFriends(const char *branchName, const char *leafName) +{ + if (branchName) + if (auto *br = FindBranchFromFriends(branchName)) + if (auto *leaf = br->GetLeaf(leafName)) + return leaf; + if (!fFriends) return nullptr; - TFriendLock lock(this,kGetLeaf); - TIter next(fFriends); - TFriendElement *fe; - while ((fe = (TFriendElement*)next())) { - TTree *t = fe->GetTree(); - if (t) { - leaf = t->GetLeaf(branchname, leafname); - if (leaf) return leaf; - } - } + TFriendLock lock(this, kGetLeaf); + + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) + if (auto *t = frEl->GetTree()) + if (auto *leaf = t->GetLeaf(branchName, leafName)) + return leaf; - //second pass in the list of friends when the leaf name - //is prefixed by the tree name + // Second pass in the list of friends when the leaf name is prefixed by the tree name TString strippedArg; - next.Reset(); - while ((fe = (TFriendElement*)next())) { - TTree *t = fe->GetTree(); + for (auto *frEl : ROOT::Detail::TRangeStaticCast(*fFriends)) { + TTree *t = frEl->GetTree(); if (!t) continue; - const char *subname = strstr(leafname,fe->GetName()); - if (subname != leafname) continue; - Int_t l = strlen(fe->GetName()); - subname += l; - if (*subname != '.') continue; - subname++; - strippedArg += subname; - leaf = t->GetLeaf(branchname,subname); - if (leaf) return leaf; + const char *subLeafName = strstr(leafName, frEl->GetName()); + if (subLeafName != leafName) + continue; + Int_t l = strlen(frEl->GetName()); + subLeafName += l; + if (*subLeafName != '.') + continue; + subLeafName++; + strippedArg += subLeafName; + if (auto *leaf = t->GetLeaf(branchName, subLeafName)) + return leaf; } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return pointer to the 1st Leaf named name in any Branch of this +/// Tree or any branch in the list of friend trees. +/// +/// The leaf name can contain the name of a friend tree with the +/// syntax: friend_dir_and_tree.full_leaf_name +/// the friend_dir_and_tree can be of the form: +/// ~~~ {.cpp} +/// TDirectoryName/TreeName +/// ~~~ + +TLeaf *TTree::GetLeafImpl(const char *branchname, const char *leafname) +{ + if (auto *leaf = GetLeafFromSelf(branchname, leafname)) + return leaf; + + if (auto *leaf = GetLeafFromFriends(branchname, leafname)) + return leaf; + return nullptr; } @@ -6344,31 +6366,56 @@ TLeaf* TTree::GetLeaf(const char *name) /// if the Tree has an associated TEventList or TEntryList, the maximum /// is computed for the entries in this list. -Double_t TTree::GetMaximum(const char* columname) +Double_t TTree::GetMaximum(const char *columname) { - TLeaf* leaf = this->GetLeaf(columname); - if (!leaf) { - return 0; - } + constexpr auto errVal{std::numeric_limits::lowest()}; + if (!columname || strcmp(columname, "") == 0) + return errVal; - // create cache if wanted - if (fCacheDoAutoInit) - SetCacheSizeAux(); + // If the requested column is in this TTree, compute the maximum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { - TBranch* branch = leaf->GetBranch(); - Double_t cmax = -DBL_MAX; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) break; - branch->GetEntry(entryNumber); - for (Int_t j = 0; j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val > cmax) { - cmax = val; + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; + + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + // create cache if wanted + if (fCacheDoAutoInit) + SetCacheSizeAux(); + + TBranch *branch = leaf->GetBranch(); + Double_t cmax{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + branch->GetEntry(entryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val > cmax) { + cmax = val; + } } } + return cmax; } - return cmax; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMaximum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// @@ -6386,29 +6433,54 @@ Long64_t TTree::GetMaxTreeSize() Double_t TTree::GetMinimum(const char* columname) { - TLeaf* leaf = this->GetLeaf(columname); - if (!leaf) { - return 0; - } + constexpr auto errVal{std::numeric_limits::max()}; + if (!columname || strcmp(columname, "") == 0) + return errVal; - // create cache if wanted - if (fCacheDoAutoInit) - SetCacheSizeAux(); + // If the requested column is in this TTree, compute the minimum value directly + if (auto *leaf = GetLeafFromSelf(nullptr, columname)) { + + // Ensure the TTree cursor is brought back to the current entry after computing the value + struct CurrentEntryRAII { + + Long64_t fCurrentEntry; + TTree &fTree; - TBranch* branch = leaf->GetBranch(); - Double_t cmin = DBL_MAX; - for (Long64_t i = 0; i < fEntries; ++i) { - Long64_t entryNumber = this->GetEntryNumber(i); - if (entryNumber < 0) break; - branch->GetEntry(entryNumber); - for (Int_t j = 0;j < leaf->GetLen(); ++j) { - Double_t val = leaf->GetValue(j); - if (val < cmin) { - cmin = val; + CurrentEntryRAII(TTree &tree) : fCurrentEntry(tree.GetReadEntry()), fTree(tree) {} + + ~CurrentEntryRAII() { fTree.GetEntry(fCurrentEntry); } + } raii{*this}; + + // create cache if wanted + if (fCacheDoAutoInit) + SetCacheSizeAux(); + + TBranch *branch = leaf->GetBranch(); + Double_t cmin{errVal}; + for (Long64_t i = 0; i < fEntries; ++i) { + Long64_t entryNumber = this->GetEntryNumber(i); + if (entryNumber < 0) + break; + branch->GetEntry(entryNumber); + for (Int_t j = 0; j < leaf->GetLen(); ++j) { + Double_t val = leaf->GetValue(j); + if (val < cmin) { + cmin = val; + } } } + return cmin; } - return cmin; + + // If there are any friends, look for the requested column name. If it is + // found in a friend, dispatch the calculation to the friend itself. + if (fFriends) + for (auto *frEl : TRangeDynCast(fFriends)) + if (auto *tree = frEl->GetTree()) + if (tree->GetLeaf(columname)) + return tree->GetMinimum(columname); + + return errVal; } //////////////////////////////////////////////////////////////////////////////// diff --git a/tree/tree/test/TTreeRegressions.cxx b/tree/tree/test/TTreeRegressions.cxx index 5aba6fe4cddf8..46dceb194c568 100644 --- a/tree/tree/test/TTreeRegressions.cxx +++ b/tree/tree/test/TTreeRegressions.cxx @@ -1,5 +1,6 @@ #include "TMemFile.h" #include "TLeaf.h" +#include "TChain.h" #include "TTree.h" #include "TInterpreter.h" #include "TSystem.h" @@ -9,7 +10,9 @@ #include "gtest/gtest.h" +#include #include +#include // ROOT-10702 TEST(TTreeRegressions, CompositeTypeWithNameClash) @@ -322,3 +325,148 @@ TEST(TTreeRegressions, DrawAutoBinning) delete h; delete gROOT->FindObject("c1"); } + +// Regression for https://github.com/root-project/root/issues/22652 +struct RegressionGH22652 : public ::testing::Test { + + constexpr static auto fMainTreeName{"main"}; + constexpr static auto fFriendTreeName{"friend"}; + constexpr static auto fMainTreeFileName{"main_global.root"}; + constexpr static auto fNFiles{3}; + constexpr static auto fNEntriesPerFile{100}; + // file 0: w in [4,5) file 1: w in [0,1) file 2: w in [9,10) + constexpr static std::array fLowerBounds{4.0, 0.0, 9.0}; + constexpr static std::array fMainChainFileNames{"main_1.root", "main_2.root", "main_3.root"}; + constexpr static std::array fFriendChainFileNames{"friend_1.root", "friend_2.root", + "friend_3.root"}; + + constexpr static auto fNShortFiles{6}; + constexpr static auto fNEntriesPerShortFile{50}; + constexpr static std::array fShortFriendChainFileNames{ + "short_friend_1.root", "short_friend_2.root", "short_friend_3.root", + "short_friend_4.root", "short_friend_5.root", "short_friend_6.root"}; + // We set the minimum value in the second file to check that the branch address is updated by TChain::GetMinimum + constexpr static std::array fShortFriendChainValues{10, 0, 30, 40, 50, 60}; + + static void SetUpTestSuite() + { + { + // Main TTree with cumulated number of entries + auto fd = std::make_unique(fMainTreeFileName, "RECREATE"); + auto td = std::make_unique(fMainTreeName, fMainTreeName); + double x{}; + td->Branch("x", &x); + for (const auto &_ : fMainChainFileNames) + for (int i = 0; i < fNEntriesPerFile; ++i) + td->Fill(); + fd->Write(); + } + + for (int i = 0; i < fNFiles; i++) { + // Trees for the main chain + { + auto fd = std::make_unique(fMainChainFileNames[i], "RECREATE"); + auto td = std::make_unique(fMainTreeName, fMainTreeName); + double x{}; + td->Branch("x", &x); + for (int j = 0; j < fNEntriesPerFile; j++) { + // x in [-0.5, 0.495] + x = j * 0.01 - 0.5; + td->Fill(); + } + fd->Write(); + } + + // Trees for the friend chain + { + auto ff = std::make_unique(fFriendChainFileNames[i], "RECREATE"); + auto tf = std::make_unique(fFriendTreeName, fFriendTreeName); + double w{}; + tf->Branch("w", &w); + for (int j = 0; j < fNEntriesPerFile; j++) { + // w in [fLowerBounds[i], fLowerBounds[i] + 1) + w = fLowerBounds[i] + j * (1.0 / fNEntriesPerFile); + tf->Fill(); + } + ff->Write(); + } + } + + // A second friend chain with the total number of entries but twice as many files (half of the entries per file) + for (auto i = 0; i < fNShortFiles; i++) { + auto fd = std::make_unique(fShortFriendChainFileNames[i], "RECREATE"); + auto td = std::make_unique(fFriendTreeName, fFriendTreeName); + double w{}; + td->Branch("w", &w); + for (int j = 0; j < fNEntriesPerShortFile; j++) { + w = fShortFriendChainValues[i]; + td->Fill(); + } + fd->Write(); + } + } + + static void TearDownTestSuite() + { + for (const auto &f : fMainChainFileNames) + std::remove(f); + + for (const auto &f : fFriendChainFileNames) + std::remove(f); + } +}; + +TEST_F(RegressionGH22652, RunMainTChain) +{ + // Main is a TChain, friend is a TChain, entries are aligned + auto m = std::make_unique(fMainTreeName); + for (const auto &fn : fMainChainFileNames) + m->Add(fn); + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 9.99); +} + +TEST_F(RegressionGH22652, RunMainTTree) +{ + // Main is a TTree, friend is a TChain, entries are aligned + + auto fm = std::make_unique(fMainTreeFileName); + std::unique_ptr m{fm->Get(fMainTreeName)}; + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 9.99); +} + +TEST_F(RegressionGH22652, TChainFriendWithShorterFiles) +{ + // Main is a TChain, friend is a TChain, total number of entries is the same + // but the friend TChain has double the number of files and half the entries + // per file. This exercises in particular the correct updating of the branch + // addresses of the friend TChain when it switches to another file even though + // the main TChain is still traversing the same file. + auto m = std::make_unique(fMainTreeName); + for (const auto &fn : fMainChainFileNames) + m->Add(fn); + + auto fc = std::make_unique(fFriendTreeName); + for (const auto &fn : fShortFriendChainFileNames) + fc->Add(fn); + + m->AddFriend(fc.get()); + + EXPECT_DOUBLE_EQ(m->GetMinimum("w"), 0.0); + EXPECT_DOUBLE_EQ(m->GetMaximum("w"), 60.0); +} \ No newline at end of file