summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/TestCmd.py8
-rw-r--r--etc/TestCommon.py286
-rw-r--r--etc/TestSCons.py149
-rw-r--r--test/ASFLAGS.py24
-rw-r--r--test/CPPFLAGS.py34
-rw-r--r--test/CXX.py20
6 files changed, 349 insertions, 172 deletions
diff --git a/etc/TestCmd.py b/etc/TestCmd.py
index 61fa2a7..ca89ed9 100644
--- a/etc/TestCmd.py
+++ b/etc/TestCmd.py
@@ -173,8 +173,8 @@ version.
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
__author__ = "Steven Knight <knight at baldmt dot com>"
-__revision__ = "TestCmd.py 0.04.D010 2004/01/27 00:11:44 knight"
-__version__ = "0.04"
+__revision__ = "TestCmd.py 0.6.D001 2004/03/20 17:39:42 knight"
+__version__ = "0.6"
import os
import os.path
@@ -190,6 +190,10 @@ import traceback
import types
import UserList
+__all__ = [ 'fail_test', 'no_result', 'pass_test',
+ 'match_exact', 'match_re', 'match_re_dotall',
+ 'python_executable', 'TestCmd' ]
+
def is_List(e):
return type(e) is types.ListType \
or isinstance(e, UserList.UserList)
diff --git a/etc/TestCommon.py b/etc/TestCommon.py
new file mode 100644
index 0000000..0354e5b
--- /dev/null
+++ b/etc/TestCommon.py
@@ -0,0 +1,286 @@
+"""
+TestCommon.py: a testing framework for commands and scripts
+ with commonly useful error handling
+
+The TestCommon module provides a simple, high-level interface for writing
+tests of executable commands and scripts, especially commands and scripts
+that interact with the file system. All methods throw exceptions and
+exit on failure, with useful error messages. This makes a number of
+explicit checks unnecessary, making the test scripts themselves simpler
+to write and easier to read.
+
+The TestCommon class is a subclass of the TestCmd class. In essence,
+TestCommon is a wrapper that handles common TestCmd error conditions in
+useful ways. You can use TestCommon directly, or subclass it for your
+program and add additional (or override) methods to tailor it to your
+program's specific needs. Alternatively, the TestCommon class serves
+as a useful example of how to define your own TestCmd subclass.
+
+As a subclass of TestCmd, TestCommon provides access to all of the
+variables and methods from the TestCmd module. Consequently, you can
+use any variable or method documented in the TestCmd module without
+having to explicitly import TestCmd.
+
+A TestCommon environment object is created via the usual invocation:
+
+ import TestCommon
+ test = TestCommon.TestCommon()
+
+You can use all of the TestCmd keyword arguments when instantiating a
+TestCommon object; see the TestCmd documentation for details.
+
+Here is an overview of the methods and keyword arguments that are
+provided by the TestCommon class:
+
+ test.must_exist('file1', ['file2', ...])
+
+ test.must_match('file', "expected contents\n")
+
+ test.must_not_exist('file1', ['file2', ...])
+
+ test.run(options = "options to be prepended to arguments",
+ stdout = "expected standard output from the program",
+ stderr = "expected error output from the program",
+ status = expected_status)
+
+The TestCommon module also provides the following variables
+
+ TestCommon.python_executable
+ TestCommon._exe
+ TestCommon._obj
+ TestCommon._shobj
+ TestCommon.lib_
+ TestCommon._lib
+ TestCommon.dll_
+ TestCommon._dll
+
+"""
+
+# Copyright 2000, 2001, 2002, 2003, 2004 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.
+#
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
+# THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+#
+# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# 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.
+
+__author__ = "Steven Knight <knight at baldmt dot com>"
+__revision__ = "TestCommon.py 0.6.D001 2004/03/20 17:39:42 knight"
+__version__ = "0.6"
+
+import os
+import os.path
+import string
+import sys
+import types
+import UserList
+
+from TestCmd import *
+from TestCmd import __all__
+
+__all__.extend([ 'TestCommon',
+ '_exe', '_obj', '_shobj', 'lib_', '_lib', 'dll_', '_dll', ])
+
+# Variables that describe the prefixes and suffixes on this system.
+if sys.platform == 'win32':
+ _exe = '.exe'
+ _obj = '.obj'
+ _shobj = '.obj'
+ lib_ = ''
+ _lib = '.lib'
+ dll_ = ''
+ _dll = '.dll'
+elif sys.platform == 'cygwin':
+ _exe = '.exe'
+ _obj = '.o'
+ _shobj = '.os'
+ lib_ = 'lib'
+ _lib = '.a'
+ dll_ = ''
+ _dll = '.dll'
+elif string.find(sys.platform, 'irix') != -1:
+ _exe = ''
+ _obj = '.o'
+ _shobj = '.o'
+ lib_ = 'lib'
+ _lib = '.a'
+ dll_ = 'lib'
+ _dll = '.so'
+else:
+ _exe = ''
+ _obj = '.o'
+ _shobj = '.os'
+ lib_ = 'lib'
+ _lib = '.a'
+ dll_ = 'lib'
+ _dll = '.so'
+
+def is_List(e):
+ return type(e) is types.ListType \
+ or isinstance(e, UserList.UserList)
+
+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
+elif os.name == 'nt':
+ def _failed(self, status = 0):
+ return not (self.status is None or status is None) and \
+ self.status != status
+ def _status(self):
+ return self.status
+
+class TestCommon(TestCmd):
+
+ # Additional methods from the Perl Test::Cmd::Common module
+ # that we may wish to add in the future:
+ #
+ # $test->subdir('subdir', ...);
+ #
+ # $test->copy('src_file', 'dst_file');
+ #
+ # $test->chmod($mode, 'file', ...);
+ #
+ # $test->touch('file', ...);
+
+ def __init__(self, **kw):
+ """Initialize a new TestCommon instance. This involves just
+ calling the base class initialization, and then changing directory
+ to the workdir.
+ """
+ apply(TestCmd.__init__, [self], kw)
+ os.chdir(self.workdir)
+
+ 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 os.path.join(x) or x, files)
+ missing = filter(lambda x: not os.path.exists(x), files)
+ if missing:
+ print "Missing files: `%s'" % string.join(missing, "', `")
+ self.fail_test(missing)
+
+ def must_match(self, file, expect):
+ """Matches the contents of the specified file (first argument)
+ against the expected contents (second argument). The expected
+ contents are a list of lines or a string which will be split
+ on newlines.
+ """
+ file_contents = self.read(file)
+ try:
+ self.fail_test(not self.match(file_contents, expect))
+ except:
+ print "Unexpected contents of `%s'" % file
+ print "EXPECTED contents ======"
+ print expect
+ print "ACTUAL contents ========"
+ print file_contents
+ raise
+
+ 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 os.path.join(x) or x, files)
+ existing = filter(os.path.exists, files)
+ if existing:
+ print "Unexpected files exist: `%s'" % string.join(existing, "', `")
+ self.fail_test(existing)
+
+ def run(self, options = None, arguments = None,
+ stdout = None, stderr = '', status = 0, **kw):
+ """Runs the program under test, checking that the test succeeded.
+
+ The arguments are the same as the base TestCmd.run() method,
+ with the addition of:
+
+ options Extra options that get appended to the beginning
+ of the arguments.
+
+ 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.
+
+ By default, this expects a successful exit (status = 0), does
+ not test standard output (stdout = None), and expects that error
+ output is empty (stderr = "").
+ """
+ if options:
+ if arguments is None:
+ arguments = options
+ else:
+ arguments = options + " " + arguments
+ kw['arguments'] = arguments
+ try:
+ apply(TestCmd.run, [self], kw)
+ except:
+ print "STDOUT ============"
+ print self.stdout()
+ print "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 "STDOUT ============"
+ print self.stdout()
+ print "STDERR ============"
+ print self.stderr()
+ raise TestFailed
+ if not stdout is None and not self.match(self.stdout(), stdout):
+ print "Expected STDOUT =========="
+ print stdout
+ print "Actual STDOUT ============"
+ print self.stdout()
+ stderr = self.stderr()
+ if stderr:
+ print "STDERR ==================="
+ print stderr
+ raise TestFailed
+ if not stderr is None and not self.match(self.stderr(), stderr):
+ print "STDOUT ==================="
+ print self.stdout()
+ print "Expected STDERR =========="
+ print stderr
+ print "Actual STDERR ============"
+ print self.stderr()
+ raise TestFailed
diff --git a/etc/TestSCons.py b/etc/TestSCons.py
index 028a815..33049c5 100644
--- a/etc/TestSCons.py
+++ b/etc/TestSCons.py
@@ -6,9 +6,10 @@ A TestSCons environment object is created via the usual invocation:
test = TestSCons()
-TestScons is a subclass of TestCmd, and hence has available all of its
-methods and attributes, as well as any overridden or additional methods
-or attributes defined in this subclass.
+TestScons is a subclass of TestCommon, which is in turn is a subclass
+of TestCmd), and hence has available all of the methods and attributes
+from those classes, as well as any overridden or additional methods or
+attributes defined in this subclass.
"""
# Copyright 2001, 2002, 2003 Steven Knight
@@ -20,9 +21,9 @@ import os.path
import string
import sys
-import TestCmd
+from TestCommon import *
-python = TestCmd.python_executable
+python = python_executable
def gccFortranLibs():
@@ -46,72 +47,27 @@ def gccFortranLibs():
return libs
+if sys.platform == 'cygwin':
+ # On Cygwin, os.path.normcase() lies, so just report back the
+ # fact that the underlying Win32 OS is case-insensitive.
+ def case_sensitive_suffixes(s1, s2):
+ return 0
+else:
+ def case_sensitive_suffixes(s1, s2):
+ return (os.path.normcase(s1) != os.path.normcase(s2))
+
+
if sys.platform == 'win32':
- _exe = '.exe'
- _obj = '.obj'
- _shobj = '.obj'
- lib_ = ''
- _lib = '.lib'
- dll_ = ''
- _dll = '.dll'
fortran_lib = gccFortranLibs()
elif sys.platform == 'cygwin':
- _exe = '.exe'
- _obj = '.o'
- _shobj = '.os'
- lib_ = 'lib'
- _lib = '.a'
- dll_ = ''
- _dll = '.dll'
fortran_lib = gccFortranLibs()
elif string.find(sys.platform, 'irix') != -1:
- _exe = ''
- _obj = '.o'
- _shobj = '.o'
- lib_ = 'lib'
- _lib = '.a'
- dll_ = 'lib'
- _dll = '.so'
fortran_lib = ['ftn']
else:
- _exe = ''
- _obj = '.o'
- _shobj = '.os'
- lib_ = 'lib'
- _lib = '.a'
- dll_ = 'lib'
- _dll = '.so'
fortran_lib = gccFortranLibs()
-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
-elif os.name == 'nt':
- def _failed(self, status = 0):
- return not (self.status is None or status is None) and \
- self.status != status
- def _status(self):
- return self.status
-
-class TestSCons(TestCmd.TestCmd):
+class TestSCons(TestCommon):
"""Class for testing SCons.
This provides a common place for initializing SCons tests,
@@ -128,7 +84,7 @@ class TestSCons(TestCmd.TestCmd):
program = 'scons' if it exists,
else 'scons.py'
interpreter = 'python'
- match = TestCmd.match_exact
+ match = match_exact
workdir = ''
The workdir value means that, by default, a temporary workspace
@@ -147,73 +103,10 @@ class TestSCons(TestCmd.TestCmd):
if not kw.has_key('interpreter') and not os.environ.get('SCONS_EXEC'):
kw['interpreter'] = python
if not kw.has_key('match'):
- kw['match'] = TestCmd.match_exact
+ kw['match'] = match_exact
if not kw.has_key('workdir'):
kw['workdir'] = ''
- apply(TestCmd.TestCmd.__init__, [self], kw)
- os.chdir(self.workdir)
-
- def run(self, options = None, arguments = None,
- stdout = None, stderr = '', status = 0, **kw):
- """Runs SCons.
-
- This is the same as the base TestCmd.run() method, with
- the addition of:
-
- 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.
-
- By default, this does not test standard output (stdout = None),
- and expects that error output is empty (stderr = "").
- """
- if options:
- arguments = options + " " + arguments
- kw['arguments'] = arguments
- try:
- apply(TestCmd.TestCmd.run, [self], kw)
- except:
- print "STDOUT ============"
- print self.stdout()
- print "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 "STDOUT ============"
- print self.stdout()
- print "STDERR ============"
- print self.stderr()
- raise TestFailed
- if not stdout is None and not self.match(self.stdout(), stdout):
- print "Expected STDOUT =========="
- print stdout
- print "Actual STDOUT ============"
- print self.stdout()
- stderr = self.stderr()
- if stderr:
- print "STDERR ==================="
- print stderr
- raise TestFailed
- if not stderr is None and not self.match(self.stderr(), stderr):
- print "STDOUT ==================="
- print self.stdout()
- print "Expected STDERR =========="
- print stderr
- print "Actual STDERR ============"
- print self.stderr()
- raise TestFailed
+ apply(TestCommon.__init__, [self], kw)
def detect(self, var, prog=None):
"""
@@ -294,6 +187,6 @@ class TestSCons(TestCmd.TestCmd):
kw['stdout'] = string.replace(kw['stdout'],'\n','\\n')
kw['stdout'] = string.replace(kw['stdout'],'.','\\.')
old_match_func = self.match_func
- self.match_func = TestCmd.match_re_dotall
+ self.match_func = match_re_dotall
apply(self.run, [], kw)
self.match_func = old_match_func
diff --git a/test/ASFLAGS.py b/test/ASFLAGS.py
index d750a6a..6396c45 100644
--- a/test/ASFLAGS.py
+++ b/test/ASFLAGS.py
@@ -170,18 +170,16 @@ test.write('test6.SPP', r"""This is a .SPP file.
test.run(arguments = '.', stderr = None)
-test.fail_test(test.read('test1' + _exe) != "%s\nThis is a .s file.\n" % o)
-
-test.fail_test(test.read('test2' + _exe) != "%s\nThis is a .S file.\n" % o_c)
-
-test.fail_test(test.read('test3' + _exe) != "%s\nThis is a .asm file.\n" % o)
-
-test.fail_test(test.read('test4' + _exe) != "%s\nThis is a .ASM file.\n" % o)
-
-test.fail_test(test.read('test5' + _exe) != "%s\nThis is a .spp file.\n" % o_c)
-
-test.fail_test(test.read('test6' + _exe) != "%s\nThis is a .SPP file.\n" % o_c)
-
-
+if TestSCons.case_sensitive_suffixes('.s', '.S'):
+ o_css = o_c
+else:
+ o_css = o
+
+test.must_match('test1' + _exe, "%s\nThis is a .s file.\n" % o)
+test.must_match('test2' + _exe, "%s\nThis is a .S file.\n" % o_css)
+test.must_match('test3' + _exe, "%s\nThis is a .asm file.\n" % o)
+test.must_match('test4' + _exe, "%s\nThis is a .ASM file.\n" % o)
+test.must_match('test5' + _exe, "%s\nThis is a .spp file.\n" % o_c)
+test.must_match('test6' + _exe, "%s\nThis is a .SPP file.\n" % o_c)
test.pass_test()
diff --git a/test/CPPFLAGS.py b/test/CPPFLAGS.py
index 87f5603..357a241 100644
--- a/test/CPPFLAGS.py
+++ b/test/CPPFLAGS.py
@@ -128,15 +128,14 @@ test.write('test3.F', r"""test3.F
test.run(arguments = '.', stderr=None)
-test.fail_test(test.read('test1' + _obj) != "test1.c\n#link\n")
-
-test.fail_test(test.read('test2' + _obj) != "test2.cpp\n#link\n")
-
-test.fail_test(test.read('test3' + _obj) != "test3.F\n#link\n")
-
-test.fail_test(test.read('foo' + _exe) != "test1.c\ntest2.cpp\ntest3.F\n")
-
-test.fail_test(test.read('mygcc.out') != "cc\nc++\ng77\n")
+test.must_match('test1' + _obj, "test1.c\n#link\n")
+test.must_match('test2' + _obj, "test2.cpp\n#link\n")
+test.must_match('test3' + _obj, "test3.F\n#link\n")
+test.must_match('foo' + _exe, "test1.c\ntest2.cpp\ntest3.F\n")
+if TestSCons.case_sensitive_suffixes('.F', '.f'):
+ test.must_match('mygcc.out', "cc\nc++\ng77\n")
+else:
+ test.must_match('mygcc.out', "cc\nc++\n")
test.write('SConstruct', """
env = Environment(CPPFLAGS = '-x',
@@ -172,14 +171,13 @@ test.unlink('test3' + _obj)
test.run(arguments = '.', stderr = None)
-test.fail_test(test.read('test1' + _shobj) != "test1.c\n#link\n")
-
-test.fail_test(test.read('test2' + _shobj) != "test2.cpp\n#link\n")
-
-test.fail_test(test.read('test3' + _shobj) != "test3.F\n#link\n")
-
-test.fail_test(test.read('foo.bar') != "test1.c\ntest2.cpp\ntest3.F\n")
-
-test.fail_test(test.read('mygcc.out') != "cc\nc++\ng77\n")
+test.must_match('test1' + _shobj, "test1.c\n#link\n")
+test.must_match('test2' + _shobj, "test2.cpp\n#link\n")
+test.must_match('test3' + _shobj, "test3.F\n#link\n")
+test.must_match('foo.bar', "test1.c\ntest2.cpp\ntest3.F\n")
+if TestSCons.case_sensitive_suffixes('.F', '.f'):
+ test.must_match('mygcc.out', "cc\nc++\ng77\n")
+else:
+ test.must_match('mygcc.out', "cc\nc++\n")
test.pass_test()
diff --git a/test/CXX.py b/test/CXX.py
index ad2eb10..6f9f0ce 100644
--- a/test/CXX.py
+++ b/test/CXX.py
@@ -145,19 +145,17 @@ test.write('test5.C++', r"""This is a .C++ file.
test.run(arguments = '.', stderr = None)
-test.fail_test(test.read('test1' + _exe) != "This is a .cc file.\n")
+test.must_match('test1' + _exe, "This is a .cc file.\n")
-test.fail_test(test.read('test2' + _exe) != "This is a .cpp file.\n")
+test.must_match('test2' + _exe, "This is a .cpp file.\n")
-test.fail_test(test.read('test3' + _exe) != "This is a .cxx file.\n")
+test.must_match('test3' + _exe, "This is a .cxx file.\n")
-test.fail_test(test.read('test4' + _exe) != "This is a .c++ file.\n")
+test.must_match('test4' + _exe, "This is a .c++ file.\n")
-test.fail_test(test.read('test5' + _exe) != "This is a .C++ file.\n")
+test.must_match('test5' + _exe, "This is a .C++ file.\n")
-# Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem.
-_is_cygwin = sys.platform == "cygwin"
-if os.path.normcase('.c') != os.path.normcase('.C') and not _is_cygwin:
+if TestSCons.case_sensitive_suffixes('.c', '.C'):
test.write('SConstruct', """
env = Environment(LINK = r'%s mylink.py',
@@ -174,7 +172,7 @@ env.Program(target = 'test6', source = 'test6.C')
test.run(arguments = '.', stderr = None)
- test.fail_test(test.read('test6' + _exe) != "This is a .C file.\n")
+ test.must_match('test6' + _exe, "This is a .C file.\n")
@@ -222,10 +220,10 @@ main(int argc, char *argv[])
test.run(arguments = 'foo' + _exe)
-test.fail_test(os.path.exists(test.workpath('wrapper.out')))
+test.must_not_exist(test.workpath('wrapper.out'))
test.run(arguments = 'bar' + _exe)
-test.fail_test(test.read('wrapper.out') != "wrapper.py\n")
+test.must_match('wrapper.out', "wrapper.py\n")
test.pass_test()