Commits

Dan Villiom Podlaski Christiansen committed 0932bb4

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.

  • Participants
  • Parent commits 2d7398f

Comments (0)

Files changed (1)

File tests/test_util.py

         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]
 
 
     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()
         _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
             fileurl(projectpath),
             self.wc_path,
             ]
-        if stupid:
+        if self.stupid or stupid:
             cmd.append('--stupid')
         if noupdate:
             cmd.append('--noupdate')
 
     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)