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