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)
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)
257 if self.
validator is not BaseTest.validator:
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]))
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:
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))
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
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))
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
441 if lreference
and os.path.isfile(lreference):
445 preproc=preproc)(stdout, result)
451 newref = open(lreference +
".new",
"w")
453 for l
in stdout.splitlines():
454 newref.write(l.rstrip() +
'\n')
464 if lreference
and os.path.isfile(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)
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)
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={}):
596 assert type(key)
in types.StringTypes
600 assert type(key)
in types.StringTypes
601 assert type(value)
in types.StringTypes
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."""
635 causes.append(self.
cause)
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()
660 """ Base class for a callable that takes a file and returns a modified
665 if hasattr(input,
"__iter__"):
669 lines = input.splitlines()
674 if l: output.append(l)
675 if mergeback: output =
'\n'.join(output)
699 if line.find(s) >= 0:
return None
701 if r.search(line):
return None
711 if self.
start in line:
714 elif self.
end in line:
723 when = re.compile(when)
726 if isinstance(rhs, RegexpReplacer):
728 res._operations = self.
_operations + rhs._operations
730 res = FilePreprocessor.__add__(self, rhs)
734 if w
is None or w.search(line):
735 line = o.sub(r, line)
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")
743 normalizeEOL.__processLine__ =
lambda line: str(line).rstrip() +
'\n'
747 skipEmptyLines.__processLine__ =
lambda line: (line.strip()
and line)
or None
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':"),
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",
830 r"EventLoopMgr.*---> Loop Finished",
834 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
842 r'Warning in <TInterpreter::ReadRootmapFile>: .* is already in .*',
845 normalizeExamples = (lineSkipper + normalizeExamples + skipEmptyLines +
846 normalizeEOL +
LineSorter(
"Services to release : "))
852 def __init__(self,reffile, cause, result_key, preproc=normalizeExamples):
860 if os.path.isfile(self.
reffile):
861 orig=open(self.
reffile).xreadlines()
866 new = stdout.splitlines()
870 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
871 filterdiffs =
map(
lambda x: x.strip(),filter(
lambda x: x[0] !=
" ",diffs))
873 result[self.
result_key] = result.Quote(
"\n".join(filterdiffs))
877 +) standard output of the test""")
878 causes.append(self.
cause)
883 Scan stdout to find ROOT TTree summaries and digest them.
885 stars = re.compile(
r"^\*+$")
886 outlines = stdout.splitlines()
887 nlines = len(outlines)
893 while i < nlines
and not stars.match(outlines[i]):
898 trees[tree[
"Name"]] = tree
904 Check that all the keys in reference are in to_check too, with the same value.
905 If the value is a dict, the function is called recursively. to_check can
906 contain more keys than reference, that will not be tested.
907 The function returns at the first difference found.
912 ignore_re = re.compile(ignore)
913 keys = [ key
for key
in reference
if not ignore_re.match(key) ]
915 keys = reference.keys()
919 if (
type(reference[k])
is dict)
and (
type(to_check[k])
is dict):
921 failed = fail_keys =
cmpTreesDicts(reference[k], to_check[k], ignore)
924 failed = to_check[k] != reference[k]
929 fail_keys.insert(0, k)
939 if c
is None or r
is None:
941 return (fail_path, r, c)
944 h_count_re = re.compile(
r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+([\s\w=-]*)")
949 Parse the TTree summary table in lines, starting from pos.
950 Returns a tuple with the dictionary with the digested informations and the
951 position of the first line after the summary.
957 splitcols =
lambda l: [ f.strip()
for f
in l.strip(
"*\n").split(
':',2) ]
960 cols = splitcols(ll[0])
961 r[
"Name"], r[
"Title"] = cols[1:]
963 cols = splitcols(ll[1])
964 r[
"Entries"] = int(cols[1])
966 sizes = cols[2].split()
967 r[
"Total size"] = int(sizes[2])
968 if sizes[-1] ==
"memory":
971 r[
"File size"] = int(sizes[-1])
973 cols = splitcols(ll[2])
974 sizes = cols[2].split()
975 if cols[0] ==
"Baskets":
976 r[
"Baskets"] = int(cols[1])
977 r[
"Basket size"] = int(sizes[2])
978 r[
"Compression"] = float(sizes[-1])
981 if i < (count - 3)
and lines[i].startswith(
"*Tree"):
982 result = parseblock(lines[i:i+3])
983 result[
"Branches"] = {}
985 while i < (count - 3)
and lines[i].startswith(
"*Br"):
986 if i < (count - 2)
and lines[i].startswith(
"*Branch "):
990 branch = parseblock(lines[i:i+3])
991 result[
"Branches"][branch[
"Name"]] = branch
998 Extract the histograms infos from the lines starting at pos.
999 Returns the position of the first line after the summary block.
1002 h_table_head = re.compile(
r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
1003 h_short_summ = re.compile(
r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
1008 m = h_count_re.search(lines[pos])
1009 name = m.group(1).strip()
1010 total = int(m.group(2))
1012 for k, v
in [ x.split(
"=")
for x
in m.group(3).split() ]:
1015 header[
"Total"] = total
1019 m = h_table_head.search(lines[pos])
1022 t = t.replace(
" profile",
"Prof")
1029 if l.startswith(
" | ID"):
1031 titles = [ x.strip()
for x
in l.split(
"|")][1:]
1033 while pos < nlines
and lines[pos].startswith(
" |"):
1035 values = [ x.strip()
for x
in l.split(
"|")][1:]
1037 for i
in range(len(titles)):
1038 hcont[titles[i]] = values[i]
1039 cont[hcont[
"ID"]] = hcont
1041 elif l.startswith(
" ID="):
1042 while pos < nlines
and lines[pos].startswith(
" ID="):
1043 values = [ x.strip()
for x
in h_short_summ.search(lines[pos]).groups() ]
1044 cont[values[0]] = values
1047 raise RuntimeError(
"Cannot understand line %d: '%s'" % (pos, l))
1051 summ[d][
"header"] = header
1056 summ[name] = {
"header": header}
1063 Scan stdout to find ROOT TTree summaries and digest them.
1065 outlines = stdout.splitlines()
1066 nlines = len(outlines) - 1
1074 match = h_count_re.search(outlines[pos])
1075 while pos < nlines
and not match:
1077 match = h_count_re.search(outlines[pos])
1080 summaries.update(summ)
1085 unsupported = [ re.compile(x)
for x
in [ str(y).strip()
for y
in unsupported_platforms ]
if x]
1086 for p_re
in unsupported :
1087 if p_re.search(platform):
1088 result.SetOutcome(result.UNTESTED)
1089 result[result.CAUSE] =
'Platform not supported.'
1095 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
1099 if "BINARY_TAG" in os.environ:
1100 arch = os.environ[
"BINARY_TAG"]
1101 elif "CMTCONFIG" in os.environ:
1102 arch = os.environ[
"CMTCONFIG"]
1103 elif "SCRAM_ARCH" in os.environ:
1104 arch = os.environ[
"SCRAM_ARCH"]
1109 Return True if the current platform is Windows.
1111 This function was needed because of the change in the CMTCONFIG format,
1112 from win32_vc71_dbg to i686-winxp-vc9-dbg.
1115 return "winxp" in platform
or platform.startswith(
"win")
def PlatformIsNotSupported(self, context, result)
def __processLine__(self, line)
def __init__(self, start, end)
def __call__(self, input)
def validateWithReference
def __processLine__(self, line)
def ValidateOutput(self, stdout, stderr, result)
def __processLine__(self, line)
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)
NamedRange_< CONTAINER > range(const CONTAINER &cnt, std::string name)
simple function to create the named range form arbitrary container
def __processLine__(self, line)
def __init__(self, signature)
def __call__(self, input)
def sanitize_for_xml(data)
def getCmpFailingValues(reference, to_check, fail_path)
def __setitem__(self, key, value)
def __processLine__(self, line)
def parseHistosSummary(lines, pos)
def _expandReferenceFileName(self, reffile)
def ROOT6WorkAroundEnabled
def __CompareText(self, s1, s2)
def __getitem__(self, key)
def findTTreeSummaries(stdout)
def __init__(self, ref, cause, result_key)
Special preprocessor sorting the list of strings (whitespace separated) that follow a signature on a ...