0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
1 |
# Copyright (C) 2005 by Canonical Ltd
|
2 |
||
3 |
# This program is free software; you can redistribute it and/or modify
|
|
4 |
# it under the terms of the GNU General Public License as published by
|
|
5 |
# the Free Software Foundation; either version 2 of the License, or
|
|
6 |
# (at your option) any later version.
|
|
7 |
||
8 |
# This program is distributed in the hope that it will be useful,
|
|
9 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11 |
# GNU General Public License for more details.
|
|
12 |
||
13 |
# You should have received a copy of the GNU General Public License
|
|
14 |
# along with this program; if not, write to the Free Software
|
|
15 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
16 |
||
17 |
||
18 |
from unittest import TestResult, TestCase |
|
19 |
||
0.1.67
by Martin Pool
More fixes to try to run on python2.3 |
20 |
def _need_subprocess(): |
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
21 |
sys.stderr.write("sorry, this test suite requires the subprocess module\n" |
22 |
"this is shipped with python2.4 and available separately for 2.3\n") |
|
0.1.67
by Martin Pool
More fixes to try to run on python2.3 |
23 |
|
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
24 |
|
25 |
class CommandFailed(Exception): |
|
26 |
pass
|
|
27 |
||
28 |
||
0.1.12
by Martin Pool
Add stubbed-out TestSkipped |
29 |
|
30 |
class TestSkipped(Exception): |
|
31 |
"""Indicates that a test was intentionally skipped, rather than failing."""
|
|
32 |
# XXX: Not used yet
|
|
33 |
||
34 |
||
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
35 |
class TestBase(TestCase): |
36 |
"""Base class for bzr test cases.
|
|
37 |
||
38 |
Just defines some useful helper functions; doesn't actually test
|
|
39 |
anything.
|
|
40 |
"""
|
|
41 |
||
42 |
# TODO: Special methods to invoke bzr, so that we can run it
|
|
43 |
# through a specified Python intepreter
|
|
44 |
||
45 |
OVERRIDE_PYTHON = None # to run with alternative python 'python' |
|
46 |
BZRPATH = 'bzr' |
|
47 |
||
48 |
_log_buf = "" |
|
49 |
||
50 |
||
0.1.10
by Martin Pool
Log messages when test is starting and finishing |
51 |
def setUp(self): |
52 |
super(TestBase, self).setUp() |
|
53 |
self.log("%s setup" % self.id()) |
|
54 |
||
55 |
||
56 |
def tearDown(self): |
|
57 |
super(TestBase, self).tearDown() |
|
58 |
self.log("%s teardown" % self.id()) |
|
59 |
self.log('') |
|
60 |
||
61 |
||
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
62 |
def formcmd(self, cmd): |
63 |
if isinstance(cmd, basestring): |
|
64 |
cmd = cmd.split() |
|
65 |
||
66 |
if cmd[0] == 'bzr': |
|
67 |
cmd[0] = self.BZRPATH |
|
68 |
if self.OVERRIDE_PYTHON: |
|
69 |
cmd.insert(0, self.OVERRIDE_PYTHON) |
|
70 |
||
71 |
self.log('$ %r' % cmd) |
|
72 |
||
73 |
return cmd |
|
74 |
||
75 |
||
76 |
def runcmd(self, cmd, retcode=0): |
|
77 |
"""Run one command and check the return code.
|
|
78 |
||
79 |
Returns a tuple of (stdout,stderr) strings.
|
|
80 |
||
81 |
If a single string is based, it is split into words.
|
|
82 |
For commands that are not simple space-separated words, please
|
|
83 |
pass a list instead."""
|
|
0.1.67
by Martin Pool
More fixes to try to run on python2.3 |
84 |
try: |
85 |
from subprocess import call, Popen, PIPE |
|
86 |
except ImportError, e: |
|
87 |
_need_subprocess
|
|
88 |
raise
|
|
89 |
||
90 |
||
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
91 |
cmd = self.formcmd(cmd) |
92 |
||
93 |
self.log('$ ' + ' '.join(cmd)) |
|
94 |
actual_retcode = call(cmd, stdout=self.TEST_LOG, stderr=self.TEST_LOG) |
|
95 |
||
96 |
if retcode != actual_retcode: |
|
97 |
raise CommandFailed("test failed: %r returned %d, expected %d" |
|
98 |
% (cmd, actual_retcode, retcode)) |
|
99 |
||
100 |
||
101 |
def backtick(self, cmd, retcode=0): |
|
102 |
"""Run a command and return its output"""
|
|
0.1.67
by Martin Pool
More fixes to try to run on python2.3 |
103 |
try: |
104 |
from subprocess import call, Popen, PIPE |
|
105 |
except ImportError, e: |
|
106 |
_need_subprocess() |
|
107 |
raise
|
|
108 |
||
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
109 |
cmd = self.formcmd(cmd) |
110 |
child = Popen(cmd, stdout=PIPE, stderr=self.TEST_LOG) |
|
111 |
outd, errd = child.communicate() |
|
112 |
self.log(outd) |
|
113 |
actual_retcode = child.wait() |
|
114 |
||
115 |
outd = outd.replace('\r', '') |
|
116 |
||
117 |
if retcode != actual_retcode: |
|
118 |
raise CommandFailed("test failed: %r returned %d, expected %d" |
|
119 |
% (cmd, actual_retcode, retcode)) |
|
120 |
||
121 |
return outd |
|
122 |
||
123 |
||
124 |
||
125 |
def build_tree(self, shape): |
|
126 |
"""Build a test tree according to a pattern.
|
|
127 |
||
128 |
shape is a sequence of file specifications. If the final
|
|
129 |
character is '/', a directory is created.
|
|
130 |
||
131 |
This doesn't add anything to a branch.
|
|
132 |
"""
|
|
133 |
# XXX: It's OK to just create them using forward slashes on windows?
|
|
134 |
import os |
|
135 |
for name in shape: |
|
136 |
assert isinstance(name, basestring) |
|
137 |
if name[-1] == '/': |
|
138 |
os.mkdir(name[:-1]) |
|
139 |
else: |
|
140 |
f = file(name, 'wt') |
|
141 |
print >>f, "contents of", name |
|
142 |
f.close() |
|
143 |
||
144 |
||
145 |
def log(self, msg): |
|
146 |
"""Log a message to a progress file"""
|
|
147 |
self._log_buf = self._log_buf + str(msg) + '\n' |
|
148 |
print >>self.TEST_LOG, msg |
|
149 |
||
150 |
||
151 |
def check_inventory_shape(self, inv, shape): |
|
152 |
"""
|
|
153 |
Compare an inventory to a list of expected names.
|
|
154 |
||
155 |
Fail if they are not precisely equal.
|
|
156 |
"""
|
|
157 |
extras = [] |
|
158 |
shape = list(shape) # copy |
|
159 |
for path, ie in inv.entries(): |
|
160 |
name = path.replace('\\', '/') |
|
161 |
if ie.kind == 'dir': |
|
162 |
name = name + '/' |
|
163 |
if name in shape: |
|
164 |
shape.remove(name) |
|
165 |
else: |
|
166 |
extras.append(name) |
|
167 |
if shape: |
|
168 |
self.fail("expected paths not found in inventory: %r" % shape) |
|
169 |
if extras: |
|
170 |
self.fail("unexpected paths found in inventory: %r" % extras) |
|
171 |
||
172 |
||
173 |
def check_file_contents(self, filename, expect): |
|
174 |
self.log("check contents of file %s" % filename) |
|
175 |
contents = file(filename, 'r').read() |
|
176 |
if contents != expect: |
|
177 |
self.log("expected: %r" % expected) |
|
178 |
self.log("actually: %r" % contents) |
|
179 |
self.fail("contents of %s not as expected") |
|
180 |
||
181 |
||
182 |
||
183 |
class InTempDir(TestBase): |
|
184 |
"""Base class for tests run in a temporary branch."""
|
|
185 |
def setUp(self): |
|
186 |
import os |
|
187 |
self.test_dir = os.path.join(self.TEST_ROOT, self.__class__.__name__) |
|
188 |
os.mkdir(self.test_dir) |
|
189 |
os.chdir(self.test_dir) |
|
190 |
||
191 |
def tearDown(self): |
|
192 |
import os |
|
193 |
os.chdir(self.TEST_ROOT) |
|
194 |
||
195 |
||
196 |
||
197 |
||
198 |
||
199 |
class _MyResult(TestResult): |
|
200 |
"""
|
|
201 |
Custom TestResult.
|
|
202 |
||
203 |
No special behaviour for now.
|
|
204 |
"""
|
|
205 |
def __init__(self, out): |
|
206 |
self.out = out |
|
207 |
TestResult.__init__(self) |
|
208 |
||
209 |
def startTest(self, test): |
|
210 |
# TODO: Maybe show test.shortDescription somewhere?
|
|
211 |
print >>self.out, '%-60.60s' % test.id(), |
|
212 |
self.out.flush() |
|
213 |
TestResult.startTest(self, test) |
|
214 |
||
215 |
def stopTest(self, test): |
|
216 |
# print
|
|
217 |
TestResult.stopTest(self, test) |
|
218 |
||
219 |
||
220 |
def addError(self, test, err): |
|
221 |
print >>self.out, 'ERROR' |
|
222 |
TestResult.addError(self, test, err) |
|
223 |
_show_test_failure('error', test, err, self.out) |
|
224 |
||
225 |
def addFailure(self, test, err): |
|
226 |
print >>self.out, 'FAILURE' |
|
227 |
TestResult.addFailure(self, test, err) |
|
228 |
_show_test_failure('failure', test, err, self.out) |
|
229 |
||
230 |
def addSuccess(self, test): |
|
231 |
print >>self.out, 'OK' |
|
232 |
TestResult.addSuccess(self, test) |
|
233 |
||
234 |
||
235 |
||
236 |
def selftest(): |
|
237 |
from unittest import TestLoader, TestSuite |
|
238 |
import bzrlib |
|
239 |
import bzrlib.selftest.whitebox |
|
240 |
import bzrlib.selftest.blackbox |
|
241 |
import bzrlib.selftest.versioning |
|
242 |
from doctest import DocTestSuite |
|
243 |
import os |
|
244 |
import shutil |
|
245 |
import time |
|
246 |
import sys |
|
247 |
||
248 |
suite = TestSuite() |
|
249 |
tl = TestLoader() |
|
250 |
||
251 |
for m in bzrlib.selftest.whitebox, \ |
|
252 |
bzrlib.selftest.versioning: |
|
253 |
suite.addTest(tl.loadTestsFromModule(m)) |
|
254 |
||
255 |
for m in bzrlib.store, bzrlib.inventory, bzrlib.branch, bzrlib.osutils, \ |
|
256 |
bzrlib.commands: |
|
257 |
suite.addTest(DocTestSuite(m)) |
|
258 |
||
259 |
suite.addTest(bzrlib.selftest.blackbox.suite()) |
|
260 |
||
261 |
return run_suite(suite) |
|
262 |
||
263 |
||
264 |
def run_suite(suite): |
|
265 |
import os |
|
266 |
import shutil |
|
267 |
import time |
|
268 |
import sys |
|
269 |
||
270 |
_setup_test_log() |
|
271 |
_setup_test_dir() |
|
272 |
print
|
|
273 |
||
274 |
# save stdout & stderr so there's no leakage from code-under-test
|
|
275 |
real_stdout = sys.stdout |
|
276 |
real_stderr = sys.stderr |
|
277 |
sys.stdout = sys.stderr = TestBase.TEST_LOG |
|
278 |
try: |
|
279 |
result = _MyResult(real_stdout) |
|
280 |
suite.run(result) |
|
281 |
finally: |
|
282 |
sys.stdout = real_stdout |
|
283 |
sys.stderr = real_stderr |
|
284 |
||
285 |
_show_results(result) |
|
286 |
||
287 |
return result.wasSuccessful() |
|
288 |
||
289 |
||
290 |
||
291 |
def _setup_test_log(): |
|
292 |
import time |
|
293 |
import os |
|
294 |
||
295 |
log_filename = os.path.abspath('test.log') |
|
296 |
TestBase.TEST_LOG = open(log_filename, 'wt', buffering=1) # line buffered |
|
297 |
||
0.1.19
by Martin Pool
remove a reference to bzr from testsweet |
298 |
print >>TestBase.TEST_LOG, "tests run at " + time.ctime() |
0.1.2
by Martin Pool
Import testsweet module adapted from bzr. |
299 |
print '%-30s %s' % ('test log', log_filename) |
300 |
||
301 |
||
302 |
def _setup_test_dir(): |
|
303 |
import os |
|
304 |
import shutil |
|
305 |
||
306 |
TestBase.ORIG_DIR = os.getcwdu() |
|
307 |
TestBase.TEST_ROOT = os.path.abspath("test.tmp") |
|
308 |
||
309 |
print '%-30s %s' % ('running tests in', TestBase.TEST_ROOT) |
|
310 |
||
311 |
if os.path.exists(TestBase.TEST_ROOT): |
|
312 |
shutil.rmtree(TestBase.TEST_ROOT) |
|
313 |
os.mkdir(TestBase.TEST_ROOT) |
|
314 |
os.chdir(TestBase.TEST_ROOT) |
|
315 |
||
316 |
# make a fake bzr directory there to prevent any tests propagating
|
|
317 |
# up onto the source directory's real branch
|
|
318 |
os.mkdir(os.path.join(TestBase.TEST_ROOT, '.bzr')) |
|
319 |
||
320 |
||
321 |
||
322 |
def _show_results(result): |
|
323 |
print
|
|
324 |
print '%4d tests run' % result.testsRun |
|
325 |
print '%4d errors' % len(result.errors) |
|
326 |
print '%4d failures' % len(result.failures) |
|
327 |
||
328 |
||
329 |
||
330 |
def _show_test_failure(kind, case, exc_info, out): |
|
331 |
from traceback import print_exception |
|
332 |
||
333 |
print >>out, '-' * 60 |
|
334 |
print >>out, case |
|
335 |
||
336 |
desc = case.shortDescription() |
|
337 |
if desc: |
|
338 |
print >>out, ' (%s)' % desc |
|
339 |
||
340 |
print_exception(exc_info[0], exc_info[1], exc_info[2], None, out) |
|
341 |
||
342 |
if isinstance(case, TestBase): |
|
343 |
print >>out |
|
344 |
print >>out, 'log from this test:' |
|
345 |
print >>out, case._log_buf |
|
346 |
||
347 |
print >>out, '-' * 60 |
|
348 |
||
349 |