diff --git a/.github/workflows/jsroot-ci.yml b/.github/workflows/jsroot-ci.yml index 8d0a64142..3305c935d 100644 --- a/.github/workflows/jsroot-ci.yml +++ b/.github/workflows/jsroot-ci.yml @@ -70,6 +70,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json @@ -192,6 +193,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json @@ -249,6 +251,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json diff --git a/demo/node/tree_tobject_test.cxx b/demo/node/tree_tobject_test.cxx new file mode 100644 index 000000000..1218f1532 --- /dev/null +++ b/demo/node/tree_tobject_test.cxx @@ -0,0 +1,34 @@ +// macro to create tree_tobject_test.root used by tree_tobject_test.js +// root -l -b -q tree_tobject_test.cxx + +#include +#include +#include +#include +#include +#include + +void tree_tobject_test() +{ + TFile f("tree_tobject_test.root", "RECREATE"); + + TTree t("t", "Objects with custom streamers"); + TLorentzVector *lv = new TLorentzVector; + TVector3 *v3 = new TVector3; + TGraph *gr = new TGraph(3); + TDatime *dt = new TDatime; + // classes with custom streamers are not split by ROOT + t.Branch("lv", &lv); + t.Branch("v3", &v3); + t.Branch("gr", &gr); + t.Branch("dt", &dt); + for (Int_t i = 0; i < 100; ++i) { + lv->SetPxPyPzE(i, 2 * i, 3 * i, 4 * i + 1); + v3->SetXYZ(i, -i, 0.5 * i); + for (Int_t j = 0; j < 3; ++j) + gr->SetPoint(j, j, i + j); + dt->Set(2026, 1, 1, 0, 0, i % 60); + t.Fill(); + } + t.Write(); +} diff --git a/demo/node/tree_tobject_test.js b/demo/node/tree_tobject_test.js new file mode 100644 index 000000000..b6f289f5e --- /dev/null +++ b/demo/node/tree_tobject_test.js @@ -0,0 +1,65 @@ +import { version, openFile } from 'jsroot'; +import { TSelector, treeProcess } from 'jsroot/tree'; + +// test reading of objects with custom streamers, file created with tree_tobject_test.cxx + +console.log(`JSROOT version ${version}`); + +const nentries = 100; + +let filename = './tree_tobject_test.root', + any_error = false, + err_cnt = 0; + +if (process?.argv && process.argv[2]) + filename = process.argv[2]; + +function failure(msg) { + any_error = true; + if (err_cnt++ < 10) + console.error(`FAILURE: ${msg}`); +} + +const file = await openFile(filename), + tree = await file.readObject('t'), + selector = new TSelector(); + +let cnt = 0; + +selector.addBranch('lv'); +selector.addBranch('v3'); +selector.addBranch('gr'); +selector.addBranch('dt'); + +selector.Process = function() { + const i = cnt++, { lv, v3, gr, dt } = this.tgtobj; + + if ((lv?.fP?.fX !== i) || (lv.fP.fY !== 2 * i) || (lv.fP.fZ !== 3 * i) || (lv.fE !== 4 * i + 1)) + failure(`entry ${i} wrong TLorentzVector ${JSON.stringify(lv)}`); + + if ((v3?.fX !== i) || (v3.fY !== -i) || (v3.fZ !== 0.5 * i)) + failure(`entry ${i} wrong TVector3 ${JSON.stringify(v3)}`); + + if ((gr?.fNpoints !== 3) || (gr.fX?.[2] !== 2) || (gr.fY?.[2] !== i + 2)) + failure(`entry ${i} wrong TGraph npoints ${gr?.fNpoints} x ${gr?.fX?.[2]} y ${gr?.fY?.[2]}`); + + // TDatime has no version header, seconds are stored in lower 6 bits + const sec = Number.isInteger(dt?.fDatime) ? dt.fDatime & 0x3f : -1; + if (sec !== i % 60) + failure(`entry ${i} wrong TDatime seconds ${sec}`); +}; + +try { + await treeProcess(tree, selector); +} catch (err) { + failure(`exception when reading tree: ${err.message}`); +} + +if (cnt !== nentries) + failure(`expected ${nentries} tree entries but got ${cnt}`); + +if (any_error) { + console.error('\nFAILURE when reading objects with custom streamers'); + process.exit(1); +} else + console.log(`OK: tree with ${cnt} entries\n\nTest OK`); diff --git a/demo/node/tree_tobject_test.root b/demo/node/tree_tobject_test.root new file mode 100644 index 000000000..0c013590c Binary files /dev/null and b/demo/node/tree_tobject_test.root differ diff --git a/modules/tree.mjs b/modules/tree.mjs index 223a65b40..940c058b7 100644 --- a/modules/tree.mjs +++ b/modules/tree.mjs @@ -2142,6 +2142,12 @@ async function treeProcess(tree, selector, args) { obj[this.name] = res; } }; + // object written by custom streamer of the class, it has version and bytecount + if (branch.fType === -1) { + member.func = function(buf, obj) { + obj[this.name] = buf.classStreamer({}, this.typename); + }; + } } }