The Gaudi Framework  v36r2 (27905625)
BaseTest.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 
12 
13 import os
14 import sys
15 import time
16 import signal
17 import threading
18 import platform
19 import tempfile
20 import inspect
21 import re
22 import logging
23 
24 from subprocess import Popen, PIPE, STDOUT
25 
26 try:
27  from html import escape as escape_for_html
28 except ImportError: # Python2
29  from cgi import escape as escape_for_html
30 
31 import six
32 
33 if sys.version_info < (3, 5):
34  # backport of 'backslashreplace' handling of UnicodeDecodeError
35  # to Python < 3.5
36  from codecs import register_error, backslashreplace_errors
37 
39  if isinstance(exc, UnicodeDecodeError):
40  code = hex(ord(exc.object[exc.start]))
41  return (u'\\' + code[1:], exc.start + 1)
42  else:
43  return backslashreplace_errors(exc)
44 
45  register_error('backslashreplace', _new_backslashreplace_errors)
46  del register_error
47  del backslashreplace_errors
48  del _new_backslashreplace_errors
49 
50 SKIP_RETURN_CODE = 77
51 
52 
53 def sanitize_for_xml(data):
54  '''
55  Take a string with invalid ASCII/UTF characters and quote them so that the
56  string can be used in an XML text.
57 
58  >>> sanitize_for_xml('this is \x1b')
59  'this is [NON-XML-CHAR-0x1B]'
60  '''
61  bad_chars = re.compile(
62  u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')
63 
64  def quote(match):
65  'helper function'
66  return ''.join('[NON-XML-CHAR-0x%2X]' % ord(c) for c in match.group())
67 
68  return bad_chars.sub(quote, data)
69 
70 
71 def dumpProcs(name):
72  '''helper to debug GAUDI-1084, dump the list of processes'''
73  from getpass import getuser
74  if 'WORKSPACE' in os.environ:
75  p = Popen(['ps', '-fH', '-U', getuser()], stdout=PIPE)
76  with open(os.path.join(os.environ['WORKSPACE'], name), 'wb') as f:
77  f.write(p.communicate()[0])
78 
79 
80 def kill_tree(ppid, sig):
81  '''
82  Send a signal to a process and all its child processes (starting from the
83  leaves).
84  '''
85  log = logging.getLogger('kill_tree')
86  ps_cmd = ['ps', '--no-headers', '-o', 'pid', '--ppid', str(ppid)]
87  get_children = Popen(ps_cmd, stdout=PIPE, stderr=PIPE)
88  children = map(int, get_children.communicate()[0].split())
89  for child in children:
90  kill_tree(child, sig)
91  try:
92  log.debug('killing process %d', ppid)
93  os.kill(ppid, sig)
94  except OSError as err:
95  if err.errno != 3: # No such process
96  raise
97  log.debug('no such process %d', ppid)
98 
99 
100 # -------------------------------------------------------------------------#
101 
102 
103 class BaseTest(object):
104 
105  _common_tmpdir = None
106 
107  def __init__(self):
108  self.program = ''
109  self.args = []
110  self.reference = ''
111  self.error_reference = ''
112  self.options = ''
113  self.stderr = ''
114  self.timeout = 600
115  self.exit_code = None
116  self.environment = dict(os.environ)
118  self.signal = None
119  self.workdir = os.curdir
120  self.use_temp_dir = False
121  # Variables not for users
122  self.status = None
123  self.name = ''
124  self.causes = []
125  self.result = Result(self)
126  self.returnedCode = 0
127  self.out = ''
128  self.err = ''
129  self.proc = None
130  self.stack_trace = None
131  self.basedir = os.getcwd()
132 
133  def run(self):
134  logging.debug('running test %s', self.name)
135 
136  self.result = Result({
137  'CAUSE': None,
138  'EXCEPTION': None,
139  'RESOURCE': None,
140  'TARGET': None,
141  'TRACEBACK': None,
142  'START_TIME': None,
143  'END_TIME': None,
144  'TIMEOUT_DETAIL': None
145  })
146 
147  if self.options:
148  if re.search(
149  r'from\s+Gaudi.Configuration\s+import\s+\*|'
150  'from\s+Configurables\s+import', self.options):
151  suffix, lang = '.py', 'python'
152  else:
153  suffix, lang = '.opts', 'c++'
154  self.result[
155  "Options"] = '<code lang="{}"><pre>{}</pre></code>'.format(
156  lang, escape_for_html(self.options))
157  optionFile = tempfile.NamedTemporaryFile(suffix=suffix)
158  optionFile.file.write(self.options.encode('utf-8'))
159  optionFile.seek(0)
160  self.args.append(RationalizePath(optionFile.name))
161 
162  platform_id = (self.environment.get('BINARY_TAG')
163  or self.environment.get('CMTCONFIG')
164  or platform.platform())
165  # If at least one regex matches we skip the test.
166  skip_test = bool([
167  None for prex in self.unsupported_platforms
168  if re.search(prex, platform_id)
169  ])
170 
171  if not skip_test:
172  # handle working/temporary directory options
173  workdir = self.workdir
174  if self.use_temp_dir:
175  if self._common_tmpdir:
176  workdir = self._common_tmpdir
177  else:
178  workdir = tempfile.mkdtemp()
179 
180  # prepare the command to execute
181  prog = ''
182  if self.program != '':
183  prog = self.program
184  elif "GAUDIEXE" in self.environment:
185  prog = self.environment["GAUDIEXE"]
186  else:
187  prog = "Gaudi.exe"
188 
189  prog_ext = os.path.splitext(prog)[1]
190  if prog_ext not in [".exe", ".py", ".bat"]:
191  prog += ".exe"
192  prog_ext = ".exe"
193 
194  prog = which(prog) or prog
195 
196  args = list(map(RationalizePath, self.args))
197 
198  if prog_ext == ".py":
199  params = ['python', RationalizePath(prog)] + args
200  else:
201  params = [RationalizePath(prog)] + args
202 
203  # we need to switch directory because the validator expects to run
204  # in the same dir as the program
205  os.chdir(workdir)
206 
207  # launching test in a different thread to handle timeout exception
208  def target():
209  logging.debug('executing %r in %s', params, workdir)
210  self.proc = Popen(
211  params, stdout=PIPE, stderr=PIPE, env=self.environment)
212  logging.debug('(pid: %d)', self.proc.pid)
213  out, err = self.proc.communicate()
214  self.out = out.decode('utf-8', errors='backslashreplace')
215  self.err = err.decode('utf-8', errors='backslashreplace')
216 
217  thread = threading.Thread(target=target)
218  thread.start()
219  # catching timeout
220  thread.join(self.timeout)
221 
222  if thread.is_alive():
223  logging.debug('time out in test %s (pid %d)', self.name,
224  self.proc.pid)
225  # get the stack trace of the stuck process
226  cmd = [
227  'gdb', '--pid',
228  str(self.proc.pid), '--batch',
229  '--eval-command=thread apply all backtrace'
230  ]
231  gdb = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
232  self.stack_trace = gdb.communicate()[0].decode(
233  'utf-8', errors='backslashreplace')
234 
235  kill_tree(self.proc.pid, signal.SIGTERM)
236  thread.join(60)
237  if thread.is_alive():
238  kill_tree(self.proc.pid, signal.SIGKILL)
239  self.causes.append('timeout')
240  else:
241  self.returnedCode = self.proc.returncode
242  if self.returnedCode != SKIP_RETURN_CODE:
243  logging.debug(
244  f'completed test {self.name} with returncode = {self.returnedCode}'
245  )
246  logging.debug('validating test...')
247  self.result, self.causes = self.ValidateOutput(
248  stdout=self.out, stderr=self.err, result=self.result)
249  else:
250  logging.debug(f'skipped test {self.name}')
251  self.status = "skipped"
252 
253  # remove the temporary directory if we created it
254  if self.use_temp_dir and not self._common_tmpdir:
255  shutil.rmtree(workdir, True)
256 
257  os.chdir(self.basedir)
258 
259  if self.status != "skipped":
260  # handle application exit code
261  if self.signal is not None:
262  if int(self.returnedCode) != -int(self.signal):
263  self.causes.append('exit code')
264 
265  elif self.exit_code is not None:
266  if int(self.returnedCode) != int(self.exit_code):
267  self.causes.append('exit code')
268 
269  elif self.returnedCode != 0:
270  self.causes.append("exit code")
271 
272  if self.causes:
273  self.status = "failed"
274  else:
275  self.status = "passed"
276 
277  else:
278  self.status = "skipped"
279 
280  logging.debug('%s: %s', self.name, self.status)
281  field_mapping = {
282  'Exit Code': 'returnedCode',
283  'stderr': 'err',
284  'Arguments': 'args',
285  'Runtime Environment': 'environment',
286  'Status': 'status',
287  'stdout': 'out',
288  'Program Name': 'program',
289  'Name': 'name',
290  'Validator': 'validator',
291  'Output Reference File': 'reference',
292  'Error Reference File': 'error_reference',
293  'Causes': 'causes',
294  # 'Validator Result': 'result.annotations',
295  'Unsupported Platforms': 'unsupported_platforms',
296  'Stack Trace': 'stack_trace'
297  }
298  resultDict = [(key, getattr(self, attr))
299  for key, attr in field_mapping.items()
300  if getattr(self, attr)]
301  resultDict.append(('Working Directory',
303  os.path.join(os.getcwd(), self.workdir))))
304  # print(dict(resultDict).keys())
305  resultDict.extend(self.result.annotations.items())
306  # print(self.result.annotations.keys())
307  resultDict = dict(resultDict)
308 
309  # Special cases
310  if "Validator" in resultDict:
311  resultDict[
312  "Validator"] = '<code lang="{}"><pre>{}</pre></code>'.format(
313  "python", escape_for_html(resultDict["Validator"]))
314  return resultDict
315 
316  # -------------------------------------------------#
317  # ----------------Validating tool------------------#
318  # -------------------------------------------------#
319 
320  def ValidateOutput(self, stdout, stderr, result):
321  if not self.stderr:
322  self.validateWithReference(stdout, stderr, result, self.causes)
323  elif stderr.strip() != self.stderr.strip():
324  self.causes.append('standard error')
325  return result, self.causes
326 
328  reference=None,
329  stdout=None,
330  result=None,
331  causes=None,
332  signature_offset=0,
333  signature=None,
334  id=None):
335  """
336  Given a block of text, tries to find it in the output. The block had to be identified by a signature line. By default, the first line is used as signature, or the line pointed to by signature_offset. If signature_offset points outside the block, a signature line can be passed as signature argument. Note: if 'signature' is None (the default), a negative signature_offset is interpreted as index in a list (e.g. -1 means the last line), otherwise the it is interpreted as the number of lines before the first one of the block the signature must appear. The parameter 'id' allow to distinguish between different calls to this function in the same validation code.
337  """
338 
339  if reference is None:
340  reference = self.reference
341  if stdout is None:
342  stdout = self.out
343  if result is None:
344  result = self.result
345  if causes is None:
346  causes = self.causes
347 
348  reflines = list(
349  filter(None, map(lambda s: s.rstrip(), reference.splitlines())))
350  if not reflines:
351  raise RuntimeError("Empty (or null) reference")
352  # the same on standard output
353  outlines = list(
354  filter(None, map(lambda s: s.rstrip(), stdout.splitlines())))
355 
356  res_field = "GaudiTest.RefBlock"
357  if id:
358  res_field += "_%s" % id
359 
360  if signature is None:
361  if signature_offset < 0:
362  signature_offset = len(reference) + signature_offset
363  signature = reflines[signature_offset]
364  # find the reference block in the output file
365  try:
366  pos = outlines.index(signature)
367  outlines = outlines[pos - signature_offset:pos + len(reflines) -
368  signature_offset]
369  if reflines != outlines:
370  msg = "standard output"
371  # I do not want 2 messages in causes if the function is called
372  # twice
373  if not msg in causes:
374  causes.append(msg)
375  result[res_field + ".observed"] = result.Quote(
376  "\n".join(outlines))
377  except ValueError:
378  causes.append("missing signature")
379  result[res_field + ".signature"] = result.Quote(signature)
380  if len(reflines) > 1 or signature != reflines[0]:
381  result[res_field + ".expected"] = result.Quote("\n".join(reflines))
382  return causes
383 
384  def countErrorLines(self,
385  expected={
386  'ERROR': 0,
387  'FATAL': 0
388  },
389  stdout=None,
390  result=None,
391  causes=None):
392  """
393  Count the number of messages with required severity (by default ERROR and FATAL)
394  and check if their numbers match the expected ones (0 by default).
395  The dictionary "expected" can be used to tune the number of errors and fatals
396  allowed, or to limit the number of expected warnings etc.
397  """
398 
399  if stdout is None:
400  stdout = self.out
401  if result is None:
402  result = self.result
403  if causes is None:
404  causes = self.causes
405 
406  # prepare the dictionary to record the extracted lines
407  errors = {}
408  for sev in expected:
409  errors[sev] = []
410 
411  outlines = stdout.splitlines()
412  from math import log10
413  fmt = "%%%dd - %%s" % (int(log10(len(outlines) + 1)))
414 
415  linecount = 0
416  for l in outlines:
417  linecount += 1
418  words = l.split()
419  if len(words) >= 2 and words[1] in errors:
420  errors[words[1]].append(fmt % (linecount, l.rstrip()))
421 
422  for e in errors:
423  if len(errors[e]) != expected[e]:
424  causes.append('%s(%d)' % (e, len(errors[e])))
425  result["GaudiTest.lines.%s" % e] = result.Quote('\n'.join(
426  errors[e]))
427  result["GaudiTest.lines.%s.expected#" % e] = result.Quote(
428  str(expected[e]))
429 
430  return causes
431 
433  stdout=None,
434  result=None,
435  causes=None,
436  trees_dict=None,
437  ignore=r"Basket|.*size|Compression"):
438  """
439  Compare the TTree summaries in stdout with the ones in trees_dict or in
440  the reference file. By default ignore the size, compression and basket
441  fields.
442  The presence of TTree summaries when none is expected is not a failure.
443  """
444  if stdout is None:
445  stdout = self.out
446  if result is None:
447  result = self.result
448  if causes is None:
449  causes = self.causes
450  if trees_dict is None:
451  lreference = self._expandReferenceFileName(self.reference)
452  # call the validator if the file exists
453  if lreference and os.path.isfile(lreference):
454  trees_dict = findTTreeSummaries(open(lreference).read())
455  else:
456  trees_dict = {}
457 
458  from pprint import PrettyPrinter
459  pp = PrettyPrinter()
460  if trees_dict:
461  result["GaudiTest.TTrees.expected"] = result.Quote(
462  pp.pformat(trees_dict))
463  if ignore:
464  result["GaudiTest.TTrees.ignore"] = result.Quote(ignore)
465 
466  trees = findTTreeSummaries(stdout)
467  failed = cmpTreesDicts(trees_dict, trees, ignore)
468  if failed:
469  causes.append("trees summaries")
470  msg = "%s: %s != %s" % getCmpFailingValues(trees_dict, trees,
471  failed)
472  result["GaudiTest.TTrees.failure_on"] = result.Quote(msg)
473  result["GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
474 
475  return causes
476 
478  stdout=None,
479  result=None,
480  causes=None,
481  dict=None,
482  ignore=None):
483  """
484  Compare the TTree summaries in stdout with the ones in trees_dict or in
485  the reference file. By default ignore the size, compression and basket
486  fields.
487  The presence of TTree summaries when none is expected is not a failure.
488  """
489  if stdout is None:
490  stdout = self.out
491  if result is None:
492  result = self.result
493  if causes is None:
494  causes = self.causes
495 
496  if dict is None:
497  lreference = self._expandReferenceFileName(self.reference)
498  # call the validator if the file exists
499  if lreference and os.path.isfile(lreference):
500  dict = findHistosSummaries(open(lreference).read())
501  else:
502  dict = {}
503 
504  from pprint import PrettyPrinter
505  pp = PrettyPrinter()
506  if dict:
507  result["GaudiTest.Histos.expected"] = result.Quote(
508  pp.pformat(dict))
509  if ignore:
510  result["GaudiTest.Histos.ignore"] = result.Quote(ignore)
511 
512  histos = findHistosSummaries(stdout)
513  failed = cmpTreesDicts(dict, histos, ignore)
514  if failed:
515  causes.append("histos summaries")
516  msg = "%s: %s != %s" % getCmpFailingValues(dict, histos, failed)
517  result["GaudiTest.Histos.failure_on"] = result.Quote(msg)
518  result["GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
519 
520  return causes
521 
523  stdout=None,
524  stderr=None,
525  result=None,
526  causes=None,
527  preproc=None):
528  '''
529  Default validation acti*on: compare standard output and error to the
530  reference files.
531  '''
532 
533  if stdout is None:
534  stdout = self.out
535  if stderr is None:
536  stderr = self.err
537  if result is None:
538  result = self.result
539  if causes is None:
540  causes = self.causes
541 
542  # set the default output preprocessor
543  if preproc is None:
544  preproc = normalizeExamples
545  # check standard output
546  lreference = self._expandReferenceFileName(self.reference)
547  # call the validator if the file exists
548  if lreference and os.path.isfile(lreference):
549  causes += ReferenceFileValidator(
550  lreference, "standard output", "Output Diff",
551  preproc=preproc)(stdout, result)
552  elif lreference:
553  causes += ["missing reference file"]
554  # Compare TTree summaries
555  causes = self.CheckTTreesSummaries(stdout, result, causes)
556  causes = self.CheckHistosSummaries(stdout, result, causes)
557  if causes and lreference: # Write a new reference file for stdout
558  try:
559  cnt = 0
560  newrefname = '.'.join([lreference, 'new'])
561  while os.path.exists(newrefname):
562  cnt += 1
563  newrefname = '.'.join([lreference, '~%d~' % cnt, 'new'])
564  newref = open(newrefname, "w")
565  # sanitize newlines
566  for l in stdout.splitlines():
567  newref.write(l.rstrip() + '\n')
568  del newref # flush and close
569  result['New Output Reference File'] = os.path.relpath(
570  newrefname, self.basedir)
571  except IOError:
572  # Ignore IO errors when trying to update reference files
573  # because we may be in a read-only filesystem
574  pass
575 
576  # check standard error
577  lreference = self._expandReferenceFileName(self.error_reference)
578  # call the validator if we have a file to use
579  if lreference:
580  if os.path.isfile(lreference):
581  newcauses = ReferenceFileValidator(
582  lreference,
583  "standard error",
584  "Error Diff",
585  preproc=preproc)(stderr, result)
586  else:
587  newcauses = ["missing error reference file"]
588  causes += newcauses
589  if newcauses and lreference: # Write a new reference file for stdedd
590  cnt = 0
591  newrefname = '.'.join([lreference, 'new'])
592  while os.path.exists(newrefname):
593  cnt += 1
594  newrefname = '.'.join([lreference, '~%d~' % cnt, 'new'])
595  newref = open(newrefname, "w")
596  # sanitize newlines
597  for l in stderr.splitlines():
598  newref.write(l.rstrip() + '\n')
599  del newref # flush and close
600  result['New Error Reference File'] = os.path.relpath(
601  newrefname, self.basedir)
602  else:
603  causes += BasicOutputValidator(lreference, "standard error",
604  "ExecTest.expected_stderr")(stderr,
605  result)
606  return causes
607 
608  def _expandReferenceFileName(self, reffile):
609  # if no file is passed, do nothing
610  if not reffile:
611  return ""
612 
613  # function to split an extension in constituents parts
614  def platformSplit(p):
615  import re
616  delim = re.compile('-' in p and r"[-+]" or r"_")
617  return set(delim.split(p))
618 
619  reference = os.path.normpath(
620  os.path.join(self.basedir, os.path.expandvars(reffile)))
621 
622  # old-style platform-specific reference name
623  spec_ref = reference[:-3] + GetPlatform(self)[0:3] + reference[-3:]
624  if os.path.isfile(spec_ref):
625  reference = spec_ref
626  else: # look for new-style platform specific reference files:
627  # get all the files whose name start with the reference filename
628  dirname, basename = os.path.split(reference)
629  if not dirname:
630  dirname = '.'
631  head = basename + "."
632  head_len = len(head)
633  platform = platformSplit(GetPlatform(self))
634  if 'do0' in platform:
635  platform.add('dbg')
636  candidates = []
637  for f in os.listdir(dirname):
638  if f.startswith(head):
639  req_plat = platformSplit(f[head_len:])
640  if platform.issuperset(req_plat):
641  candidates.append((len(req_plat), f))
642  if candidates: # take the one with highest matching
643  # FIXME: it is not possible to say if x86_64-slc5-gcc43-dbg
644  # has to use ref.x86_64-gcc43 or ref.slc5-dbg
645  candidates.sort()
646  reference = os.path.join(dirname, candidates[-1][1])
647  return reference
648 
649 
650 # ======= GAUDI TOOLS =======
651 
652 import shutil
653 import string
654 import difflib
655 import calendar
656 
657 try:
658  from GaudiKernel import ROOT6WorkAroundEnabled
659 except ImportError:
660 
662  # dummy implementation
663  return False
664 
665 
666 # --------------------------------- TOOLS ---------------------------------#
667 
668 
670  """
671  Function used to normalize the used path
672  """
673  newPath = os.path.normpath(os.path.expandvars(p))
674  if os.path.exists(newPath):
675  p = os.path.realpath(newPath)
676  return p
677 
678 
679 def which(executable):
680  """
681  Locates an executable in the executables path ($PATH) and returns the full
682  path to it. An application is looked for with or without the '.exe' suffix.
683  If the executable cannot be found, None is returned
684  """
685  if os.path.isabs(executable):
686  if not os.path.isfile(executable):
687  if executable.endswith('.exe'):
688  if os.path.isfile(executable[:-4]):
689  return executable[:-4]
690  else:
691  executable = os.path.split(executable)[1]
692  else:
693  return executable
694  for d in os.environ.get("PATH").split(os.pathsep):
695  fullpath = os.path.join(d, executable)
696  if os.path.isfile(fullpath):
697  return fullpath
698  elif executable.endswith('.exe') and os.path.isfile(fullpath[:-4]):
699  return fullpath[:-4]
700  return None
701 
702 
703 # -------------------------------------------------------------------------#
704 # ----------------------------- Result Classe -----------------------------#
705 # -------------------------------------------------------------------------#
706 import types
707 
708 
709 class Result:
710 
711  PASS = 'PASS'
712  FAIL = 'FAIL'
713  ERROR = 'ERROR'
714  UNTESTED = 'UNTESTED'
715 
716  EXCEPTION = ""
717  RESOURCE = ""
718  TARGET = ""
719  TRACEBACK = ""
720  START_TIME = ""
721  END_TIME = ""
722  TIMEOUT_DETAIL = ""
723 
724  def __init__(self, kind=None, id=None, outcome=PASS, annotations={}):
725  self.annotations = annotations.copy()
726 
727  def __getitem__(self, key):
728  assert isinstance(key, six.string_types)
729  return self.annotations[key]
730 
731  def __setitem__(self, key, value):
732  assert isinstance(key, six.string_types)
733  assert isinstance(
734  value, six.string_types), '{!r} is not a string'.format(value)
735  self.annotations[key] = value
736 
737  def Quote(self, text):
738  """
739  Convert text to html by escaping special chars and adding <pre> tags.
740  """
741  return "<pre>{}</pre>".format(escape_for_html(text))
742 
743 
744 # -------------------------------------------------------------------------#
745 # --------------------------- Validator Classes ---------------------------#
746 # -------------------------------------------------------------------------#
747 
748 # Basic implementation of an option validator for Gaudi test. This
749 # implementation is based on the standard (LCG) validation functions used
750 # in QMTest.
751 
752 
754  def __init__(self, ref, cause, result_key):
755  self.ref = ref
756  self.cause = cause
757  self.result_key = result_key
758 
759  def __call__(self, out, result):
760  """Validate the output of the program.
761  'stdout' -- A string containing the data written to the standard output
762  stream.
763  'stderr' -- A string containing the data written to the standard error
764  stream.
765  'result' -- A 'Result' object. It may be used to annotate
766  the outcome according to the content of stderr.
767  returns -- A list of strings giving causes of failure."""
768 
769  causes = []
770  # Check the output
771  if not self.__CompareText(out, self.ref):
772  causes.append(self.cause)
773  result[self.result_key] = result.Quote(self.ref)
774 
775  return causes
776 
777  def __CompareText(self, s1, s2):
778  """Compare 's1' and 's2', ignoring line endings.
779  's1' -- A string.
780  's2' -- A string.
781  returns -- True if 's1' and 's2' are the same, ignoring
782  differences in line endings."""
783  if ROOT6WorkAroundEnabled('ReadRootmapCheck'):
784  # FIXME: (MCl) Hide warnings from new rootmap sanity check until we
785  # can fix them
786  to_ignore = re.compile(
787  r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*'
788  )
789 
790  def keep_line(l):
791  return not to_ignore.match(l)
792 
793  return list(filter(keep_line, s1.splitlines())) == list(
794  filter(keep_line, s2.splitlines()))
795  else:
796  return s1.splitlines() == s2.splitlines()
797 
798 
799 # ------------------------ Preprocessor elements ------------------------#
801  """ Base class for a callable that takes a file and returns a modified
802  version of it."""
803 
804  def __processLine__(self, line):
805  return line
806 
807  def __processFile__(self, lines):
808  output = []
809  for l in lines:
810  l = self.__processLine__(l)
811  if l:
812  output.append(l)
813  return output
814 
815  def __call__(self, input):
816  if not isinstance(input, six.string_types):
817  lines = input
818  mergeback = False
819  else:
820  lines = input.splitlines()
821  mergeback = True
822  output = self.__processFile__(lines)
823  if mergeback:
824  output = '\n'.join(output)
825  return output
826 
827  def __add__(self, rhs):
828  return FilePreprocessorSequence([self, rhs])
829 
830 
832  def __init__(self, members=[]):
833  self.members = members
834 
835  def __add__(self, rhs):
836  return FilePreprocessorSequence(self.members + [rhs])
837 
838  def __call__(self, input):
839  output = input
840  for pp in self.members:
841  output = pp(output)
842  return output
843 
844 
846  def __init__(self, strings=[], regexps=[]):
847  import re
848  self.strings = strings
849  self.regexps = list(map(re.compile, regexps))
850 
851  def __processLine__(self, line):
852  for s in self.strings:
853  if line.find(s) >= 0:
854  return None
855  for r in self.regexps:
856  if r.search(line):
857  return None
858  return line
859 
860 
862  def __init__(self, start, end):
863  self.start = start
864  self.end = end
865  self._skipping = False
866 
867  def __processLine__(self, line):
868  if self.start in line:
869  self._skipping = True
870  return None
871  elif self.end in line:
872  self._skipping = False
873  elif self._skipping:
874  return None
875  return line
876 
877 
879  def __init__(self, orig, repl="", when=None):
880  if when:
881  when = re.compile(when)
882  self._operations = [(when, re.compile(orig), repl)]
883 
884  def __add__(self, rhs):
885  if isinstance(rhs, RegexpReplacer):
886  res = RegexpReplacer("", "", None)
887  res._operations = self._operations + rhs._operations
888  else:
889  res = FilePreprocessor.__add__(self, rhs)
890  return res
891 
892  def __processLine__(self, line):
893  for w, o, r in self._operations:
894  if w is None or w.search(line):
895  line = o.sub(r, line)
896  return line
897 
898 
899 # Common preprocessors
900 maskPointers = RegexpReplacer("0x[0-9a-fA-F]{4,16}", "0x########")
901 normalizeDate = RegexpReplacer(
902  "[0-2]?[0-9]:[0-5][0-9]:[0-5][0-9] [0-9]{4}[-/][01][0-9][-/][0-3][0-9][ A-Z]*",
903  "00:00:00 1970-01-01")
904 normalizeEOL = FilePreprocessor()
905 normalizeEOL.__processLine__ = lambda line: str(line).rstrip() + '\n'
906 
907 skipEmptyLines = FilePreprocessor()
908 # FIXME: that's ugly
909 skipEmptyLines.__processLine__ = lambda line: (line.strip() and line) or None
910 
911 # Special preprocessor sorting the list of strings (whitespace separated)
912 # that follow a signature on a single line
913 
914 
916  def __init__(self, signature):
917  self.signature = signature
918  self.siglen = len(signature)
919 
920  def __processLine__(self, line):
921  pos = line.find(self.signature)
922  if pos >= 0:
923  line = line[:(pos + self.siglen)]
924  lst = line[(pos + self.siglen):].split()
925  lst.sort()
926  line += " ".join(lst)
927  return line
928 
929 
931  '''
932  Sort group of lines matching a regular expression
933  '''
934 
935  def __init__(self, exp):
936  self.exp = exp if hasattr(exp, 'match') else re.compile(exp)
937 
938  def __processFile__(self, lines):
939  match = self.exp.match
940  output = []
941  group = []
942  for l in lines:
943  if match(l):
944  group.append(l)
945  else:
946  if group:
947  group.sort()
948  output.extend(group)
949  group = []
950  output.append(l)
951  return output
952 
953 
954 # Preprocessors for GaudiExamples
955 normalizeExamples = maskPointers + normalizeDate
956 for w, o, r in [
957  # ("TIMER.TIMER",r"[0-9]", "0"), # Normalize time output
958  ("TIMER.TIMER", r"\s+[+-]?[0-9]+[0-9.]*", " 0"), # Normalize time output
959  ("release all pending", r"^.*/([^/]*:.*)", r"\1"),
960  ("^#.*file", r"file '.*[/\\]([^/\\]*)$", r"file '\1"),
961  ("^JobOptionsSvc.*options successfully read in from",
962  r"read in from .*[/\\]([^/\\]*)$",
963  r"file \1"), # normalize path to options
964  # Normalize UUID, except those ending with all 0s (i.e. the class IDs)
965  (None,
966  r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}(?!-0{12})-[0-9A-Fa-f]{12}",
967  "00000000-0000-0000-0000-000000000000"),
968  # Absorb a change in ServiceLocatorHelper
969  ("ServiceLocatorHelper::", "ServiceLocatorHelper::(create|locate)Service",
970  "ServiceLocatorHelper::service"),
971  # Remove the leading 0 in Windows' exponential format
972  (None, r"e([-+])0([0-9][0-9])", r"e\1\2"),
973  # Output line changed in Gaudi v24
974  (None, r'Service reference count check:',
975  r'Looping over all active services...'),
976  # Ignore count of declared properties (anyway they are all printed)
977  (None,
978  r"^(.*(DEBUG|SUCCESS) List of ALL properties of .*#properties = )\d+",
979  r"\1NN"),
980  ('ApplicationMgr', r'(declareMultiSvcType|addMultiSvc): ', ''),
981  (r"Property \['Name': Value\]", r"( = '[^']+':)'(.*)'", r'\1\2'),
982  ('TimelineSvc', "to file 'TimelineFile':", "to file "),
983  ('DataObjectHandleBase', r'DataObjectHandleBase\‍("([^"]*)"\‍)', r"'\1'"),
984 ]: # [ ("TIMER.TIMER","[0-9]+[0-9.]*", "") ]
985  normalizeExamples += RegexpReplacer(o, r, w)
986 
987 lineSkipper = LineSkipper(
988  [
989  "//GP:",
990  "JobOptionsSvc INFO # ",
991  "JobOptionsSvc WARNING # ",
992  "Time User",
993  "Welcome to",
994  "This machine has a speed",
995  "TIME:",
996  "running on",
997  "ToolSvc.Sequenc... INFO",
998  "DataListenerSvc INFO XML written to file:",
999  "[INFO]",
1000  "[WARNING]",
1001  "DEBUG No writable file catalog found which contains FID:",
1002  "DEBUG Service base class initialized successfully",
1003  # changed between v20 and v21
1004  "DEBUG Incident timing:",
1005  # introduced with patch #3487
1006  # changed the level of the message from INFO to
1007  # DEBUG
1008  "INFO 'CnvServices':[",
1009  # message removed because could be printed in constructor
1010  "DEBUG 'CnvServices':[",
1011  # The signal handler complains about SIGXCPU not
1012  # defined on some platforms
1013  'SIGXCPU',
1014  # Message removed with redesing of JobOptionsSvc
1015  'ServiceLocatorHelper::service: found service JobOptionsSvc',
1016  # Ignore warnings for properties case mismatch
1017  'mismatching case for property name:',
1018  # Message demoted to DEBUG in gaudi/Gaudi!992
1019  'Histograms saving not required.',
1020  # Message added in gaudi/Gaudi!577
1021  'Properties are dumped into',
1022  ],
1023  regexps=[
1024  r"^JobOptionsSvc INFO *$",
1025  r"^# ", # Ignore python comments
1026  # skip the message reporting the version of the root file
1027  r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
1028  r"File '.*.xml' does not exist",
1029  r"INFO Refer to dataset .* by its file ID:",
1030  r"INFO Referring to dataset .* by its file ID:",
1031  r"INFO Disconnect from dataset",
1032  r"INFO Disconnected from dataset",
1033  r"INFO Disconnected data IO:",
1034  r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
1035  # Ignore StatusCodeSvc related messages
1036  r".*StatusCodeSvc.*",
1037  r"Num\s*\|\s*Function\s*\|\s*Source Library",
1038  r"^[-+]*\s*$",
1039  # Hide the fake error message coming from POOL/ROOT (ROOT 5.21)
1040  r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
1041  # Hide unchecked StatusCodes from dictionaries
1042  r"^ +[0-9]+ \|.*ROOT",
1043  r"^ +[0-9]+ \|.*\|.*Dict",
1044  # Hide EventLoopMgr total timing report
1045  r"EventLoopMgr.*---> Loop Finished",
1046  r"HiveSlimEventLo.*---> Loop Finished",
1047  # Remove ROOT TTree summary table, which changes from one version to the
1048  # other
1049  r"^\*.*\*$",
1050  # Remove Histos Summaries
1051  r"SUCCESS\s*Booked \d+ Histogram\‍(s\‍)",
1052  r"^ \|",
1053  r"^ ID=",
1054  # Ignore added/removed properties
1055  r"Property(.*)'Audit(Algorithm|Tool|Service)s':",
1056  r"Property(.*)'Audit(Begin|End)Run':",
1057  # these were missing in tools
1058  r"Property(.*)'AuditRe(start|initialize)':",
1059  r"Property(.*)'Blocking':",
1060  # removed with gaudi/Gaudi!273
1061  r"Property(.*)'ErrorCount(er)?':",
1062  # added with gaudi/Gaudi!306
1063  r"Property(.*)'Sequential':",
1064  # added with gaudi/Gaudi!314
1065  r"Property(.*)'FilterCircularDependencies':",
1066  # removed with gaudi/Gaudi!316
1067  r"Property(.*)'IsClonable':",
1068  # ignore uninteresting/obsolete messages
1069  r"Property update for OutputLevel : new value =",
1070  r"EventLoopMgr\s*DEBUG Creating OutputStream",
1071  ])
1072 
1073 if ROOT6WorkAroundEnabled('ReadRootmapCheck'):
1074  # FIXME: (MCl) Hide warnings from new rootmap sanity check until we can
1075  # fix them
1076  lineSkipper += LineSkipper(regexps=[
1077  r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
1078  ])
1079 
1080 normalizeExamples = (
1081  lineSkipper + normalizeExamples + skipEmptyLines + normalizeEOL +
1082  LineSorter("Services to release : ") +
1083  SortGroupOfLines(r'^\S+\s+(DEBUG|SUCCESS) Property \[\'Name\':'))
1084 
1085 # --------------------- Validation functions/classes ---------------------#
1086 
1087 
1089  def __init__(self, reffile, cause, result_key, preproc=normalizeExamples):
1090  self.reffile = os.path.expandvars(reffile)
1091  self.cause = cause
1092  self.result_key = result_key
1093  self.preproc = preproc
1094 
1095  def __call__(self, stdout, result):
1096  causes = []
1097  if os.path.isfile(self.reffile):
1098  orig = open(self.reffile).readlines()
1099  if self.preproc:
1100  orig = self.preproc(orig)
1101  result[self.result_key + '.preproc.orig'] = \
1102  result.Quote('\n'.join(map(str.strip, orig)))
1103  else:
1104  orig = []
1105  new = stdout.splitlines()
1106  if self.preproc:
1107  new = self.preproc(new)
1108 
1109  diffs = difflib.ndiff(orig, new, charjunk=difflib.IS_CHARACTER_JUNK)
1110  filterdiffs = list(
1111  map(lambda x: x.strip(), filter(lambda x: x[0] != " ", diffs)))
1112  if filterdiffs:
1113  result[self.result_key] = result.Quote("\n".join(filterdiffs))
1114  result[self.result_key] += result.Quote("""
1115  Legend:
1116  -) reference file
1117  +) standard output of the test""")
1118  result[self.result_key + '.preproc.new'] = \
1119  result.Quote('\n'.join(map(str.strip, new)))
1120  causes.append(self.cause)
1121  return causes
1122 
1123 
1125  """
1126  Scan stdout to find ROOT TTree summaries and digest them.
1127  """
1128  stars = re.compile(r"^\*+$")
1129  outlines = stdout.splitlines()
1130  nlines = len(outlines)
1131  trees = {}
1132 
1133  i = 0
1134  while i < nlines: # loop over the output
1135  # look for
1136  while i < nlines and not stars.match(outlines[i]):
1137  i += 1
1138  if i < nlines:
1139  tree, i = _parseTTreeSummary(outlines, i)
1140  if tree:
1141  trees[tree["Name"]] = tree
1142 
1143  return trees
1144 
1145 
1146 def cmpTreesDicts(reference, to_check, ignore=None):
1147  """
1148  Check that all the keys in reference are in to_check too, with the same value.
1149  If the value is a dict, the function is called recursively. to_check can
1150  contain more keys than reference, that will not be tested.
1151  The function returns at the first difference found.
1152  """
1153  fail_keys = []
1154  # filter the keys in the reference dictionary
1155  if ignore:
1156  ignore_re = re.compile(ignore)
1157  keys = [key for key in reference if not ignore_re.match(key)]
1158  else:
1159  keys = reference.keys()
1160  # loop over the keys (not ignored) in the reference dictionary
1161  for k in keys:
1162  if k in to_check: # the key must be in the dictionary to_check
1163  if (type(reference[k]) is dict) and (type(to_check[k]) is dict):
1164  # if both reference and to_check values are dictionaries,
1165  # recurse
1166  failed = fail_keys = cmpTreesDicts(reference[k], to_check[k],
1167  ignore)
1168  else:
1169  # compare the two values
1170  failed = to_check[k] != reference[k]
1171  else: # handle missing keys in the dictionary to check (i.e. failure)
1172  to_check[k] = None
1173  failed = True
1174  if failed:
1175  fail_keys.insert(0, k)
1176  break # exit from the loop at the first failure
1177  return fail_keys # return the list of keys bringing to the different values
1178 
1179 
1180 def getCmpFailingValues(reference, to_check, fail_path):
1181  c = to_check
1182  r = reference
1183  for k in fail_path:
1184  c = c.get(k, None)
1185  r = r.get(k, None)
1186  if c is None or r is None:
1187  break # one of the dictionaries is not deep enough
1188  return (fail_path, r, c)
1189 
1190 
1191 # signature of the print-out of the histograms
1192 h_count_re = re.compile(
1193  r"^(.*)SUCCESS\s+Booked (\d+) Histogram\‍(s\‍) :\s+([\s\w=-]*)")
1194 
1195 
1196 def _parseTTreeSummary(lines, pos):
1197  """
1198  Parse the TTree summary table in lines, starting from pos.
1199  Returns a tuple with the dictionary with the digested informations and the
1200  position of the first line after the summary.
1201  """
1202  result = {}
1203  i = pos + 1 # first line is a sequence of '*'
1204  count = len(lines)
1205 
1206  def splitcols(l):
1207  return [f.strip() for f in l.strip("*\n").split(':', 2)]
1208 
1209  def parseblock(ll):
1210  r = {}
1211  cols = splitcols(ll[0])
1212  r["Name"], r["Title"] = cols[1:]
1213 
1214  cols = splitcols(ll[1])
1215  r["Entries"] = int(cols[1])
1216 
1217  sizes = cols[2].split()
1218  r["Total size"] = int(sizes[2])
1219  if sizes[-1] == "memory":
1220  r["File size"] = 0
1221  else:
1222  r["File size"] = int(sizes[-1])
1223 
1224  cols = splitcols(ll[2])
1225  sizes = cols[2].split()
1226  if cols[0] == "Baskets":
1227  r["Baskets"] = int(cols[1])
1228  r["Basket size"] = int(sizes[2])
1229  r["Compression"] = float(sizes[-1])
1230  return r
1231 
1232  if i < (count - 3) and lines[i].startswith("*Tree"):
1233  result = parseblock(lines[i:i + 3])
1234  result["Branches"] = {}
1235  i += 4
1236  while i < (count - 3) and lines[i].startswith("*Br"):
1237  if i < (count - 2) and lines[i].startswith("*Branch "):
1238  # skip branch header
1239  i += 3
1240  continue
1241  branch = parseblock(lines[i:i + 3])
1242  result["Branches"][branch["Name"]] = branch
1243  i += 4
1244 
1245  return (result, i)
1246 
1247 
1248 def parseHistosSummary(lines, pos):
1249  """
1250  Extract the histograms infos from the lines starting at pos.
1251  Returns the position of the first line after the summary block.
1252  """
1253  global h_count_re
1254  h_table_head = re.compile(
1255  r'SUCCESS\s+(1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"'
1256  )
1257  h_short_summ = re.compile(r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
1258 
1259  nlines = len(lines)
1260 
1261  # decode header
1262  m = h_count_re.search(lines[pos])
1263  name = m.group(1).strip()
1264  total = int(m.group(2))
1265  header = {}
1266  for k, v in [x.split("=") for x in m.group(3).split()]:
1267  header[k] = int(v)
1268  pos += 1
1269  header["Total"] = total
1270 
1271  summ = {}
1272  while pos < nlines:
1273  m = h_table_head.search(lines[pos])
1274  if m:
1275  t, d = m.groups(1) # type and directory
1276  t = t.replace(" profile", "Prof")
1277  pos += 1
1278  if pos < nlines:
1279  l = lines[pos]
1280  else:
1281  l = ""
1282  cont = {}
1283  if l.startswith(" | ID"):
1284  # table format
1285  titles = [x.strip() for x in l.split("|")][1:]
1286  pos += 1
1287  while pos < nlines and lines[pos].startswith(" |"):
1288  l = lines[pos]
1289  values = [x.strip() for x in l.split("|")][1:]
1290  hcont = {}
1291  for i in range(len(titles)):
1292  hcont[titles[i]] = values[i]
1293  cont[hcont["ID"]] = hcont
1294  pos += 1
1295  elif l.startswith(" ID="):
1296  while pos < nlines and lines[pos].startswith(" ID="):
1297  values = [
1298  x.strip()
1299  for x in h_short_summ.search(lines[pos]).groups()
1300  ]
1301  cont[values[0]] = values
1302  pos += 1
1303  else: # not interpreted
1304  raise RuntimeError(
1305  "Cannot understand line %d: '%s'" % (pos, l))
1306  if not d in summ:
1307  summ[d] = {}
1308  summ[d][t] = cont
1309  summ[d]["header"] = header
1310  else:
1311  break
1312  if not summ:
1313  # If the full table is not present, we use only the header
1314  summ[name] = {"header": header}
1315  return summ, pos
1316 
1317 
1319  """
1320  Scan stdout to find ROOT TTree summaries and digest them.
1321  """
1322  outlines = stdout.splitlines()
1323  nlines = len(outlines) - 1
1324  summaries = {}
1325  global h_count_re
1326 
1327  pos = 0
1328  while pos < nlines:
1329  summ = {}
1330  # find first line of block:
1331  match = h_count_re.search(outlines[pos])
1332  while pos < nlines and not match:
1333  pos += 1
1334  match = h_count_re.search(outlines[pos])
1335  if match:
1336  summ, pos = parseHistosSummary(outlines, pos)
1337  summaries.update(summ)
1338  return summaries
1339 
1340 
1341 def GetPlatform(self):
1342  """
1343  Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1344  """
1345  arch = "None"
1346  # check architecture name
1347  if "BINARY_TAG" in os.environ:
1348  arch = os.environ["BINARY_TAG"]
1349  elif "CMTCONFIG" in os.environ:
1350  arch = os.environ["CMTCONFIG"]
1351  elif "SCRAM_ARCH" in os.environ:
1352  arch = os.environ["SCRAM_ARCH"]
1353  elif os.environ.get("ENV_CMAKE_BUILD_TYPE", "") in ("Debug", "FastDebug",
1354  "Developer"):
1355  arch = "dummy-dbg"
1356  elif os.environ.get("ENV_CMAKE_BUILD_TYPE",
1357  "") in ("Release", "MinSizeRel", "RelWithDebInfo",
1358  ""): # RelWithDebInfo == -O2 -g -DNDEBUG
1359  arch = "dummy-opt"
1360  return arch
1361 
1362 
1363 def isWinPlatform(self):
1364  """
1365  Return True if the current platform is Windows.
1366 
1367  This function was needed because of the change in the CMTCONFIG format,
1368  from win32_vc71_dbg to i686-winxp-vc9-dbg.
1369  """
1370  platform = GetPlatform(self)
1371  return "winxp" in platform or platform.startswith("win")
GaudiTesting.BaseTest.ReferenceFileValidator.reffile
reffile
Definition: BaseTest.py:1090
GaudiTesting.BaseTest.BaseTest.causes
causes
Definition: BaseTest.py:124
GaudiTesting.BaseTest.SortGroupOfLines.__init__
def __init__(self, exp)
Definition: BaseTest.py:935
GaudiTesting.BaseTest.BaseTest.options
options
Definition: BaseTest.py:112
GaudiTesting.BaseTest.FilePreprocessor
Definition: BaseTest.py:800
MSG::hex
MsgStream & hex(MsgStream &log)
Definition: MsgStream.h:282
GaudiTesting.BaseTest.Result.__getitem__
def __getitem__(self, key)
Definition: BaseTest.py:727
GaudiTesting.BaseTest.BasicOutputValidator.ref
ref
Definition: BaseTest.py:755
GaudiTesting.BaseTest.dumpProcs
def dumpProcs(name)
Definition: BaseTest.py:71
GaudiTesting.BaseTest.LineSorter.siglen
siglen
Definition: BaseTest.py:918
GaudiTesting.BaseTest.FilePreprocessor.__call__
def __call__(self, input)
Definition: BaseTest.py:815
GaudiTesting.BaseTest.LineSorter
Definition: BaseTest.py:915
GaudiTesting.BaseTest._parseTTreeSummary
def _parseTTreeSummary(lines, pos)
Definition: BaseTest.py:1196
GaudiTesting.BaseTest.LineSorter.__processLine__
def __processLine__(self, line)
Definition: BaseTest.py:920
GaudiTesting.BaseTest.BaseTest.out
out
Definition: BaseTest.py:127
GaudiTesting.BaseTest.BaseTest.CheckHistosSummaries
def CheckHistosSummaries(self, stdout=None, result=None, causes=None, dict=None, ignore=None)
Definition: BaseTest.py:477
GaudiTesting.BaseTest.sanitize_for_xml
def sanitize_for_xml(data)
Definition: BaseTest.py:53
GaudiTesting.BaseTest.BaseTest._common_tmpdir
_common_tmpdir
Definition: BaseTest.py:105
GaudiTesting.BaseTest.BaseTest.reference
reference
Definition: BaseTest.py:110
GaudiTesting.BaseTest.BasicOutputValidator.__init__
def __init__(self, ref, cause, result_key)
Definition: BaseTest.py:754
GaudiTesting.BaseTest.BaseTest.timeout
timeout
Definition: BaseTest.py:114
GaudiTesting.BaseTest.ReferenceFileValidator.preproc
preproc
Definition: BaseTest.py:1093
GaudiTesting.BaseTest.BaseTest.validateWithReference
def validateWithReference(self, stdout=None, stderr=None, result=None, causes=None, preproc=None)
Definition: BaseTest.py:522
GaudiTesting.BaseTest.getCmpFailingValues
def getCmpFailingValues(reference, to_check, fail_path)
Definition: BaseTest.py:1180
GaudiTesting.BaseTest.BasicOutputValidator.result_key
result_key
Definition: BaseTest.py:757
GaudiTesting.BaseTest.BaseTest.proc
proc
Definition: BaseTest.py:129
GaudiTesting.BaseTest._new_backslashreplace_errors
def _new_backslashreplace_errors(exc)
Definition: BaseTest.py:38
hivetimeline.read
def read(f, regex='.*', skipevents=0)
Definition: hivetimeline.py:33
GaudiTesting.BaseTest.BaseTest.stack_trace
stack_trace
Definition: BaseTest.py:130
GaudiTesting.BaseTest.FilePreprocessor.__processFile__
def __processFile__(self, lines)
Definition: BaseTest.py:807
GaudiTesting.BaseTest.BaseTest.environment
environment
Definition: BaseTest.py:116
GaudiTesting.BaseTest.LineSorter.signature
signature
Definition: BaseTest.py:917
GaudiTesting.BaseTest.BaseTest.exit_code
exit_code
Definition: BaseTest.py:115
GaudiTesting.BaseTest.BlockSkipper.start
start
Definition: BaseTest.py:863
GaudiTesting.BaseTest.kill_tree
def kill_tree(ppid, sig)
Definition: BaseTest.py:80
GaudiTesting.BaseTest.Result.Quote
def Quote(self, text)
Definition: BaseTest.py:737
GaudiTesting.BaseTest.FilePreprocessorSequence.__add__
def __add__(self, rhs)
Definition: BaseTest.py:835
Containers::map
struct GAUDI_API map
Parametrisation class for map-like implementation.
Definition: KeyedObjectManager.h:35
GaudiTesting.BaseTest.FilePreprocessorSequence
Definition: BaseTest.py:831
GaudiTesting.BaseTest.BaseTest.__init__
def __init__(self)
Definition: BaseTest.py:107
GaudiTesting.BaseTest.RegexpReplacer._operations
_operations
Definition: BaseTest.py:882
GaudiTesting.BaseTest.BaseTest.err
err
Definition: BaseTest.py:128
GaudiTesting.BaseTest.SortGroupOfLines.__processFile__
def __processFile__(self, lines)
Definition: BaseTest.py:938
GaudiTesting.BaseTest.BlockSkipper
Definition: BaseTest.py:861
GaudiTesting.BaseTest.BaseTest.args
args
Definition: BaseTest.py:109
GaudiTesting.BaseTest.BaseTest.result
result
Definition: BaseTest.py:125
GaudiTesting.BaseTest.FilePreprocessor.__processLine__
def __processLine__(self, line)
Definition: BaseTest.py:804
GaudiTesting.BaseTest.FilePreprocessorSequence.__call__
def __call__(self, input)
Definition: BaseTest.py:838
GaudiTesting.BaseTest.BaseTest.workdir
workdir
Definition: BaseTest.py:119
Gaudi::Functional::details::get
auto get(const Handle &handle, const Algo &, const EventContext &) -> decltype(details::deref(handle.get()))
Definition: FunctionalDetails.h:405
GaudiTesting.BaseTest.BlockSkipper._skipping
_skipping
Definition: BaseTest.py:865
GaudiTesting.BaseTest.ReferenceFileValidator.cause
cause
Definition: BaseTest.py:1091
GaudiTesting.BaseTest.parseHistosSummary
def parseHistosSummary(lines, pos)
Definition: BaseTest.py:1248
GaudiTesting.BaseTest.RegexpReplacer
Definition: BaseTest.py:878
GaudiTesting.BaseTest.isWinPlatform
def isWinPlatform(self)
Definition: BaseTest.py:1363
GaudiTesting.BaseTest.LineSkipper.regexps
regexps
Definition: BaseTest.py:849
GaudiTesting.BaseTest.BaseTest.basedir
basedir
Definition: BaseTest.py:131
GaudiTesting.BaseTest.which
def which(executable)
Definition: BaseTest.py:679
GaudiTesting.BaseTest.SortGroupOfLines.exp
exp
Definition: BaseTest.py:936
GaudiTesting.BaseTest.BaseTest.unsupported_platforms
unsupported_platforms
Definition: BaseTest.py:117
GaudiTesting.BaseTest.Result.__init__
def __init__(self, kind=None, id=None, outcome=PASS, annotations={})
Definition: BaseTest.py:724
GaudiTesting.BaseTest.BaseTest.countErrorLines
def countErrorLines(self, expected={ 'ERROR':0, 'FATAL':0 }, stdout=None, result=None, causes=None)
Definition: BaseTest.py:384
GaudiTesting.BaseTest.BlockSkipper.end
end
Definition: BaseTest.py:864
GaudiTesting.BaseTest.BaseTest.returnedCode
returnedCode
Definition: BaseTest.py:126
GaudiTesting.BaseTest.LineSkipper.strings
strings
Definition: BaseTest.py:848
GaudiTesting.BaseTest.BasicOutputValidator.__call__
def __call__(self, out, result)
Definition: BaseTest.py:759
GaudiTesting.BaseTest.cmpTreesDicts
def cmpTreesDicts(reference, to_check, ignore=None)
Definition: BaseTest.py:1146
GaudiTesting.BaseTest.Result.annotations
annotations
Definition: BaseTest.py:725
GaudiTesting.BaseTest.BaseTest.name
name
Definition: BaseTest.py:123
format
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition: MsgStream.cpp:119
GaudiTesting.BaseTest.RegexpReplacer.__processLine__
def __processLine__(self, line)
Definition: BaseTest.py:892
GaudiTesting.BaseTest.ReferenceFileValidator.__init__
def __init__(self, reffile, cause, result_key, preproc=normalizeExamples)
Definition: BaseTest.py:1089
GaudiTesting.BaseTest.FilePreprocessorSequence.members
members
Definition: BaseTest.py:833
GaudiTesting.BaseTest.BaseTest._expandReferenceFileName
def _expandReferenceFileName(self, reffile)
Definition: BaseTest.py:608
GaudiTesting.BaseTest.BaseTest.signal
signal
Definition: BaseTest.py:118
GaudiTesting.BaseTest.SortGroupOfLines
Definition: BaseTest.py:930
GaudiTesting.BaseTest.BaseTest.findReferenceBlock
def findReferenceBlock(self, reference=None, stdout=None, result=None, causes=None, signature_offset=0, signature=None, id=None)
Definition: BaseTest.py:327
GaudiTesting.BaseTest.RationalizePath
def RationalizePath(p)
Definition: BaseTest.py:669
GaudiTesting.BaseTest.LineSkipper
Definition: BaseTest.py:845
GaudiTesting.BaseTest.ReferenceFileValidator
Definition: BaseTest.py:1088
gaudirun.type
type
Definition: gaudirun.py:154
GaudiTesting.BaseTest.BaseTest.program
program
Definition: BaseTest.py:108
GaudiTesting.BaseTest.FilePreprocessorSequence.__init__
def __init__(self, members=[])
Definition: BaseTest.py:832
GaudiTesting.BaseTest.ReferenceFileValidator.__call__
def __call__(self, stdout, result)
Definition: BaseTest.py:1095
GaudiTesting.BaseTest.BasicOutputValidator.cause
cause
Definition: BaseTest.py:756
GaudiTesting.BaseTest.FilePreprocessor.__add__
def __add__(self, rhs)
Definition: BaseTest.py:827
GaudiTesting.BaseTest.ReferenceFileValidator.result_key
result_key
Definition: BaseTest.py:1092
GaudiTesting.BaseTest.BlockSkipper.__init__
def __init__(self, start, end)
Definition: BaseTest.py:862
GaudiTesting.BaseTest.RegexpReplacer.__init__
def __init__(self, orig, repl="", when=None)
Definition: BaseTest.py:879
GaudiTesting.BaseTest.findHistosSummaries
def findHistosSummaries(stdout)
Definition: BaseTest.py:1318
GaudiTesting.BaseTest.Result.__setitem__
def __setitem__(self, key, value)
Definition: BaseTest.py:731
GaudiTesting.BaseTest.BaseTest.CheckTTreesSummaries
def CheckTTreesSummaries(self, stdout=None, result=None, causes=None, trees_dict=None, ignore=r"Basket|.*size|Compression")
Definition: BaseTest.py:432
GaudiTesting.BaseTest.BaseTest
Definition: BaseTest.py:103
GaudiTesting.BaseTest.BaseTest.error_reference
error_reference
Definition: BaseTest.py:111
GaudiTesting.BaseTest.BaseTest.ValidateOutput
def ValidateOutput(self, stdout, stderr, result)
Definition: BaseTest.py:320
GaudiTesting.BaseTest.BasicOutputValidator
Definition: BaseTest.py:753
GaudiTesting.BaseTest.LineSkipper.__init__
def __init__(self, strings=[], regexps=[])
Definition: BaseTest.py:846
GaudiTesting.BaseTest.Result
Definition: BaseTest.py:709
GaudiTesting.BaseTest.BaseTest.run
def run(self)
Definition: BaseTest.py:133
GaudiTesting.BaseTest.findTTreeSummaries
def findTTreeSummaries(stdout)
Definition: BaseTest.py:1124
GaudiTesting.BaseTest.BasicOutputValidator.__CompareText
def __CompareText(self, s1, s2)
Definition: BaseTest.py:777
GaudiTesting.BaseTest.RegexpReplacer.__add__
def __add__(self, rhs)
Definition: BaseTest.py:884
compareOutputFiles.pp
pp
Definition: compareOutputFiles.py:507
GaudiTesting.BaseTest.BaseTest.stderr
stderr
Definition: BaseTest.py:113
GaudiTesting.BaseTest.LineSorter.__init__
def __init__(self, signature)
Definition: BaseTest.py:916
GaudiTesting.BaseTest.LineSkipper.__processLine__
def __processLine__(self, line)
Definition: BaseTest.py:851
GaudiTesting.BaseTest.ROOT6WorkAroundEnabled
def ROOT6WorkAroundEnabled(id=None)
Definition: BaseTest.py:661
GaudiTesting.BaseTest.BaseTest.use_temp_dir
use_temp_dir
Definition: BaseTest.py:120
GaudiTesting.BaseTest.GetPlatform
def GetPlatform(self)
Definition: BaseTest.py:1341
GaudiTesting.BaseTest.BaseTest.status
status
Definition: BaseTest.py:122
Gaudi::Functional::details::zip::range
decltype(auto) range(Args &&... args)
Zips multiple containers together to form a single range.
Definition: FunctionalDetails.h:101
GaudiTesting.BaseTest.BlockSkipper.__processLine__
def __processLine__(self, line)
Definition: BaseTest.py:867