Use python1.5 in default Aegis builds. Fix testing infrastructure for Python 1.5.
[scons.git] / etc / TestCommon.py
1 """
2 TestCommon.py:  a testing framework for commands and scripts
3                 with commonly useful error handling
4
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.
11
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.
18
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.
23
24 A TestCommon environment object is created via the usual invocation:
25
26     import TestCommon
27     test = TestCommon.TestCommon()
28
29 You can use all of the TestCmd keyword arguments when instantiating a
30 TestCommon object; see the TestCmd documentation for details.
31
32 Here is an overview of the methods and keyword arguments that are
33 provided by the TestCommon class:
34
35     test.must_exist('file1', ['file2', ...])
36
37     test.must_match('file', "expected contents\n")
38
39     test.must_not_exist('file1', ['file2', ...])
40
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)
45
46 The TestCommon module also provides the following variables
47
48     TestCommon.python_executable
49     TestCommon.exe_suffix
50     TestCommon.obj_suffix
51     TestCommon.shobj_suffix
52     TestCommon.lib_prefix
53     TestCommon.lib_suffix
54     TestCommon.dll_prefix
55     TestCommon.dll_suffix
56
57 """
58
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.
63 #
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
67 # DAMAGE.
68 #
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.
74
75 __author__ = "Steven Knight <knight at baldmt dot com>"
76 __revision__ = "TestCommon.py 0.6.D002 2004/03/29 06:21:41 knight"
77 __version__ = "0.6"
78
79 import os
80 import os.path
81 import string
82 import sys
83 import types
84 import UserList
85
86 from TestCmd import *
87 from TestCmd import __all__
88
89 __all__.extend([ 'TestCommon',
90                  'exe_suffix',
91                  'obj_suffix',
92                  'shobj_suffix',
93                  'lib_prefix',
94                  'lib_suffix',
95                  'dll_prefix',
96                  'dll_suffix',
97                ])
98
99 # Variables that describe the prefixes and suffixes on this system.
100 if sys.platform == 'win32':
101     exe_suffix   = '.exe'
102     obj_suffix   = '.obj'
103     shobj_suffix = '.obj'
104     lib_prefix   = ''
105     lib_suffix   = '.lib'
106     dll_prefix   = ''
107     dll_suffix   = '.dll'
108 elif sys.platform == 'cygwin':
109     exe_suffix   = '.exe'
110     obj_suffix   = '.o'
111     shobj_suffix = '.os'
112     lib_prefix   = 'lib'
113     lib_suffix   = '.a'
114     dll_prefix   = ''
115     dll_suffix   = '.dll'
116 elif string.find(sys.platform, 'irix') != -1:
117     exe_suffix   = ''
118     obj_suffix   = '.o'
119     shobj_suffix = '.o'
120     lib_prefix   = 'lib'
121     lib_suffix   = '.a'
122     dll_prefix   = 'lib'
123     dll_suffix   = '.so'
124 else:
125     exe_suffix   = ''
126     obj_suffix   = '.o'
127     shobj_suffix = '.os'
128     lib_prefix   = 'lib'
129     lib_suffix   = '.a'
130     dll_prefix   = 'lib'
131     dll_suffix   = '.so'
132
133 def is_List(e):
134     return type(e) is types.ListType \
135         or isinstance(e, UserList.UserList)
136
137 class TestFailed(Exception):
138     def __init__(self, args=None):
139         self.args = args
140
141 class TestNoResult(Exception):
142     def __init__(self, args=None):
143         self.args = args
144
145 if os.name == 'posix':
146     def _failed(self, status = 0):
147         if self.status is None or status is None:
148             return None
149         if os.WIFSIGNALED(self.status):
150             return None
151         return _status(self) != status
152     def _status(self):
153         if os.WIFEXITED(self.status):
154             return os.WEXITSTATUS(self.status)
155         else:
156             return None
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
161     def _status(self):
162         return self.status
163
164 class TestCommon(TestCmd):
165
166     # Additional methods from the Perl Test::Cmd::Common module
167     # that we may wish to add in the future:
168     #
169     #  $test->subdir('subdir', ...);
170     #
171     #  $test->copy('src_file', 'dst_file');
172     #
173     #  $test->chmod($mode, 'file', ...);
174     #
175     #  $test->touch('file', ...);
176
177     def __init__(self, **kw):
178         """Initialize a new TestCommon instance.  This involves just
179         calling the base class initialization, and then changing directory
180         to the workdir.
181         """
182         apply(TestCmd.__init__, [self], kw)
183         os.chdir(self.workdir)
184
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.
190         """
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)
193         if missing:
194             print "Missing files: `%s'" % string.join(missing, "', `")
195             self.fail_test(missing)
196
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
201         on newlines.
202         """
203         file_contents = self.read(file)
204         try:
205             self.fail_test(not self.match(file_contents, expect))
206         except:
207             print "Unexpected contents of `%s'" % file
208             print "EXPECTED contents ======"
209             print expect
210             print "ACTUAL contents ========"
211             print file_contents
212             raise
213
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.
219         """
220         files = map(lambda x: is_List(x) and os.path.join(x) or x, files)
221         existing = filter(os.path.exists, files)
222         if existing:
223             print "Unexpected files exist: `%s'" % string.join(existing, "', `")
224             self.fail_test(existing)
225
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.
229
230         The arguments are the same as the base TestCmd.run() method,
231         with the addition of:
232
233                 options Extra options that get appended to the beginning
234                         of the arguments.
235
236                 stdout  The expected standard output from
237                         the command.  A value of None means
238                         don't test standard output.
239
240                 stderr  The expected error output from
241                         the command.  A value of None means
242                         don't test error output.
243
244                 status  The expected exit status from the
245                         command.  A value of None means don't
246                         test exit status.
247
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 = "").
251         """
252         if options:
253             if arguments is None:
254                 arguments = options
255             else:
256                 arguments = options + " " + arguments
257         kw['arguments'] = arguments
258         try:
259             apply(TestCmd.run, [self], kw)
260         except:
261             print "STDOUT ============"
262             print self.stdout()
263             print "STDERR ============"
264             print self.stderr()
265             raise
266         if _failed(self, status):
267             expect = ''
268             if status != 0:
269                 expect = " (expected %s)" % str(status)
270             print "%s returned %s%s" % (self.program, str(_status(self)), expect)
271             print "STDOUT ============"
272             print self.stdout()
273             print "STDERR ============"
274             print self.stderr()
275             raise TestFailed
276         if not stdout is None and not self.match(self.stdout(), stdout):
277             print "Expected STDOUT =========="
278             print stdout
279             print "Actual STDOUT ============"
280             print self.stdout()
281             stderr = self.stderr()
282             if stderr:
283                 print "STDERR ==================="
284                 print stderr
285             raise TestFailed
286         if not stderr is None and not self.match(self.stderr(), stderr):
287             print "STDOUT ==================="
288             print self.stdout()
289             print "Expected STDERR =========="
290             print stderr
291             print "Actual STDERR ============"
292             print self.stderr()
293             raise TestFailed