| The Gaudi Framework v32r1 (f65d50dc)

GaudiTest.py
Go to the documentation of this file.
1
5 __author__ = 'Marco Clemencic CERN/PH-LBC'
6
9 import os
10 import sys
11 import re
12 import tempfile
13 import shutil
14 import string
15 import difflib
16 import time
17 import calendar
18 import codecs
19
20 from subprocess import Popen, PIPE, STDOUT
21
22 try:
23 from GaudiKernel import ROOT6WorkAroundEnabled
24 except ImportError:
25
26 def ROOT6WorkAroundEnabled(id=None):
27 # dummy implementation
28 return False
29
30
31 # ensure the preferred locale
32 os.environ['LC_ALL'] = 'C'
33
34 # Needed for the XML wrapper
35 try:
36 import xml.etree.cElementTree as ET
37 except ImportError:
38 import xml.etree.ElementTree as ET
39
40 # redefinition of timedelta.total_seconds() because it is not present in the 2.6 version
41
42
43 def total_seconds_replacement(timedelta):
44 return timedelta.days * 86400 + timedelta.seconds + timedelta.microseconds / 1000000
45
46
47 import qm
48 from qm.test.classes.command import ExecTestBase
49 from qm.test.result_stream import ResultStream
50
51 # Needed by the re-implementation of TimeoutExecutable
52 import qm.executable
53 import signal
54 # The classes in this module are implemented differently depending on
55 # the operating system in use.
56 if sys.platform == "win32":
57 import msvcrt
58 import pywintypes
59 from threading import *
60 import win32api
61 import win32con
62 import win32event
63 import win32file
64 import win32pipe
65 import win32process
66 else:
67 from six.moves import cPickle
68 import fcntl
69 import select
70 import qm.sigmask
71
72
75
76
77 class TemporaryEnvironment:
78 """
79 Class to changes the environment temporarily.
80 """
81
82 def __init__(self, orig=os.environ, keep_same=False):
83 """
84 Create a temporary environment on top of the one specified
85 (it can be another TemporaryEnvironment instance).
86 """
87 # print "New environment"
88 self.old_values = {}
89 self.env = orig
90 self._keep_same = keep_same
91
92 def __setitem__(self, key, value):
93 """
94 Set an environment variable recording the previous value.
95 """
96 if key not in self.old_values:
97 if key in self.env:
98 if not self._keep_same or self.env[key] != value:
99 self.old_values[key] = self.env[key]
100 else:
101 self.old_values[key] = None
102 self.env[key] = value
103
104 def __getitem__(self, key):
105 """
106 Get an environment variable.
107 Needed to provide the same interface as os.environ.
108 """
109 return self.env[key]
110
111 def __delitem__(self, key):
112 """
113 Unset an environment variable.
114 Needed to provide the same interface as os.environ.
115 """
116 if key not in self.env:
117 raise KeyError(key)
118 self.old_values[key] = self.env[key]
119 del self.env[key]
120
121 def keys(self):
122 """
123 Return the list of defined environment variables.
124 Needed to provide the same interface as os.environ.
125 """
126 return self.env.keys()
127
128 def items(self):
129 """
130 Return the list of (name,value) pairs for the defined environment variables.
131 Needed to provide the same interface as os.environ.
132 """
133 return self.env.items()
134
135 def __contains__(self, key):
136 """
137 Operator 'in'.
138 Needed to provide the same interface as os.environ.
139 """
140 return key in self.env
141
142 def restore(self):
143 """
144 Revert all the changes done to the original environment.
145 """
146 for key, value in self.old_values.items():
147 if value is None:
148 del self.env[key]
149 else:
150 self.env[key] = value
151 self.old_values = {}
152
153 def __del__(self):
154 """
155 Revert the changes on destruction.
156 """
157 # print "Restoring the environment"
158 self.restore()
159
160 def gen_script(self, shell_type):
161 """
162 Generate a shell script to reproduce the changes in the environment.
163 """
164 shells = ['csh', 'sh', 'bat']
165 if shell_type not in shells:
166 raise RuntimeError("Shell type '%s' unknown. Available: %s" %
167 (shell_type, shells))
168 out = ""
169 for key, value in self.old_values.items():
170 if key not in self.env:
171 # unset variable
172 if shell_type == 'csh':
173 out += 'unsetenv %s\n' % key
174 elif shell_type == 'sh':
175 out += 'unset %s\n' % key
176 elif shell_type == 'bat':
177 out += 'set %s=\n' % key
178 else:
179 # set variable
180 if shell_type == 'csh':
181 out += 'setenv %s "%s"\n' % (key, self.env[key])
182 elif shell_type == 'sh':
183 out += 'export %s="%s"\n' % (key, self.env[key])
184 elif shell_type == 'bat':
185 out += 'set %s=%s\n' % (key, self.env[key])
186 return out
187
188
189 class TempDir:
190 """Small class for temporary directories.
191 When instantiated, it creates a temporary directory and the instance
192 behaves as the string containing the directory name.
193 When the instance goes out of scope, it removes all the content of
194 the temporary directory (automatic clean-up).
195 """
196
197 def __init__(self, keep=False, chdir=False):
198 self.name = tempfile.mkdtemp()
199 self._keep = keep
200 self._origdir = None
201 if chdir:
202 self._origdir = os.getcwd()
203 os.chdir(self.name)
204
205 def __str__(self):
206 return self.name
207
208 def __del__(self):
209 if self._origdir:
210 os.chdir(self._origdir)
211 if self.name and not self._keep:
212 shutil.rmtree(self.name)
213
214 def __getattr__(self, attr):
215 return getattr(self.name, attr)
216
217
218 class TempFile:
219 """Small class for temporary files.
220 When instantiated, it creates a temporary directory and the instance
221 behaves as the string containing the directory name.
222 When the instance goes out of scope, it removes all the content of
223 the temporary directory (automatic clean-up).
224 """
225
226 def __init__(self,
227 suffix='',
228 prefix='tmp',
229 dir=None,
230 text=False,
231 keep=False):
232 self.file = None
233 self.name = None
234 self._keep = keep
235
236 self._fd, self.name = tempfile.mkstemp(suffix, prefix, dir, text)
237 self.file = os.fdopen(self._fd, "r+")
238
239 def __str__(self):
240 return self.name
241
242 def __del__(self):
243 if self.file:
244 self.file.close()
245 if self.name and not self._keep:
246 os.remove(self.name)
247
248 def __getattr__(self, attr):
249 return getattr(self.file, attr)
250
251
252 class CMT:
253 """Small wrapper to call CMT.
254 """
255
256 def __init__(self, path=None):
257 if path is None:
258 path = os.getcwd()
259 self.path = path
260
261 def _run_cmt(self, command, args):
262 # prepare command line
263 if type(args) is str:
264 args = [args]
265 cmd = "cmt %s" % command
266 for arg in args:
267 cmd += ' "%s"' % arg
268
269 # go to the execution directory
270 olddir = os.getcwd()
271 os.chdir(self.path)
272 # run cmt
273 result = os.popen4(cmd)[1].read()
274 # return to the old directory
275 os.chdir(olddir)
276 return result
277
278 def __getattr__(self, attr):
279 return lambda args=[]: self._run_cmt(attr, args)
280
281 def runtime_env(self, env=None):
282 """Returns a dictionary containing the runtime environment produced by CMT.
283 If a dictionary is passed a modified instance of it is returned.
284 """
285 if env is None:
286 env = {}
287 for l in self.setup("-csh").splitlines():
288 l = l.strip()
289 if l.startswith("setenv"):
290 dummy, name, value = l.split(None, 3)
291 env[name] = value.strip('"')
292 elif l.startswith("unsetenv"):
293 dummy, name = l.split(None, 2)
294 if name in env:
295 del env[name]
296 return env
297
298 def show_macro(self, k):
299 r = self.show(["macro", k])
300 if r.find("CMT> Error: symbol not found") >= 0:
301 return None
302 else:
303 return self.show(["macro_value", k]).strip()
304
305
306 # Locates an executable in the executables path ($PATH) and returns the full
307 # path to it.
308 # If the executable cannot be found, None is returned
309 def which(executable):
310 """
311 Locates an executable in the executables path ($PATH) and returns the full
312 path to it. An application is looked for with or without the '.exe' suffix.
313 If the executable cannot be found, None is returned
314 """
315 if os.path.isabs(executable):
316 if not os.path.exists(executable):
317 if executable.endswith('.exe'):
318 if os.path.exists(executable[:-4]):
319 return executable[:-4]
320 return executable
321 for d in os.environ.get("PATH").split(os.pathsep):
322 fullpath = os.path.join(d, executable)
323 if os.path.exists(fullpath):
324 return fullpath
325 if executable.endswith('.exe'):
326 return which(executable[:-4])
327 return None
328
329
330 def rationalizepath(p):
331 np = os.path.normpath(os.path.expandvars(p))
332 if os.path.exists(np):
333 p = os.path.realpath(np)
334 return p
335
336
337 # XML Escaping character
338 import re
339
340 # xml 1.0 valid characters:
341 # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
342 # so to invert that, not in Char ::
343 # x0 - x8 | xB | xC | xE - x1F
344 # (most control characters, though TAB, CR, LF allowed)
345 # | #xD800 - #xDFFF
346 # (unicode surrogate characters)
347 # | #xFFFE | #xFFFF |
348 # (unicode end-of-plane non-characters)
349 # >= 110000
350 # that would be beyond unicode!!!
351 _illegal_xml_chars_RE = re.compile(
352 u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')
353
354
355 def hexreplace(match):
356 "Return the hex string "
357 return "".join(map(hexConvert, match.group()))
358
359
360 def hexConvert(char):
361 return hex(ord(char))
362
363
364 def convert_xml_illegal_chars(val):
365 return _illegal_xml_chars_RE.sub(hexreplace, val)
366
367
368 def escape_xml_illegal_chars(val, replacement='?'):
369 """Filter out characters that are illegal in XML.
370 Looks for any character in val that is not allowed in XML
371 and replaces it with replacement ('?' by default).
372
373 """
374 return _illegal_xml_chars_RE.sub(replacement, val)
375
376
377
380
381
382 class BasicOutputValidator:
383 """Basic implementation of an option validator for Gaudi tests.
384 This implementation is based on the standard (LCG) validation functions
385 used in QMTest.
386 """
387
388 def __init__(self, ref, cause, result_key):
389 self.reference = ref
390 self.cause = cause
391 self.result_key = result_key
392
393 def __call__(self, out, result):
394 """Validate the output of the program.
395
396 'stdout' -- A string containing the data written to the standard output
397 stream.
398
399 'stderr' -- A string containing the data written to the standard error
400 stream.
401
402 'result' -- A 'Result' object. It may be used to annotate
403 the outcome according to the content of stderr.
404
405 returns -- A list of strings giving causes of failure."""
406
407 causes = []
408 # Check to see if theoutput matches.
409 if not self.__CompareText(out, self.reference):
410 causes.append(self.cause)
411 result[self.result_key] = result.Quote(self.reference)
412
413 return causes
414
415 def __CompareText(self, s1, s2):
416 """Compare 's1' and 's2', ignoring line endings.
417
418 's1' -- A string.
419
420 's2' -- A string.
421
422 returns -- True if 's1' and 's2' are the same, ignoring
423 differences in line endings."""
424
425 # The "splitlines" method works independently of the line ending
426 # convention in use.
427 if ROOT6WorkAroundEnabled('ReadRootmapCheck'):
428 # FIXME: (MCl) Hide warnings from new rootmap sanity check until we can fix them
429 to_ignore = re.compile(
430 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*'
431 )
432
433 def keep_line(l):
434 return not to_ignore.match(l)
435
436 return filter(keep_line, s1.splitlines()) == filter(
437 keep_line, s2.splitlines())
438 else:
439 return s1.splitlines() == s2.splitlines()
440
441
442 class FilePreprocessor:
443 """ Base class for a callable that takes a file and returns a modified
444 version of it."""
445
446 def __processLine__(self, line):
447 return line
448
449 def __call__(self, input):
450 if hasattr(input, "__iter__"):
451 lines = input
452 mergeback = False
453 else:
454 lines = input.splitlines()
455 mergeback = True
456 output = []
457 for l in lines:
458 l = self.__processLine__(l)
459 if l:
460 output.append(l)
461 if mergeback:
462 output = '\n'.join(output)
463 return output
464
465 def __add__(self, rhs):
466 return FilePreprocessorSequence([self, rhs])
467
468
469 class FilePreprocessorSequence(FilePreprocessor):
470 def __init__(self, members=[]):
471 self.members = members
472
473 def __add__(self, rhs):
474 return FilePreprocessorSequence(self.members + [rhs])
475
476 def __call__(self, input):
477 output = input
478 for pp in self.members:
479 output = pp(output)
480 return output
481
482
483 class LineSkipper(FilePreprocessor):
484 def __init__(self, strings=[], regexps=[]):
485 import re
486 self.strings = strings
487 self.regexps = map(re.compile, regexps)
488
489 def __processLine__(self, line):
490 for s in self.strings:
491 if line.find(s) >= 0:
492 return None
493 for r in self.regexps:
494 if r.search(line):
495 return None
496 return line
497
498
499 class BlockSkipper(FilePreprocessor):
500 def __init__(self, start, end):
501 self.start = start
502 self.end = end
503 self._skipping = False
504
505 def __processLine__(self, line):
506 if self.start in line:
507 self._skipping = True
508 return None
509 elif self.end in line:
510 self._skipping = False
511 elif self._skipping:
512 return None
513 return line
514
515
516 class RegexpReplacer(FilePreprocessor):
517 def __init__(self, orig, repl="", when=None):
518 if when:
519 when = re.compile(when)
520 self._operations = [(when, re.compile(orig), repl)]
521
522 def __add__(self, rhs):
523 if isinstance(rhs, RegexpReplacer):
524 res = RegexpReplacer("", "", None)
525 res._operations = self._operations + rhs._operations
526 else:
527 res = FilePreprocessor.__add__(self, rhs)
528 return res
529
530 def __processLine__(self, line):
531 for w, o, r in self._operations:
532 if w is None or w.search(line):
533 line = o.sub(r, line)
534 return line
535
536
537 # Common preprocessors
538 maskPointers = RegexpReplacer("0x[0-9a-fA-F]{4,16}", "0x########")
539 normalizeDate = RegexpReplacer(
540 "[0-2]?[0-9]:[0-5][0-9]:[0-5][0-9] [0-9]{4}[-/][01][0-9][-/][0-3][0-9] *(CES?T)?",
541 "00:00:00 1970-01-01")
542 normalizeEOL = FilePreprocessor()
543 normalizeEOL.__processLine__ = lambda line: str(line).rstrip() + '\n'
544
545 skipEmptyLines = FilePreprocessor()
546 # FIXME: that's ugly
547 skipEmptyLines.__processLine__ = lambda line: (line.strip() and line) or None
548
549 # Special preprocessor sorting the list of strings (whitespace separated)
550 # that follow a signature on a single line
551
552
553 class LineSorter(FilePreprocessor):
554 def __init__(self, signature):
555 self.signature = signature
556 self.siglen = len(signature)
557
558 def __processLine__(self, line):
559 pos = line.find(self.signature)
560 if pos >= 0:
561 line = line[:(pos + self.siglen)]
562 lst = line[(pos + self.siglen):].split()
563 lst.sort()
564 line += " ".join(lst)
565 return line
566
567
568 # Preprocessors for GaudiExamples
569 normalizeExamples = maskPointers + normalizeDate
570 for w, o, r in [
571 # ("TIMER.TIMER",r"[0-9]", "0"), # Normalize time output
572 ("TIMER.TIMER", r"\s+[+-]?[0-9]+[0-9.]*", " 0"), # Normalize time output
573 ("release all pending", r"^.*/([^/]*:.*)", r"\1"),
574 ("0x########", r"\[.*/([^/]*.*)\]", r"[\1]"),
575 ("^#.*file", r"file '.*[/\\]([^/\\]*)$", r"file '\1"),
576 ("^JobOptionsSvc.*options successfully read in from",
577 r"read in from .*[/\\]([^/\\]*)$",
578 r"file \1"), # normalize path to options
579 # Normalize UUID, except those ending with all 0s (i.e. the class IDs)
580 (None,
581 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}",
582 "00000000-0000-0000-0000-000000000000"),
583 # Absorb a change in ServiceLocatorHelper
584 ("ServiceLocatorHelper::", "ServiceLocatorHelper::(create|locate)Service",
585 "ServiceLocatorHelper::service"),
586 # Remove the leading 0 in Windows' exponential format
587 (None, r"e([-+])0([0-9][0-9])", r"e\1\2"),
588 # Output line changed in Gaudi v24
589 (None, r'Service reference count check:',
590 r'Looping over all active services...'),
591 # Change of property name in Algorithm (GAUDI-1030)
592 (None, r"Property(.*)'ErrorCount':", r"Property\1'ErrorCounter':"),
593 ]: # [ ("TIMER.TIMER","[0-9]+[0-9.]*", "") ]
594 normalizeExamples += RegexpReplacer(o, r, w)
595
596 lineSkipper = LineSkipper(
597 [
598 "//GP:",
599 "JobOptionsSvc INFO # ",
600 "JobOptionsSvc WARNING # ",
601 "Time User",
602 "Welcome to",
603 "This machine has a speed",
604 "TIME:",
605 "running on",
606 "ToolSvc.Sequenc... INFO",
607 "DataListenerSvc INFO XML written to file:",
608 "[INFO]",
609 "[WARNING]",
610 "DEBUG No writable file catalog found which contains FID:",
611 "0 local", # hack for ErrorLogExample
612 "DEBUG Service base class initialized successfully", # changed between v20 and v21
613 "DEBUG Incident timing:", # introduced with patch #3487
614 # changed the level of the message from INFO to DEBUG
615 "INFO 'CnvServices':[",
616 # The signal handler complains about SIGXCPU not defined on some platforms
617 'SIGXCPU',
618 # FIXME: special lines printed in GaudiHive
619 'EventLoopMgr SUCCESS Event Number = ',
620 'EventLoopMgr SUCCESS ---> Loop Finished',
621 ],
622 regexps=[
623 r"^JobOptionsSvc INFO *$",
624 r"^#", # Ignore python comments
625 # skip the message reporting the version of the root file
626 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
627 # hack for ErrorLogExample
628 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
629 # hack for ErrorLogExample
630 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
631 r"File '.*.xml' does not exist",
632 r"INFO Refer to dataset .* by its file ID:",
633 r"INFO Referring to dataset .* by its file ID:",
634 r"INFO Disconnect from dataset",
635 r"INFO Disconnected from dataset",
636 r"INFO Disconnected data IO:",
637 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
638 # I want to ignore the header of the unchecked StatusCode report
639 r"^StatusCodeSvc.*listing all unchecked return codes:",
640 r"^StatusCodeSvc\s*INFO\s*$",
641 r"Num\s*\|\s*Function\s*\|\s*Source Library",
642 r"^[-+]*\s*$",
643 # Hide the fake error message coming from POOL/ROOT (ROOT 5.21)
644 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
645 # Hide unchecked StatusCodes from dictionaries
646 r"^ +[0-9]+ \|.*ROOT",
647 r"^ +[0-9]+ \|.*\|.*Dict",
648 # Hide success StatusCodeSvc message
649 r"StatusCodeSvc.*all StatusCode instances where checked",
650 # Hide EventLoopMgr total timing report
651 r"EventLoopMgr.*---> Loop Finished",
652 # Remove ROOT TTree summary table, which changes from one version to the other
653 r"^\*.*\*$",
654 # Remove Histos Summaries
655 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
656 r"^ \|",
657 r"^ ID=",
658 ])
659 if ROOT6WorkAroundEnabled('ReadRootmapCheck'):
660 # FIXME: (MCl) Hide warnings from new rootmap sanity check until we can fix them
661 lineSkipper += LineSkipper(regexps=[
662 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
663 ])
664
665 normalizeExamples = (lineSkipper + normalizeExamples + skipEmptyLines +
666 normalizeEOL + LineSorter("Services to release : "))
667
668
669 class ReferenceFileValidator:
670 def __init__(self, reffile, cause, result_key, preproc=normalizeExamples):
671 self.reffile = os.path.expandvars(reffile)
672 self.cause = cause
673 self.result_key = result_key
674 self.preproc = preproc
675
676 def __call__(self, stdout, result):
677 causes = []
678 if os.path.isfile(self.reffile):
679 orig = open(self.reffile).xreadlines()
680 if self.preproc:
681 orig = self.preproc(orig)
682 else:
683 orig = []
684
685 new = stdout.splitlines()
686 if self.preproc:
687 new = self.preproc(new)
688 #open(self.reffile + ".test","w").writelines(new)
689 diffs = difflib.ndiff(orig, new, charjunk=difflib.IS_CHARACTER_JUNK)
690 filterdiffs = map(lambda x: x.strip(),
691 filter(lambda x: x[0] != " ", diffs))
692 #filterdiffs = [x.strip() for x in diffs]
693 if filterdiffs:
694 result[self.result_key] = result.Quote("\n".join(filterdiffs))
695 result[self.result_key] += result.Quote("""
696 Legend:
697 -) reference file
698 +) standard output of the test""")
699 causes.append(self.cause)
700
701 return causes
702
703
704
707
708
709 def findReferenceBlock(reference,
710 stdout,
711 result,
712 causes,
713 signature_offset=0,
714 signature=None,
715 id=None):
716 """
717 Given a block of text, tries to find it in the output.
718 The block had to be identified by a signature line. By default, the first
719 line is used as signature, or the line pointed to by signature_offset. If
720 signature_offset points outside the block, a signature line can be passed as
721 signature argument. Note: if 'signature' is None (the default), a negative
722 signature_offset is interpreted as index in a list (e.g. -1 means the last
723 line), otherwise the it is interpreted as the number of lines before the
724 first one of the block the signature must appear.
725 The parameter 'id' allow to distinguish between different calls to this
726 function in the same validation code.
727 """
728 # split reference file, sanitize EOLs and remove empty lines
729 reflines = filter(None, map(lambda s: s.rstrip(), reference.splitlines()))
730 if not reflines:
731 raise RuntimeError("Empty (or null) reference")
732 # the same on standard output
733 outlines = filter(None, map(lambda s: s.rstrip(), stdout.splitlines()))
734
735 res_field = "GaudiTest.RefBlock"
736 if id:
737 res_field += "_%s" % id
738
739 if signature is None:
740 if signature_offset < 0:
741 signature_offset = len(reference) + signature_offset
742 signature = reflines[signature_offset]
743 # find the reference block in the output file
744 try:
745 pos = outlines.index(signature)
746 outlines = outlines[pos - signature_offset:pos + len(reflines) -
747 signature_offset]
748 if reflines != outlines:
749 msg = "standard output"
750 # I do not want 2 messages in causes if teh function is called twice
751 if not msg in causes:
752 causes.append(msg)
753 result[res_field + ".observed"] = result.Quote("\n".join(outlines))
754 except ValueError:
755 causes.append("missing signature")
756 result[res_field + ".signature"] = result.Quote(signature)
757 if len(reflines) > 1 or signature != reflines[0]:
758 result[res_field + ".expected"] = result.Quote("\n".join(reflines))
759
760 return causes
761
762
763 def countErrorLines(expected={'ERROR': 0, 'FATAL': 0}, **kwargs):
764 """
765 Count the number of messages with required severity (by default ERROR and FATAL)
766 and check if their numbers match the expected ones (0 by default).
767 The dictionary "expected" can be used to tune the number of errors and fatals
768 allowed, or to limit the number of expected warnings etc.
769 """
770 stdout = kwargs["stdout"]
771 result = kwargs["result"]
772 causes = kwargs["causes"]
773
774 # prepare the dictionary to record the extracted lines
775 errors = {}
776 for sev in expected:
777 errors[sev] = []
778
779 outlines = stdout.splitlines()
780 from math import log10
781 fmt = "%%%dd - %%s" % (int(log10(len(outlines)) + 1))
782
783 linecount = 0
784 for l in outlines:
785 linecount += 1
786 words = l.split()
787 if len(words) >= 2 and words[1] in errors:
788 errors[words[1]].append(fmt % (linecount, l.rstrip()))
789
790 for e in errors:
791 if len(errors[e]) != expected[e]:
792 causes.append('%s(%d)' % (e, len(errors[e])))
793 result["GaudiTest.lines.%s" % e] = result.Quote('\n'.join(
794 errors[e]))
795 result["GaudiTest.lines.%s.expected#" % e] = result.Quote(
796 str(expected[e]))
797
798 return causes
799
800
801 def _parseTTreeSummary(lines, pos):
802 """
803 Parse the TTree summary table in lines, starting from pos.
804 Returns a tuple with the dictionary with the digested informations and the
805 position of the first line after the summary.
806 """
807 result = {}
808 i = pos + 1 # first line is a sequence of '*'
809 count = len(lines)
810
811 def splitcols(l):
812 return [f.strip() for f in l.strip("*\n").split(':', 2)]
813
814 def parseblock(ll):
815 r = {}
816 cols = splitcols(ll[0])
817 r["Name"], r["Title"] = cols[1:]
818
819 cols = splitcols(ll[1])
820 r["Entries"] = int(cols[1])
821
822 sizes = cols[2].split()
823 r["Total size"] = int(sizes[2])
824 if sizes[-1] == "memory":
825 r["File size"] = 0
826 else:
827 r["File size"] = int(sizes[-1])
828
829 cols = splitcols(ll[2])
830 sizes = cols[2].split()
831 if cols[0] == "Baskets":
832 r["Baskets"] = int(cols[1])
833 r["Basket size"] = int(sizes[2])
834 r["Compression"] = float(sizes[-1])
835 return r
836
837 if i < (count - 3) and lines[i].startswith("*Tree"):
838 result = parseblock(lines[i:i + 3])
839 result["Branches"] = {}
840 i += 4
841 while i < (count - 3) and lines[i].startswith("*Br"):
842 if i < (count - 2) and lines[i].startswith("*Branch "):
843 # skip branch header
844 i += 3
845 continue
846 branch = parseblock(lines[i:i + 3])
847 result["Branches"][branch["Name"]] = branch
848 i += 4
849
850 return (result, i)
851
852
853 def findTTreeSummaries(stdout):
854 """
855 Scan stdout to find ROOT TTree summaries and digest them.
856 """
857 stars = re.compile(r"^\*+$")
858 outlines = stdout.splitlines()
859 nlines = len(outlines)
860 trees = {}
861
862 i = 0
863 while i < nlines: # loop over the output
864 # look for
865 while i < nlines and not stars.match(outlines[i]):
866 i += 1
867 if i < nlines:
868 tree, i = _parseTTreeSummary(outlines, i)
869 if tree:
870 trees[tree["Name"]] = tree
871
872 return trees
873
874
875 def cmpTreesDicts(reference, to_check, ignore=None):
876 """
877 Check that all the keys in reference are in to_check too, with the same value.
878 If the value is a dict, the function is called recursively. to_check can
879 contain more keys than reference, that will not be tested.
880 The function returns at the first difference found.
881 """
882 fail_keys = []
883 # filter the keys in the reference dictionary
884 if ignore:
885 ignore_re = re.compile(ignore)
886 keys = [key for key in reference if not ignore_re.match(key)]
887 else:
888 keys = reference.keys()
889 # loop over the keys (not ignored) in the reference dictionary
890 for k in keys:
891 if k in to_check: # the key must be in the dictionary to_check
892 if (type(reference[k]) is dict) and (type(to_check[k]) is dict):
893 # if both reference and to_check values are dictionaries, recurse
894 failed = fail_keys = cmpTreesDicts(reference[k], to_check[k],
895 ignore)
896 else:
897 # compare the two values
898 failed = to_check[k] != reference[k]
899 else: # handle missing keys in the dictionary to check (i.e. failure)
900 to_check[k] = None
901 failed = True
902 if failed:
903 fail_keys.insert(0, k)
904 break # exit from the loop at the first failure
905 return fail_keys # return the list of keys bringing to the different values
906
907
908 def getCmpFailingValues(reference, to_check, fail_path):
909 c = to_check
910 r = reference
911 for k in fail_path:
912 c = c.get(k, None)
913 r = r.get(k, None)
914 if c is None or r is None:
915 break # one of the dictionaries is not deep enough
916 return (fail_path, r, c)
917
918
919 # signature of the print-out of the histograms
920 h_count_re = re.compile(r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
921
922
923 def parseHistosSummary(lines, pos):
924 """
925 Extract the histograms infos from the lines starting at pos.
926 Returns the position of the first line after the summary block.
927 """
928 global h_count_re
929 h_table_head = re.compile(
930 r'SUCCESS\s+(1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"'
931 )
932 h_short_summ = re.compile(r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
933
934 nlines = len(lines)
935
936 # decode header
937 m = h_count_re.search(lines[pos])
938 name = m.group(1).strip()
939 total = int(m.group(2))
940 header = {}
941 for k, v in [x.split("=") for x in m.group(3).split()]:
942 header[k] = int(v)
943 pos += 1
944 header["Total"] = total
945
946 summ = {}
947 while pos < nlines:
948 m = h_table_head.search(lines[pos])
949 if m:
950 t, d = m.groups(1) # type and directory
951 t = t.replace(" profile", "Prof")
952 pos += 1
953 if pos < nlines:
954 l = lines[pos]
955 else:
956 l = ""
957 cont = {}
958 if l.startswith(" | ID"):
959 # table format
960 titles = [x.strip() for x in l.split("|")][1:]
961 pos += 1
962 while pos < nlines and lines[pos].startswith(" |"):
963 l = lines[pos]
964 values = [x.strip() for x in l.split("|")][1:]
965 hcont = {}
966 for i in range(len(titles)):
967 hcont[titles[i]] = values[i]
968 cont[hcont["ID"]] = hcont
969 pos += 1
970 elif l.startswith(" ID="):
971 while pos < nlines and lines[pos].startswith(" ID="):
972 values = [
973 x.strip()
974 for x in h_short_summ.search(lines[pos]).groups()
975 ]
976 cont[values[0]] = values
977 pos += 1
978 else: # not interpreted
979 raise RuntimeError(
980 "Cannot understand line %d: '%s'" % (pos, l))
981 if not d in summ:
982 summ[d] = {}
983 summ[d][t] = cont
984 summ[d]["header"] = header
985 else:
986 break
987 if not summ:
988 # If the full table is not present, we use only the header
989 summ[name] = {"header": header}
990 return summ, pos
991
992
993 def findHistosSummaries(stdout):
994 """
995 Scan stdout to find ROOT TTree summaries and digest them.
996 """
997 outlines = stdout.splitlines()
998 nlines = len(outlines) - 1
999 summaries = {}
1000 global h_count_re
1001
1002 pos = 0
1003 while pos < nlines:
1004 summ = {}
1005 # find first line of block:
1006 match = h_count_re.search(outlines[pos])
1007 while pos < nlines and not match:
1008 pos += 1
1009 match = h_count_re.search(outlines[pos])
1010 if match:
1011 summ, pos = parseHistosSummary(outlines, pos)
1012 summaries.update(summ)
1013 return summaries
1014
1015
1016 class GaudiFilterExecutable(qm.executable.Filter):
1017 def __init__(self, input, timeout=-1):
1018 """Create a new 'Filter'.
1019
1020 'input' -- The string containing the input to provide to the
1021 child process.
1022
1023 'timeout' -- As for 'TimeoutExecutable.__init__'."""
1024
1025 super(GaudiFilterExecutable, self).__init__(input, timeout)
1026 self.__input = input
1027 self.__timeout = timeout
1028 self.stack_trace_file = None
1029 # Temporary file to pass the stack trace from one process to the other
1030 # The file must be closed and reopened when needed to avoid conflicts
1031 # between the processes
1032 tmpf = tempfile.mkstemp()
1033 os.close(tmpf[0])
1034 self.stack_trace_file = tmpf[1] # remember only the name
1035
1036 def __UseSeparateProcessGroupForChild(self):
1037 """Copied from TimeoutExecutable to allow the re-implementation of
1038 _HandleChild.
1039 """
1040 if sys.platform == "win32":
1041 # In Windows 2000 (or later), we should use "jobs" by
1042 # analogy with UNIX process groups. However, that
1043 # functionality is not (yet) provided by the Python Win32
1044 # extensions.
1045 return 0
1046
1047 return self.__timeout >= 0 or self.__timeout == -2
1048
1049
1051
1052 def _HandleChild(self):
1053 """Code copied from both FilterExecutable and TimeoutExecutable.
1054 """
1055 # Close the pipe ends that we do not need.
1056 if self._stdin_pipe:
1057 self._ClosePipeEnd(self._stdin_pipe[0])
1058 if self._stdout_pipe:
1059 self._ClosePipeEnd(self._stdout_pipe[1])
1060 if self._stderr_pipe:
1061 self._ClosePipeEnd(self._stderr_pipe[1])
1062
1063 # The pipes created by 'RedirectedExecutable' must be closed
1064 # before the monitor process (created by 'TimeoutExecutable')
1065 # is created. Otherwise, if the child process dies, 'select'
1066 # in the parent will not return if the monitor process may
1067 # still have one of the file descriptors open.
1068
1069 super(qm.executable.TimeoutExecutable, self)._HandleChild()
1070
1071 if self.__UseSeparateProcessGroupForChild():
1072 # Put the child into its own process group. This step is
1073 # performed in both the parent and the child; therefore both
1074 # processes can safely assume that the creation of the process
1075 # group has taken place.
1076 child_pid = self._GetChildPID()
1077 try:
1078 os.setpgid(child_pid, child_pid)
1079 except:
1080 # The call to setpgid may fail if the child has exited,
1081 # or has already called 'exec'. In that case, we are
1082 # guaranteed that the child has already put itself in the
1083 # desired process group.
1084 pass
1085 # Create the monitoring process.
1086 #
1087 # If the monitoring process is in parent's process group and
1088 # kills the child after waitpid has returned in the parent, we
1089 # may end up trying to kill a process group other than the one
1090 # that we intend to kill. Therefore, we put the monitoring
1091 # process in the same process group as the child; that ensures
1092 # that the process group will persist until the monitoring
1093 # process kills it.
1094 self.__monitor_pid = os.fork()
1095 if self.__monitor_pid != 0:
1096 # Make sure that the monitoring process is placed into the
1097 # child's process group before the parent process calls
1098 # 'waitpid'. In this way, we are guaranteed that the process
1099 # group as the child
1100 os.setpgid(self.__monitor_pid, child_pid)
1101 else:
1102 # Put the monitoring process into the child's process
1103 # group. We know the process group still exists at
1104 # this point because either (a) we are in the process
1105 # group, or (b) the parent has not yet called waitpid.
1106 os.setpgid(0, child_pid)
1107
1108 # Close all open file descriptors. They are not needed
1109 # in the monitor process. Furthermore, when the parent
1110 # closes the write end of the stdin pipe to the child,
1111 # we do not want the pipe to remain open; leaving the
1112 # pipe open in the monitor process might cause the child
1113 # to block waiting for additional input.
1114 try:
1115 max_fds = os.sysconf("SC_OPEN_MAX")
1116 except:
1117 max_fds = 256
1118 for fd in xrange(max_fds):
1119 try:
1120 os.close(fd)
1121 except:
1122 pass
1123 try:
1124 if self.__timeout >= 0:
1125 # Give the child time to run.
1126 time.sleep(self.__timeout)
1127
1129 if sys.platform == "linux2": # we should be have /proc and gdb
1130 cmd = [
1131 "gdb",
1132 os.path.join("/proc", str(child_pid), "exe"),
1133 str(child_pid), "-batch", "-n", "-x",
1134 "'%s'" % os.path.join(
1135 os.path.dirname(__file__),
1136 "stack-trace.gdb")
1137 ]
1138 # FIXME: I wanted to use subprocess.Popen, but it doesn't want to work
1139 # in this context.
1140 o = os.popen(" ".join(cmd)).read()
1141 open(self.stack_trace_file, "w").write(o)
1142
1143
1144 # Kill all processes in the child process group.
1145 os.kill(0, signal.SIGKILL)
1146 else:
1147 # This call to select will never terminate.
1148 select.select([], [], [])
1149 finally:
1150 # Exit. This code is in a finally clause so that
1151 # we are guaranteed to get here no matter what.
1152 os._exit(0)
1153 elif self.__timeout >= 0 and sys.platform == "win32":
1154 # Create a monitoring thread.
1155 self.__monitor_thread = Thread(target=self.__Monitor)
1156 self.__monitor_thread.start()
1157
1158 if sys.platform == "win32":
1159
1160 def __Monitor(self):
1161 """Code copied from FilterExecutable.
1162 Kill the child if the timeout expires.
1163
1164 This function is run in the monitoring thread."""
1165
1166 # The timeout may be expressed as a floating-point value
1167 # on UNIX, but it must be an integer number of
1168 # milliseconds when passed to WaitForSingleObject.
1169 timeout = int(self.__timeout * 1000)
1170 # Wait for the child process to terminate or for the
1171 # timer to expire.
1172 result = win32event.WaitForSingleObject(self._GetChildPID(),
1173 timeout)
1174 # If the timeout occurred, kill the child process.
1175 if result == win32con.WAIT_TIMEOUT:
1176 self.Kill()
1177
1178
1179
1182
1183
1184 class GaudiExeTest(ExecTestBase):
1185 """Standard Gaudi test.
1186 """
1187 arguments = [
1188 qm.fields.TextField(
1189 name="program",
1190 title="Program",
1191 not_empty_text=1,
1192 description="""The path to the program.
1193
1194 This field indicates the path to the program. If it is not
1195 an absolute path, the value of the 'PATH' environment
1196 variable will be used to search for the program.
1197 If not specified, $GAUDIEXE or Gaudi.exe are used.
1198 """),
1199 qm.fields.SetField(
1200 qm.fields.TextField(
1201 name="args",
1202 title="Argument List",
1203 description="""The command-line arguments.
1204
1205 If this field is left blank, the program is run without any
1206 arguments.
1207
1208 Use this field to specify the option files.
1209
1210 An implicit 0th argument (the path to the program) is added
1211 automatically.""")),
1212 qm.fields.TextField(
1213 name="options",
1214 title="Options",
1215 description="""Options to be passed to the application.
1216
1217 This field allows to pass a list of options to the main program
1218 without the need of a separate option file.
1219
1220 The content of the field is written to a temporary file which name
1221 is passed the the application as last argument (appended to the
1222 field "Argument List".
1223 """,
1224 verbatim="true",
1225 multiline="true",
1226 default_value=""),
1227 qm.fields.TextField(
1228 name="workdir",
1229 title="Working Directory",
1230 description="""Path to the working directory.
1231
1232 If this field is left blank, the program will be run from the qmtest
1233 directory, otherwise from the directory specified.""",
1234 default_value=""),
1235 qm.fields.TextField(
1236 name="reference",
1237 title="Reference Output",
1238 description="""Path to the file containing the reference output.
1239
1240 If this field is left blank, any standard output will be considered
1241 valid.
1242
1243 If the reference file is specified, any output on standard error is
1244 ignored."""),
1245 qm.fields.TextField(
1246 name="error_reference",
1247 title="Reference for standard error",
1248 description=
1249 """Path to the file containing the reference for the standard error.
1250
1251 If this field is left blank, any standard output will be considered
1252 valid.
1253
1254 If the reference file is specified, any output on standard error is
1255 ignored."""),
1256 qm.fields.SetField(
1257 qm.fields.TextField(
1258 name="unsupported_platforms",
1259 title="Unsupported Platforms",
1260 description="""Platform on which the test must not be run.
1261
1262 List of regular expressions identifying the platforms on which the
1263 test is not run and the result is set to UNTESTED.""")),
1264 qm.fields.TextField(
1265 name="validator",
1266 title="Validator",
1267 description="""Function to validate the output of the test.
1268
1269 If defined, the function is used to validate the products of the
1270 test.
1271 The function is called passing as arguments:
1272 self: the test class instance
1273 stdout: the standard output of the executed test
1274 stderr: the standard error of the executed test
1275 result: the Result objects to fill with messages
1276 The function must return a list of causes for the failure.
1277 If specified, overrides standard output, standard error and
1278 reference files.
1279 """,
1280 verbatim="true",
1281 multiline="true",
1282 default_value=""),
1283 qm.fields.BooleanField(
1284 name="use_temp_dir",
1285 title="Use temporary directory",
1286 description="""Use temporary directory.
1287
1288 If set to true, use a temporary directory as working directory.
1289 """,
1290 default_value="false"),
1291 qm.fields.IntegerField(
1292 name="signal",
1293 title="Expected signal",
1294 description="""Expect termination by signal.""",
1295 default_value=None),
1296 ]
1297
1298 def PlatformIsNotSupported(self, context, result):
1299 platform = self.GetPlatform()
1300 unsupported = [
1301 re.compile(x)
1302 for x in [str(y).strip() for y in self.unsupported_platforms] if x
1303 ]
1304 for p_re in unsupported:
1305 if p_re.search(platform):
1306 result.SetOutcome(result.UNTESTED)
1307 result[result.CAUSE] = 'Platform not supported.'
1308 return True
1309 return False
1310
1311 def GetPlatform(self):
1312 """
1313 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1314 """
1315 arch = "None"
1316 # check architecture name
1317 if "CMTCONFIG" in os.environ:
1318 arch = os.environ["CMTCONFIG"]
1319 elif "SCRAM_ARCH" in os.environ:
1320 arch = os.environ["SCRAM_ARCH"]
1321 return arch
1322
1323 def isWinPlatform(self):
1324 """
1325 Return True if the current platform is Windows.
1326
1327 This function was needed because of the change in the CMTCONFIG format,
1328 from win32_vc71_dbg to i686-winxp-vc9-dbg.
1329 """
1330 platform = self.GetPlatform()
1331 return "winxp" in platform or platform.startswith("win")
1332
1333 def _expandReferenceFileName(self, reffile):
1334 # if no file is passed, do nothing
1335 if not reffile:
1336 return ""
1337
1338 # function to split an extension in constituents parts
1339 def platformSplit(p):
1340 return set(p.split('-' in p and '-' or '_'))
1341
1342 reference = os.path.normpath(os.path.expandvars(reffile))
1343 # old-style platform-specific reference name
1344 spec_ref = reference[:-3] + self.GetPlatform()[0:3] + reference[-3:]
1345 if os.path.isfile(spec_ref):
1346 reference = spec_ref
1347 else: # look for new-style platform specific reference files:
1348 # get all the files whose name start with the reference filename
1349 dirname, basename = os.path.split(reference)
1350 if not dirname:
1351 dirname = '.'
1352 head = basename + "."
1353 head_len = len(head)
1354 platform = platformSplit(self.GetPlatform())
1355 if 'do0' in platform:
1356 platform.add('dbg')
1357 candidates = []
1358 for f in os.listdir(dirname):
1359 if f.startswith(head):
1360 req_plat = platformSplit(f[head_len:])
1361 if platform.issuperset(req_plat):
1362 candidates.append((len(req_plat), f))
1363 if candidates: # take the one with highest matching
1364 # FIXME: it is not possible to say if x86_64-slc5-gcc43-dbg
1365 # has to use ref.x86_64-gcc43 or ref.slc5-dbg
1366 candidates.sort()
1367 reference = os.path.join(dirname, candidates[-1][1])
1368 return reference
1369
1370 def CheckTTreesSummaries(self,
1371 stdout,
1372 result,
1373 causes,
1374 trees_dict=None,
1375 ignore=r"Basket|.*size|Compression"):
1376 """
1377 Compare the TTree summaries in stdout with the ones in trees_dict or in
1378 the reference file. By default ignore the size, compression and basket
1379 fields.
1380 The presence of TTree summaries when none is expected is not a failure.
1381 """
1382 if trees_dict is None:
1383 reference = self._expandReferenceFileName(self.reference)
1384 # call the validator if the file exists
1385 if reference and os.path.isfile(reference):
1386 trees_dict = findTTreeSummaries(open(reference).read())
1387 else:
1388 trees_dict = {}
1389
1390 from pprint import PrettyPrinter
1391 pp = PrettyPrinter()
1392 if trees_dict:
1393 result["GaudiTest.TTrees.expected"] = result.Quote(
1394 pp.pformat(trees_dict))
1395 if ignore:
1396 result["GaudiTest.TTrees.ignore"] = result.Quote(ignore)
1397
1398 trees = findTTreeSummaries(stdout)
1399 failed = cmpTreesDicts(trees_dict, trees, ignore)
1400 if failed:
1401 causes.append("trees summaries")
1402 msg = "%s: %s != %s" % getCmpFailingValues(trees_dict, trees,
1403 failed)
1404 result["GaudiTest.TTrees.failure_on"] = result.Quote(msg)
1405 result["GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
1406
1407 return causes
1408
1409 def CheckHistosSummaries(self,
1410 stdout,
1411 result,
1412 causes,
1413 dict=None,
1414 ignore=None):
1415 """
1416 Compare the TTree summaries in stdout with the ones in trees_dict or in
1417 the reference file. By default ignore the size, compression and basket
1418 fields.
1419 The presence of TTree summaries when none is expected is not a failure.
1420 """
1421 if dict is None:
1422 reference = self._expandReferenceFileName(self.reference)
1423 # call the validator if the file exists
1424 if reference and os.path.isfile(reference):
1425 dict = findHistosSummaries(open(reference).read())
1426 else:
1427 dict = {}
1428
1429 from pprint import PrettyPrinter
1430 pp = PrettyPrinter()
1431 if dict:
1432 result["GaudiTest.Histos.expected"] = result.Quote(
1433 pp.pformat(dict))
1434 if ignore:
1435 result["GaudiTest.Histos.ignore"] = result.Quote(ignore)
1436
1437 histos = findHistosSummaries(stdout)
1438 failed = cmpTreesDicts(dict, histos, ignore)
1439 if failed:
1440 causes.append("histos summaries")
1441 msg = "%s: %s != %s" % getCmpFailingValues(dict, histos, failed)
1442 result["GaudiTest.Histos.failure_on"] = result.Quote(msg)
1443 result["GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
1444
1445 return causes
1446
1447 def ValidateWithReference(self,
1448 stdout,
1449 stderr,
1450 result,
1451 causes,
1452 preproc=None):
1453 """
1454 Default validation action: compare standard output and error to the
1455 reference files.
1456 """
1457 # set the default output preprocessor
1458 if preproc is None:
1459 preproc = normalizeExamples
1460 # check standard output
1461 reference = self._expandReferenceFileName(self.reference)
1462 # call the validator if the file exists
1463 if reference and os.path.isfile(reference):
1464 result["GaudiTest.output_reference"] = reference
1465 causes += ReferenceFileValidator(
1466 reference,
1467 "standard output",
1468 "GaudiTest.output_diff",
1469 preproc=preproc)(stdout, result)
1470
1471 # Compare TTree summaries
1472 causes = self.CheckTTreesSummaries(stdout, result, causes)
1473 causes = self.CheckHistosSummaries(stdout, result, causes)
1474
1475 if causes: # Write a new reference file for stdout
1476 try:
1477 newref = open(reference + ".new", "w")
1478 # sanitize newlines
1479 for l in stdout.splitlines():
1480 newref.write(l.rstrip() + '\n')
1481 del newref # flush and close
1482 except IOError:
1483 # Ignore IO errors when trying to update reference files
1484 # because we may be in a read-only filesystem
1485 pass
1486
1487 # check standard error
1488 reference = self._expandReferenceFileName(self.error_reference)
1489 # call the validator if we have a file to use
1490 if reference and os.path.isfile(reference):
1491 result["GaudiTest.error_reference"] = reference
1492 newcauses = ReferenceFileValidator(
1493 reference,
1494 "standard error",
1495 "GaudiTest.error_diff",
1496 preproc=preproc)(stderr, result)
1497 causes += newcauses
1498 if newcauses: # Write a new reference file for stdedd
1499 newref = open(reference + ".new", "w")
1500 # sanitize newlines
1501 for l in stderr.splitlines():
1502 newref.write(l.rstrip() + '\n')
1503 del newref # flush and close
1504 else:
1505 causes += BasicOutputValidator(self.stderr, "standard error",
1506 "ExecTest.expected_stderr")(stderr,
1507 result)
1508
1509 return causes
1510
1511 def ValidateOutput(self, stdout, stderr, result):
1512 causes = []
1513 # if the test definition contains a custom validator, use it
1514 if self.validator.strip() != "":
1515
1516 class CallWrapper(object):
1517 """
1518 Small wrapper class to dynamically bind some default arguments
1519 to a callable.
1520 """
1521
1522 def __init__(self, callable, extra_args={}):
1523 self.callable = callable
1524 self.extra_args = extra_args
1525 # get the list of names of positional arguments
1526 from inspect import getargspec
1527 self.args_order = getargspec(callable)[0]
1528 # Remove "self" from the list of positional arguments
1529 # since it is added automatically
1530 if self.args_order[0] == "self":
1531 del self.args_order[0]
1532
1533 def __call__(self, *args, **kwargs):
1534 # Check which positional arguments are used
1535 positional = self.args_order[:len(args)]
1536
1537 kwargs = dict(kwargs) # copy the arguments dictionary
1538 for a in self.extra_args:
1539 # use "extra_args" for the arguments not specified as
1540 # positional or keyword
1541 if a not in positional and a not in kwargs:
1542 kwargs[a] = self.extra_args[a]
1543 return self.callable(*args, **kwargs)
1544
1545 # local names to be exposed in the script
1546 exported_symbols = {
1547 "self":
1548 self,
1549 "stdout":
1550 stdout,
1551 "stderr":
1552 stderr,
1553 "result":
1554 result,
1555 "causes":
1556 causes,
1557 "findReferenceBlock":
1558 CallWrapper(findReferenceBlock, {
1559 "stdout": stdout,
1560 "result": result,
1561 "causes": causes
1562 }),
1563 "validateWithReference":
1564 CallWrapper(
1565 self.ValidateWithReference, {
1566 "stdout": stdout,
1567 "stderr": stderr,
1568 "result": result,
1569 "causes": causes
1570 }),
1571 "countErrorLines":
1572 CallWrapper(countErrorLines, {
1573 "stdout": stdout,
1574 "result": result,
1575 "causes": causes
1576 }),
1577 "checkTTreesSummaries":
1578 CallWrapper(self.CheckTTreesSummaries, {
1579 "stdout": stdout,
1580 "result": result,
1581 "causes": causes
1582 }),
1583 "checkHistosSummaries":
1584 CallWrapper(self.CheckHistosSummaries, {
1585 "stdout": stdout,
1586 "result": result,
1587 "causes": causes
1588 }),
1589 }
1590 exec (self.validator in globals(), exported_symbols)
1591 else:
1592 self.ValidateWithReference(stdout, stderr, result, causes)
1593
1594 return causes
1595
1596 def DumpEnvironment(self, result):
1597 """
1598 Add the content of the environment to the result object.
1599
1600 Copied from the QMTest class of COOL.
1601 """
1602 vars = os.environ.keys()
1603 vars.sort()
1604 result['GaudiTest.environment'] = \
1605 result.Quote(
1606 '\n'.join(["%s=%s" % (v, os.environ[v]) for v in vars]))
1607
1608 def Run(self, context, result):
1609 """Run the test.
1610
1611 'context' -- A 'Context' giving run-time parameters to the
1612 test.
1613
1614 'result' -- A 'Result' object. The outcome will be
1615 'Result.PASS' when this method is called. The 'result' may be
1616 modified by this method to indicate outcomes other than
1617 'Result.PASS' or to add annotations."""
1618
1619 # Check if the platform is supported
1620 if self.PlatformIsNotSupported(context, result):
1621 return
1622
1623 # Prepare program name and arguments (expanding variables, and converting to absolute)
1624 if self.program:
1625 prog = rationalizepath(self.program)
1626 elif "GAUDIEXE" in os.environ:
1627 prog = os.environ["GAUDIEXE"]
1628 else:
1629 prog = "Gaudi.exe"
1630 self.program = prog
1631
1632 dummy, prog_ext = os.path.splitext(prog)
1633 if prog_ext not in [".exe", ".py", ".bat"] and self.isWinPlatform():
1634 prog += ".exe"
1635 prog_ext = ".exe"
1636
1637 prog = which(prog) or prog
1638
1639 # Convert paths to absolute paths in arguments and reference files
1640 args = map(rationalizepath, self.args)
1641 self.reference = rationalizepath(self.reference)
1642 self.error_reference = rationalizepath(self.error_reference)
1643
1644 # check if the user provided inline options
1645 tmpfile = None
1646 if self.options.strip():
1647 ext = ".opts"
1648 if re.search(
1649 r"from\s+Gaudi.Configuration\s+import\s+\*|from\s+Configurables\s+import",
1650 self.options):
1651 ext = ".py"
1652 tmpfile = TempFile(ext)
1653 tmpfile.writelines("\n".join(self.options.splitlines()))
1654 tmpfile.flush()
1655 args.append(tmpfile.name)
1656 result["GaudiTest.options"] = result.Quote(self.options)
1657
1658 # if the program is a python file, execute it through python
1659 if prog_ext == ".py":
1660 args.insert(0, prog)
1661 if self.isWinPlatform():
1662 prog = which("python.exe") or "python.exe"
1663 else:
1664 prog = which("python") or "python"
1665
1666 # Change to the working directory if specified or to the default temporary
1667 origdir = os.getcwd()
1668 if self.workdir:
1669 os.chdir(str(os.path.normpath(os.path.expandvars(self.workdir))))
1670 elif self.use_temp_dir == "true":
1671 if "QMTEST_TMPDIR" in os.environ:
1672 qmtest_tmpdir = os.environ["QMTEST_TMPDIR"]
1673 if not os.path.exists(qmtest_tmpdir):
1674 os.makedirs(qmtest_tmpdir)
1675 os.chdir(qmtest_tmpdir)
1676 elif "qmtest.tmpdir" in context:
1677 os.chdir(context["qmtest.tmpdir"])
1678
1679 if "QMTEST_IGNORE_TIMEOUT" not in os.environ:
1680 self.timeout = max(self.timeout, 600)
1681 else:
1682 self.timeout = -1
1683
1684 try:
1685 # Generate eclipse.org debug launcher for the test
1686 self._CreateEclipseLaunch(
1687 prog, args, destdir=os.path.join(origdir, '.eclipse'))
1688 # Run the test
1689 self.RunProgram(prog, [prog] + args, context, result)
1690 # Record the content of the enfironment for failing tests
1691 if result.GetOutcome() not in [result.PASS]:
1692 self.DumpEnvironment(result)
1693 finally:
1694 # revert to the original directory
1695 os.chdir(origdir)
1696
1697 def RunProgram(self, program, arguments, context, result):
1698 """Run the 'program'.
1699
1700 'program' -- The path to the program to run.
1701
1702 'arguments' -- A list of the arguments to the program. This
1703 list must contain a first argument corresponding to 'argv[0]'.
1704
1705 'context' -- A 'Context' giving run-time parameters to the
1706 test.
1707
1708 'result' -- A 'Result' object. The outcome will be
1709 'Result.PASS' when this method is called. The 'result' may be
1710 modified by this method to indicate outcomes other than
1711 'Result.PASS' or to add annotations.
1712
1713 @attention: This method has been copied from command.ExecTestBase
1714 (QMTest 2.3.0) and modified to keep stdout and stderr
1715 for tests that have been terminated by a signal.
1716 (Fundamental for debugging in the Application Area)
1717 """
1718
1719 # Construct the environment.
1720 environment = self.MakeEnvironment(context)
1721 # FIXME: whithout this, we get some spurious '\x1b[?1034' in the std out on SLC6
1722 if "slc6" in environment.get('CMTCONFIG', ''):
1723 environment['TERM'] = 'dumb'
1724 # Create the executable.
1725 if self.timeout >= 0:
1726 timeout = self.timeout
1727 else:
1728 # If no timeout was specified, we sill run this process in a
1729 # separate process group and kill the entire process group
1730 # when the child is done executing. That means that
1731 # orphaned child processes created by the test will be
1732 # cleaned up.
1733 timeout = -2
1734 e = GaudiFilterExecutable(self.stdin, timeout)
1735 # Run it.
1736 exit_status = e.Run(arguments, environment, path=program)
1737 # Get the stack trace from the temporary file (if present)
1738 if e.stack_trace_file and os.path.exists(e.stack_trace_file):
1739 stack_trace = open(e.stack_trace_file).read()
1740 os.remove(e.stack_trace_file)
1741 else:
1742 stack_trace = None
1743 if stack_trace:
1744 result["ExecTest.stack_trace"] = result.Quote(stack_trace)
1745
1746 # If the process terminated normally, check the outputs.
1747 if (sys.platform == "win32" or os.WIFEXITED(exit_status)
1748 or self.signal == os.WTERMSIG(exit_status)):
1749 # There are no causes of failure yet.
1750 causes = []
1751 # The target program terminated normally. Extract the
1752 # exit code, if this test checks it.
1753 if self.exit_code is None:
1754 exit_code = None
1755 elif sys.platform == "win32":
1756 exit_code = exit_status
1757 else:
1758 exit_code = os.WEXITSTATUS(exit_status)
1759 # Get the output generated by the program.
1760 stdout = e.stdout
1761 stderr = e.stderr
1762 # Record the results.
1763 result["ExecTest.exit_code"] = str(exit_code)
1764 result["ExecTest.stdout"] = result.Quote(stdout)
1765 result["ExecTest.stderr"] = result.Quote(stderr)
1766 # Check to see if the exit code matches.
1767 if exit_code != self.exit_code:
1768 causes.append("exit_code")
1769 result["ExecTest.expected_exit_code"] \
1770 = str(self.exit_code)
1771 # Validate the output.
1772 causes += self.ValidateOutput(stdout, stderr, result)
1773 # If anything went wrong, the test failed.
1774 if causes:
1775 result.Fail("Unexpected %s." % string.join(causes, ", "))
1776 elif os.WIFSIGNALED(exit_status):
1777 # The target program terminated with a signal. Construe
1778 # that as a test failure.
1779 signal_number = str(os.WTERMSIG(exit_status))
1780 if not stack_trace:
1781 result.Fail("Program terminated by signal.")
1782 else:
1783 # The presence of stack_trace means tha we stopped the job because
1784 # of a time-out
1785 result.Fail("Exceeded time limit (%ds), terminated." % timeout)
1786 result["ExecTest.signal_number"] = signal_number
1787 result["ExecTest.stdout"] = result.Quote(e.stdout)
1788 result["ExecTest.stderr"] = result.Quote(e.stderr)
1789 if self.signal:
1790 result["ExecTest.expected_signal_number"] = str(self.signal)
1791 elif os.WIFSTOPPED(exit_status):
1792 # The target program was stopped. Construe that as a
1793 # test failure.
1794 signal_number = str(os.WSTOPSIG(exit_status))
1795 if not stack_trace:
1796 result.Fail("Program stopped by signal.")
1797 else:
1798 # The presence of stack_trace means tha we stopped the job because
1799 # of a time-out
1800 result.Fail("Exceeded time limit (%ds), stopped." % timeout)
1801 result["ExecTest.signal_number"] = signal_number
1802 result["ExecTest.stdout"] = result.Quote(e.stdout)
1803 result["ExecTest.stderr"] = result.Quote(e.stderr)
1804 else:
1805 # The target program terminated abnormally in some other
1806 # manner. (This shouldn't normally happen...)
1807 result.Fail("Program did not terminate normally.")
1808
1809 # Marco Cl.: This is a special trick to fix a "problem" with the output
1810 # of gaudi jobs when they use colors
1811 esc = '\x1b'
1812 repr_esc = '\\x1b'
1813 result["ExecTest.stdout"] = result["ExecTest.stdout"].replace(
1814 esc, repr_esc)
1815 # TODO: (MCl) improve the hack for colors in standard output
1816 # may be converting them to HTML tags
1817
1818 def _CreateEclipseLaunch(self, prog, args, destdir=None):
1819 if 'NO_ECLIPSE_LAUNCHERS' in os.environ:
1820 # do not generate eclipse launchers if the user asks so
1821 return
1822 # Find the project name used in ecplise.
1823 # The name is in a file called ".project" in one of the parent directories
1824 projbasedir = os.path.normpath(destdir)
1825 while not os.path.exists(os.path.join(projbasedir, ".project")):
1826 oldprojdir = projbasedir
1827 projbasedir = os.path.normpath(
1828 os.path.join(projbasedir, os.pardir))
1829 # FIXME: the root level is invariant when trying to go up one level,
1830 # but it must be cheched on windows
1831 if oldprojdir == projbasedir:
1832 # If we cannot find a .project, so no point in creating a .launch file
1833 return
1834 # Ensure that we have a place where to write.
1835 if not os.path.exists(destdir):
1836 os.makedirs(destdir)
1837 # Use ElementTree to parse the XML file
1838 from xml.etree import ElementTree as ET
1839 t = ET.parse(os.path.join(projbasedir, ".project"))
1840 projectName = t.find("name").text
1841
1842 # prepare the name/path of the generated file
1843 destfile = "%s.launch" % self._Runnable__id
1844 if destdir:
1845 destfile = os.path.join(destdir, destfile)
1846
1847 if self.options.strip():
1848 # this means we have some custom options in the qmt file, so we have
1849 # to copy them from the temporary file at the end of the arguments
1850 # in another file
1851 tempfile = args.pop()
1852 optsfile = destfile + os.path.splitext(tempfile)[1]
1853 shutil.copyfile(tempfile, optsfile)
1854 args.append(optsfile)
1855
1856 # prepare the data to insert in the XML file
1857 from xml.sax.saxutils import quoteattr # useful to quote XML special chars
1858 data = {}
1859 # Note: the "quoteattr(k)" is not needed because special chars cannot be part of a variable name,
1860 # but it doesn't harm.
1861 data["environment"] = "\n".join([
1862 '<mapEntry key=%s value=%s/>' % (quoteattr(k), quoteattr(v))
1863 for k, v in os.environ.iteritems()
1864 if k not in ('MAKEOVERRIDES', 'MAKEFLAGS', 'MAKELEVEL')
1865 ])
1866
1867 data["exec"] = which(prog) or prog
1868 if os.path.basename(data["exec"]).lower().startswith("python"):
1869 # do not stop at main when debugging Python scripts
1870 data["stopAtMain"] = "false"
1871 else:
1872 data["stopAtMain"] = "true"
1873
1874 data["args"] = "&#10;".join(map(rationalizepath, args))
1875 if self.isWinPlatform():
1876 data["args"] = "&#10;".join(
1877 ["/debugexe"] + map(rationalizepath, [data["exec"]] + args))
1878 data["exec"] = which("vcexpress.exe")
1879
1880 if not self.use_temp_dir:
1881 data["workdir"] = os.getcwd()
1882 else:
1883 # If the test is using a tmporary directory, it is better to run it
1884 # in the same directory as the .launch file when debugged in eclipse
1885 data["workdir"] = destdir
1886
1887 data["project"] = projectName.strip()
1888
1889 # Template for the XML file, based on eclipse 3.4
1890 xml_template = u"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1891 <launchConfiguration type="org.eclipse.cdt.launch.applicationLaunchType">
1892 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB" value="true"/>
1893 <listAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB_LIST"/>
1894 <stringAttribute key="org.eclipse.cdt.debug.mi.core.DEBUG_NAME" value="gdb"/>
1895 <stringAttribute key="org.eclipse.cdt.debug.mi.core.GDB_INIT" value=".gdbinit"/>
1896 <listAttribute key="org.eclipse.cdt.debug.mi.core.SOLIB_PATH"/>
1897 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.STOP_ON_SOLIB_EVENTS" value="false"/>
1898 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.breakpointsFullPath" value="false"/>
1899 <stringAttribute key="org.eclipse.cdt.debug.mi.core.commandFactory" value="org.eclipse.cdt.debug.mi.core.standardCommandFactory"/>
1900 <stringAttribute key="org.eclipse.cdt.debug.mi.core.protocol" value="mi"/>
1901 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.verboseMode" value="false"/>
1902 <intAttribute key="org.eclipse.cdt.launch.ATTR_BUILD_BEFORE_LAUNCH_ATTR" value="0"/>
1903 <stringAttribute key="org.eclipse.cdt.launch.COREFILE_PATH" value=""/>
1904 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_ID" value="org.eclipse.cdt.debug.mi.core.CDebuggerNew"/>
1905 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_REGISTER_GROUPS" value=""/>
1906 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_START_MODE" value="run"/>
1907 <booleanAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN" value="%(stopAtMain)s"/>
1908 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN_SYMBOL" value="main"/>
1909 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_REGISTER_BOOKKEEPING" value="false"/>
1910 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_VARIABLE_BOOKKEEPING" value="false"/>
1911 <stringAttribute key="org.eclipse.cdt.launch.FORMAT" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&lt;contentList/&gt;"/>
1912 <stringAttribute key="org.eclipse.cdt.launch.GLOBAL_VARIABLES" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;globalVariableList/&gt;&#10;"/>
1913 <stringAttribute key="org.eclipse.cdt.launch.MEMORY_BLOCKS" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;memoryBlockExpressionList/&gt;&#10;"/>
1914 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_ARGUMENTS" value="%(args)s"/>
1915 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="%(exec)s"/>
1916 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_ATTR" value="%(project)s"/>
1917 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value=""/>
1918 <stringAttribute key="org.eclipse.cdt.launch.WORKING_DIRECTORY" value="%(workdir)s"/>
1919 <booleanAttribute key="org.eclipse.cdt.launch.ui.ApplicationCDebuggerTab.DEFAULTS_SET" value="true"/>
1920 <booleanAttribute key="org.eclipse.cdt.launch.use_terminal" value="true"/>
1921 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
1922 <listEntry value="/%(project)s"/>
1923 </listAttribute>
1924 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
1925 <listEntry value="4"/>
1926 </listAttribute>
1927 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="false"/>
1928 <mapAttribute key="org.eclipse.debug.core.environmentVariables">
1929 %(environment)s
1930 </mapAttribute>
1931 <mapAttribute key="org.eclipse.debug.core.preferred_launchers">
1932 <mapEntry key="[debug]" value="org.eclipse.cdt.cdi.launch.localCLaunch"/>
1933 </mapAttribute>
1934 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
1935 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
1936 </listAttribute>
1937 </launchConfiguration>
1938 """
1939 try:
1940 # ensure the correct encoding of data values
1941 for k in data:
1942 data[k] = codecs.decode(data[k], 'utf-8')
1943 xml = xml_template % data
1944
1945 # Write the output file
1946 codecs.open(destfile, "w", encoding='utf-8').write(xml)
1947 except:
1948 print 'WARNING: problem generating Eclipse launcher'
1949
1950
1951 try:
1952 import json
1953 except ImportError:
1954 # Use simplejson for LCG
1955 import simplejson as json
1956
1957
1958 class HTMLResultStream(ResultStream):
1959 """An 'HTMLResultStream' writes its output to a set of HTML files.
1960
1961 The argument 'dir' is used to select the destination directory for the HTML
1962 report.
1963 The destination directory may already contain the report from a previous run
1964 (for example of a different package), in which case it will be extended to
1965 include the new data.
1966 """
1967 arguments = [
1968 qm.fields.TextField(
1969 name="dir",
1970 title="Destination Directory",
1971 description="""The name of the directory.
1972
1973 All results will be written to the directory indicated.""",
1974 verbatim="true",
1975 default_value=""),
1976 ]
1977
1978 def __init__(self, arguments=None, **args):
1979 """Prepare the destination directory.
1980
1981 Creates the destination directory and store in it some preliminary
1982 annotations and the static files found in the template directory
1983 'html_report'.
1984 """
1985 ResultStream.__init__(self, arguments, **args)
1986 self._summary = []
1987 self._summaryFile = os.path.join(self.dir, "summary.json")
1988 self._annotationsFile = os.path.join(self.dir, "annotations.json")
1989 # Prepare the destination directory using the template
1990 templateDir = os.path.join(os.path.dirname(__file__), "html_report")
1991 if not os.path.isdir(self.dir):
1992 os.makedirs(self.dir)
1993 # Copy the files in the template directory excluding the directories
1994 for f in os.listdir(templateDir):
1995 src = os.path.join(templateDir, f)
1996 dst = os.path.join(self.dir, f)
1997 if not os.path.isdir(src) and not os.path.exists(dst):
1998 shutil.copy(src, dst)
1999 # Add some non-QMTest attributes
2000 if "CMTCONFIG" in os.environ:
2001 self.WriteAnnotation("cmt.cmtconfig", os.environ["CMTCONFIG"])
2002 import socket
2003 self.WriteAnnotation("hostname", socket.gethostname())
2004
2005 def _updateSummary(self):
2006 """Helper function to extend the global summary file in the destination
2007 directory.
2008 """
2009 if os.path.exists(self._summaryFile):
2010 oldSummary = json.load(open(self._summaryFile))
2011 else:
2012 oldSummary = []
2013 ids = set([i["id"] for i in self._summary])
2014 newSummary = [i for i in oldSummary if i["id"] not in ids]
2015 newSummary.extend(self._summary)
2016 json.dump(newSummary, open(self._summaryFile, "w"), sort_keys=True)
2017
2018 def WriteAnnotation(self, key, value):
2019 """Writes the annotation to the annotation file.
2020 If the key is already present with a different value, the value becomes
2021 a list and the new value is appended to it, except for start_time and
2022 end_time.
2023 """
2024 # Initialize the annotation dict from the file (if present)
2025 if os.path.exists(self._annotationsFile):
2026 annotations = json.load(open(self._annotationsFile))
2027 else:
2028 annotations = {}
2029 # hack because we do not have proper JSON support
2030 key, value = map(str, [key, value])
2031 if key == "qmtest.run.start_time":
2032 # Special handling of the start time:
2033 # if we are updating a result, we have to keep the original start
2034 # time, but remove the original end time to mark the report to be
2035 # in progress.
2036 if key not in annotations:
2037 annotations[key] = value
2038 if "qmtest.run.end_time" in annotations:
2039 del annotations["qmtest.run.end_time"]
2040 else:
2041 # All other annotations are added to a list
2042 if key in annotations:
2043 old = annotations[key]
2044 if type(old) is list:
2045 if value not in old:
2046 annotations[key].append(value)
2047 elif value != old:
2048 annotations[key] = [old, value]
2049 else:
2050 annotations[key] = value
2051 # Write the new annotations file
2052 json.dump(
2053 annotations, open(self._annotationsFile, "w"), sort_keys=True)
2054
2055 def WriteResult(self, result):
2056 """Prepare the test result directory in the destination directory storing
2057 into it the result fields.
2058 A summary of the test result is stored both in a file in the test directory
2059 and in the global summary file.
2060 """
2061 summary = {}
2062 summary["id"] = result.GetId()
2063 summary["outcome"] = result.GetOutcome()
2064 summary["cause"] = result.GetCause()
2065 summary["fields"] = result.keys()
2066 summary["fields"].sort()
2067
2068 # Since we miss proper JSON support, I hack a bit
2069 for f in ["id", "outcome", "cause"]:
2070 summary[f] = str(summary[f])
2071 summary["fields"] = map(str, summary["fields"])
2072
2073 self._summary.append(summary)
2074
2075 # format:
2076 # testname/summary.json
2077 # testname/field1
2078 # testname/field2
2079 testOutDir = os.path.join(self.dir, summary["id"])
2080 if not os.path.isdir(testOutDir):
2081 os.makedirs(testOutDir)
2082 json.dump(
2083 summary,
2084 open(os.path.join(testOutDir, "summary.json"), "w"),
2085 sort_keys=True)
2086 for f in summary["fields"]:
2087 open(os.path.join(testOutDir, f), "w").write(result[f])
2088
2089 self._updateSummary()
2090
2091 def Summarize(self):
2092 # Not implemented.
2093 pass
2094
2095
2096 class XMLResultStream(ResultStream):
2097 """An 'XMLResultStream' writes its output to a Ctest XML file.
2098
2099 The argument 'dir' is used to select the destination file for the XML
2100 report.
2101 The destination directory may already contain the report from a previous run
2102 (for example of a different package), in which case it will be overrided to
2103 with the new data.
2104 """
2105 arguments = [
2106 qm.fields.TextField(
2107 name="dir",
2108 title="Destination Directory",
2109 description="""The name of the directory.
2110
2111 All results will be written to the directory indicated.""",
2112 verbatim="true",
2113 default_value=""),
2114 qm.fields.TextField(
2115 name="prefix",
2116 title="Output File Prefix",
2117 description="""The output file name will be the specified prefix
2118 followed by 'Test.xml' (CTest convention).""",
2119 verbatim="true",
2120 default_value=""),
2121 ]
2122
2123 def __init__(self, arguments=None, **args):
2124 """Prepare the destination directory.
2125
2126 Creates the destination directory and store in it some preliminary
2127 annotations .
2128 """
2129 ResultStream.__init__(self, arguments, **args)
2130
2131 self._xmlFile = os.path.join(self.dir, self.prefix + 'Test.xml')
2132
2133 # add some global variable
2134 self._startTime = None
2135 self._endTime = None
2136 # Format the XML file if it not exists
2137 if not os.path.isfile(self._xmlFile):
2138 # check that the container directory exists and create it if not
2139 if not os.path.exists(os.path.dirname(self._xmlFile)):
2140 os.makedirs(os.path.dirname(self._xmlFile))
2141
2142 newdataset = ET.Element("newdataset")
2143 self._tree = ET.ElementTree(newdataset)
2144 self._tree.write(self._xmlFile)
2145 else:
2146 # Read the xml file
2147 self._tree = ET.parse(self._xmlFile)
2148 newdataset = self._tree.getroot()
2149
2150 # Find the corresponding site, if do not exist, create it
2151
2152 #site = newdataset.find('Site[@BuildStamp="'+result["qmtest.start_time"]+'"][@OSPlatform="'+os.getenv("CMTOPT")+'"]')
2153 # I don't know why this syntax doesn't work. Maybe it is because of the python version. Indeed,
2154 # This works well in the python terminal. So I have to make a for:
2155 for site in newdataset.getiterator():
2156 # and site.get("BuildStamp") == result["qmtest.start_time"] and:
2157 if site.get("OSPlatform") == os.uname()[4]:
2158 # Here we can add some variable to define the difference beetween 2 site
2159 self._site = site
2160 break
2161 else:
2162 site = None
2163
2164 if site is None:
2165 import socket
2166 import multiprocessing
2167 attrib = {
2168 "BuildName": os.getenv("CMTCONFIG"),
2169 "Name": os.uname()[1],
2170 "Generator": "QMTest " + qm.version,
2171 "OSName": os.uname()[0],
2172 "Hostname": socket.gethostname(),
2173 "OSRelease": os.uname()[2],
2174 "OSVersion": os.uname()[3],
2175 "OSPlatform": os.uname()[4],
2176 "Is64Bits": "unknown",
2177 "VendorString": "unknown",
2178 "VendorID": "unknown",
2179 "FamilyID": "unknown",
2180 "ModelID": "unknown",
2181 "ProcessorCacheSize": "unknown",
2182 "NumberOfLogicalCPU": str(multiprocessing.cpu_count()),
2183 "NumberOfPhysicalCPU": "0",
2184 "TotalVirtualMemory": "0",
2185 "TotalPhysicalMemory": "0",
2186 "LogicalProcessorsPerPhysical": "0",
2187 "ProcessorClockFrequency": "0",
2188 }
2189 self._site = ET.SubElement(newdataset, "Site", attrib)
2190 self._Testing = ET.SubElement(self._site, "Testing")
2191
2192 # Start time elements
2193 self._StartDateTime = ET.SubElement(self._Testing, "StartDateTime")
2194
2195 self._StartTestTime = ET.SubElement(self._Testing, "StartTestTime")
2196
2197 self._TestList = ET.SubElement(self._Testing, "TestList")
2198
2199 # End time elements
2200 self._EndDateTime = ET.SubElement(self._Testing, "EndDateTime")
2201
2202 self._EndTestTime = ET.SubElement(self._Testing, "EndTestTime")
2203
2204 self._ElapsedMinutes = ET.SubElement(self._Testing,
2205 "ElapsedMinutes")
2206
2207 else: # We get the elements
2208 self._Testing = self._site.find("Testing")
2209 self._StartDateTime = self._Testing.find("StartDateTime")
2210 self._StartTestTime = self._Testing.find("StartTestTime")
2211 self._TestList = self._Testing.find("TestList")
2212 self._EndDateTime = self._Testing.find("EndDateTime")
2213 self._EndTestTime = self._Testing.find("EndTestTime")
2214 self._ElapsedMinutes = self._Testing.find("ElapsedMinutes")
2215 """
2216 # Add some non-QMTest attributes
2217 if "CMTCONFIG" in os.environ:
2218 self.WriteAnnotation("cmt.cmtconfig", os.environ["CMTCONFIG"])
2219 import socket
2220 self.WriteAnnotation("hostname", socket.gethostname())
2221 """
2222
2223 def WriteAnnotation(self, key, value):
2224 if key == "qmtest.run.start_time":
2225 if self._site.get("qmtest.run.start_time") is not None:
2226 return None
2227 self._site.set(str(key), str(value))
2228
2229 def WriteResult(self, result):
2230 """Prepare the test result directory in the destination directory storing
2231 into it the result fields.
2232 A summary of the test result is stored both in a file in the test directory
2233 and in the global summary file.
2234 """
2235 summary = {}
2236 summary["id"] = result.GetId()
2237 summary["outcome"] = result.GetOutcome()
2238 summary["cause"] = result.GetCause()
2239 summary["fields"] = result.keys()
2240 summary["fields"].sort()
2241
2242 # Since we miss proper JSON support, I hack a bit
2243 for f in ["id", "outcome", "cause"]:
2244 summary[f] = str(summary[f])
2245 summary["fields"] = map(str, summary["fields"])
2246
2247 # format
2248 # package_Test.xml
2249
2250 if "qmtest.start_time" in summary["fields"]:
2251 haveStartDate = True
2252 else:
2253 haveStartDate = False
2254 if "qmtest.end_time" in summary["fields"]:
2255 haveEndDate = True
2256 else:
2257 haveEndDate = False
2258
2259 # writing the start date time
2260 if haveStartDate:
2261 self._startTime = calendar.timegm(
2262 time.strptime(result["qmtest.start_time"],
2263 "%Y-%m-%dT%H:%M:%SZ"))
2264 if self._StartTestTime.text is None:
2265 self._StartDateTime.text = time.strftime(
2266 "%b %d %H:%M %Z", time.localtime(self._startTime))
2267 self._StartTestTime.text = str(self._startTime)
2268 self._site.set("BuildStamp", result["qmtest.start_time"])
2269
2270 # Save the end date time in memory
2271 if haveEndDate:
2272 self._endTime = calendar.timegm(
2273 time.strptime(result["qmtest.end_time"], "%Y-%m-%dT%H:%M:%SZ"))
2274
2275 # add the current test to the test list
2276 tl = ET.Element("Test")
2277 tl.text = summary["id"]
2278 self._TestList.insert(0, tl)
2279
2280 # Fill the current test
2281 Test = ET.Element("Test")
2282 if summary["outcome"] == "PASS":
2283 Test.set("Status", "passed")
2284 elif summary["outcome"] == "FAIL":
2285 Test.set("Status", "failed")
2286 elif summary["outcome"] == "SKIPPED" or summary[
2287 "outcome"] == "UNTESTED":
2288 Test.set("Status", "skipped")
2289 elif summary["outcome"] == "ERROR":
2290 Test.set("Status", "failed")
2291 Name = ET.SubElement(
2292 Test,
2293 "Name",
2294 )
2295 Name.text = summary["id"]
2296 Results = ET.SubElement(Test, "Results")
2297
2298 # add the test after the other test
2299 self._Testing.insert(3, Test)
2300
2301 if haveStartDate and haveEndDate:
2302 # Compute the test duration
2303 delta = self._endTime - self._startTime
2304 testduration = str(delta)
2305 Testduration = ET.SubElement(Results, "NamedMeasurement")
2306 Testduration.set("name", "Execution Time")
2307 Testduration.set("type", "numeric/float")
2308 value = ET.SubElement(Testduration, "Value")
2309 value.text = testduration
2310
2311 # remove the fields that we store in a different way
2312 for n in ("qmtest.end_time", "qmtest.start_time", "qmtest.cause",
2313 "ExecTest.stdout"):
2314 if n in summary["fields"]:
2315 summary["fields"].remove(n)
2316
2317 # Here we can add some NamedMeasurment which we know the type
2318 #
2319 if "ExecTest.exit_code" in summary["fields"]:
2320 summary["fields"].remove("ExecTest.exit_code")
2321 ExitCode = ET.SubElement(Results, "NamedMeasurement")
2322 ExitCode.set("name", "exit_code")
2323 ExitCode.set("type", "numeric/integer")
2324 value = ET.SubElement(ExitCode, "Value")
2325 value.text = convert_xml_illegal_chars(
2326 result["ExecTest.exit_code"])
2327
2328 TestStartTime = ET.SubElement(Results, "NamedMeasurement")
2329 TestStartTime.set("name", "Start_Time")
2330 TestStartTime.set("type", "String")
2331 value = ET.SubElement(TestStartTime, "Value")
2332 if haveStartDate:
2333 value.text = escape_xml_illegal_chars(
2334 time.strftime("%b %d %H:%M %Z %Y",
2335 time.localtime(self._startTime)))
2336 else:
2337 value.text = ""
2338
2339 TestEndTime = ET.SubElement(Results, "NamedMeasurement")
2340 TestEndTime.set("name", "End_Time")
2341 TestEndTime.set("type", "String")
2342 value = ET.SubElement(TestEndTime, "Value")
2343 if haveStartDate:
2344 value.text = escape_xml_illegal_chars(
2345 time.strftime("%b %d %H:%M %Z %Y",
2346 time.localtime(self._endTime)))
2347 else:
2348 value.text = ""
2349
2350 if summary["cause"]:
2351 FailureCause = ET.SubElement(Results, "NamedMeasurement")
2352 FailureCause.set("name", "Cause")
2353 FailureCause.set("type", "String")
2354 value = ET.SubElement(FailureCause, "Value")
2355 value.text = escape_xml_illegal_chars(summary["cause"])
2356
2357 # Fill the result
2358 fields = {}
2359 for field in summary["fields"]:
2360 fields[field] = ET.SubElement(Results, "NamedMeasurement")
2361 fields[field].set("type", "String")
2362 fields[field].set("name", field)
2363 value = ET.SubElement(fields[field], "Value")
2364 # to escape the <pre></pre>
2365 if "<pre>" in result[field][0:6]:
2366 value.text = convert_xml_illegal_chars(result[field][5:-6])
2367 else:
2368 value.text = convert_xml_illegal_chars(result[field])
2369
2370 if result.has_key("ExecTest.stdout"): # "ExecTest.stdout" in result :
2371 Measurement = ET.SubElement(Results, "Measurement")
2372 value = ET.SubElement(Measurement, "Value")
2373 if "<pre>" in result["ExecTest.stdout"][0:6]:
2374 value.text = convert_xml_illegal_chars(
2375 result["ExecTest.stdout"][5:-6])
2376 else:
2377 value.text = convert_xml_illegal_chars(
2378 result["ExecTest.stdout"])
2379
2380 # write the file
2381 # ,True) in python 2.7 to add the xml header
2382 self._tree.write(self._xmlFile, "utf-8")
2383
2384 def Summarize(self):
2385
2386 # Set the final end date time
2387 self._EndTestTime.text = str(self._endTime)
2388 self._EndDateTime.text = time.strftime("%b %d %H:%M %Z",
2389 time.localtime(self._endTime))
2390
2391 # Compute the total duration
2392 if self._endTime and self._startTime:
2393 delta = self._endTime - self._startTime
2394 else:
2395 delta = 0
2396 self._ElapsedMinutes.text = str(delta / 60)
2397
2398 # Write into the file
2399 # ,True) in python 2.7 to add the xml header
2400 self._tree.write(self._xmlFile, "utf-8")
GaudiTest.TempFile.__init__
def __init__(self, suffix='', prefix='tmp', dir=None, text=False, keep=False)
Definition: GaudiTest.py:226
GaudiTest.BasicOutputValidator.__init__
def __init__(self, ref, cause, result_key)
Definition: GaudiTest.py:388
GaudiTest.RegexpReplacer.__init__
def __init__(self, orig, repl="", when=None)
Definition: GaudiTest.py:517
GaudiTest.LineSkipper.__init__
def __init__(self, strings=[], regexps=[])
Definition: GaudiTest.py:484
GaudiTest.HTMLResultStream._summaryFile
_summaryFile
Definition: GaudiTest.py:1987
GaudiTest.TempDir.__init__
def __init__(self, keep=False, chdir=False)
Definition: GaudiTest.py:197
GaudiTest.parseHistosSummary
def parseHistosSummary(lines, pos)
Definition: GaudiTest.py:923
GaudiTest.TempDir._origdir
_origdir
Definition: GaudiTest.py:200
GaudiTest.GaudiExeTest.args_order
args_order
Definition: GaudiTest.py:1527
GaudiTest.HTMLResultStream._updateSummary
def _updateSummary(self)
Definition: GaudiTest.py:2005
GaudiTest.GaudiFilterExecutable.__init__
def __init__(self, input, timeout=-1)
Definition: GaudiTest.py:1017
GaudiTest.HTMLResultStream
Definition: GaudiTest.py:1958
GaudiTest.TempFile.file
file
Definition: GaudiTest.py:227
GaudiTest.GaudiExeTest.CheckHistosSummaries
def CheckHistosSummaries(self, stdout, result, causes, dict=None, ignore=None)
Definition: GaudiTest.py:1409
GaudiTest.countErrorLines
def countErrorLines(expected={ 'ERROR':0, 'FATAL':0}, **kwargs)
Definition: GaudiTest.py:763
GaudiTest.LineSorter.__init__
def __init__(self, signature)
Definition: GaudiTest.py:554
GaudiTest.GaudiExeTest
Definition: GaudiTest.py:1184
GaudiTest.LineSorter.siglen
siglen
Definition: GaudiTest.py:556
GaudiTest.BlockSkipper
Definition: GaudiTest.py:499
GaudiTest.FilePreprocessor
Definition: GaudiTest.py:442
GaudiTest.TempDir.__str__
def __str__(self)
Definition: GaudiTest.py:205
GaudiTest.FilePreprocessor.__add__
def __add__(self, rhs)
Definition: GaudiTest.py:465
Gaudi::Functional::details::zip::range
decltype(auto) range(Args &&... args)
Zips multiple containers together to form a single range.
Definition: FunctionalDetails.h:71
GaudiTest.hexreplace
def hexreplace(match)
Definition: GaudiTest.py:355
GaudiTest.HTMLResultStream.Summarize
def Summarize(self)
Definition: GaudiTest.py:2091
GaudiTest.BasicOutputValidator.result_key
result_key
Definition: GaudiTest.py:391
GaudiTest.GaudiExeTest.timeout
timeout
Definition: GaudiTest.py:1680
GaudiTest.XMLResultStream.__init__
def __init__(self, arguments=None, **args)
Definition: GaudiTest.py:2123
GaudiTest.GaudiExeTest.PlatformIsNotSupported
def PlatformIsNotSupported(self, context, result)
Definition: GaudiTest.py:1298
GaudiTest.GaudiFilterExecutable
Definition: GaudiTest.py:1016
GaudiTest.BlockSkipper.end
end
Definition: GaudiTest.py:502
GaudiTest.GaudiFilterExecutable.__input
__input
Definition: GaudiTest.py:1026
GaudiTest.RegexpReplacer
Definition: GaudiTest.py:516
GaudiTest.ReferenceFileValidator.__init__
def __init__(self, reffile, cause, result_key, preproc=normalizeExamples)
Definition: GaudiTest.py:670
GaudiTest.hexConvert
def hexConvert(char)
Definition: GaudiTest.py:360
GaudiTest.RegexpReplacer.__processLine__
def __processLine__(self, line)
Definition: GaudiTest.py:530
GaudiTest.CMT.path
path
Definition: GaudiTest.py:259
GaudiTest.TempDir.name
name
Definition: GaudiTest.py:198
Gaudi::Functional::details::insert
constexpr struct Gaudi::Functional::details::insert_t insert
GaudiTest.BlockSkipper.start
start
Definition: GaudiTest.py:501
GaudiTest.TempFile
Definition: GaudiTest.py:218
GaudiTest.TempFile.name
name
Definition: GaudiTest.py:228
GaudiTest.GaudiExeTest.extra_args
extra_args
Definition: GaudiTest.py:1524
GaudiTest.TempFile.__str__
def __str__(self)
Definition: GaudiTest.py:239
GaudiTest.GaudiExeTest.DumpEnvironment
def DumpEnvironment(self, result)
Definition: GaudiTest.py:1596
GaudiTest.TemporaryEnvironment.__delitem__
def __delitem__(self, key)
Definition: GaudiTest.py:111
GaudiTest.HTMLResultStream._summary
_summary
Definition: GaudiTest.py:1986
GaudiTest.GaudiFilterExecutable._HandleChild
def _HandleChild(self)
Needs to replace the ones from RedirectedExecutable and TimeoutExecutable.
Definition: GaudiTest.py:1052
GaudiTest.FilePreprocessorSequence
Definition: GaudiTest.py:469
GaudiTest.FilePreprocessorSequence.__init__
def __init__(self, members=[])
Definition: GaudiTest.py:470
GaudiTest.GaudiExeTest.reference
reference
Definition: GaudiTest.py:1641
GaudiTest.TemporaryEnvironment.__contains__
def __contains__(self, key)
Definition: GaudiTest.py:135
hivetimeline.read
def read(f, regex='.*', skipevents=0)
Definition: hivetimeline.py:22
GaudiTest.TemporaryEnvironment.env
env
Definition: GaudiTest.py:89
GaudiTest.XMLResultStream._startTime
_startTime
Definition: GaudiTest.py:2134
GaudiTest.FilePreprocessorSequence.__add__
def __add__(self, rhs)
Definition: GaudiTest.py:473
GaudiTest.TemporaryEnvironment.__del__
def __del__(self)
Definition: GaudiTest.py:153
GaudiTest.CMT.runtime_env
def runtime_env(self, env=None)
Definition: GaudiTest.py:281
GaudiTest.GaudiFilterExecutable.__timeout
__timeout
Definition: GaudiTest.py:1027
GaudiTest.TemporaryEnvironment
Utility Classes.
Definition: GaudiTest.py:77
GaudiTest.GaudiExeTest._expandReferenceFileName
def _expandReferenceFileName(self, reffile)
Definition: GaudiTest.py:1333
GaudiTest.escape_xml_illegal_chars
def escape_xml_illegal_chars(val, replacement='?')
Definition: GaudiTest.py:368
MSG::hex
MsgStream & hex(MsgStream &log)
Definition: MsgStream.h:271
max
EventIDBase max(const EventIDBase &lhs, const EventIDBase &rhs)
Definition: EventIDBase.h:215
GaudiTest.XMLResultStream.WriteAnnotation
def WriteAnnotation(self, key, value)
Definition: GaudiTest.py:2223
GaudiTest.TemporaryEnvironment.__setitem__
def __setitem__(self, key, value)
Definition: GaudiTest.py:92
GaudiTest.which
def which(executable)
Definition: GaudiTest.py:309
GaudiTest.GaudiExeTest.isWinPlatform
def isWinPlatform(self)
Definition: GaudiTest.py:1323
IOTest.start
def start
Definition: IOTest.py:97
GaudiTest.TemporaryEnvironment.old_values
old_values
Definition: GaudiTest.py:88
GaudiTest.FilePreprocessorSequence.members
members
Definition: GaudiTest.py:471
GaudiTest.cmpTreesDicts
def cmpTreesDicts(reference, to_check, ignore=None)
Definition: GaudiTest.py:875
GaudiTest.GaudiExeTest.RunProgram
def RunProgram(self, program, arguments, context, result)
Definition: GaudiTest.py:1697
GaudiTest.LineSorter.__processLine__
def __processLine__(self, line)
Definition: GaudiTest.py:558
GaudiTest.GaudiFilterExecutable.__monitor_thread
__monitor_thread
This is the interesting part: dump the stack trace to a file.
Definition: GaudiTest.py:1155
GaudiTest.GaudiFilterExecutable.__Monitor
def __Monitor(self)
Definition: GaudiTest.py:1160
GaudiTest.HTMLResultStream._annotationsFile
_annotationsFile
Definition: GaudiTest.py:1988
GaudiTest.GaudiExeTest.callable
callable
Definition: GaudiTest.py:1523
GaudiTest.GaudiFilterExecutable.__UseSeparateProcessGroupForChild
def __UseSeparateProcessGroupForChild(self)
Definition: GaudiTest.py:1036
GaudiTest.TempDir.__del__
def __del__(self)
Definition: GaudiTest.py:208
GaudiTest.GaudiExeTest.CheckTTreesSummaries
def CheckTTreesSummaries(self, stdout, result, causes, trees_dict=None, ignore=r"Basket|.*size|Compression")
Definition: GaudiTest.py:1370
GaudiTest.XMLResultStream.WriteResult
def WriteResult(self, result)
Definition: GaudiTest.py:2229
GaudiTest.XMLResultStream._EndDateTime
_EndDateTime
Definition: GaudiTest.py:2200
Containers::map
struct GAUDI_API map
Parametrisation class for map-like implementation.
Definition: KeyedObjectManager.h:25
GaudiTest.XMLResultStream._site
_site
Definition: GaudiTest.py:2159
GaudiTest.convert_xml_illegal_chars
def convert_xml_illegal_chars(val)
Definition: GaudiTest.py:364
GaudiTest._parseTTreeSummary
def _parseTTreeSummary(lines, pos)
Definition: GaudiTest.py:801
GaudiTest.LineSorter.signature
signature
Definition: GaudiTest.py:555
GaudiTest.XMLResultStream._Testing
_Testing
Definition: GaudiTest.py:2190
GaudiTest.TempDir
Definition: GaudiTest.py:189
GaudiTest.ReferenceFileValidator.__call__
def __call__(self, stdout, result)
Definition: GaudiTest.py:676
GaudiTest.TemporaryEnvironment.gen_script
def gen_script(self, shell_type)
Definition: GaudiTest.py:160
GaudiTest.getCmpFailingValues
def getCmpFailingValues(reference, to_check, fail_path)
Definition: GaudiTest.py:908
GaudiTest.GaudiExeTest.program
program
Definition: GaudiTest.py:1630
GaudiTest.TemporaryEnvironment.items
def items(self)
Definition: GaudiTest.py:128
gaudirun.type
type
Definition: gaudirun.py:142
GaudiTest.RegexpReplacer.__add__
def __add__(self, rhs)
Definition: GaudiTest.py:522
GaudiTest.GaudiExeTest.Run
def Run(self, context, result)
Definition: GaudiTest.py:1608
GaudiTest.GaudiExeTest._CreateEclipseLaunch
def _CreateEclipseLaunch(self, prog, args, destdir=None)
Definition: GaudiTest.py:1818
GaudiTest.GaudiExeTest.GetPlatform
def GetPlatform(self)
Definition: GaudiTest.py:1311
GaudiTest.GaudiFilterExecutable.__monitor_pid
__monitor_pid
Definition: GaudiTest.py:1094
GaudiTest.FilePreprocessor.__processLine__
def __processLine__(self, line)
Definition: GaudiTest.py:446
GaudiTest.BasicOutputValidator
Output Validation Classes.
Definition: GaudiTest.py:382
GaudiTest.BasicOutputValidator.cause
cause
Definition: GaudiTest.py:390
GaudiTest.TempFile._keep
_keep
Definition: GaudiTest.py:229
GaudiTest.ReferenceFileValidator.result_key
result_key
Definition: GaudiTest.py:673
GaudiTest.BlockSkipper.__processLine__
def __processLine__(self, line)
Definition: GaudiTest.py:505
GaudiTest.GaudiExeTest.use_temp_dir
use_temp_dir
Definition: GaudiTest.py:1670
GaudiTest.total_seconds_replacement
def total_seconds_replacement(timedelta)
Definition: GaudiTest.py:43
GaudiTest.XMLResultStream._xmlFile
_xmlFile
Definition: GaudiTest.py:2131
GaudiTest.CMT._run_cmt
def _run_cmt(self, command, args)
Definition: GaudiTest.py:261
GaudiTest.HTMLResultStream.WriteAnnotation
def WriteAnnotation(self, key, value)
Definition: GaudiTest.py:2018
GaudiTest.LineSkipper.strings
strings
Definition: GaudiTest.py:486
GaudiTest.FilePreprocessor.__call__
def __call__(self, input)
Definition: GaudiTest.py:449
GaudiTest.GaudiExeTest.signal
signal
Definition: GaudiTest.py:1748
GaudiTest.LineSkipper
Definition: GaudiTest.py:483
GaudiTest.HTMLResultStream.__init__
def __init__(self, arguments=None, **args)
Definition: GaudiTest.py:1978
GaudiTest.TempFile.__getattr__
def __getattr__(self, attr)
Definition: GaudiTest.py:248
GaudiTest.XMLResultStream._endTime
_endTime
Definition: GaudiTest.py:2135
Gaudi::Functional::details::get
auto get(const Handle &handle, const Algo &, const EventContext &) -> decltype(details::deref(handle.get()))
Definition: FunctionalDetails.h:332
GaudiTest.XMLResultStream._StartDateTime
_StartDateTime
Definition: GaudiTest.py:2193
GaudiTest.TemporaryEnvironment._keep_same
_keep_same
Definition: GaudiTest.py:90
GaudiTest.findHistosSummaries
def findHistosSummaries(stdout)
Definition: GaudiTest.py:993
GaudiTest.CMT
Definition: GaudiTest.py:252
GaudiTest.GaudiExeTest.ValidateOutput
def ValidateOutput(self, stdout, stderr, result)
Definition: GaudiTest.py:1511
GaudiTest.XMLResultStream._StartTestTime
_StartTestTime
Definition: GaudiTest.py:2195
GaudiTest.XMLResultStream.Summarize
def Summarize(self)
Definition: GaudiTest.py:2384
GaudiTest.TempFile.__del__
def __del__(self)
Definition: GaudiTest.py:242
GaudiTest.GaudiExeTest.error_reference
error_reference
Definition: GaudiTest.py:1642
GaudiTest.XMLResultStream._EndTestTime
_EndTestTime
Definition: GaudiTest.py:2202
GaudiTest.TemporaryEnvironment.restore
def restore(self)
Definition: GaudiTest.py:142
GaudiTest.TemporaryEnvironment.__getitem__
def __getitem__(self, key)
Definition: GaudiTest.py:104
GaudiTest.findReferenceBlock
def findReferenceBlock(reference, stdout, result, causes, signature_offset=0, signature=None, id=None)
Definition: GaudiTest.py:709
GaudiTest.RegexpReplacer._operations
_operations
Definition: GaudiTest.py:520
compareOutputFiles.pp
pp
Definition: compareOutputFiles.py:495
GaudiTest.XMLResultStream._ElapsedMinutes
_ElapsedMinutes
Definition: GaudiTest.py:2204
GaudiTest.LineSkipper.__processLine__
def __processLine__(self, line)
Definition: GaudiTest.py:489
GaudiTest.findTTreeSummaries
def findTTreeSummaries(stdout)
Definition: GaudiTest.py:853
GaudiTest.XMLResultStream._tree
_tree
Definition: GaudiTest.py:2143
GaudiTest.BasicOutputValidator.reference
reference
Definition: GaudiTest.py:389
GaudiTest.XMLResultStream._TestList
_TestList
Definition: GaudiTest.py:2197
GaudiTest.CMT.__init__
def __init__(self, path=None)
Definition: GaudiTest.py:256
GaudiTest.TempDir.__getattr__
def __getattr__(self, attr)
Definition: GaudiTest.py:214
GaudiTest.BlockSkipper._skipping
_skipping
Definition: GaudiTest.py:503
GaudiTest.ReferenceFileValidator.cause
cause
Definition: GaudiTest.py:672
GaudiTest.BlockSkipper.__init__
def __init__(self, start, end)
Definition: GaudiTest.py:500
GaudiTest.LineSorter
Definition: GaudiTest.py:553
GaudiTest.ReferenceFileValidator
Definition: GaudiTest.py:669
GaudiTest.ReferenceFileValidator.preproc
preproc
Definition: GaudiTest.py:674
GaudiTest.BasicOutputValidator.__call__
def __call__(self, out, result)
Definition: GaudiTest.py:393
GaudiTest.CMT.__getattr__
def __getattr__(self, attr)
Definition: GaudiTest.py:278
GaudiTest.FilePreprocessorSequence.__call__
def __call__(self, input)
Definition: GaudiTest.py:476
GaudiTest.GaudiFilterExecutable.stack_trace_file
stack_trace_file
Definition: GaudiTest.py:1028
GaudiTest.ReferenceFileValidator.reffile
reffile
Definition: GaudiTest.py:671
GaudiTest.HTMLResultStream.WriteResult
def WriteResult(self, result)
Definition: GaudiTest.py:2055
GaudiTest.ROOT6WorkAroundEnabled
def ROOT6WorkAroundEnabled(id=None)
Definition: GaudiTest.py:26
GaudiTest.rationalizepath
def rationalizepath(p)
Definition: GaudiTest.py:330
GaudiTest.CMT.show_macro
def show_macro(self, k)
Definition: GaudiTest.py:298
GaudiTest.TemporaryEnvironment.keys
def keys(self)
Definition: GaudiTest.py:121
GaudiTest.GaudiExeTest.ValidateWithReference
def ValidateWithReference(self, stdout, stderr, result, causes, preproc=None)
Definition: GaudiTest.py:1447
GaudiTest.BasicOutputValidator.__CompareText
def __CompareText(self, s1, s2)
Definition: GaudiTest.py:415
GaudiTest.TemporaryEnvironment.__init__
def __init__(self, orig=os.environ, keep_same=False)
Definition: GaudiTest.py:82
GaudiTest.TempDir._keep
_keep
Definition: GaudiTest.py:199
GaudiTest.LineSkipper.regexps
regexps
Definition: GaudiTest.py:487