| The Gaudi Framework v29r1 (5cd95fa2)

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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