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
00461 'Note: (file "(tmpfile)", line 2) File "set" already loaded',
00462 ],regexps = [
00463 r"^#",
00464 r"(Always|SUCCESS)\s*(Root f|[^ ]* F)ile version:",
00465 r"0x[0-9a-fA-F#]+ *Algorithm::sysInitialize\(\) *\[",
00466 r"0x[0-9a-fA-F#]* *__gxx_personality_v0 *\[",
00467 r"File '.*.xml' does not exist",
00468 r"INFO Refer to dataset .* by its file ID:",
00469 r"INFO Referring to dataset .* by its file ID:",
00470 r"INFO Disconnect from dataset",
00471 r"INFO Disconnected from dataset",
00472 r"INFO Disconnected data IO:",
00473 r"IncidentSvc\s*(DEBUG (Adding|Removing)|VERBOSE Calling)",
00474
00475 r"^StatusCodeSvc.*listing all unchecked return codes:",
00476 r"^StatusCodeSvc\s*INFO\s*$",
00477 r"Num\s*|\s*Function\s*|\s*Source Library",
00478 r"^[-+]*\s*$",
00479
00480 r"ERROR Failed to modify file: .* Errno=2 No such file or directory",
00481
00482 r"^ +[0-9]+ \|.*ROOT",
00483 r"^ +[0-9]+ \|.*\|.*Dict",
00484
00485 r"^\*.*\*$",
00486
00487 r"SUCCESS\s*Booked \d+ Histogram\(s\)",
00488 r"^ \|",
00489 r"^ ID=",
00490 ] ) + normalizeExamples + skipEmptyLines + \
00491 normalizeEOL + \
00492 LineSorter("Services to release : ")
00493
00494 class ReferenceFileValidator:
00495 def __init__(self, reffile, cause, result_key, preproc = normalizeExamples):
00496 self.reffile = os.path.expandvars(reffile)
00497 self.cause = cause
00498 self.result_key = result_key
00499 self.preproc = preproc
00500 def __call__(self, stdout, result):
00501 causes = []
00502 if os.path.isfile(self.reffile):
00503 orig = open(self.reffile).xreadlines()
00504 if self.preproc:
00505 orig = self.preproc(orig)
00506 else:
00507 orig = []
00508
00509 new = stdout.splitlines()
00510 if self.preproc:
00511 new = self.preproc(new)
00512
00513 diffs = difflib.ndiff(orig,new,charjunk=difflib.IS_CHARACTER_JUNK)
00514 filterdiffs = map(lambda x: x.strip(),filter(lambda x: x[0] != " ",diffs))
00515
00516 if filterdiffs:
00517 result[self.result_key] = result.Quote("\n".join(filterdiffs))
00518 result[self.result_key] += result.Quote("""
00519 Legend:
00520 -) reference file
00521 +) standard output of the test""")
00522 causes.append(self.cause)
00523
00524 return causes
00525
00526
00527
00528
00529 def findReferenceBlock(reference, stdout, result, causes, signature_offset=0, signature=None,
00530 id = None):
00531 """
00532 Given a block of text, tries to find it in the output.
00533 The block had to be identified by a signature line. By default, the first
00534 line is used as signature, or the line pointed to by signature_offset. If
00535 signature_offset points outside the block, a signature line can be passed as
00536 signature argument. Note: if 'signature' is None (the default), a negative
00537 signature_offset is interpreted as index in a list (e.g. -1 means the last
00538 line), otherwise the it is interpreted as the number of lines before the
00539 first one of the block the signature must appear.
00540 The parameter 'id' allow to distinguish between different calls to this
00541 function in the same validation code.
00542 """
00543
00544 reflines = filter(None,map(lambda s: s.rstrip(), reference.splitlines()))
00545 if not reflines:
00546 raise RuntimeError("Empty (or null) reference")
00547
00548 outlines = filter(None,map(lambda s: s.rstrip(), stdout.splitlines()))
00549
00550 res_field = "GaudiTest.RefBlock"
00551 if id:
00552 res_field += "_%s" % id
00553
00554 if signature is None:
00555 if signature_offset < 0:
00556 signature_offset = len(reference)+signature_offset
00557 signature = reflines[signature_offset]
00558
00559 try:
00560 pos = outlines.index(signature)
00561 outlines = outlines[pos-signature_offset:pos+len(reflines)-signature_offset]
00562 if reflines != outlines:
00563 msg = "standard output"
00564
00565 if not msg in causes:
00566 causes.append(msg)
00567 result[res_field + ".observed"] = result.Quote("\n".join(outlines))
00568 except ValueError:
00569 causes.append("missing signature")
00570 result[res_field + ".signature"] = result.Quote(signature)
00571 if len(reflines) > 1 or signature != reflines[0]:
00572 result[res_field + ".expected"] = result.Quote("\n".join(reflines))
00573
00574 return causes
00575
00576 def countErrorLines(expected = {'ERROR':0, 'FATAL':0}, **kwargs):
00577 """
00578 Count the number of messages with required severity (by default ERROR and FATAL)
00579 and check if their numbers match the expected ones (0 by default).
00580 The dictionary "expected" can be used to tune the number of errors and fatals
00581 allowed, or to limit the number of expected warnings etc.
00582 """
00583 stdout = kwargs["stdout"]
00584 result = kwargs["result"]
00585 causes = kwargs["causes"]
00586
00587
00588 errors = {}
00589 for sev in expected:
00590 errors[sev] = []
00591
00592 outlines = stdout.splitlines()
00593 from math import log10
00594 fmt = "%%%dd - %%s" % (int(log10(len(outlines))+1))
00595
00596 linecount = 0
00597 for l in outlines:
00598 linecount += 1
00599 words = l.split()
00600 if len(words) >= 2 and words[1] in errors:
00601 errors[words[1]].append(fmt%(linecount,l.rstrip()))
00602
00603 for e in errors:
00604 if len(errors[e]) != expected[e]:
00605 causes.append('%s(%d)'%(e,len(errors[e])))
00606 result["GaudiTest.lines.%s"%e] = result.Quote('\n'.join(errors[e]))
00607 result["GaudiTest.lines.%s.expected#"%e] = result.Quote(str(expected[e]))
00608
00609 return causes
00610
00611
00612 def _parseTTreeSummary(lines, pos):
00613 """
00614 Parse the TTree summary table in lines, starting from pos.
00615 Returns a tuple with the dictionary with the digested informations and the
00616 position of the first line after the summary.
00617 """
00618 result = {}
00619 i = pos + 1
00620 count = len(lines)
00621
00622 splitcols = lambda l: [ f.strip() for f in l.strip("*\n").split(':',2) ]
00623 def parseblock(ll):
00624 r = {}
00625 cols = splitcols(ll[0])
00626 r["Name"], r["Title"] = cols[1:]
00627
00628 cols = splitcols(ll[1])
00629 r["Entries"] = int(cols[1])
00630
00631 sizes = cols[2].split()
00632 r["Total size"] = int(sizes[2])
00633 if sizes[-1] == "memory":
00634 r["File size"] = 0
00635 else:
00636 r["File size"] = int(sizes[-1])
00637
00638 cols = splitcols(ll[2])
00639 sizes = cols[2].split()
00640 if cols[0] == "Baskets":
00641 r["Baskets"] = int(cols[1])
00642 r["Basket size"] = int(sizes[2])
00643 r["Compression"] = float(sizes[-1])
00644 return r
00645
00646 if i < (count - 3) and lines[i].startswith("*Tree"):
00647 result = parseblock(lines[i:i+3])
00648 result["Branches"] = {}
00649 i += 4
00650 while i < (count - 3) and lines[i].startswith("*Br"):
00651 branch = parseblock(lines[i:i+3])
00652 result["Branches"][branch["Name"]] = branch
00653 i += 4
00654
00655 return (result, i)
00656
00657 def findTTreeSummaries(stdout):
00658 """
00659 Scan stdout to find ROOT TTree summaries and digest them.
00660 """
00661 stars = re.compile(r"^\*+$")
00662 outlines = stdout.splitlines()
00663 nlines = len(outlines)
00664 trees = {}
00665
00666 i = 0
00667 while i < nlines:
00668
00669 while i < nlines and not stars.match(outlines[i]):
00670 i += 1
00671 if i < nlines:
00672 tree, i = _parseTTreeSummary(outlines, i)
00673 if tree:
00674 trees[tree["Name"]] = tree
00675
00676 return trees
00677
00678 def cmpTreesDicts(reference, to_check, ignore = None):
00679 """
00680 Check that all the keys in reference are in to_check too, with the same value.
00681 If the value is a dict, the function is called recursively. to_check can
00682 contain more keys than reference, that will not be tested.
00683 The function returns at the first difference found.
00684 """
00685 fail_keys = []
00686
00687 if ignore:
00688 ignore_re = re.compile(ignore)
00689 keys = [ key for key in reference if not ignore_re.match(key) ]
00690 else:
00691 keys = reference.keys()
00692
00693 for k in keys:
00694 if k in to_check:
00695 if (type(reference[k]) is dict) and (type(to_check[k]) is dict):
00696
00697 failed = fail_keys = cmpTreesDicts(reference[k], to_check[k], ignore)
00698 else:
00699
00700 failed = to_check[k] != reference[k]
00701 else:
00702 to_check[k] = None
00703 failed = True
00704 if failed:
00705 fail_keys.insert(0, k)
00706 break
00707 return fail_keys
00708
00709 def getCmpFailingValues(reference, to_check, fail_path):
00710 c = to_check
00711 r = reference
00712 for k in fail_path:
00713 c = c.get(k,None)
00714 r = r.get(k,None)
00715 if c is None or r is None:
00716 break
00717 return (fail_path, r, c)
00718
00719
00720 h_count_re = re.compile(r"^(.*)SUCCESS\s+Booked (\d+) Histogram\(s\) :\s+(.*)")
00721
00722 def parseHistosSummary(lines, pos):
00723 """
00724 Extract the histograms infos from the lines starting at pos.
00725 Returns the position of the first line after the summary block.
00726 """
00727 global h_count_re
00728 h_table_head = re.compile(r'SUCCESS\s+List of booked (1D|2D|3D|1D profile|2D profile) histograms in directory\s+"(\w*)"')
00729 h_short_summ = re.compile(r"ID=([^\"]+)\s+\"([^\"]+)\"\s+(.*)")
00730
00731 nlines = len(lines)
00732
00733
00734 m = h_count_re.search(lines[pos])
00735 name = m.group(1).strip()
00736 total = int(m.group(2))
00737 header = {}
00738 for k, v in [ x.split("=") for x in m.group(3).split() ]:
00739 header[k] = int(v)
00740 pos += 1
00741 header["Total"] = total
00742
00743 summ = {}
00744 while pos < nlines:
00745 m = h_table_head.search(lines[pos])
00746 if m:
00747 t, d = m.groups(1)
00748 t = t.replace(" profile", "Prof")
00749 pos += 1
00750 if pos < nlines:
00751 l = lines[pos]
00752 else:
00753 l = ""
00754 cont = {}
00755 if l.startswith(" | ID"):
00756
00757 titles = [ x.strip() for x in l.split("|")][1:]
00758 pos += 1
00759 while pos < nlines and lines[pos].startswith(" |"):
00760 l = lines[pos]
00761 values = [ x.strip() for x in l.split("|")][1:]
00762 hcont = {}
00763 for i in range(len(titles)):
00764 hcont[titles[i]] = values[i]
00765 cont[hcont["ID"]] = hcont
00766 pos += 1
00767 elif l.startswith(" ID="):
00768 while pos < nlines and lines[pos].startswith(" ID="):
00769 values = [ x.strip() for x in h_short_summ.search(lines[pos]).groups() ]
00770 cont[values[0]] = values
00771 pos += 1
00772 else:
00773 raise RuntimeError("Cannot understand line %d: '%s'" % (pos, l))
00774 if not d in summ:
00775 summ[d] = {}
00776 summ[d][t] = cont
00777 summ[d]["header"] = header
00778 else:
00779 break
00780 if not summ:
00781
00782 summ[name] = {"header": header}
00783 return summ, pos
00784
00785 def findHistosSummaries(stdout):
00786 """
00787 Scan stdout to find ROOT TTree summaries and digest them.
00788 """
00789 outlines = stdout.splitlines()
00790 nlines = len(outlines) - 1
00791 summaries = {}
00792 global h_count_re
00793
00794 pos = 0
00795 while pos < nlines:
00796 summ = {}
00797
00798 match = h_count_re.search(outlines[pos])
00799 while pos < nlines and not match:
00800 pos += 1
00801 match = h_count_re.search(outlines[pos])
00802 if match:
00803 summ, pos = parseHistosSummary(outlines, pos)
00804 summaries.update(summ)
00805 return summaries
00806
00807 class GaudiFilterExecutable(qm.executable.Filter):
00808 def __init__(self, input, timeout = -1):
00809 """Create a new 'Filter'.
00810
00811 'input' -- The string containing the input to provide to the
00812 child process.
00813
00814 'timeout' -- As for 'TimeoutExecutable.__init__'."""
00815
00816 super(GaudiFilterExecutable, self).__init__(input, timeout)
00817 self.__input = input
00818 self.__timeout = timeout
00819 self.stack_trace_file = None
00820
00821
00822
00823 tmpf = tempfile.mkstemp()
00824 os.close(tmpf[0])
00825 self.stack_trace_file = tmpf[1]
00826
00827 def __UseSeparateProcessGroupForChild(self):
00828 """Copied from TimeoutExecutable to allow the re-implementation of
00829 _HandleChild.
00830 """
00831 if sys.platform == "win32":
00832
00833
00834
00835
00836 return 0
00837
00838 return self.__timeout >= 0 or self.__timeout == -2
00839
00840
00841 def _HandleChild(self):
00842 """Code copied from both FilterExecutable and TimeoutExecutable.
00843 """
00844
00845 if self._stdin_pipe:
00846 self._ClosePipeEnd(self._stdin_pipe[0])
00847 if self._stdout_pipe:
00848 self._ClosePipeEnd(self._stdout_pipe[1])
00849 if self._stderr_pipe:
00850 self._ClosePipeEnd(self._stderr_pipe[1])
00851
00852
00853
00854
00855
00856
00857
00858 super(qm.executable.TimeoutExecutable, self)._HandleChild()
00859
00860 if self.__UseSeparateProcessGroupForChild():
00861
00862
00863
00864
00865 child_pid = self._GetChildPID()
00866 try:
00867 os.setpgid(child_pid, child_pid)
00868 except:
00869
00870
00871
00872
00873 pass
00874
00875
00876
00877
00878
00879
00880
00881
00882
00883 self.__monitor_pid = os.fork()
00884 if self.__monitor_pid != 0:
00885
00886
00887
00888
00889 os.setpgid(self.__monitor_pid, child_pid)
00890 else:
00891
00892
00893
00894
00895 os.setpgid(0, child_pid)
00896
00897
00898
00899
00900
00901
00902
00903 try:
00904 max_fds = os.sysconf("SC_OPEN_MAX")
00905 except:
00906 max_fds = 256
00907 for fd in xrange(max_fds):
00908 try:
00909 os.close(fd)
00910 except:
00911 pass
00912 try:
00913 if self.__timeout >= 0:
00914
00915 time.sleep (self.__timeout)
00916
00917
00918 if sys.platform == "linux2":
00919 cmd = ["gdb",
00920 os.path.join("/proc", str(child_pid), "exe"),
00921 str(child_pid),
00922 "-batch", "-n", "-x",
00923 "'%s'" % os.path.join(os.path.dirname(__file__), "stack-trace.gdb")]
00924
00925
00926 o = os.popen(" ".join(cmd)).read()
00927 open(self.stack_trace_file,"w").write(o)
00928
00929
00930
00931 os.kill(0, signal.SIGKILL)
00932 else:
00933
00934 select.select ([], [], [])
00935 finally:
00936
00937
00938 os._exit(0)
00939 elif self.__timeout >= 0 and sys.platform == "win32":
00940
00941 self.__monitor_thread = Thread(target = self.__Monitor)
00942 self.__monitor_thread.start()
00943
00944 if sys.platform == "win32":
00945
00946 def __Monitor(self):
00947 """Code copied from FilterExecutable.
00948 Kill the child if the timeout expires.
00949
00950 This function is run in the monitoring thread."""
00951
00952
00953
00954
00955 timeout = int(self.__timeout * 1000)
00956
00957
00958 result = win32event.WaitForSingleObject(self._GetChildPID(),
00959 timeout)
00960
00961 if result == win32con.WAIT_TIMEOUT:
00962 self.Kill()
00963
00964
00965
00966
00967 class GaudiExeTest(ExecTestBase):
00968 """Standard Gaudi test.
00969 """
00970 arguments = [
00971 qm.fields.TextField(
00972 name="program",
00973 title="Program",
00974 not_empty_text=1,
00975 description="""The path to the program.
00976
00977 This field indicates the path to the program. If it is not
00978 an absolute path, the value of the 'PATH' environment
00979 variable will be used to search for the program.
00980 If not specified, $GAUDIEXE or Gaudi.exe are used.
00981 """
00982 ),
00983 qm.fields.SetField(qm.fields.TextField(
00984 name="args",
00985 title="Argument List",
00986 description="""The command-line arguments.
00987
00988 If this field is left blank, the program is run without any
00989 arguments.
00990
00991 Use this field to specify the option files.
00992
00993 An implicit 0th argument (the path to the program) is added
00994 automatically."""
00995 )),
00996 qm.fields.TextField(
00997 name="options",
00998 title="Options",
00999 description="""Options to be passed to the application.
01000
01001 This field allows to pass a list of options to the main program
01002 without the need of a separate option file.
01003
01004 The content of the field is written to a temporary file which name
01005 is passed the the application as last argument (appended to the
01006 field "Argument List".
01007 """,
01008 verbatim="true",
01009 multiline="true",
01010 default_value=""
01011 ),
01012 qm.fields.TextField(
01013 name="workdir",
01014 title="Working Directory",
01015 description="""Path to the working directory.
01016
01017 If this field is left blank, the program will be run from the qmtest
01018 directory, otherwise from the directory specified.""",
01019 default_value=""
01020 ),
01021 qm.fields.TextField(
01022 name="reference",
01023 title="Reference Output",
01024 description="""Path to the file containing the reference output.
01025
01026 If this field is left blank, any standard output will be considered
01027 valid.
01028
01029 If the reference file is specified, any output on standard error is
01030 ignored."""
01031 ),
01032 qm.fields.TextField(
01033 name="error_reference",
01034 title="Reference for standard error",
01035 description="""Path to the file containing the reference for the standard error.
01036
01037 If this field is left blank, any standard output will be considered
01038 valid.
01039
01040 If the reference file is specified, any output on standard error is
01041 ignored."""
01042 ),
01043 qm.fields.SetField(qm.fields.TextField(
01044 name = "unsupported_platforms",
01045 title = "Unsupported Platforms",
01046 description = """Platform on which the test must not be run.
01047
01048 List of regular expressions identifying the platforms on which the
01049 test is not run and the result is set to UNTESTED."""
01050 )),
01051
01052 qm.fields.TextField(
01053 name = "validator",
01054 title = "Validator",
01055 description = """Function to validate the output of the test.
01056
01057 If defined, the function is used to validate the products of the
01058 test.
01059 The function is called passing as arguments:
01060 self: the test class instance
01061 stdout: the standard output of the executed test
01062 stderr: the standard error of the executed test
01063 result: the Result objects to fill with messages
01064 The function must return a list of causes for the failure.
01065 If specified, overrides standard output, standard error and
01066 reference files.
01067 """,
01068 verbatim="true",
01069 multiline="true",
01070 default_value=""
01071 ),
01072
01073 qm.fields.BooleanField(
01074 name = "use_temp_dir",
01075 title = "Use temporary directory",
01076 description = """Use temporary directory.
01077
01078 If set to true, use a temporary directory as working directory.
01079 """,
01080 default_value="false"
01081 ),
01082 ]
01083
01084 def PlatformIsNotSupported(self, context, result):
01085 platform = self.GetPlatform()
01086 unsupported = [ re.compile(x)
01087 for x in [ str(y).strip()
01088 for y in self.unsupported_platforms ]
01089 if x
01090 ]
01091 for p_re in unsupported:
01092 if p_re.search(platform):
01093 result.SetOutcome(result.UNTESTED)
01094 result[result.CAUSE] = 'Platform not supported.'
01095 return True
01096 return False
01097
01098 def GetPlatform(self):
01099 """
01100 Return the platform Id defined in CMTCONFIG or SCRAM_ARCH.
01101 """
01102 arch = "None"
01103
01104 if "CMTCONFIG" in os.environ:
01105 arch = os.environ["CMTCONFIG"]
01106 elif "SCRAM_ARCH" in os.environ:
01107 arch = os.environ["SCRAM_ARCH"]
01108 return arch
01109
01110 def _expandReferenceFileName(self, reffile):
01111
01112 if not reffile:
01113 return ""
01114
01115 reference = os.path.normpath(os.path.expandvars(reffile))
01116
01117 spec_ref = reference[:-3] + self.GetPlatform()[0:3] + reference[-3:]
01118 if os.path.isfile(spec_ref):
01119 reference = spec_ref
01120 else:
01121
01122 dirname, basename = os.path.split(reference)
01123 if not dirname: dirname = '.'
01124 head = basename + "."
01125 head_len = len(head)
01126 platform = self.GetPlatform()
01127 candidates = []
01128 for f in os.listdir(dirname):
01129 if f.startswith(head) and platform.startswith(f[head_len:]):
01130 candidates.append( (len(f) - head_len, f) )
01131 if candidates:
01132 candidates.sort()
01133 reference = os.path.join(dirname, candidates[-1][1])
01134 return reference
01135
01136 def CheckTTreesSummaries(self, stdout, result, causes,
01137 trees_dict = None,
01138 ignore = r"Basket|.*size|Compression"):
01139 """
01140 Compare the TTree summaries in stdout with the ones in trees_dict or in
01141 the reference file. By default ignore the size, compression and basket
01142 fields.
01143 The presence of TTree summaries when none is expected is not a failure.
01144 """
01145 if trees_dict is None:
01146 reference = self._expandReferenceFileName(self.reference)
01147
01148 if reference and os.path.isfile(reference):
01149 trees_dict = findTTreeSummaries(open(reference).read())
01150 else:
01151 trees_dict = {}
01152
01153 from pprint import PrettyPrinter
01154 pp = PrettyPrinter()
01155 if trees_dict:
01156 result["GaudiTest.TTrees.expected"] = result.Quote(pp.pformat(trees_dict))
01157 if ignore:
01158 result["GaudiTest.TTrees.ignore"] = result.Quote(ignore)
01159
01160 trees = findTTreeSummaries(stdout)
01161 failed = cmpTreesDicts(trees_dict, trees, ignore)
01162 if failed:
01163 causes.append("trees summaries")
01164 msg = "%s: %s != %s" % getCmpFailingValues(trees_dict, trees, failed)
01165 result["GaudiTest.TTrees.failure_on"] = result.Quote(msg)
01166 result["GaudiTest.TTrees.found"] = result.Quote(pp.pformat(trees))
01167
01168 return causes
01169
01170 def CheckHistosSummaries(self, stdout, result, causes,
01171 dict = None,
01172 ignore = None):
01173 """
01174 Compare the TTree summaries in stdout with the ones in trees_dict or in
01175 the reference file. By default ignore the size, compression and basket
01176 fields.
01177 The presence of TTree summaries when none is expected is not a failure.
01178 """
01179 if dict is None:
01180 reference = self._expandReferenceFileName(self.reference)
01181
01182 if reference and os.path.isfile(reference):
01183 dict = findHistosSummaries(open(reference).read())
01184 else:
01185 dict = {}
01186
01187 from pprint import PrettyPrinter
01188 pp = PrettyPrinter()
01189 if dict:
01190 result["GaudiTest.Histos.expected"] = result.Quote(pp.pformat(dict))
01191 if ignore:
01192 result["GaudiTest.Histos.ignore"] = result.Quote(ignore)
01193
01194 histos = findHistosSummaries(stdout)
01195 failed = cmpTreesDicts(dict, histos, ignore)
01196 if failed:
01197 causes.append("histos summaries")
01198 msg = "%s: %s != %s" % getCmpFailingValues(dict, histos, failed)
01199 result["GaudiTest.Histos.failure_on"] = result.Quote(msg)
01200 result["GaudiTest.Histos.found"] = result.Quote(pp.pformat(histos))
01201
01202 return causes
01203
01204 def ValidateWithReference(self, stdout, stderr, result, causes, preproc = None):
01205 """
01206 Default validation action: compare standard output and error to the
01207 reference files.
01208 """
01209
01210 if preproc is None:
01211 preproc = normalizeExamples
01212
01213 reference = self._expandReferenceFileName(self.reference)
01214
01215 if reference and os.path.isfile(reference):
01216 result["GaudiTest.output_reference"] = reference
01217 causes += ReferenceFileValidator(reference,
01218 "standard output",
01219 "GaudiTest.output_diff",
01220 preproc = preproc)(stdout, result)
01221
01222
01223 causes = self.CheckTTreesSummaries(stdout, result, causes)
01224 causes = self.CheckHistosSummaries(stdout, result, causes)
01225
01226 if causes:
01227 newref = open(reference + ".new","w")
01228
01229 for l in stdout.splitlines():
01230 newref.write(l.rstrip() + '\n')
01231 del newref
01232
01233
01234
01235 reference = self._expandReferenceFileName(self.error_reference)
01236
01237 if reference and os.path.isfile(reference):
01238 result["GaudiTest.error_reference"] = reference
01239 newcauses = ReferenceFileValidator(reference,
01240 "standard error",
01241 "GaudiTest.error_diff",
01242 preproc = preproc)(stderr, result)
01243 causes += newcauses
01244 if newcauses:
01245 newref = open(reference + ".new","w")
01246
01247 for l in stderr.splitlines():
01248 newref.write(l.rstrip() + '\n')
01249 del newref
01250 else:
01251 causes += BasicOutputValidator(self.stderr,
01252 "standard error",
01253 "ExecTest.expected_stderr")(stderr, result)
01254
01255 return causes
01256
01257 def ValidateOutput(self, stdout, stderr, result):
01258 causes = []
01259
01260 if self.validator.strip() != "":
01261 class CallWrapper(object):
01262 """
01263 Small wrapper class to dynamically bind some default arguments
01264 to a callable.
01265 """
01266 def __init__(self, callable, extra_args = {}):
01267 self.callable = callable
01268 self.extra_args = extra_args
01269
01270 from inspect import getargspec
01271 self.args_order = getargspec(callable)[0]
01272
01273
01274 if self.args_order[0] == "self":
01275 del self.args_order[0]
01276 def __call__(self, *args, **kwargs):
01277
01278 positional = self.args_order[:len(args)]
01279
01280 kwargs = dict(kwargs)
01281 for a in self.extra_args:
01282
01283
01284 if a not in positional and a not in kwargs:
01285 kwargs[a] = self.extra_args[a]
01286 return apply(self.callable, args, kwargs)
01287
01288 exported_symbols = {"self":self,
01289 "stdout":stdout,
01290 "stderr":stderr,
01291 "result":result,
01292 "causes":causes,
01293 "findReferenceBlock":
01294 CallWrapper(findReferenceBlock, {"stdout":stdout,
01295 "result":result,
01296 "causes":causes}),
01297 "validateWithReference":
01298 CallWrapper(self.ValidateWithReference, {"stdout":stdout,
01299 "stderr":stderr,
01300 "result":result,
01301 "causes":causes}),
01302 "countErrorLines":
01303 CallWrapper(countErrorLines, {"stdout":stdout,
01304 "result":result,
01305 "causes":causes}),
01306 "checkTTreesSummaries":
01307 CallWrapper(self.CheckTTreesSummaries, {"stdout":stdout,
01308 "result":result,
01309 "causes":causes}),
01310 "checkHistosSummaries":
01311 CallWrapper(self.CheckHistosSummaries, {"stdout":stdout,
01312 "result":result,
01313 "causes":causes}),
01314
01315 }
01316 exec self.validator in globals(), exported_symbols
01317 else:
01318 self.ValidateWithReference(stdout, stderr, result, causes)
01319
01320 return causes
01321
01322 def DumpEnvironment(self, result):
01323 """
01324 Add the content of the environment to the result object.
01325
01326 Copied from the QMTest class of COOL.
01327 """
01328 vars = os.environ.keys()
01329 vars.sort()
01330 result['GaudiTest.environment'] = \
01331 result.Quote('\n'.join(["%s=%s"%(v,os.environ[v]) for v in vars]))
01332
01333 def _find_program(self,prog):
01334
01335
01336 if not os.path.isabs(prog) and not os.path.isfile(prog):
01337 for d in os.environ["PATH"].split(os.pathsep):
01338 p = os.path.join(d,prog)
01339 if os.path.isfile(p):
01340 return p
01341 return prog
01342
01343 def Run(self, context, result):
01344 """Run the test.
01345
01346 'context' -- A 'Context' giving run-time parameters to the
01347 test.
01348
01349 'result' -- A 'Result' object. The outcome will be
01350 'Result.PASS' when this method is called. The 'result' may be
01351 modified by this method to indicate outcomes other than
01352 'Result.PASS' or to add annotations."""
01353
01354
01355 if self.PlatformIsNotSupported(context, result):
01356 return
01357
01358
01359 if self.program:
01360 prog = rationalizepath(self.program)
01361 elif "GAUDIEXE" in os.environ:
01362 prog = os.environ["GAUDIEXE"]
01363 else:
01364 prog = "Gaudi.exe"
01365 self.program = prog
01366
01367 dummy, prog_ext = os.path.splitext(prog)
01368 if prog_ext not in [ ".exe", ".py", ".bat" ] and self.GetPlatform()[0:3] == "win":
01369 prog += ".exe"
01370 prog_ext = ".exe"
01371
01372 prog = self._find_program(prog)
01373
01374
01375 args = map(rationalizepath, self.args)
01376 self.reference = rationalizepath(self.reference)
01377 self.error_reference = rationalizepath(self.error_reference)
01378
01379
01380
01381 tmpfile = None
01382 if self.options.strip():
01383 ext = ".opts"
01384 if re.search(r"from\s*Gaudi.Configuration\s*import\s*\*", self.options):
01385 ext = ".py"
01386 tmpfile = TempFile(ext)
01387 tmpfile.writelines("\n".join(self.options.splitlines()))
01388 tmpfile.flush()
01389 args.append(tmpfile.name)
01390 result["GaudiTest.options"] = result.Quote(self.options)
01391
01392
01393 if prog_ext == ".py":
01394 args.insert(0,prog)
01395 if self.GetPlatform()[0:3] == "win":
01396 prog = self._find_program("python.exe")
01397 else:
01398 prog = self._find_program("python")
01399
01400
01401 origdir = os.getcwd()
01402 if self.workdir:
01403 os.chdir(str(os.path.normpath(os.path.expandvars(self.workdir))))
01404 elif "qmtest.tmpdir" in context and self.use_temp_dir == "true":
01405 os.chdir(context["qmtest.tmpdir"])
01406
01407 if "QMTEST_IGNORE_TIMEOUT" not in os.environ:
01408 self.timeout = max(self.timeout,600)
01409 else:
01410 self.timeout = -1
01411
01412 try:
01413
01414 self._CreateEclipseLaunch(prog, args, destdir = origdir)
01415
01416 self.RunProgram(prog,
01417 [ prog ] + args,
01418 context, result)
01419
01420 if result.GetOutcome() not in [ result.PASS ]:
01421 self.DumpEnvironment(result)
01422 finally:
01423
01424 os.chdir(origdir)
01425
01426 def RunProgram(self, program, arguments, context, result):
01427 """Run the 'program'.
01428
01429 'program' -- The path to the program to run.
01430
01431 'arguments' -- A list of the arguments to the program. This
01432 list must contain a first argument corresponding to 'argv[0]'.
01433
01434 'context' -- A 'Context' giving run-time parameters to the
01435 test.
01436
01437 'result' -- A 'Result' object. The outcome will be
01438 'Result.PASS' when this method is called. The 'result' may be
01439 modified by this method to indicate outcomes other than
01440 'Result.PASS' or to add annotations.
01441
01442 @attention: This method has been copied from command.ExecTestBase
01443 (QMTest 2.3.0) and modified to keep stdout and stderr
01444 for tests that have been terminated by a signal.
01445 (Fundamental for debugging in the Application Area)
01446 """
01447
01448
01449 environment = self.MakeEnvironment(context)
01450
01451 if self.timeout >= 0:
01452 timeout = self.timeout
01453 else:
01454
01455
01456
01457
01458
01459 timeout = -2
01460 e = GaudiFilterExecutable(self.stdin, timeout)
01461
01462 exit_status = e.Run(arguments, environment, path = program)
01463
01464 if e.stack_trace_file and os.path.exists(e.stack_trace_file):
01465 stack_trace = open(e.stack_trace_file).read()
01466 os.remove(e.stack_trace_file)
01467 else:
01468 stack_trace = None
01469 if stack_trace:
01470 result["ExecTest.stack_trace"] = result.Quote(stack_trace)
01471
01472
01473 if sys.platform == "win32" or os.WIFEXITED(exit_status):
01474
01475 causes = []
01476
01477
01478 if self.exit_code is None:
01479 exit_code = None
01480 elif sys.platform == "win32":
01481 exit_code = exit_status
01482 else:
01483 exit_code = os.WEXITSTATUS(exit_status)
01484
01485 stdout = e.stdout
01486 stderr = e.stderr
01487
01488 result["ExecTest.exit_code"] = str(exit_code)
01489 result["ExecTest.stdout"] = result.Quote(stdout)
01490 result["ExecTest.stderr"] = result.Quote(stderr)
01491
01492 if exit_code != self.exit_code:
01493 causes.append("exit_code")
01494 result["ExecTest.expected_exit_code"] \
01495 = str(self.exit_code)
01496
01497 causes += self.ValidateOutput(stdout, stderr, result)
01498
01499 if causes:
01500 result.Fail("Unexpected %s." % string.join(causes, ", "))
01501 elif os.WIFSIGNALED(exit_status):
01502
01503
01504 signal_number = str(os.WTERMSIG(exit_status))
01505 result.Fail("Program terminated by signal.")
01506 result["ExecTest.signal_number"] = signal_number
01507 result["ExecTest.stdout"] = result.Quote(e.stdout)
01508 result["ExecTest.stderr"] = result.Quote(e.stderr)
01509 elif os.WIFSTOPPED(exit_status):
01510
01511
01512 signal_number = str(os.WSTOPSIG(exit_status))
01513 result.Fail("Program stopped by signal.")
01514 result["ExecTest.signal_number"] = signal_number
01515 result["ExecTest.stdout"] = result.Quote(e.stdout)
01516 result["ExecTest.stderr"] = result.Quote(e.stderr)
01517 else:
01518
01519
01520 result.Fail("Program did not terminate normally.")
01521
01522
01523
01524 esc = '\x1b'
01525 repr_esc = '\\x1b'
01526 result["ExecTest.stdout"] = result["ExecTest.stdout"].replace(esc,repr_esc)
01527
01528
01529
01530 def _CreateEclipseLaunch(self, prog, args, destdir = None):
01531
01532
01533 projbasedir = os.path.normpath(destdir)
01534 while not os.path.exists(os.path.join(projbasedir, ".project")):
01535 oldprojdir = projbasedir
01536 projbasedir = os.path.normpath(os.path.join(projbasedir, os.pardir))
01537
01538
01539 if oldprojdir == projbasedir:
01540
01541 return
01542
01543 from xml.etree import ElementTree as ET
01544 t = ET.parse(os.path.join(projbasedir, ".project"))
01545 projectName = t.find("name").text
01546
01547
01548 destfile = "%s.launch" % self._Runnable__id
01549 if destdir:
01550 destfile = os.path.join(destdir, destfile)
01551
01552 if self.options.strip():
01553
01554
01555
01556 tempfile = args.pop()
01557 optsfile = destfile + os.path.splitext(tempfile)[1]
01558 shutil.copyfile(tempfile, optsfile)
01559 args.append(optsfile)
01560
01561
01562 from xml.sax.saxutils import quoteattr
01563 data = {}
01564
01565
01566 data["environment"] = "\n".join(['<mapEntry key=%s value=%s/>' % (quoteattr(k), quoteattr(v))
01567 for k, v in os.environ.iteritems()])
01568
01569 data["exec"] = which(prog)
01570
01571 data["args"] = " ".join(map(rationalizepath, args))
01572
01573 if not self.use_temp_dir:
01574 data["workdir"] = os.getcwd()
01575 else:
01576
01577
01578 data["workdir"] = destdir
01579
01580 data["project"] = projectName.strip()
01581
01582
01583 xml = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
01584 <launchConfiguration type="org.eclipse.cdt.launch.applicationLaunchType">
01585 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB" value="true"/>
01586 <listAttribute key="org.eclipse.cdt.debug.mi.core.AUTO_SOLIB_LIST"/>
01587 <stringAttribute key="org.eclipse.cdt.debug.mi.core.DEBUG_NAME" value="gdb"/>
01588 <stringAttribute key="org.eclipse.cdt.debug.mi.core.GDB_INIT" value=".gdbinit"/>
01589 <listAttribute key="org.eclipse.cdt.debug.mi.core.SOLIB_PATH"/>
01590 <booleanAttribute key="org.eclipse.cdt.debug.mi.core.STOP_ON_SOLIB_EVENTS" value="false"/>
01591 <stringAttribute key="org.eclipse.cdt.debug.mi.core.protocol" value="mi"/>
01592 <stringAttribute key="org.eclipse.cdt.launch.COREFILE_PATH" value=""/>
01593 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_ID" value="org.eclipse.cdt.debug.mi.core.CDebugger"/>
01594 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_REGISTER_GROUPS" value=""/>
01595 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_START_MODE" value="run"/>
01596 <booleanAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN" value="true"/>
01597 <stringAttribute key="org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN_SYMBOL" value="main"/>
01598 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_REGISTER_BOOKKEEPING" value="false"/>
01599 <booleanAttribute key="org.eclipse.cdt.launch.ENABLE_VARIABLE_BOOKKEEPING" value="false"/>
01600 <stringAttribute key="org.eclipse.cdt.launch.FORMAT" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?><contentList/>"/>
01601 <stringAttribute key="org.eclipse.cdt.launch.GLOBAL_VARIABLES" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <globalVariableList/> "/>
01602 <stringAttribute key="org.eclipse.cdt.launch.MEMORY_BLOCKS" value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <memoryBlockExpressionList/> "/>
01603 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_ARGUMENTS" value="%(args)s"/>
01604 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="%(exec)s"/>
01605 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_ATTR" value="%(project)s"/>
01606 <stringAttribute key="org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR" value=""/>
01607 <stringAttribute key="org.eclipse.cdt.launch.WORKING_DIRECTORY" value="%(workdir)s"/>
01608 <booleanAttribute key="org.eclipse.cdt.launch.use_terminal" value="true"/>
01609 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
01610 <listEntry value="/%(project)s"/>
01611 </listAttribute>
01612 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
01613 <listEntry value="4"/>
01614 </listAttribute>
01615 <booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="false"/>
01616 <mapAttribute key="org.eclipse.debug.core.environmentVariables">
01617 %(environment)s
01618 </mapAttribute>
01619 <mapAttribute key="org.eclipse.debug.core.preferred_launchers">
01620 <mapEntry key="[debug]" value="org.eclipse.cdt.cdi.launch.localCLaunch"/>
01621 </mapAttribute>
01622 <listAttribute key="org.eclipse.debug.ui.favoriteGroups">
01623 <listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
01624 </listAttribute>
01625 </launchConfiguration>
01626 """ % data
01627
01628
01629 open(destfile, "w").write(xml)
01630