LDMX Software
FunctionalCoreTest.cxx
1#include <catch2/catch_approx.hpp>
2#include <catch2/catch_test_macros.hpp>
3#include <catch2/matchers/catch_matchers.hpp>
4#include <cstdio> //for remove
5
6#include "Framework/EventFile.h"
8#include "Framework/Process.h"
9#include "Framework/RunHeader.h"
10#include "Hcal/Event/HcalHit.h"
13#include "TFile.h" //to open and check root files
14#include "TH1F.h" //for test histogram
15#include "TTreeReader.h" //to check output event files
16
17using Catch::Approx;
18
19namespace framework {
20namespace test {
21
41class TestProducer : public Producer {
44
47
48 public:
49 TestProducer(const std::string& name, Process& p) : Producer(name, p) {}
50 ~TestProducer() {}
51
52 void configure(framework::config::Parameters& p) final override {
53 createRunHeader_ = p.getParameter<bool>("createRunHeader");
54 }
55
56 void beforeNewRun(ldmx::RunHeader& header) final override {
57 if (not createRunHeader_) return;
58 header.setIntParameter("Should Be Run Number", header.getRunNumber());
59 return;
60 }
61
62 void produce(framework::Event& event) final override {
63 int i_event = event.getEventNumber();
64
65 REQUIRE(i_event > 0);
66
67 std::vector<ldmx::CalorimeterHit> caloHits;
68 for (int i = 0; i < i_event; i++) {
69 caloHits.emplace_back();
70 caloHits.back().setID(i_event * 10 + i);
71 }
72
73 REQUIRE_NOTHROW(event.add("TestCollection", caloHits));
74
75 ldmx::HcalHit maxPEHit;
76 maxPEHit.setID(i_event);
77
79 res.setMaxPEHit(maxPEHit);
80 res.setVetoResult(i_event % 2 == 0);
81
82 REQUIRE_NOTHROW(event.add("TestObject", res));
83
84 events_ = i_event;
85
86 std::vector<int> event_indices = {i_event, i_event};
87 REQUIRE_NOTHROW(event.add("EventIndex", event_indices));
88
89 float test_float = i_event * 0.1;
90 REQUIRE_NOTHROW(event.add("EventTenth", test_float));
91
92 if (res.passesVeto()) setStorageHint(StorageControl::Hint::MustKeep);
93
94 return;
95 }
96}; // TestProducer
97
107class TestAnalyzer : public Analyzer {
108 public:
109 TestAnalyzer(const std::string& name, Process& p) : Analyzer(name, p) {}
110 ~TestAnalyzer() {}
111
112 void onProcessStart() final override {
113 REQUIRE_NOTHROW(getHistoDirectory());
114 test_hist_ = new TH1F("test_hist_", "Test Histogram", 101, -50, 50);
115 test_hist_->SetCanExtend(TH1::kAllAxes);
116 }
117
118 void analyze(const framework::Event& event) final override {
119 int i_event = event.getEventNumber();
120
121 REQUIRE(i_event > 0);
122
123 const std::vector<ldmx::CalorimeterHit>& caloHits =
124 event.getCollection<ldmx::CalorimeterHit>("TestCollection");
125
126 CHECK(caloHits.size() == i_event);
127 for (unsigned int i = 0; i < caloHits.size(); i++) {
128 CHECK(caloHits.at(i).getID() == i_event * 10 + i);
129 test_hist_->Fill(caloHits.at(i).getID());
130 }
131
132 const ldmx::HcalVetoResult& vetoRes =
133 event.getObject<ldmx::HcalVetoResult>("TestObject");
134
135 auto maxPEHit{vetoRes.getMaxPEHit()};
136
137 CHECK(maxPEHit.getID() == i_event);
138 CHECK(vetoRes.passesVeto() == (i_event % 2 == 0));
139
140 const float& tenth_event = event.getObject<float>("EventTenth");
141 CHECK(tenth_event == Approx(i_event * 0.1));
142
143 const std::vector<int>& i_event_from_bus =
144 event.getCollection<int>("EventIndex");
145
146 CHECK(i_event_from_bus.size() == 2);
147 CHECK(i_event_from_bus.at(0) == i_event);
148 CHECK(i_event_from_bus.at(1) == i_event);
149
150 return;
151 }
152
153 private:
156}; // TestAnalyzer
157
173class isGoodHistogramFile : public Catch::Matchers::MatcherBase<std::string> {
174 private:
177
178 public:
185
193 bool match(const std::string& filename) const override {
194 // Open file
195 TFile* f = TFile::Open(filename.c_str());
196 if (!f) return false;
197 TDirectory* d = (TDirectory*)f->Get("TestAnalyzer");
198 if (!d) return false;
199 TH1F* h = (TH1F*)d->Get("test_hist_");
200 if (!h) return false;
201
202 return (h->GetEntries() == correctGetEntries_);
203 }
204
211 virtual std::string describe() const override {
212 std::ostringstream ss;
213 ss << "has the histogram 'TestAnalyzer/test_hist_' with the number of "
214 "entries "
216 return ss.str();
217 }
218}; // isGoodHistogramFile
219
235class isGoodEventFile : public Catch::Matchers::MatcherBase<std::string> {
236 private:
238 std::string pass_;
239
242
244 int runs_;
245
248
251
252 public:
258 isGoodEventFile(const std::string& pass, const int& entries, const int& runs,
259 bool existColl = true, bool existObj = true)
260 : pass_(pass),
261 entries_(entries),
262 runs_(runs),
263 existCollection_(existColl),
264 existObject_(existObj) {}
265
274 bool match(const std::string& filename) const override {
275 TFile* f = TFile::Open(filename.c_str());
276 if (!f) return false;
277
278 TTreeReader events("LDMX_Events", f);
279
280 if (events.GetEntries(true) != entries_) {
281 f->Close();
282 return false;
283 }
284
285 // Event tree should _always_ have the ldmx::EventHeader
286 TTreeReaderValue<ldmx::EventHeader> header(events, "EventHeader");
287
288 if (existCollection_) {
289 // make sure collection matches pattern
290 TTreeReaderValue<std::vector<ldmx::CalorimeterHit>> collection(
291 events, ("TestCollection_" + pass_).c_str());
292 while (events.Next()) {
293 if (collection->size() != header->getEventNumber()) {
294 f->Close();
295 return false;
296 }
297 for (unsigned int i = 0; i < collection->size(); i++)
298 if (collection->at(i).getID() != header->getEventNumber() * 10 + i) {
299 f->Close();
300 return false;
301 }
302 }
303 // restart in case checking object as well
304 events.Restart();
305 } else {
306 // check to make sure collection is NOT there
307 auto t{(TTree*)f->Get("LDMX_Events")};
308 if (t and t->GetBranch(("TestCollection_" + pass_).c_str())) {
309 f->Close();
310 return false;
311 }
312 }
313
314 if (existObject_) {
315 // make sure object matches pattern
316 TTreeReaderValue<ldmx::HcalVetoResult> object(
317 events, ("TestObject_" + pass_).c_str());
318 while (events.Next()) {
319 if (object->getMaxPEHit().getID() != header->getEventNumber()) {
320 f->Close();
321 return false;
322 }
323 }
324 } else {
325 // check to make sure object is NOT there
326 auto t{(TTree*)f->Get("LDMX_Events")};
327 if (t and t->GetBranch(("TestObject_" + pass_).c_str())) {
328 f->Close();
329 return false;
330 }
331 }
332
333 TTreeReader runs("LDMX_Run", f);
334
335 if (runs.GetEntries(true) != runs_) {
336 f->Close();
337 return false;
338 }
339
340 TTreeReaderValue<ldmx::RunHeader> runHeader(runs, "RunHeader");
341
342 while (runs.Next()) {
343 if (runHeader->getRunNumber() !=
344 runHeader->getIntParameter("Should Be Run Number")) {
345 f->Close();
346 return false;
347 }
348 }
349
350 f->Close();
351
352 return true;
353 }
354
358 virtual std::string describe() const override {
359 std::ostringstream ss;
360 ss << "can be opened and has the correct number of entries in the event "
361 "tree and the run tree.";
362
363 ss << " TestCollection_" << pass_ << " was verified to ";
365 ss << " be the correct pattern.";
366 else
367 ss << " not be in the file.";
368
369 ss << " TestObject_" << pass_ << " was verified to ";
370 if (existObject_)
371 ss << " be the correct pattern.";
372 else
373 ss << " not be in the file.";
374
375 return ss.str();
376 }
377
378}; // isGoodEventFile
379
388static bool removeFile(const std::string& filepath) {
389 return remove(filepath.c_str()) == 0;
390}
391
395static bool runProcess(const std::map<std::string, std::any>& parameters) {
396 framework::config::Parameters configuration;
397 configuration.setParameters(parameters);
399 try {
400 p = std::make_unique<Process>(configuration);
402 std::cerr << "Config Error [" << e.name() << "] : " << e.message()
403 << std::endl;
404 std::cerr << " at " << e.module() << ":" << e.line() << " in "
405 << e.function() << std::endl;
406 return false;
407 }
408 p->run();
409 return true;
410}
411
412} // namespace test
413} // namespace framework
414
415DECLARE_PRODUCER_NS(framework::test, TestProducer)
416DECLARE_ANALYZER_NS(framework::test, TestAnalyzer)
417
418
451TEST_CASE("Core Framework Functionality", "[Framework][functionality]") {
452 // these parameters aren't tested/changed, so we set them out here
453 std::map<std::string, std::any> process;
454 process["passName"] = std::string("test");
455 process["compressionSetting"] = 9;
456 process["maxTriesPerEvent"] = 1;
457 process["logFrequency"] = -1;
458 process["termLogLevel"] = 4;
459 process["fileLogLevel"] = 4;
460 process["logFileName"] = std::string();
461 process["tree_name"] = std::string("LDMX_Events");
462
463 process["histogramFile"] =
464 std::string(""); // will be changed in some branches
465 process["maxEvents"] = -1; // will be changed
466 process["skimDefaultIsKeep"] = true; // will be changed in some branches
467 process["run"] = -1; // will be changed in some branches
468
469 std::map<std::string, std::any> producerParameters;
470 producerParameters["className"] =
471 std::string("framework::test::TestProducer");
472 producerParameters["instanceName"] = std::string("TestProducer");
473 producerParameters["createRunHeader"] = false;
474
475 std::map<std::string, std::any> analyzerParameters;
476 analyzerParameters["className"] =
477 std::string("framework::test::TestAnalyzer");
478 analyzerParameters["instanceName"] = std::string("TestAnalyzer");
479
480 // parameters classes to wrap parameters in
481 framework::config::Parameters processConfig, producerConfig,
482 analyzerConfig; // parameters classes to wrap parameters in
483 analyzerConfig.setParameters(analyzerParameters);
484
485 // declare used and re-used types, not used in all branches
486 std::vector<framework::config::Parameters> sequence;
487 std::vector<std::string> inputFiles, outputFiles;
488
489 SECTION("Production Mode") {
490 // no input files, only output files
491
492 outputFiles = {"test_productionmode_events.root"};
493 process["outputFiles"] = outputFiles;
494 process["maxEvents"] = 3;
495 process["run"] = 3;
496
497 producerParameters["createRunHeader"] = true;
498 producerConfig.setParameters(producerParameters);
499
500 sequence = {producerConfig};
501 process["sequence"] = sequence;
502
503 SECTION("only producers") {
504 SECTION("no drop/keep rules") {
505 // Process owns and deletes the processors
506 REQUIRE(framework::test::runProcess(process));
507 CHECK_THAT(outputFiles.at(0),
509 }
510
511 SECTION("drop TestCollection") {
512 std::vector<std::string> keep = {"drop .*Collection.*"};
513 process["keep"] = keep;
514 REQUIRE(framework::test::runProcess(process));
515 CHECK_THAT(outputFiles.at(0),
516 framework::test::isGoodEventFile("test", 3, 1, false));
517 }
518
519 SECTION("skim for even indexed events") {
520 process["skimDefaultIsKeep"] = false;
521 std::vector<std::string> rules = {"TestProducer", ""};
522 process["skimRules"] = rules;
523 REQUIRE(framework::test::runProcess(process));
524 CHECK_THAT(outputFiles.at(0),
526 }
527 }
528
529 SECTION("with Analyses") {
530 std::string hist_file_path =
531 "test_productionmode_withanalyses_hists.root";
532
533 process["histogramFile"] = hist_file_path;
534
535 sequence.push_back(analyzerConfig);
536 process["sequence"] = sequence;
537
538 SECTION("no drop/keep rules") {
539 REQUIRE(framework::test::runProcess(process));
540 CHECK_THAT(outputFiles.at(0),
542 }
543
544 SECTION("drop TestCollection") {
545 std::vector<std::string> keep = {"drop .*Collection.*"};
546 process["keep"] = keep;
547 REQUIRE(framework::test::runProcess(process));
548 CHECK_THAT(outputFiles.at(0),
549 framework::test::isGoodEventFile("test", 3, 1, false));
550 }
551
552 SECTION("skim for even indexed events") {
553 process["skimDefaultIsKeep"] = false;
554 std::vector<std::string> rules = {"TestProducer", ""};
555 process["skimRules"] = rules;
556 REQUIRE(framework::test::runProcess(process));
557 CHECK_THAT(outputFiles.at(0),
559 }
560
561 CHECK_THAT(hist_file_path,
563 CHECK(framework::test::removeFile(hist_file_path));
564 }
565
566 CHECK(framework::test::removeFile(outputFiles.at(0)));
567 } // Production Mode
568
569 SECTION("Need Input Files") {
570 inputFiles = {"test_needinputfiles_2_events.root",
571 "test_needinputfiles_3_events.root",
572 "test_needinputfiles_4_events.root"};
573
574 producerParameters["createRunHeader"] = true;
575 producerConfig.setParameters(producerParameters);
576
577 sequence = {producerConfig};
578
579 auto makeInputs = process;
580 makeInputs["passName"] = std::string("makeInputs");
581 makeInputs["sequence"] = sequence;
582
583 outputFiles = {inputFiles.at(0)};
584 makeInputs["outputFiles"] = outputFiles;
585 makeInputs["maxEvents"] = 2;
586 makeInputs["run"] = 2;
587
588 REQUIRE(framework::test::runProcess(makeInputs));
589 REQUIRE_THAT(inputFiles.at(0),
590 framework::test::isGoodEventFile("makeInputs", 2, 1));
591
592 outputFiles = {inputFiles.at(1)};
593 makeInputs["outputFiles"] = outputFiles;
594 makeInputs["maxEvents"] = 3;
595 makeInputs["run"] = 3;
596
597 REQUIRE(framework::test::runProcess(makeInputs));
598 REQUIRE_THAT(inputFiles.at(1),
599 framework::test::isGoodEventFile("makeInputs", 3, 1));
600
601 outputFiles = {inputFiles.at(2)};
602 makeInputs["outputFiles"] = outputFiles;
603 makeInputs["maxEvents"] = 4;
604 makeInputs["run"] = 4;
605
606 REQUIRE(framework::test::runProcess(makeInputs));
607 REQUIRE_THAT(inputFiles.at(2),
608 framework::test::isGoodEventFile("makeInputs", 4, 1));
609
610 SECTION("Analysis Mode") {
611 // no output files, only histogram output
612
613 sequence = {analyzerConfig};
614 process["sequence"] = sequence;
615
616 std::string hist_file_path = "test_analysismode_hists.root";
617 process["histogramFile"] = hist_file_path;
618
619 SECTION("one input file") {
620 std::vector<std::string> inputFile = {inputFiles.at(0)};
621 process["inputFiles"] = inputFile;
622 REQUIRE(framework::test::runProcess(process));
623 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(1 + 2));
624 CHECK(framework::test::removeFile(hist_file_path));
625 }
626
627 SECTION("multiple input files") {
628 process["inputFiles"] = inputFiles;
629 REQUIRE(framework::test::runProcess(process));
630 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(
631 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
632 CHECK(framework::test::removeFile(hist_file_path));
633 }
634
635 } // Analysis Mode
636
637 SECTION("Merge Mode") {
638 // many input files to one output file
639
640 process["inputFiles"] = inputFiles;
641
642 std::string event_file_path = "test_mergemode_events.root";
643 outputFiles = {event_file_path};
644 process["outputFiles"] = outputFiles;
645
646 SECTION("with analyzers") {
647 sequence = {analyzerConfig};
648
649 std::string hist_file_path = "test_mergemode_withanalyzers_hists.root";
650
651 process["sequence"] = sequence;
652 process["histogramFile"] = hist_file_path;
653
654 SECTION("no drop/keep rules") {
655 REQUIRE(framework::test::runProcess(process));
656 CHECK_THAT(event_file_path, framework::test::isGoodEventFile(
657 "makeInputs", 2 + 3 + 4, 3));
658 }
659
660 SECTION("drop TestCollection") {
661 std::vector<std::string> keep = {"drop .*Collection.*"};
662 process["keep"] = keep;
663 REQUIRE(framework::test::runProcess(process));
664 CHECK_THAT(event_file_path, framework::test::isGoodEventFile(
665 "makeInputs", 2 + 3 + 4, 3, false));
666 }
667
668 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(
669 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
670 CHECK(framework::test::removeFile(hist_file_path));
671 }
672
673 SECTION("with producers") {
674 producerParameters["createRunHeader"] = false;
675 producerConfig.setParameters(producerParameters);
676 sequence = {producerConfig};
677
678 process["sequence"] = sequence;
679
680 SECTION("not listening to storage hints") {
681 REQUIRE(framework::test::runProcess(process));
682 CHECK_THAT(event_file_path,
683 framework::test::isGoodEventFile("test", 2 + 3 + 4, 3));
684 }
685
686 SECTION("skim for even indexed events") {
687 process["skimDefaultIsKeep"] = false;
688 std::vector<std::string> rules = {"TestProducer", ""};
689 process["skimRules"] = rules;
690 REQUIRE(framework::test::runProcess(process));
691 CHECK_THAT(event_file_path,
692 framework::test::isGoodEventFile("test", 1 + 1 + 2, 3));
693 }
694 }
695
696 CHECK(framework::test::removeFile(event_file_path));
697
698 } // Merge Mode
699
700 } // need input files
701
702} // process test
Class that represents a reconstructed hit in a calorimeter cell within the detector.
Base classes for all user event processing components to extend.
#define DECLARE_ANALYZER_NS(NS, CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
#define DECLARE_PRODUCER_NS(NS, CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class that stores Stores reconstructed hit information from the HCAL.
Class used to encapsulate the results obtained from HcalVetoProcessor.
Class which represents the process under execution.
Base class for a module which does not produce a data product.
TDirectory * getHistoDirectory()
Access/create a directory in the histogram file for this event processor to create histograms and ana...
void setStorageHint(framework::StorageControl::Hint hint)
Mark the current event as having the given storage control hint from this module.
Implements an event buffer system for storing event data.
Definition Event.h:41
Class which represents the process under execution.
Definition Process.h:36
Base class for a module which produces a data product.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
void setParameters(std::map< std::string, std::any > parameters)
Set the mapping of parameter names to value.
Definition Parameters.h:41
Standard base exception class with some useful output information.
Definition Exception.h:20
const std::string & function() const
Get the function name where the exception occurred.
Definition Exception.h:74
int line() const
Get the source line number where the exception occurred.
Definition Exception.h:80
const std::string & message() const
Get the message of the exception.
Definition Exception.h:62
const std::string & name() const
Get the name of the exception.
Definition Exception.h:56
const std::string & module() const
Get the source filename where the exception occurred.
Definition Exception.h:68
Bare analyzer that looks for objects matching what the TestProducer put in.
void analyze(const framework::Event &event) final override
Process the event and make histograms or summaries.
TH1F * test_hist_
test histogram filled with event indices
void onProcessStart() final override
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
Bare producer that creates a collection and an object and puts them on the event bus.
int events_
number of events we've gotten to
void configure(framework::config::Parameters &p) final override
Callback for the EventProcessor to configure itself from the given set of parameters.
void produce(framework::Event &event) final override
Process the event and put new data products into it.
void beforeNewRun(ldmx::RunHeader &header) final override
Handle allowing producers to modify run headers before the run begins.
bool createRunHeader_
should we create the run header?
virtual std::string describe() const override
Human-readable statement for any match that is true.
isGoodEventFile(const std::string &pass, const int &entries, const int &runs, bool existColl=true, bool existObj=true)
Constructor.
bool existCollection_
collection should exist in file
bool existObject_
object should exist in file
std::string pass_
pass name to check the collection and/or object for
bool match(const std::string &filename) const override
Actually do the matching.
int entries_
correct number of entries in the event ttree
Runs a variety of checks to make sure the histogram in the input filename is what we expect it to be.
virtual std::string describe() const override
Describe this matcher in a helpful, human-readable way.
int correctGetEntries_
Correct number of entries.
bool match(const std::string &filename) const override
Performs the test for this matcher.
isGoodHistogramFile(int const &n)
Constructor.
Represents a reconstructed hit in a calorimeter cell within the detector.
void setID(int id)
Set the detector ID.
Stores reconstructed hit information from the HCAL.
Definition HcalHit.h:23
bool passesVeto() const
Checks if the event passes the Hcal veto.
ldmx::HcalHit getMaxPEHit() const
void setVetoResult(const bool &passesVeto=true)
Sets whether the Hcal veto was passed or not.
void setMaxPEHit(const ldmx::HcalHit maxPEHit)
Set the maximum PE hit.
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:54
All classes in the ldmx-sw project use this namespace.
Definition PerfDict.cxx:45
std::unique_ptr< Process > ProcessHandle
A handle to the current process Used to pass a process from ConfigurePython to fire....
Definition Process.h:248