The Gaudi Framework  v36r6 (b1ee9983)
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".*StatusCodeCheck.*",
1058  r"Num\s*\|\s*Function\s*\|\s*Source Library",
1059  r"^[-+]*\s*$",
1060  # Hide the fake error message coming from POOL/ROOT (ROOT 5.21)
1061  r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
1062  # Hide unchecked StatusCodes from dictionaries
1063  r"^ +[0-9]+ \|.*ROOT",
1064  r"^ +[0-9]+ \|.*\|.*Dict",
1065  # Hide EventLoopMgr total timing report
1066  r"EventLoopMgr.*---> Loop Finished",
1067  r"HiveSlimEventLo.*---> Loop Finished",
1068  # Remove ROOT TTree summary table, which changes from one version to the
1069  # other
1070  r"^\*.*\*$",
1071  # Remove Histos Summaries
1072  r"SUCCESS\s*Booked \d+ Histogram\‍(s\‍)",
1073  r"^ \|",
1074  r"^ ID=",
1075  # Ignore added/removed properties
1076  r"Property(.*)'Audit(Algorithm|Tool|Service)s':",
1077  r"Property(.*)'Audit(Begin|End)Run':",
1078  # these were missing in tools
1079  r"Property(.*)'AuditRe(start|initialize)':",
1080  r"Property(.*)'Blocking':",
1081  # removed with gaudi/Gaudi!273
1082  r"Property(.*)'ErrorCount(er)?':",
1083  # added with gaudi/Gaudi!306
1084  r"Property(.*)'Sequential':",
1085  # added with gaudi/Gaudi!314
1086  r"Property(.*)'FilterCircularDependencies':",
1087  # removed with gaudi/Gaudi!316
1088  r"Property(.*)'IsClonable':",
1089  # ignore uninteresting/obsolete messages
1090  r"Property update for OutputLevel : new value =",
1091  r"EventLoopMgr\s*DEBUG Creating OutputStream",
1092  ],
1093 )
1094 
1095 if ROOT6WorkAroundEnabled("ReadRootmapCheck"):
1096  # FIXME: (MCl) Hide warnings from new rootmap sanity check until we can
1097  # fix them
1098  lineSkipper += LineSkipper(
1099  regexps=[
1100  r"Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*",
1101  ]
1102  )
1103 
1104 normalizeExamples = (
1105  lineSkipper
1106  + normalizeExamples
1107  + skipEmptyLines
1108  + normalizeEOL
1109  + LineSorter("Services to release : ")
1110  + SortGroupOfLines(r"^\S+\s+(DEBUG|SUCCESS) Property \[\'Name\':")
1111 )
1112 
1113 # --------------------- Validation functions/classes ---------------------#
1114 
1115 
1117  def __init__(self, reffile, cause, result_key, preproc=normalizeExamples):
1118  self.reffile = os.path.expandvars(reffile)
1119  self.cause = cause
1120  self.result_key = result_key
1121  self.preproc = preproc
1122 
1123  def __call__(self, stdout, result):
1124  causes = []
1125  if os.path.isfile(self.reffile):
1126  orig = open(self.reffile).readlines()
1127  if self.preproc:
1128  orig = self.preproc(orig)
1129  result[self.result_key + ".preproc.orig"] = result.Quote(
1130  "\n".join(map(str.strip, orig))
1131  )
1132  else:
1133  orig = []
1134  new = stdout.splitlines()
1135  if self.preproc:
1136  new = self.preproc(new)
1137 
1138  diffs = difflib.ndiff(orig, new, charjunk=difflib.IS_CHARACTER_JUNK)
1139  filterdiffs = list(
1140  map(lambda x: x.strip(), filter(lambda x: x[0] != " ", diffs))
1141  )
1142  if filterdiffs:
1143  result[self.result_key] = result.Quote("\n".join(filterdiffs))
1144  result[self.result_key] += result.Quote(
1145  """
1146  Legend:
1147  -) reference file
1148  +) standard output of the test"""
1149  )
1150  result[self.result_key + ".preproc.new"] = result.Quote(
1151  "\n".join(map(str.strip, new))
1152  )
1153  causes.append(self.cause)
1154  return causes
1155 
1156 
1158  """
1159  Scan stdout to find ROOT TTree summaries and digest them.
1160  """
1161  stars = re.compile(r"^\*+$")
1162  outlines = stdout.splitlines()
1163  nlines = len(outlines)
1164  trees = {}
1165 
1166  i = 0
1167  while i < nlines: # loop over the output
1168  # look for
1169  while i < nlines and not stars.match(outlines[i]):
1170  i += 1
1171  if i < nlines:
1172  tree, i = _parseTTreeSummary(outlines, i)
1173  if tree:
1174  trees[tree["Name"]] = tree
1175 
1176  return trees
1177 
1178 
1179 def cmpTreesDicts(reference, to_check, ignore=None):
1180  """
1181  Check that all the keys in reference are in to_check too, with the same value.
1182  If the value is a dict, the function is called recursively. to_check can
1183  contain more keys than reference, that will not be tested.
1184  The function returns at the first difference found.
1185  """
1186  fail_keys = []
1187  # filter the keys in the reference dictionary
1188  if ignore:
1189  ignore_re = re.compile(ignore)
1190  keys = [key for key in reference if not ignore_re.match(key)]
1191  else:
1192  keys = reference.keys()
1193  # loop over the keys (not ignored) in the reference dictionary
1194  for k in keys:
1195  if k in to_check: # the key must be in the dictionary to_check
1196  if (type(reference[k]) is dict) and (type(to_check[k]) is dict):
1197  # if both reference and to_check values are dictionaries,
1198  # recurse
1199  failed = fail_keys = cmpTreesDicts(reference[k], to_check[k], ignore)
1200  else:
1201  # compare the two values
1202  failed = to_check[k] != reference[k]
1203  else: # handle missing keys in the dictionary to check (i.e. failure)
1204  to_check[k] = None
1205  failed = True
1206  if failed:
1207  fail_keys.insert(0, k)
1208  break # exit from the loop at the first failure
1209  return fail_keys # return the list of keys bringing to the different values
1210 
1211 
1212 def getCmpFailingValues(reference, to_check, fail_path):
1213  c = to_check
1214  r = reference
1215  for k in fail_path:
1216  c = c.get(k, None)
1217  r = r.get(k, None)
1218  if c is None or r is None:
1219  break # one of the dictionaries is not deep enough
1220  return (fail_path, r, c)
1221 
1222 
1223 # signature of the print-out of the histograms
1224 h_count_re = re.compile(r"^(.*)SUCCESS\s+Booked (\d+) Histogram\‍(s\‍) :\s+([\s\w=-]*)")
1225 
1226 
1227 def _parseTTreeSummary(lines, pos):
1228  """
1229  Parse the TTree summary table in lines, starting from pos.
1230  Returns a tuple with the dictionary with the digested informations and the
1231  position of the first line after the summary.
1232  """
1233  result = {}
1234  i = pos + 1 # first line is a sequence of '*'
1235  count = len(lines)
1236 
1237  def splitcols(l):
1238  return [f.strip() for f in l.strip("*\n").split(":", 2)]
1239 
1240  def parseblock(ll):
1241  r = {}
1242  cols = splitcols(ll[0])
1243  r["Name"], r["Title"] = cols[1:]
1244 
1245  cols = splitcols(ll[1])
1246  r["Entries"] = int(cols[1])
1247 
1248  sizes = cols[2].split()
1249  r["Total size"] = int(sizes[2])
1250  if sizes[-1] == "memory":
1251  r["File size"] = 0
1252  else:
1253  r["File size"] = int(sizes[-1])
1254 
1255  cols = splitcols(ll[2])
1256  sizes = cols[2].split()
1257  if cols[0] == "Baskets":
1258  r["Baskets"] = int(cols[1])
1259  r["Basket size"] = int(sizes[2])
1260  r["Compression"] = float(sizes[-1])
1261  return r
1262 
1263  if i < (count - 3) and lines[i].startswith("*Tree"):
1264  result = parseblock(lines[i : i + 3])
1265  result["Branches"] = {}
1266  i += 4
1267  while i < (count - 3) and lines[i].startswith("*Br"):
1268  if i < (count - 2) and lines[i].startswith("*Branch "):
1269  # skip branch header
1270  i += 3
1271  continue
1272  branch = parseblock(lines[i : i + 3])
1273  result["Branches"][branch["Name"]] = branch
1274  i += 4
1275 
1276  return (result, i)
1277 
1278 
1279 def parseHistosSummary(lines, pos):
1280  """
1281  Extract the histograms infos from the lines starting at pos.
1282  Returns the position of the first line after the summary block.
1283  """
1284  global h_count_re
1285  h_table_head = re.compile(
1286  r'SUCCESS\s+(1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"'
1287  )
1288  h_short_summ = re.compile(r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
1289 
1290  nlines = len(lines)
1291 
1292  # decode header
1293  m = h_count_re.search(lines[pos])
1294  name = m.group(1).strip()
1295  total = int(m.group(2))
1296  header = {}
1297  for k, v in [x.split("=") for x in m.group(3).split()]:
1298  header[k] = int(v)
1299  pos += 1
1300  header["Total"] = total
1301 
1302  summ = {}
1303  while pos < nlines:
1304  m = h_table_head.search(lines[pos])
1305  if m:
1306  t, d = m.groups(1) # type and directory
1307  t = t.replace(" profile", "Prof")
1308  pos += 1
1309  if pos < nlines:
1310  l = lines[pos]
1311  else:
1312  l = ""
1313  cont = {}
1314  if l.startswith(" | ID"):
1315  # table format
1316  titles = [x.strip() for x in l.split("|")][1:]
1317  pos += 1
1318  while pos < nlines and lines[pos].startswith(" |"):
1319  l = lines[pos]
1320  values = [x.strip() for x in l.split("|")][1:]
1321  hcont = {}
1322  for i in range(len(titles)):
1323  hcont[titles[i]] = values[i]
1324  cont[hcont["ID"]] = hcont
1325  pos += 1
1326  elif l.startswith(" ID="):
1327  while pos < nlines and lines[pos].startswith(" ID="):
1328  values = [
1329  x.strip() for x in h_short_summ.search(lines[pos]).groups()
1330  ]
1331  cont[values[0]] = values
1332  pos += 1
1333  else: # not interpreted
1334  raise RuntimeError("Cannot understand line %d: '%s'" % (pos, l))
1335  if not d in summ:
1336  summ[d] = {}
1337  summ[d][t] = cont
1338  summ[d]["header"] = header
1339  else:
1340  break
1341  if not summ:
1342  # If the full table is not present, we use only the header
1343  summ[name] = {"header": header}
1344  return summ, pos
1345 
1346 
1348  """
1349  Scan stdout to find ROOT TTree summaries and digest them.
1350  """
1351  outlines = stdout.splitlines()
1352  nlines = len(outlines) - 1
1353  summaries = {}
1354  global h_count_re
1355 
1356  pos = 0
1357  while pos < nlines:
1358  summ = {}
1359  # find first line of block:
1360  match = h_count_re.search(outlines[pos])
1361  while pos < nlines and not match:
1362  pos += 1
1363  match = h_count_re.search(outlines[pos])
1364  if match:
1365  summ, pos = parseHistosSummary(outlines, pos)
1366  summaries.update(summ)
1367  return summaries
1368 
1369 
1370 def GetPlatform(self):
1371  """
1372  Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1373  """
1374  arch = "None"
1375  # check architecture name
1376  if "BINARY_TAG" in os.environ:
1377  arch = os.environ["BINARY_TAG"]
1378  elif "CMTCONFIG" in os.environ:
1379  arch = os.environ["CMTCONFIG"]
1380  elif "SCRAM_ARCH" in os.environ:
1381  arch = os.environ["SCRAM_ARCH"]
1382  elif os.environ.get("ENV_CMAKE_BUILD_TYPE", "") in (
1383  "Debug",
1384  "FastDebug",
1385  "Developer",
1386  ):
1387  arch = "dummy-dbg"
1388  elif os.environ.get("ENV_CMAKE_BUILD_TYPE", "") in (
1389  "Release",
1390  "MinSizeRel",
1391  "RelWithDebInfo",
1392  "",
1393  ): # RelWithDebInfo == -O2 -g -DNDEBUG
1394  arch = "dummy-opt"
1395  return arch
1396 
1397 
1398 def isWinPlatform(self):
1399  """
1400  Return True if the current platform is Windows.
1401 
1402  This function was needed because of the change in the CMTCONFIG format,
1403  from win32_vc71_dbg to i686-winxp-vc9-dbg.
1404  """
1405  platform = GetPlatform(self)
1406  return "winxp" in platform or platform.startswith("win")
GaudiTesting.BaseTest.ReferenceFileValidator.reffile
reffile
Definition: BaseTest.py:1118
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:1227
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:1121
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:1212
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:444
GaudiTesting.BaseTest.BlockSkipper._skipping
_skipping
Definition: BaseTest.py:876
GaudiTesting.BaseTest.ReferenceFileValidator.cause
cause
Definition: BaseTest.py:1119
GaudiTesting.BaseTest.parseHistosSummary
def parseHistosSummary(lines, pos)
Definition: BaseTest.py:1279
GaudiTesting.BaseTest.RegexpReplacer
Definition: BaseTest.py:889
GaudiTesting.BaseTest.isWinPlatform
def isWinPlatform(self)
Definition: BaseTest.py:1398
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:1179
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:1117
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:1116
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:1123
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:1120
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:1347
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:1157
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:1370
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