Fix the expansion of the $Source$ keyword.
[cvs2svn.git] / svntest / testcase.py
blobf25f0c33e166473e3b3934ae191d127d50fe76d2
2 # testcase.py: Control of test case execution.
4 # Subversion is a tool for revision control.
5 # See http://subversion.tigris.org for more information.
7 # ====================================================================
8 # Licensed to the Apache Software Foundation (ASF) under one
9 # or more contributor license agreements. See the NOTICE file
10 # distributed with this work for additional information
11 # regarding copyright ownership. The ASF licenses this file
12 # to you under the Apache License, Version 2.0 (the
13 # "License"); you may not use this file except in compliance
14 # with the License. You may obtain a copy of the License at
16 # http://www.apache.org/licenses/LICENSE-2.0
18 # Unless required by applicable law or agreed to in writing,
19 # software distributed under the License is distributed on an
20 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 # KIND, either express or implied. See the License for the
22 # specific language governing permissions and limitations
23 # under the License.
24 ######################################################################
26 import os, types, sys
28 import svntest
30 # if somebody does a "from testcase import *", they only get these names
31 __all__ = ['XFail', 'Wimp', 'Skip', 'SkipUnless']
33 RESULT_OK = 'ok'
34 RESULT_FAIL = 'fail'
35 RESULT_SKIP = 'skip'
38 class TextColors:
39 '''Some ANSI terminal constants for output color'''
40 ENDC = '\033[0;m'
41 FAILURE = '\033[1;31m'
42 SUCCESS = '\033[1;32m'
44 @classmethod
45 def disable(cls):
46 cls.ENDC = ''
47 cls.FAILURE = ''
48 cls.SUCCESS = ''
50 @classmethod
51 def success(cls, str):
52 return lambda: cls.SUCCESS + str + cls.ENDC
54 @classmethod
55 def failure(cls, str):
56 return lambda: cls.FAILURE + str + cls.ENDC
59 if not sys.stdout.isatty() or sys.platform == 'win32':
60 TextColors.disable()
63 class TestCase:
64 """A thing that can be tested. This is an abstract class with
65 several methods that need to be overridden."""
67 _result_map = {
68 RESULT_OK: (0, TextColors.success('PASS: '), True),
69 RESULT_FAIL: (1, TextColors.failure('FAIL: '), False),
70 RESULT_SKIP: (2, TextColors.success('SKIP: '), True),
73 def __init__(self, delegate=None, cond_func=lambda: True, doc=None, wip=None):
74 """Create a test case instance based on DELEGATE.
76 COND_FUNC is a callable that is evaluated at test run time and should
77 return a boolean value that determines how a pass or failure is
78 interpreted: see the specialized kinds of test case such as XFail and
79 Skip for details. The evaluation of COND_FUNC is deferred so that it
80 can base its decision on useful bits of information that are not
81 available at __init__ time (like the fact that we're running over a
82 particular RA layer).
84 DOC is ...
86 WIP is ...
87 """
88 assert hasattr(cond_func, '__call__')
90 self._delegate = delegate
91 self._cond_func = cond_func
92 self.description = doc or delegate.description
93 self.inprogress = wip
95 def get_function_name(self):
96 """Return the name of the python function implementing the test."""
97 return self._delegate.get_function_name()
99 def get_sandbox_name(self):
100 """Return the name that should be used for the sandbox.
102 If a sandbox should not be constructed, this method returns None.
104 return self._delegate.get_sandbox_name()
106 def run(self, sandbox):
107 """Run the test within the given sandbox."""
108 return self._delegate.run(sandbox)
110 def list_mode(self):
111 return ''
113 def results(self, result):
114 # if our condition applied, then use our result map. otherwise, delegate.
115 if self._cond_func():
116 val = list(self._result_map[result])
117 val[1] = val[1]()
118 return val
119 return self._delegate.results(result)
122 class FunctionTestCase(TestCase):
123 """A TestCase based on a naked Python function object.
125 FUNC should be a function that returns None on success and throws an
126 svntest.Failure exception on failure. It should have a brief
127 docstring describing what it does (and fulfilling certain conditions).
128 FUNC must take one argument, an Sandbox instance. (The sandbox name
129 is derived from the file name in which FUNC was defined)
132 def __init__(self, func):
133 # it better be a function that accepts an sbox parameter and has a
134 # docstring on it.
135 assert isinstance(func, types.FunctionType)
137 name = func.func_name
139 assert func.func_code.co_argcount == 1, \
140 '%s must take an sbox argument' % name
142 doc = func.__doc__.strip()
143 assert doc, '%s must have a docstring' % name
145 # enforce stylistic guidelines for the function docstrings:
146 # - no longer than 50 characters
147 # - should not end in a period
148 # - should not be capitalized
149 assert len(doc) <= 50, \
150 "%s's docstring must be 50 characters or less" % name
151 assert doc[-1] != '.', \
152 "%s's docstring should not end in a period" % name
153 assert doc[0].lower() == doc[0], \
154 "%s's docstring should not be capitalized" % name
156 TestCase.__init__(self, doc=doc)
157 self.func = func
159 def get_function_name(self):
160 return self.func.func_name
162 def get_sandbox_name(self):
163 """Base the sandbox's name on the name of the file in which the
164 function was defined."""
166 filename = self.func.func_code.co_filename
167 return os.path.splitext(os.path.basename(filename))[0]
169 def run(self, sandbox):
170 return self.func(sandbox)
173 class XFail(TestCase):
174 """A test that is expected to fail, if its condition is true."""
176 _result_map = {
177 RESULT_OK: (1, TextColors.failure('XPASS:'), False),
178 RESULT_FAIL: (0, TextColors.success('XFAIL:'), True),
179 RESULT_SKIP: (2, TextColors.success('SKIP: '), True),
182 def __init__(self, test_case, cond_func=lambda: True, wip=None):
183 """Create an XFail instance based on TEST_CASE. COND_FUNC is a
184 callable that is evaluated at test run time and should return a
185 boolean value. If COND_FUNC returns true, then TEST_CASE is
186 expected to fail (and a pass is considered an error); otherwise,
187 TEST_CASE is run normally. The evaluation of COND_FUNC is
188 deferred so that it can base its decision on useful bits of
189 information that are not available at __init__ time (like the fact
190 that we're running over a particular RA layer).
192 WIP is ..."""
194 TestCase.__init__(self, create_test_case(test_case), cond_func, wip=wip)
196 def list_mode(self):
197 # basically, the only possible delegate is a Skip test. favor that mode.
198 return self._delegate.list_mode() or 'XFAIL'
201 class Wimp(XFail):
202 """Like XFail, but indicates a work-in-progress: an unexpected pass
203 is not considered a test failure."""
205 _result_map = {
206 RESULT_OK: (0, TextColors.success('XPASS:'), True),
207 RESULT_FAIL: (0, TextColors.success('XFAIL:'), True),
208 RESULT_SKIP: (2, TextColors.success('SKIP: '), True),
211 def __init__(self, wip, test_case, cond_func=lambda: True):
212 XFail.__init__(self, test_case, cond_func, wip)
215 class Skip(TestCase):
216 """A test that will be skipped if its conditional is true."""
218 def __init__(self, test_case, cond_func=lambda: True):
219 """Create a Skip instance based on TEST_CASE. COND_FUNC is a
220 callable that is evaluated at test run time and should return a
221 boolean value. If COND_FUNC returns true, then TEST_CASE is
222 skipped; otherwise, TEST_CASE is run normally.
223 The evaluation of COND_FUNC is deferred so that it can base its
224 decision on useful bits of information that are not available at
225 __init__ time (like the fact that we're running over a
226 particular RA layer)."""
228 TestCase.__init__(self, create_test_case(test_case), cond_func)
230 def list_mode(self):
231 if self._cond_func():
232 return 'SKIP'
233 return self._delegate.list_mode()
235 def get_sandbox_name(self):
236 if self._cond_func():
237 return None
238 return self._delegate.get_sandbox_name()
240 def run(self, sandbox):
241 if self._cond_func():
242 raise svntest.Skip
243 return self._delegate.run(sandbox)
246 class SkipUnless(Skip):
247 """A test that will be skipped if its conditional is false."""
249 def __init__(self, test_case, cond_func):
250 Skip.__init__(self, test_case, lambda c=cond_func: not c())
253 def create_test_case(func):
254 if isinstance(func, TestCase):
255 return func
256 else:
257 return FunctionTestCase(func)