diff --git a/DQM/HLTEvF/plugins/ScoutingCollectionMonitor.cc b/DQM/HLTEvF/plugins/ScoutingCollectionMonitor.cc index 81249242ade27..f52367e253c3d 100644 --- a/DQM/HLTEvF/plugins/ScoutingCollectionMonitor.cc +++ b/DQM/HLTEvF/plugins/ScoutingCollectionMonitor.cc @@ -725,20 +725,6 @@ void ScoutingCollectionMonitor::analyze(const edm::Event& iEvent, const edm::Eve rechitZeroSuppression_pho_hist->Fill(pho.rechitZeroSuppression() ? -1. : 1.); } - // --- ValueMaps --- - const auto& vmBestIdx = iEvent.get(vmBestTrackIndexToken_); - const auto& vmD0 = iEvent.get(vmTrkd0Token_); - const auto& vmDz = iEvent.get(vmTrkdzToken_); - const auto& vmPt = iEvent.get(vmTrkptToken_); - const auto& vmEta = iEvent.get(vmTrketaToken_); - const auto& vmPhi = iEvent.get(vmTrkphiToken_); - const auto& vmPMode = iEvent.get(vmTrkpModeToken_); - const auto& vmEtaMode = iEvent.get(vmTrketaModeToken_); - const auto& vmPhiMode = iEvent.get(vmTrkphiModeToken_); - const auto& vmQoverpModeErr = iEvent.get(vmTrkqoverpModeErrorToken_); - const auto& vmChi2 = iEvent.get(vmTrkchi2overndfToken_); - const auto& vmCharge = iEvent.get(vmTrkchargeToken_); - // determine the beamspot position (if it exists in the event) std::unique_ptr beamspotVertex{nullptr}; edm::Handle beamSpotH; @@ -765,6 +751,48 @@ void ScoutingCollectionMonitor::analyze(const edm::Event& iEvent, const edm::Eve return bestVtx; }; + // --- best electron ValueMaps --- + edm::Handle> vmBestIdxH; + edm::Handle> vmD0H; + edm::Handle> vmDzH; + edm::Handle> vmPtH; + edm::Handle> vmEtaH; + edm::Handle> vmPhiH; + edm::Handle> vmPModeH; + edm::Handle> vmEtaModeH; + edm::Handle> vmPhiModeH; + edm::Handle> vmQoverpModeErrH; + edm::Handle> vmChi2H; + edm::Handle> vmChargeH; + + if (!getValidHandle(iEvent, vmBestTrackIndexToken_, vmBestIdxH, "vmBestTrackIndex") || + !getValidHandle(iEvent, vmTrkd0Token_, vmD0H, "vmTrkd0") || + !getValidHandle(iEvent, vmTrkdzToken_, vmDzH, "vmTrkdz") || + !getValidHandle(iEvent, vmTrkptToken_, vmPtH, "vmTrkpt") || + !getValidHandle(iEvent, vmTrketaToken_, vmEtaH, "vmTrketa") || + !getValidHandle(iEvent, vmTrkphiToken_, vmPhiH, "vmTrkphi") || + !getValidHandle(iEvent, vmTrkpModeToken_, vmPModeH, "vmTrkpMode") || + !getValidHandle(iEvent, vmTrketaModeToken_, vmEtaModeH, "vmTrketaMode") || + !getValidHandle(iEvent, vmTrkphiModeToken_, vmPhiModeH, "vmTrkphiMode") || + !getValidHandle(iEvent, vmTrkqoverpModeErrorToken_, vmQoverpModeErrH, "vmTrkqoverpModeError") || + !getValidHandle(iEvent, vmTrkchi2overndfToken_, vmChi2H, "vmTrkchi2overndf") || + !getValidHandle(iEvent, vmTrkchargeToken_, vmChargeH, "vmTrkcharge")) { + return; + } + + const auto& vmBestIdx = *vmBestIdxH; + const auto& vmD0 = *vmD0H; + const auto& vmDz = *vmDzH; + const auto& vmPt = *vmPtH; + const auto& vmEta = *vmEtaH; + const auto& vmPhi = *vmPhiH; + const auto& vmPMode = *vmPModeH; + const auto& vmEtaMode = *vmEtaModeH; + const auto& vmPhiMode = *vmPhiModeH; + const auto& vmQoverpModeErr = *vmQoverpModeErrH; + const auto& vmChi2 = *vmChi2H; + const auto& vmCharge = *vmChargeH; + // fill all the electron histograms for (std::size_t iEl = 0; iEl < electronsH->size(); ++iEl) { // Ref needed to index into the ValueMaps @@ -832,6 +860,7 @@ void ScoutingCollectionMonitor::analyze(const edm::Event& iEvent, const edm::Eve }; // compute w.r.t. beamspot + // skip beamspot-based plots if not valid if (beamspotVertex) { auto [dxy_bs, dz_bs] = computeIP(beamspotVertex->x(), beamspotVertex->y(), beamspotVertex->z()); trkd0BS_ele_hist->Fill(dxy_bs); diff --git a/DQM/HLTEvF/test/BuildFile.xml b/DQM/HLTEvF/test/BuildFile.xml index fa3d24558549b..42fde63f8c5af 100644 --- a/DQM/HLTEvF/test/BuildFile.xml +++ b/DQM/HLTEvF/test/BuildFile.xml @@ -1,2 +1,8 @@ + + + + + + diff --git a/DQM/HLTEvF/test/testScoutingCollectionMonitor.cc b/DQM/HLTEvF/test/testScoutingCollectionMonitor.cc new file mode 100644 index 0000000000000..626d571af7dfd --- /dev/null +++ b/DQM/HLTEvF/test/testScoutingCollectionMonitor.cc @@ -0,0 +1,195 @@ +#include "FWCore/TestProcessor/interface/TestProcessor.h" +#include "FWCore/Utilities/interface/Exception.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include +#include +#include "TROOT.h" + +// Scouting data formats needed to put mock collections +#include "DataFormats/Scouting/interface/Run3ScoutingMuon.h" +#include "DataFormats/Scouting/interface/Run3ScoutingElectron.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPhoton.h" +#include "DataFormats/Scouting/interface/Run3ScoutingPFJet.h" +#include "DataFormats/Scouting/interface/Run3ScoutingParticle.h" +#include "DataFormats/Scouting/interface/Run3ScoutingTrack.h" +#include "DataFormats/Scouting/interface/Run3ScoutingVertex.h" +#include "DataFormats/Common/interface/ValueMap.h" + +#define CATCH_CONFIG_MAIN +#include "catch2/catch_all.hpp" + +//Function to run the catch2 tests +//___________________________________________________________________________________________ +void runTestForAnalyzer(const std::string& baseConfig, const std::string& analyzerName) { + edm::test::TestProcessor::Config config{baseConfig}; + + SECTION(analyzerName + " base configuration is OK") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(edm::test::TestProcessor(config)); + } + + SECTION(analyzerName + " No Runs data") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testWithNoRuns()); + } + + SECTION(analyzerName + " beginJob and endJob only") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testBeginAndEndJobOnly()); + } + + SECTION(analyzerName + " No event data") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.test()); + } + + SECTION(analyzerName + " Run with no LuminosityBlocks") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testRunWithNoLuminosityBlocks()); + } + + SECTION(analyzerName + " LuminosityBlock with no Events") { + gROOT->GetList()->Delete(); // to get rid of duplicated ME warnings + edm::test::TestProcessor tester(config); + REQUIRE_NOTHROW(tester.testLuminosityBlockWithNoEvents()); + } +} + +// Function to generate base configuration string +//___________________________________________________________________________________________ +std::string generateBaseConfig(const std::string& analyzerName) { + // Define a raw string literal + constexpr const char* rawString = R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.load("MagneticField.Engine.uniformMagneticField_cfi") +process.load("Configuration.Geometry.GeometryExtended2024Reco_cff") +process.load("Configuration.StandardSequences.FrontierConditions_GlobalTag_cff") +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:phase1_2024_design', '') +from DQM.HLTEvF.{}_cfi import {} +process.dqmAnalyzer = {} +process.moduleToTest(process.dqmAnalyzer) +process.add_(cms.Service('DQMStore')) +process.add_(cms.Service('MessageLogger')) +process.add_(cms.Service('JobReportService')) +process.add_(cms.Service('SiteLocalConfigService')) + )_"; + + // Format the raw string literal using std::format + return std::format(rawString, analyzerName, analyzerName, analyzerName); +} + +// Generates a config where the beamspot label is replaced with a label +// that will never be produced, exercising the invalid-beamspot branch. +// All mandatory collections are declared as "to be put" by the test harness. +//___________________________________________________________________________________________ +std::string generateNoBeamspotConfig(const std::string& analyzerName) { + constexpr const char* rawString = R"_(from FWCore.TestProcessor.TestProcess import * +process = TestProcess() +process.load("MagneticField.Engine.uniformMagneticField_cfi") +process.load("Configuration.Geometry.GeometryExtended2024Reco_cff") +process.load("Configuration.StandardSequences.FrontierConditions_GlobalTag_cff") +from Configuration.AlCa.GlobalTag import GlobalTag +process.GlobalTag = GlobalTag(process.GlobalTag, 'auto:phase1_2024_design', '') +from DQM.HLTEvF.{}_cfi import {} +process.dqmAnalyzer = {} +# Override the beamSpot to point at a label that will never be produced, +# so getValidHandle returns false for beamspot while all mandatory +# collections are still present (supplied via TestProcessor::put calls). +process.dqmAnalyzer.beamSpot = cms.InputTag("nonExistentBeamSpot") +process.dqmAnalyzer.onlyScouting = cms.bool(True) +process.moduleToTest(process.dqmAnalyzer) +process.add_(cms.Service('DQMStore')) +process.add_(cms.Service('MessageLogger')) +process.add_(cms.Service('JobReportService')) +process.add_(cms.Service('SiteLocalConfigService')) + )_"; + + return std::format(rawString, analyzerName, analyzerName, analyzerName); +} + +//___________________________________________________________________________________________ +TEST_CASE("ScoutingCollectionMonitor tests", "[ScoutingCollectionMonitor]") { + const std::string baseConfig = generateBaseConfig("scoutingCollectionMonitor"); + runTestForAnalyzer(baseConfig, "ScoutingCollectionMonitor"); +} + +// Separate test case that exercises the invalid-beamspot code path. +// All mandatory collections are supplied as empty vectors so the early-return +// guard in analyze() is satisfied, but the beamspot handle will be invalid, +// so the beamspot-based histogram fills (tk_BS_dxy, tk_BS_dz, trkd0BS, trkdzBS) +// are skipped, verifying that branch does not crash. +//___________________________________________________________________________________________ +TEST_CASE("ScoutingCollectionMonitor invalid beamspot", "[ScoutingCollectionMonitor]") { + const std::string config = generateNoBeamspotConfig("scoutingCollectionMonitor"); + edm::test::TestProcessor::Config cfg{config}; + + // The first argument must match the module label used in the InputTag + // in the Python config (e.g. cms.InputTag("hltScoutingMuonPackerNoVtx")) + auto muonsToken = cfg.produces>("hltScoutingMuonPackerNoVtx"); + auto muonsVtxToken = cfg.produces>("hltScoutingMuonPackerVtx"); + auto electronsToken = cfg.produces>("hltScoutingEgammaPacker"); + auto photonsToken = cfg.produces>("hltScoutingEgammaPacker"); + auto pfjetsToken = cfg.produces>("hltScoutingPFPacker"); + auto pfcandsToken = cfg.produces>("hltScoutingPFPacker"); + auto tracksToken = cfg.produces>("hltScoutingTrackPacker"); + auto pvToken = cfg.produces>("hltScoutingPrimaryVertexPacker", "primaryVtx"); + auto dvToken = cfg.produces>("hltScoutingMuonPackerVtx", "displacedVtx"); + auto dvNoVtxToken = cfg.produces>("hltScoutingMuonPackerNoVtx", "displacedVtx"); + auto rhoToken = cfg.produces("hltScoutingPFPacker", "rho"); + auto metPtToken = cfg.produces("hltScoutingPFPacker", "pfMetPt"); + auto metPhiToken = cfg.produces("hltScoutingPFPacker", "pfMetPhi"); + + const std::string prod = "run3ScoutingElectronBestTrack"; + auto vmBestTrackIndexToken = cfg.produces>(prod, "Run3ScoutingElectronBestTrackIndex"); + auto vmTrkd0Token = cfg.produces>(prod, "Run3ScoutingElectronTrackd0"); + auto vmTrkdzToken = cfg.produces>(prod, "Run3ScoutingElectronTrackdz"); + auto vmTrkptToken = cfg.produces>(prod, "Run3ScoutingElectronTrackpt"); + auto vmTrketaToken = cfg.produces>(prod, "Run3ScoutingElectronTracketa"); + auto vmTrkphiToken = cfg.produces>(prod, "Run3ScoutingElectronTrackphi"); + auto vmTrkpModeToken = cfg.produces>(prod, "Run3ScoutingElectronTrackpMode"); + auto vmTrketaModeToken = cfg.produces>(prod, "Run3ScoutingElectronTracketaMode"); + auto vmTrkphiModeToken = cfg.produces>(prod, "Run3ScoutingElectronTrackphiMode"); + auto vmTrkqoverpModeErrorToken = cfg.produces>(prod, "Run3ScoutingElectronTrackqoverpModeError"); + auto vmTrkchi2overndfToken = cfg.produces>(prod, "Run3ScoutingElectronTrackchi2overndf"); + auto vmTrkchargeToken = cfg.produces>(prod, "Run3ScoutingElectronTrackcharge"); + + auto tracks = std::make_unique>(); + tracks->emplace_back(); // default constructed + + SECTION("Runs without crashing when beamspot handle is invalid") { + gROOT->GetList()->Delete(); + edm::test::TestProcessor tester(cfg); + + REQUIRE_NOTHROW(tester.test(std::make_pair(muonsToken, std::make_unique>()), + std::make_pair(muonsVtxToken, std::make_unique>()), + std::make_pair(electronsToken, std::make_unique>()), + std::make_pair(photonsToken, std::make_unique>()), + std::make_pair(pfjetsToken, std::make_unique>()), + std::make_pair(pfcandsToken, std::make_unique>()), + std::make_pair(tracksToken, std::move(tracks)), + std::make_pair(pvToken, std::make_unique>()), + std::make_pair(dvToken, std::make_unique>()), + std::make_pair(dvNoVtxToken, std::make_unique>()), + std::make_pair(rhoToken, std::make_unique(0.0)), + std::make_pair(metPtToken, std::make_unique(0.0)), + std::make_pair(metPhiToken, std::make_unique(0.0)), + std::make_pair(vmBestTrackIndexToken, std::make_unique>()), + std::make_pair(vmTrkd0Token, std::make_unique>()), + std::make_pair(vmTrkdzToken, std::make_unique>()), + std::make_pair(vmTrkptToken, std::make_unique>()), + std::make_pair(vmTrketaToken, std::make_unique>()), + std::make_pair(vmTrkphiToken, std::make_unique>()), + std::make_pair(vmTrkpModeToken, std::make_unique>()), + std::make_pair(vmTrketaModeToken, std::make_unique>()), + std::make_pair(vmTrkphiModeToken, std::make_unique>()), + std::make_pair(vmTrkqoverpModeErrorToken, std::make_unique>()), + std::make_pair(vmTrkchi2overndfToken, std::make_unique>()), + std::make_pair(vmTrkchargeToken, std::make_unique>()))); + } +}