changeset 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 2d7398fffd0d
children cd256960b622
files tests/test_util.py
diffstat 1 files changed, 51 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -160,6 +160,18 @@ def requiresoption(option):
         raise TypeError('requiresoption takes a string argument')
     return decorator
 
+def requiresreplay(method):
+    '''Skip a test in stupid mode.'''
+    def test(self, *args, **kwargs):
+        if self.stupid:
+            if SkipTest:
+                raise SkipTest(message)
+        else:
+            return method(self, *args, **kwargs)
+
+    test.__name__ = method.__name__
+    return test
+
 def filtermanifest(manifest):
     return [f for f in manifest if f not in util.ignoredfiles]
 
@@ -291,18 +303,53 @@ def _obsolete_wrap(cls, name):
 
     setattr(cls, wrapper.__name__, wrapper)
 
+
+def _stupid_wrap(cls, name):
+    origfunc = getattr(cls, name)
+
+    if not name.startswith('test_') or not origfunc:
+        return
+
+    def wrapper(self, *args, **opts):
+        self.assertFalse(self.stupid, 'stupid mode was already active')
+
+        self.stupid = True
+
+        try:
+            origfunc(self, *args, **opts)
+        finally:
+            self.stupid = False
+
+    wrapper.__name__ = name + ' stupid'
+    wrapper.__module__ = origfunc.__module__
+
+    if origfunc.__doc__:
+        firstline = origfunc.__doc__.strip().splitlines()[0]
+        wrapper.__doc__ = firstline + ' (stupid)'
+
+    assert getattr(cls, wrapper.__name__, None) is None
+
+    setattr(cls, wrapper.__name__, wrapper)
+
 class TestMeta(type):
     def __init__(cls, *args, **opts):
         if cls.obsolete_mode_tests:
             for origname in dir(cls):
                 _obsolete_wrap(cls, origname)
 
+        if cls.stupid_mode_tests:
+            for origname in dir(cls):
+                _stupid_wrap(cls, origname)
+
         return super(TestMeta, cls).__init__(*args, **opts)
 
 class TestBase(unittest.TestCase):
     __metaclass__ = TestMeta
 
     obsolete_mode_tests = False
+    stupid_mode_tests = False
+
+    stupid = False
 
     def setUp(self):
         _verify_our_modules()
@@ -368,7 +415,7 @@ class TestBase(unittest.TestCase):
         _verify_our_modules()
 
     def ui(self, stupid=False, layout='auto'):
-        return testui(stupid, layout)
+        return testui(self.stupid or stupid, layout)
 
     def load_svndump(self, fixture_name):
         '''Loads an svnadmin dump into a fresh repo. Return the svn repo
@@ -420,7 +467,7 @@ class TestBase(unittest.TestCase):
             fileurl(projectpath),
             self.wc_path,
             ]
-        if stupid:
+        if self.stupid or stupid:
             cmd.append('--stupid')
         if noupdate:
             cmd.append('--noupdate')
@@ -480,7 +527,8 @@ class TestBase(unittest.TestCase):
 
     def pushrevisions(self, stupid=False, expected_extra_back=0):
         before = repolen(self.repo)
-        self.repo.ui.setconfig('hgsubversion', 'stupid', str(stupid))
+        self.repo.ui.setconfig('hgsubversion', 'stupid',
+                               str(self.stupid or stupid))
         res = commands.push(self.repo.ui, self.repo)
         after = repolen(self.repo)
         self.assertEqual(expected_extra_back, after - before)