1# 2# testcase.py: Control of test case execution. 3# 4# Subversion is a tool for revision control. 5# See http://subversion.tigris.org for more information. 6# 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 15# 16# http://www.apache.org/licenses/LICENSE-2.0 17# 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###################################################################### 25 26import os, types, sys 27 28import svntest 29 30# if somebody does a "from testcase import *", they only get these names 31__all__ = ['_XFail', '_Wimp', '_Skip', '_SkipUnless', 32 '_SkipDumpLoadCrossCheck'] 33 34RESULT_OK = 'ok' 35RESULT_FAIL = 'fail' 36RESULT_SKIP = 'skip' 37 38 39class TextColors: 40 '''Some ANSI terminal constants for output color''' 41 ENDC = '\033[0;m' 42 FAILURE = '\033[1;31m' 43 SUCCESS = '\033[1;32m' 44 45 @classmethod 46 def disable(cls): 47 cls.ENDC = '' 48 cls.FAILURE = '' 49 cls.SUCCESS = '' 50 51 @classmethod 52 def success(cls, str): 53 return lambda: cls.SUCCESS + str + cls.ENDC 54 55 @classmethod 56 def failure(cls, str): 57 return lambda: cls.FAILURE + str + cls.ENDC 58 59 60if not sys.stdout.isatty() or sys.platform == 'win32': 61 TextColors.disable() 62 63 64class TestCase: 65 """A thing that can be tested. This is an abstract class with 66 several methods that need to be overridden.""" 67 68 _result_map = { 69 RESULT_OK: (0, TextColors.success('PASS: '), True), 70 RESULT_FAIL: (1, TextColors.failure('FAIL: '), False), 71 RESULT_SKIP: (2, TextColors.success('SKIP: '), True), 72 } 73 74 def __init__(self, delegate=None, cond_func=lambda: True, doc=None, wip=None, 75 issues=None): 76 """Create a test case instance based on DELEGATE. 77 78 COND_FUNC is a callable that is evaluated at test run time and should 79 return a boolean value that determines how a pass or failure is 80 interpreted: see the specialized kinds of test case such as XFail and 81 Skip for details. The evaluation of COND_FUNC is deferred so that it 82 can base its decision on useful bits of information that are not 83 available at __init__ time (like the fact that we're running over a 84 particular RA layer). 85 86 DOC is ... 87 88 WIP is a string describing the reason for the work-in-progress 89 """ 90 assert hasattr(cond_func, '__call__') 91 92 self._delegate = delegate 93 self._cond_func = cond_func 94 self.description = doc or delegate.description 95 self.inprogress = wip 96 self.issues = issues 97 98 def get_function_name(self): 99 """Return the name of the python function implementing the test.""" 100 return self._delegate.get_function_name() 101 102 def get_sandbox_name(self): 103 """Return the name that should be used for the sandbox. 104 105 If a sandbox should not be constructed, this method returns None. 106 """ 107 return self._delegate.get_sandbox_name() 108 109 def set_issues(self, issues): 110 """Set the issues associated with this test.""" 111 self.issues = issues 112 113 def run(self, sandbox): 114 """Run the test within the given sandbox.""" 115 return self._delegate.run(sandbox) 116 117 def list_mode(self): 118 return '' 119 120 def results(self, result): 121 # if our condition applied, then use our result map. otherwise, delegate. 122 if self._cond_func(): 123 val = list(self._result_map[result]) 124 val[1] = val[1]() 125 return val 126 return self._delegate.results(result) 127 128 129class FunctionTestCase(TestCase): 130 """A TestCase based on a naked Python function object. 131 132 FUNC should be a function that returns None on success and throws an 133 svntest.Failure exception on failure. It should have a brief 134 docstring describing what it does (and fulfilling certain conditions). 135 FUNC must take one argument, an Sandbox instance. (The sandbox name 136 is derived from the file name in which FUNC was defined) 137 """ 138 139 def __init__(self, func, issues=None, skip_cross_check=False): 140 # it better be a function that accepts an sbox parameter and has a 141 # docstring on it. 142 assert isinstance(func, types.FunctionType) 143 144 name = func.__name__ 145 146 assert func.__code__.co_argcount == 1, \ 147 '%s must take an sbox argument' % name 148 149 doc = func.__doc__.strip() 150 assert doc, '%s must have a docstring' % name 151 152 # enforce stylistic guidelines for the function docstrings: 153 # - no longer than 50 characters 154 # - should not end in a period 155 # - should not be capitalized 156 assert len(doc) <= 50, \ 157 "%s's docstring must be 50 characters or less" % name 158 assert doc[-1] != '.', \ 159 "%s's docstring should not end in a period" % name 160 assert doc[0].lower() == doc[0], \ 161 "%s's docstring should not be capitalized" % name 162 163 TestCase.__init__(self, doc=doc, issues=issues) 164 self.func = func 165 self.skip_cross_check = skip_cross_check 166 167 def get_function_name(self): 168 return self.func.__name__ 169 170 def get_sandbox_name(self): 171 """Base the sandbox's name on the name of the file in which the 172 function was defined.""" 173 174 filename = self.func.__code__.co_filename 175 return os.path.splitext(os.path.basename(filename))[0] 176 177 def run(self, sandbox): 178 result = self.func(sandbox) 179 sandbox.verify(skip_cross_check = self.skip_cross_check) 180 return result 181 182 183class _XFail(TestCase): 184 """A test that is expected to fail, if its condition is true.""" 185 186 _result_map = { 187 RESULT_OK: (1, TextColors.failure('XPASS:'), False), 188 RESULT_FAIL: (0, TextColors.success('XFAIL:'), True), 189 RESULT_SKIP: (2, TextColors.success('SKIP: '), True), 190 } 191 192 def __init__(self, test_case, cond_func=lambda: True, wip=None, 193 issues=None): 194 """Create an XFail instance based on TEST_CASE. COND_FUNC is a 195 callable that is evaluated at test run time and should return a 196 boolean value. If COND_FUNC returns true, then TEST_CASE is 197 expected to fail (and a pass is considered an error); otherwise, 198 TEST_CASE is run normally. The evaluation of COND_FUNC is 199 deferred so that it can base its decision on useful bits of 200 information that are not available at __init__ time (like the fact 201 that we're running over a particular RA layer). 202 203 WIP is ... 204 205 ISSUES is an issue number (or a list of issue numbers) tracking this.""" 206 207 TestCase.__init__(self, create_test_case(test_case), cond_func, wip=wip, 208 issues=issues) 209 210 def list_mode(self): 211 # basically, the only possible delegate is a Skip test. favor that mode. 212 return self._delegate.list_mode() or 'XFAIL' 213 214 215class _Wimp(_XFail): 216 """Like XFail, but indicates a work-in-progress: an unexpected pass 217 is not considered a test failure.""" 218 219 _result_map = { 220 RESULT_OK: (0, TextColors.success('XPASS:'), True), 221 RESULT_FAIL: (0, TextColors.success('XFAIL:'), True), 222 RESULT_SKIP: (2, TextColors.success('SKIP: '), True), 223 } 224 225 def __init__(self, wip, test_case, cond_func=lambda: True, issues=None): 226 _XFail.__init__(self, test_case, cond_func, wip, issues) 227 228 229class _Skip(TestCase): 230 """A test that will be skipped if its conditional is true.""" 231 232 def __init__(self, test_case, cond_func=lambda: True, issues=None): 233 """Create a Skip instance based on TEST_CASE. COND_FUNC is a 234 callable that is evaluated at test run time and should return a 235 boolean value. If COND_FUNC returns true, then TEST_CASE is 236 skipped; otherwise, TEST_CASE is run normally. 237 The evaluation of COND_FUNC is deferred so that it can base its 238 decision on useful bits of information that are not available at 239 __init__ time (like the fact that we're running over a 240 particular RA layer).""" 241 242 TestCase.__init__(self, create_test_case(test_case), cond_func, 243 issues=issues) 244 245 def list_mode(self): 246 if self._cond_func(): 247 return 'SKIP' 248 return self._delegate.list_mode() 249 250 def get_sandbox_name(self): 251 if self._cond_func(): 252 return None 253 return self._delegate.get_sandbox_name() 254 255 def run(self, sandbox): 256 if self._cond_func(): 257 raise svntest.Skip 258 return self._delegate.run(sandbox) 259 260 261class _SkipUnless(_Skip): 262 """A test that will be skipped if its conditional is false.""" 263 264 def __init__(self, test_case, cond_func): 265 _Skip.__init__(self, test_case, lambda c=cond_func: not c()) 266 267 268class _SkipDumpLoadCrossCheck(TestCase): 269 """A test that will skip the post-test dump/load cross-check.""" 270 271 def __init__(self, test_case, cond_func=lambda: True, wip=None, 272 issues=None): 273 TestCase.__init__(self, 274 create_test_case(test_case, skip_cross_check=True), 275 cond_func, wip=wip, issues=issues) 276 277 278def create_test_case(func, issues=None, skip_cross_check=False): 279 if isinstance(func, TestCase): 280 return func 281 else: 282 return FunctionTestCase(func, issues=issues, 283 skip_cross_check=skip_cross_check) 284 285 286# Various decorators to make declaring tests as such simpler 287def XFail_deco(cond_func = lambda: True): 288 def _second(func): 289 if isinstance(func, TestCase): 290 return _XFail(func, cond_func, issues=func.issues) 291 else: 292 return _XFail(func, cond_func) 293 294 return _second 295 296 297def Wimp_deco(wip, cond_func = lambda: True): 298 def _second(func): 299 if isinstance(func, TestCase): 300 return _Wimp(wip, func, cond_func, issues=func.issues) 301 else: 302 return _Wimp(wip, func, cond_func) 303 304 return _second 305 306 307def Skip_deco(cond_func = lambda: True): 308 def _second(func): 309 if isinstance(func, TestCase): 310 return _Skip(func, cond_func, issues=func.issues) 311 else: 312 return _Skip(func, cond_func) 313 314 return _second 315 316 317def SkipUnless_deco(cond_func): 318 def _second(func): 319 if isinstance(func, TestCase): 320 return _Skip(func, lambda c=cond_func: not c(), issues=func.issues) 321 else: 322 return _Skip(func, lambda c=cond_func: not c()) 323 324 return _second 325 326 327def Issues_deco(*issues): 328 def _second(func): 329 if isinstance(func, TestCase): 330 # if the wrapped thing is already a test case, just set the issues 331 func.set_issues(issues) 332 return func 333 334 else: 335 # we need to wrap the function 336 return create_test_case(func, issues=issues) 337 338 return _second 339 340def SkipDumpLoadCrossCheck_deco(cond_func = lambda: True): 341 def _second(func): 342 if isinstance(func, TestCase): 343 return _SkipDumpLoadCrossCheck(func, cond_func, issues=func.issues) 344 else: 345 return _SkipDumpLoadCrossCheck(func, cond_func) 346 347 return _second 348 349 350# Create a singular alias, for linguistic correctness 351Issue_deco = Issues_deco 352