2"""Shared harness for the EventSelectionConfig unit tests.
4Each *_unitTest.py script builds a real ConfigSequence containing a single
5EventSelectionConfig block, configures it through a real ConfigAccumulator,
6and inspects the resulting algorithm sequence. No mocking: the accumulator
7instantiates the real CP algorithms, so a Python/C++ property-name mismatch
8is caught here rather than at runtime.
10In a full job the object-config blocks register the input containers and their
11selection working points upstream. In isolation we register them here:
12 * input containers -> config.setSourceName(container, container)
13 * object selection WPs -> config.addSelection(container, wp, decoration)
16from AnaAlgorithm.AlgSequence
import AlgSequence
17from AnaAlgorithm.DualUseConfig
import isAthena
18from AnalysisAlgorithmsConfig.ConfigSequence
import ConfigSequence
19from AnalysisAlgorithmsConfig.ConfigAccumulator
import ConfigAccumulator, DataType
20from EventSelectionAlgorithms.EventSelectionConfig
import (
21 EventSelectionConfig, EventSelectionMergerConfig,
28_DUMMY_WPS = [
"baseline",
"mysel",
"goodjet",
"centraljet",
"topjet",
29 "elsel",
"musel",
"esel",
"msel",
"tsel"]
33 """Strip a trailing '.selection' to get the bare container name."""
34 return spec.split(
".")[0]
38 algSeq = AlgSequence()
39 config = ConfigAccumulator(dataType=dataType, algSeq=algSeq)
40 for cont
in {
_container(c)
for c
in containers}:
41 config.setSourceName(cont, cont)
45 config.addSelection(cont, wp, f
"{wp}_%SYS%")
49def run(cuts, *, dataType=DataType.FullSim, containers=None,
50 selectionName="SR", **options):
51 """Configure one EventSelectionConfig block and return its algorithms.
53 `containers` maps EventSelectionConfig option names to container specs,
54 e.g. {'electrons': 'AnaElectrons'} or {'jets': 'AnaJets.baseline'}.
56 containers = containers
or {}
58 seq = ConfigSequence()
59 block = EventSelectionConfig()
60 block.setOptionValue(
"selectionName", selectionName)
61 block.setOptionValue(
"selectionCuts", cuts)
62 for opt, cname
in containers.items():
63 block.setOptionValue(opt, cname)
64 for opt, val
in options.items():
65 block.setOptionValue(opt, val)
67 seq.fullConfigure(config)
69 return config.CA.getEventAlgos()
73def run_merger(region_names, *, dataType=DataType.FullSim, noFilter=False):
74 """Build real regions then the merger, and return the algorithm sequence.
76 Each region is a trivial EventSelectionConfig (RUN_NUMBER + SAVE) so that
77 the merger's `EventSelection` dependency is satisfied and `eventSelectionNames`
78 is populated through the normal SAVE path (no manual metadata).
81 seq = ConfigSequence()
82 for name
in region_names:
83 block = EventSelectionConfig()
84 block.setOptionValue(
"selectionName", name)
85 block.setOptionValue(
"selectionCuts",
"RUN_NUMBER >= 0\nSAVE")
87 merger = EventSelectionMergerConfig()
88 merger._instance_number = 1
89 merger.setOptionValue(
"noFilter", noFilter)
91 seq.fullConfigure(config)
93 return config.CA.getEventAlgos()
98 """Selector algorithms only (exclude the SAVE filter)."""
99 return [a
for a
in algs
if a.getType() !=
"CP::SaveFilterAlg"]
103 return [a
for a
in algs
if substr
in a.getName()]
106def prop(alg, name, default=_UNSET):
107 """Read a property set on the algorithm; return `default` if unset."""
108 val = getattr(alg, name, _UNSET)
110 if default
is _UNSET:
111 raise AttributeError(f
"{alg.getName()} has no property {name!r}")
_new_accumulator(dataType, containers)
prop(alg, name, default=_UNSET)
run(cuts, *, dataType=DataType.FullSim, containers=None, selectionName="SR", **options)
run_merger(region_names, *, dataType=DataType.FullSim, noFilter=False)