diff --git a/DataFormats/HLTReco/README.md b/DataFormats/HLTReco/README.md new file mode 100644 index 0000000000000..f9d1b929228ef --- /dev/null +++ b/DataFormats/HLTReco/README.md @@ -0,0 +1,10 @@ +# DataFormats/HLTReco + +## `trigger::TriggerEvent` + +The class `trigger::TriggerEvent` is part of the RAW data, and any changes must be backwards compatible. In order to ensure it can be read by all future CMSSW releases, there is a `TestTriggerEventFormat` unit test, which makes use of the `TestReadTriggerEvent` analyzer and the `TestWriteTriggerEvent` producer. The unit test checks that the object can be read properly from + +* a file written by the same release +* files written by (some) earlier releases + +If the persistent format of class `trigger::TriggerEvent` gets changed in the future, please adjust the `TestReadTriggerEvent` and `TestWriteTriggerEvent` modules accordingly. It is important that every member container has some content in this test. Please also add a new file to the [https://github.com/cms-data/DataFormats-HLTReco/](https://github.com/cms-data/DataFormats-HLTReco/) repository, and update the `TestTriggerEventFormat` unit test to read the newly created file. The file name should contain the release or pre-release with which it was written. diff --git a/DataFormats/HLTReco/test/BuildFile.xml b/DataFormats/HLTReco/test/BuildFile.xml new file mode 100644 index 0000000000000..ee654008c3eb8 --- /dev/null +++ b/DataFormats/HLTReco/test/BuildFile.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/DataFormats/HLTReco/test/TestReadTriggerEvent.cc b/DataFormats/HLTReco/test/TestReadTriggerEvent.cc new file mode 100644 index 0000000000000..fea498324e2a2 --- /dev/null +++ b/DataFormats/HLTReco/test/TestReadTriggerEvent.cc @@ -0,0 +1,191 @@ +// -*- C++ -*- +// +// Package: DataFormats/HLTReco +// Class: TestReadTriggerEvent +// +/**\class edmtest::TestReadTriggerEvent + Description: Used as part of tests that ensure the trigger::TriggerEvent + data format can be persistently written and in a subsequent process + read. First, this is done using the current release version for writing + and reading. In addition, the output file of the write process should + be saved permanently each time the trigger::TriggerEvent persistent data + format changes. In unit tests, we read each of those saved files to verify + that the current releases can read older versions of the data format. +*/ +// Original Author: W. David Dagenhart +// Created: 8 May 2023 + +#include "DataFormats/HLTReco/interface/TriggerEvent.h" +#include "DataFormats/HLTReco/interface/TriggerTypeDefs.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/global/EDAnalyzer.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/EDGetToken.h" +#include "FWCore/Utilities/interface/Exception.h" +#include "FWCore/Utilities/interface/InputTag.h" +#include "FWCore/Utilities/interface/StreamID.h" + +#include +#include + +namespace edmtest { + + class TestReadTriggerEvent : public edm::global::EDAnalyzer<> { + public: + TestReadTriggerEvent(edm::ParameterSet const&); + void analyze(edm::StreamID, edm::Event const&, edm::EventSetup const&) const override; + void throwWithMessage(const char*) const; + static void fillDescriptions(edm::ConfigurationDescriptions&); + + private: + // These expected values are meaningless other than we use them + // to check that values read from persistent storage match the values + // we know were written. + std::string expectedUsedProcessName_; + std::vector expectedCollectionTags_; + std::vector expectedCollectionKeys_; + + std::vector expectedIds_; + std::vector expectedPts_; + std::vector expectedEtas_; + std::vector expectedPhis_; + std::vector expectedMasses_; + + std::vector expectedFilterTags_; + unsigned int expectedElementsPerVector_; + std::vector expectedFilterIds_; + std::vector expectedFilterKeys_; + + edm::EDGetTokenT triggerEventToken_; + }; + + TestReadTriggerEvent::TestReadTriggerEvent(edm::ParameterSet const& iPSet) + : expectedUsedProcessName_(iPSet.getParameter("expectedUsedProcessName")), + expectedCollectionTags_(iPSet.getParameter>("expectedCollectionTags")), + expectedCollectionKeys_(iPSet.getParameter>("expectedCollectionKeys")), + expectedIds_(iPSet.getParameter>("expectedIds")), + expectedPts_(iPSet.getParameter>("expectedPts")), + expectedEtas_(iPSet.getParameter>("expectedEtas")), + expectedPhis_(iPSet.getParameter>("expectedPhis")), + expectedMasses_(iPSet.getParameter>("expectedMasses")), + + expectedFilterTags_(iPSet.getParameter>("expectedFilterTags")), + expectedElementsPerVector_(iPSet.getParameter("expectedElementsPerVector")), + expectedFilterIds_(iPSet.getParameter>("expectedFilterIds")), + expectedFilterKeys_(iPSet.getParameter>("expectedFilterKeys")), + + triggerEventToken_(consumes(iPSet.getParameter("triggerEventTag"))) { + if (expectedIds_.size() != expectedPts_.size() || expectedIds_.size() != expectedEtas_.size() || + expectedIds_.size() != expectedPhis_.size() || expectedIds_.size() != expectedMasses_.size()) { + throw cms::Exception("TestFailure") + << "TestReadTriggerEvent, test configuration error: " + "expectedIds, expectedPts, expectedEtas, expectedPhis, and expectedMasses should have the same size."; + } + if (expectedFilterIds_.size() != expectedElementsPerVector_ * expectedFilterTags_.size() || + expectedFilterKeys_.size() != expectedElementsPerVector_ * expectedFilterTags_.size()) { + throw cms::Exception("TestFailure") << "TestReadTriggerEvent, test configuration error: " + "size of expectedFilterIds and size of expectedFilterKeys " + "should equal size of expectedFilterTags times expectedElementsPerVector"; + } + } + + void TestReadTriggerEvent::analyze(edm::StreamID, edm::Event const& iEvent, edm::EventSetup const&) const { + auto const& triggerEvent = iEvent.get(triggerEventToken_); + + if (triggerEvent.usedProcessName() != expectedUsedProcessName_) { + throwWithMessage("usedProcessName does not have expected value"); + } + + if (triggerEvent.collectionTags() != expectedCollectionTags_) { + throwWithMessage("collectionTags do not have expected values"); + } + + trigger::Keys expectedKeys; + expectedKeys.reserve(expectedCollectionKeys_.size()); + for (auto const& element : expectedCollectionKeys_) { + expectedKeys.push_back(static_cast(element)); + } + + if (triggerEvent.collectionKeys() != expectedKeys) { + throwWithMessage("collectionKeys do not have expected values"); + } + + trigger::TriggerObjectCollection const& triggerObjectCollection = triggerEvent.getObjects(); + if (triggerObjectCollection.size() != expectedIds_.size()) { + throwWithMessage("triggerObjectCollection does not have expected size"); + } + for (unsigned int i = 0; i < triggerObjectCollection.size(); ++i) { + trigger::TriggerObject const& triggerObject = triggerObjectCollection[i]; + if (triggerObject.id() != expectedIds_[i]) { + throwWithMessage("triggerObjectCollection id does not have expected value"); + } + if (triggerObject.pt() != static_cast(expectedPts_[i])) { + throwWithMessage("triggerObjectCollection pt does not have expected value"); + } + if (triggerObject.eta() != static_cast(expectedEtas_[i])) { + throwWithMessage("triggerObjectCollection eta does not have expected value"); + } + if (triggerObject.phi() != static_cast(expectedPhis_[i])) { + throwWithMessage("triggerObjectCollection phi does not have expected value"); + } + if (triggerObject.mass() != static_cast(expectedMasses_[i])) { + throwWithMessage("triggerObjectCollection mass does not have expected value"); + } + } + + if (triggerEvent.sizeFilters() != expectedFilterTags_.size()) { + throwWithMessage("triggerFilters does not have expected size"); + } + + for (unsigned int i = 0; i < expectedFilterTags_.size(); ++i) { + if (triggerEvent.filterLabel(i) != expectedFilterTags_[i]) { + throwWithMessage("filterTags does not have expected value"); + } + trigger::Vids const& filterIds = triggerEvent.filterIds(i); + if (filterIds.size() != expectedElementsPerVector_) { + throwWithMessage("filterIds does not have expected size"); + } + trigger::Keys const& filterKeys = triggerEvent.filterKeys(i); + if (filterKeys.size() != expectedElementsPerVector_) { + throwWithMessage("filterKeys does not have expected size"); + } + for (unsigned int j = 0; j < expectedElementsPerVector_; ++j) { + if (filterIds[j] != expectedFilterIds_[i * expectedElementsPerVector_ + j]) { + throwWithMessage("filterIds does not contain expected values"); + } + if (filterKeys[j] != expectedFilterKeys_[i * expectedElementsPerVector_ + j]) { + throwWithMessage("filterKeys does not contain expected values"); + } + } + } + } + + void TestReadTriggerEvent::throwWithMessage(const char* msg) const { + throw cms::Exception("TestFailure") << "TestReadTriggerEvent::analyze, " << msg; + } + + void TestReadTriggerEvent::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("expectedUsedProcessName"); + desc.add>("expectedCollectionTags"); + desc.add>("expectedCollectionKeys"); + desc.add>("expectedIds"); + desc.add>("expectedPts"); + desc.add>("expectedEtas"); + desc.add>("expectedPhis"); + desc.add>("expectedMasses"); + desc.add>("expectedFilterTags"); + desc.add("expectedElementsPerVector"); + desc.add>("expectedFilterIds"); + desc.add>("expectedFilterKeys"); + desc.add("triggerEventTag"); + descriptions.addDefault(desc); + } +} // namespace edmtest + +using edmtest::TestReadTriggerEvent; +DEFINE_FWK_MODULE(TestReadTriggerEvent); diff --git a/DataFormats/HLTReco/test/TestTriggerEventFormat.sh b/DataFormats/HLTReco/test/TestTriggerEventFormat.sh new file mode 100755 index 0000000000000..99e724981d300 --- /dev/null +++ b/DataFormats/HLTReco/test/TestTriggerEventFormat.sh @@ -0,0 +1,19 @@ +#!/bin/sh -ex + +function die { echo $1: status $2 ; exit $2; } + +LOCAL_TEST_DIR=${SCRAM_TEST_PATH} + +cmsRun ${LOCAL_TEST_DIR}/create_TriggerEvent_test_file_cfg.py || die 'Failure using create_TriggerEvent_test_file_cfg.py' $? + +file=testTriggerEvent.root + +cmsRun ${LOCAL_TEST_DIR}/test_readTriggerEvent_cfg.py "$file" || die "Failure using test_readTriggerEvent_cfg.py $file" $? + +oldFiles="testTriggerEvent_CMSSW_13_0_0.root testTriggerEvent_CMSSW_13_1_0_pre3.root" +for file in $oldFiles; do + inputfile=$(edmFileInPath DataFormats/HLTReco/data/$file) || die "Failure edmFileInPath DataFormats/HLTReco/data/$file" $? + cmsRun ${LOCAL_TEST_DIR}/test_readTriggerEvent_cfg.py "$inputfile" || die "Failed to read old file $file" $? +done + +exit 0 diff --git a/DataFormats/HLTReco/test/TestWriteTriggerEvent.cc b/DataFormats/HLTReco/test/TestWriteTriggerEvent.cc new file mode 100644 index 0000000000000..608c105965ba9 --- /dev/null +++ b/DataFormats/HLTReco/test/TestWriteTriggerEvent.cc @@ -0,0 +1,151 @@ +// -*- C++ -*- +// +// Package: DataFormats/HLTReco +// Class: TestWriteTriggerEvent +// +/**\class edmtest::TestWriteTriggerEvent + Description: Used as part of tests that ensure the trigger::TriggerEvent + data format can be persistently written and in a subsequent process + read. First, this is done using the current release version for writing + and reading. In addition, the output file of the write process should + be saved permanently each time the trigger::TriggerEvent persistent data + format changes. In unit tests, we read each of those saved files to verify + that the current releases can read older versions of the data format. +*/ +// Original Author: W. David Dagenhart +// Created: 8 May 2023 + +#include "DataFormats/HLTReco/interface/TriggerEvent.h" +#include "DataFormats/HLTReco/interface/TriggerObject.h" +#include "DataFormats/HLTReco/interface/TriggerTypeDefs.h" +#include "FWCore/Framework/interface/Event.h" +#include "FWCore/Framework/interface/Frameworkfwd.h" +#include "FWCore/Framework/interface/global/EDProducer.h" +#include "FWCore/Framework/interface/MakerMacros.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ParameterSetDescription.h" +#include "FWCore/Utilities/interface/EDPutToken.h" +#include "FWCore/Utilities/interface/Exception.h" +#include "FWCore/Utilities/interface/StreamID.h" + +#include +#include +#include +#include + +namespace edmtest { + + class TestWriteTriggerEvent : public edm::global::EDProducer<> { + public: + TestWriteTriggerEvent(edm::ParameterSet const&); + void produce(edm::StreamID, edm::Event&, edm::EventSetup const&) const override; + static void fillDescriptions(edm::ConfigurationDescriptions&); + + private: + std::string usedProcessName_; + std::vector collectionTags_; + std::vector collectionKeys_; + std::vector ids_; + std::vector pts_; + std::vector etas_; + std::vector phis_; + std::vector masses_; + std::vector filterTags_; + unsigned int elementsPerVector_; + std::vector filterIds_; + std::vector filterKeys_; + + edm::EDPutTokenT triggerEventPutToken_; + }; + + TestWriteTriggerEvent::TestWriteTriggerEvent(edm::ParameterSet const& iPSet) + : usedProcessName_(iPSet.getParameter("usedProcessName")), + collectionTags_(iPSet.getParameter>("collectionTags")), + collectionKeys_(iPSet.getParameter>("collectionKeys")), + ids_(iPSet.getParameter>("ids")), + pts_(iPSet.getParameter>("pts")), + etas_(iPSet.getParameter>("etas")), + phis_(iPSet.getParameter>("phis")), + masses_(iPSet.getParameter>("masses")), + filterTags_(iPSet.getParameter>("filterTags")), + elementsPerVector_(iPSet.getParameter("elementsPerVector")), + filterIds_(iPSet.getParameter>("filterIds")), + filterKeys_(iPSet.getParameter>("filterKeys")), + + triggerEventPutToken_(produces()) { + if (ids_.size() != pts_.size() || ids_.size() != etas_.size() || ids_.size() != phis_.size() || + ids_.size() != masses_.size()) { + throw cms::Exception("TestFailure") << "TestWriteTriggerEvent, test configuration error: " + "ids, pts, etas, phis, and masses should have the same size."; + } + if (filterIds_.size() != elementsPerVector_ * filterTags_.size() || + filterKeys_.size() != elementsPerVector_ * filterTags_.size()) { + throw cms::Exception("TestFailure") + << "TestWriteTriggerEvent, test configuration error: " + "size of filterIds and size of filterKeys should equal size of filterTags times elementsPerVector"; + } + } + + void TestWriteTriggerEvent::produce(edm::StreamID, edm::Event& iEvent, edm::EventSetup const&) const { + // Fill a TriggerEvent object. Make sure all the containers inside + // of it have something in them (not empty). The values are meaningless. + // We will later check that after writing this object to persistent storage + // and then reading it in a later process we obtain matching values for + // all this content. + + auto triggerEvent = std::make_unique( + usedProcessName_, collectionTags_.size(), ids_.size(), filterTags_.size()); + trigger::Keys keys; + keys.reserve(collectionKeys_.size()); + for (auto const& element : collectionKeys_) { + keys.push_back(static_cast(element)); + } + triggerEvent->addCollections(collectionTags_, keys); + + trigger::TriggerObjectCollection triggerObjectCollection; + triggerObjectCollection.reserve(ids_.size()); + for (unsigned int i = 0; i < ids_.size(); ++i) { + triggerObjectCollection.emplace_back(ids_[i], + static_cast(pts_[i]), + static_cast(etas_[i]), + static_cast(phis_[i]), + static_cast(masses_[i])); + } + triggerEvent->addObjects(triggerObjectCollection); + + for (unsigned int i = 0; i < filterTags_.size(); ++i) { + trigger::Vids filterIds; + filterIds.reserve(elementsPerVector_); + trigger::Keys filterKeys; + filterKeys.reserve(elementsPerVector_); + for (unsigned int j = 0; j < elementsPerVector_; ++j) { + filterIds.push_back(filterIds_[i * elementsPerVector_ + j]); + filterKeys.push_back(filterKeys_[i * elementsPerVector_ + j]); + } + triggerEvent->addFilter(edm::InputTag(filterTags_[i]), filterIds, filterKeys); + } + + iEvent.put(triggerEventPutToken_, std::move(triggerEvent)); + } + + void TestWriteTriggerEvent::fillDescriptions(edm::ConfigurationDescriptions& descriptions) { + edm::ParameterSetDescription desc; + desc.add("usedProcessName"); + desc.add>("collectionTags"); + desc.add>("collectionKeys"); + desc.add>("ids"); + desc.add>("pts"); + desc.add>("etas"); + desc.add>("phis"); + desc.add>("masses"); + desc.add>("filterTags"); + desc.add("elementsPerVector"); + desc.add>("filterIds"); + desc.add>("filterKeys"); + descriptions.addDefault(desc); + } +} // namespace edmtest + +using edmtest::TestWriteTriggerEvent; +DEFINE_FWK_MODULE(TestWriteTriggerEvent); diff --git a/DataFormats/HLTReco/test/create_TriggerEvent_test_file_cfg.py b/DataFormats/HLTReco/test/create_TriggerEvent_test_file_cfg.py new file mode 100644 index 0000000000000..591b837394ee8 --- /dev/null +++ b/DataFormats/HLTReco/test/create_TriggerEvent_test_file_cfg.py @@ -0,0 +1,34 @@ +import FWCore.ParameterSet.Config as cms + +process = cms.Process("PROD") + +process.load("FWCore.MessageService.MessageLogger_cfi") + +process.source = cms.Source("EmptySource") +process.maxEvents.input = 1 + +process.triggerEventProducer = cms.EDProducer("TestWriteTriggerEvent", + # Test values below are meaningless. We just make sure when we read + # we get the same values. + usedProcessName = cms.string("testName"), + collectionTags = cms.vstring('moduleA', 'moduleB', 'moduleC'), + collectionKeys = cms.vuint32(11, 21, 31), + ids = cms.vint32(1, 3, 5), + # I stick to values exactly convertable to float + # to avoid potential rounding issues in the test. + pts = cms.vdouble(11.0, 21.0, 31.0), + etas = cms.vdouble(101.0, 102.0, 103.0), + phis = cms.vdouble(201.0, 202.0, 203.0), + masses = cms.vdouble(301.0, 302.0, 303.0), + filterTags = cms.vstring('moduleAA', 'moduleBB'), + elementsPerVector = cms.uint32(2), + filterIds = cms.vint32(1001, 1002, 1003, 1004), + filterKeys = cms.vuint32(2001, 2002, 2003, 2004) +) + +process.out = cms.OutputModule("PoolOutputModule", + fileName = cms.untracked.string('testTriggerEvent.root') +) + +process.path = cms.Path(process.triggerEventProducer) +process.endPath = cms.EndPath(process.out) diff --git a/DataFormats/HLTReco/test/test_readTriggerEvent_cfg.py b/DataFormats/HLTReco/test/test_readTriggerEvent_cfg.py new file mode 100644 index 0000000000000..3cf2a3cab5329 --- /dev/null +++ b/DataFormats/HLTReco/test/test_readTriggerEvent_cfg.py @@ -0,0 +1,35 @@ +import FWCore.ParameterSet.Config as cms +import sys + +process = cms.Process("READ") + +process.source = cms.Source("PoolSource", fileNames = cms.untracked.vstring("file:"+sys.argv[2])) +process.maxEvents.input = 1 + +process.testReadTriggerEvent = cms.EDAnalyzer("TestReadTriggerEvent", + expectedUsedProcessName = cms.string("testName"), + expectedCollectionTags = cms.vstring('moduleA', 'moduleB', 'moduleC'), + expectedCollectionKeys = cms.vuint32(11, 21, 31), + expectedIds = cms.vint32(1, 3, 5), + # I stick to values exactly convertable from double to float + # to avoid potential rounding issues in the test. + # (configuration only supports double not float and + # the data format holds floats) + expectedPts = cms.vdouble(11.0, 21.0, 31.0), + expectedEtas = cms.vdouble(101.0, 102.0, 103.0), + expectedPhis = cms.vdouble(201.0, 202.0, 203.0), + expectedMasses = cms.vdouble(301.0, 302.0, 303.0), + expectedFilterTags = cms.vstring('moduleAA', 'moduleBB'), + expectedElementsPerVector = cms.uint32(2), + expectedFilterIds = cms.vint32(1001, 1002, 1003, 1004), + expectedFilterKeys = cms.vuint32(2001, 2002, 2003, 2004), + triggerEventTag = cms.InputTag("triggerEventProducer", "", "PROD") +) + +process.out = cms.OutputModule("PoolOutputModule", + fileName = cms.untracked.string('testTriggerEvent2.root') +) + +process.path = cms.Path(process.testReadTriggerEvent) + +process.endPath = cms.EndPath(process.out)