ATLAS Offline Software
Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py
Go to the documentation of this file.
1 #
2 # Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
3 #
4 
5 '''
6 Definition of a Trigger ART test to be configured and executed
7 '''
8 
9 import sys
10 import os
11 import json
12 import subprocess
13 from collections import OrderedDict
14 
15 from TrigValTools.TrigValSteering.Common import get_logger, art_result, clear_art_summary, package_prefix_dict
16 from TrigValTools.TrigValSteering.Step import get_step_from_list, get_step_type_from_list
17 
18 
19 class Test(object):
20  '''Definition of a Trigger ART test to be configured and executed'''
21  def __init__(self):
22  self.log = get_logger()
23  self.name = None
24  self.package_name = None
25  self.art_type = None
26  self.exec_steps = []
27  self.check_steps = []
28  self.dry_run = None
29  self.configured = False
30 
31  def configure(self):
32  # Configure name
33  self.configure_name()
34  self.log.info("Configuring test %s", self.name)
35 
36  # Configure timeout for all steps
37  self.configure_timeout()
38 
39  # Configure steps
40  for step in self.exec_steps:
41  step.configure(self)
42  for step in self.check_steps:
43  self.log.debug('Configuring check step %s', step.name)
44  step.configure(self)
45 
46  duplicate_names = self.duplicate_step_names()
47  if len(duplicate_names) > 0:
49  'Found test steps with duplicate names: '
50  '{}'.format(duplicate_names)+' Aborting because this'
51  ' configuration could lead to overwriting logs')
52 
53  # Configure dry_run option
54  if self.dry_run is None:
55  # A hook to use dry_run for unit tests
56  env = os.environ.get('TRIGVALSTEERING_DRY_RUN')
57  if env:
58  self.dry_run = True
59 
60  self.configured = True
61 
62  # Print configuration
63  self.log.debug(
64  'Test configuration complete:\n-- %s',
65  '\n-- '.join(['{}: {}'.format(k, v) for k, v in self.__dict__.items()]))
66 
67  def run(self):
68  if not self.configured:
69  self.configure()
70 
71  self.log.info("Running test %s", self.name)
72 
73  # Clear the result summary log
75 
76  # Store the executed commands
77  commands = OrderedDict()
78 
79  # Pre-exec - a useful hook for some workarounds
80  pre_exec_cmd = self.pre_exec()
81  if len(pre_exec_cmd) > 0:
82  commands['pre_exec'] = pre_exec_cmd
83 
84  commands['exec_steps'] = []
85  commands['check_steps'] = []
86 
87  # Run the exec steps
88  self.run_steps(self.exec_steps, commands['exec_steps'])
89 
90  # Make a summary result code for all exec steps if there are multiple
91  exec_summary = 0
92  if len(self.exec_steps) > 1:
93  for step in self.exec_steps:
94  if step.result > exec_summary:
95  exec_summary = step.result
96  if exec_summary > 0:
97  self.log.info('At least one of the exec steps failed, using the largest code as ExecSummary')
98  else:
99  self.log.info('All exec steps succeeded')
100  art_result(exec_summary, 'ExecSummary')
101  elif len(self.exec_steps) == 1:
102  exec_summary = self.exec_steps[0].result
103 
104  # Run the check steps
105  self.run_steps(self.check_steps, commands['check_steps'], exec_status=exec_summary)
106 
107  # Dump all commands to JSON
108  with open('commands.json', 'w') as outfile:
109  json.dump(commands, outfile, indent=4)
110 
111  # Create the exit code and message from required steps
112  exit_code = 0
113  failed_required_steps = []
114  for step in self.exec_steps + self.check_steps:
115  if step.required and (step.result != 0):
116  self.log.debug('Required step %s finished with result %s', step.name, step.result)
117  failed_required_steps.append(step.name)
118  if abs(step.result) > exit_code:
119  exit_code = abs(step.result)
120  exit_msg = 'Test {:s} finished with code {:d}'.format(self.name, exit_code)
121  if exit_code == 0:
122  exit_msg += ' because all required steps were successful'
123  else:
124  exit_msg += ' because the following required steps failed: {:s}'.format(str(failed_required_steps))
125  self.log.info(exit_msg)
126  return exit_code
127 
128  def run_steps(self, steps, commands_list, exec_status=0):
129  previous_code = 0
130  for step in steps:
131  if (previous_code != 0 and step.depends_on_previous) or \
132  (exec_status != 0 and step.depends_on_exec):
133  self.log.error('Skipping step %s because previous step(s) failed', step.name)
134  step.result = 1
135  code, cmd = step.result, '# Skipped {} because of earlier failure'.format(step.name)
136  if step.auto_report_result:
137  step.report_result()
138  continue
139  code, cmd = step.run(self.dry_run)
140  previous_code = code
141  commands_list.append(cmd)
142 
143  def configure_timeout(self):
144  '''Set default timeout values for steps which don't have it set'''
145  for exec_step in self.exec_steps:
146  if exec_step.timeout is None:
147  # 12h for grid tests, 1h for build tests
148  exec_step.timeout = 12*3600 if self.art_type == 'grid' else 3600
149  for check_step in self.check_steps:
150  if check_step.timeout is None:
151  # 5 min for all check steps
152  check_step.timeout = 5*60
153 
154  def configure_name(self):
155  filename = os.path.basename(sys.argv[0])
156  self.log.debug('Parsing file name %s', filename)
157  prefix = 'test_'
158  suffix = '.py'
159  if not filename.startswith(prefix) or not filename.endswith(suffix):
160  self.configuration_error(
161  'Test file name {} does not match '.format(filename) +
162  'the required pattern {}*{}'.format(prefix, suffix))
163  for package_name, package_prefix in package_prefix_dict.items():
164  if filename.startswith(prefix+package_prefix):
165  self.package_name = package_name
166  this_package_prefix = package_prefix
167  if self.package_name is None:
168  self.configuration_error(
169  'Test file name {} could not be matched '.format(filename) +
170  'to any of the required package prefixes: {}'.format(
171  package_prefix_dict.values()))
172  if not self.art_type or self.art_type not in ['build', 'grid']:
173  self.configuration_error(
174  'Incorrect test art_type = {:s}, only "build" and "grid" are supported'.format(self.art_type))
175  if not filename.endswith('_' + self.art_type + suffix) and \
176  self.package_name != "TrigInDetValidation": # TIDV is exempt from this rule
177  self.configuration_error(
178  'Test file name does not match the art_type="{:s}". '.format(self.art_type) +
179  'Expected name {:s}'.format(prefix+this_package_prefix+'*_'+self.art_type+suffix))
180  max_len = 50
181  if len(filename) > max_len:
182  self.configuration_error(
183  'Test file name is too long. The limit is {} '.format(max_len) +
184  'characters, but it has {}'.format(len(filename)))
185  self.name = filename[len(prefix):-len(suffix)]
186 
188  d = {}
189  for step in self.exec_steps:
190  d.setdefault(step.name, 0)
191  d[step.name] += 1
192  for step in self.check_steps:
193  d.setdefault(step.name, 0)
194  d[step.name] += 1
195  duplicates = [name for name, count in d.items() if count > 1]
196  self.log.debug('all steps: %s', d)
197  self.log.debug('duplicates: %s', duplicates)
198  return duplicates
199 
200  def configuration_error(self, message):
201  self.log.error(message)
202  art_result(1, 'TestConfig')
203  sys.exit(1)
204 
205  def get_step(self, step_name):
206  step = get_step_from_list(step_name, self.exec_steps)
207  if step is None:
208  step = get_step_from_list(step_name, self.check_steps)
209  return step
210 
211  def get_step_by_type(self, step_type):
212  step = get_step_type_from_list(step_type, self.exec_steps)
213  if step is None:
214  step = get_step_type_from_list(step_type, self.check_steps)
215  return step
216 
217  def pre_exec(self):
218  '''Extra pre-exec function executed just before the steps'''
219  cmd_list = []
220  # Create empty POOL File Catalog to avoid incorrect grid failure handling
221  if self.art_type == 'grid':
222  cmd = 'which art.py >/dev/null 2>&1'
223  art_available = subprocess.call(cmd, shell=True)
224  if art_available == 0:
225  cmd = 'art.py createpoolfile'
226  self.log.debug('Executing pre-exec command %s', cmd)
227  subprocess.call(cmd, shell=True)
228  cmd_list.append(cmd)
229  return cmd_list
grepfile.info
info
Definition: grepfile.py:38
python.TrigValSteering.Test.Test.check_steps
check_steps
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:27
python.TrigValSteering.Test.Test.configure_name
def configure_name(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:154
vtune_athena.format
format
Definition: vtune_athena.py:14
python.TrigValSteering.Test.Test.configuration_error
def configuration_error(self, message)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:200
python.TrigValSteering.Step.get_step_from_list
def get_step_from_list(step_name, step_list)
Definition: Step.py:230
python.TrigValSteering.Common.clear_art_summary
def clear_art_summary()
Definition: Common.py:51
python.TrigValSteering.Test.Test.name
name
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:23
python.TrigValSteering.Test.Test.exec_steps
exec_steps
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:26
python.TrigValSteering.Step.get_step_type_from_list
def get_step_type_from_list(step_type, step_list)
Definition: Step.py:241
python.TrigValSteering.Test.Test.configured
configured
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:29
python.TrigValSteering.Test.Test.get_step_by_type
def get_step_by_type(self, step_type)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:211
python.TrigValSteering.Test.Test.log
log
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:22
python.TrigValSteering.Test.Test.__init__
def __init__(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:21
python.TrigValSteering.Test.Test.run
def run(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:67
python.TrigValSteering.Test.Test.package_name
package_name
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:24
python.TrigValSteering.Test.Test.configure_timeout
def configure_timeout(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:143
python.TrigValSteering.Test.Test.run_steps
def run_steps(self, steps, commands_list, exec_status=0)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:128
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
python.TrigValSteering.Test.Test.duplicate_step_names
def duplicate_step_names(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:187
python.TrigValSteering.Test.Test.configure
def configure(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:31
debug
const bool debug
Definition: MakeUncertaintyPlots.cxx:53
python.TrigValSteering.Test.Test.pre_exec
def pre_exec(self)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:217
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:79
python.TrigValSteering.Test.Test.dry_run
dry_run
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:28
Trk::open
@ open
Definition: BinningType.h:40
python.TrigValSteering.Test.Test
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:19
python.TrigValSteering.Common.get_logger
def get_logger()
Definition: Common.py:33
pickleTool.object
object
Definition: pickleTool.py:30
str
Definition: BTagTrackIpAccessor.cxx:11
python.TrigValSteering.Common.art_result
def art_result(result, name)
Definition: Common.py:42
error
Definition: IImpactPoint3dEstimator.h:70
python.TrigValSteering.Test.Test.art_type
art_type
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:25
python.TrigValSteering.Test.Test.get_step
def get_step(self, step_name)
Definition: Trigger/TrigValidation/TrigValTools/python/TrigValSteering/Test.py:205