Skip to content

Commit 6a31f76

Browse files
committed
[tree] Align behaviour of SetBranchStatus of TChain and TTree
When a user calls SetBranchStatus("*", false) it means that all branches of the dataset should be deactivated, no matter what other method is called, except if then the user calls SetBranchStatus(someBranch, true) to activate a branch. So, if a user calls SetBranchStatus to deactivate all branches and then calls SetBranchAddress, this should have no effect. No data should be read because the branch related to SetBranchAddress should not have been activated. Before this commit, TTree was respecting this rule, but TChain was not. This commit ensures that TChain only sets the status of the branch of the current TTree if the corresponding TChainElement was explicitly provided with a status by the user.
1 parent 0ba8001 commit 6a31f76

File tree

4 files changed

+114
-35
lines changed

4 files changed

+114
-35
lines changed

README/ReleaseNotes/v638/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
## Core Libraries
99
* Behavior change: when selecting a template instantiation for a dictionary, all the template arguments have to be fully defined - the forward declarations are not enough any more. The error prompted by the dictionary generator will be `Warning: Unused class rule: MyTemplate<MyFwdDeclaredClass>`.
1010

11+
## I/O
12+
13+
### TTree
14+
15+
* Behaviour change: the behaviour of `TChain::SetBranchStatus` has been aligned to the one of `TTree::SetBranchStatus`. In particular, when `SetBranchStatus` is called to deactivate all branches, a subsequent call to `TChain::SetBranchAddress` would override the previous instruction and activate that single branch. Instead `TTree::SetBranchAddress` respects the rule imposed by `SetBranchStatus`. If a user needs to activate only one or more branches, they should call `SetBranchStatus("brName", true)` on each branch that needs to be active in the TChain, like it was already necessary for a TTree. See https://github.com/root-project/root/pull/19221 for more details.
16+
1117
## Math
1218

1319
### Minuit2

roottest/root/tree/branches/execGetBranch.ref

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,9 @@ Error in <TTree::SetBranchAddress>: unknown branch -> x.x
1313
Testing on TTree
1414
Testing on TChain
1515
Error in <TChain::SetBranchAddress>: unknown branch -> x.x
16-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
17-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
18-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
19-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
20-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
2116
Error in <TChain::SetBranchAddress>: unknown branch -> x.x
22-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
23-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
24-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
25-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
2617
TTreeReader::Next:
2718
TTreeReader::Next:
28-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
29-
Error in <TTree::SetBranchStatus>: unknown branch -> x.x
3019
RDataFrame columns:
3120
i
3221
i.x

tree/tree/src/TChain.cxx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,10 @@ Long64_t TChain::LoadTree(Long64_t entry)
14461446
TIter fnext(fStatus);
14471447
while ((frelement = (TChainElement*) fnext())) {
14481448
Int_t status = frelement->GetStatus();
1449-
fTree->SetBranchStatus(frelement->GetName(), status);
1449+
// Only set the branch status if it has a value provided
1450+
// by the user
1451+
if (status != -1)
1452+
fTree->SetBranchStatus(frelement->GetName(), status);
14501453
}
14511454

14521455
// Set the branch addresses for the newly opened file.
@@ -1743,7 +1746,10 @@ Long64_t TChain::LoadTree(Long64_t entry)
17431746
TIter next(fStatus);
17441747
while ((element = (TChainElement*) next())) {
17451748
Int_t status = element->GetStatus();
1746-
fTree->SetBranchStatus(element->GetName(), status);
1749+
// Only set the branch status if it has a value provided
1750+
// by the user
1751+
if (status != -1)
1752+
fTree->SetBranchStatus(element->GetName(), status);
17471753
}
17481754

17491755
// Set the branch addresses for the newly opened file.

tree/tree/test/TChainRegressions.cxx

Lines changed: 100 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -128,31 +128,109 @@ TEST(TChain, UncommonFileExtension)
128128
gSystem->Unlink(dirname);
129129
}
130130

