2 TestCommon.py: a testing framework for commands and scripts
3 with commonly useful error handling
5 The TestCommon module provides a simple, high-level interface for writing
6 tests of executable commands and scripts, especially commands and scripts
7 that interact with the file system. All methods throw exceptions and
8 exit on failure, with useful error messages. This makes a number of
9 explicit checks unnecessary, making the test scripts themselves simpler
10 to write and easier to read.
12 The TestCommon class is a subclass of the TestCmd class. In essence,
13 TestCommon is a wrapper that handles common TestCmd error conditions in
14 useful ways. You can use TestCommon directly, or subclass it for your
15 program and add additional (or override) methods to tailor it to your
16 program's specific needs. Alternatively, the TestCommon class serves
17 as a useful example of how to define your own TestCmd subclass.
19 As a subclass of TestCmd, TestCommon provides access to all of the
20 variables and methods from the TestCmd module. Consequently, you can
21 use any variable or method documented in the TestCmd module without
22 having to explicitly import TestCmd.
24 A TestCommon environment object is created via the usual invocation:
27 test = TestCommon.TestCommon()
29 You can use all of the TestCmd keyword arguments when instantiating a
30 TestCommon object; see the TestCmd documentation for details.
32 Here is an overview of the methods and keyword arguments that are
33 provided by the TestCommon class:
35 test.must_exist('file1', ['file2', ...])
37 test.must_match('file', "expected contents\n")
39 test.must_not_exist('file1', ['file2', ...])
41 test.run(options = "options to be prepended to arguments",
42 stdout = "expected standard output from the program",
43 stderr = "expected error output from the program",
44 status = expected_status)
46 The TestCommon module also provides the following variables
48 TestCommon.python_executable
51 TestCommon.shobj_suffix
59 # Copyright 2000, 2001, 2002, 2003, 2004 Steven Knight
60 # This module is free software, and you may redistribute it and/or modify
61 # it under the same terms as Python itself, so long as this copyright message
62 # and disclaimer are retained in their original form.
64 # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
65 # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
66 # THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
69 # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
70 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
71 # PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
72 # AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
73 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
75 __author__ = "Steven Knight <knight at baldmt dot com>"
76 __revision__ = "TestCommon.py 0.6.D002 2004/03/29 06:21:41 knight"
87 from TestCmd import __all__
89 __all__.extend([ 'TestCommon',
99 # Variables that describe the prefixes and suffixes on this system.
100 if sys.platform == 'win32':
103 shobj_suffix = '.obj'
108 elif sys.platform == 'cygwin':
116 elif string.find(sys.platform, 'irix') != -1:
134 return type(e) is types.ListType \
135 or isinstance(e, UserList.UserList)
137 class TestFailed(Exception):
138 def __init__(self, args=None):
141 class TestNoResult(Exception):
142 def __init__(self, args=None):
145 if os.name == 'posix':
146 def _failed(self, status = 0):
147 if self.status is None or status is None:
149 if os.WIFSIGNALED(self.status):
151 return _status(self) != status
153 if os.WIFEXITED(self.status):
154 return os.WEXITSTATUS(self.status)
157 elif os.name == 'nt':
158 def _failed(self, status = 0):
159 return not (self.status is None or status is None) and \
160 self.status != status
164 class TestCommon(TestCmd):
166 # Additional methods from the Perl Test::Cmd::Common module
167 # that we may wish to add in the future:
169 # $test->subdir('subdir', ...);
171 # $test->copy('src_file', 'dst_file');
173 # $test->chmod($mode, 'file', ...);
175 # $test->touch('file', ...);
177 def __init__(self, **kw):
178 """Initialize a new TestCommon instance. This involves just
179 calling the base class initialization, and then changing directory
182 apply(TestCmd.__init__, [self], kw)
183 os.chdir(self.workdir)
185 def must_exist(self, *files):
186 """Ensures that the specified file(s) must exist. An individual
187 file be specified as a list of directory names, in which case the
188 pathname will be constructed by concatenating them. Exits FAILED
189 if any of the files does not exist.
191 files = map(lambda x: is_List(x) and os.path.join(x) or x, files)
192 missing = filter(lambda x: not os.path.exists(x), files)
194 print "Missing files: `%s'" % string.join(missing, "', `")
195 self.fail_test(missing)
197 def must_match(self, file, expect):
198 """Matches the contents of the specified file (first argument)
199 against the expected contents (second argument). The expected
200 contents are a list of lines or a string which will be split
203 file_contents = self.read(file)
205 self.fail_test(not self.match(file_contents, expect))
207 print "Unexpected contents of `%s'" % file
208 print "EXPECTED contents ======"
210 print "ACTUAL contents ========"
214 def must_not_exist(self, *files):
215 """Ensures that the specified file(s) must not exist.
216 An individual file be specified as a list of directory names, in
217 which case the pathname will be constructed by concatenating them.
218 Exits FAILED if any of the files exists.
220 files = map(lambda x: is_List(x) and os.path.join(x) or x, files)
221 existing = filter(os.path.exists, files)
223 print "Unexpected files exist: `%s'" % string.join(existing, "', `")
224 self.fail_test(existing)
226 def run(self, options = None, arguments = None,
227 stdout = None, stderr = '', status = 0, **kw):
228 """Runs the program under test, checking that the test succeeded.
230 The arguments are the same as the base TestCmd.run() method,
231 with the addition of:
233 options Extra options that get appended to the beginning
236 stdout The expected standard output from
237 the command. A value of None means
238 don't test standard output.
240 stderr The expected error output from
241 the command. A value of None means
242 don't test error output.
244 status The expected exit status from the
245 command. A value of None means don't
248 By default, this expects a successful exit (status = 0), does
249 not test standard output (stdout = None), and expects that error
250 output is empty (stderr = "").
253 if arguments is None:
256 arguments = options + " " + arguments
257 kw['arguments'] = arguments
259 apply(TestCmd.run, [self], kw)
261 print "STDOUT ============"
263 print "STDERR ============"
266 if _failed(self, status):
269 expect = " (expected %s)" % str(status)
270 print "%s returned %s%s" % (self.program, str(_status(self)), expect)
271 print "STDOUT ============"
273 print "STDERR ============"
276 if not stdout is None and not self.match(self.stdout(), stdout):
277 print "Expected STDOUT =========="
279 print "Actual STDOUT ============"
281 stderr = self.stderr()
283 print "STDERR ==================="
286 if not stderr is None and not self.match(self.stderr(), stderr):
287 print "STDOUT ==================="
289 print "Expected STDERR =========="
291 print "Actual STDERR ============"