diff --git a/bindings/pyroot/pythonizations/test/regression_23728.py b/bindings/pyroot/pythonizations/test/regression_23728.py index 0371a664c691f..243473c6b4f79 100644 --- a/bindings/pyroot/pythonizations/test/regression_23728.py +++ b/bindings/pyroot/pythonizations/test/regression_23728.py @@ -29,7 +29,12 @@ def test_exit_cleanup_after_interpreted_destructor(self): with ROOT.TFile(fname, "RECREATE") as f: t = ROOT.TTree("t", "t") v = ROOT.std.vector[ROOT.std.vector[int]]() - t.Branch("vv", v) + # Use the overload taking the class name and the address: the + # templated Branch() refuses collections without a compiled + # CollectionProxy, which would silently leave the tree without + # the branch and the test without its purpose. + branch = t.Branch("vv", "vector >", ROOT.AddressOf(v)) + self.assertTrue(branch, "Branch() refused to create the branch") v.push_back(ROOT.std.vector[int]([1, 2])) t.Fill() f.Write() @@ -53,8 +58,22 @@ def test_exit_cleanup_after_interpreted_destructor(self): if t.GetDirectory() != f: raise RuntimeError("the tree is not owned by the file") - # Make the interpreter JIT-compile a destructor before exit. - ROOT.std.vector[ROOT.TNamed]() + # Sanity checks that the branch exists and that bytes are + # actually read, which is required for the reproducer. + if not t.GetBranch("vv"): + raise RuntimeError("the tree has no 'vv' branch") + if t.GetEntry(0) <= 0: + raise RuntimeError("no bytes read for entry 0: no branch object was created") + + # Make the interpreter JIT-compile the destructor of the branch + # class before exit. This creates the destructor cache before the + # end-of-process cleanup needs it (and, before the fix, destroyed + # it again before that). Using the very same class keeps the + # exit-time destructor call a pure cache lookup: compiling code + # at that point is not possible on Windows, where the atexit + # handler of libCore runs only when libCling's statics have + # already been torn down by its DLL_PROCESS_DETACH. + ROOT.std.vector[ROOT.std.vector[int]]() print("end of script") """ @@ -65,12 +84,16 @@ def test_exit_cleanup_after_interpreted_destructor(self): # whatever the heap layout and the interpreter internals. env = dict(os.environ, MALLOC_PERTURB_="85") + # Without the fix, ROOT's crash handler can hang on the way out + # of the segfault instead of terminating the process: fail + # instead of waiting for the ctest timeout. res = subprocess.run( [sys.executable, "-c", script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env, + timeout=300, ) self.assertEqual(res.returncode, 0, msg=res.stdout)