* subversion/tests/cmdline/svntest/sandbox.py
[svnrdump.git] / svntest / testcase.py
blob5b2ab4519b2940132cf0a4c4ffca56d9ee467301
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 = ''
51 if not sys.stdout.isatty() or sys.platform == 'win32':
52 TextColors.disable()
55 class TestCase:
56 """A thing that can be tested. This is an abstract class with
57 several methods that need to be overridden."""
59 _result_map = {
60 RESULT_OK: (0, TextColors.SUCCESS + 'PASS: ' + TextColors.ENDC, True),
61 RESULT_FAIL: (1, TextColors.FAILURE + 'FAIL: ' + TextColors.ENDC, False),
62 RESULT_SKIP: (2, TextColors.SUCCESS + 'SKIP: ' + TextColors.ENDC, True),
65 def __init__(self, delegate=None, cond_func=lambda: True, doc=None, wip=None):
66 assert hasattr(cond_func, '__call__')
68 self._delegate = delegate
69 self._cond_func = cond_func
70 self.description = doc or delegate.description
71 self.inprogress = wip
73 def get_function_name(self):
74 """Return the name of the python function implementing the test."""
75 return self._delegate.get_function_name()
77 def get_sandbox_name(self):
78 """Return the name that should be used for the sandbox.
80 If a sandbox should not be constructed, this method returns None.
81 """
82 return self._delegate.get_sandbox_name()
84 def run(self, sandbox):
85 """Run the test within the given sandbox."""
86 return self._delegate.run(sandbox)
88 def list_mode(self):
89 return ''
91 def results(self, result):
92 # if our condition applied, then use our result map. otherwise, delegate.
93 if self._cond_func():
94 return self._result_map[result]
95 return self._delegate.results(result)
98 class FunctionTestCase(TestCase):
99 """A TestCase based on a naked Python function object.
101 FUNC should be a function that returns None on success and throws an
102 svntest.Failure exception on failure. It should have a brief
103 docstring describing what it does (and fulfilling certain conditions).
104 FUNC must take one argument, an Sandbox instance. (The sandbox name
105 is derived from the file name in which FUNC was defined)
108 def __init__(self, func):
109 # it better be a function that accepts an sbox parameter and has a
110 # docstring on it.
111 assert isinstance(func, types.FunctionType)
113 name = func.func_name
115 assert func.func_code.co_argcount == 1, \
116 '%s must take an sbox argument' % name
118 doc = func.__doc__.strip()
119 assert doc, '%s must have a docstring' % name
121 # enforce stylistic guidelines for the function docstrings:
122 # - no longer than 50 characters
123 # - should not end in a period
124 # - should not be capitalized
125 assert len(doc) <= 50, \
126 "%s's docstring must be 50 characters or less" % name
127 assert doc[-1] != '.', \
128 "%s's docstring should not end in a period" % name
129 assert doc[0].lower() == doc[0], \
130 "%s's docstring should not be capitalized" % name
132 TestCase.__init__(self, doc=doc)
133 self.func = func
135 def get_function_name(self):
136 return self.func.func_name
138 def get_sandbox_name(self):
139 """Base the sandbox's name on the name of the file in which the
140 function was defined."""
142 filename = self.func.func_code.co_filename
143 return os.path.splitext(os.path.basename(filename))[0]
145 def run(self, sandbox):
146 return self.func(sandbox)
149 class XFail(TestCase):
150 """A test that is expected to fail, if its condition is true."""
152 _result_map = {
153 RESULT_OK: (1, TextColors.FAILURE + 'XPASS:' + TextColors.ENDC, False),
154 RESULT_FAIL: (0, TextColors.SUCCESS + 'XFAIL:' + TextColors.ENDC, True),
155 RESULT_SKIP: (2, TextColors.SUCCESS + 'SKIP: ' + TextColors.ENDC, True),
158 def __init__(self, test_case, cond_func=lambda: True, wip=None):
159 """Create an XFail instance based on TEST_CASE. COND_FUNC is a
160 callable that is evaluated at test run time and should return a
161 boolean value. If COND_FUNC returns true, then TEST_CASE is
162 expected to fail (and a pass is considered an error); otherwise,
163 TEST_CASE is run normally. The evaluation of COND_FUNC is
164 deferred so that it can base its decision on useful bits of
165 information that are not available at __init__ time (like the fact
166 that we're running over a particular RA layer)."""
168 TestCase.__init__(self, create_test_case(test_case), cond_func, wip=wip)
170 def list_mode(self):
171 # basically, the only possible delegate is a Skip test. favor that mode.
172 return self._delegate.list_mode() or 'XFAIL'
175 class Wimp(XFail):
176 """Like XFail, but indicates a work-in-progress: an unexpected pass
177 is not considered a test failure."""
179 _result_map = {
180 RESULT_OK: (0, TextColors.SUCCESS + 'XPASS:' + TextColors.ENDC, True),
181 RESULT_FAIL: (0, TextColors.SUCCESS + 'XFAIL:' + TextColors.ENDC, True),
182 RESULT_SKIP: (2, TextColors.SUCCESS + 'SKIP: ' + TextColors.ENDC, True),
185 def __init__(self, wip, test_case, cond_func=lambda: True):
186 XFail.__init__(self, test_case, cond_func, wip)
189 class Skip(TestCase):
190 """A test that will be skipped if its conditional is true."""
192 def __init__(self, test_case, cond_func=lambda: True):
193 """Create an Skip instance based on TEST_CASE. COND_FUNC is a
194 callable that is evaluated at test run time and should return a
195 boolean value. If COND_FUNC returns true, then TEST_CASE is
196 skipped; otherwise, TEST_CASE is run normally.
197 The evaluation of COND_FUNC is deferred so that it can base its
198 decision on useful bits of information that are not available at
199 __init__ time (like the fact that we're running over a
200 particular RA layer)."""
202 TestCase.__init__(self, create_test_case(test_case), cond_func)
204 def list_mode(self):
205 if self._cond_func():
206 return 'SKIP'
207 return self._delegate.list_mode()
209 def get_sandbox_name(self):
210 if self._cond_func():
211 return None
212 return self._delegate.get_sandbox_name()
214 def run(self, sandbox):
215 if self._cond_func():
216 raise svntest.Skip
217 return self._delegate.run(sandbox)
220 class SkipUnless(Skip):
221 """A test that will be skipped if its conditional is false."""
223 def __init__(self, test_case, cond_func):
224 Skip.__init__(self, test_case, lambda c=cond_func: not c())
227 def create_test_case(func):
228 if isinstance(func, TestCase):
229 return func
230 else:
231 return FunctionTestCase(func)