BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
compare_files.C
Go to the documentation of this file.
1// Compare the data content of two CERN ROOT files.
2//
3// Even "identical" ROOT files differ binary-wise: the file header stores the file
4// name and modification time, and every TKey/TBasket stores its creation time.
5// Therefore the comparison is done on the deserialized data instead: every key of
6// both files is read back, the objects are serialized into memory buffers and
7// compared byte by byte; TTrees are compared entry by entry, branch by branch.
8// NB: full comparison of user classes requires their dictionaries to be loaded.
9//
10// If size_only = kTRUE, a coarse comparison is performed instead, which does not
11// require any class dictionaries: only the number of entries and the uncompressed
12// data size of every branch and of every non-tree object are compared.
13// NB: equal sizes do not guarantee equal content (e.g. a changed value of a fixed-size type)
14//
15// The same data can also be reduced to a content hash of a single file with hash_file(), which is
16// invariant against all the metadata ignored by the comparison
17// NB: the hash is only as reproducible as the streamers of the stored classes are. A class whose
18// serialized form contains addresses or uninitialized memory gives a different hash
19//
20// Usage: root -l -b -q 'compare_files.C("file1.root", "file2.root")'
21// root -l -b -q 'compare_files.C("file1.root", "file2.root", kTRUE)'
22// root -l -b -q -e '.L compare_files.C' -e 'hash_file("file.root")'
23// root -l -b -q -e '.L compare_files.C' -e 'hash_file("file.root", kTRUE)'
24
25#include "TBranchElement.h"
26#include "TBufferFile.h"
27#include "TClass.h"
28#include "TFile.h"
29#include "TGeoManager.h"
30#include "TH1.h"
31#include "TKey.h"
32#include "TLeaf.h"
33#include "TMD5.h"
34#include "TROOT.h"
35#include "TSystem.h"
36#include "TTree.h"
37
38#include <cstring>
39#include <iostream>
40#include <map>
41#include <set>
42#include <string>
43using namespace std;
44
45// Keys holding a geometry (a TGeoManager or a parameter set containing one, e.g. FairGeoParSet)
46// build that geometry while they are read, and TGeoManager::Init() deletes the geometry the global
47// pointer refers to - which here is the object read from the first file and still needed. Detaching
48// the global pointer before every read leaves the previous geometry alone.
50{
51 gGeoManager = nullptr;
52}
53
54// Make ROOT forget the geometries read by this macro before the process ends, so that none of them
55// is deleted twice on teardown. They are leaked on purpose: the macro is short-lived, and their
56// owners (the parameter sets) may have released them already.
58{
59 gGeoManager = nullptr;
60 if (gROOT->GetListOfGeometries() != nullptr)
61 gROOT->GetListOfGeometries()->Clear("nodelete");
62}
63
64// serialize two objects of the same class into memory buffers and compare them byte by byte
65bool AreObjectsEqual(void* obj1, void* obj2, TClass* obj_class)
66{
67 if ((obj1 == nullptr) || (obj2 == nullptr))
68 return obj1 == obj2;
69
70 // stream via the known class directly: unlike WriteObjectAny, this does not try to detect
71 // the actual class via the virtual IsA(), which fails for classes without a dictionary
74 obj_class->Streamer(obj1, buffer1);
75 obj_class->Streamer(obj2, buffer2);
76
77 if (buffer1.Length() != buffer2.Length())
78 return false;
79 return memcmp(buffer1.Buffer(), buffer2.Buffer(), buffer1.Length()) == 0;
80}
81
82// compare the number of entries and the uncompressed data size of a branch and all its sub-branches
83bool CompareBranchSizes(TBranch* branch1, TBranch* branch2, const TString& obj_path)
84{
85 bool is_equal = true;
86
87 if (branch1->GetEntries() != branch2->GetEntries()) {
88 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' has different number of entries ("
89 << branch1->GetEntries() << " vs " << branch2->GetEntries() << ")" << endl;
90 is_equal = false;
91 } else if (branch1->GetTotBytes() != branch2->GetTotBytes()) {
92 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' has different data size ("
93 << branch1->GetTotBytes() << " vs " << branch2->GetTotBytes() << " bytes uncompressed)" << endl;
94 is_equal = false;
95 }
96
97 TObjArray* sub_branches1 = branch1->GetListOfBranches();
98 TObjArray* sub_branches2 = branch2->GetListOfBranches();
99 if (sub_branches1->GetEntries() != sub_branches2->GetEntries()) {
100 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' has different number of sub-branches ("
101 << sub_branches1->GetEntries() << " vs " << sub_branches2->GetEntries() << ")" << endl;
102 return false;
103 }
104 for (Int_t i = 0; i < sub_branches1->GetEntries(); i++) {
105 TBranch* sub_branch1 = (TBranch*)sub_branches1->At(i);
106 TBranch* sub_branch2 = (TBranch*)sub_branches2->FindObject(sub_branch1->GetName());
107 if (sub_branch2 == nullptr) {
108 cout << "DIFF " << obj_path << ": branch '" << sub_branch1->GetName() << "' is missing in the second file"
109 << endl;
110 is_equal = false;
111 } else if (!CompareBranchSizes(sub_branch1, sub_branch2, obj_path))
112 is_equal = false;
113 }
114
115 return is_equal;
116}
117
118// compare two trees: entry by entry, branch by branch (or only entry counts and branch sizes if size_only)
119bool CompareTrees(TTree* tree1, TTree* tree2, const TString& obj_path, bool size_only)
120{
121 Long64_t entry_count = tree1->GetEntries();
122 if (entry_count != tree2->GetEntries()) {
123 cout << "DIFF " << obj_path << ": different number of entries (" << entry_count << " vs " << tree2->GetEntries()
124 << ")" << endl;
125 return false;
126 }
127
128 // compare the branch structure
129 TObjArray* branches1 = tree1->GetListOfBranches();
130 Int_t branch_count = branches1->GetEntries();
131 if (branch_count != tree2->GetListOfBranches()->GetEntries()) {
132 cout << "DIFF " << obj_path << ": different number of branches (" << branch_count << " vs "
133 << tree2->GetListOfBranches()->GetEntries() << ")" << endl;
134 return false;
135 }
136 for (Int_t i = 0; i < branch_count; i++) {
137 TBranch* branch1 = (TBranch*)branches1->At(i);
138 if (tree2->GetBranch(branch1->GetName()) == nullptr) {
139 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' is missing in the second file"
140 << endl;
141 return false;
142 }
143 }
144
145 // coarse mode: compare only the entry counts and uncompressed data sizes of all branches
146 if (size_only) {
147 bool is_equal = true;
148 for (Int_t i = 0; i < branch_count; i++) {
149 TBranch* branch1 = (TBranch*)branches1->At(i);
150 if (!CompareBranchSizes(branch1, tree2->GetBranch(branch1->GetName()), obj_path))
151 is_equal = false;
152 }
153 if (is_equal)
154 cout << "OK " << obj_path << " (TTree): " << entry_count << " entries, same branch sizes ("
155 << tree1->GetTotBytes() << " bytes uncompressed)" << endl;
156 return is_equal;
157 }
158
159 for (Long64_t entry = 0; entry < entry_count; entry++) {
160 // read the whole entry in both trees first (branch addresses are allocated by ROOT itself),
161 // so that leaf-count branches are up to date before any data are compared
162 for (Int_t i = 0; i < branch_count; i++) {
163 TBranch* branch1 = (TBranch*)branches1->At(i);
164 branch1->GetEntry(entry);
165 tree2->GetBranch(branch1->GetName())->GetEntry(entry);
166 }
167
168 for (Int_t i = 0; i < branch_count; i++) {
169 TBranch* branch1 = (TBranch*)branches1->At(i);
170 TBranch* branch2 = tree2->GetBranch(branch1->GetName());
171
172 // object branches: compare the deserialized objects
173 TBranchElement* branch_el1 = dynamic_cast<TBranchElement*>(branch1);
174 TBranchElement* branch_el2 = dynamic_cast<TBranchElement*>(branch2);
175 if ((branch_el1 != nullptr) && (branch_el2 != nullptr) && (branch_el1->GetObject() != nullptr)
176 && (branch_el2->GetObject() != nullptr))
177 {
178 if (strcmp(branch_el1->GetClassName(), branch_el2->GetClassName()) != 0) {
179 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' has different classes ("
180 << branch_el1->GetClassName() << " vs " << branch_el2->GetClassName() << ")" << endl;
181 return false;
182 }
183 TClass* branch_class = TClass::GetClass(branch_el1->GetClassName());
184 if (!AreObjectsEqual(branch_el1->GetObject(), branch_el2->GetObject(), branch_class)) {
185 cout << "DIFF " << obj_path << ": branch '" << branch1->GetName() << "' differs at entry " << entry
186 << endl;
187 return false;
188 }
189 continue;
190 }
191
192 // branches of basic types: compare the leaf data
193 TIter next_leaf(branch1->GetListOfLeaves());
194 TLeaf* leaf1;
195 while ((leaf1 = (TLeaf*)next_leaf()) != nullptr) {
196 TLeaf* leaf2 = branch2->GetLeaf(leaf1->GetName());
197 if ((leaf2 == nullptr) || (leaf1->GetLen() != leaf2->GetLen())
198 || (memcmp(leaf1->GetValuePointer(), leaf2->GetValuePointer(),
199 leaf1->GetLen() * leaf1->GetLenType())
200 != 0))
201 {
202 cout << "DIFF " << obj_path << ": leaf '" << branch1->GetName() << "/" << leaf1->GetName()
203 << "' differs at entry " << entry << endl;
204 return false;
205 }
206 }
207 }
208 }
209
210 cout << "OK " << obj_path << " (TTree): " << entry_count << " entries are identical" << endl;
211 return true;
212}
213
214// compare all keys of two directories (the highest cycle of each key), recursing into subdirectories
215bool CompareDirectories(TDirectory* dir1, TDirectory* dir2, const TString& dir_path, bool size_only)
216{
217 bool is_equal = true;
218
219 set<string> key_names1, key_names2;
220 TIter next_key1(dir1->GetListOfKeys());
221 TKey* key;
222 while ((key = (TKey*)next_key1()) != nullptr)
223 key_names1.insert(key->GetName());
224 TIter next_key2(dir2->GetListOfKeys());
225 while ((key = (TKey*)next_key2()) != nullptr)
226 key_names2.insert(key->GetName());
227
228 for (const string& key_name : key_names1)
229 if (key_names2.count(key_name) == 0) {
230 cout << "DIFF " << dir_path << key_name << ": present only in the first file" << endl;
231 is_equal = false;
232 }
233 for (const string& key_name : key_names2)
234 if (key_names1.count(key_name) == 0) {
235 cout << "DIFF " << dir_path << key_name << ": present only in the second file" << endl;
236 is_equal = false;
237 }
238
239 for (const string& key_name : key_names1) {
240 if (key_names2.count(key_name) == 0)
241 continue;
242
243 TKey* key1 = dir1->GetKey(key_name.c_str());
244 TKey* key2 = dir2->GetKey(key_name.c_str());
245 TString obj_path = dir_path + key_name;
246
247 if (strcmp(key1->GetClassName(), key2->GetClassName()) != 0) {
248 cout << "DIFF " << obj_path << ": different classes (" << key1->GetClassName() << " vs "
249 << key2->GetClassName() << ")" << endl;
250 is_equal = false;
251 continue;
252 }
253
254 TClass* obj_class = TClass::GetClass(key1->GetClassName());
255
256 // TProcessID contains only a time-based GUID, i.e. pure metadata
257 if ((obj_class != nullptr) && obj_class->InheritsFrom("TProcessID"))
258 continue;
259
260 if ((obj_class != nullptr) && obj_class->InheritsFrom(TTree::Class())) {
261 TTree* tree1 = (TTree*)key1->ReadObj();
262 TTree* tree2 = (TTree*)key2->ReadObj();
263 if (!CompareTrees(tree1, tree2, obj_path, size_only))
264 is_equal = false;
265 } else if ((obj_class != nullptr) && obj_class->InheritsFrom(TDirectory::Class())) {
266 if (!CompareDirectories(dir1->GetDirectory(key_name.c_str()), dir2->GetDirectory(key_name.c_str()),
267 obj_path + "/", size_only))
268 is_equal = false;
269 } else if (size_only) {
270 // coarse mode: compare only the uncompressed size of the stored object (no dictionary is required)
271 if (key1->GetObjlen() == key2->GetObjlen())
272 cout << "OK " << obj_path << " (" << key1->GetClassName() << "): same size of " << key1->GetObjlen()
273 << " bytes" << endl;
274 else {
275 cout << "DIFF " << obj_path << " (" << key1->GetClassName() << "): different size ("
276 << key1->GetObjlen() << " vs " << key2->GetObjlen() << " bytes uncompressed)" << endl;
277 is_equal = false;
278 }
279 } else if (obj_class == nullptr) {
280 cout << "WARN " << obj_path << ": unknown class " << key1->GetClassName() << ", the key is skipped" << endl;
281 } else {
283 void* obj1 = key1->ReadObjectAny(obj_class);
285 void* obj2 = key2->ReadObjectAny(obj_class);
286 if (AreObjectsEqual(obj1, obj2, obj_class))
287 cout << "OK " << obj_path << " (" << key1->GetClassName() << ")" << endl;
288 else {
289 cout << "DIFF " << obj_path << " (" << key1->GetClassName() << "): content differs" << endl;
290 is_equal = false;
291 }
292 if (obj1 != nullptr)
293 obj_class->Destructor(obj1);
294 if (obj2 != nullptr)
295 obj_class->Destructor(obj2);
296 }
297 }
298
299 return is_equal;
300}
301
302// ------------------------------------------------------------------------------------------------
303// Content hash of a single file: exactly the same data that are compared above are fed, in a
304// canonical (name-sorted) order, into an MD5 digest, while all metadata are left out. Two files
305// with equal data content therefore get the same hash although their bytes on disk differ, i.e.
306// the guarantee is: if compare_files() reports IDENTICAL, the hashes of both files are equal.
307// The hash of a file written with size_only = kTRUE is a different, coarser one and must never be
308// compared with a full content hash.
309// ------------------------------------------------------------------------------------------------
310
311// tag of the hashing scheme: any change of the code below must change this tag, so that hashes
312// produced by different versions of this macro are never compared with each other
313static const char* hash_scheme_tag = "bmnroot-hash-1";
314
315// feed a text token into the digest (the separator prevents ambiguous concatenations)
316void HashText(TMD5& digest, const char* text)
317{
318 digest.Update((const UChar_t*)text, strlen(text));
319 digest.Update((const UChar_t*)"\n", 1);
320}
321
322// feed a number into the digest as text, so that the hash does not depend on the byte order
323void HashNumber(TMD5& digest, Long64_t number)
324{
325 HashText(digest, Form("%lld", number));
326}
327
328// feed a length-prefixed memory block into the digest
329void HashBlock(TMD5& digest, const void* data, Int_t length)
330{
331 HashNumber(digest, length);
332 if (length > 0)
333 digest.Update((const UChar_t*)data, length);
334}
335
336// feed the serialized representation of an object into the digest (see AreObjectsEqual)
337void HashObject(TMD5& digest, void* obj, TClass* obj_class)
338{
339 if ((obj == nullptr) || (obj_class == nullptr)) {
340 HashText(digest, "no-object");
341 return;
342 }
343
345 obj_class->Streamer(obj, buffer);
346 HashBlock(digest, buffer.Buffer(), buffer.Length());
347}
348
349// feed the number of entries and the uncompressed data size of a branch and all its sub-branches into the digest
350void HashBranchSizes(TMD5& digest, TBranch* branch)
351{
352 HashText(digest, branch->GetName());
353 HashNumber(digest, branch->GetEntries());
354 HashNumber(digest, branch->GetTotBytes());
355
356 // the sub-branches are hashed in alphabetical order, so that the digest does not depend on their storage order
357 map<string, TBranch*> sub_branches;
358 TObjArray* sub_branch_list = branch->GetListOfBranches();
359 for (Int_t i = 0; i < sub_branch_list->GetEntries(); i++) {
360 TBranch* sub_branch = (TBranch*)sub_branch_list->At(i);
361 sub_branches[sub_branch->GetName()] = sub_branch;
362 }
363 HashNumber(digest, sub_branches.size());
364 for (const auto& sub_branch : sub_branches)
365 HashBranchSizes(digest, sub_branch.second);
366}
367
368// feed a tree into the digest: entry by entry, branch by branch (or only entry counts and branch sizes if size_only)
369void HashTree(TMD5& digest, TTree* tree, bool size_only)
370{
371 Long64_t entry_count = tree->GetEntries();
372 HashNumber(digest, entry_count);
373
374 // the branches are hashed in alphabetical order, so that the digest does not depend on their storage order
375 map<string, TBranch*> branches;
376 TObjArray* branch_list = tree->GetListOfBranches();
377 for (Int_t i = 0; i < branch_list->GetEntries(); i++) {
378 TBranch* branch = (TBranch*)branch_list->At(i);
379 branches[branch->GetName()] = branch;
380 }
381 HashNumber(digest, branches.size());
382
383 // coarse mode: hash only the entry counts and uncompressed data sizes of all branches
384 if (size_only) {
385 for (const auto& branch : branches)
386 HashBranchSizes(digest, branch.second);
387 return;
388 }
389
390 for (Long64_t entry = 0; entry < entry_count; entry++) {
391 // read the whole entry first (branch addresses are allocated by ROOT itself),
392 // so that leaf-count branches are up to date before any data are hashed
393 for (const auto& branch : branches)
394 branch.second->GetEntry(entry);
395
396 for (const auto& branch : branches) {
397 HashText(digest, branch.first.c_str());
398
399 // object branches: hash the deserialized objects
400 TBranchElement* branch_el = dynamic_cast<TBranchElement*>(branch.second);
401 if ((branch_el != nullptr) && (branch_el->GetObject() != nullptr)) {
402 HashText(digest, branch_el->GetClassName());
403 HashObject(digest, branch_el->GetObject(), TClass::GetClass(branch_el->GetClassName()));
404 continue;
405 }
406
407 // branches of basic types: hash the leaf data
408 TIter next_leaf(branch.second->GetListOfLeaves());
409 TLeaf* leaf;
410 while ((leaf = (TLeaf*)next_leaf()) != nullptr) {
411 HashText(digest, leaf->GetName());
412 if (leaf->GetValuePointer() == nullptr)
413 HashText(digest, "no-value");
414 else
415 HashBlock(digest, leaf->GetValuePointer(), leaf->GetLen() * leaf->GetLenType());
416 }
417 }
418 }
419}
420
421// feed all keys of a directory (the highest cycle of each key) into the digest, recursing into subdirectories
422void HashDirectory(TMD5& digest, TDirectory* dir, const TString& dir_path, bool size_only)
423{
424 // the keys are hashed in alphabetical order, so that the digest does not depend on their storage order
425 set<string> key_names;
426 TIter next_key(dir->GetListOfKeys());
427 TKey* key;
428 while ((key = (TKey*)next_key()) != nullptr)
429 key_names.insert(key->GetName());
430
431 for (const string& key_name : key_names) {
432 TKey* dir_key = dir->GetKey(key_name.c_str());
433 TString obj_path = dir_path + key_name;
434 TClass* obj_class = TClass::GetClass(dir_key->GetClassName());
435
436 // TProcessID contains only a time-based GUID, i.e. pure metadata
437 if ((obj_class != nullptr) && obj_class->InheritsFrom("TProcessID"))
438 continue;
439
440 // objects of unknown classes are skipped by the comparison as well, otherwise files reported
441 // as identical could get different hashes
442 if ((obj_class == nullptr) && !size_only) {
443 cout << "WARN " << obj_path << ": unknown class " << dir_key->GetClassName() << ", the key is not hashed"
444 << endl;
445 continue;
446 }
447
448 HashText(digest, obj_path.Data());
449 HashText(digest, dir_key->GetClassName());
450
451 if ((obj_class != nullptr) && obj_class->InheritsFrom(TTree::Class()))
452 HashTree(digest, (TTree*)dir_key->ReadObj(), size_only);
453 else if ((obj_class != nullptr) && obj_class->InheritsFrom(TDirectory::Class()))
454 HashDirectory(digest, dir->GetDirectory(key_name.c_str()), obj_path + "/", size_only);
455 else if (size_only)
456 // coarse mode: hash only the uncompressed size of the stored object (no dictionary is required)
457 HashNumber(digest, dir_key->GetObjlen());
458 else {
460 void* obj = dir_key->ReadObjectAny(obj_class);
461 HashObject(digest, obj, obj_class);
462 if (obj != nullptr)
463 obj_class->Destructor(obj);
464 }
465 }
466}
467
468// compute the hash of the data content of a ROOT file ignoring metadata
469// if size_only = kTRUE, the coarser hash of the entry counts and
470// uncompressed data sizes is computed instead (no class dictionaries required)
471TString hash_file(TString root_file_path, Bool_t size_only = kFALSE)
472{
473 gSystem->ExpandPathName(root_file_path);
474
475 if (gSystem->AccessPathName(root_file_path.Data())) {
476 cerr << "Error: file does not exist or is not accessible: " << root_file_path << endl;
477 return "";
478 }
479
480 TFile* root_file = TFile::Open(root_file_path);
481 if ((root_file == nullptr) || root_file->IsZombie()) {
482 cerr << "Error: could not open ROOT file: " << root_file_path << endl;
483 return "";
484 }
485
486 // do not attach histograms being read to the current directory, so they can be safely deleted
488
489 TMD5 digest;
490 HashText(digest, hash_scheme_tag);
491 HashText(digest, size_only ? "size-only" : "content");
492 HashDirectory(digest, root_file, "/", size_only);
493 digest.Final();
494
495 TString hash = digest.AsString();
496 cout << hash << " " << root_file_path << (size_only ? " (sizes only)" : " (data content)") << endl;
497
499 root_file->Close();
500 return hash;
501}
502
503// compare the data content of two ROOT files ignoring metadata (timestamps in the file header, TKey and TBasket);
504// if size_only = kTRUE, only entry counts and uncompressed data sizes are compared (no class dictionaries required)
505void compare_files(TString root_file_path1, TString root_file_path2, Bool_t size_only = kFALSE)
506{
507 gSystem->ExpandPathName(root_file_path1);
508 gSystem->ExpandPathName(root_file_path2);
509
510 // check that the files exist and can be opened
511 if (gSystem->AccessPathName(root_file_path1.Data())) {
512 cerr << "Error: file does not exist or is not accessible: " << root_file_path1 << endl;
513 return;
514 }
515 if (gSystem->AccessPathName(root_file_path2.Data())) {
516 cerr << "Error: file does not exist or is not accessible: " << root_file_path2 << endl;
517 return;
518 }
519
520 TFile* root_file1 = TFile::Open(root_file_path1);
521 if ((root_file1 == nullptr) || root_file1->IsZombie()) {
522 cerr << "Error: could not open ROOT file: " << root_file_path1 << endl;
523 return;
524 }
525 TFile* root_file2 = TFile::Open(root_file_path2);
526 if ((root_file2 == nullptr) || root_file2->IsZombie()) {
527 cerr << "Error: could not open ROOT file: " << root_file_path2 << endl;
528 root_file1->Close();
529 return;
530 }
531
532 // do not attach histograms being read to the current directory, so they can be safely deleted
534
535 bool is_equal = CompareDirectories(root_file1, root_file2, "/", size_only);
536
537 cout << endl;
538 if (is_equal)
539 cout << "The files are IDENTICAL ("
540 << (size_only ? "by entry counts and data sizes only" : "data content, ignoring metadata") << ")" << endl;
541 else
542 cout << "The files are DIFFERENT" << endl;
543
545 root_file1->Close();
546 root_file2->Close();
547}
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
unsigned char UChar_t
constexpr Bool_t kFALSE
long long Long64_t
R__EXTERN TGeoManager * gGeoManager
#define gROOT
char * Form(const char *fmt,...)
R__EXTERN TSystem * gSystem
const char * GetClassName() const override
char * GetObject() const
virtual TLeaf * GetLeaf(const char *name) const
TObjArray * GetListOfBranches()
virtual Int_t GetEntry(Long64_t entry=0, Int_t getall=0)
Long64_t GetEntries() const
Long64_t GetTotBytes(Option_t *option="") const
TObjArray * GetListOfLeaves()
Int_t Length() const
char * Buffer() const
void Destructor(ObjectPtr obj, Bool_t dtorOnly=kFALSE)
static TClass * GetClass(Bool_t load=kTRUE, Bool_t silent=kFALSE)
Bool_t InheritsFrom(const char *cl) const override
void Streamer(TBuffer &) override
static TClass * Class()
virtual TDirectory * GetDirectory(const char *namecycle, Bool_t printError=false, const char *funcname="GetDirectory")
virtual TKey * GetKey(const char *, Short_t=9999) const
virtual TList * GetListOfKeys() const
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
void Close(Option_t *option="") override
static void AddDirectory(Bool_t add=kTRUE)
Int_t GetObjlen() const
virtual const char * GetClassName() const
virtual void * ReadObjectAny(const TClass *expectedClass)
virtual TObject * ReadObj()
virtual void * GetValuePointer() const
virtual Int_t GetLenType() const
virtual Int_t GetLen() const
const char * AsString() const
void Update(const UChar_t *buf, UInt_t len)
void Final()
const char * GetName() const override
Int_t GetEntries() const override
TObject * At(Int_t idx) const override
TObject * FindObject(const char *name) const override
R__ALWAYS_INLINE Bool_t IsZombie() const
const char * Data() const
virtual TBranch * GetBranch(const char *name)
virtual Long64_t GetEntries() const
virtual Long64_t GetTotBytes() const
virtual TObjArray * GetListOfBranches()
static TClass * Class()
static const char * hash_scheme_tag
void DetachGeometry()
void HashBlock(TMD5 &digest, const void *data, Int_t length)
bool CompareBranchSizes(TBranch *branch1, TBranch *branch2, const TString &obj_path)
void HashText(TMD5 &digest, const char *text)
void HashDirectory(TMD5 &digest, TDirectory *dir, const TString &dir_path, bool size_only)
void ForgetGeometries()
void HashNumber(TMD5 &digest, Long64_t number)
void compare_files(TString root_file_path1, TString root_file_path2, Bool_t size_only=kFALSE)
void HashBranchSizes(TMD5 &digest, TBranch *branch)
TString hash_file(TString root_file_path, Bool_t size_only=kFALSE)
void HashTree(TMD5 &digest, TTree *tree, bool size_only)
bool CompareDirectories(TDirectory *dir1, TDirectory *dir2, const TString &dir_path, bool size_only)
bool CompareTrees(TTree *tree1, TTree *tree2, const TString &obj_path, bool size_only)
void HashObject(TMD5 &digest, void *obj, TClass *obj_class)
bool AreObjectsEqual(void *obj1, void *obj2, TClass *obj_class)
Definition json.hpp:4446