00001
00002
00003
00004
00005 __author__ = 'Marco Clemencic CERN/PH-LBC'
00006 __version__ = "$Revision: 1.52 $"
00007 __tag__ = "$Name: $"
00008
00009
00010
00011 import os
00012 import sys
00013 import re
00014 import tempfile
00015 import shutil
00016 import string
00017 import difflib
00018 from subprocess import Popen, PIPE, STDOUT
00019
00020 import qm
00021 from qm.test.classes.command import ExecTestBase
00022
00023
00024 import qm.executable
00025 import time, signal
00026
00027
00028 if sys.platform == "win32":
00029 import msvcrt
00030 import pywintypes
00031 from threading import *
00032 import win32api
00033 import win32con
00034 import win32event
00035 import win32file
00036 import win32pipe
00037 import win32process
00038 else:
00039 import cPickle
00040 import fcntl
00041 import select
00042 import qm.sigmask
00043
00044
00045
00046
00047 class TemporaryEnvironment:
00048 """
00049 Class to changes the environment temporarily.
00050 """
00051 def __init__(self, orig = os.environ, keep_same = False):
00052 """
00053 Create a temporary environment on top of the one specified
00054 (it can be another TemporaryEnvironment instance).
00055 """
00056
00057 self.old_values = {}
00058 self.env = orig
00059 self._keep_same = keep_same
00060
00061 def __setitem__(self,key,value):
00062 """
00063 Set an environment variable recording the previous value.
00064 """
00065 if key not in self.old_values :
00066 if key in self.env :
00067 if not self._keep_same or self.env[key] != value:
00068 self.old_values[key] = self.env[key]
00069 else:
00070 self.old_values[key] = None
00071 self.env[key] = value
00072
00073 def __getitem__(self,key):
00074 """
00075 Get an environment variable.
00076 Needed to provide the same interface as os.environ.
00077 """
00078 return self.env[key]
00079
00080 def __delitem__(self,key):
00081 """
00082 Unset an environment variable.
00083 Needed to provide the same interface as os.environ.
00084 """
00085 if key not in self.env :
00086 raise KeyError(key)
00087 self.old_values[key] = self.env[key]
00088 del self.env[key]
00089
00090 def keys(self):
00091 """
00092 Return the list of defined environment variables.
00093 Needed to provide the same interface as os.environ.
00094 """
00095 return self.env.keys()
00096
00097 def items(self):
00098 """
00099 Return the list of (name,value) pairs for the defined environment variables.
00100 Needed to provide the same interface as os.environ.
00101 """
00102 return self.env.items()
00103
00104 def __contains__(self,key):
00105 """
00106 Operator 'in'.
00107 Needed to provide the same interface as os.environ.
00108 """
00109 return key in self.env
00110
00111 def restore(self):
00112 """
00113 Revert all the changes done to the orignal environment.
00114 """
00115 for key,value in self.old_values.items():
00116 if value is None:
00117 del self.env[key]
00118 else:
00119 self.env[key] = value
00120 self.old_values = {}
00121
00122 def __del__(self):
00123 """
00124 Revert the changes on destruction.
00125 """
00126
00127 self.restore()
00128
00129 def gen_script(self,shell_type):
00130 """
00131 Generate a shell script to reproduce the changes in the environment.
00132 """
00133 shells = [ 'csh', 'sh', 'bat' ]
00134 if shell_type not in shells:
00135 raise RuntimeError("Shell type '%s' unknown. Available: %s"%(shell_type,shells))
00136 out = ""
00137 for key,value in self.old_values.items():
00138 if key not in self.env:
00139
00140 if shell_type == 'csh':
00141 out += 'unsetenv %s\n'%key
00142 elif shell_type == 'sh':
00143 out += 'unset %s\n'%key
00144 elif shell_type == 'bat':
00145 out += 'set %s=\n'%key
00146 else:
00147
00148 if shell_type == 'csh':
00149 out += 'setenv %s "%s"\n'%(key,self.env[key])
00150 elif shell_type == 'sh':
00151 out += 'export %s="%s"\n'%(key,self.env[key])
00152 elif shell_type == 'bat':
00153 out += 'set %s=%s\n'%(key,self.env[key])
00154 return out
00155
00156 class TempDir:
00157 """Small class for temporary directories.
00158 When instantiated, it creates a temporary directory and the instance
00159 behaves as the string containing the directory name.
00160 When the instance goes out of scope, it removes all the content of
00161 the temporary directory (automatic clean-up).
00162 """
00163 def __init__(self, keep = False, chdir = False):
00164 self.name = tempfile.mkdtemp()
00165 self._keep = keep
00166 self._origdir = None
00167 if chdir:
00168 self._origdir = os.getcwd()
00169 os.chdir(self.name)
00170
00171 def __str__(self):
00172 return self.name
00173
00174 def __del__(self):
00175 if self._origdir:
00176 os.chdir(self._origdir)
00177 if self.name and not self._keep:
00178 shutil.rmtree(self.name)
00179
00180 def __getattr__(self,attr):
00181 return getattr(self.name,attr)
00182
00183 class TempFile:
00184 """Small class for temporary files.
00185 When instantiated, it creates a temporary directory and the instance
00186 behaves as the string containing the directory name.
00187 When the instance goes out of scope, it removes all the content of
00188 the temporary directory (automatic clean-up).
00189 """
00190 def __init__(self, suffix='', prefix='tmp', dir=None, text=False, keep = False):
00191 self.file = None
00192 self.name = None
00193 self._keep = keep
00194
00195 self._fd, self.name = tempfile.mkstemp(suffix,prefix,dir,text)
00196 self.file = os.fdopen(self._fd,"r+")
00197
00198 def __str__(self):
00199 return self.name
00200
00201 def __del__(self):
00202 if self.file:
00203 self.file.close()
00204 if self.name and not self._keep:
00205 os.remove(self.name)
00206
00207 def __getattr__(self,attr):
00208 return getattr(self.file,attr)
00209
00210 class CMT:
00211 """Small wrapper to call CMT.
00212 """
00213 def __init__(self,path=None):
00214 if path is None:
00215 path = os.getcwd()
00216 self.path = path
00217
00218 def _run_cmt(self,command,args):
00219
00220 if type(args) is str:
00221 args = [args]
00222 cmd = "cmt %s"%command
00223 for arg in args:
00224 cmd += ' "%s"'%arg
00225
00226
00227 olddir = os.getcwd()
00228 os.chdir(self.path)
00229
00230 result = os.popen4(cmd)[1].read()
00231
00232 os.chdir(olddir)
00233 return result
00234
00235 def __getattr__(self,attr):
00236 return lambda args=[]: self._run_cmt(attr, args)
00237
00238 def runtime_env(self,env = None):
00239 """Returns a dictionary containing the runtime environment produced by CMT.
00240 If a dictionary is passed a modified instance of it is returned.
00241 """
00242 if env is None:
00243 env = {}
00244 for l in self.setup("-csh").splitlines():
00245 l = l.strip()
00246 if l.startswith("setenv"):
00247 dummy,name,value = l.split(None,3)
00248 env[name] = value.strip('"')
00249 elif l.startswith("unsetenv"):
00250 dummy,name = l.split(None,2)
00251 if name in env:
00252 del env[name]
00253 return env
00254 def show_macro(self,k):
00255 r = self.show(["macro",k])
00256 if r.find("CMT> Error: symbol not found") >= 0:
00257 return None
00258 else:
00259 return self.show(["macro_value",k]).strip()
00260
00261
00262
00263
00264 def which(executable):
00265 for d in os.environ.get("PATH").split(os.pathsep):
00266 fullpath = os.path.join(d,executable)
00267 if os.path.exists(fullpath):
00268 return fullpath
00269 return None
00270
00271 def rationalizepath(p):
00272 p = os.path.normpath(os.path.expandvars(p))
00273 if os.path.exists(p):
00274 p = os.path.realpath(p)
00275 return p
00276
00277
00278
00279
00280 class BasicOutputValidator:
00281 """Basic implementation of an option validator for Gaudi tests.
00282 This implementation is based on the standard (LCG) validation functions
00283 used in QMTest.
00284 """
00285 def __init__(self,ref,cause,result_key):
00286 self.reference = ref
00287 self.cause = cause
00288 self.result_key = result_key
00289
00290 def __call__(self, out, result):
00291 """Validate the output of the program.
00292
00293 'stdout' -- A string containing the data written to the standard output
00294 stream.
00295
00296 'stderr' -- A string containing the data written to the standard error
00297 stream.
00298
00299 'result' -- A 'Result' object. It may be used to annotate
00300 the outcome according to the content of stderr.
00301
00302 returns -- A list of strings giving causes of failure."""
00303
00304 causes = []
00305
00306 if not self.__CompareText(out, self.reference):
00307 causes.append(self.cause)
00308 result[self.result_key] = result.Quote(self.reference)
00309
00310 return causes
00311
00312 def __CompareText(self, s1, s2):
00313 """Compare 's1' and 's2', ignoring line endings.
00314
00315 's1' -- A string.
00316
00317 's2' -- A string.
00318
00319 returns -- True if 's1' and 's2' are the same, ignoring
00320 differences in line endings."""
00321
00322
00323
00324 return s1.splitlines() == s2.splitlines()
00325
00326 class FilePreprocessor:
00327 """ Base class for a callable that takes a file and returns a modified
00328 version of it."""
00329 def __processLine__(self, line):
00330 return line
00331 def __call__(self, input):
00332 if hasattr(input,"__iter__"):
00333 lines = input
00334 mergeback = False
00335 else:
00336 lines = input.splitlines()
00337 mergeback = True
00338 output = []
00339 for l in lines:
00340 l = self.__processLine__(l)
00341 if l: output.append(l)
00342 if mergeback: output = '\n'.join(output)
00343 return output
00344 def __add__(self, rhs):
00345 return FilePreprocessorSequence([self,rhs])
00346
00347 class FilePreprocessorSequence(FilePreprocessor):
00348 def __init__(self, members = []):
00349 self.members = members
00350 def __add__(self, rhs):
00351 return FilePreprocessorSequence(self.members + [rhs])
00352 def __call__(self, input):
00353 output = input
00354 for pp in self.members:
00355 output = pp(output)
00356 return output
00357
00358 class LineSkipper(FilePreprocessor):
00359 def __init__(self, strings = [], regexps = []):
00360 import re
00361 self.strings = strings
00362 self.regexps = map(re.compile,regexps)
00363
00364 def __processLine__(self, line):
00365 for s in self.strings:
00366 if line.find(s) >= 0: return None
00367 for r in self.regexps:
00368 if r.search(line): return None
00369 return line
00370
00371 class BlockSkipper(FilePreprocessor):
00372 def __init__(self, start, end):
00373 self.start = start
00374 self.end = end
00375 self._skipping = False
00376
00377 def __processLine__(self, line):
00378 if self.start in line:
00379 self._skipping = True
00380 return None
00381 elif self.end in line:
00382 self._skipping = False
00383 elif self._skipping:
00384 return None
00385 return line
00386
00387 class RegexpReplacer(FilePreprocessor):
00388 def __init__(self, orig, repl = "", when = None):
00389 if when:
00390 when = re.compile(when)
00391 self._operations = [ (when, re.compile(orig), repl) ]
00392 def __add__(self,rhs):
00393 if isinstance(rhs, RegexpReplacer):
00394 res = RegexpReplacer("","",None)
00395 res._operations = self._operations + rhs._operations
00396 else:
00397 res = FilePreprocessor.__add__(self, rhs)
00398 return res
00399 def __processLine__(self, line):
00400 for w,o,r in self._operations:
00401 if w is None or w.search(line):
00402 line = o.sub(r, line)
00403 return line
00404
00405
00406 maskPointers = RegexpReplacer("0x[0-9a-fA-F]{4,16}","0x########")
00407 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)?",
00408 "00:00:00 1970-01-01")
00409 normalizeEOL = FilePreprocessor()
00410 normalizeEOL.__processLine__ = lambda line: str(line).rstrip() + '\n'
00411
00412 skipEmptyLines = FilePreprocessor()
00413
00414 skipEmptyLines.__processLine__ = lambda line: (line.strip() and line) or None
00415
00416
00417
00418 class LineSorter(FilePreprocessor):
00419 def __init__(self, signature):
00420 self.signature = signature
00421 self.siglen = len(signature)
00422 def __processLine__(self, line):
00423 pos = line.find(self.signature)
00424 if pos >=0:
00425 line = line[:(pos+self.siglen)]
00426 lst = line[(pos+self.siglen):].split()
00427 lst.sort()
00428 line += " ".join(lst)
00429 return line
00430
00431
00432 normalizeExamples = maskPointers + normalizeDate
00433 for w,o,r in [
00434
00435 ("TIMER.TIMER",r"\s+[+-]?[0-9]+[0-9.]*", " 0"),
00436 ("release all pending",r"^.*/([^/]*:.*)",r"\1"),
00437 ("0x########",r"\[.*/([^/]*.*)\]",r"[\1]"),
00438 ("^#.*file",r"file '.*[/\\]([^/\\]*)$",r"file '\1"),
00439 ("^JobOptionsSvc.*options successfully read in from",r"read in from .*[/\\]([^/\\]*)$",r"file \1"),
00440
00441 (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"),
00442
00443 ("ServiceLocatorHelper::", "ServiceLocatorHelper::(create|locate)Service", "ServiceLocatorHelper::service"),
00444
00445 (None, r"e([-+])0([0-9][0-9])", r"e\1\2"),
00446 ]:
00447 normalizeExamples += RegexpReplacer(o,r,w)
00448 normalizeExamples = LineSkipper(["//GP:",
00449 "Time User",
00450 "Welcome to",
00451 "This machine has a speed",
00452 "TIME:",
00453 "running on",
00454 "ToolSvc.Sequenc... INFO",
00455 "DataListenerSvc INFO XML written to file:",
00456 "[INFO]","[WARNING]",
00457 "DEBUG No writable file catalog found which contains FID:",
00458 "0 local",
00459 "DEBUG Service base class initialized successfully",
00460 "DEBUG Incident timing:",
00461
00462 'Note: (file "(tmpfile)", line 2) File "set" already loaded',
00463 ],regexps = [
00464 r"^#",
00465 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
00466 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
00467 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
00468 r"File '.*.xml' does not exist",
00469 r"INFO Refer to dataset .* by its file ID:",
00470 r"INFO Referring to dataset .* by its file ID:",
00471 r"INFO Disconnect from dataset",
00472 r"INFO Disconnected from dataset",
00473 r"INFO Disconnected data IO:",
00474 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
00475
00476 r"^StatusCodeSvc.*listing all unchecked return codes:",
00477 r"^StatusCodeSvc\s*INFO\s*$",
00478 r"Num\s*|\s*Function\s*|\s*Source Library",
00479 r"^[-+]*\s*$",
00480
00481 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
00482
00483 r"^ +[0-9]+ \|.*ROOT",
00484 r"^ +[0-9]+ \|.*\|.*Dict",
00485
00486 r"^\*.*\*$",
00487
00488 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
00489 r"^ \|",
00490 r"^ ID=",
00491 ] ) + normalizeExamples + skipEmptyLines + \
00492 normalizeEOL + \
00493 LineSorter("Services to release : ")
00494
00495 class ReferenceFileValidator:
00496 def __init__(self, reffile, cause, result_key, preproc = normalizeExamples):
00497 self.reffile = os.path.expandvars(reffile)
00498 self.cause = cause
00499 self.result_key = result_key
00500 self.preproc = preproc
00501 def __call__(self, stdout, result):
00502 causes = []
00503 if os.path.isfile(self.reffile):
00504 orig = open(self.reffile).xreadlines()
00505 if self.preproc:
00506 orig = self.preproc(orig)
00507 else:
00508 orig = []
00509
00510 new = stdout.splitlines()
00511 if self.preproc:
00512 new = self.preproc(new)
00513
00514 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
00515 filterdiffs = map(lambda x: x.strip(),filter(lambda x: x[0] != " ",diffs))
00516
00517 if filterdiffs:
00518 result[self.result_key] = result.Quote("\n".join(filterdiffs))
00519 result[self.result_key] += result.Quote("""
00520 Legend:
00521 -) reference file
00522 +) standard output of the test""")
00523 causes.append(self.cause)
00524
00525 return causes
00526
00527
00528
00529
00530 def findReferenceBlock(reference, stdout, result, causes, signature_offset=0, signature=None,
00531 id = None):
00532 """
00533 Given a block of text, tries to find it in the output.
00534 The block had to be identified by a signature line. By default, the first
00535 line is used as signature, or the line pointed to by signature_offset. If
00536 signature_offset points outside the block, a signature line can be passed as
00537 signature argument. Note: if 'signature' is None (the default), a negative
00538 signature_offset is interpreted as index in a list (e.g. -1 means the last
00539 line), otherwise the it is interpreted as the number of lines before the
00540 first one of the block the signature must appear.
00541 The parameter 'id' allow to distinguish between different calls to this
00542 function in the same validation code.
00543 """
00544
00545 reflines = filter(None,map(lambda s: s.rstrip(), reference.splitlines()))
00546 if not reflines:
00547 raise RuntimeError("Empty (or null) reference")
00548
00549 outlines = filter(None,map(lambda s: s.rstrip(), stdout.splitlines()))
00550
00551 res_field = "GaudiTest.RefBlock"
00552 if id:
00553 res_field += "_%s" % id
00554
00555 if signature is None:
00556 if signature_offset < 0:
00557 signature_offset = len(reference)+signature_offset
00558 signature = reflines[signature_offset]
00559
00560 try:
00561 pos = outlines.index(signature)
00562 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
00563 if reflines != outlines:
00564 msg = "standard output"
00565
00566 if not msg in causes:
00567 causes.append(msg)
00568 result[res_field + ".observed"] = result.Quote("\n".join(outlines))
00569 except ValueError:
00570 causes.append("missing signature")
00571 result[res_field + ".signature"] = result.Quote(signature)
00572 if len(reflines) > 1 or signature != reflines[0]:
00573 result[res_field + ".expected"] = result.Quote("\n".join(reflines))
00574
00575 return causes
00576
00577 def countErrorLines(expected = {'ERROR':0, 'FATAL':0}, **kwargs):
00578 """
00579 Count the number of messages with required severity (by default ERROR and FATAL)
00580 and check if their numbers match the expected ones (0 by default).
00581 The dictionary "expected" can be used to tune the number of errors and fatals
00582 allowed, or to limit the number of expected warnings etc.
00583 """
00584 stdout = kwargs["stdout"]
00585 result = kwargs["result"]
00586 causes = kwargs["causes"]
00587
00588
00589 errors = {}
00590 for sev in expected:
00591 errors[sev] = []
00592
00593 outlines = stdout.splitlines()
00594 from math import log10
00595 fmt = "%%%dd - %%s" % (int(log10(len(outlines))+1))
00596
00597 linecount = 0
00598 for l in outlines:
00599 linecount += 1
00600 words = l.split()
00601 if len(words) >= 2 and words[1] in errors:
00602 errors[words[1]].append(fmt%(linecount,l.rstrip()))
00603
00604 for e in errors:
00605 if len(errors[e]) != expected[e]:
00606 causes.append('%s(%d)'%(e,len(errors[e])))
00607 result["GaudiTest.lines.%s"%e] = result.Quote('\n'.join(errors[e]))
00608 result["GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
00609
00610 return causes
00611
00612
00613 def _parseTTreeSummary(lines, pos):
00614 """
00615 Parse the TTree summary table in lines, starting from pos.
00616 Returns a tuple with the dictionary with the digested informations and the
00617 position of the first line after the summary.
00618 """
00619 result = {}
00620 i = pos + 1
00621 count = len(lines)
00622
00623 splitcols = lambda l: [ f.strip() for f in l.strip("*\n").split(':',2) ]
00624 def parseblock(ll):
00625 r = {}
00626 cols = splitcols(ll[0])
00627 r["Name"], r["Title"] = cols[1:]
00628
00629 cols = splitcols(ll[1])
00630 r["Entries"] = int(cols[1])
00631
00632 sizes = cols[2].split()
00633 r["Total size"] = int(sizes[2])
00634 if sizes[-1] == "memory":
00635 r["File size"] = 0
00636 else:
00637 r["File size"] = int(sizes[-1])
00638
00639 cols = splitcols(ll[2])
00640 sizes = cols[2].split()
00641 if cols[0] == "Baskets":
00642 r["Baskets"] = int(cols[1])
00643 r["Basket size"] = int(sizes[2])
00644 r["Compression"] = float(sizes[-1])
00645 return r
00646
00647 if i < (count - 3) and lines[i].startswith("*Tree"):
00648 result = parseblock(lines[i:i+3])
00649 result["Branches"] = {}
00650 i += 4
00651 while i < (count - 3) and lines[i].startswith("*Br"):
00652 branch = parseblock(lines[i:i+3])
00653 result["Branches"][branch["Name"]] = branch
00654 i += 4
00655
00656 return (result, i)
00657
00658 def findTTreeSummaries(stdout):
00659 """
00660 Scan stdout to find ROOT TTree summaries and digest them.
00661 """
00662 stars = re.compile(r"^\*+$")
00663 outlines = stdout.splitlines()
00664 nlines = len(outlines)
00665 trees = {}
00666
00667 i = 0
00668 while i < nlines:
00669
00670 while i < nlines and not stars.match(outlines[i]):
00671 i += 1
00672 if i < nlines:
00673 tree, i = _parseTTreeSummary(outlines, i)
00674 if tree:
00675 trees[tree["Name"]] = tree
00676
00677 return trees
00678
00679 def cmpTreesDicts(reference, to_check, ignore = None):
00680 """
00681 Check that all the keys in reference are in to_check too, with the same value.
00682 If the value is a dict, the function is called recursively. to_check can
00683 contain more keys than reference, that will not be tested.
00684 The function returns at the first difference found.
00685 """
00686 fail_keys = []
00687
00688 if ignore:
00689 ignore_re = re.compile(ignore)
00690 keys = [ key for key in reference if not ignore_re.match(key) ]
00691 else:
00692 keys = reference.keys()
00693
00694 for k in keys:
00695 if k in to_check:
00696 if (type(reference[k]) is dict) and (type(to_check[k]) is dict):
00697
00698 failed = fail_keys = cmpTreesDicts(reference[k], to_check[k], ignore)
00699 else:
00700
00701 failed = to_check[k] != reference[k]
00702 else:
00703 to_check[k] = None
00704 failed = True
00705 if failed:
00706 fail_keys.insert(0, k)
00707 break
00708 return fail_keys
00709
00710 def getCmpFailingValues(reference, to_check, fail_path):
00711 c = to_check
00712 r = reference
00713 for k in fail_path:
00714 c = c.get(k,None)
00715 r = r.get(k,None)
00716 if c is None or r is None:
00717 break
00718 return (fail_path, r, c)
00719
00720
00721 h_count_re = re.compile(r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
00722
00723 def parseHistosSummary(lines, pos):
00724 """
00725 Extract the histograms infos from the lines starting at pos.
00726 Returns the position of the first line after the summary block.
00727 """
00728 global h_count_re
00729 h_table_head = re.compile(r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
00730 h_short_summ = re.compile(r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
00731
00732 nlines = len(lines)
00733
00734
00735 m = h_count_re.search(lines[pos])
00736 name = m.group(1).strip()
00737 total = int(m.group(2))
00738 header = {}
00739 for k, v in [ x.split("=") for x in m.group(3).split() ]:
00740 header[k] = int(v)
00741 pos += 1
00742 header["Total"] = total
00743
00744 summ = {}
00745 while pos < nlines:
00746 m = h_table_head.search(lines[pos])
00747 if m:
00748 t, d = m.groups(1)
00749 t = t.replace(" profile", "Prof")
00750 pos += 1
00751 if pos < nlines:
00752 l = lines[pos]
00753 else:
00754 l = ""
00755 cont = {}
00756 if l.startswith(" | ID"):
00757
00758 titles = [ x.strip() for x in l.split("|")][1:]
00759 pos += 1
00760 while pos < nlines and lines[pos].startswith(" |"):
00761 l = lines[pos]
00762 values = [ x.strip() for x in l.split("|")][1:]
00763 hcont = {}
00764 for i in range(len(titles)):
00765 hcont[titles[i]] = values[i]
00766 cont[hcont["ID"]] = hcont
00767 pos += 1
00768 elif l.startswith(" ID="):
00769 while pos < nlines and lines[pos].startswith(" ID="):
00770 values = [ x.strip() for x in h_short_summ.search(lines[pos]).groups() ]
00771 cont[values[0]] = values
00772 pos += 1
00773 else:
00774 raise RuntimeError("Cannot understand line %d: '%s'" % (pos, l))
00775 if not d in summ:
00776 summ[d] = {}
00777 summ[d][t] = cont
00778 summ[d]["header"] = header
00779 else:
00780 break
00781 if not summ:
00782
00783 summ[name] = {"header": header}
00784 return summ, pos
00785
00786 def findHistosSummaries(stdout):
00787 """
00788 Scan stdout to find ROOT TTree summaries and digest them.
00789 """
00790 outlines = stdout.splitlines()
00791 nlines = len(outlines) - 1
00792 summaries = {}
00793 global h_count_re
00794
00795 pos = 0
00796 while pos < nlines:
00797 summ = {}
00798
00799 match = h_count_re.search(outlines[pos])
00800 while pos < nlines and not match:
00801 pos += 1
00802 match = h_count_re.search(outlines[pos])
00803 if match:
00804 summ, pos = parseHistosSummary(outlines, pos)
00805 summaries.update(summ)
00806 return summaries
00807
00808 class GaudiFilterExecutable(qm.executable.Filter):
00809 def __init__(self, input, timeout = -1):
00810 """Create a new 'Filter'.
00811
00812 'input' -- The string containing the input to provide to the
00813 child process.
00814
00815 'timeout' -- As for 'TimeoutExecutable.__init__'."""
00816
00817 super(GaudiFilterExecutable, self).__init__(input, timeout)
00818 self.__input = input
00819 self.__timeout = timeout
00820 self.stack_trace_file = None
00821
00822
00823
00824 tmpf = tempfile.mkstemp()
00825 os.close(tmpf[0])
00826 self.stack_trace_file = tmpf[1]
00827
00828 def __UseSeparateProcessGroupForChild(self):
00829 """Copied from TimeoutExecutable to allow the re-implementation of
00830 _HandleChild.
00831 """
00832 if sys.platform == "win32":
00833
00834
00835
00836
00837 return 0
00838
00839 return self.__timeout >= 0 or self.__timeout == -2
00840
00841
00842 def _HandleChild(self):
00843 """Code copied from both FilterExecutable and TimeoutExecutable.
00844 """
00845
00846 if self._stdin_pipe:
00847 self._ClosePipeEnd(self._stdin_pipe[0])
00848 if self._stdout_pipe:
00849 self._ClosePipeEnd(self._stdout_pipe[1])
00850 if self._stderr_pipe:
00851 self._ClosePipeEnd(self._stderr_pipe[1])
00852
00853
00854
00855
00856
00857
00858
00859 super(qm.executable.TimeoutExecutable, self)._HandleChild()
00860
00861 if self.__UseSeparateProcessGroupForChild():
00862
00863
00864
00865
00866 child_pid = self._GetChildPID()
00867 try:
00868 os.setpgid(child_pid, child_pid)
00869 except:
00870
00871
00872
00873
00874 pass
00875
00876
00877
00878
00879
00880
00881
00882
00883
00884 self.__monitor_pid = os.fork()
00885 if self.__monitor_pid != 0:
00886
00887
00888
00889
00890 os.setpgid(self.__monitor_pid, child_pid)
00891 else:
00892
00893
00894
00895
00896 os.setpgid(0, child_pid)
00897
00898
00899
00900
00901
00902
00903
00904 try:
00905 max_fds = os.sysconf("SC_OPEN_MAX")
00906 except:
00907 max_fds = 256
00908 for fd in xrange(max_fds):
00909 try:
00910 os.close(fd)
00911 except:
00912 pass
00913 try:
00914 if self.__timeout >= 0:
00915
00916 time.sleep (self.__timeout)
00917
00918
00919 if sys.platform == "linux2":
00920 cmd = ["gdb",
00921 os.path.join("/proc", str(child_pid), "exe"),
00922 str(child_pid),
00923 "-batch", "-n", "-x",
00924 "'%s'" % os.path.join(os.path.dirname(__file__), "stack-trace.gdb")]
00925
00926
00927 o = os.popen(" ".join(cmd)).read()
00928 open(self.stack_trace_file,"w").write(o)
00929
00930
00931
00932 os.kill(0, signal.SIGKILL)
00933 else:
00934
00935 select.select ([], [], [])
00936 finally:
00937
00938
00939 os._exit(0)
00940 elif self.__timeout >= 0 and sys.platform == "win32":
00941
00942 self.__monitor_thread = Thread(target = self.__Monitor)
00943 self.__monitor_thread.start()
00944
00945 if sys.platform == "win32":
00946
00947 def __Monitor(self):
00948 """Code copied from FilterExecutable.
00949 Kill the child if the timeout expires.
00950
00951 This function is run in the monitoring thread."""
00952
00953
00954
00955
00956 timeout = int(self.__timeout * 1000)
00957
00958
00959 result = win32event.WaitForSingleObject(self._GetChildPID(),
00960 timeout)
00961
00962 if result == win32con.WAIT_TIMEOUT:
00963 self.Kill()
00964
00965
00966
00967
00968 class GaudiExeTest(ExecTestBase):
00969 """Standard Gaudi test.
00970 """
00971 arguments = [
00972 qm.fields.TextField(
00973 name="program",
00974 title="Program",
00975 not_empty_text=1,
00976 description="""The path to the program.
00977
00978 This field indicates the path to the program. If it is not
00979 an absolute path, the value of the 'PATH' environment
00980 variable will be used to search for the program.
00981 If not specified, $GAUDIEXE or Gaudi.exe are used.
00982 """
00983 ),
00984 qm.fields.SetField(qm.fields.TextField(
00985 name="args",
00986 title="Argument List",
00987 description="""The command-line arguments.
00988
00989 If this field is left blank, the program is run without any
00990 arguments.
00991
00992 Use this field to specify the option files.
00993
00994 An implicit 0th argument (the path to the program) is added
00995 automatically."""
00996 )),
00997 qm.fields.TextField(
00998 name="options",
00999 title="Options",
01000 description="""Options to be passed to the application.
01001
01002 This field allows to pass a list of options to the main program
01003 without the need of a separate option file.
01004
01005 The content of the field is written to a temporary file which name
01006 is passed the the application as last argument (appended to the
01007 field "Argument List".
01008 """,
01009 verbatim="true",
01010 multiline="true",
01011 default_value=""
01012 ),
01013 qm.fields.TextField(
01014 name="workdir",
01015 title="Working Directory",
01016 description="""Path to the working directory.
01017
01018 If this field is left blank, the program will be run from the qmtest
01019 directory, otherwise from the directory specified.""",
01020 default_value=""
01021 ),
01022 qm.fields.TextField(
01023 name="reference",
01024 title="Reference Output",
01025 description="""Path to the file containing the reference output.
01026
01027 If this field is left blank, any standard output will be considered
01028 valid.
01029
01030 If the reference file is specified, any output on standard error is
01031 ignored."""
01032 ),
01033 qm.fields.TextField(
01034 name="error_reference",
01035 title="Reference for standard error",
01036 description="""Path to the file containing the reference for the standard error.
01037
01038 If this field is left blank, any standard output will be considered
01039 valid.
01040
01041 If the reference file is specified, any output on standard error is
01042 ignored."""
01043 ),
01044 qm.fields.SetField(qm.fields.TextField(
01045 name = "unsupported_platforms",
01046 title = "Unsupported Platforms",
01047 description = """Platform on which the test must not be run.
01048
01049 List of regular expressions identifying the platforms on which the
01050 test is not run and the result is set to UNTESTED."""
01051 )),
01052
01053 qm.fields.TextField(
01054 name = "validator",
01055 title = "Validator",
01056 description = """Function to validate the output of the test.
01057
01058 If defined, the function is used to validate the products of the
01059 test.
01060 The function is called passing as arguments:
01061 self: the test class instance
01062 stdout: the standard output of the executed test
01063 stderr: the standard error of the executed test
01064 result: the Result objects to fill with messages
01065 The function must return a list of causes for the failure.
01066 If specified, overrides standard output, standard error and
01067 reference files.
01068 """,
01069 verbatim="true",
01070 multiline="true",
01071 default_value=""
01072 ),
01073
01074 qm.fields.BooleanField(
01075 name = "use_temp_dir",
01076 title = "Use temporary directory",
01077 description = """Use temporary directory.
01078
01079 If set to true, use a temporary directory as working directory.
01080 """,
01081 default_value="false"
01082 ),
01083 ]
01084
01085 def PlatformIsNotSupported(self, context, result):
01086 platform = self.GetPlatform()
01087 unsupported = [ re.compile(x)
01088 for x in [ str(y).strip()
01089 for y in self.unsupported_platforms ]
01090 if x
01091 ]
01092 for p_re in unsupported:
01093 if p_re.search(platform):
01094 result.SetOutcome(result.UNTESTED)
01095 result[result.CAUSE] = 'Platform not supported.'
01096 return True
01097 return False
01098
01099 def GetPlatform(self):
01100 """
01101 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
01102 """
01103 arch = "None"
01104
01105 if "CMTCONFIG" in os.environ:
01106 arch = os.environ["CMTCONFIG"]
01107 elif "SCRAM_ARCH" in os.environ:
01108 arch = os.environ["SCRAM_ARCH"]
01109 return arch
01110
01111 def _expandReferenceFileName(self, reffile):
01112
01113 if not reffile:
01114 return ""
01115
01116 reference = os.path.normpath(os.path.expandvars(reffile))
01117
01118 spec_ref = reference[:-3] + self.GetPlatform()[0:3] + reference[-3:]
01119 if os.path.isfile(spec_ref):
01120 reference = spec_ref
01121 else:
01122
01123 dirname, basename = os.path.split(reference)
01124 if not dirname: dirname = '.'
01125 head = basename + "."
01126 head_len = len(head)
01127 platform = self.GetPlatform()
01128 candidates = []
01129 for f in os.listdir(dirname):
01130 if f.startswith(head) and platform.startswith(f[head_len:]):
01131 candidates.append( (len(f) - head_len, f) )
01132 if candidates:
01133 candidates.sort()
01134 reference = os.path.join(dirname, candidates[-1][1])
01135 return reference
01136
01137 def CheckTTreesSummaries(self, stdout, result, causes,
01138 trees_dict = None,
01139 ignore = r"Basket|.*size|Compression"):
01140 """
01141 Compare the TTree summaries in stdout with the ones in trees_dict or in
01142 the reference file. By default ignore the size, compression and basket
01143 fields.
01144 The presence of TTree summaries when none is expected is not a failure.
01145 """
01146 if trees_dict is None:
01147 reference = self._expandReferenceFileName(self.reference)
01148
01149 if reference and os.path.isfile(reference):
01150 trees_dict = findTTreeSummaries(open(reference).read())
01151 else:
01152 trees_dict = {}
01153
01154 from pprint import PrettyPrinter
01155 pp = PrettyPrinter()
01156 if trees_dict:
01157 result["GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
01158 if ignore:
01159 result["GaudiTest.TTrees.ignore"] = result.Quote(ignore)
01160
01161 trees = findTTreeSummaries(stdout)
01162 failed = cmpTreesDicts(trees_dict, trees, ignore)
01163 if failed:
01164 causes.append("trees summaries")
01165 msg = "%s: %s != %s" % getCmpFailingValues(trees_dict, trees, failed)
01166 result["GaudiTest.TTrees.failure_on"] = result.Quote(msg)
01167 result["GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
01168
01169 return causes
01170
01171 def CheckHistosSummaries(self, stdout, result, causes,
01172 dict = None,
01173 ignore = None):
01174 """
01175 Compare the TTree summaries in stdout with the ones in trees_dict or in
01176 the reference file. By default ignore the size, compression and basket
01177 fields.
01178 The presence of TTree summaries when none is expected is not a failure.
01179 """
01180 if dict is None:
01181 reference = self._expandReferenceFileName(self.reference)
01182
01183 if reference and os.path.isfile(reference):
01184 dict = findHistosSummaries(open(reference).read())
01185 else:
01186 dict = {}
01187
01188 from pprint import PrettyPrinter
01189 pp = PrettyPrinter()
01190 if dict:
01191 result["GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
01192 if ignore:
01193 result["GaudiTest.Histos.ignore"] = result.Quote(ignore)
01194
01195 histos = findHistosSummaries(stdout)
01196 failed = cmpTreesDicts(dict, histos, ignore)
01197 if failed:
01198 causes.append("histos summaries")
01199 msg = "%s: %s != %s" % getCmpFailingValues(dict, histos, failed)
01200 result["GaudiTest.Histos.failure_on"] = result.Quote(msg)
01201 result["GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
01202
01203 return causes
01204
01205 def ValidateWithReference(self, stdout, stderr, result, causes, preproc = None):
01206 """
01207 Default validation action: compare standard output and error to the
01208 reference files.
01209 """
01210
01211 if preproc is None:
01212 preproc = normalizeExamples
01213
01214 reference = self._expandReferenceFileName(self.reference)
01215
01216 if reference and os.path.isfile(reference):
01217 result["GaudiTest.output_reference"] = reference
01218 causes += ReferenceFileValidator(reference,
01219 "standard output",
01220 "GaudiTest.output_diff",
01221 preproc = preproc)(stdout, result)
01222
01223
01224 causes = self.CheckTTreesSummaries(stdout, result, causes)
01225 causes = self.CheckHistosSummaries(stdout, result, causes)
01226
01227 if causes:
01228 try:
01229 newref = open(reference + ".new","w")
01230
01231 for l in stdout.splitlines():
01232 newref.write(l.rstrip() + '\n')
01233 del newref
01234 except IOError:
01235
01236
01237 pass
01238
01239
01240 reference = self._expandReferenceFileName(self.error_reference)
01241
01242 if reference and os.path.isfile(reference):
01243 result["GaudiTest.error_reference"] = reference
01244 newcauses = ReferenceFileValidator(reference,
01245 "standard error",
01246 "GaudiTest.error_diff",
01247 preproc = preproc)(stderr, result)
01248 causes += newcauses
01249 if newcauses:
01250 newref = open(reference + ".new","w")
01251
01252 for l in stderr.splitlines():
01253 newref.write(l.rstrip() + '\n')
01254 del newref
01255 else:
01256 causes += BasicOutputValidator(self.stderr,
01257 "standard error",
01258 "ExecTest.expected_stderr")(stderr, result)
01259
01260 return causes
01261
01262 def ValidateOutput(self, stdout, stderr, result):
01263 causes = []
01264
01265 if self.validator.strip() != "":
01266 class CallWrapper(object):
01267 """
01268 Small wrapper class to dynamically bind some default arguments
01269 to a callable.
01270 """
01271 def __init__(self, callable, extra_args = {}):
01272 self.callable = callable
01273 self.extra_args = extra_args
01274
01275 from inspect import getargspec
01276 self.args_order = getargspec(callable)[0]
01277
01278
01279 if self.args_order[0] == "self":
01280 del self.args_order[0]
01281 def __call__(self, *args, **kwargs):
01282
01283 positional = self.args_order[:len(args)]
01284
01285 kwargs = dict(kwargs)
01286 for a in self.extra_args:
01287
01288
01289 if a not in positional and a not in kwargs:
01290 kwargs[a] = self.extra_args[a]
01291 return apply(self.callable, args, kwargs)
01292
01293 exported_symbols = {"self":self,
01294 "stdout":stdout,
01295 "stderr":stderr,
01296 "result":result,
01297 "causes":causes,
01298 "findReferenceBlock":
01299 CallWrapper(findReferenceBlock, {"stdout":stdout,
01300 "result":result,
01301 "causes":causes}),
01302 "validateWithReference":
01303 CallWrapper(self.ValidateWithReference, {"stdout":stdout,
01304 "stderr":stderr,
01305 "result":result,
01306 "causes":causes}),
01307 "countErrorLines":
01308 CallWrapper(countErrorLines, {"stdout":stdout,
01309 "result":result,
01310 "causes":causes}),
01311 "checkTTreesSummaries":
01312 CallWrapper(self.CheckTTreesSummaries, {"stdout":stdout,
01313 "result":result,
01314 "causes":causes}),
01315 "checkHistosSummaries":
01316 CallWrapper(self.CheckHistosSummaries, {"stdout":stdout,
01317 "result":result,
01318 "causes":causes}),
01319
01320 }
01321 exec self.validator in globals(), exported_symbols
01322 else:
01323 self.ValidateWithReference(stdout, stderr, result, causes)
01324
01325 return causes
01326
01327 def DumpEnvironment(self, result):
01328 """
01329 Add the content of the environment to the result object.
01330
01331 Copied from the QMTest class of COOL.
01332 """
01333 vars = os.environ.keys()
01334 vars.sort()
01335 result['GaudiTest.environment'] = \
01336 result.Quote('\n'.join(["%s=%s"%(v,os.environ[v]) for v in vars]))
01337
01338 def _find_program(self,prog):
01339
01340
01341 if not os.path.isabs(prog) and not os.path.isfile(prog):
01342 for d in os.environ["PATH"].split(os.pathsep):
01343 p = os.path.join(d,prog)
01344 if os.path.isfile(p):
01345 return p
01346 return prog
01347
01348 def Run(self, context, result):
01349 """Run the test.
01350
01351 'context' -- A 'Context' giving run-time parameters to the
01352 test.
01353
01354 'result' -- A 'Result' object. The outcome will be
01355 'Result.PASS' when this method is called. The 'result' may be
01356 modified by this method to indicate outcomes other than
01357 'Result.PASS' or to add annotations."""
01358
01359
01360 if self.PlatformIsNotSupported(context, result):
01361 return
01362
01363
01364 if self.program:
01365 prog = rationalizepath(self.program)
01366 elif "GAUDIEXE" in os.environ:
01367 prog = os.environ["GAUDIEXE"]
01368 else:
01369 prog = "Gaudi.exe"
01370 self.program = prog
01371
01372 dummy, prog_ext = os.path.splitext(prog)
01373 if prog_ext not in [ ".exe", ".py", ".bat" ] and self.GetPlatform()[0:3] == "win":
01374 prog += ".exe"
01375 prog_ext = ".exe"
01376
01377 prog = self._find_program(prog)
01378
01379
01380 args = map(rationalizepath, self.args)
01381 self.reference = rationalizepath(self.reference)
01382 self.error_reference = rationalizepath(self.error_reference)
01383
01384
01385
01386 tmpfile = None
01387 if self.options.strip():
01388 ext = ".opts"
01389 if re.search(r"from\s*Gaudi.Configuration\s*import\s*\*", self.options):
01390 ext = ".py"
01391 tmpfile = TempFile(ext)
01392 tmpfile.writelines("\n".join(self.options.splitlines()))
01393 tmpfile.flush()
01394 args.append(tmpfile.name)
01395 result["GaudiTest.options"] = result.Quote(self.options)
01396
01397
01398 if prog_ext == ".py":
01399 args.insert(0,prog)
01400 if self.GetPlatform()[0:3] == "win":
01401 prog = self._find_program("python.exe")
01402 else:
01403 prog = self._find_program("python")
01404
01405
01406 origdir = os.getcwd()
01407 if self.workdir:
01408 os.chdir(str(os.path.normpath(os.path.expandvars(self.workdir))))
01409 elif self.use_temp_dir == "true":
01410 if "QMTEST_TMPDIR" in os.environ:
01411 os.chdir(os.environ["QMTEST_TMPDIR"])
01412 elif "qmtest.tmpdir" in context:
01413 os.chdir(context["qmtest.tmpdir"])
01414
01415 if "QMTEST_IGNORE_TIMEOUT" not in os.environ:
01416 self.timeout = max(self.timeout,600)
01417 else:
01418 self.timeout = -1
01419
01420 try:
01421
01422 self._CreateEclipseLaunch(prog, args, destdir = origdir)
01423
01424 self.RunProgram(prog,
01425 [ prog ] + args,
01426 context, result)
01427
01428 if result.GetOutcome() not in [ result.PASS ]:
01429 self.DumpEnvironment(result)
01430 finally:
01431
01432 os.chdir(origdir)
01433
01434 def RunProgram(self, program, arguments, context, result):
01435 """Run the 'program'.
01436
01437 'program' -- The path to the program to run.
01438
01439 'arguments' -- A list of the arguments to the program. This
01440 list must contain a first argument corresponding to 'argv[0]'.
01441
01442 'context' -- A 'Context' giving run-time parameters to the
01443 test.
01444
01445 'result' -- A 'Result' object. The outcome will be
01446 'Result.PASS' when this method is called. The 'result' may be
01447 modified by this method to indicate outcomes other than
01448 'Result.PASS' or to add annotations.
01449
01450 @attention: This method has been copied from command.ExecTestBase
01451 (QMTest 2.3.0) and modified to keep stdout and stderr
01452 for tests that have been terminated by a signal.
01453 (Fundamental for debugging in the Application Area)
01454 """
01455
01456
01457 environment = self.MakeEnvironment(context)
01458
01459 if self.timeout >= 0:
01460 timeout = self.timeout
01461 else:
01462
01463
01464
01465
01466
01467 timeout = -2
01468 e = GaudiFilterExecutable(self.stdin, timeout)
01469
01470 exit_status = e.Run(arguments, environment, path = program)
01471
01472 if e.stack_trace_file and os.path.exists(e.stack_trace_file):
01473 stack_trace = open(e.stack_trace_file).read()
01474 os.remove(e.stack_trace_file)
01475 else:
01476 stack_trace = None
01477 if stack_trace:
01478 result["ExecTest.stack_trace"] = result.Quote(stack_trace)
01479
01480
01481 if sys.platform == "win32" or os.WIFEXITED(exit_status):
01482
01483 causes = []
01484
01485
01486 if self.exit_code is None:
01487 exit_code = None
01488 elif sys.platform == "win32":
01489 exit_code = exit_status
01490 else:
01491 exit_code = os.WEXITSTATUS(exit_status)
01492
01493 stdout = e.stdout
01494 stderr = e.stderr
01495
01496 result["ExecTest.exit_code"] = str(exit_code)
01497 result["ExecTest.stdout"] = result.Quote(stdout)
01498 result["ExecTest.stderr"] = result.Quote(stderr)
01499
01500 if exit_code != self.exit_code:
01501 causes.append("exit_code")
01502 result["ExecTest.expected_exit_code"] \
01503 = str(self.exit_code)
01504
01505 causes += self.ValidateOutput(stdout, stderr, result)
01506
01507 if causes:
01508 result.Fail("Unexpected %s." % string.join(causes, ", "))
01509 elif os.WIFSIGNALED(exit_status):
01510
01511
01512 signal_number = str(os.WTERMSIG(exit_status))
01513 if not stack_trace:
01514 result.Fail("Program terminated by signal.")
01515 else:
01516
01517
01518 result.Fail("Exceeded time limit (%ds), terminated." % timeout)
01519 result["ExecTest.signal_number"] = signal_number
01520 result["ExecTest.stdout"] = result.Quote(e.stdout)
01521 result["ExecTest.stderr"] = result.Quote(e.stderr)
01522 elif os.WIFSTOPPED(exit_status):
01523
01524
01525 signal_number = str(os.WSTOPSIG(exit_status))
01526 if not stack_trace:
01527 result.Fail("Program stopped by signal.")
01528 else:
01529
01530
01531 result.Fail("Exceeded time limit (%ds), stopped." % timeout)
01532 result["ExecTest.signal_number"] = signal_number
01533 result["ExecTest.stdout"] = result.Quote(e.stdout)
01534 result["ExecTest.stderr"] = result.Quote(e.stderr)
01535 else:
01536
01537
01538 result.Fail("Program did not terminate normally.")
01539
01540
01541
01542 esc = '\x1b'
01543 repr_esc = '\\x1b'
01544 result["ExecTest.stdout"] = result["ExecTest.stdout"].replace(esc,repr_esc)
01545
01546
01547
01548 def _CreateEclipseLaunch(self, prog, args, destdir = None):
01549
01550
01551 projbasedir = os.path.normpath(destdir)
01552 while not os.path.exists(os.path.join(projbasedir, ".project")):
01553 oldprojdir = projbasedir
01554 projbasedir = os.path.normpath(os.path.join(projbasedir, os.pardir))
01555
01556
01557 if oldprojdir == projbasedir:
01558
01559 return
01560
01561 from xml.etree import ElementTree as ET
01562 t = ET.parse(os.path.join(projbasedir, ".project"))
01563 projectName = t.find("name").text
01564
01565
01566 destfile = "%s.launch" % self._Runnable__id
01567 if destdir:
01568 destfile = os.path.join(destdir, destfile)
01569
01570 if self.options.strip():
01571
01572
01573
01574 tempfile = args.pop()
01575 optsfile = destfile + os.path.splitext(tempfile)[1]
01576 shutil.copyfile(tempfile, optsfile)
01577 args.append(optsfile)
01578
01579
01580 from xml.sax.saxutils import quoteattr
01581 data = {}
01582
01583
01584 data["environment"] = "\n".join(['<mapEntry key=%s value=%s/>' % (quoteattr(k), quoteattr(v))
01585 for k, v in os.environ.iteritems()])
01586
01587 data["exec"] = which(prog)
01588
01589 data["args"] = " ".join(map(rationalizepath, args))
01590
01591 if not self.use_temp_dir:
01592 data["workdir"] = os.getcwd()
01593 else:
01594
01595
01596 data["workdir"] = destdir
01597
01598 data["project"] = projectName.strip()
01599
01600
01601 xml = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
01602 <launchConfiguration type="org.eclipse.cdt.launch.applicationLaunchType">
01603 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB" value="true"/>
01604 <listAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB_LIST"/>
01605 <stringAttribute key="org.eclipse.cdt.debug.mi.core.DEBUG_NAME" value="gdb"/>
01606 <stringAttribute key="org.eclipse.cdt.debug.mi.core.GDB_INIT" value=".gdbinit"/>
01607 <listAttribute key="org.eclipse.cdt.debug.mi.core.SOLIB_PATH"/>
01608 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.STOP_ON_SOLIB_EVENTS" value="false"/>
01609 <stringAttribute key="org.eclipse.cdt.debug.mi.core.protocol" value="mi"/>
01610 <stringAttribute key="org.eclipse.cdt.launch.COREFILE_PATH" value=""/>
01611 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_ID" value="org.eclipse.cdt.debug.mi.core.CDebugger"/>
01612 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_REGISTER_GROUPS" value=""/>
01613 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_START_MODE" value="run"/>
01614 <booleanAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN" value="true"/>
01615 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN_SYMBOL" value="main"/>
01616 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_REGISTER_BOOKKEEPING" value="false"/>
01617 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_VARIABLE_BOOKKEEPING" value="false"/>
01618 <stringAttribute key="org.eclipse.cdt.launch.FORMAT" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?><contentList/>"/>
01619 <stringAttribute key="org.eclipse.cdt.launch.GLOBAL_VARIABLES" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <globalVariableList/> "/>
01620 <stringAttribute key="org.eclipse.cdt.launch.MEMORY_BLOCKS" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <memoryBlockExpressionList/> "/>
01621 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_ARGUMENTS" value="%(args)s"/>
01622 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="%(exec)s"/>
01623 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_ATTR" value="%(project)s"/>
01624 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value=""/>
01625 <stringAttribute key="org.eclipse.cdt.launch.WORKING_DIRECTORY" value="%(workdir)s"/>
01626 <booleanAttribute key="org.eclipse.cdt.launch.use_terminal" value="true"/>
01627 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
01628 <listEntry value="/%(project)s"/>
01629 </listAttribute>
01630 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
01631 <listEntry value="4"/>
01632 </listAttribute>
01633 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="false"/>
01634 <mapAttribute key="org.eclipse.debug.core.environmentVariables">
01635 %(environment)s
01636 </mapAttribute>
01637 <mapAttribute key="org.eclipse.debug.core.preferred_launchers">
01638 <mapEntry key="[debug]" value="org.eclipse.cdt.cdi.launch.localCLaunch"/>
01639 </mapAttribute>
01640 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
01641 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
01642 </listAttribute>
01643 </launchConfiguration>
01644 """ % data
01645
01646
01647 open(destfile, "w").write(xml)
01648