comparison tests/test_util.py @ 1056:0932bb4d8870

tests: add a metaclass for triggering stupid on a class level We use a metaclass similar to the tests for obsolete mode. This metaclass deliberately duplicates each test for each mode; enabling both means that each test function is run four times. This makes it less likely that a fix is accidentally applied to replay mode only, as new tests will often automatically cover both modes. However, as certiain features remain deliberately unimplemented in stupid modes -- filemaps being a notable example -- we also add a decorator function for marking methods testing them. We do this for reasons of both consistency and coverage; we avoid littering the tests with *_stupid variants, and thus are less likely to forget adding them. I already found a couple of bugs in stupid mode thanks to this increased coverage.
author Dan Villiom Podlaski Christiansen <danchr@gmail.com>
date Fri, 09 Aug 2013 23:34:16 +0200
parents 608e7c8740af
children 1afd308b8f46
comparison
equal deleted inserted replaced
1055:2d7398fffd0d 1056:0932bb4d8870
158 return 158 return
159 if not isinstance(option, str): 159 if not isinstance(option, str):
160 raise TypeError('requiresoption takes a string argument') 160 raise TypeError('requiresoption takes a string argument')
161 return decorator 161 return decorator
162 162
163 def requiresreplay(method):
164 '''Skip a test in stupid mode.'''
165 def test(self, *args, **kwargs):
166 if self.stupid:
167 if SkipTest:
168 raise SkipTest(message)
169 else:
170 return method(self, *args, **kwargs)
171
172 test.__name__ = method.__name__
173 return test
174
163 def filtermanifest(manifest): 175 def filtermanifest(manifest):
164 return [f for f in manifest if f not in util.ignoredfiles] 176 return [f for f in manifest if f not in util.ignoredfiles]
165 177
166 def fileurl(path): 178 def fileurl(path):
167 path = os.path.abspath(path).replace(os.sep, '/') 179 path = os.path.abspath(path).replace(os.sep, '/')
289 301
290 assert getattr(cls, wrapper.__name__, None) is None 302 assert getattr(cls, wrapper.__name__, None) is None
291 303
292 setattr(cls, wrapper.__name__, wrapper) 304 setattr(cls, wrapper.__name__, wrapper)
293 305
306
307 def _stupid_wrap(cls, name):
308 origfunc = getattr(cls, name)
309
310 if not name.startswith('test_') or not origfunc:
311 return
312
313 def wrapper(self, *args, **opts):
314 self.assertFalse(self.stupid, 'stupid mode was already active')
315
316 self.stupid = True
317
318 try:
319 origfunc(self, *args, **opts)
320 finally:
321 self.stupid = False
322
323 wrapper.__name__ = name + ' stupid'
324 wrapper.__module__ = origfunc.__module__
325
326 if origfunc.__doc__:
327 firstline = origfunc.__doc__.strip().splitlines()[0]
328 wrapper.__doc__ = firstline + ' (stupid)'
329
330 assert getattr(cls, wrapper.__name__, None) is None
331
332 setattr(cls, wrapper.__name__, wrapper)
333
294 class TestMeta(type): 334 class TestMeta(type):
295 def __init__(cls, *args, **opts): 335 def __init__(cls, *args, **opts):
296 if cls.obsolete_mode_tests: 336 if cls.obsolete_mode_tests:
297 for origname in dir(cls): 337 for origname in dir(cls):
298 _obsolete_wrap(cls, origname) 338 _obsolete_wrap(cls, origname)
299 339
340 if cls.stupid_mode_tests:
341 for origname in dir(cls):
342 _stupid_wrap(cls, origname)
343
300 return super(TestMeta, cls).__init__(*args, **opts) 344 return super(TestMeta, cls).__init__(*args, **opts)
301 345
302 class TestBase(unittest.TestCase): 346 class TestBase(unittest.TestCase):
303 __metaclass__ = TestMeta 347 __metaclass__ = TestMeta
304 348
305 obsolete_mode_tests = False 349 obsolete_mode_tests = False
350 stupid_mode_tests = False
351
352 stupid = False
306 353
307 def setUp(self): 354 def setUp(self):
308 _verify_our_modules() 355 _verify_our_modules()
309 356
310 # the Python 2.7 default of 640 is obnoxiously low 357 # the Python 2.7 default of 640 is obnoxiously low
366 setattr(ui.ui, self.patch[0].func_name, self.patch[0]) 413 setattr(ui.ui, self.patch[0].func_name, self.patch[0])
367 414
368 _verify_our_modules() 415 _verify_our_modules()
369 416
370 def ui(self, stupid=False, layout='auto'): 417 def ui(self, stupid=False, layout='auto'):
371 return testui(stupid, layout) 418 return testui(self.stupid or stupid, layout)
372 419
373 def load_svndump(self, fixture_name): 420 def load_svndump(self, fixture_name):
374 '''Loads an svnadmin dump into a fresh repo. Return the svn repo 421 '''Loads an svnadmin dump into a fresh repo. Return the svn repo
375 path. 422 path.
376 ''' 423 '''
418 '--layout=%s' % layout, 465 '--layout=%s' % layout,
419 '--startrev=%s' % startrev, 466 '--startrev=%s' % startrev,
420 fileurl(projectpath), 467 fileurl(projectpath),
421 self.wc_path, 468 self.wc_path,
422 ] 469 ]
423 if stupid: 470 if self.stupid or stupid:
424 cmd.append('--stupid') 471 cmd.append('--stupid')
425 if noupdate: 472 if noupdate:
426 cmd.append('--noupdate') 473 cmd.append('--noupdate')
427 if rev is not None: 474 if rev is not None:
428 cmd.append('--rev=%s' % rev) 475 cmd.append('--rev=%s' % rev)
478 def repo(self): 525 def repo(self):
479 return hg.repository(testui(), self.wc_path) 526 return hg.repository(testui(), self.wc_path)
480 527
481 def pushrevisions(self, stupid=False, expected_extra_back=0): 528 def pushrevisions(self, stupid=False, expected_extra_back=0):
482 before = repolen(self.repo) 529 before = repolen(self.repo)
483 self.repo.ui.setconfig('hgsubversion', 'stupid', str(stupid)) 530 self.repo.ui.setconfig('hgsubversion', 'stupid',
531 str(self.stupid or stupid))
484 res = commands.push(self.repo.ui, self.repo) 532 res = commands.push(self.repo.ui, self.repo)
485 after = repolen(self.repo) 533 after = repolen(self.repo)
486 self.assertEqual(expected_extra_back, after - before) 534 self.assertEqual(expected_extra_back, after - before)
487 return res 535 return res
488 536