Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-01 07:53:31

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 // This ROOT script compares two ROOT files in an order-insensitive way. Its
0010 // intended use is to compare the output of a single-threaded and multi-threaded
0011 // programs in order to check that results are perfectly reproducible.
0012 //
0013 // As a current limitation, which may be lifted in the future, the script does
0014 // all of its processing in RAM, which means that the input dataset must fit in
0015 // RAM. So do not try to run this on terabytes of data. You don't need that much
0016 // data to check that your multithreaded program runs well anyhow.
0017 //
0018 // Another limitation is that the comparison relies on perfect output
0019 // reproducibility, which is a very costly guarantee to achieve in a
0020 // multi-threaded environment. If you want to compare "slightly different"
0021 // outputs, this script will not work as currently written. I cannot think of a
0022 // way in which imperfect reproducibility could be checked in a manner which
0023 // doesn't depend on the details of the data being compared.
0024 
0025 #include <cstring>
0026 #include <map>
0027 #include <string>
0028 #include <utility>
0029 #include <vector>
0030 
0031 #include "TBranch.h"
0032 #include "TEfficiency.h"
0033 #include "TFile.h"
0034 #include "TH2F.h"
0035 #include "TKey.h"
0036 #include "TObject.h"
0037 #include "TProfile.h"
0038 #include "TTree.h"
0039 #include "TTreeReader.h"
0040 #include "TVectorT.h"
0041 #include "compareRootFiles.hpp"
0042 
0043 // Minimal mechanism for assertion checking and comparison
0044 #define ASSERT(pred, msg)          \
0045   if (!(pred)) {                   \
0046     std::cout << msg << std::endl; \
0047     return 1;                      \
0048   }
0049 
0050 #define ASSERT_EQUAL(v1, v2, msg) \
0051   ASSERT((v1) == (v2), msg << "(" << (v1) << " vs " << (v2) << ") ")
0052 
0053 #define ASSERT_STR_EQUAL(s1, s2, msg) \
0054   ASSERT(strcmp((s1), (s2)) == 0, msg << " (" << (s1) << " vs " << (s2) << ") ")
0055 
0056 // This script returns 0 if the files have identical contents except for event
0057 // ordering, and a nonzero result if the contents differ or an error occurred.
0058 //
0059 // If the optional dump_data_on_failure flag is set, it will also dump the
0060 // mismatching event data to stdout on failure for manual inspection.
0061 //
0062 // If the optional skip_unsupported_branches flag is set, the script will ignore
0063 // unsupported branch types in the input file instead of aborting.
0064 //
0065 
0066 int compareRootFiles(const std::string& file1, const std::string& file2,
0067                      bool dump_data_on_failure = false,
0068                      bool skip_unsupported_branches = false) {
0069   std::cout << "Comparing ROOT files " << file1 << " and " << file2
0070             << std::endl;
0071 
0072   std::cout << "* Opening the files..." << std::endl;
0073   HomogeneousPair<TFile> files{file1.c_str(), file2.c_str()};
0074   if (files.first.IsZombie()) {
0075     std::cout << "  - Could not open file " << file1 << "!" << std::endl;
0076     return 2;
0077   } else if (files.second.IsZombie()) {
0078     std::cout << "  - Could not open file " << file2 << "!" << std::endl;
0079     return 2;
0080   }
0081 
0082   std::cout << "* Extracting file keys..." << std::endl;
0083   HomogeneousPair<std::vector<TKey*>> fileKeys;
0084   {
0085     // This is how we would extract keys from one file
0086     const auto loadKeys = [](const TFile& file, std::vector<TKey*>& target) {
0087       const int keyCount = file.GetNkeys();
0088       target.reserve(keyCount);
0089       TIter keyIter{file.GetListOfKeys()};
0090       for (int i = 0; i < keyCount; ++i) {
0091         target.emplace_back(dynamic_cast<TKey*>(keyIter()));
0092       }
0093     };
0094 
0095     // Do it for each of our files
0096     loadKeys(files.first, fileKeys.first);
0097     loadKeys(files.second, fileKeys.second);
0098   }
0099 
0100   std::cout << "* Selecting the latest key cycle..." << std::endl;
0101   std::vector<HomogeneousPair<TKey*>> keyPairs;
0102   {
0103     // For each file and for each key name, we want to know what is the latest
0104     // key cycle, and who is the associated key object
0105     using KeyMetadata = std::pair<short, TKey*>;
0106     using FileMetadata = std::map<std::string, KeyMetadata>;
0107     HomogeneousPair<FileMetadata> metadata;
0108 
0109     // This is how we compute this metadata for a single file
0110     const auto findLatestCycle = [](const std::vector<TKey*>& keys,
0111                                     FileMetadata& target) {
0112       // Iterate through the file's keys
0113       for (const auto key : keys) {
0114         // Extract information about the active key
0115         const std::string keyName{key->GetName()};
0116         const short newCycle{key->GetCycle()};
0117 
0118         // Do we already know of a key with the same name?
0119         auto latestCycleIter = target.find(keyName);
0120         if (latestCycleIter != target.end()) {
0121           // If so, keep the key with the most recent cycle number
0122           auto& latestCycleMetadata = latestCycleIter->second;
0123           if (newCycle > latestCycleMetadata.first) {
0124             latestCycleMetadata = {newCycle, key};
0125           }
0126         } else {
0127           // If not, this is obviously the most recent key we've seen so
0128           // far
0129           target.emplace(keyName, KeyMetadata{newCycle, key});
0130         }
0131       }
0132     };
0133 
0134     // We'll compute this information for both of our files...
0135     std::cout << "  - Finding the latest cycle for each file..." << std::endl;
0136     findLatestCycle(fileKeys.first, metadata.first);
0137     findLatestCycle(fileKeys.second, metadata.second);
0138 
0139     // ...and then we'll group the latest keys by name, detect keys which only
0140     // exist in a single file along the way, and report that as an error
0141     std::cout << "  - Grouping per-file latest keys..." << std::endl;
0142     {
0143       // Make sure that both files have the same amount of keys once duplicate
0144       // versions are removed
0145       const auto f1KeyCount = metadata.first.size();
0146       const auto f2KeyCount = metadata.second.size();
0147       ASSERT_EQUAL(f1KeyCount, f2KeyCount,
0148                    "    o Number of keys does not match");
0149       keyPairs.reserve(f1KeyCount);
0150 
0151       // Iterate through the keys, in the same order (as guaranteed by std::map)
0152       for (auto f1MetadataIter = metadata.first.cbegin(),
0153                 f2MetadataIter = metadata.second.cbegin();
0154            f1MetadataIter != metadata.first.cend();
0155            ++f1MetadataIter, ++f2MetadataIter) {
0156         // Do the keys have the same name?
0157         const auto& f1KeyName = f1MetadataIter->first;
0158         const auto& f2KeyName = f2MetadataIter->first;
0159         ASSERT_EQUAL(f1KeyName, f2KeyName, "    o Key names do not match");
0160 
0161         // If so, extract the associated key pair
0162         keyPairs.emplace_back(f1MetadataIter->second.second,
0163                               f2MetadataIter->second.second);
0164       }
0165     }
0166   }
0167 
0168   std::cout << "* Comparing key metadata..." << std::endl;
0169   for (const auto& keyPair : keyPairs) {
0170     const auto& key1 = keyPair.first;
0171     const auto& key2 = keyPair.second;
0172 
0173     ASSERT_STR_EQUAL(key1->GetClassName(), key2->GetClassName(),
0174                      "  - Class name does not match!");
0175     ASSERT_STR_EQUAL(key1->GetTitle(), key2->GetTitle(),
0176                      "  - Title does not match!");
0177     ASSERT_EQUAL(key1->GetVersion(), key2->GetVersion(),
0178                  "  - Key version does not match!");
0179   }
0180 
0181   // NOTE: The current version of this script only supports some file contents.
0182   //       It may be extended later if the need for other data formats arise.
0183   std::cout << "* Extracting TTrees..." << std::endl;
0184   std::vector<HomogeneousPair<TTree*>> treePairs;
0185   std::vector<HomogeneousPair<TVectorT<float>*>> vectorPairs;
0186   std::vector<HomogeneousPair<TEfficiency*>> efficiencyPairs;
0187   std::vector<HomogeneousPair<TProfile*>> profilePairs;
0188   std::vector<HomogeneousPair<TH2F*>> th2fPairs;
0189 
0190   for (const auto& keyPair : keyPairs) {
0191     TObject* obj1 = keyPair.first->ReadObj();
0192     TObject* obj2 = keyPair.second->ReadObj();
0193 
0194     ASSERT_STR_EQUAL(obj1->ClassName(), obj2->ClassName(),
0195                      "  - Object type does not match!");
0196 
0197     // Check if the object is a TTree
0198     bool isTTree = (strcmp(obj1->ClassName(), "TTree") == 0);
0199 
0200     if (isTTree) {
0201       TTree* tree1 = dynamic_cast<TTree*>(obj1);
0202       TTree* tree2 = dynamic_cast<TTree*>(obj2);
0203       if (tree1 != nullptr && tree2 != nullptr) {
0204         treePairs.emplace_back(tree1, tree2);
0205       }
0206       continue;  // Skip the rest of the loop
0207     }
0208 
0209     bool isTVector = (strcmp(obj1->ClassName(), "TVectorT<float>") == 0);
0210 
0211     if (isTVector) {
0212       TVectorT<float>* vector1 = dynamic_cast<TVectorT<float>*>(obj1);
0213       TVectorT<float>* vector2 = dynamic_cast<TVectorT<float>*>(obj2);
0214       if (vector1 != nullptr && vector2 != nullptr) {
0215         vectorPairs.emplace_back(vector1, vector2);
0216       }
0217       continue;  // Skip the rest of the loop
0218     }
0219 
0220     bool isTEfficiency = (strcmp(obj1->ClassName(), "TEfficiency") == 0);
0221 
0222     if (isTEfficiency) {
0223       TEfficiency* efficiency1 = dynamic_cast<TEfficiency*>(obj1);
0224       TEfficiency* efficiency2 = dynamic_cast<TEfficiency*>(obj2);
0225       if (efficiency1 != nullptr && efficiency2 != nullptr) {
0226         efficiencyPairs.emplace_back(efficiency1, efficiency2);
0227       }
0228       continue;  // Skip the rest of the loop
0229     }
0230 
0231     bool isTProfile = (strcmp(obj1->ClassName(), "TProfile") == 0);
0232 
0233     if (isTProfile) {
0234       TProfile* profile1 = dynamic_cast<TProfile*>(obj1);
0235       TProfile* profile2 = dynamic_cast<TProfile*>(obj2);
0236       if (profile1 != nullptr && profile2 != nullptr) {
0237         profilePairs.emplace_back(profile1, profile2);
0238       }
0239       continue;  // Skip the rest of the loop
0240     }
0241 
0242     bool isTH2F = (strcmp(obj1->ClassName(), "TH2F") == 0);
0243 
0244     if (isTH2F) {
0245       TH2F* th2f1 = dynamic_cast<TH2F*>(obj1);
0246       TH2F* th2f2 = dynamic_cast<TH2F*>(obj2);
0247       if (th2f1 != nullptr && th2f2 != nullptr) {
0248         th2fPairs.emplace_back(th2f1, th2f2);
0249       }
0250       continue;  // Skip the rest of the loop
0251     }
0252 
0253     ASSERT(false, "  - Input " << obj1->ClassName() << " is not supported!");
0254   }
0255 
0256   std::cout << "* Comparing the trees..." << std::endl;
0257   for (const auto& treePair : treePairs) {
0258     const auto& tree1 = treePair.first;
0259     const auto& tree2 = treePair.second;
0260 
0261     std::cout << "  - Comparing tree " << tree1->GetName() << "..."
0262               << std::endl;
0263 
0264     std::cout << "    o Comparing tree-wide metadata..." << std::endl;
0265     const std::size_t t1EntryCount = tree1->GetEntries();
0266     {
0267       const std::size_t t2EntryCount = tree2->GetEntries();
0268       ASSERT_EQUAL(t1EntryCount, t2EntryCount,
0269                    "      ~ Number of entries does not match!");
0270     }
0271 
0272     if (t1EntryCount == 0) {
0273       std::cout << "    o Skipping empty tree!" << std::endl;
0274       continue;
0275     }
0276 
0277     std::cout << "    o Preparing for tree readout..." << std::endl;
0278     TTreeReader t1Reader(tree1);
0279     TTreeReader t2Reader(tree2);
0280     BranchComparisonHarness::TreeMetadata treeMetadata{t1Reader, t2Reader,
0281                                                        t1EntryCount};
0282 
0283     std::cout << "    o Comparing branch metadata..." << std::endl;
0284     std::vector<HomogeneousPair<TBranch*>> branchPairs;
0285     {
0286       // Check number of branches and allocate branch storage
0287       const int t1BranchCount = tree1->GetNbranches();
0288       const int t2BranchCount = tree2->GetNbranches();
0289       ASSERT_EQUAL(t1BranchCount, t2BranchCount,
0290                    "      ~ Number of branches does not match!");
0291       branchPairs.reserve(t1BranchCount);
0292 
0293       // Extract branches using TTree::GetListOfBranches()
0294       TIter t1BranchIter{tree1->GetListOfBranches()};
0295       TIter t2BranchIter{tree2->GetListOfBranches()};
0296       for (int i = 0; i < t1BranchCount; ++i) {
0297         branchPairs.emplace_back(dynamic_cast<TBranch*>(t1BranchIter()),
0298                                  dynamic_cast<TBranch*>(t2BranchIter()));
0299       }
0300     }
0301 
0302     std::cout << "    o Setting up branch-specific processing..." << std::endl;
0303     std::vector<BranchComparisonHarness> branchComparisonHarnesses;
0304     branchComparisonHarnesses.reserve(branchPairs.size());
0305     for (const auto& branchPair : branchPairs) {
0306       const auto& branch1 = branchPair.first;
0307       const auto& branch2 = branchPair.second;
0308 
0309       std::cout << "      ~ Checking branch metadata..." << std::endl;
0310       std::string b1ClassName, b1BranchName;
0311       EDataType b1DataType{};
0312       {
0313         std::string b2ClassName, b2BranchName;
0314         EDataType b2DataType{};
0315         TClass* unused = nullptr;
0316 
0317         b1ClassName = branch1->GetClassName();
0318         b2ClassName = branch2->GetClassName();
0319         ASSERT_EQUAL(b1ClassName, b2ClassName,
0320                      "        + Class name does not match!");
0321         branch1->GetExpectedType(unused, b1DataType);
0322         branch2->GetExpectedType(unused, b2DataType);
0323         ASSERT_EQUAL(b1DataType, b2DataType,
0324                      "        + Raw data type does not match!");
0325         const int b1LeafCount = branch1->GetNleaves();
0326         const int b2LeafCount = branch2->GetNleaves();
0327         ASSERT_EQUAL(b1LeafCount, b2LeafCount,
0328                      "        + Number of leaves does not match!");
0329         ASSERT_EQUAL(
0330             b1LeafCount, 1,
0331             "        + Branches with several leaves are not supported!");
0332         b1BranchName = branch1->GetName();
0333         b2BranchName = branch2->GetName();
0334         ASSERT_EQUAL(b1BranchName, b2BranchName,
0335                      "        + Branch name does not match!");
0336       }
0337 
0338       std::cout << "      ~ Building comparison harness for branch "
0339                 << b1BranchName << "..." << std::endl;
0340       try {
0341         auto branchHarness = BranchComparisonHarness::create(
0342             treeMetadata, b1BranchName, b1DataType, b1ClassName);
0343         branchComparisonHarnesses.emplace_back(std::move(branchHarness));
0344       } catch (const BranchComparisonHarness::UnsupportedBranchType&) {
0345         // When encountering an unsupported branch type, we can either skip
0346         // the branch or abort depending on configuration
0347         std::cout << "        + Unsupported branch type! "
0348                   << "(eDataType: " << b1DataType << ", ClassName: \""
0349                   << b1ClassName << "\")" << std::endl;
0350         if (skip_unsupported_branches) {
0351           continue;
0352         } else {
0353           return 3;
0354         }
0355       }
0356     }
0357 
0358     std::cout << "    o Reading event data..." << std::endl;
0359     for (std::size_t i = 0; i < t1EntryCount; ++i) {
0360       // Move to the next TTree entry (= next event)
0361       t1Reader.Next();
0362       t2Reader.Next();
0363 
0364       // Load the data associated with each branch
0365       for (auto& branchHarness : branchComparisonHarnesses) {
0366         branchHarness.loadCurrentEvent();
0367       }
0368     }
0369 
0370     std::cout << "    o Sorting the first tree..." << std::endl;
0371     {
0372       std::cout << "      ~ Defining event comparison operator..." << std::endl;
0373       IndexComparator t1CompareEvents = [&branchComparisonHarnesses](
0374                                             std::size_t i,
0375                                             std::size_t j) -> Ordering {
0376         for (auto& branchHarness : branchComparisonHarnesses) {
0377           const auto order = branchHarness.sortHarness.first.first(i, j);
0378           if (order != Ordering::EQUAL) {
0379             return order;
0380           }
0381         }
0382         return Ordering::EQUAL;
0383       };
0384 
0385       std::cout << "      ~ Defining event swapping operator..." << std::endl;
0386       IndexSwapper t1SwapEvents = [&branchComparisonHarnesses](std::size_t i,
0387                                                                std::size_t j) {
0388         for (auto& branchHarness : branchComparisonHarnesses) {
0389           branchHarness.sortHarness.first.second(i, j);
0390         }
0391       };
0392 
0393       std::cout << "      ~ Running quicksort on the tree..." << std::endl;
0394       quickSort(0, t1EntryCount - 1, t1CompareEvents, t1SwapEvents);
0395     }
0396 
0397     std::cout << "    o Sorting the second tree..." << std::endl;
0398     {
0399       std::cout << "      ~ Defining event comparison operator..." << std::endl;
0400       IndexComparator t2CompareEvents = [&branchComparisonHarnesses](
0401                                             std::size_t i,
0402                                             std::size_t j) -> Ordering {
0403         for (auto& branchHarness : branchComparisonHarnesses) {
0404           const auto order = branchHarness.sortHarness.second.first(i, j);
0405           if (order != Ordering::EQUAL) {
0406             return order;
0407           }
0408         }
0409         return Ordering::EQUAL;
0410       };
0411 
0412       std::cout << "      ~ Defining event swapping operator..." << std::endl;
0413       IndexSwapper t2SwapEvents = [&branchComparisonHarnesses](std::size_t i,
0414                                                                std::size_t j) {
0415         for (auto& branchHarness : branchComparisonHarnesses) {
0416           branchHarness.sortHarness.second.second(i, j);
0417         }
0418       };
0419 
0420       std::cout << "      ~ Running quicksort on the tree..." << std::endl;
0421       quickSort(0, t1EntryCount - 1, t2CompareEvents, t2SwapEvents);
0422     }
0423 
0424     std::cout << "    o Checking that both trees are now equal..." << std::endl;
0425     for (auto& branchHarness : branchComparisonHarnesses) {
0426       std::cout << "      ~ Comparing branch " << branchHarness.branchName
0427                 << "..." << std::endl;
0428       if (!branchHarness.eventDataEqual()) {
0429         std::cout << "        + Branch contents do not match!" << std::endl;
0430         if (dump_data_on_failure) {
0431           std::cout << "        + Dumping branch contents:" << std::endl;
0432           branchHarness.dumpEventData();
0433         }
0434         return 4;
0435       }
0436     }
0437   }
0438 
0439   std::cout << "* Comparing the vectors..." << std::endl;
0440   for (const auto& vectorPair : vectorPairs) {
0441     const auto& vector1 = vectorPair.first;
0442     const auto& vector2 = vectorPair.second;
0443 
0444     std::cout << "  - Comparing vector " << vector1->GetName() << "..."
0445               << std::endl;
0446 
0447     std::cout << "    o Comparing vector-wide metadata..." << std::endl;
0448     const std::size_t v1Size = vector1->GetNoElements();
0449     {
0450       const std::size_t v2Size = vector2->GetNoElements();
0451       ASSERT_EQUAL(v1Size, v2Size,
0452                    "      ~ Number of elements does not match!");
0453     }
0454 
0455     if (v1Size == 0) {
0456       std::cout << "    o Skipping empty vector!" << std::endl;
0457       continue;
0458     }
0459 
0460     std::cout << "    o Comparing vector data..." << std::endl;
0461     for (std::size_t i = 0; i < v1Size; ++i) {
0462       ASSERT_EQUAL(vector1->operator[](i), vector2->operator[](i),
0463                    "      ~ Vector elements do not match!");
0464     }
0465   }
0466 
0467   std::cout << "* Comparing the efficiencies..." << std::endl;
0468   for (const auto& efficiencyPair : efficiencyPairs) {
0469     const auto& efficiency1 = efficiencyPair.first;
0470     const auto& efficiency2 = efficiencyPair.second;
0471 
0472     std::cout << "  - Comparing efficiency " << efficiency1->GetName() << "..."
0473               << std::endl;
0474 
0475     std::cout << "    o Comparing efficiency-wide metadata..." << std::endl;
0476     const auto e1Size = static_cast<std::size_t>(
0477         efficiency1->GetTotalHistogram()->GetEntries());
0478     {
0479       const auto e2Size = static_cast<std::size_t>(
0480           efficiency2->GetTotalHistogram()->GetEntries());
0481       ASSERT_EQUAL(e1Size, e2Size, "      ~ Number of entries does not match!");
0482     }
0483 
0484     if (e1Size == 0) {
0485       std::cout << "    o Skipping empty efficiency!" << std::endl;
0486       continue;
0487     }
0488 
0489     std::cout << "    o Comparing efficiency data..." << std::endl;
0490     for (std::size_t i = 0; i < e1Size; ++i) {
0491       ASSERT_EQUAL(efficiency1->GetEfficiency(i), efficiency2->GetEfficiency(i),
0492                    "      ~ Efficiency elements do not match!");
0493     }
0494   }
0495 
0496   std::cout << "* Comparing the profiles..." << std::endl;
0497   for (const auto& profilePair : profilePairs) {
0498     const auto& profile1 = profilePair.first;
0499     const auto& profile2 = profilePair.second;
0500 
0501     std::cout << "  - Comparing profile " << profile1->GetName() << "..."
0502               << std::endl;
0503 
0504     std::cout << "    o Comparing profile-wide metadata..." << std::endl;
0505     const auto p1Size = static_cast<std::size_t>(profile1->GetEntries());
0506     {
0507       const auto p2Size = static_cast<std::size_t>(profile2->GetEntries());
0508       ASSERT_EQUAL(p1Size, p2Size, "      ~ Number of entries does not match!");
0509     }
0510 
0511     if (p1Size == 0) {
0512       std::cout << "    o Skipping empty profile!" << std::endl;
0513       continue;
0514     }
0515 
0516     std::cout << "    o Comparing profile data..." << std::endl;
0517     for (std::size_t i = 0; i < p1Size; ++i) {
0518       ASSERT_EQUAL(profile1->GetBinContent(i), profile2->GetBinContent(i),
0519                    "      ~ Profile elements do not match!");
0520     }
0521   }
0522 
0523   std::cout << "* Comparing the TH2Fs..." << std::endl;
0524   for (const auto& th2fPair : th2fPairs) {
0525     const auto& th2f1 = th2fPair.first;
0526     const auto& th2f2 = th2fPair.second;
0527 
0528     std::cout << "  - Comparing TH2F " << th2f1->GetName() << "..."
0529               << std::endl;
0530 
0531     std::cout << "    o Comparing TH2F-wide metadata..." << std::endl;
0532     const auto th2f1Size = static_cast<std::size_t>(th2f1->GetEntries());
0533     {
0534       const auto th2f2Size = static_cast<std::size_t>(th2f2->GetEntries());
0535       ASSERT_EQUAL(th2f1Size, th2f2Size,
0536                    "      ~ Number of entries does not match!");
0537     }
0538 
0539     if (th2f1Size == 0) {
0540       std::cout << "    o Skipping empty TH2F!" << std::endl;
0541       continue;
0542     }
0543 
0544     std::cout << "    o Comparing TH2F data..." << std::endl;
0545     for (std::size_t i = 0; i < th2f1Size; ++i) {
0546       ASSERT_EQUAL(th2f1->GetBinContent(i), th2f2->GetBinContent(i),
0547                    "      ~ TH2F elements do not match!");
0548     }
0549   }
0550 
0551   std::cout << "* Input files are equal, event order aside!" << std::endl;
0552   return 0;
0553 }
0554 
0555 #ifndef __CLING__
0556 int main(int argc, char* argv[]) {
0557   std::string file1{};
0558   std::string file2{};
0559   bool dumpDataOnFailure = false;
0560   bool skipUnsupportedBranches = false;
0561 
0562   std::vector<std::string> args(argv + 1, argv + argc);
0563 
0564   if (args.size() < 2) {
0565     std::cerr << "Usage: " << argv[0]
0566               << " file1 file2 [--dump-data-on-failure] "
0567                  "[--skip-unsupported-branches]\n";
0568     return 1;
0569   }
0570 
0571   file1 = args[0];
0572   file2 = args[1];
0573 
0574   for (size_t i = 2; i < args.size(); ++i) {
0575     if (args[i] == "--dump-data-on-failure") {
0576       dumpDataOnFailure = true;
0577     } else if (args[i] == "--skip-unsupported-branches") {
0578       skipUnsupportedBranches = true;
0579     } else {
0580       std::cerr << "Unknown option: " << args[i] << "\n";
0581       return 1;
0582     }
0583   }
0584 
0585   // Output parsed values (for demonstration)
0586   std::cout << "file1: " << file1 << "\n";
0587   std::cout << "file2: " << file2 << "\n";
0588   std::cout << "dumpDataOnFailure: " << (dumpDataOnFailure ? "true" : "false")
0589             << "\n";
0590   std::cout << "skipUnsupportedBranches: "
0591             << (skipUnsupportedBranches ? "true" : "false") << "\n";
0592 
0593   const int result = compareRootFiles(file1, file2, dumpDataOnFailure,
0594                                       skipUnsupportedBranches);
0595 
0596   return result;
0597 }
0598 #endif