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)
89 logging.debug(
'running test %s', self.
name)
92 if re.search(
r'from\s+Gaudi.Configuration\s+import\s+\*|' 93 'from\s+Configurables\s+import', self.
options):
94 optionFile = tempfile.NamedTemporaryFile(suffix=
'.py')
96 optionFile = tempfile.NamedTemporaryFile(suffix=
'.opts')
97 optionFile.file.write(self.
options)
103 else : self.
environment=dict(self.environment.items()+os.environ.items())
105 platform_id = (os.environ.get(
'BINARY_TAG')
or 106 os.environ.get(
'CMTCONFIG')
or 109 skip_test = bool([
None 111 if re.search(prex, platform_id)])
120 workdir = tempfile.mkdtemp()
126 elif "GAUDIEXE" in os.environ :
127 prog = os.environ[
"GAUDIEXE"]
131 dummy, prog_ext = os.path.splitext(prog)
132 if prog_ext
not in [
".exe",
".py",
".bat" ]:
136 prog =
which(prog)
or prog
138 args =
map(RationalizePath, self.
args)
140 if prog_ext ==
".py" :
145 validatorRes =
Result({
'CAUSE':
None,
'EXCEPTION':
None,
146 'RESOURCE':
None,
'TARGET':
None,
147 'TRACEBACK':
None,
'START_TIME':
None,
148 'END_TIME':
None,
'TIMEOUT_DETAIL':
None})
149 self.
result = validatorRes
157 logging.debug(
'executing %r in %s',
159 self.
proc = Popen(params, stdout=PIPE, stderr=PIPE,
161 logging.debug(
'(pid: %d)', self.proc.pid)
162 self.
out, self.
err = self.proc.communicate()
164 thread = threading.Thread(target=target)
169 if thread.is_alive():
170 logging.debug(
'time out in test %s (pid %d)', self.
name, self.proc.pid)
172 cmd = [
'gdb',
'--pid', str(self.proc.pid),
'--batch',
173 '--eval-command=thread apply all backtrace']
174 gdb = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
179 if thread.is_alive():
181 self.causes.append(
'timeout')
183 logging.debug(
'completed test %s', self.
name)
186 logging.debug(
'returnedCode = %s', self.proc.returncode)
189 logging.debug(
'validating test...')
196 shutil.rmtree(workdir,
True)
201 if self.
signal is not None:
203 self.causes.append(
'exit code')
207 self.causes.append(
'exit code')
210 self.causes.append(
"exit code")
220 logging.debug(
'%s: %s', self.
name, self.
status)
221 field_mapping = {
'Exit Code':
'returnedCode',
224 'Environment':
'environment',
227 'Program Name':
'program',
229 'Validator':
'validator',
230 'Output Reference File':
'reference',
231 'Error Reference File':
'error_reference',
234 'Unsupported Platforms':
'unsupported_platforms',
235 'Stack Trace':
'stack_trace'}
236 resultDict = [(key, getattr(self, attr))
237 for key, attr
in field_mapping.iteritems()
238 if getattr(self, attr)]
239 resultDict.append((
'Working Directory',
243 resultDict.extend(self.result.annotations.iteritems())
245 return dict(resultDict)
255 elif stderr.strip() != self.stderr.strip():
256 self.causes.append(
'standard error')
257 return result, self.
causes 259 def findReferenceBlock(self,reference=None, stdout=None, result=None, causes=None, signature_offset=0, signature=None, id = None):
261 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. 264 if reference
is None : reference=self.
reference 265 if stdout
is None : stdout=self.
out 266 if result
is None : result=self.
result 267 if causes
is None : causes=self.
causes 269 reflines = filter(
None,
map(
lambda s: s.rstrip(), reference.splitlines()))
271 raise RuntimeError(
"Empty (or null) reference")
273 outlines = filter(
None,
map(
lambda s: s.rstrip(), stdout.splitlines()))
275 res_field =
"GaudiTest.RefBlock" 277 res_field +=
"_%s" % id
279 if signature
is None:
280 if signature_offset < 0:
281 signature_offset = len(reference)+signature_offset
282 signature = reflines[signature_offset]
285 pos = outlines.index(signature)
286 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
287 if reflines != outlines:
288 msg =
"standard output" 290 if not msg
in causes:
292 result[res_field +
".observed"] = result.Quote(
"\n".join(outlines))
294 causes.append(
"missing signature")
295 result[res_field +
".signature"] = result.Quote(signature)
296 if len(reflines) > 1
or signature != reflines[0]:
297 result[res_field +
".expected"] = result.Quote(
"\n".join(reflines))
300 def countErrorLines(self, expected = {'ERROR':0,
'FATAL':0}, stdout=
None, result=
None,causes=
None):
302 Count the number of messages with required severity (by default ERROR and FATAL) 303 and check if their numbers match the expected ones (0 by default). 304 The dictionary "expected" can be used to tune the number of errors and fatals 305 allowed, or to limit the number of expected warnings etc. 308 if stdout
is None : stdout=self.
out 309 if result
is None : result=self.
result 310 if causes
is None : causes=self.
causes 317 outlines = stdout.splitlines()
318 from math
import log10
319 fmt =
"%%%dd - %%s" % (int(log10(len(outlines)+1)))
325 if len(words) >= 2
and words[1]
in errors:
326 errors[words[1]].append(fmt%(linecount,l.rstrip()))
329 if len(errors[e]) != expected[e]:
330 causes.append(
'%s(%d)'%(e,len(errors[e])))
331 result[
"GaudiTest.lines.%s"%e] = result.Quote(
'\n'.join(errors[e]))
332 result[
"GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
338 ignore =
r"Basket|.*size|Compression"):
340 Compare the TTree summaries in stdout with the ones in trees_dict or in 341 the reference file. By default ignore the size, compression and basket 343 The presence of TTree summaries when none is expected is not a failure. 345 if stdout
is None : stdout=self.
out 346 if result
is None : result=self.
result 347 if causes
is None : causes=self.
causes 348 if trees_dict
is None:
351 if lreference
and os.path.isfile(lreference):
356 from pprint
import PrettyPrinter
359 result[
"GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
361 result[
"GaudiTest.TTrees.ignore"] = result.Quote(ignore)
366 causes.append(
"trees summaries")
368 result[
"GaudiTest.TTrees.failure_on"] = result.Quote(msg)
369 result[
"GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
377 Compare the TTree summaries in stdout with the ones in trees_dict or in 378 the reference file. By default ignore the size, compression and basket 380 The presence of TTree summaries when none is expected is not a failure. 382 if stdout
is None : stdout=self.
out 383 if result
is None : result=self.
result 384 if causes
is None : causes=self.
causes 389 if lreference
and os.path.isfile(lreference):
394 from pprint
import PrettyPrinter
397 result[
"GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
399 result[
"GaudiTest.Histos.ignore"] = result.Quote(ignore)
404 causes.append(
"histos summaries")
406 result[
"GaudiTest.Histos.failure_on"] = result.Quote(msg)
407 result[
"GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
412 causes=
None, preproc=
None):
414 Default validation acti*on: compare standard output and error to the 418 if stdout
is None : stdout = self.
out 419 if stderr
is None : stderr = self.
err 420 if result
is None : result = self.
result 421 if causes
is None : causes = self.
causes 425 preproc = normalizeExamples
429 if lreference
and os.path.isfile(lreference):
433 preproc=preproc)(stdout, result)
439 newref = open(lreference +
".new",
"w")
441 for l
in stdout.splitlines():
442 newref.write(l.rstrip() +
'\n')
452 if lreference
and os.path.isfile(lreference):
456 preproc=preproc)(stderr, result)
459 newref = open(lreference +
".new",
"w")
461 for l
in stderr.splitlines():
462 newref.write(l.rstrip() +
'\n')
465 causes +=
BasicOutputValidator(lreference,
"standard error",
"ExecTest.expected_stderr")(stderr, result)
474 platformSplit =
lambda p: set(p.split(
'-' in p
and '-' or '_'))
476 reference = os.path.normpath(os.path.join(self.
basedir,
477 os.path.expandvars(reffile)))
480 spec_ref = reference[:-3] +
GetPlatform(self)[0:3] + reference[-3:]
481 if os.path.isfile(spec_ref):
485 dirname, basename = os.path.split(reference)
486 if not dirname: dirname =
'.' 487 head = basename +
"." 490 if 'do0' in platform:
493 for f
in os.listdir(dirname):
494 if f.startswith(head):
495 req_plat = platformSplit(f[head_len:])
496 if platform.issuperset(req_plat):
497 candidates.append( (len(req_plat), f) )
502 reference = os.path.join(dirname, candidates[-1][1])
517 from GaudiKernel
import ROOT6WorkAroundEnabled
527 Function used to normalize the used path 529 newPath = os.path.normpath(os.path.expandvars(p))
530 if os.path.exists(newPath) :
531 p = os.path.realpath(newPath)
537 Locates an executable in the executables path ($PATH) and returns the full 538 path to it. An application is looked for with or without the '.exe' suffix. 539 If the executable cannot be found, None is returned 541 if os.path.isabs(executable):
542 if not os.path.exists(executable):
543 if executable.endswith(
'.exe'):
544 if os.path.exists(executable[:-4]):
545 return executable[:-4]
547 head,executable = os.path.split(executable)
550 for d
in os.environ.get(
"PATH").split(os.pathsep):
551 fullpath = os.path.join(d, executable)
552 if os.path.exists(fullpath):
554 if executable.endswith(
'.exe'):
555 return which(executable[:-4])
580 def __init__(self,kind=None,id=None,outcome=PASS,annotations={}):
584 assert type(key)
in types.StringTypes
588 assert type(key)
in types.StringTypes
589 assert type(value)
in types.StringTypes
611 """Validate the output of the program. 612 'stdout' -- A string containing the data written to the standard output 614 'stderr' -- A string containing the data written to the standard error 616 'result' -- A 'Result' object. It may be used to annotate 617 the outcome according to the content of stderr. 618 returns -- A list of strings giving causes of failure.""" 623 causes.append(self.
cause)
631 """Compare 's1' and 's2', ignoring line endings. 634 returns -- True if 's1' and 's2' are the same, ignoring 635 differences in line endings.""" 638 to_ignore = re.compile(
r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*')
639 keep_line =
lambda l:
not to_ignore.match(l)
640 return filter(keep_line, s1.splitlines()) == filter(keep_line, s2.splitlines())
642 return s1.splitlines() == s2.splitlines()
648 """ Base class for a callable that takes a file and returns a modified 656 if l: output.append(l)
659 if hasattr(input,
"__iter__"):
663 lines = input.splitlines()
666 if mergeback: output =
'\n'.join(output)
690 if line.find(s) >= 0:
return None 692 if r.search(line):
return None 702 if self.
start in line:
705 elif self.
end in line:
714 when = re.compile(when)
717 if isinstance(rhs, RegexpReplacer):
719 res._operations = self.
_operations + rhs._operations
721 res = FilePreprocessor.__add__(self, rhs)
725 if w
is None or w.search(line):
726 line = o.sub(r, line)
731 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]*",
732 "00:00:00 1970-01-01")
734 normalizeEOL.__processLine__ =
lambda line: str(line).rstrip() +
'\n' 738 skipEmptyLines.__processLine__ =
lambda line: (line.strip()
and line)
or None 749 line = line[:(pos+self.
siglen)]
750 lst = line[(pos+self.
siglen):].split()
752 line +=
" ".join(lst)
757 Sort group of lines matching a regular expression 760 self.
exp = exp
if hasattr(exp,
'match')
else re.compile(exp)
762 match = self.exp.match
777 normalizeExamples = maskPointers + normalizeDate
780 (
"TIMER.TIMER",
r"\s+[+-]?[0-9]+[0-9.]*",
" 0"),
781 (
"release all pending",
r"^.*/([^/]*:.*)",
r"\1"),
782 (
"^#.*file",
r"file '.*[/\\]([^/\\]*)$",
r"file '\1"),
783 (
"^JobOptionsSvc.*options successfully read in from",
r"read in from .*[/\\]([^/\\]*)$",
r"file \1"),
785 (
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"),
787 (
"ServiceLocatorHelper::",
"ServiceLocatorHelper::(create|locate)Service",
"ServiceLocatorHelper::service"),
789 (
None,
r"e([-+])0([0-9][0-9])",
r"e\1\2"),
791 (
None,
r'Service reference count check:',
r'Looping over all active services...'),
793 (
None,
r"^(.*(DEBUG|SUCCESS) List of ALL properties of .*#properties = )\d+",
r"\1NN"),
798 "JobOptionsSvc INFO # ",
799 "JobOptionsSvc WARNING # ",
802 "This machine has a speed",
805 "ToolSvc.Sequenc... INFO",
806 "DataListenerSvc INFO XML written to file:",
807 "[INFO]",
"[WARNING]",
808 "DEBUG No writable file catalog found which contains FID:",
809 "DEBUG Service base class initialized successfully",
810 "DEBUG Incident timing:",
811 "INFO 'CnvServices':[",
815 r"^JobOptionsSvc INFO *$",
817 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
818 r"File '.*.xml' does not exist",
819 r"INFO Refer to dataset .* by its file ID:",
820 r"INFO Referring to dataset .* by its file ID:",
821 r"INFO Disconnect from dataset",
822 r"INFO Disconnected from dataset",
823 r"INFO Disconnected data IO:",
824 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
826 r"^StatusCodeSvc.*listing all unchecked return codes:",
827 r"^StatusCodeSvc\s*INFO\s*$",
828 r"Num\s*\|\s*Function\s*\|\s*Source Library",
831 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
833 r"^ +[0-9]+ \|.*ROOT",
834 r"^ +[0-9]+ \|.*\|.*Dict",
836 r"StatusCodeSvc.*all StatusCode instances where checked",
838 r"EventLoopMgr.*---> Loop Finished",
842 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
846 r"Property(.*)'Audit(Algorithm|Tool|Service)s':",
847 r"Property(.*)'AuditRe(start|initialize)':",
848 r"Property(.*)'IsIOBound':",
849 r"Property(.*)'ErrorCount(er)?':",
850 r"Property(.*)'Sequential':",
852 r"Property update for OutputLevel : new value =",
853 r"EventLoopMgr\s*DEBUG Creating OutputStream",
859 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
862 normalizeExamples = (lineSkipper + normalizeExamples + skipEmptyLines +
863 normalizeEOL +
LineSorter(
"Services to release : ") +
869 def __init__(self,reffile, cause, result_key, preproc=normalizeExamples):
877 if os.path.isfile(self.
reffile):
878 orig = open(self.
reffile).xreadlines()
882 result.Quote(
'\n'.join(
map(str.strip, orig)))
885 new = stdout.splitlines()
889 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
890 filterdiffs =
map(
lambda x: x.strip(),filter(
lambda x: x[0] !=
" ",diffs))
892 result[self.
result_key] = result.Quote(
"\n".join(filterdiffs))
896 +) standard output of the test""")
898 result.Quote(
'\n'.join(
map(str.strip, new)))
899 causes.append(self.
cause)
904 Scan stdout to find ROOT TTree summaries and digest them. 906 stars = re.compile(
r"^\*+$")
907 outlines = stdout.splitlines()
908 nlines = len(outlines)
914 while i < nlines
and not stars.match(outlines[i]):
919 trees[tree[
"Name"]] = tree
925 Check that all the keys in reference are in to_check too, with the same value. 926 If the value is a dict, the function is called recursively. to_check can 927 contain more keys than reference, that will not be tested. 928 The function returns at the first difference found. 933 ignore_re = re.compile(ignore)
934 keys = [ key
for key
in reference
if not ignore_re.match(key) ]
936 keys = reference.keys()
940 if (
type(reference[k])
is dict)
and (
type(to_check[k])
is dict):
942 failed = fail_keys =
cmpTreesDicts(reference[k], to_check[k], ignore)
945 failed = to_check[k] != reference[k]
950 fail_keys.insert(0, k)
960 if c
is None or r
is None:
962 return (fail_path, r, c)
965 h_count_re = re.compile(
r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+([\s\w=-]*)")
970 Parse the TTree summary table in lines, starting from pos. 971 Returns a tuple with the dictionary with the digested informations and the 972 position of the first line after the summary. 978 splitcols =
lambda l: [ f.strip()
for f
in l.strip(
"*\n").split(
':',2) ]
981 cols = splitcols(ll[0])
982 r[
"Name"], r[
"Title"] = cols[1:]
984 cols = splitcols(ll[1])
985 r[
"Entries"] = int(cols[1])
987 sizes = cols[2].split()
988 r[
"Total size"] = int(sizes[2])
989 if sizes[-1] ==
"memory":
992 r[
"File size"] = int(sizes[-1])
994 cols = splitcols(ll[2])
995 sizes = cols[2].split()
996 if cols[0] ==
"Baskets":
997 r[
"Baskets"] = int(cols[1])
998 r[
"Basket size"] = int(sizes[2])
999 r[
"Compression"] = float(sizes[-1])
1002 if i < (count - 3)
and lines[i].startswith(
"*Tree"):
1003 result = parseblock(lines[i:i+3])
1004 result[
"Branches"] = {}
1006 while i < (count - 3)
and lines[i].startswith(
"*Br"):
1007 if i < (count - 2)
and lines[i].startswith(
"*Branch "):
1011 branch = parseblock(lines[i:i+3])
1012 result[
"Branches"][branch[
"Name"]] = branch
1019 Extract the histograms infos from the lines starting at pos. 1020 Returns the position of the first line after the summary block. 1023 h_table_head = re.compile(
r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
1024 h_short_summ = re.compile(
r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
1029 m = h_count_re.search(lines[pos])
1030 name = m.group(1).strip()
1031 total = int(m.group(2))
1033 for k, v
in [ x.split(
"=")
for x
in m.group(3).split() ]:
1036 header[
"Total"] = total
1040 m = h_table_head.search(lines[pos])
1043 t = t.replace(
" profile",
"Prof")
1050 if l.startswith(
" | ID"):
1052 titles = [ x.strip()
for x
in l.split(
"|")][1:]
1054 while pos < nlines
and lines[pos].startswith(
" |"):
1056 values = [ x.strip()
for x
in l.split(
"|")][1:]
1058 for i
in range(len(titles)):
1059 hcont[titles[i]] = values[i]
1060 cont[hcont[
"ID"]] = hcont
1062 elif l.startswith(
" ID="):
1063 while pos < nlines
and lines[pos].startswith(
" ID="):
1064 values = [ x.strip()
for x
in h_short_summ.search(lines[pos]).groups() ]
1065 cont[values[0]] = values
1068 raise RuntimeError(
"Cannot understand line %d: '%s'" % (pos, l))
1072 summ[d][
"header"] = header
1077 summ[name] = {
"header": header}
1084 Scan stdout to find ROOT TTree summaries and digest them. 1086 outlines = stdout.splitlines()
1087 nlines = len(outlines) - 1
1095 match = h_count_re.search(outlines[pos])
1096 while pos < nlines
and not match:
1098 match = h_count_re.search(outlines[pos])
1101 summaries.update(summ)
1106 unsupported = [ re.compile(x)
for x
in [ str(y).strip()
for y
in unsupported_platforms ]
if x]
1107 for p_re
in unsupported :
1108 if p_re.search(platform):
1109 result.SetOutcome(result.UNTESTED)
1110 result[result.CAUSE] =
'Platform not supported.' 1116 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH. 1120 if "BINARY_TAG" in os.environ:
1121 arch = os.environ[
"BINARY_TAG"]
1122 elif "CMTCONFIG" in os.environ:
1123 arch = os.environ[
"CMTCONFIG"]
1124 elif "SCRAM_ARCH" in os.environ:
1125 arch = os.environ[
"SCRAM_ARCH"]
1130 Return True if the current platform is Windows. 1132 This function was needed because of the change in the CMTCONFIG format, 1133 from win32_vc71_dbg to i686-winxp-vc9-dbg. 1136 return "winxp" in platform
or platform.startswith(
"win")
def PlatformIsNotSupported(self, context, result)
def __processLine__(self, line)
def __init__(self, start, end)
decltype(auto) range(Args &&...args)
Zips multiple containers together to form a single range.
def __call__(self, input)
def validateWithReference(self, stdout=None, stderr=None, result=None, causes=None, preproc=None)
def __processLine__(self, line)
def cmpTreesDicts(reference, to_check, ignore=None)
def __processFile__(self, lines)
def ValidateOutput(self, stdout, stderr, result)
def read(f, regex='.*', skipevents=0)
def __processLine__(self, line)
def __processFile__(self, lines)
def __call__(self, out, result)
def findHistosSummaries(stdout)
def _parseTTreeSummary(lines, pos)
struct GAUDI_API map
Parametrisation class for map-like implementation.
def __call__(self, stdout, result)
def __processLine__(self, line)
def __init__(self, orig, repl="", when=None)
def __init__(self, signature)
def __call__(self, input)
def sanitize_for_xml(data)
def getCmpFailingValues(reference, to_check, fail_path)
def __init__(self, members=[])
def __init__(self, strings=[], regexps=[])
def __setitem__(self, key, value)
def __init__(self, kind=None, id=None, outcome=PASS, annotations={})
def __processLine__(self, line)
def parseHistosSummary(lines, pos)
def _expandReferenceFileName(self, reffile)
def findReferenceBlock(self, reference=None, stdout=None, result=None, causes=None, signature_offset=0, signature=None, id=None)
def CheckHistosSummaries(self, stdout=None, result=None, causes=None, dict=None, ignore=None)
def __CompareText(self, s1, s2)
def __init__(self, reffile, cause, result_key, preproc=normalizeExamples)
def __getitem__(self, key)
def findTTreeSummaries(stdout)
def __init__(self, ref, cause, result_key)
def ROOT6WorkAroundEnabled(id=None)
def CheckTTreesSummaries(self, stdout=None, result=None, causes=None, trees_dict=None, ignore=r"Basket|.*size|Compression")
Special preprocessor sorting the list of strings (whitespace separated) that follow a signature on a ...