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