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 framework::config::Parameters& parameters) {
397 try {
398 p = std::make_unique<Process>(parameters);
400 std::cerr << "Config Error [" << e.name() << "] : " << e.message()
401 << std::endl;
402 std::cerr << " at " << e.module() << ":" << e.line() << " in "
403 << e.function() << std::endl;
404 return false;
405 }
406 p->run();
407 return true;
408}
409
410} // namespace test
411} // namespace framework
412
413DECLARE_PRODUCER_NS(framework::test, TestProducer)
414DECLARE_ANALYZER_NS(framework::test, TestAnalyzer)
415
416
449TEST_CASE("Core Framework Functionality", "[Framework][functionality]") {
450 // these parameters aren't tested/changed, so we set them out here
452 process.add("compressionSetting", 9);
453 process.add("maxTriesPerEvent", 1);
454 process.add("logFrequency", -1);
455 process.add("termLogLevel", 4);
456 process.add("fileLogLevel", 4);
457 process.add<std::string>("logFileName", "");
458 process.add<std::string>("tree_name", "LDMX_Events");
459
460 framework::config::Parameters producerParameters;
461 producerParameters.add<std::string>("className",
462 "framework::test::TestProducer");
463 producerParameters.add<std::string>("instanceName", "TestProducer");
464
465 framework::config::Parameters analyzerParameters;
466 analyzerParameters.add<std::string>("className",
467 "framework::test::TestAnalyzer");
468 analyzerParameters.add<std::string>("instanceName", "TestAnalyzer");
469
470 // declare used and re-used types, not used in all branches
471 std::vector<framework::config::Parameters> sequence;
472 std::vector<std::string> inputFiles, outputFiles;
473
474 SECTION("Production Mode") {
475 // no input files, only output files
476
477 outputFiles = {"test_productionmode_events.root"};
478 process.add<std::string>("passName", "test");
479 process.add("outputFiles", outputFiles);
480 process.add("maxEvents", 3);
481 process.add("run", 3);
482
483 producerParameters.add("createRunHeader", true);
484
485 sequence = {producerParameters};
486
487 SECTION("only producers") {
488 process.add("sequence", sequence);
489 process.add<std::string>("histogramFile", "");
490 SECTION("no drop/keep rules") {
491 // Process owns and deletes the processors
492 REQUIRE(framework::test::runProcess(process));
493 CHECK_THAT(outputFiles.at(0),
495 }
496
497 SECTION("drop TestCollection") {
498 std::vector<std::string> keep = {"drop .*Collection.*"};
499 process.add("keep", keep);
500 REQUIRE(framework::test::runProcess(process));
501 CHECK_THAT(outputFiles.at(0),
502 framework::test::isGoodEventFile("test", 3, 1, false));
503 }
504
505 SECTION("skim for even indexed events") {
506 process.add("skimDefaultIsKeep", false);
507 std::vector<std::string> rules = {"TestProducer", ""};
508 process.add("skimRules", rules);
509 REQUIRE(framework::test::runProcess(process));
510 CHECK_THAT(outputFiles.at(0),
512 }
513 }
514
515 SECTION("with Analyses") {
516 std::string hist_file_path =
517 "test_productionmode_withanalyses_hists.root";
518
519 process.add("histogramFile", hist_file_path);
520
521 sequence.push_back(analyzerParameters);
522 process.add("sequence", sequence);
523
524 SECTION("no drop/keep rules") {
525 REQUIRE(framework::test::runProcess(process));
526 CHECK_THAT(outputFiles.at(0),
528 }
529
530 SECTION("drop TestCollection") {
531 std::vector<std::string> keep = {"drop .*Collection.*"};
532 process.add("keep", keep);
533 REQUIRE(framework::test::runProcess(process));
534 CHECK_THAT(outputFiles.at(0),
535 framework::test::isGoodEventFile("test", 3, 1, false));
536 }
537
538 SECTION("skim for even indexed events") {
539 process.add("skimDefaultIsKeep", false);
540 std::vector<std::string> rules = {"TestProducer", ""};
541 process.add("skimRules", rules);
542 REQUIRE(framework::test::runProcess(process));
543 CHECK_THAT(outputFiles.at(0),
545 }
546
547 CHECK_THAT(hist_file_path,
549 CHECK(framework::test::removeFile(hist_file_path));
550 }
551
552 CHECK(framework::test::removeFile(outputFiles.at(0)));
553 } // Production Mode
554
555 SECTION("Need Input Files") {
556 inputFiles = {"test_needinputfiles_2_events.root",
557 "test_needinputfiles_3_events.root",
558 "test_needinputfiles_4_events.root"};
559
560 for (int run{2}; run < 5; run++) {
561 auto makeInputs = process;
562 makeInputs.add<std::string>("passName", "makeInputs");
563 auto producer = producerParameters;
564 producer.add("createRunHeader", true);
565 makeInputs.add<std::vector<framework::config::Parameters>>("sequence",
566 {producer});
567 outputFiles = {inputFiles.at(run - 2)};
568 makeInputs.add("outputFiles", outputFiles);
569 makeInputs.add("maxEvents", run);
570 makeInputs.add("run", run);
571 REQUIRE(framework::test::runProcess(makeInputs));
572 REQUIRE_THAT(inputFiles.at(run - 2),
573 framework::test::isGoodEventFile("makeInputs", run, 1));
574 }
575 process.add<std::string>("passName", "test");
576
577 SECTION("Analysis Mode") {
578 // no output files, only histogram output
579
580 sequence = {analyzerParameters};
581 process.add("sequence", sequence);
582
583 std::string hist_file_path = "test_analysismode_hists.root";
584 process.add("histogramFile", hist_file_path);
585
586 SECTION("one input file") {
587 std::vector<std::string> inputFile = {inputFiles.at(0)};
588 process.add("inputFiles", inputFile);
589 REQUIRE(framework::test::runProcess(process));
590 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(1 + 2));
591 CHECK(framework::test::removeFile(hist_file_path));
592 }
593
594 SECTION("multiple input files") {
595 process.add("inputFiles", inputFiles);
596 REQUIRE(framework::test::runProcess(process));
597 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(
598 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
599 CHECK(framework::test::removeFile(hist_file_path));
600 }
601
602 } // Analysis Mode
603
604 SECTION("Merge Mode") {
605 // many input files to one output file
606
607 process.add("inputFiles", inputFiles);
608
609 std::string event_file_path = "test_mergemode_events.root";
610 outputFiles = {event_file_path};
611 process.add("outputFiles", outputFiles);
612
613 SECTION("with analyzers") {
614 sequence = {analyzerParameters};
615
616 std::string hist_file_path = "test_mergemode_withanalyzers_hists.root";
617
618 process.add("sequence", sequence);
619 process.add("histogramFile", hist_file_path);
620
621 SECTION("no drop/keep rules") {
622 REQUIRE(framework::test::runProcess(process));
623 CHECK_THAT(event_file_path, framework::test::isGoodEventFile(
624 "makeInputs", 2 + 3 + 4, 3));
625 }
626
627 SECTION("drop TestCollection") {
628 std::vector<std::string> keep = {"drop .*Collection.*"};
629 process.add("keep", keep);
630 REQUIRE(framework::test::runProcess(process));
631 CHECK_THAT(event_file_path, framework::test::isGoodEventFile(
632 "makeInputs", 2 + 3 + 4, 3, false));
633 }
634
635 CHECK_THAT(hist_file_path, framework::test::isGoodHistogramFile(
636 1 + 2 + 1 + 2 + 3 + 1 + 2 + 3 + 4));
637 CHECK(framework::test::removeFile(hist_file_path));
638 }
639
640 SECTION("with producers") {
641 producerParameters.add("createRunHeader", false);
642 sequence = {producerParameters};
643
644 process.add("sequence", sequence);
645
646 SECTION("not listening to storage hints") {
647 REQUIRE(framework::test::runProcess(process));
648 CHECK_THAT(event_file_path,
649 framework::test::isGoodEventFile("test", 2 + 3 + 4, 3));
650 }
651
652 SECTION("skim for even indexed events") {
653 process.add("skimDefaultIsKeep", false);
654 std::vector<std::string> rules = {"TestProducer", ""};
655 process.add("skimRules", rules);
656 REQUIRE(framework::test::runProcess(process));
657 CHECK_THAT(event_file_path,
658 framework::test::isGoodEventFile("test", 1 + 1 + 2, 3));
659 }
660 }
661
662 CHECK(framework::test::removeFile(event_file_path));
663
664 } // Merge Mode
665
666 } // need input files
667
668} // 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.
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
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: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:292
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:233