14 from subprocess
import Popen, PIPE, STDOUT
18 Take a string with invalid ASCII/UTF characters and quote them so that the
19 string can be used in an XML text.
21 >>> sanitize_for_xml('this is \x1b')
22 'this is [NON-XML-CHAR-0x1B]'
24 bad_chars = re.compile(
u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')
27 return ''.join(
'[NON-XML-CHAR-0x%2X]' % ord(c)
for c
in match.group())
28 return bad_chars.sub(quote, data)
31 '''helper to debug GAUDI-1084, dump the list of processes'''
32 from getpass
import getuser
33 if 'WORKSPACE' in os.environ:
34 p = Popen([
'ps',
'-fH',
'-U', getuser()], stdout=PIPE)
35 with open(os.path.join(os.environ[
'WORKSPACE'], name),
'w')
as f:
36 f.write(p.communicate()[0])
40 Send a signal to a process and all its child processes (starting from the
43 log = logging.getLogger(
'kill_tree')
44 ps_cmd = [
'ps',
'--no-headers',
'-o',
'pid',
'--ppid', str(ppid)]
45 get_children = Popen(ps_cmd, stdout=PIPE, stderr=PIPE)
46 children =
map(int, get_children.communicate()[0].split())
47 for child
in children:
50 log.debug(
'killing process %d', ppid)
55 log.debug(
'no such process %d', ppid)
58 class BaseTest(object):
66 self.error_reference =
''
71 self.environment =
None
72 self.unsupported_platforms = []
74 self.workdir = os.curdir
75 self.use_temp_dir =
False
80 self.result = Result(self)
85 self.stack_trace =
None
86 self.basedir = os.getcwd()
88 def validator(self, stdout='',stderr=''):
92 logging.debug(
'running test %s', self.name)
95 if re.search(
r'from\s+Gaudi.Configuration\s+import\s+\*|'
96 'from\s+Configurables\s+import', self.options):
97 optionFile = tempfile.NamedTemporaryFile(suffix=
'.py')
99 optionFile = tempfile.NamedTemporaryFile(suffix=
'.opts')
100 optionFile.file.write(self.options)
105 if self.environment
is None : self.environment = os.environ
106 else : self.environment=dict(self.environment.items()+os.environ.items())
108 platform_id = (os.environ.get(
'BINARY_TAG')
or
109 os.environ.get(
'CMTCONFIG')
or
112 skip_test = bool([
None
113 for prex
in self.unsupported_platforms
114 if re.search(prex, platform_id)])
118 workdir = self.workdir
119 if self.use_temp_dir:
120 if self._common_tmpdir:
121 workdir = self._common_tmpdir
123 workdir = tempfile.mkdtemp()
127 if self.program !=
'':
129 elif "GAUDIEXE" in os.environ :
130 prog = os.environ[
"GAUDIEXE"]
134 dummy, prog_ext = os.path.splitext(prog)
135 if prog_ext
not in [
".exe",
".py",
".bat" ]:
139 prog =
which(prog)
or prog
141 args =
map(RationalizePath, self.args)
143 if prog_ext ==
".py" :
148 validatorRes = Result({
'CAUSE':
None,
'EXCEPTION':
None,
149 'RESOURCE':
None,
'TARGET':
None,
150 'TRACEBACK':
None,
'START_TIME':
None,
151 'END_TIME':
None,
'TIMEOUT_DETAIL':
None})
152 self.result = validatorRes
160 logging.debug(
'executing %r in %s',
162 self.proc = Popen(params, stdout=PIPE, stderr=PIPE,
163 env=self.environment)
164 logging.debug(
'(pid: %d)', self.proc.pid)
165 self.out, self.err = self.proc.communicate()
167 thread = threading.Thread(target=target)
170 thread.join(self.timeout)
172 if thread.is_alive():
173 logging.debug(
'time out in test %s (pid %d)', self.name, self.proc.pid)
175 cmd = [
'gdb',
'--pid', str(self.proc.pid),
'--batch',
176 '--eval-command=thread apply all backtrace']
177 gdb = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
178 self.stack_trace = gdb.communicate()[0]
182 if thread.is_alive():
184 self.causes.append(
'timeout')
186 logging.debug(
'completed test %s', self.name)
189 logging.debug(
'returnedCode = %s', self.proc.returncode)
190 self.returnedCode = self.proc.returncode
192 logging.debug(
'validating test...')
193 self.result, self.causes = self.ValidateOutput(stdout=self.out,
198 if self.use_temp_dir
and not self._common_tmpdir:
199 shutil.rmtree(workdir,
True)
201 os.chdir(self.basedir)
204 if self.signal
is not None:
205 if int(self.returnedCode) != -int(self.signal):
206 self.causes.append(
'exit code')
208 elif self.exit_code
is not None:
209 if int(self.returnedCode) != int(self.exit_code):
210 self.causes.append(
'exit code')
212 elif self.returnedCode != 0:
213 self.causes.append(
"exit code")
216 self.status =
"failed"
218 self.status =
"passed"
221 self.status =
"skipped"
223 logging.debug(
'%s: %s', self.name, self.status)
224 field_mapping = {
'Exit Code':
'returnedCode',
227 'Environment':
'environment',
230 'Program Name':
'program',
232 'Validator':
'validator',
233 'Output Reference File':
'reference',
234 'Error Reference File':
'error_reference',
237 'Unsupported Platforms':
'unsupported_platforms',
238 'Stack Trace':
'stack_trace'}
239 resultDict = [(key, getattr(self, attr))
240 for key, attr
in field_mapping.iteritems()
241 if getattr(self, attr)]
242 resultDict.append((
'Working Directory',
246 resultDict.extend(self.result.annotations.iteritems())
248 return dict(resultDict)
255 def ValidateOutput(self, stdout, stderr, result):
257 if self.validator
is not BaseTest.validator:
258 self.validator(stdout, stderr, result, self.causes,
259 self.reference, self.error_reference)
261 if self.stderr ==
'':
262 self.validateWithReference(stdout, stderr, result, causes)
263 elif stderr.strip() != self.stderr.strip():
264 self.causes.append(
'standard error')
267 return result, causes
271 def findReferenceBlock(self,reference=None, stdout=None, result=None, causes=None, signature_offset=0, signature=None, id = None):
273 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.
276 if reference
is None : reference=self.reference
277 if stdout
is None : stdout=self.out
278 if result
is None : result=self.result
279 if causes
is None : causes=self.causes
281 reflines = filter(
None,
map(
lambda s: s.rstrip(), reference.splitlines()))
283 raise RuntimeError(
"Empty (or null) reference")
285 outlines = filter(
None,
map(
lambda s: s.rstrip(), stdout.splitlines()))
287 res_field =
"GaudiTest.RefBlock"
289 res_field +=
"_%s" % id
291 if signature
is None:
292 if signature_offset < 0:
293 signature_offset = len(reference)+signature_offset
294 signature = reflines[signature_offset]
297 pos = outlines.index(signature)
298 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
299 if reflines != outlines:
300 msg =
"standard output"
302 if not msg
in causes:
304 result[res_field +
".observed"] = result.Quote(
"\n".join(outlines))
306 causes.append(
"missing signature")
307 result[res_field +
".signature"] = result.Quote(signature)
308 if len(reflines) > 1
or signature != reflines[0]:
309 result[res_field +
".expected"] = result.Quote(
"\n".join(reflines))
312 def countErrorLines(self, expected = {'ERROR':0,
'FATAL':0}, stdout=
None, result=
None,causes=
None):
314 Count the number of messages with required severity (by default ERROR and FATAL)
315 and check if their numbers match the expected ones (0 by default).
316 The dictionary "expected" can be used to tune the number of errors and fatals
317 allowed, or to limit the number of expected warnings etc.
320 if stdout
is None : stdout=self.out
321 if result
is None : result=self.result
322 if causes
is None : causes=self.causes
329 outlines = stdout.splitlines()
330 from math
import log10
331 fmt =
"%%%dd - %%s" % (int(log10(len(outlines)+1)))
337 if len(words) >= 2
and words[1]
in errors:
338 errors[words[1]].append(fmt%(linecount,l.rstrip()))
341 if len(errors[e]) != expected[e]:
342 causes.append(
'%s(%d)'%(e,len(errors[e])))
343 result[
"GaudiTest.lines.%s"%e] = result.Quote(
'\n'.join(errors[e]))
344 result[
"GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
348 def CheckTTreesSummaries(self, stdout=None, result=None, causes=None,
350 ignore =
r"Basket|.*size|Compression"):
352 Compare the TTree summaries in stdout with the ones in trees_dict or in
353 the reference file. By default ignore the size, compression and basket
355 The presence of TTree summaries when none is expected is not a failure.
357 if stdout
is None : stdout=self.out
358 if result
is None : result=self.result
359 if causes
is None : causes=self.causes
360 if trees_dict
is None:
361 lreference = self._expandReferenceFileName(self.reference)
363 if lreference
and os.path.isfile(lreference):
368 from pprint
import PrettyPrinter
371 result[
"GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
373 result[
"GaudiTest.TTrees.ignore"] = result.Quote(ignore)
378 causes.append(
"trees summaries")
380 result[
"GaudiTest.TTrees.failure_on"] = result.Quote(msg)
381 result[
"GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
385 def CheckHistosSummaries(self, stdout=None, result=None, causes=None,
389 Compare the TTree summaries in stdout with the ones in trees_dict or in
390 the reference file. By default ignore the size, compression and basket
392 The presence of TTree summaries when none is expected is not a failure.
394 if stdout
is None : stdout=self.out
395 if result
is None : result=self.result
396 if causes
is None : causes=self.causes
399 lreference = self._expandReferenceFileName(self.reference)
401 if lreference
and os.path.isfile(lreference):
406 from pprint
import PrettyPrinter
409 result[
"GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
411 result[
"GaudiTest.Histos.ignore"] = result.Quote(ignore)
416 causes.append(
"histos summaries")
418 result[
"GaudiTest.Histos.failure_on"] = result.Quote(msg)
419 result[
"GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
423 def validateWithReference(self, stdout=None, stderr=None, result=None,
424 causes=
None, preproc=
None):
426 Default validation acti*on: compare standard output and error to the
430 if stdout
is None : stdout = self.out
431 if stderr
is None : stderr = self.err
432 if result
is None : result = self.result
433 if causes
is None : causes = self.causes
437 preproc = normalizeExamples
439 lreference = self._expandReferenceFileName(self.reference)
441 if lreference
and os.path.isfile(lreference):
442 causes += ReferenceFileValidator(lreference,
445 preproc=preproc)(stdout, result)
447 causes = self.CheckTTreesSummaries(stdout, result, causes)
448 causes = self.CheckHistosSummaries(stdout, result, causes)
451 newref = open(lreference +
".new",
"w")
453 for l
in stdout.splitlines():
454 newref.write(l.rstrip() +
'\n')
462 lreference = self._expandReferenceFileName(self.error_reference)
464 if lreference
and os.path.isfile(lreference):
465 newcauses = ReferenceFileValidator(lreference,
468 preproc=preproc)(stderr, result)
471 newref = open(lreference +
".new",
"w")
473 for l
in stderr.splitlines():
474 newref.write(l.rstrip() +
'\n')
477 causes += BasicOutputValidator(lreference,
"standard error",
"ExecTest.expected_stderr")(stderr, result)
480 def _expandReferenceFileName(self, reffile):
486 platformSplit =
lambda p: set(p.split(
'-' in p
and '-' or '_'))
488 reference = os.path.normpath(os.path.join(self.basedir,
489 os.path.expandvars(reffile)))
492 spec_ref = reference[:-3] +
GetPlatform(self)[0:3] + reference[-3:]
493 if os.path.isfile(spec_ref):
497 dirname, basename = os.path.split(reference)
498 if not dirname: dirname =
'.'
499 head = basename +
"."
502 if 'do0' in platform:
505 for f
in os.listdir(dirname):
506 if f.startswith(head):
507 req_plat = platformSplit(f[head_len:])
508 if platform.issuperset(req_plat):
509 candidates.append( (len(req_plat), f) )
514 reference = os.path.join(dirname, candidates[-1][1])
529 from GaudiKernel
import ROOT6WorkAroundEnabled
539 Function used to normalize the used path
541 newPath = os.path.normpath(os.path.expandvars(p))
542 if os.path.exists(newPath) :
543 p = os.path.realpath(newPath)
547 def which(executable):
549 Locates an executable in the executables path ($PATH) and returns the full
550 path to it. An application is looked for with or without the '.exe' suffix.
551 If the executable cannot be found, None is returned
553 if os.path.isabs(executable):
554 if not os.path.exists(executable):
555 if executable.endswith(
'.exe'):
556 if os.path.exists(executable[:-4]):
557 return executable[:-4]
559 head,executable = os.path.split(executable)
562 for d
in os.environ.get(
"PATH").split(os.pathsep):
563 fullpath = os.path.join(d, executable)
564 if os.path.exists(fullpath):
566 if executable.endswith(
'.exe'):
567 return which(executable[:-4])
592 def __init__(self,kind=None,id=None,outcome=PASS,annotations={}):
593 self.annotations = annotations.copy()
595 def __getitem__(self,key):
596 assert type(key)
in types.StringTypes
597 return self.annotations[key]
599 def __setitem__(self,key,value):
600 assert type(key)
in types.StringTypes
601 assert type(value)
in types.StringTypes
602 self.annotations[key]=value
604 def Quote(self,string):
615 class BasicOutputValidator:
617 def __init__(self,ref,cause,result_key):
620 self.result_key=result_key
622 def __call__(self,out,result):
623 """Validate the output of the program.
624 'stdout' -- A string containing the data written to the standard output
626 'stderr' -- A string containing the data written to the standard error
628 'result' -- A 'Result' object. It may be used to annotate
629 the outcome according to the content of stderr.
630 returns -- A list of strings giving causes of failure."""
634 if not self.__CompareText(out,self.ref):
635 causes.append(self.cause)
636 result[self.result_key] =result.Quote(self.ref)
642 def __CompareText(self, s1, s2):
643 """Compare 's1' and 's2', ignoring line endings.
646 returns -- True if 's1' and 's2' are the same, ignoring
647 differences in line endings."""
650 to_ignore = re.compile(
r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*')
651 keep_line =
lambda l:
not to_ignore.match(l)
652 return filter(keep_line, s1.splitlines()) == filter(keep_line, s2.splitlines())
654 return s1.splitlines() == s2.splitlines()
659 class FilePreprocessor:
660 """ Base class for a callable that takes a file and returns a modified
662 def __processLine__(self, line):
664 def __call__(self, input):
665 if hasattr(input,
"__iter__"):
669 lines = input.splitlines()
673 l = self.__processLine__(l)
674 if l: output.append(l)
675 if mergeback: output =
'\n'.join(output)
677 def __add__(self, rhs):
678 return FilePreprocessorSequence([self,rhs])
680 class FilePreprocessorSequence(FilePreprocessor):
681 def __init__(self, members = []):
682 self.members = members
683 def __add__(self, rhs):
684 return FilePreprocessorSequence(self.members + [rhs])
685 def __call__(self, input):
687 for pp
in self.members:
691 class LineSkipper(FilePreprocessor):
692 def __init__(self, strings = [], regexps = []):
694 self.strings = strings
695 self.regexps =
map(re.compile,regexps)
697 def __processLine__(self, line):
698 for s
in self.strings:
699 if line.find(s) >= 0:
return None
700 for r
in self.regexps:
701 if r.search(line):
return None
704 class BlockSkipper(FilePreprocessor):
705 def __init__(self, start, end):
708 self._skipping =
False
710 def __processLine__(self, line):
711 if self.start
in line:
712 self._skipping =
True
714 elif self.end
in line:
715 self._skipping =
False
720 class RegexpReplacer(FilePreprocessor):
721 def __init__(self, orig, repl = "", when = None):
723 when = re.compile(when)
724 self._operations = [ (when, re.compile(orig), repl) ]
725 def __add__(self,rhs):
726 if isinstance(rhs, RegexpReplacer):
727 res = RegexpReplacer(
"",
"",
None)
728 res._operations = self._operations + rhs._operations
730 res = FilePreprocessor.__add__(self, rhs)
732 def __processLine__(self, line):
733 for w,o,r
in self._operations:
734 if w
is None or w.search(line):
735 line = o.sub(r, line)
739 maskPointers = RegexpReplacer(
"0x[0-9a-fA-F]{4,16}",
"0x########")
740 normalizeDate = RegexpReplacer(
"[0-2]?[0-9]:[0-5][0-9]:[0-5][0-9] [0-9]{4}[-/][01][0-9][-/][0-3][0-9][ A-Z]*",
741 "00:00:00 1970-01-01")
742 normalizeEOL = FilePreprocessor()
743 normalizeEOL.__processLine__ =
lambda line: str(line).rstrip() +
'\n'
745 skipEmptyLines = FilePreprocessor()
747 skipEmptyLines.__processLine__ =
lambda line: (line.strip()
and line)
or None
751 class LineSorter(FilePreprocessor):
752 def __init__(self, signature):
753 self.signature = signature
754 self.siglen = len(signature)
755 def __processLine__(self, line):
756 pos = line.find(self.signature)
758 line = line[:(pos+self.siglen)]
759 lst = line[(pos+self.siglen):].split()
761 line +=
" ".join(lst)
765 normalizeExamples = maskPointers + normalizeDate
768 (
"TIMER.TIMER",
r"\s+[+-]?[0-9]+[0-9.]*",
" 0"),
769 (
"release all pending",
r"^.*/([^/]*:.*)",
r"\1"),
770 (
"0x########",
r"\[.*/([^/]*.*)\]",
r"[\1]"),
771 (
"^#.*file",
r"file '.*[/\\]([^/\\]*)$",
r"file '\1"),
772 (
"^JobOptionsSvc.*options successfully read in from",
r"read in from .*[/\\]([^/\\]*)$",
r"file \1"),
774 (
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"),
776 (
"ServiceLocatorHelper::",
"ServiceLocatorHelper::(create|locate)Service",
"ServiceLocatorHelper::service"),
778 (
None,
r"e([-+])0([0-9][0-9])",
r"e\1\2"),
780 (
None,
r'Service reference count check:',
r'Looping over all active services...'),
782 (
None,
r"Property(.*)'ErrorCount':",
r"Property\1'ErrorCounter':"),
784 normalizeExamples += RegexpReplacer(o,r,w)
786 lineSkipper = LineSkipper([
"//GP:",
787 "JobOptionsSvc INFO # ",
788 "JobOptionsSvc WARNING # ",
791 "This machine has a speed",
794 "ToolSvc.Sequenc... INFO",
795 "DataListenerSvc INFO XML written to file:",
796 "[INFO]",
"[WARNING]",
797 "DEBUG No writable file catalog found which contains FID:",
799 "DEBUG Service base class initialized successfully",
800 "DEBUG Incident timing:",
801 "INFO 'CnvServices':[",
805 r"^JobOptionsSvc INFO *$",
807 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
808 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
809 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
810 r"File '.*.xml' does not exist",
811 r"INFO Refer to dataset .* by its file ID:",
812 r"INFO Referring to dataset .* by its file ID:",
813 r"INFO Disconnect from dataset",
814 r"INFO Disconnected from dataset",
815 r"INFO Disconnected data IO:",
816 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
818 r"^StatusCodeSvc.*listing all unchecked return codes:",
819 r"^StatusCodeSvc\s*INFO\s*$",
820 r"Num\s*\|\s*Function\s*\|\s*Source Library",
823 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
825 r"^ +[0-9]+ \|.*ROOT",
826 r"^ +[0-9]+ \|.*\|.*Dict",
828 r"StatusCodeSvc.*all StatusCode instances where checked",\
832 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
839 lineSkipper += LineSkipper(regexps = [
840 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
843 normalizeExamples = (lineSkipper + normalizeExamples + skipEmptyLines +
844 normalizeEOL + LineSorter(
"Services to release : "))
849 class ReferenceFileValidator:
850 def __init__(self,reffile, cause, result_key, preproc=normalizeExamples):
851 self.reffile = os.path.expandvars(reffile)
853 self.result_key = result_key
854 self.preproc = preproc
856 def __call__(self,stdout, result) :
858 if os.path.isfile(self.reffile):
859 orig=open(self.reffile).xreadlines()
861 orig = self.preproc(orig)
864 new = stdout.splitlines()
866 new = self.preproc(new)
868 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
869 filterdiffs =
map(
lambda x: x.strip(),filter(
lambda x: x[0] !=
" ",diffs))
871 result[self.result_key] = result.Quote(
"\n".join(filterdiffs))
872 result[self.result_key] += result.Quote(
"""
875 +) standard output of the test""")
876 causes.append(self.cause)
881 Scan stdout to find ROOT TTree summaries and digest them.
883 stars = re.compile(
r"^\*+$")
884 outlines = stdout.splitlines()
885 nlines = len(outlines)
891 while i < nlines
and not stars.match(outlines[i]):
896 trees[tree[
"Name"]] = tree
902 Check that all the keys in reference are in to_check too, with the same value.
903 If the value is a dict, the function is called recursively. to_check can
904 contain more keys than reference, that will not be tested.
905 The function returns at the first difference found.
910 ignore_re = re.compile(ignore)
911 keys = [ key
for key
in reference
if not ignore_re.match(key) ]
913 keys = reference.keys()
917 if (
type(reference[k])
is dict)
and (
type(to_check[k])
is dict):
919 failed = fail_keys =
cmpTreesDicts(reference[k], to_check[k], ignore)
922 failed = to_check[k] != reference[k]
927 fail_keys.insert(0, k)
937 if c
is None or r
is None:
939 return (fail_path, r, c)
942 h_count_re = re.compile(
r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
947 Parse the TTree summary table in lines, starting from pos.
948 Returns a tuple with the dictionary with the digested informations and the
949 position of the first line after the summary.
955 splitcols =
lambda l: [ f.strip()
for f
in l.strip(
"*\n").split(
':',2) ]
958 cols = splitcols(ll[0])
959 r[
"Name"], r[
"Title"] = cols[1:]
961 cols = splitcols(ll[1])
962 r[
"Entries"] = int(cols[1])
964 sizes = cols[2].split()
965 r[
"Total size"] = int(sizes[2])
966 if sizes[-1] ==
"memory":
969 r[
"File size"] = int(sizes[-1])
971 cols = splitcols(ll[2])
972 sizes = cols[2].split()
973 if cols[0] ==
"Baskets":
974 r[
"Baskets"] = int(cols[1])
975 r[
"Basket size"] = int(sizes[2])
976 r[
"Compression"] = float(sizes[-1])
979 if i < (count - 3)
and lines[i].startswith(
"*Tree"):
980 result = parseblock(lines[i:i+3])
981 result[
"Branches"] = {}
983 while i < (count - 3)
and lines[i].startswith(
"*Br"):
984 if i < (count - 2)
and lines[i].startswith(
"*Branch "):
988 branch = parseblock(lines[i:i+3])
989 result[
"Branches"][branch[
"Name"]] = branch
996 Extract the histograms infos from the lines starting at pos.
997 Returns the position of the first line after the summary block.
1000 h_table_head = re.compile(
r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
1001 h_short_summ = re.compile(
r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
1006 m = h_count_re.search(lines[pos])
1007 name = m.group(1).strip()
1008 total = int(m.group(2))
1010 for k, v
in [ x.split(
"=")
for x
in m.group(3).split() ]:
1013 header[
"Total"] = total
1017 m = h_table_head.search(lines[pos])
1020 t = t.replace(
" profile",
"Prof")
1027 if l.startswith(
" | ID"):
1029 titles = [ x.strip()
for x
in l.split(
"|")][1:]
1031 while pos < nlines
and lines[pos].startswith(
" |"):
1033 values = [ x.strip()
for x
in l.split(
"|")][1:]
1035 for i
in range(len(titles)):
1036 hcont[titles[i]] = values[i]
1037 cont[hcont[
"ID"]] = hcont
1039 elif l.startswith(
" ID="):
1040 while pos < nlines
and lines[pos].startswith(
" ID="):
1041 values = [ x.strip()
for x
in h_short_summ.search(lines[pos]).groups() ]
1042 cont[values[0]] = values
1045 raise RuntimeError(
"Cannot understand line %d: '%s'" % (pos, l))
1049 summ[d][
"header"] = header
1054 summ[name] = {
"header": header}
1061 Scan stdout to find ROOT TTree summaries and digest them.
1063 outlines = stdout.splitlines()
1064 nlines = len(outlines) - 1
1072 match = h_count_re.search(outlines[pos])
1073 while pos < nlines
and not match:
1075 match = h_count_re.search(outlines[pos])
1078 summaries.update(summ)
1083 unsupported = [ re.compile(x)
for x
in [ str(y).strip()
for y
in unsupported_platforms ]
if x]
1084 for p_re
in unsupported :
1085 if p_re.search(platform):
1086 result.SetOutcome(result.UNTESTED)
1087 result[result.CAUSE] =
'Platform not supported.'
1093 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1097 if "CMTCONFIG" in os.environ:
1098 arch = os.environ[
"CMTCONFIG"]
1099 elif "SCRAM_ARCH" in os.environ:
1100 arch = os.environ[
"SCRAM_ARCH"]
1105 Return True if the current platform is Windows.
1107 This function was needed because of the change in the CMTCONFIG format,
1108 from win32_vc71_dbg to i686-winxp-vc9-dbg.
1111 return "winxp" in platform
or platform.startswith(
"win")
def findTTreeSummaries(stdout)
def sanitize_for_xml(data)
def findHistosSummaries(stdout)
def getCmpFailingValues(reference, to_check, fail_path)
def _parseTTreeSummary(lines, pos)
struct GAUDI_API map
Parametrisation class for map-like implementation.
NamedRange_< CONTAINER > range(const CONTAINER &cnt, std::string name)
simple function to create the named range form arbitrary container
def parseHistosSummary(lines, pos)
def PlatformIsNotSupported(self, context, result)
def ROOT6WorkAroundEnabled