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