131-
// https://github.com/root-project/root/issues/7567
132-
TEST(TChain, GetEntriesSingleFileDeactivateBranches)
131+
// Originally reproducer of https://github.com/root-project/root/issues/7567
132+
// but see also https://github.com/root-project/root/issues/19220 for an update
133+
// The test parameters are
134+
// - int: number of files (1, 2), only relevant for TChain.
135+
// - bool: call `GetEntries` at the beginning or not.
136+
// - bool: call `SetBranchStatus("*", false)` or not.
137+
// - bool: call `SetBranchStatus("random", true)` or not.
138+
class SetBranchStatusInteraction : public ::testing::TestWithParam<std::tuple<int, bool, bool, bool>> {};
139+
140+
TEST_P(SetBranchStatusInteraction, TestTChain)
141+
{
142+
const auto [nFiles, callGetEntries, deactivateAllBranches, activateSingleBranch] = GetParam();
143+
144+
const auto treename = "ntuple";
145+
const auto filename = "$ROOTSYS/tutorials/hsimple.root";
146+
147+
TChain chain(treename);
148+
for (auto i = 0; i < nFiles; ++i) {
149+
chain.Add(filename);
150+
}
151+
auto nEntries = callGetEntries ? chain.GetEntries() : chain.GetEntriesFast();
152+
if (deactivateAllBranches)
153+
chain.SetBranchStatus("*", 0);
154+
// read a single branch
155+
float random = 0.333333;
156+
TBranch *b_random{nullptr};
157+
// attention! SetBranchAddress!=SetBranchStatus. When all the branches are deactivated because of a previous
158+
// `SetBranchStatus("*", false)` call, the only way to actually read the "random" branch is to activate it first!
159+
if (deactivateAllBranches && activateSingleBranch)
160+
chain.SetBranchStatus("random", true);
161+
chain.SetBranchAddress("random", &random, &b_random);
162+
for (decltype(nEntries) i = 0; i < chain.GetEntriesFast(); ++i) {
163+
auto bytes = chain.GetEntry(i);
164+
if (deactivateAllBranches) {
165+
if (activateSingleBranch)
166+
ASSERT_GT(bytes, 0);
167+
else
168+
ASSERT_EQ(bytes, 0);
169+
} else {
170+
// by default the branches will be activated
171+
ASSERT_GT(bytes, 0);
172+
}
173+
}
174+
}
175+
176+
TEST_P(SetBranchStatusInteraction, TestTTree)
133177
{
178+
const auto [_, callGetEntries, deactivateAllBranches, activateSingleBranch] = GetParam();
179+
134180
const auto treename = "ntuple";
135181
const auto filename = "$ROOTSYS/tutorials/hsimple.root";
136-
for (auto nFiles : {2, 1}) {
137-
for (auto DoGetEntries : {false, true}) {
138-
for (auto DoDeactivateBranches : {false, true}) {
139-
TChain chain(treename);
140-
for (auto i = 0; i < nFiles; ++i) {
141-
chain.Add(filename);
142-
}
143-
const long nEntries = DoGetEntries ? chain.GetEntries() : -1;
144-
if (DoDeactivateBranches)
145-
chain.SetBranchStatus("*", 0); // before the fix, this line, together with a previous GetEntries() caused
146-
// a bug, but only if a single file is passed to the TChain!
147-
// read a single branch
148-
float random = 0.333333;
149-
TBranch *b_random;
150-
chain.SetBranchAddress("random", &random, &b_random); // initialize one branch only
151-
for (long i = 0; i < (DoGetEntries ? nEntries : chain.GetEntriesFast()); ++i) {
152-
auto bytes = chain.GetEntry(i);
153-
ASSERT_GT(bytes, 0);
154-
}
155-
}
182+
183+
auto tfile = std::make_unique<TFile>(filename);
184+
auto *ttree = tfile->Get<TTree>(treename);
185+
186+
auto nEntries = callGetEntries ? ttree->GetEntries() : ttree->GetEntriesFast();
187+
if (deactivateAllBranches)
188+
ttree->SetBranchStatus("*", 0);
189+
// read a single branch
190+
float random = 0.333333;
191+
TBranch *b_random{nullptr};
192+
// attention! SetBranchAddress!=SetBranchStatus. When all the branches are deactivated because of a previous
193+
// `SetBranchStatus("*", false)` call, the only way to actually read the "random" branch is to activate it first!
194+
if (deactivateAllBranches && activateSingleBranch)
195+
ttree->SetBranchStatus("random", true);
196+
ttree->SetBranchAddress("random", &random, &b_random);
197+
for (decltype(nEntries) i = 0; i < nEntries; ++i) {
198+
auto bytes = ttree->GetEntry(i);
199+
if (deactivateAllBranches) {
200+
if (activateSingleBranch)
201+
ASSERT_GT(bytes, 0);
202+
else
203+
ASSERT_EQ(bytes, 0);
204+
} else {
205+
// by default the branches will be activated
206+
ASSERT_GT(bytes, 0);
156207
}
157208
}
158209
}
210+
211+
INSTANTIATE_TEST_SUITE_P(
212+
RunTests, SetBranchStatusInteraction,
213+
::testing::Combine(
214+
// number of files, only relevant for TChain
215+
::testing::Values(1, 2),
216+
// call `GetEntries` at the beginning or not
217+
::testing::Values(true, false),
218+
// call `SetBranchStatus("*", false)` or not
219+
::testing::Values(true, false),
220+
// call `SetBranchStatus("random", true)` or not
221+
::testing::Values(true, false)),
222+
// Extra parenthesis around lambda to avoid preprocessor errors, see
223+
// https://stackoverflow.com/questions/79438894/lambda-with-structured-bindings-inside-macro-call
224+
([](const testing::TestParamInfo<SetBranchStatusInteraction::ParamType> &paramInfo) {
225+
auto &&[nFiles, callGetEntries, deactivateAllBranches, activateSingleBranch] = paramInfo.param;
226+
// googletest only accepts ASCII alphanumeric characters for labels
227+
std::string label{"f"};
228+
label += std::to_string(nFiles);
229+
label += "e";
230+
label += std::to_string(callGetEntries);
231+
label += "d";
232+
label += std::to_string(deactivateAllBranches);
233+
label += "a";
234+
label += std::to_string(activateSingleBranch);
235+
return label;
236+
}));

0 commit comments

Comments
 (0)