Belle II Software  release-05-02-19
test_support.py
1 import random
2 import shutil
3 import subprocess
4 import os
5 import sys
6 import tempfile
7 from glob import glob
8 
9 import basf2
10 import generators
11 from simulation import add_simulation
12 from rawdata import add_packers
13 from L1trigger import add_tsim
14 from softwaretrigger import constants
15 from softwaretrigger.constants import DEFAULT_EXPRESSRECO_COMPONENTS, RAWDATA_OBJECTS, DEFAULT_HLT_COMPONENTS
16 from ROOT import Belle2
17 find_file = Belle2.FileSystem.findFile
18 
19 
20 class CheckForCorrectHLTResults(basf2.Module):
21  """Test module for assuring correct data store content"""
22 
23  def event(self):
24  """reimplementation of Module::event()."""
25  sft_trigger = Belle2.PyStoreObj("SoftwareTriggerResult")
26 
27  if not sft_trigger.isValid():
28  basf2.B2FATAL("SoftwareTriggerResult object not created")
29  elif len(sft_trigger.getResults()) == 0:
30  basf2.B2FATAL("SoftwareTriggerResult exists but has no entries")
31 
32  if not Belle2.PyStoreArray("ROIs").isValid():
33  basf2.B2FATAL("ROIs are not present")
34 
35 
36 def generate_input_file(run_type, location, output_file_name, exp_number, passthrough):
37  """
38  Generate an input file for usage in the test.
39  Simulate uubar for "beam" and two muons for "cosmic" setting.
40 
41  Only raw data will be stored to the given output file.
42  :param run_type: Whether to simulate cosmic or beam
43  :param location: Whether to simulate expressreco (with ROIs) or hlt (no PXD)
44  :param output_file_name: where to store the result file
45  :param exp_number: which experiment number to simulate
46  :param passthrough: if true don't generate a trigger result in the input file
47  """
48  if os.path.exists(output_file_name):
49  return
50 
51  basf2.set_random_seed(12345)
52 
53  path = basf2.Path()
54  path.add_module('EventInfoSetter', evtNumList=[4], expList=[exp_number])
55 
56  if run_type == constants.RunTypes.beam:
57  generators.add_continuum_generator(path, finalstate="uubar")
58  elif run_type == constants.RunTypes.cosmic:
59  # add something which looks a tiny bit like a cosmic generator. We
60  # cannot use the normal cosmic generator as that needs a bigger
61  # simulation top volume than the default geometry from the database.
62  path.add_module("ParticleGun", pdgCodes=[-13, 13], momentumParams=[10, 200])
63 
64  add_simulation(path, usePXDDataReduction=(location == constants.Location.expressreco))
65  add_tsim(path)
66 
67  if location == constants.Location.hlt:
68  components = DEFAULT_HLT_COMPONENTS
69  elif location == constants.Location.expressreco:
70  components = DEFAULT_EXPRESSRECO_COMPONENTS
71  else:
72  basf2.B2FATAL("Location {} for test is not supported".format(location.name))
73 
74  components.append("TRG")
75 
76  add_packers(path, components=components)
77 
78  # express reco expects to have an HLT results, so lets add a fake one
79  if location == constants.Location.expressreco and not passthrough:
80  class FakeHLTResult(basf2.Module):
81  def initialize(self):
82  self.results = Belle2.PyStoreObj(Belle2.SoftwareTriggerResult.Class(), "SoftwareTriggerResult")
83  self.results.registerInDataStore()
84 
85  self.EventMetaData = Belle2.PyStoreObj("EventMetaData")
86 
87  def event(self):
88  self.results.create()
89  # First event: Add all the results that are used on express reco just to test all paths
90  if (self.EventMetaData.obj().getEvent() == 1):
91  self.results.addResult("software_trigger_cut&all&total_result", 1)
92  self.results.addResult("software_trigger_cut&skim&accept_mumutight", 1)
93  self.results.addResult("software_trigger_cut&skim&accept_dstar_1", 1)
94  self.results.addResult("software_trigger_cut&filter&L1_trigger", 1)
95  # Second event: No skim lines to replicate a HLT discared event with filter ON
96  elif (self.EventMetaData.obj().getEvent() == 2):
97  self.results.addResult("software_trigger_cut&all&total_result", 1)
98  self.results.addResult("software_trigger_cut&filter&L1_trigger", 1)
99  # Third event: Does not pass through L1 passthrough
100  elif (self.EventMetaData.obj().getEvent() == 3):
101  self.results.addResult("software_trigger_cut&all&total_result", 1)
102  self.results.addResult("software_trigger_cut&skim&accept_mumutight", 1)
103  self.results.addResult("software_trigger_cut&skim&accept_dstar_1", 1)
104  self.results.addResult("software_trigger_cut&filter&L1_trigger", 0)
105  # Fourth event: HLT discarded but passes HLT skims (possible in HLT filter OFF mode)
106  elif (self.EventMetaData.obj().getEvent() == 4):
107  self.results.addResult("software_trigger_cut&all&total_result", 0)
108  self.results.addResult("software_trigger_cut&skim&accept_mumutight", 1)
109  self.results.addResult("software_trigger_cut&skim&accept_dstar_1", 1)
110  self.results.addResult("software_trigger_cut&filter&L1_trigger", 0)
111 
112  path.add_module(FakeHLTResult())
113 
114  # remove everything but HLT input raw objects
115  branch_names = RAWDATA_OBJECTS + ["EventMetaData", "TRGSummary"]
116  if not passthrough:
117  branch_names += ["SoftwareTriggerResult"]
118  if location == constants.Location.hlt:
119  branch_names.remove("RawPXDs")
120  branch_names.remove("ROIs")
121 
122  # There is no packer for the following objects :(
123  branch_names.remove("RawTRGs")
124 
125  path.add_module("RootOutput", outputFileName=output_file_name, branchNames=branch_names)
126 
127  basf2.process(path)
128 
129 
130 def test_script(script_location, input_file_name, temp_dir):
131  """
132  Test a script with the given file path using the given input file.
133  Raises an exception if the execution fails or if the needed output is not in
134  the output file.
135  The results are stored in the temporary directory location.
136 
137  :param script_location: the script to test
138  :param input_file_name: the file path of the input file
139  :param temp_dir: where to store and run
140  """
141  input_buffer = "UNUSED" # unused
142  output_buffer = "UNUSED" # unused
143  histo_port = 6666 # unused
144 
145  random_seed = "".join(random.choices("abcdef", k=4))
146 
147  histos_file_name = os.path.join(temp_dir, f"{random_seed}_histos.root")
148  output_file_name = os.path.join(temp_dir, f"{random_seed}_output.root")
149  # TODO: should we use the default global tag here?
150  globaltags = list(basf2.conditions.default_globaltags)
151  num_processes = 1
152 
153  os.chdir(os.path.dirname(script_location))
154  cmd = [sys.executable, script_location,
155  "--central-db-tag"] + globaltags + [
156  "--input-file", os.path.abspath(input_file_name),
157  "--histo-output-file", os.path.abspath(histos_file_name),
158  "--output-file", os.path.abspath(output_file_name),
159  "--number-processes", str(num_processes),
160  input_buffer, output_buffer, str(histo_port)]
161 
162  subprocess.check_call(cmd)
163 
164  test_path = basf2.Path()
165  test_path.add_module("RootInput", inputFileName=output_file_name)
166  test_path.add_module(CheckForCorrectHLTResults())
167 
168  if "expressreco" not in script_location and "beam_reco" in script_location:
169  basf2.process(test_path)
170 
171 
172 def test_folder(location, run_type, exp_number, phase, passthrough=False):
173  """
174  Run all hlt operation scripts in a given folder
175  and test the outputs of the files.
176 
177  Will call the test_script function on all files in the folder given
178  by the location after having created a suitable input file with the given
179  experiment number.
180 
181  :param location: hlt or expressreco, depending on which operation files to run
182  and which input to simulate
183  :param run_type: cosmic or beam, depending on which operation files to run
184  and which input to simulate
185  :param exp_number: which experiment number to simulate
186  :param phase: where to look for the operation files (will search in the folder
187  hlt/operation/{phase}/global/{location}/evp_scripts/)
188  :param passthrough: only relevant for express reco: If true don't create a
189  software trigger result in the input file to test running
190  express reco if hlt is in passthrough mode
191 
192  """
193  temp_dir = tempfile.mkdtemp()
194  output_file_name = os.path.join(temp_dir, f"{location.name}_{run_type.name}.root")
195  generate_input_file(run_type=run_type, location=location,
196  output_file_name=output_file_name, exp_number=exp_number,
197  passthrough=passthrough)
198 
199  script_dir = find_file(f"hlt/operation/{phase}/global/{location.name}/evp_scripts/")
200  run_at_least_one = False
201  for script_location in glob(os.path.join(script_dir, f"run_{run_type.name}*.py")):
202  run_at_least_one = True
203  test_script(script_location, input_file_name=output_file_name, temp_dir=temp_dir)
204 
205  assert run_at_least_one
206 
207  shutil.rmtree(temp_dir)
softwaretrigger.test_support.CheckForCorrectHLTResults
Definition: test_support.py:20
Belle2::PyStoreObj
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:69
softwaretrigger.constants
Definition: constants.py:1
basf2.process
def process(path, max_event=0)
Definition: __init__.py:25
softwaretrigger.test_support.CheckForCorrectHLTResults.event
def event(self)
Definition: test_support.py:23
Belle2::PyStoreArray
a (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:58
generators.add_continuum_generator
def add_continuum_generator(path, finalstate, userdecfile='', *skip_on_failure=True)
Definition: generators.py:331