5 from subprocess
import *
14 import xml.sax.saxutils
as XSS
48 begining = int(time.time())
49 DOB = time.localtime()
50 dateOfBegining = str(DOB[1])+
"/"+str(DOB[2])+
"/"+str(DOB[0])+
" "+str(DOB[3])+
":"+str(DOB[4])+
":"+str(DOB[5])
53 if self.options !=
'' :
54 if self.options.startswith(
"import")
or self.options.startswith(
"from")
or self.options.startswith(
"\nfrom")
or self.options.startswith(
"\nimport")
or (self.options.startswith(
"\n#")
and not self.options.startswith(
'\n#include')):
55 optionFile = tempfile.NamedTemporaryFile(suffix=
'.py')
57 optionFile = tempfile.NamedTemporaryFile(suffix=
'.opts')
58 optionFile.file.write(self.options)
63 if self.environment
is None : self.environment = os.environ
64 else : self.environment=dict(self.environment.items()+os.environ.items())
70 if self.program !=
'' :
72 elif "GAUDIEXE" in os.environ :
73 prog = os.environ[
"GAUDIEXE"]
77 dummy, prog_ext = os.path.splitext(prog)
78 if prog_ext
not in [
".exe",
".py",
".bat" ]:
82 prog =
which(prog)
or prog
84 if prog_ext ==
".py" :
85 params = [
"/afs/cern.ch/user/v/valentin/workspace/Gaudi/build.x86_64-slc6-gcc48-opt/run",
"/afs/cern.ch/sw/lcg/external/Python/2.7.3/x86_64-slc6-gcc48-opt/bin/python",
RationalizePath(prog)]+self.args
87 params = [
"/afs/cern.ch/user/v/valentin/workspace/Gaudi/build.x86_64-slc6-gcc48-opt/run",
RationalizePath(prog)]+self.args
91 self.proc= Popen(params,stdout=PIPE,stderr=PIPE, env=self.environment)
94 thread = threading.Thread(target=target)
97 thread.join(self.timeout)
100 self.proc.send_signal(signal.SIGABRT)
104 self.out = self.proc.stdout.read()
106 self.err = self.proc.stderr.read()
108 self.returnedCode = self.proc.returncode
111 for p
in self.unsupported_platforms :
112 if re.search(p,platform.platform()):
121 lasted = end-begining
123 DOE = time.localtime()
124 dateOfEnding = str(DOE[1])+
"/"+str(DOE[2])+
"/"+str(DOE[0])+
" "+str(DOE[3])+
":"+str(DOE[4])+
":"+str(DOE[5])
127 validatorRes = Result({
'CAUSE':
None,
'EXCEPTION':
None,
'RESOURCE':
None,
'TARGET':
None,
'TRACEBACK':
None,
'START_TIME':
None,
'END_TIME':
None,
'TIMEOUT_DETAIL':
None})
128 self.result=validatorRes
131 self.result[
"Abort cause"]=self.result.Quote(
"Event Timeout")
133 self.result,self.causes=self.ValidateOutput(stdout=self.out,stderr=self.err,result=validatorRes)
136 self.status =
"Failed"
137 if self.signal
is not None :
138 if (int(self.returnedCode) - int(self.signal) - 128)!=0:
139 self.causes.append(
'wrong return code')
140 if self.exit_code
is not None:
141 if int(self.returnedCode) != int(self.exit_code) :
142 self.causes.append(
'wrong return code')
143 if self.returnedCode!=0
and self.exit_code
is None and self.signal
is None:
144 self.causes.append(
"Return code !=0")
145 if self.causes == []:
146 self.status =
"Passed"
148 self.status=
"SKIPPED"
150 resultDic = {
'Execution Time':lasted,
'Exit code':self.returnedCode,
'Start Time':dateOfBegining,
'End Time':dateOfEnding,
'Stderr':self.err,
'Arguments':self.args,
'Environment':self.environment,
'Expected stderr':self.stderr,
'Status':self.status,
'Measurement':self.out,
'Program Name':self.program,
'Name':self.name,
'Validator':self.validator,
'Reference file':self.reference,
'Error reference file':self.error_reference,
'Causes':self.causes,
'Validator results':self.result.annotations,
'Unsupported platforms':self.unsupported_platforms}
163 if inspect.getsource(self.
validator)!=
""" def validator(self, stdout='',stderr=''):
166 self.
validator(stdout, stderr, result, causes, reference, error_reference)
171 self.causes.append(
"DIFFERENT STDERR THAN EXPECTED")
178 def findReferenceBlock(self,reference=None, stdout=None, result=None, causes=None, signature_offset=0, signature=None, id = None):
180 Given a block of text, tries to find it in the output. The block had to be identified by a signature line. By default, the first line is used as signature, or the line pointed to by signature_offset. If signature_offset points outside the block, a signature line can be passed as signature argument. Note: if 'signature' is None (the default), a negative signature_offset is interpreted as index in a list (e.g. -1 means the last line), otherwise the it is interpreted as the number of lines before the first one of the block the signature must appear. The parameter 'id' allow to distinguish between different calls to this function in the same validation code.
183 if reference
is None : reference=self.
reference
184 if stdout
is None : stdout=self.
out
185 if result
is None : result=self.
result
186 if causes
is None : causes=self.
causes
188 reflines = filter(
None,
map(
lambda s: s.rstrip(), reference.splitlines()))
190 raise RuntimeError(
"Empty (or null) reference")
192 outlines = filter(
None,
map(
lambda s: s.rstrip(), stdout.splitlines()))
194 res_field =
"GaudiTest.RefBlock"
196 res_field +=
"_%s" % id
198 if signature
is None:
199 if signature_offset < 0:
200 signature_offset = len(reference)+signature_offset
201 signature = reflines[signature_offset]
204 pos = outlines.index(signature)
205 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
206 if reflines != outlines:
207 msg =
"standard output"
209 if not msg
in causes:
211 result[res_field +
".observed"] = result.Quote(
"\n".join(outlines))
213 causes.append(
"missing signature")
214 result[res_field +
".signature"] = result.Quote(signature)
215 if len(reflines) > 1
or signature != reflines[0]:
216 result[res_field +
".expected"] = result.Quote(
"\n".join(reflines))
219 def countErrorLines(self, expected = {'ERROR':0,
'FATAL':0}, stdout=
None, result=
None,causes=
None):
221 Count the number of messages with required severity (by default ERROR and FATAL)
222 and check if their numbers match the expected ones (0 by default).
223 The dictionary "expected" can be used to tune the number of errors and fatals
224 allowed, or to limit the number of expected warnings etc.
227 if stdout
is None : stdout=self.
out
228 if result
is None : result=self.
result
229 if causes
is None : causes=self.
causes
236 outlines = stdout.splitlines()
237 from math
import log10
238 fmt =
"%%%dd - %%s" % (int(log10(len(outlines)+1)))
244 if len(words) >= 2
and words[1]
in errors:
245 errors[words[1]].append(fmt%(linecount,l.rstrip()))
248 if len(errors[e]) != expected[e]:
249 causes.append(
'%s(%d)'%(e,len(errors[e])))
250 result[
"GaudiTest.lines.%s"%e] = result.Quote(
'\n'.join(errors[e]))
251 result[
"GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
257 ignore =
r"Basket|.*size|Compression"):
259 Compare the TTree summaries in stdout with the ones in trees_dict or in
260 the reference file. By default ignore the size, compression and basket
262 The presence of TTree summaries when none is expected is not a failure.
264 if stdout
is None : stdout=self.
out
265 if result
is None : result=self.
result
266 if causes
is None : causes=self.
causes
267 if trees_dict
is None:
270 if lreference
and os.path.isfile(lreference):
275 from pprint
import PrettyPrinter
278 result[
"GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
280 result[
"GaudiTest.TTrees.ignore"] = result.Quote(ignore)
285 causes.append(
"trees summaries")
287 result[
"GaudiTest.TTrees.failure_on"] = result.Quote(msg)
288 result[
"GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
296 Compare the TTree summaries in stdout with the ones in trees_dict or in
297 the reference file. By default ignore the size, compression and basket
299 The presence of TTree summaries when none is expected is not a failure.
301 if stdout
is None : stdout=self.
out
302 if result
is None : result=self.
result
303 if causes
is None : causes=self.
causes
308 if lreference
and os.path.isfile(lreference):
313 from pprint
import PrettyPrinter
316 result[
"GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
318 result[
"GaudiTest.Histos.ignore"] = result.Quote(ignore)
323 causes.append(
"histos summaries")
325 result[
"GaudiTest.Histos.failure_on"] = result.Quote(msg)
326 result[
"GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
332 Default validation action: compare standard output and error to the
336 if stdout
is None : stdout=self.
out
337 if stderr
is None : stderr=self.
err
338 if result
is None : result=self.
result
339 if causes
is None : causes=self.
causes
343 preproc = normalizeExamples
347 if lreference
and os.path.isfile(lreference):
348 result[
"GaudiTest.output_reference"] = lreference
351 "GaudiTest.output_diff",
352 preproc = preproc)(stdout, result)
358 newref = open(lreference +
".new",
"w")
360 for l
in stdout.splitlines():
361 newref.write(l.rstrip() +
'\n')
373 newcauses =
ReferenceFileValidator(lreference,
"standard error",
"GaudiTest.error_diff", preproc = preproc)(stderr, result)
376 newref = open(self.
reference +
".new",
"w")
378 for l
in stderr.splitlines():
379 newref.write(l.rstrip() +
'\n')
382 causes +=
BasicOutputValidator(lreference,
"standard error",
"ExecTest.expected_stderr")(stderr, result)
396 from subprocess
import Popen, PIPE, STDOUT
398 import xml.etree.ElementTree
as ET
401 from GaudiKernel
import ROOT6WorkAroundEnabled
407 if sys.platform ==
"win32":
410 from threading
import *
427 Function used to normalize the used path
429 newPath = os.path.normpath(os.path.expandvars(p))
430 if os.path.exists(newPath) :
431 p = os.path.realpath(newPath)
432 p = os.path.realpath(newPath)
438 Locates an executable in the executables path ($PATH) and returns the full
439 path to it. An application is looked for with or without the '.exe' suffix.
440 If the executable cannot be found, None is returned
442 if os.path.isabs(executable):
443 if not os.path.exists(executable):
444 if executable.endswith(
'.exe'):
445 if os.path.exists(executable[:-4]):
446 return executable[:-4]
448 head,executable = os.path.split(executable)
451 for d
in os.environ.get(
"PATH").split(os.pathsep):
452 fullpath = os.path.join(d, executable)
453 if os.path.exists(fullpath):
455 if executable.endswith(
'.exe'):
456 return which(executable[:-4])
481 def __init__(self,kind=None,id=None,outcome=PASS,annotations={}):
485 assert type(key)
in types.StringTypes
489 assert type(key)
in types.StringTypes
490 assert type(value)
in types.StringTypes
512 """Validate the output of the program.
513 'stdout' -- A string containing the data written to the standard output
515 'stderr' -- A string containing the data written to the standard error
517 'result' -- A 'Result' object. It may be used to annotate
518 the outcome according to the content of stderr.
519 returns -- A list of strings giving causes of failure."""
524 causes.append(self.
cause)
532 """Compare 's1' and 's2', ignoring line endings.
535 returns -- True if 's1' and 's2' are the same, ignoring
536 differences in line endings."""
539 to_ignore = re.compile(
r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*')
540 keep_line =
lambda l:
not to_ignore.match(l)
541 return filter(keep_line, s1.splitlines()) == filter(keep_line, s2.splitlines())
543 return s1.splitlines() == s2.splitlines()
549 """ Base class for a callable that takes a file and returns a modified
554 if hasattr(input,
"__iter__"):
558 lines = input.splitlines()
563 if l: output.append(l)
564 if mergeback: output =
'\n'.join(output)
588 if line.find(s) >= 0:
return None
590 if r.search(line):
return None
600 if self.
start in line:
603 elif self.
end in line:
612 when = re.compile(when)
615 if isinstance(rhs, RegexpReplacer):
617 res._operations = self.
_operations + rhs._operations
619 res = FilePreprocessor.__add__(self, rhs)
623 if w
is None or w.search(line):
624 line = o.sub(r, line)
629 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)?",
630 "00:00:00 1970-01-01")
632 normalizeEOL.__processLine__ =
lambda line: str(line).rstrip() +
'\n'
636 skipEmptyLines.__processLine__ =
lambda line: (line.strip()
and line)
or None
647 line = line[:(pos+self.
siglen)]
648 lst = line[(pos+self.
siglen):].split()
650 line +=
" ".join(lst)
654 normalizeExamples = maskPointers + normalizeDate
657 (
"TIMER.TIMER",
r"\s+[+-]?[0-9]+[0-9.]*",
" 0"),
658 (
"release all pending",
r"^.*/([^/]*:.*)",
r"\1"),
659 (
"0x########",
r"\[.*/([^/]*.*)\]",
r"[\1]"),
660 (
"^#.*file",
r"file '.*[/\\]([^/\\]*)$",
r"file '\1"),
661 (
"^JobOptionsSvc.*options successfully read in from",
r"read in from .*[/\\]([^/\\]*)$",
r"file \1"),
663 (
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"),
665 (
"ServiceLocatorHelper::",
"ServiceLocatorHelper::(create|locate)Service",
"ServiceLocatorHelper::service"),
667 (
None,
r"e([-+])0([0-9][0-9])",
r"e\1\2"),
669 (
None,
r'Service reference count check:',
r'Looping over all active services...'),
674 "JobOptionsSvc INFO # ",
675 "JobOptionsSvc WARNING # ",
678 "This machine has a speed",
681 "ToolSvc.Sequenc... INFO",
682 "DataListenerSvc INFO XML written to file:",
683 "[INFO]",
"[WARNING]",
684 "DEBUG No writable file catalog found which contains FID:",
686 "DEBUG Service base class initialized successfully",
687 "DEBUG Incident timing:",
688 "INFO 'CnvServices':[",
692 r"^JobOptionsSvc INFO *$",
694 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
695 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
696 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
697 r"File '.*.xml' does not exist",
698 r"INFO Refer to dataset .* by its file ID:",
699 r"INFO Referring to dataset .* by its file ID:",
700 r"INFO Disconnect from dataset",
701 r"INFO Disconnected from dataset",
702 r"INFO Disconnected data IO:",
703 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
705 r"^StatusCodeSvc.*listing all unchecked return codes:",
706 r"^StatusCodeSvc\s*INFO\s*$",
707 r"Num\s*\|\s*Function\s*\|\s*Source Library",
710 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
712 r"^ +[0-9]+ \|.*ROOT",
713 r"^ +[0-9]+ \|.*\|.*Dict",
717 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
725 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
728 normalizeExamples = (lineSkipper + normalizeExamples + skipEmptyLines +
729 normalizeEOL +
LineSorter(
"Services to release : "))
735 def __init__(self,reffile, cause, result_key, preproc=normalizeExamples):
743 if os.path.isfile(self.
reffile):
744 orig=open(self.
reffile).xreadlines()
749 new = stdout.splitlines()
753 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
754 filterdiffs =
map(
lambda x: x.strip(),filter(
lambda x: x[0] !=
" ",diffs))
756 result[self.
result_key] = result.Quote(
"\n".join(filterdiffs))
760 +) standard output of the test""")
761 causes.append(self.
cause)
766 Scan stdout to find ROOT TTree summaries and digest them.
768 stars = re.compile(
r"^\*+$")
769 outlines = stdout.splitlines()
770 nlines = len(outlines)
776 while i < nlines
and not stars.match(outlines[i]):
781 trees[tree[
"Name"]] = tree
787 Check that all the keys in reference are in to_check too, with the same value.
788 If the value is a dict, the function is called recursively. to_check can
789 contain more keys than reference, that will not be tested.
790 The function returns at the first difference found.
795 ignore_re = re.compile(ignore)
796 keys = [ key
for key
in reference
if not ignore_re.match(key) ]
798 keys = reference.keys()
802 if (
type(reference[k])
is dict)
and (
type(to_check[k])
is dict):
804 failed = fail_keys =
cmpTreesDicts(reference[k], to_check[k], ignore)
807 failed = to_check[k] != reference[k]
812 fail_keys.insert(0, k)
822 if c
is None or r
is None:
824 return (fail_path, r, c)
827 h_count_re = re.compile(
r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
832 Parse the TTree summary table in lines, starting from pos.
833 Returns a tuple with the dictionary with the digested informations and the
834 position of the first line after the summary.
840 splitcols =
lambda l: [ f.strip()
for f
in l.strip(
"*\n").split(
':',2) ]
843 cols = splitcols(ll[0])
844 r[
"Name"], r[
"Title"] = cols[1:]
846 cols = splitcols(ll[1])
847 r[
"Entries"] = int(cols[1])
849 sizes = cols[2].split()
850 r[
"Total size"] = int(sizes[2])
851 if sizes[-1] ==
"memory":
854 r[
"File size"] = int(sizes[-1])
856 cols = splitcols(ll[2])
857 sizes = cols[2].split()
858 if cols[0] ==
"Baskets":
859 r[
"Baskets"] = int(cols[1])
860 r[
"Basket size"] = int(sizes[2])
861 r[
"Compression"] = float(sizes[-1])
864 if i < (count - 3)
and lines[i].startswith(
"*Tree"):
865 result = parseblock(lines[i:i+3])
866 result[
"Branches"] = {}
868 while i < (count - 3)
and lines[i].startswith(
"*Br"):
869 if i < (count - 2)
and lines[i].startswith(
"*Branch "):
873 branch = parseblock(lines[i:i+3])
874 result[
"Branches"][branch[
"Name"]] = branch
881 Extract the histograms infos from the lines starting at pos.
882 Returns the position of the first line after the summary block.
885 h_table_head = re.compile(
r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
886 h_short_summ = re.compile(
r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
891 m = h_count_re.search(lines[pos])
892 name = m.group(1).strip()
893 total = int(m.group(2))
895 for k, v
in [ x.split(
"=")
for x
in m.group(3).split() ]:
898 header[
"Total"] = total
902 m = h_table_head.search(lines[pos])
905 t = t.replace(
" profile",
"Prof")
912 if l.startswith(
" | ID"):
914 titles = [ x.strip()
for x
in l.split(
"|")][1:]
916 while pos < nlines
and lines[pos].startswith(
" |"):
918 values = [ x.strip()
for x
in l.split(
"|")][1:]
920 for i
in range(len(titles)):
921 hcont[titles[i]] = values[i]
922 cont[hcont[
"ID"]] = hcont
924 elif l.startswith(
" ID="):
925 while pos < nlines
and lines[pos].startswith(
" ID="):
926 values = [ x.strip()
for x
in h_short_summ.search(lines[pos]).groups() ]
927 cont[values[0]] = values
930 raise RuntimeError(
"Cannot understand line %d: '%s'" % (pos, l))
934 summ[d][
"header"] = header
939 summ[name] = {
"header": header}
946 Scan stdout to find ROOT TTree summaries and digest them.
948 outlines = stdout.splitlines()
949 nlines = len(outlines) - 1
957 match = h_count_re.search(outlines[pos])
958 while pos < nlines
and not match:
960 match = h_count_re.search(outlines[pos])
963 summaries.update(summ)
968 unsupported = [ re.compile(x)
for x
in [ str(y).strip()
for y
in unsupported_platforms ]
if x]
969 for p_re
in unsupported :
970 if p_re.search(platform):
971 result.SetOutcome(result.UNTESTED)
972 result[result.CAUSE] =
'Platform not supported.'
978 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
982 if "CMTCONFIG" in os.environ:
983 arch = os.environ[
"CMTCONFIG"]
984 elif "SCRAM_ARCH" in os.environ:
985 arch = os.environ[
"SCRAM_ARCH"]
990 Return True if the current platform is Windows.
992 This function was needed because of the change in the CMTCONFIG format,
993 from win32_vc71_dbg to i686-winxp-vc9-dbg.
996 return "winxp" in platform
or platform.startswith(
"win")
1005 platformSplit =
lambda p: set(p.split(
'-' in p
and '-' or '_'))
1007 reference = os.path.normpath(os.path.expandvars(reffile))
1009 spec_ref = reference[:-3] +
GetPlatform(self)[0:3] + reference[-3:]
1010 if os.path.isfile(spec_ref):
1011 reference = spec_ref
1014 dirname, basename = os.path.split(reference)
1015 if not dirname: dirname =
'.'
1016 head = basename +
"."
1017 head_len = len(head)
1020 for f
in os.listdir(dirname):
1021 if f.startswith(head):
1022 req_plat = platformSplit(f[head_len:])
1023 if platform.issuperset(req_plat):
1024 candidates.append( (len(req_plat), f) )
1029 reference = os.path.join(dirname, candidates[-1][1])
def ROOT6WorkAroundEnabled
def validateWithReference
def PlatformIsNotSupported
struct GAUDI_API map
Parametrisation class for map-like implementation.
Special preprocessor sorting the list of strings (whitespace separated) that follow a signature on a ...
def _expandReferenceFileName
NamedRange_< CONTAINER > range(const CONTAINER &cnt, const std::string &name)
simple function to create the named range form arbitrary container