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
42class TestProducer : public Producer {
45
48
49 public:
50 TestProducer(const std::string& name, Process& p) : Producer(name, p) {}
51 ~TestProducer() {}
52
53 void configure(framework::config::Parameters& p) final override {
54 create_run_header_ = p.get<bool>("createRunHeader");
55 }
56
57 void beforeNewRun(ldmx::RunHeader& header) final override {
58 if (not create_run_header_) return;
59 header.setIntParameter("Should Be Run Number", header.getRunNumber());
60 return;
61 }
62
63 void produce(framework::Event& event) final override {
64 int i_event = event.getEventNumber();
65
66 REQUIRE(i_event > 0);
67
68 std::vector<ldmx::CalorimeterHit> calo_hits;
69 for (int i = 0; i < i_event; i++) {
70 calo_hits.emplace_back();
71 calo_hits.back().setID(i_event * 10 + i);
72 }
73
74 REQUIRE_NOTHROW(event.add("TestCollection", calo_hits));
75
76 ldmx::HcalHit max_pe_hit;
77 max_pe_hit.setID(i_event);
78
80 res.setMaxPEHit(max_pe_hit);
81 res.setVetoResult(i_event % 2 == 0);
82
83 REQUIRE_NOTHROW(event.add("TestObject", res));
84
85 events_ = i_event;
86
87 std::vector<int> event_indices = {i_event, i_event};
88 REQUIRE_NOTHROW(event.add("EventIndex", event_indices));
89
90 float test_float = i_event * 0.1;
91 REQUIRE_NOTHROW(event.add("EventTenth", test_float));
92
93 if (res.passesVeto()) setStorageHint(StorageControl::Hint::MustKeep);
94
95 return;
96 }
97}; // TestProducer
98
108class TestAnalyzer : public Analyzer {
109 private:
110 std::string test_collection_passname_;
111 std::string test_object_passname_;
112 std::string veto_test_object_passname_;
113 std::string tenth_event_passname_;
114 std::string event_index_passname_;
115
116 public:
117 TestAnalyzer(const std::string& name, Process& p) : Analyzer(name, p) {}
118 ~TestAnalyzer() {}
119
121 test_collection_passname_ =
122 ps.get<std::string>("test_collection_passname", "");
123 test_object_passname_ = ps.get<std::string>("test_object_passname", "");
124 veto_test_object_passname_ =
125 ps.get<std::string>("veto_test_object_passname", "");
126 tenth_event_passname_ = ps.get<std::string>("tenth_event_passname", "");
127 event_index_passname_ = ps.get<std::string>("event_index_passname", "");
128 }
129
130 void onProcessStart() final override {
131 REQUIRE_NOTHROW(getHistoDirectory());
132 test_hist_ = new TH1F("test_hist_", "Test Histogram", 101, -50, 50);
133 test_hist_->SetCanExtend(TH1::kAllAxes);
134 }
135
136 void analyze(const framework::Event& event) final override {
137 int i_event = event.getEventNumber();
138
139 REQUIRE(i_event > 0);
140
141 const std::vector<ldmx::CalorimeterHit>& calo_hits =
142 event.getCollection<ldmx::CalorimeterHit>("TestCollection",
143 test_collection_passname_);
144
145 CHECK(calo_hits.size() == i_event);
146 for (unsigned int i = 0; i < calo_hits.size(); i++) {
147 CHECK(calo_hits.at(i).getID() == i_event * 10 + i);
148 test_hist_->Fill(calo_hits.at(i).getID());
149 }
150
151 const ldmx::HcalVetoResult& veto_res =
152 event.getObject<ldmx::HcalVetoResult>("TestObject",
153 veto_test_object_passname_);
154
155 auto max_pe_hit{veto_res.getMaxPEHit()};
156
157 CHECK(max_pe_hit.getID() == i_event);
158 CHECK(veto_res.passesVeto() == (i_event % 2 == 0));
159
160 const float& tenth_event =
161 event.getObject<float>("EventTenth", tenth_event_passname_);
162 CHECK(tenth_event == Approx(i_event * 0.1));
163
164 const std::vector<int>& i_event_from_bus =
165 event.getCollection<int>("EventIndex", event_index_passname_);
166
167 CHECK(i_event_from_bus.size() == 2);
168 CHECK(i_event_from_bus.at(0) == i_event);
169 CHECK(i_event_from_bus.at(1) == i_event);
170
171 return;
172 }
173
174 private:
177}; // TestAnalyzer
178
194class IsGoodHistogramFile : public Catch::Matchers::MatcherBase<std::string> {
195 private:
198
199 public:
206
214 bool match(const std::string& filename) const override {
215 // Open file
216 TFile* f = TFile::Open(filename.c_str());
217 if (!f) return false;
218 TDirectory* d = (TDirectory*)f->Get("TestAnalyzer");
219 if (!d) return false;
220 TH1F* h = (TH1F*)d->Get("test_hist_");
221 if (!h) return false;
222
223 return (h->GetEntries() == correct_get_entries_);
224 }
225
232 virtual std::string describe() const override {
233 std::ostringstream ss;
234 ss << "has the histogram 'TestAnalyzer/test_hist_' with the number of "
235 "entries "
237 return ss.str();
238 }
239}; // isGoodHistogramFile
240
256class IsGoodEventFile : public Catch::Matchers::MatcherBase<std::string> {
257 private:
259 std::string pass_;
260
263
265 int runs_;
266
269
272
273 public:
279 IsGoodEventFile(const std::string& pass, const int& entries, const int& runs,
280 bool existColl = true, bool existObj = true)
281 : pass_(pass),
282 entries_(entries),
283 runs_(runs),
284 exist_collection_(existColl),
285 exist_object_(existObj) {}
286
295 bool match(const std::string& filename) const override {
296 TFile* f = TFile::Open(filename.c_str());
297 if (!f) return false;
298
299 TTreeReader events("LDMX_Events", f);
300
301 if (events.GetEntries(true) != entries_) {
302 f->Close();
303 return false;
304 }
305
306 // Event tree should _always_ have the ldmx::EventHeader
307 TTreeReaderValue<ldmx::EventHeader> header(events, "EventHeader");
308
309 if (exist_collection_) {
310 // make sure collection matches pattern
311 TTreeReaderValue<std::vector<ldmx::CalorimeterHit>> collection(
312 events, ("test_collection_" + pass_).c_str());
313 while (events.Next()) {
314 if (collection->size() != header->getEventNumber()) {
315 f->Close();
316 return false;
317 }
318 for (unsigned int i = 0; i < collection->size(); i++)
319 if (collection->at(i).getID() != header->getEventNumber() * 10 + i) {
320 f->Close();
321 return false;
322 }
323 }
324 // restart in case checking object as well
325 events.Restart();
326 } else {
327 // check to make sure collection is NOT there
328 auto t{(TTree*)f->Get("LDMX_Events")};
329 if (t and t->GetBranch(("test_collection_" + pass_).c_str())) {
330 f->Close();
331 return false;
332 }
333 }
334
335 if (exist_object_) {
336 // make sure object matches pattern
337 TTreeReaderValue<ldmx::HcalVetoResult> object(
338 events, ("test_object_" + pass_).c_str());
339 while (events.Next()) {
340 if (object->getMaxPEHit().getID() != header->getEventNumber()) {
341 f->Close();
342 return false;
343 }
344 }
345 } else {
346 // check to make sure object is NOT there
347 auto t{(TTree*)f->Get("LDMX_Events")};
348 if (t and t->GetBranch(("test_object_" + pass_).c_str())) {
349 f->Close();
350 return false;
351 }
352 }
353
354 TTreeReader runs("LDMX_Run", f);
355
356 if (runs.GetEntries(true) != runs_) {
357 f->Close();
358 return false;
359 }
360
361 TTreeReaderValue<ldmx::RunHeader> run_header(runs, "RunHeader");
362
363 while (runs.Next()) {
364 if (run_header->getRunNumber() !=
365 run_header->getIntParameter("Should Be Run Number")) {
366 f->Close();
367 return false;
368 }
369 }
370
371 f->Close();
372
373 return true;
374 }
375
379 virtual std::string describe() const override {
380 std::ostringstream ss;
381 ss << "can be opened and has the correct number of entries in the event "
382 "tree and the run tree.";
383
384 ss << " test_collection_" << pass_ << " was verified to ";
386 ss << " be the correct pattern.";
387 else
388 ss << " not be in the file.";
389
390 ss << " test_object_" << pass_ << " was verified to ";
391 if (exist_object_)
392 ss << " be the correct pattern.";
393 else
394 ss << " not be in the file.";
395
396 return ss.str();
397 }
398
399}; // isGoodEventFile
400
409static bool removeFile(const std::string& filepath) {
410 return remove(filepath.c_str()) == 0;
411}
412
416static bool runProcess(const framework::config::Parameters& parameters) {
418 try {
419 p = std::make_unique<Process>(parameters);
421 std::cerr << "Config Error [" << e.name() << "] : " << e.message()
422 << std::endl;
423 std::cerr << " at " << e.module() << ":" << e.line() << " in "
424 << e.function() << std::endl;
425 return false;
426 }
427 p->run();
428 return true;
429}
430
431} // namespace test
432} // namespace framework
433
436
437
470TEST_CASE("Core Framework Functionality", "[Framework][functionality]") {
471 // these parameters aren't tested/changed, so we set them out here
473 process.add("compressionSetting", 9);
474 process.add("maxTriesPerEvent", 1);
475 process.add("logFrequency", -1);
476 process.add("termLogLevel", 4);
477 process.add("fileLogLevel", 4);
478 process.add<std::string>("logFileName", "");
479 process.add<std::string>("tree_name", "LDMX_Events");
480
481 framework::config::Parameters producer_parameters;
482 producer_parameters.add<std::string>("className",
483 "framework::test::TestProducer");
484 producer_parameters.add<std::string>("instanceName", "TestProducer");
485
486 framework::config::Parameters analyzer_parameters;
487 analyzer_parameters.add<std::string>("className",
488 "framework::test::TestAnalyzer");
489 analyzer_parameters.add<std::string>("instanceName", "TestAnalyzer");
490
491 // declare used and re-used types, not used in all branches
492 std::vector<framework::config::Parameters> sequence;
493 std::vector<std::string> input_files, output_files;
494
495 SECTION("Production Mode") {
496 // no input files, only output files
497
498 output_files = {"test_productionmode_events.root"};
499 process.add<std::string>("passName", "test");
500 process.add("outputFiles", output_files);
501 process.add("maxEvents", 3);
502 process.add("run", 3);
503
504 producer_parameters.add("createRunHeader", true);
505
506 sequence = {producer_parameters};
507
508 SECTION("only producers") {
509 process.add("sequence", sequence);
510 process.add<std::string>("histogramFile", "");
511 SECTION("no drop/keep rules") {
512 // Process owns and deletes the processors
513 REQUIRE(framework::test::runProcess(process));
514 CHECK_THAT(output_files.at(0),
516 }
517
518 SECTION("drop TestCollection") {
519 std::vector<std::string> keep = {"drop .*Collection.*"};
520 process.add("keep", keep);
521 REQUIRE(framework::test::runProcess(process));
522 CHECK_THAT(output_files.at(0),
523 framework::test::IsGoodEventFile("test", 3, 1, false));
524 }
525
526 SECTION("skim for even indexed events") {
527 process.add("skimDefaultIsKeep", false);
528 std::vector<std::string> rules = {"TestProducer", ""};
529 process.add("skimRules", rules);
530 REQUIRE(framework::test::runProcess(process));
531 CHECK_THAT(output_files.at(0),
533 }
534 }
535
536 SECTION("with Analyses") {
537 std::string hist_file_path =
538 "test_productionmode_withanalyses_hists.root";
539
540 process.add("histogramFile", hist_file_path);
541
542 sequence.push_back(analyzer_parameters);
543 process.add("sequence", sequence);
544
545 SECTION("no drop/keep rules") {
546 REQUIRE(framework::test::runProcess(process));
547 CHECK_THAT(output_files.at(0),
549 }
550
551 SECTION("drop TestCollection") {
552 std::vector<std::string> keep = {"drop .*Collection.*"};
553 process.add("keep", keep);
554 REQUIRE(framework::test::runProcess(process));
555 CHECK_THAT(output_files.at(0),
556 framework::test::IsGoodEventFile("test", 3, 1, false));
557 }
558
559 SECTION("skim for even indexed events") {
560 process.add("skimDefaultIsKeep", false);
561 std::vector<std::string> rules = {"TestProducer", ""};
562 process.add("skimRules", rules);
563 REQUIRE(framework::test::runProcess(process));
564 CHECK_THAT(output_files.at(0),
566 }
567
568 CHECK_THAT(hist_file_path,
570 CHECK(framework::test::removeFile(hist_file_path));
571 }
572
573 CHECK(framework::test::removeFile(output_files.at(0)));
574 } // Production Mode
575
576 SECTION("Need Input Files") {
577 input_files = {"test_needinputfiles_2_events.root",
578 "test_needinputfiles_3_events.root",
579 "test_needinputfiles_4_events.root"};
580
581 for (int run{2}; run < 5; run++) {
582 auto make_inputs = process;
583 make_inputs.add<std::string>("passName", "makeInputs");
584 auto producer = producer_parameters;
585 producer.add("createRunHeader", true);
586 make_inputs.add<std::vector<framework::config::Parameters>>("sequence",
587 {producer});
588 output_files = {input_files.at(run - 2)};
589 make_inputs.add("outputFiles", output_files);
590 make_inputs.add("maxEvents", run);
591 make_inputs.add("run", run);
592 REQUIRE(framework::test::runProcess(make_inputs));
593 REQUIRE_THAT(input_files.at(run - 2),
594 framework::test::IsGoodEventFile("makeInputs", run, 1));
595 }
596 process.add<std::string>("passName", "test");
597
598 SECTION("Analysis Mode") {
599 // no output files, only histogram output
600
601 sequence = {analyzer_parameters};
602 process.add("sequence", sequence);
603
604 std::string hist_file_path = "test_analysismode_hists.root";
605 process.add("histogramFile", hist_file_path);
606
607 SECTION("one input file") {
608 std::vector<std::string> input_file = {input_files.at(0)};
609 process.add("inputFiles", input_file);
610 REQUIRE(framework::test::runProcess(process));
611 CHECK_THAT(hist_file_path, framework::test::IsGoodHistogramFile(1 + 2));
612 CHECK(framework::test::removeFile(hist_file_path));
613 }
614
615 SECTION("multiple input files") {
616 process.add("inputFiles", input_files);
617 REQUIRE(framework::test::runProcess(process));
618 CHECK_THAT(hist_file_path, framework::test::IsGoodHistogramFile(
619 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
620 CHECK(framework::test::removeFile(hist_file_path));
621 }
622
623 } // Analysis Mode
624
625 SECTION("Merge Mode") {
626 // many input files to one output file
627
628 process.add("inputFiles", input_files);
629
630 std::string event_file_path = "test_mergemode_events.root";
631 output_files = {event_file_path};
632 process.add("outputFiles", output_files);
633
634 SECTION("with analyzers") {
635 sequence = {analyzer_parameters};
636
637 std::string hist_file_path = "test_mergemode_withanalyzers_hists.root";
638
639 process.add("sequence", sequence);
640 process.add("histogramFile", hist_file_path);
641
642 SECTION("no drop/keep rules") {
643 REQUIRE(framework::test::runProcess(process));
644 CHECK_THAT(event_file_path, framework::test::IsGoodEventFile(
645 "makeInputs", 2 + 3 + 4, 3));
646 }
647
648 SECTION("drop TestCollection") {
649 std::vector<std::string> keep = {"drop .*Collection.*"};
650 process.add("keep", keep);
651 REQUIRE(framework::test::runProcess(process));
652 CHECK_THAT(event_file_path, framework::test::IsGoodEventFile(
653 "makeInputs", 2 + 3 + 4, 3, false));
654 }
655
656 CHECK_THAT(hist_file_path, framework::test::IsGoodHistogramFile(
657 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
658 CHECK(framework::test::removeFile(hist_file_path));
659 }
660
661 SECTION("with producers") {
662 producer_parameters.add("createRunHeader", false);
663 sequence = {producer_parameters};
664
665 process.add("sequence", sequence);
666
667 SECTION("not listening to storage hints") {
668 REQUIRE(framework::test::runProcess(process));
669 CHECK_THAT(event_file_path,
670 framework::test::IsGoodEventFile("test", 2 + 3 + 4, 3));
671 }
672
673 SECTION("skim for even indexed events") {
674 process.add("skimDefaultIsKeep", false);
675 std::vector<std::string> rules = {"TestProducer", ""};
676 process.add("skimRules", rules);
677 REQUIRE(framework::test::runProcess(process));
678 CHECK_THAT(event_file_path,
679 framework::test::IsGoodEventFile("test", 1 + 1 + 2, 3));
680 }
681 }
682
683 CHECK(framework::test::removeFile(event_file_path));
684
685 } // Merge Mode
686
687 } // need input files
688
689} // 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(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
#define DECLARE_PRODUCER(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.
Analyzer(const std::string &name, Process &process)
Class constructor.
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:42
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:29
void add(const std::string &name, const T &value)
Add a parameter to the parameter list.
Definition Parameters.h:42
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
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
int entries_
correct number of entries in the event ttree
bool exist_object_
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.
IsGoodEventFile(const std::string &pass, const int &entries, const int &runs, bool existColl=true, bool existObj=true)
Constructor.
bool exist_collection_
collection should exist in file
virtual std::string describe() const override
Human-readable statement for any match that is true.
IsGoodHistogramFile(int const &n)
Constructor.
bool match(const std::string &filename) const override
Performs the test for this matcher.
virtual std::string describe() const override
Describe this matcher in a helpful, human-readable way.
int correct_get_entries_
Correct number of entries.
Bare analyzer that looks for objects matching what the TestProducer put in.
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
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
bool create_run_header_
should we create the run header?
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
Callback for Producers to add parameters to the run header before conditions are initialized.
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:24
void setVetoResult(const bool &passes_veto=true)
Sets whether the Hcal veto was passed or not.
bool passesVeto() const
Checks if the event passes the Hcal veto.
ldmx::HcalHit getMaxPEHit() const
void setMaxPEHit(const ldmx::HcalHit max_PE_hit)
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:57
Parameters run(const std::string &root_object, const std::string &pythonScript, char *args[], int nargs)
run the python script and extract the parameters
Definition Python.cxx:300
All classes in the ldmx-sw project use this namespace.
std::unique_ptr< Process > ProcessHandle
A handle to the current process Used to pass a process from ConfigurePython to fire....
Definition Process.h:233