test.must_contain('file', 'required text\n')
+ test.must_contain_all_lines(output, lines, ['title', find])
+
+ test.must_contain_any_line(output, lines, ['title', find])
+
test.must_exist('file1', ['file2', ...])
test.must_match('file', "expected contents\n")
test.must_not_be_writable('file1', ['file2', ...])
+ test.must_not_contain('file', 'banned text\n')
+
+ test.must_not_contain_any_line(output, lines, ['title', find])
+
test.must_not_exist('file1', ['file2', ...])
test.run(options = "options to be prepended to arguments",
TestCommon.python_executable
TestCommon.exe_suffix
TestCommon.obj_suffix
+ TestCommon.shobj_prefix
TestCommon.shobj_suffix
TestCommon.lib_prefix
TestCommon.lib_suffix
"""
-# 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.
# 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 <knight at baldmt dot com>"
-__revision__ = "TestCommon.py 0.26.D001 2007/08/20 21:58:58 knight"
-__version__ = "0.26"
+__revision__ = "TestCommon.py 0.37.D001 2010/01/11 16:55:50 knight"
+__version__ = "0.37"
+import copy
import os
import os.path
import stat
-import string
import sys
-import types
import UserList
from TestCmd import *
from TestCmd import __all__
__all__.extend([ 'TestCommon',
- 'TestFailed',
- 'TestNoResult',
'exe_suffix',
'obj_suffix',
+ 'shobj_prefix',
'shobj_suffix',
'lib_prefix',
'lib_suffix',
exe_suffix = '.exe'
obj_suffix = '.obj'
shobj_suffix = '.obj'
+ shobj_prefix = ''
lib_prefix = ''
lib_suffix = '.lib'
dll_prefix = ''
exe_suffix = '.exe'
obj_suffix = '.o'
shobj_suffix = '.os'
+ shobj_prefix = ''
lib_prefix = 'lib'
lib_suffix = '.a'
dll_prefix = ''
dll_suffix = '.dll'
-elif string.find(sys.platform, 'irix') != -1:
+elif sys.platform.find('irix') != -1:
exe_suffix = ''
obj_suffix = '.o'
shobj_suffix = '.o'
+ shobj_prefix = ''
lib_prefix = 'lib'
lib_suffix = '.a'
dll_prefix = 'lib'
dll_suffix = '.so'
-elif string.find(sys.platform, 'darwin') != -1:
+elif sys.platform.find('darwin') != -1:
+ exe_suffix = ''
+ obj_suffix = '.o'
+ shobj_suffix = '.os'
+ shobj_prefix = ''
+ lib_prefix = 'lib'
+ lib_suffix = '.a'
+ dll_prefix = 'lib'
+ dll_suffix = '.dylib'
+elif sys.platform.find('sunos') != -1:
exe_suffix = ''
obj_suffix = '.o'
shobj_suffix = '.os'
+ shobj_prefix = 'so_'
lib_prefix = 'lib'
lib_suffix = '.a'
dll_prefix = 'lib'
exe_suffix = ''
obj_suffix = '.o'
shobj_suffix = '.os'
+ shobj_prefix = ''
lib_prefix = 'lib'
lib_suffix = '.a'
dll_prefix = 'lib'
dll_suffix = '.so'
-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(map(lambda l: '< ' + l, a[a1:a2]))
- elif op == 'insert':
- result.append("%da%s" % (a1, comma(b1, b2)))
- result.extend(map(lambda l: '> ' + l, b[b1:b2]))
- elif op == 'replace':
- result.append("%sc%s" % (comma(a1, a2), comma(b1, b2)))
- result.extend(map(lambda l: '< ' + l, a[a1:a2]))
- result.append('---')
- result.extend(map(lambda l: '> ' + l, b[b1:b2]))
- return result
-
def is_List(e):
- return type(e) is types.ListType \
+ return isinstance(e, list) \
or isinstance(e, UserList.UserList)
def is_writable(f):
missing.append(f)
return existing, missing
-class TestFailed(Exception):
- def __init__(self, args=None):
- self.args = args
-
-class TestNoResult(Exception):
- def __init__(self, args=None):
- self.args = args
-
if os.name == 'posix':
def _failed(self, status = 0):
if self.status is None or status is None:
return None
- if os.WIFSIGNALED(self.status):
- return None
return _status(self) != status
def _status(self):
- if os.WIFEXITED(self.status):
- return os.WEXITSTATUS(self.status)
- else:
- return None
+ return self.status
elif os.name == 'nt':
def _failed(self, status = 0):
return not (self.status is None or status is None) and \
calling the base class initialization, and then changing directory
to the workdir.
"""
- apply(TestCmd.__init__, [self], kw)
+ TestCmd.__init__(self, **kw)
os.chdir(self.workdir)
- try:
- difflib
- except NameError:
- pass
- else:
- self.diff_function = simple_diff
- #self.diff_function = difflib.context_diff
- #self.diff_function = difflib.unified_diff
-
- 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))
-
- 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 = apply(self.diff_function, args, kw)
- for l in lines:
- print l
def must_be_writable(self, *files):
"""Ensures that the specified file(s) exist and are writable.
them. Exits FAILED if any of the files does not exist or is
not writable.
"""
- files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
+ files = [is_List(x) and os.path.join(*x) or x for x in files]
existing, missing = separate_files(files)
- unwritable = filter(lambda x, iw=is_writable: not iw(x), existing)
+ unwritable = [x for x in existing if not is_writable(x)]
if missing:
- print "Missing files: `%s'" % string.join(missing, "', `")
+ print "Missing files: `%s'" % "', `".join(missing)
if unwritable:
- print "Unwritable files: `%s'" % string.join(unwritable, "', `")
+ print "Unwritable files: `%s'" % "', `".join(unwritable)
self.fail_test(missing + unwritable)
def must_contain(self, file, required, mode = 'rb'):
"""Ensures that the specified file contains the required text.
"""
file_contents = self.read(file, mode)
- contains = (string.find(file_contents, required) != -1)
+ contains = (file_contents.find(required) != -1)
if not contains:
print "File `%s' does not contain required string." % file
print self.banner('Required string ')
print file_contents
self.fail_test(not contains)
+ def must_contain_all_lines(self, output, lines, title=None, find=None):
+ """Ensures that the specified output string (first argument)
+ contains all of the specified lines (second argument).
+
+ An optional third argument can be used to describe the type
+ of output being searched, and only shows up in failure output.
+
+ An optional fourth argument can be used to supply a different
+ function, of the form "find(line, output), to use when searching
+ for lines in the output.
+ """
+ if find is None:
+ find = lambda o, l: o.find(l) != -1
+ missing = []
+ for line in lines:
+ if not find(output, line):
+ missing.append(line)
+
+ if missing:
+ if title is None:
+ title = 'output'
+ sys.stdout.write("Missing expected lines from %s:\n" % title)
+ for line in missing:
+ sys.stdout.write(' ' + repr(line) + '\n')
+ sys.stdout.write(self.banner(title + ' '))
+ sys.stdout.write(output)
+ self.fail_test()
+
+ def must_contain_any_line(self, output, lines, title=None, find=None):
+ """Ensures that the specified output string (first argument)
+ contains at least one of the specified lines (second argument).
+
+ An optional third argument can be used to describe the type
+ of output being searched, and only shows up in failure output.
+
+ An optional fourth argument can be used to supply a different
+ function, of the form "find(line, output), to use when searching
+ for lines in the output.
+ """
+ if find is None:
+ find = lambda o, l: o.find(l) != -1
+ for line in lines:
+ if find(output, line):
+ return
+
+ if title is None:
+ title = 'output'
+ sys.stdout.write("Missing any expected line from %s:\n" % title)
+ for line in lines:
+ sys.stdout.write(' ' + repr(line) + '\n')
+ sys.stdout.write(self.banner(title + ' '))
+ sys.stdout.write(output)
+ self.fail_test()
+
+ def must_contain_lines(self, lines, output, title=None):
+ # Deprecated; retain for backwards compatibility.
+ return self.must_contain_all_lines(output, lines, title)
+
def must_exist(self, *files):
"""Ensures that the specified file(s) must exist. An individual
file be specified as a list of directory names, in which case the
pathname will be constructed by concatenating them. Exits FAILED
if any of the files does not exist.
"""
- files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
- missing = filter(lambda x: not os.path.exists(x), files)
+ files = [is_List(x) and os.path.join(*x) or x for x in files]
+ missing = [x for x in files if not os.path.exists(x)]
if missing:
- print "Missing files: `%s'" % string.join(missing, "', `")
+ print "Missing files: `%s'" % "', `".join(missing)
self.fail_test(missing)
def must_match(self, file, expect, mode = 'rb'):
self.diff(expect, file_contents, 'contents ')
raise
+ def must_not_contain(self, file, banned, mode = 'rb'):
+ """Ensures that the specified file doesn't contain the banned text.
+ """
+ file_contents = self.read(file, mode)
+ contains = (file_contents.find(banned) != -1)
+ if contains:
+ print "File `%s' contains banned string." % file
+ print self.banner('Banned string ')
+ print banned
+ print self.banner('%s contents ' % file)
+ print file_contents
+ self.fail_test(contains)
+
+ def must_not_contain_any_line(self, output, lines, title=None, find=None):
+ """Ensures that the specified output string (first argument)
+ does not contain any of the specified lines (second argument).
+
+ An optional third argument can be used to describe the type
+ of output being searched, and only shows up in failure output.
+
+ An optional fourth argument can be used to supply a different
+ function, of the form "find(line, output), to use when searching
+ for lines in the output.
+ """
+ if find is None:
+ find = lambda o, l: o.find(l) != -1
+ unexpected = []
+ for line in lines:
+ if find(output, line):
+ unexpected.append(line)
+
+ if unexpected:
+ if title is None:
+ title = 'output'
+ sys.stdout.write("Unexpected lines in %s:\n" % title)
+ for line in unexpected:
+ sys.stdout.write(' ' + repr(line) + '\n')
+ sys.stdout.write(self.banner(title + ' '))
+ sys.stdout.write(output)
+ self.fail_test()
+
+ def must_not_contain_lines(self, lines, output, title=None):
+ return self.must_not_contain_any_line(output, lines, title)
+
def must_not_exist(self, *files):
"""Ensures that the specified file(s) must not exist.
An individual file be specified as a list of directory names, in
which case the pathname will be constructed by concatenating them.
Exits FAILED if any of the files exists.
"""
- files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
- existing = filter(os.path.exists, files)
+ files = [is_List(x) and os.path.join(*x) or x for x in files]
+ existing = list(filter(os.path.exists, files))
if existing:
- print "Unexpected files exist: `%s'" % string.join(existing, "', `")
+ print "Unexpected files exist: `%s'" % "', `".join(existing)
self.fail_test(existing)
them. Exits FAILED if any of the files does not exist or is
writable.
"""
- files = map(lambda x: is_List(x) and apply(os.path.join, x) or x, files)
+ files = [is_List(x) and os.path.join(*x) or x for x in files]
existing, missing = separate_files(files)
- writable = filter(is_writable, existing)
+ writable = list(filter(is_writable, existing))
if missing:
- print "Missing files: `%s'" % string.join(missing, "', `")
+ print "Missing files: `%s'" % "', `".join(missing)
if writable:
- print "Writable files: `%s'" % string.join(writable, "', `")
+ print "Writable files: `%s'" % "', `".join(writable)
self.fail_test(missing + writable)
+ def _complete(self, actual_stdout, expected_stdout,
+ actual_stderr, expected_stderr, status, match):
+ """
+ Post-processes running a subcommand, checking for failure
+ status and displaying output appropriately.
+ """
+ if _failed(self, status):
+ expect = ''
+ if status != 0:
+ expect = " (expected %s)" % str(status)
+ print "%s returned %s%s" % (self.program, str(_status(self)), expect)
+ print self.banner('STDOUT ')
+ print actual_stdout
+ print self.banner('STDERR ')
+ print actual_stderr
+ self.fail_test()
+ if not expected_stdout is None and not match(actual_stdout, expected_stdout):
+ self.diff(expected_stdout, actual_stdout, 'STDOUT ')
+ if actual_stderr:
+ print self.banner('STDERR ')
+ print actual_stderr
+ self.fail_test()
+ if not expected_stderr is None and not match(actual_stderr, expected_stderr):
+ print self.banner('STDOUT ')
+ print actual_stdout
+ self.diff(expected_stderr, actual_stderr, 'STDERR ')
+ self.fail_test()
+
+ def start(self, program = None,
+ interpreter = None,
+ arguments = None,
+ universal_newlines = None,
+ **kw):
+ """
+ Starts a program or script for the test environment.
+
+ This handles the "options" keyword argument and exceptions.
+ """
+ try:
+ options = kw['options']
+ del kw['options']
+ except KeyError:
+ pass
+ else:
+ if options:
+ if arguments is None:
+ arguments = options
+ else:
+ arguments = options + " " + arguments
+ try:
+ return TestCmd.start(self, program, interpreter, arguments, universal_newlines,
+ **kw)
+ except KeyboardInterrupt:
+ raise
+ except Exception, e:
+ print self.banner('STDOUT ')
+ try:
+ print self.stdout()
+ except IndexError:
+ pass
+ print self.banner('STDERR ')
+ try:
+ print self.stderr()
+ except IndexError:
+ pass
+ cmd_args = self.command_args(program, interpreter, arguments)
+ sys.stderr.write('Exception trying to execute: %s\n' % cmd_args)
+ raise e
+
+ def finish(self, popen, stdout = None, stderr = '', status = 0, **kw):
+ """
+ Finishes and waits for the process being run under control of
+ the specified popen argument. Additional arguments are similar
+ to those of the run() method:
+
+ stdout The expected standard output from
+ the command. A value of None means
+ don't test standard output.
+
+ stderr The expected error output from
+ the command. A value of None means
+ don't test error output.
+
+ status The expected exit status from the
+ command. A value of None means don't
+ test exit status.
+ """
+ TestCmd.finish(self, popen, **kw)
+ match = kw.get('match', self.match)
+ self._complete(self.stdout(), stdout,
+ self.stderr(), stderr, status, match)
+
def run(self, options = None, arguments = None,
stdout = None, stderr = '', status = 0, **kw):
"""Runs the program under test, checking that the test succeeded.
del kw['match']
except KeyError:
match = self.match
- try:
- apply(TestCmd.run, [self], kw)
- except KeyboardInterrupt:
- raise
- except:
- print self.banner('STDOUT ')
- print self.stdout()
- print self.banner('STDERR ')
- print self.stderr()
- raise
- if _failed(self, status):
- expect = ''
- if status != 0:
- expect = " (expected %s)" % str(status)
- print "%s returned %s%s" % (self.program, str(_status(self)), expect)
- print self.banner('STDOUT ')
- print self.stdout()
- print self.banner('STDERR ')
- print self.stderr()
- raise TestFailed
- if not stdout is None and not match(self.stdout(), stdout):
- self.diff(stdout, self.stdout(), 'STDOUT ')
- stderr = self.stderr()
- if stderr:
- print self.banner('STDERR ')
- print stderr
- raise TestFailed
- if not stderr is None and not match(self.stderr(), stderr):
- print self.banner('STDOUT ')
- print self.stdout()
- self.diff(stderr, self.stderr(), 'STDERR ')
- raise TestFailed
+ TestCmd.run(self, **kw)
+ self._complete(self.stdout(), stdout,
+ self.stderr(), stderr, status, match)
def skip_test(self, message="Skipping test.\n"):
"""Skips a test.
# We're under the development directory for this change,
# so this is an Aegis invocation; pass the test (exit 0).
self.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: