Belle II Software  release-05-01-25
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=[1], 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  def event(self):
86  self.results.create()
87  # and add all the results that are used on express reco just to test all paths
88  self.results.addResult("software_trigger_cut&all&total_result", 1)
89  self.results.addResult("software_trigger_cut&skim&accept_mumutight", 1)
90  self.results.addResult("software_trigger_cut&skim&accept_dstar_1", 1)
91 
92  path.add_module(FakeHLTResult())
93 
94  # remove everything but HLT input raw objects
95  branch_names = RAWDATA_OBJECTS + ["EventMetaData", "TRGSummary"]
96  if not passthrough:
97  branch_names += ["SoftwareTriggerResult"]
98  if location == constants.Location.hlt:
99  branch_names.remove("RawPXDs")
100  branch_names.remove("ROIs")
101 
102  # There is no packer for the following objects :(
103  branch_names.remove("RawTRGs")
104 
105  path.add_module("RootOutput", outputFileName=output_file_name, branchNames=branch_names)
106 
107  basf2.process(path)
108 
109 
110 def test_script(script_location, input_file_name, temp_dir):
111  """
112  Test a script with the given file path using the given input file.
113  Raises an exception if the execution fails or if the needed output is not in
114  the output file.
115  The results are stored in the temporary directory location.
116 
117  :param script_location: the script to test
118  :param input_file_name: the file path of the input file
119  :param temp_dir: where to store and run
120  """
121  input_buffer = "UNUSED" # unused
122  output_buffer = "UNUSED" # unused
123  histo_port = 6666 # unused
124 
125  random_seed = "".join(random.choices("abcdef", k=4))
126 
127  histos_file_name = os.path.join(temp_dir, f"{random_seed}_histos.root")
128  output_file_name = os.path.join(temp_dir, f"{random_seed}_output.root")
129  # TODO: should we use the default global tag here?
130  globaltags = list(basf2.conditions.default_globaltags)
131  num_processes = 1
132 
133  os.chdir(os.path.dirname(script_location))
134  cmd = [sys.executable, script_location,
135  "--central-db-tag"] + globaltags + [
136  "--input-file", os.path.abspath(input_file_name),
137  "--histo-output-file", os.path.abspath(histos_file_name),
138  "--output-file", os.path.abspath(output_file_name),
139  "--number-processes", str(num_processes),
140  input_buffer, output_buffer, str(histo_port)]
141 
142  subprocess.check_call(cmd)
143 
144  test_path = basf2.Path()
145  test_path.add_module("RootInput", inputFileName=output_file_name)
146  test_path.add_module(CheckForCorrectHLTResults())
147 
148  if "expressreco" not in script_location and "beam_reco" in script_location:
149  basf2.process(test_path)
150 
151 
152 def test_folder(location, run_type, exp_number, phase, passthrough=False):
153  """
154  Run all hlt operation scripts in a given folder
155  and test the outputs of the files.
156 
157  Will call the test_script function on all files in the folder given
158  by the location after having created a suitable input file with the given
159  experiment number.
160 
161  :param location: hlt or expressreco, depending on which operation files to run
162  and which input to simulate
163  :param run_type: cosmic or beam, depending on which operation files to run
164  and which input to simulate
165  :param exp_number: which experiment number to simulate
166  :param phase: where to look for the operation files (will search in the folder
167  hlt/operation/{phase}/global/{location}/evp_scripts/)
168  :param passthrough: only relevant for express reco: If true don't create a
169  software trigger result in the input file to test running
170  express reco if hlt is in passthrough mode
171 
172  """
173  temp_dir = tempfile.mkdtemp()
174  output_file_name = os.path.join(temp_dir, f"{location.name}_{run_type.name}.root")
175  generate_input_file(run_type=run_type, location=location,
176  output_file_name=output_file_name, exp_number=exp_number,
177  passthrough=passthrough)
178 
179  script_dir = find_file(f"hlt/operation/{phase}/global/{location.name}/evp_scripts/")
180  run_at_least_one = False
181  for script_location in glob(os.path.join(script_dir, f"run_{run_type.name}*.py")):
182  run_at_least_one = True
183  test_script(script_location, input_file_name=output_file_name, temp_dir=temp_dir)
184 
185  assert run_at_least_one
186 
187  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:324