| The Gaudi Framework v27r1

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