X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=QMTest%2FTestCmd.py;h=7c8e1a509c3f937f8959ae1f64e2fd21a0b9615d;hb=704f6e2480ef60718f1aa42c266f04afc9c79580;hp=48ba850b2be6a8e0bfb5c6b83eba4deb30367ad8;hpb=e5b8ed3cb1c630795870f2611ddada8975d2bc6f;p=scons.git diff --git a/QMTest/TestCmd.py b/QMTest/TestCmd.py index 48ba850b..7c8e1a50 100644 --- a/QMTest/TestCmd.py +++ b/QMTest/TestCmd.py @@ -25,6 +25,7 @@ There are a bunch of keyword arguments available at instantiation: subdir = 'subdir', verbose = Boolean, match = default_match_function, + diff = default_diff_function, combine = Boolean) There are a bunch of methods that let you do different things: @@ -103,6 +104,11 @@ There are a bunch of methods that let you do different things: test.symlink(target, link) + test.banner(string) + test.banner(string, width) + + test.diff(actual, expected) + test.match(actual, expected) test.match_exact("actual 1\nactual 2\n", "expected 1\nexpected 2\n") @@ -164,6 +170,24 @@ in the same way as the match_*() methods described above. test = TestCmd.TestCmd(match = TestCmd.match_re_dotall) +The TestCmd module provides unbound functions that can be used for the +"diff" argument to TestCmd.TestCmd instantiation: + + import TestCmd + + test = TestCmd.TestCmd(match = TestCmd.match_re, + diff = TestCmd.diff_re) + + test = TestCmd.TestCmd(diff = TestCmd.simple_diff) + +The "diff" argument can also be used with standard difflib functions: + + import difflib + + test = TestCmd.TestCmd(diff = difflib.context_diff) + + test = TestCmd.TestCmd(diff = difflib.unified_diff) + Lastly, the where_is() method also exists in an unbound function version. @@ -174,7 +198,7 @@ version. TestCmd.where_is('foo', 'PATH1;PATH2', '.suffix3;.suffix4') """ -# Copyright 2000, 2001, 2002, 2003, 2004 Steven Knight +# Copyright 2000-2010 Steven Knight # This module is free software, and you may redistribute it and/or modify # it under the same terms as Python itself, so long as this copyright message # and disclaimer are retained in their original form. @@ -189,10 +213,11 @@ version. # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +from __future__ import generators ### KEEP FOR COMPATIBILITY FIXERS __author__ = "Steven Knight " -__revision__ = "TestCmd.py 0.35.D001 2009/02/08 07:10:39 knight" -__version__ = "0.35" +__revision__ = "TestCmd.py 0.37.D001 2010/01/11 16:55:50 knight" +__version__ = "0.37" import errno import os @@ -200,12 +225,10 @@ import os.path import re import shutil import stat -import string import sys import tempfile import time import traceback -import types import UserList __all__ = [ @@ -220,8 +243,13 @@ __all__ = [ 'TestCmd' ] +try: + import difflib +except ImportError: + __all__.append('simple_diff') + def is_List(e): - return type(e) is types.ListType \ + return isinstance(e, list) \ or isinstance(e, UserList.UserList) try: @@ -230,14 +258,15 @@ except ImportError: class UserString: pass -if hasattr(types, 'UnicodeType'): +try: unicode +except NameError: def is_String(e): - return type(e) is types.StringType \ - or type(e) is types.UnicodeType \ - or isinstance(e, UserString) + return isinstance(e, str) or isinstance(e, UserString) else: def is_String(e): - return type(e) is types.StringType or isinstance(e, UserString) + return isinstance(e, str) \ + or isinstance(e, unicode) \ + or isinstance(e, UserString) tempfile.template = 'testcmd.' if os.name in ('posix', 'nt'): @@ -253,7 +282,7 @@ _chain_to_exitfunc = None def _clean(): global _Cleanup - cleanlist = filter(None, _Cleanup) + cleanlist = [_f for _f in _Cleanup if _f] del _Cleanup[:] cleanlist.reverse() for test in cleanlist: @@ -278,16 +307,16 @@ try: except NameError: def zip(*lists): result = [] - for i in xrange(min(map(len, lists))): - result.append(tuple(map(lambda l, i=i: l[i], lists))) + for i in xrange(min(list(map(len, lists)))): + result.append(tuple([l[i] for l in lists])) return result class Collector: def __init__(self, top): self.entries = [top] def __call__(self, arg, dirname, names): - pathjoin = lambda n, d=dirname: os.path.join(d, n) - self.entries.extend(map(pathjoin, names)) + pathjoin = lambda n: os.path.join(dirname, n) + self.entries.extend(list(map(pathjoin, names))) def _caller(tblist, skip): string = "" @@ -378,9 +407,9 @@ def match_exact(lines = None, matches = None): """ """ if not is_List(lines): - lines = string.split(lines, "\n") + lines = lines.split("\n") if not is_List(matches): - matches = string.split(matches, "\n") + matches = matches.split("\n") if len(lines) != len(matches): return for i in range(len(lines)): @@ -392,9 +421,9 @@ def match_re(lines = None, res = None): """ """ if not is_List(lines): - lines = string.split(lines, "\n") + lines = lines.split("\n") if not is_List(res): - res = string.split(res, "\n") + res = res.split("\n") if len(lines) != len(res): return for i in range(len(lines)): @@ -411,10 +440,10 @@ def match_re(lines = None, res = None): def match_re_dotall(lines = None, res = None): """ """ - if not type(lines) is type(""): - lines = string.join(lines, "\n") - if not type(res) is type(""): - res = string.join(res, "\n") + if not isinstance(lines, str): + lines = "\n".join(lines) + if not isinstance(res, str): + res = "\n".join(res) s = "^" + res + "$" try: expr = re.compile(s, re.DOTALL) @@ -424,6 +453,36 @@ def match_re_dotall(lines = None, res = None): if expr.match(lines): return 1 +try: + import difflib +except ImportError: + pass +else: + def simple_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + sm = difflib.SequenceMatcher(None, a, b) + def comma(x1, x2): + return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2) + result = [] + for op, a1, a2, b1, b2 in sm.get_opcodes(): + if op == 'delete': + result.append("%sd%d" % (comma(a1, a2), b1)) + result.extend(['< ' + l for l in a[a1:a2]]) + elif op == 'insert': + result.append("%da%s" % (a1, comma(b1, b2))) + result.extend(['> ' + l for l in b[b1:b2]]) + elif op == 'replace': + result.append("%sc%s" % (comma(a1, a2), comma(b1, b2))) + result.extend(['< ' + l for l in a[a1:a2]]) + result.append('---') + result.extend(['> ' + l for l in b[b1:b2]]) + return result + def diff_re(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n'): """ @@ -471,13 +530,13 @@ if sys.platform == 'win32': if path is None: path = os.environ['PATH'] if is_String(path): - path = string.split(path, os.pathsep) + path = path.split(os.pathsep) if pathext is None: pathext = os.environ['PATHEXT'] if is_String(pathext): - pathext = string.split(pathext, os.pathsep) + pathext = pathext.split(os.pathsep) for ext in pathext: - if string.lower(ext) == string.lower(file[-len(ext):]): + if ext.lower() == file[-len(ext):].lower(): pathext = [''] break for dir in path: @@ -494,7 +553,7 @@ else: if path is None: path = os.environ['PATH'] if is_String(path): - path = string.split(path, os.pathsep) + path = path.split(os.pathsep) for dir in path: f = os.path.join(dir, file) if os.path.isfile(f): @@ -590,14 +649,14 @@ except ImportError: universal_newlines = 1 def __init__(self, command, **kw): if kw.get('stderr') == 'STDOUT': - apply(popen2.Popen4.__init__, (self, command, 1)) + popen2.Popen4.__init__(self, command, 1) else: - apply(popen2.Popen3.__init__, (self, command, 1)) + popen2.Popen3.__init__(self, command, 1) self.stdin = self.tochild self.stdout = self.fromchild self.stderr = self.childerr def wait(self, *args, **kw): - resultcode = apply(popen2.Popen3.wait, (self,)+args, kw) + resultcode = popen2.Popen3.wait(self, *args, **kw) if os.WIFEXITED(resultcode): return os.WEXITSTATUS(resultcode) elif os.WIFSIGNALED(resultcode): @@ -759,6 +818,7 @@ def recv_some(p, t=.1, e=1, tr=5, stderr=0): time.sleep(max((x-time.time())/tr, 0)) return ''.join(y) +# TODO(3.0: rewrite to use memoryview() def send_all(p, data): while len(data): sent = p.send(data) @@ -768,7 +828,15 @@ def send_all(p, data): -class TestCmd: +try: + object +except NameError: + class object: + pass + + + +class TestCmd(object): """Class TestCmd """ @@ -779,6 +847,7 @@ class TestCmd: subdir = None, verbose = None, match = None, + diff = None, combine = 0, universal_newlines = 1): self._cwd = os.getcwd() @@ -794,12 +863,23 @@ class TestCmd: self.combine = combine self.universal_newlines = universal_newlines if not match is None: - self.match_func = match + self.match_function = match else: - self.match_func = match_re + self.match_function = match_re + if not diff is None: + self.diff_function = diff + else: + try: + difflib + except NameError: + pass + else: + self.diff_function = simple_diff + #self.diff_function = difflib.context_diff + #self.diff_function = difflib.unified_diff self._dirlist = [] self._preserve = {'pass_test': 0, 'fail_test': 0, 'no_result': 0} - if os.environ.has_key('PRESERVE') and not os.environ['PRESERVE'] is '': + if 'PRESERVE' in os.environ and not os.environ['PRESERVE'] is '': self._preserve['pass_test'] = os.environ['PRESERVE'] self._preserve['fail_test'] = os.environ['PRESERVE'] self._preserve['no_result'] = os.environ['PRESERVE'] @@ -829,6 +909,14 @@ class TestCmd: def __repr__(self): return "%x" % id(self) + banner_char = '=' + banner_width = 80 + + def banner(self, s, width=None): + if width is None: + width = self.banner_width + return s + self.banner_char * (width - len(s)) + if os.name == 'posix': def escape(self, arg): @@ -836,9 +924,9 @@ class TestCmd: slash = '\\' special = '"$' - arg = string.replace(arg, slash, slash+slash) + arg = arg.replace(slash, slash+slash) for c in special: - arg = string.replace(arg, c, slash+c) + arg = arg.replace(c, slash+c) if re_space.search(arg): arg = '"' + arg + '"' @@ -856,7 +944,7 @@ class TestCmd: def canonicalize(self, path): if is_List(path): - path = apply(os.path.join, tuple(path)) + path = os.path.join(*tuple(path)) if not os.path.isabs(path): path = os.path.join(self.workdir, path) return path @@ -909,22 +997,22 @@ class TestCmd: interpreter = None, arguments = None): if program: - if type(program) == type('') and not os.path.isabs(program): + if isinstance(program, str) and not os.path.isabs(program): program = os.path.join(self._cwd, program) else: program = self.program if not interpreter: interpreter = self.interpreter - if not type(program) in [type([]), type(())]: + if not type(program) in [list, tuple]: program = [program] cmd = list(program) if interpreter: - if not type(interpreter) in [type([]), type(())]: + if not type(interpreter) in [list, tuple]: interpreter = [interpreter] cmd = list(interpreter) + cmd if arguments: - if type(arguments) == type(''): - arguments = string.split(arguments) + if isinstance(arguments, str): + arguments = arguments.split() cmd.extend(arguments) return cmd @@ -933,9 +1021,21 @@ class TestCmd: """ self.description = description -# def diff(self): -# """Diff two arrays. -# """ + try: + difflib + except NameError: + def diff(self, a, b, name, *args, **kw): + print self.banner('Expected %s' % name) + print a + print self.banner('Actual %s' % name) + print b + else: + def diff(self, a, b, name, *args, **kw): + print self.banner(name) + args = (a.splitlines(), b.splitlines()) + args + lines = self.diff_function(*args, **kw) + for l in lines: + print l def fail_test(self, condition = 1, function = None, skip = 0): """Cause the test to fail. @@ -957,7 +1057,7 @@ class TestCmd: def match(self, lines, matches): """Compare actual and expected file contents. """ - return self.match_func(lines, matches) + return self.match_function(lines, matches) def match_exact(self, lines, matches): """Compare actual and expected file contents. @@ -1049,12 +1149,20 @@ class TestCmd: prepended unless it is enclosed in a [list]. """ cmd = self.command_args(program, interpreter, arguments) - cmd_string = string.join(map(self.escape, cmd), ' ') + cmd_string = ' '.join(map(self.escape, cmd)) if self.verbose: sys.stderr.write(cmd_string + "\n") if universal_newlines is None: universal_newlines = self.universal_newlines + # On Windows, if we make stdin a pipe when we plan to send + # no input, and the test program exits before + # Popen calls msvcrt.open_osfhandle, that call will fail. + # So don't use a pipe for stdin if we don't need one. + stdin = kw.get('stdin', None) + if stdin is not None: + stdin = subprocess.PIPE + combine = kw.get('combine', self.combine) if combine: stderr_value = subprocess.STDOUT @@ -1062,7 +1170,7 @@ class TestCmd: stderr_value = subprocess.PIPE return Popen(cmd, - stdin=subprocess.PIPE, + stdin=stdin, stdout=subprocess.PIPE, stderr=stderr_value, universal_newlines=universal_newlines) @@ -1104,14 +1212,18 @@ class TestCmd: if self.verbose: sys.stderr.write("chdir(" + chdir + ")\n") os.chdir(chdir) - p = self.start(program, interpreter, arguments, universal_newlines) + p = self.start(program, + interpreter, + arguments, + universal_newlines, + stdin=stdin) if stdin: if is_List(stdin): for line in stdin: p.stdin.write(line) else: p.stdin.write(stdin) - p.stdin.close() + p.stdin.close() out = p.stdout.read() if p.stderr is None: @@ -1202,7 +1314,7 @@ class TestCmd: if sub is None: continue if is_List(sub): - sub = apply(os.path.join, tuple(sub)) + sub = os.path.join(*tuple(sub)) new = os.path.join(self.workdir, sub) try: os.mkdir(new) @@ -1250,7 +1362,7 @@ class TestCmd: # letters is pretty much random on win32: drive,rest = os.path.splitdrive(path) if drive: - path = string.upper(drive) + rest + path = drive.upper() + rest # self._dirlist.append(path) @@ -1292,7 +1404,7 @@ class TestCmd: """Find an executable file. """ if is_List(file): - file = apply(os.path.join, tuple(file)) + file = os.path.join(*tuple(file)) if not os.path.isabs(file): file = where_is(file, path, pathext) return file @@ -1314,7 +1426,7 @@ class TestCmd: the temporary working directory name with the specified arguments using the os.path.join() method. """ - return apply(os.path.join, (self.workdir,) + tuple(args)) + return os.path.join(self.workdir, *tuple(args)) def readable(self, top, read=1): """Make the specified directory tree readable (read == 1) @@ -1471,3 +1583,9 @@ class TestCmd: if mode[0] != 'w': raise ValueError, "mode must begin with 'w'" open(file, mode).write(content) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: