5 __author__ =
'Marco Clemencic CERN/PH-LBC'
16 from subprocess
import Popen, PIPE, STDOUT
19 from qm.test.classes.command
import ExecTestBase
20 from qm.test.result_stream
import ResultStream
27 if sys.platform ==
"win32":
30 from threading
import *
48 Class to changes the environment temporarily.
50 def __init__(self, orig = os.environ, keep_same = False):
52 Create a temporary environment on top of the one specified
53 (it can be another TemporaryEnvironment instance).
62 Set an environment variable recording the previous value.
74 Get an environment variable.
75 Needed to provide the same interface as os.environ.
81 Unset an environment variable.
82 Needed to provide the same interface as os.environ.
84 if key
not in self.
env :
91 Return the list of defined environment variables.
92 Needed to provide the same interface as os.environ.
94 return self.env.keys()
98 Return the list of (name,value) pairs for the defined environment variables.
99 Needed to provide the same interface as os.environ.
101 return self.env.items()
106 Needed to provide the same interface as os.environ.
108 return key
in self.
env
112 Revert all the changes done to the orignal environment.
114 for key,value
in self.old_values.items():
118 self.
env[key] = value
123 Revert the changes on destruction.
130 Generate a shell script to reproduce the changes in the environment.
132 shells = [
'csh',
'sh',
'bat' ]
133 if shell_type
not in shells:
134 raise RuntimeError(
"Shell type '%s' unknown. Available: %s"%(shell_type,shells))
136 for key,value
in self.old_values.items():
137 if key
not in self.
env:
139 if shell_type ==
'csh':
140 out +=
'unsetenv %s\n'%key
141 elif shell_type ==
'sh':
142 out +=
'unset %s\n'%key
143 elif shell_type ==
'bat':
144 out +=
'set %s=\n'%key
147 if shell_type ==
'csh':
148 out +=
'setenv %s "%s"\n'%(key,self.
env[key])
149 elif shell_type ==
'sh':
150 out +=
'export %s="%s"\n'%(key,self.
env[key])
151 elif shell_type ==
'bat':
152 out +=
'set %s=%s\n'%(key,self.
env[key])
156 """Small class for temporary directories.
157 When instantiated, it creates a temporary directory and the instance
158 behaves as the string containing the directory name.
159 When the instance goes out of scope, it removes all the content of
160 the temporary directory (automatic clean-up).
177 shutil.rmtree(self.
name)
180 return getattr(self.
name,attr)
183 """Small class for temporary files.
184 When instantiated, it creates a temporary directory and the instance
185 behaves as the string containing the directory name.
186 When the instance goes out of scope, it removes all the content of
187 the temporary directory (automatic clean-up).
189 def __init__(self, suffix='', prefix='tmp', dir=None, text=False, keep = False):
194 self._fd, self.
name = tempfile.mkstemp(suffix,prefix,dir,text)
195 self.
file = os.fdopen(self._fd,
"r+")
207 return getattr(self.
file,attr)
210 """Small wrapper to call CMT.
219 if type(args)
is str:
221 cmd =
"cmt %s"%command
229 result = os.popen4(cmd)[1].read()
235 return lambda args=[]: self.
_run_cmt(attr, args)
238 """Returns a dictionary containing the runtime environment produced by CMT.
239 If a dictionary is passed a modified instance of it is returned.
243 for l
in self.setup(
"-csh").splitlines():
245 if l.startswith(
"setenv"):
246 dummy,name,value = l.split(
None,3)
247 env[name] = value.strip(
'"')
248 elif l.startswith(
"unsetenv"):
249 dummy,name = l.split(
None,2)
254 r = self.show([
"macro",k])
255 if r.find(
"CMT> Error: symbol not found") >= 0:
258 return self.show([
"macro_value",k]).strip()
265 Locates an executable in the executables path ($PATH) and returns the full
266 path to it. An application is looked for with or without the '.exe' suffix.
267 If the executable cannot be found, None is returned
269 if os.path.isabs(executable):
270 if not os.path.exists(executable):
271 if executable.endswith(
'.exe'):
272 if os.path.exists(executable[:-4]):
273 return executable[:-4]
275 for d
in os.environ.get(
"PATH").split(os.pathsep):
276 fullpath = os.path.join(d, executable)
277 if os.path.exists(fullpath):
279 if executable.endswith(
'.exe'):
280 return which(executable[:-4])
284 p = os.path.normpath(os.path.expandvars(p))
285 if os.path.exists(p):
286 p = os.path.realpath(p)
293 """Basic implementation of an option validator for Gaudi tests.
294 This implementation is based on the standard (LCG) validation functions
303 """Validate the output of the program.
305 'stdout' -- A string containing the data written to the standard output
308 'stderr' -- A string containing the data written to the standard error
311 'result' -- A 'Result' object. It may be used to annotate
312 the outcome according to the content of stderr.
314 returns -- A list of strings giving causes of failure."""
319 causes.append(self.
cause)
325 """Compare 's1' and 's2', ignoring line endings.
331 returns -- True if 's1' and 's2' are the same, ignoring
332 differences in line endings."""
336 return s1.splitlines() == s2.splitlines()
339 """ Base class for a callable that takes a file and returns a modified
344 if hasattr(input,
"__iter__"):
348 lines = input.splitlines()
353 if l: output.append(l)
354 if mergeback: output =
'\n'.join(output)
378 if line.find(s) >= 0:
return None
380 if r.search(line):
return None
390 if self.
start in line:
393 elif self.
end in line:
402 when = re.compile(when)
405 if isinstance(rhs, RegexpReplacer):
407 res._operations = self.
_operations + rhs._operations
409 res = FilePreprocessor.__add__(self, rhs)
413 if w
is None or w.search(line):
414 line = o.sub(r, line)
419 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)?",
420 "00:00:00 1970-01-01")
422 normalizeEOL.__processLine__ =
lambda line: str(line).rstrip() +
'\n'
426 skipEmptyLines.__processLine__ =
lambda line: (line.strip()
and line)
or None
437 line = line[:(pos+self.
siglen)]
438 lst = line[(pos+self.
siglen):].split()
440 line +=
" ".join(lst)
444 normalizeExamples = maskPointers + normalizeDate
447 (
"TIMER.TIMER",
r"\s+[+-]?[0-9]+[0-9.]*",
" 0"),
448 (
"release all pending",
r"^.*/([^/]*:.*)",
r"\1"),
449 (
"0x########",
r"\[.*/([^/]*.*)\]",
r"[\1]"),
450 (
"^#.*file",
r"file '.*[/\\]([^/\\]*)$",
r"file '\1"),
451 (
"^JobOptionsSvc.*options successfully read in from",
r"read in from .*[/\\]([^/\\]*)$",
r"file \1"),
453 (
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"),
455 (
"ServiceLocatorHelper::",
"ServiceLocatorHelper::(create|locate)Service",
"ServiceLocatorHelper::service"),
457 (
None,
r"e([-+])0([0-9][0-9])",
r"e\1\2"),
461 "JobOptionsSvc INFO # ",
462 "JobOptionsSvc WARNING # ",
465 "This machine has a speed",
468 "ToolSvc.Sequenc... INFO",
469 "DataListenerSvc INFO XML written to file:",
470 "[INFO]",
"[WARNING]",
471 "DEBUG No writable file catalog found which contains FID:",
473 "DEBUG Service base class initialized successfully",
474 "DEBUG Incident timing:",
475 "INFO 'CnvServices':[",
477 'Note: (file "(tmpfile)", line 2) File "set" already loaded',
481 r"^JobOptionsSvc INFO *$",
483 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
484 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
485 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
486 r"File '.*.xml' does not exist",
487 r"INFO Refer to dataset .* by its file ID:",
488 r"INFO Referring to dataset .* by its file ID:",
489 r"INFO Disconnect from dataset",
490 r"INFO Disconnected from dataset",
491 r"INFO Disconnected data IO:",
492 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
494 r"^StatusCodeSvc.*listing all unchecked return codes:",
495 r"^StatusCodeSvc\s*INFO\s*$",
496 r"Num\s*\|\s*Function\s*\|\s*Source Library",
499 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
501 r"^ +[0-9]+ \|.*ROOT",
502 r"^ +[0-9]+ \|.*\|.*Dict",
506 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
509 ] ) + normalizeExamples + skipEmptyLines + \
514 def __init__(self, reffile, cause, result_key, preproc = normalizeExamples):
521 if os.path.isfile(self.
reffile):
522 orig = open(self.
reffile).xreadlines()
528 new = stdout.splitlines()
532 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
533 filterdiffs = map(
lambda x: x.strip(),filter(
lambda x: x[0] !=
" ",diffs))
536 result[self.
result_key] = result.Quote(
"\n".join(filterdiffs))
540 +) standard output of the test""")
541 causes.append(self.
cause)
548 def findReferenceBlock(reference, stdout, result, causes, signature_offset=0, signature=None,
551 Given a block of text, tries to find it in the output.
552 The block had to be identified by a signature line. By default, the first
553 line is used as signature, or the line pointed to by signature_offset. If
554 signature_offset points outside the block, a signature line can be passed as
555 signature argument. Note: if 'signature' is None (the default), a negative
556 signature_offset is interpreted as index in a list (e.g. -1 means the last
557 line), otherwise the it is interpreted as the number of lines before the
558 first one of the block the signature must appear.
559 The parameter 'id' allow to distinguish between different calls to this
560 function in the same validation code.
563 reflines = filter(
None,map(
lambda s: s.rstrip(), reference.splitlines()))
565 raise RuntimeError(
"Empty (or null) reference")
567 outlines = filter(
None,map(
lambda s: s.rstrip(), stdout.splitlines()))
569 res_field =
"GaudiTest.RefBlock"
571 res_field +=
"_%s" % id
573 if signature
is None:
574 if signature_offset < 0:
575 signature_offset = len(reference)+signature_offset
576 signature = reflines[signature_offset]
579 pos = outlines.index(signature)
580 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
581 if reflines != outlines:
582 msg =
"standard output"
584 if not msg
in causes:
586 result[res_field +
".observed"] = result.Quote(
"\n".join(outlines))
588 causes.append(
"missing signature")
589 result[res_field +
".signature"] = result.Quote(signature)
590 if len(reflines) > 1
or signature != reflines[0]:
591 result[res_field +
".expected"] = result.Quote(
"\n".join(reflines))
597 Count the number of messages with required severity (by default ERROR and FATAL)
598 and check if their numbers match the expected ones (0 by default).
599 The dictionary "expected" can be used to tune the number of errors and fatals
600 allowed, or to limit the number of expected warnings etc.
602 stdout = kwargs[
"stdout"]
603 result = kwargs[
"result"]
604 causes = kwargs[
"causes"]
611 outlines = stdout.splitlines()
612 from math
import log10
613 fmt =
"%%%dd - %%s" % (int(log10(len(outlines))+1))
619 if len(words) >= 2
and words[1]
in errors:
620 errors[words[1]].append(fmt%(linecount,l.rstrip()))
623 if len(errors[e]) != expected[e]:
624 causes.append(
'%s(%d)'%(e,len(errors[e])))
625 result[
"GaudiTest.lines.%s"%e] = result.Quote(
'\n'.join(errors[e]))
626 result[
"GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
633 Parse the TTree summary table in lines, starting from pos.
634 Returns a tuple with the dictionary with the digested informations and the
635 position of the first line after the summary.
641 splitcols =
lambda l: [ f.strip()
for f
in l.strip(
"*\n").split(
':',2) ]
644 cols = splitcols(ll[0])
645 r[
"Name"], r[
"Title"] = cols[1:]
647 cols = splitcols(ll[1])
648 r[
"Entries"] = int(cols[1])
650 sizes = cols[2].split()
651 r[
"Total size"] = int(sizes[2])
652 if sizes[-1] ==
"memory":
655 r[
"File size"] = int(sizes[-1])
657 cols = splitcols(ll[2])
658 sizes = cols[2].split()
659 if cols[0] ==
"Baskets":
660 r[
"Baskets"] = int(cols[1])
661 r[
"Basket size"] = int(sizes[2])
662 r[
"Compression"] = float(sizes[-1])
665 if i < (count - 3)
and lines[i].startswith(
"*Tree"):
666 result = parseblock(lines[i:i+3])
667 result[
"Branches"] = {}
669 while i < (count - 3)
and lines[i].startswith(
"*Br"):
670 if i < (count - 2)
and lines[i].startswith(
"*Branch "):
674 branch = parseblock(lines[i:i+3])
675 result[
"Branches"][branch[
"Name"]] = branch
682 Scan stdout to find ROOT TTree summaries and digest them.
684 stars = re.compile(
r"^\*+$")
685 outlines = stdout.splitlines()
686 nlines = len(outlines)
692 while i < nlines
and not stars.match(outlines[i]):
697 trees[tree[
"Name"]] = tree
703 Check that all the keys in reference are in to_check too, with the same value.
704 If the value is a dict, the function is called recursively. to_check can
705 contain more keys than reference, that will not be tested.
706 The function returns at the first difference found.
711 ignore_re = re.compile(ignore)
712 keys = [ key
for key
in reference
if not ignore_re.match(key) ]
714 keys = reference.keys()
718 if (
type(reference[k])
is dict)
and (
type(to_check[k])
is dict):
720 failed = fail_keys =
cmpTreesDicts(reference[k], to_check[k], ignore)
723 failed = to_check[k] != reference[k]
728 fail_keys.insert(0, k)
738 if c
is None or r
is None:
740 return (fail_path, r, c)
743 h_count_re = re.compile(
r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
747 Extract the histograms infos from the lines starting at pos.
748 Returns the position of the first line after the summary block.
751 h_table_head = re.compile(
r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
752 h_short_summ = re.compile(
r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
757 m = h_count_re.search(lines[pos])
758 name = m.group(1).strip()
759 total = int(m.group(2))
761 for k, v
in [ x.split(
"=")
for x
in m.group(3).split() ]:
764 header[
"Total"] = total
768 m = h_table_head.search(lines[pos])
771 t = t.replace(
" profile",
"Prof")
778 if l.startswith(
" | ID"):
780 titles = [ x.strip()
for x
in l.split(
"|")][1:]
782 while pos < nlines
and lines[pos].startswith(
" |"):
784 values = [ x.strip()
for x
in l.split(
"|")][1:]
786 for i
in range(len(titles)):
787 hcont[titles[i]] = values[i]
788 cont[hcont[
"ID"]] = hcont
790 elif l.startswith(
" ID="):
791 while pos < nlines
and lines[pos].startswith(
" ID="):
792 values = [ x.strip()
for x
in h_short_summ.search(lines[pos]).groups() ]
793 cont[values[0]] = values
796 raise RuntimeError(
"Cannot understand line %d: '%s'" % (pos, l))
800 summ[d][
"header"] = header
805 summ[name] = {
"header": header}
810 Scan stdout to find ROOT TTree summaries and digest them.
812 outlines = stdout.splitlines()
813 nlines = len(outlines) - 1
821 match = h_count_re.search(outlines[pos])
822 while pos < nlines
and not match:
824 match = h_count_re.search(outlines[pos])
827 summaries.update(summ)
832 """Create a new 'Filter'.
834 'input' -- The string containing the input to provide to the
837 'timeout' -- As for 'TimeoutExecutable.__init__'."""
839 super(GaudiFilterExecutable, self).
__init__(input, timeout)
846 tmpf = tempfile.mkstemp()
851 """Copied from TimeoutExecutable to allow the re-implementation of
854 if sys.platform ==
"win32":
865 """Code copied from both FilterExecutable and TimeoutExecutable.
869 self._ClosePipeEnd(self._stdin_pipe[0])
870 if self._stdout_pipe:
871 self._ClosePipeEnd(self._stdout_pipe[1])
872 if self._stderr_pipe:
873 self._ClosePipeEnd(self._stderr_pipe[1])
881 super(qm.executable.TimeoutExecutable, self).
_HandleChild()
888 child_pid = self._GetChildPID()
890 os.setpgid(child_pid, child_pid)
918 os.setpgid(0, child_pid)
927 max_fds = os.sysconf(
"SC_OPEN_MAX")
930 for fd
in xrange(max_fds):
941 if sys.platform ==
"linux2":
943 os.path.join(
"/proc", str(child_pid),
"exe"),
945 "-batch",
"-n",
"-x",
946 "'%s'" % os.path.join(os.path.dirname(__file__),
"stack-trace.gdb")]
949 o = os.popen(
" ".join(cmd)).read()
954 os.kill(0, signal.SIGKILL)
957 select.select ([], [], [])
962 elif self.
__timeout >= 0
and sys.platform ==
"win32":
965 self.__monitor_thread.start()
967 if sys.platform ==
"win32":
970 """Code copied from FilterExecutable.
971 Kill the child if the timeout expires.
973 This function is run in the monitoring thread."""
981 result = win32event.WaitForSingleObject(self._GetChildPID(),
984 if result == win32con.WAIT_TIMEOUT:
991 """Standard Gaudi test.
998 description=
"""The path to the program.
1000 This field indicates the path to the program. If it is not
1001 an absolute path, the value of the 'PATH' environment
1002 variable will be used to search for the program.
1003 If not specified, $GAUDIEXE or Gaudi.exe are used.
1006 qm.fields.SetField(qm.fields.TextField(
1008 title=
"Argument List",
1009 description=
"""The command-line arguments.
1011 If this field is left blank, the program is run without any
1014 Use this field to specify the option files.
1016 An implicit 0th argument (the path to the program) is added
1019 qm.fields.TextField(
1022 description=
"""Options to be passed to the application.
1024 This field allows to pass a list of options to the main program
1025 without the need of a separate option file.
1027 The content of the field is written to a temporary file which name
1028 is passed the the application as last argument (appended to the
1029 field "Argument List".
1035 qm.fields.TextField(
1037 title=
"Working Directory",
1038 description=
"""Path to the working directory.
1040 If this field is left blank, the program will be run from the qmtest
1041 directory, otherwise from the directory specified.""",
1044 qm.fields.TextField(
1046 title=
"Reference Output",
1047 description=
"""Path to the file containing the reference output.
1049 If this field is left blank, any standard output will be considered
1052 If the reference file is specified, any output on standard error is
1055 qm.fields.TextField(
1056 name=
"error_reference",
1057 title=
"Reference for standard error",
1058 description=
"""Path to the file containing the reference for the standard error.
1060 If this field is left blank, any standard output will be considered
1063 If the reference file is specified, any output on standard error is
1066 qm.fields.SetField(qm.fields.TextField(
1067 name =
"unsupported_platforms",
1068 title =
"Unsupported Platforms",
1069 description =
"""Platform on which the test must not be run.
1071 List of regular expressions identifying the platforms on which the
1072 test is not run and the result is set to UNTESTED."""
1075 qm.fields.TextField(
1077 title =
"Validator",
1078 description =
"""Function to validate the output of the test.
1080 If defined, the function is used to validate the products of the
1082 The function is called passing as arguments:
1083 self: the test class instance
1084 stdout: the standard output of the executed test
1085 stderr: the standard error of the executed test
1086 result: the Result objects to fill with messages
1087 The function must return a list of causes for the failure.
1088 If specified, overrides standard output, standard error and
1096 qm.fields.BooleanField(
1097 name =
"use_temp_dir",
1098 title =
"Use temporary directory",
1099 description =
"""Use temporary directory.
1101 If set to true, use a temporary directory as working directory.
1103 default_value=
"false"
1109 unsupported = [ re.compile(x)
1110 for x
in [ str(y).strip()
1111 for y
in self.unsupported_platforms ]
1114 for p_re
in unsupported:
1115 if p_re.search(platform):
1116 result.SetOutcome(result.UNTESTED)
1117 result[result.CAUSE] =
'Platform not supported.'
1123 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1127 if "CMTCONFIG" in os.environ:
1128 arch = os.environ[
"CMTCONFIG"]
1129 elif "SCRAM_ARCH" in os.environ:
1130 arch = os.environ[
"SCRAM_ARCH"]
1135 Return True if the current platform is Windows.
1137 This function was needed because of the change in the CMTCONFIG format,
1138 from win32_vc71_dbg to i686-winxp-vc9-dbg.
1141 return "winxp" in platform
or platform.startswith(
"win")
1149 platformSplit =
lambda p: set(p.split(
'-' in p
and '-' or '_'))
1151 reference = os.path.normpath(os.path.expandvars(reffile))
1153 spec_ref = reference[:-3] + self.
GetPlatform()[0:3] + reference[-3:]
1154 if os.path.isfile(spec_ref):
1155 reference = spec_ref
1158 dirname, basename = os.path.split(reference)
1159 if not dirname: dirname =
'.'
1160 head = basename +
"."
1161 head_len = len(head)
1164 for f
in os.listdir(dirname):
1165 if f.startswith(head):
1166 req_plat = platformSplit(f[head_len:])
1167 if platform.issuperset(req_plat):
1168 candidates.append( (len(req_plat), f) )
1173 reference = os.path.join(dirname, candidates[-1][1])
1178 ignore =
r"Basket|.*size|Compression"):
1180 Compare the TTree summaries in stdout with the ones in trees_dict or in
1181 the reference file. By default ignore the size, compression and basket
1183 The presence of TTree summaries when none is expected is not a failure.
1185 if trees_dict
is None:
1188 if reference
and os.path.isfile(reference):
1193 from pprint
import PrettyPrinter
1194 pp = PrettyPrinter()
1196 result[
"GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
1198 result[
"GaudiTest.TTrees.ignore"] = result.Quote(ignore)
1203 causes.append(
"trees summaries")
1205 result[
"GaudiTest.TTrees.failure_on"] = result.Quote(msg)
1206 result[
"GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
1214 Compare the TTree summaries in stdout with the ones in trees_dict or in
1215 the reference file. By default ignore the size, compression and basket
1217 The presence of TTree summaries when none is expected is not a failure.
1222 if reference
and os.path.isfile(reference):
1227 from pprint
import PrettyPrinter
1228 pp = PrettyPrinter()
1230 result[
"GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
1232 result[
"GaudiTest.Histos.ignore"] = result.Quote(ignore)
1237 causes.append(
"histos summaries")
1239 result[
"GaudiTest.Histos.failure_on"] = result.Quote(msg)
1240 result[
"GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
1246 Default validation action: compare standard output and error to the
1251 preproc = normalizeExamples
1255 if reference
and os.path.isfile(reference):
1256 result[
"GaudiTest.output_reference"] = reference
1259 "GaudiTest.output_diff",
1260 preproc = preproc)(stdout, result)
1268 newref = open(reference +
".new",
"w")
1270 for l
in stdout.splitlines():
1271 newref.write(l.rstrip() +
'\n')
1281 if reference
and os.path.isfile(reference):
1282 result[
"GaudiTest.error_reference"] = reference
1285 "GaudiTest.error_diff",
1286 preproc = preproc)(stderr, result)
1289 newref = open(reference +
".new",
"w")
1291 for l
in stderr.splitlines():
1292 newref.write(l.rstrip() +
'\n')
1297 "ExecTest.expected_stderr")(stderr, result)
1304 if self.validator.strip() !=
"":
1305 class CallWrapper(object):
1307 Small wrapper class to dynamically bind some default arguments
1310 def __init__(self, callable, extra_args = {}):
1314 from inspect
import getargspec
1320 def __call__(self, *args, **kwargs):
1324 kwargs = dict(kwargs)
1328 if a
not in positional
and a
not in kwargs:
1330 return apply(self.
callable, args, kwargs)
1332 exported_symbols = {
"self":self,
1337 "findReferenceBlock":
1338 CallWrapper(findReferenceBlock, {
"stdout":stdout,
1341 "validateWithReference":
1347 CallWrapper(countErrorLines, {
"stdout":stdout,
1350 "checkTTreesSummaries":
1354 "checkHistosSummaries":
1360 exec self.validator
in globals(), exported_symbols
1368 Add the content of the environment to the result object.
1370 Copied from the QMTest class of COOL.
1372 vars = os.environ.keys()
1374 result[
'GaudiTest.environment'] = \
1375 result.Quote(
'\n'.join([
"%s=%s"%(v,os.environ[v])
for v
in vars]))
1377 def Run(self, context, result):
1380 'context' -- A 'Context' giving run-time parameters to the
1383 'result' -- A 'Result' object. The outcome will be
1384 'Result.PASS' when this method is called. The 'result' may be
1385 modified by this method to indicate outcomes other than
1386 'Result.PASS' or to add annotations."""
1395 elif "GAUDIEXE" in os.environ:
1396 prog = os.environ[
"GAUDIEXE"]
1401 dummy, prog_ext = os.path.splitext(prog)
1402 if prog_ext
not in [
".exe",
".py",
".bat" ]
and self.
isWinPlatform():
1406 prog =
which(prog)
or prog
1409 args = map(rationalizepath, self.args)
1416 if self.options.strip():
1418 if re.search(
r"from\s*Gaudi.Configuration\s*import\s*\*", self.options):
1421 tmpfile.writelines(
"\n".join(self.options.splitlines()))
1423 args.append(tmpfile.name)
1424 result[
"GaudiTest.options"] = result.Quote(self.options)
1427 if prog_ext ==
".py":
1430 prog =
which(
"python.exe")
or "python.exe"
1432 prog =
which(
"python")
or "python"
1435 origdir = os.getcwd()
1437 os.chdir(str(os.path.normpath(os.path.expandvars(self.workdir))))
1439 if "QMTEST_TMPDIR" in os.environ:
1440 qmtest_tmpdir = os.environ[
"QMTEST_TMPDIR"]
1441 if not os.path.exists(qmtest_tmpdir):
1442 os.makedirs(qmtest_tmpdir)
1443 os.chdir(qmtest_tmpdir)
1444 elif "qmtest.tmpdir" in context:
1445 os.chdir(context[
"qmtest.tmpdir"])
1447 if "QMTEST_IGNORE_TIMEOUT" not in os.environ:
1460 if result.GetOutcome()
not in [ result.PASS ]:
1467 """Run the 'program'.
1469 'program' -- The path to the program to run.
1471 'arguments' -- A list of the arguments to the program. This
1472 list must contain a first argument corresponding to 'argv[0]'.
1474 'context' -- A 'Context' giving run-time parameters to the
1477 'result' -- A 'Result' object. The outcome will be
1478 'Result.PASS' when this method is called. The 'result' may be
1479 modified by this method to indicate outcomes other than
1480 'Result.PASS' or to add annotations.
1482 @attention: This method has been copied from command.ExecTestBase
1483 (QMTest 2.3.0) and modified to keep stdout and stderr
1484 for tests that have been terminated by a signal.
1485 (Fundamental for debugging in the Application Area)
1489 environment = self.MakeEnvironment(context)
1491 if "slc6" in environment.get(
'CMTCONFIG',
''):
1492 environment[
'TERM'] =
'dumb'
1505 exit_status = e.Run(arguments, environment, path = program)
1507 if e.stack_trace_file
and os.path.exists(e.stack_trace_file):
1508 stack_trace = open(e.stack_trace_file).read()
1509 os.remove(e.stack_trace_file)
1513 result[
"ExecTest.stack_trace"] = result.Quote(stack_trace)
1516 if sys.platform ==
"win32" or os.WIFEXITED(exit_status):
1521 if self.exit_code
is None:
1523 elif sys.platform ==
"win32":
1524 exit_code = exit_status
1526 exit_code = os.WEXITSTATUS(exit_status)
1531 result[
"ExecTest.exit_code"] = str(exit_code)
1532 result[
"ExecTest.stdout"] = result.Quote(stdout)
1533 result[
"ExecTest.stderr"] = result.Quote(stderr)
1535 if exit_code != self.exit_code:
1536 causes.append(
"exit_code")
1537 result[
"ExecTest.expected_exit_code"] \
1538 = str(self.exit_code)
1543 result.Fail(
"Unexpected %s." % string.join(causes,
", "))
1544 elif os.WIFSIGNALED(exit_status):
1547 signal_number = str(os.WTERMSIG(exit_status))
1549 result.Fail(
"Program terminated by signal.")
1553 result.Fail(
"Exceeded time limit (%ds), terminated." % timeout)
1554 result[
"ExecTest.signal_number"] = signal_number
1555 result[
"ExecTest.stdout"] = result.Quote(e.stdout)
1556 result[
"ExecTest.stderr"] = result.Quote(e.stderr)
1557 elif os.WIFSTOPPED(exit_status):
1560 signal_number = str(os.WSTOPSIG(exit_status))
1562 result.Fail(
"Program stopped by signal.")
1566 result.Fail(
"Exceeded time limit (%ds), stopped." % timeout)
1567 result[
"ExecTest.signal_number"] = signal_number
1568 result[
"ExecTest.stdout"] = result.Quote(e.stdout)
1569 result[
"ExecTest.stderr"] = result.Quote(e.stderr)
1573 result.Fail(
"Program did not terminate normally.")
1579 result[
"ExecTest.stdout"] = result[
"ExecTest.stdout"].replace(esc,repr_esc)
1586 projbasedir = os.path.normpath(destdir)
1587 while not os.path.exists(os.path.join(projbasedir,
".project")):
1588 oldprojdir = projbasedir
1589 projbasedir = os.path.normpath(os.path.join(projbasedir, os.pardir))
1592 if oldprojdir == projbasedir:
1596 if not os.path.exists(destdir):
1597 os.makedirs(destdir)
1599 from xml.etree
import ElementTree
as ET
1600 t = ET.parse(os.path.join(projbasedir,
".project"))
1601 projectName = t.find(
"name").text
1604 destfile =
"%s.launch" % self._Runnable__id
1606 destfile = os.path.join(destdir, destfile)
1608 if self.options.strip():
1612 tempfile = args.pop()
1613 optsfile = destfile + os.path.splitext(tempfile)[1]
1614 shutil.copyfile(tempfile, optsfile)
1615 args.append(optsfile)
1618 from xml.sax.saxutils
import quoteattr
1622 data[
"environment"] =
"\n".join([
'<mapEntry key=%s value=%s/>' % (quoteattr(k), quoteattr(v))
1623 for k, v
in os.environ.iteritems()])
1625 data[
"exec"] =
which(prog)
or prog
1626 if os.path.basename(data[
"exec"]).lower().startswith(
"python"):
1627 data[
"stopAtMain"] =
"false"
1629 data[
"stopAtMain"] =
"true"
1631 data[
"args"] =
" ".join(map(rationalizepath, args))
1633 data[
"args"] =
" ".join([
"/debugexe"] + map(rationalizepath, [data[
"exec"]] + args))
1634 data[
"exec"] =
which(
"vcexpress.exe")
1637 data[
"workdir"] = os.getcwd()
1641 data[
"workdir"] = destdir
1643 data[
"project"] = projectName.strip()
1646 xml =
"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
1647 <launchConfiguration type="org.eclipse.cdt.launch.applicationLaunchType">
1648 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB" value="true"/>
1649 <listAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB_LIST"/>
1650 <stringAttribute key="org.eclipse.cdt.debug.mi.core.DEBUG_NAME" value="gdb"/>
1651 <stringAttribute key="org.eclipse.cdt.debug.mi.core.GDB_INIT" value=".gdbinit"/>
1652 <listAttribute key="org.eclipse.cdt.debug.mi.core.SOLIB_PATH"/>
1653 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.STOP_ON_SOLIB_EVENTS" value="false"/>
1654 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.breakpointsFullPath" value="false"/>
1655 <stringAttribute key="org.eclipse.cdt.debug.mi.core.commandFactory" value="org.eclipse.cdt.debug.mi.core.standardCommandFactory"/>
1656 <stringAttribute key="org.eclipse.cdt.debug.mi.core.protocol" value="mi"/>
1657 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.verboseMode" value="false"/>
1658 <intAttribute key="org.eclipse.cdt.launch.ATTR_BUILD_BEFORE_LAUNCH_ATTR" value="0"/>
1659 <stringAttribute key="org.eclipse.cdt.launch.COREFILE_PATH" value=""/>
1660 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_ID" value="org.eclipse.cdt.debug.mi.core.CDebuggerNew"/>
1661 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_REGISTER_GROUPS" value=""/>
1662 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_START_MODE" value="run"/>
1663 <booleanAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN" value="%(stopAtMain)s"/>
1664 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN_SYMBOL" value="main"/>
1665 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_REGISTER_BOOKKEEPING" value="false"/>
1666 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_VARIABLE_BOOKKEEPING" value="false"/>
1667 <stringAttribute key="org.eclipse.cdt.launch.FORMAT" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?><contentList/>"/>
1668 <stringAttribute key="org.eclipse.cdt.launch.GLOBAL_VARIABLES" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <globalVariableList/> "/>
1669 <stringAttribute key="org.eclipse.cdt.launch.MEMORY_BLOCKS" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <memoryBlockExpressionList/> "/>
1670 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_ARGUMENTS" value="%(args)s"/>
1671 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="%(exec)s"/>
1672 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_ATTR" value="%(project)s"/>
1673 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value=""/>
1674 <stringAttribute key="org.eclipse.cdt.launch.WORKING_DIRECTORY" value="%(workdir)s"/>
1675 <booleanAttribute key="org.eclipse.cdt.launch.ui.ApplicationCDebuggerTab.DEFAULTS_SET" value="true"/>
1676 <booleanAttribute key="org.eclipse.cdt.launch.use_terminal" value="true"/>
1677 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
1678 <listEntry value="/%(project)s"/>
1680 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
1681 <listEntry value="4"/>
1683 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="false"/>
1684 <mapAttribute key="org.eclipse.debug.core.environmentVariables">
1687 <mapAttribute key="org.eclipse.debug.core.preferred_launchers">
1688 <mapEntry key="[debug]" value="org.eclipse.cdt.cdi.launch.localCLaunch"/>
1690 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
1691 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
1693 </launchConfiguration>
1697 open(destfile,
"w").write(xml)
1705 import simplejson
as json
1708 """An 'HTMLResultStream' writes its output to a set of HTML files.
1710 The argument 'dir' is used to select the destination directory for the HTML
1712 The destination directory may already contain the report from a previous run
1713 (for example of a different package), in which case it will be extended to
1714 include the new data.
1717 qm.fields.TextField(
1719 title =
"Destination Directory",
1720 description =
"""The name of the directory.
1722 All results will be written to the directory indicated.""",
1724 default_value =
""),
1728 """Prepare the destination directory.
1730 Creates the destination directory and store in it some preliminary
1731 annotations and the static files found in the template directory
1734 ResultStream.__init__(self, arguments, **args)
1739 templateDir = os.path.join(os.path.dirname(__file__),
"html_report")
1740 if not os.path.isdir(self.dir):
1741 os.makedirs(self.dir)
1743 for f
in os.listdir(templateDir):
1744 src = os.path.join(templateDir, f)
1745 dst = os.path.join(self.dir, f)
1746 if not os.path.isdir(src)
and not os.path.exists(dst):
1747 shutil.copy(src, dst)
1749 if "CMTCONFIG" in os.environ:
1755 """Helper function to extend the global summary file in the destination
1762 ids = set([ i[
"id"]
for i
in self.
_summary ])
1763 newSummary = [ i
for i
in oldSummary
if i[
"id"]
not in ids ]
1769 """Writes the annotation to the annotation file.
1770 If the key is already present with a different value, the value becomes
1771 a list and the new value is appended to it, except for start_time and
1780 key, value = map(str, [key, value])
1781 if key ==
"qmtest.run.start_time":
1786 if key
not in annotations:
1787 annotations[key] = value
1788 if "qmtest.run.end_time" in annotations:
1789 del annotations[
"qmtest.run.end_time"]
1792 if key
in annotations:
1793 old = annotations[key]
1794 if type(old)
is list:
1795 if value
not in old:
1796 annotations[key].append(value)
1798 annotations[key] = [old, value]
1800 annotations[key] = value
1806 """Prepare the test result directory in the destination directory storing
1807 into it the result fields.
1808 A summary of the test result is stored both in a file in the test directory
1809 and in the global summary file.
1812 summary[
"id"] = result.GetId()
1813 summary[
"outcome"] = result.GetOutcome()
1814 summary[
"cause"] = result.GetCause()
1815 summary[
"fields"] = result.keys()
1816 summary[
"fields"].sort()
1819 for f
in [
"id",
"outcome",
"cause"]:
1820 summary[f] = str(summary[f])
1821 summary[
"fields"] = map(str, summary[
"fields"])
1823 self._summary.append(summary)
1829 testOutDir = os.path.join(self.dir, summary[
"id"])
1830 if not os.path.isdir(testOutDir):
1831 os.makedirs(testOutDir)
1832 json.dump(summary, open(os.path.join(testOutDir,
"summary.json"),
"w"),
1834 for f
in summary[
"fields"]:
1835 open(os.path.join(testOutDir, f),
"w").write(result[f])