[devtools] Re-implement generative tests using subtests
authorDenis Laxalde <denis.laxalde@logilab.fr>
Thu, 14 Jan 2016 18:35:07 +0100
changeset 11076 403a901b6b1e
parent 11073 d7e8912549cd
child 11077 09be48c01fa4
[devtools] Re-implement generative tests using subtests Generative tests as implemented in logilab.common.testib are not compatible with tests runner other than lgc.pytest and this implementation differs from the standard library, which has support for subtests_ since Python 3.4. Use unittest2 to bridge the gap. Maybe it'd be good to implement this on logilab.common.testlib side at some point. Let's see how this gets received here first. .. _subtests: https://docs.python.org/3/library/unittest.html#subtests
cubicweb/__pkginfo__.py
cubicweb/devtools/qunit.py
cubicweb/devtools/repotest.py
cubicweb/devtools/test/unittest_qunit.py
cubicweb/devtools/testlib.py
debian/control
--- a/cubicweb/__pkginfo__.py	Tue Jan 19 10:03:16 2016 +0100
+++ b/cubicweb/__pkginfo__.py	Thu Jan 14 18:35:07 2016 +0100
@@ -52,7 +52,8 @@
     'logilab-database': '>= 1.15.0',
     'passlib': '',
     'pytz': '',
-    'Markdown': ''
+    'Markdown': '',
+    'unittest2': '>= 0.7.0',
     }
 
 __recommends__ = {
--- a/cubicweb/devtools/qunit.py	Tue Jan 19 10:03:16 2016 +0100
+++ b/cubicweb/devtools/qunit.py	Thu Jan 14 18:35:07 2016 +0100
@@ -25,7 +25,7 @@
 from six.moves.queue import Queue, Empty
 
 # imported by default to simplify further import statements
-from logilab.common.testlib import unittest_main, with_tempdir, InnerTest, Tags
+from logilab.common.testlib import unittest_main, with_tempdir, Tags
 import webtest.http
 
 import cubicweb
@@ -109,8 +109,9 @@
                 depends = args[1]
             else:
                 depends = ()
-            for js_test in self._test_qunit(test_file, depends):
-                yield js_test
+            for name, func, args in self._test_qunit(test_file, depends):
+                with self.subTest(name=name):
+                    func(*args)
 
     @with_tempdir
     def _test_qunit(self, test_file, depends=(), timeout=10):
@@ -127,8 +128,10 @@
         browser.start(self.config['base-url'] + "?vid=qunit")
         test_count = 0
         error = False
-        def raise_exception(cls, *data):
-            raise cls(*data)
+
+        def runtime_error(*data):
+            raise RuntimeError(*data)
+
         while not error:
             try:
                 result, test_name, msg = self.test_queue.get(timeout=timeout)
@@ -138,18 +141,16 @@
                     break
                 test_count += 1
                 if result:
-                    yield InnerTest(test_name, lambda : 1)
+                    yield test_name, lambda *args: 1, ()
                 else:
-                    yield InnerTest(test_name, self.fail, msg)
+                    yield test_name, self.fail, (msg, )
             except Empty:
                 error = True
                 msg = '%s inactivity timeout (%is). %i test results received'
-                yield InnerTest(test_file, raise_exception, RuntimeError,
-                                 msg % (test_file, timeout, test_count))
+                yield test_file, runtime_error, (msg % (test_file, timeout, test_count), )
         browser.stop()
         if test_count <= 0 and not error:
-            yield InnerTest(test_name, raise_exception, RuntimeError,
-                            'No test yielded by qunit for %s' % test_file)
+            yield test_name, runtime_error, ('No test yielded by qunit for %s' % test_file, )
 
 class QUnitResultController(Controller):
 
--- a/cubicweb/devtools/repotest.py	Tue Jan 19 10:03:16 2016 +0100
+++ b/cubicweb/devtools/repotest.py	Thu Jan 14 18:35:07 2016 +0100
@@ -137,13 +137,14 @@
 
 from rql import RQLHelper
 
+from cubicweb.devtools.testlib import BaseTestCase
 from cubicweb.devtools.fake import FakeRepo, FakeConfig, FakeSession
 from cubicweb.server import set_debug, debugged
 from cubicweb.server.querier import QuerierHelper
 from cubicweb.server.session import Session
 from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions
 
-class RQLGeneratorTC(TestCase):
+class RQLGeneratorTC(BaseTestCase):
     schema = backend = None # set this in concrete class
 
     @classmethod
--- a/cubicweb/devtools/test/unittest_qunit.py	Tue Jan 19 10:03:16 2016 +0100
+++ b/cubicweb/devtools/test/unittest_qunit.py	Thu Jan 14 18:35:07 2016 +0100
@@ -17,9 +17,9 @@
         js_tests = list(self._test_qunit(js('test_simple_failure.js')))
         self.assertEqual(len(js_tests), 3)
         test_1, test_2, test_3 = js_tests
-        self.assertRaises(self.failureException, test_1[0], *test_1[1:])
-        self.assertRaises(self.failureException, test_2[0], *test_2[1:])
-        test_3[0](*test_3[1:])
+        self.assertRaises(self.failureException, test_1[1], *test_1[2:])
+        self.assertRaises(self.failureException, test_2[1], *test_2[2:])
+        test_3[1](*test_3[2:])
 
 
 if __name__ == '__main__':
--- a/cubicweb/devtools/testlib.py	Tue Jan 19 10:03:16 2016 +0100
+++ b/cubicweb/devtools/testlib.py	Thu Jan 14 18:35:07 2016 +0100
@@ -31,7 +31,7 @@
 
 import yams.schema
 
-from logilab.common.testlib import TestCase, InnerTest, Tags
+from logilab.common.testlib import TestCase, Tags
 from logilab.common.pytest import nocoverage
 from logilab.common.debugger import Debugger
 from logilab.common.umessage import message_from_string
@@ -51,6 +51,16 @@
 from cubicweb.devtools import fake, htmlparser, DEFAULT_EMPTY_DB_ID
 
 
+if sys.version_info[:2] < (3, 4):
+    import unittest2
+    if not hasattr(unittest2.TestCase, 'subTest'):
+        raise ImportError('no subTest support in available unittest2')
+    class BaseTestCase(unittest2.TestCase, TestCase):
+        """Mix of logilab.common.testlib.TestCase and unittest2.TestCase"""
+else:
+    BaseTestCase = TestCase
+
+
 # low-level utilities ##########################################################
 
 class CubicWebDebugger(Debugger):
@@ -253,7 +263,7 @@
 
 # base class for cubicweb tests requiring a full cw environments ###############
 
-class CubicWebTC(TestCase):
+class CubicWebTC(BaseTestCase):
     """abstract class for test using an apptest environment
 
     attributes:
@@ -1238,18 +1248,19 @@
                 propdefs[k]['default'] = True
         for view in self.list_views_for(rset):
             backup_rset = rset.copy(rset.rows, rset.description)
-            yield InnerTest(self._testname(rset, view.__regid__, 'view'),
-                            self.view, view.__regid__, rset,
-                            rset.req.reset_headers(), 'main-template')
+            with self.subTest(name=self._testname(rset, view.__regid__, 'view')):
+                self.view(view.__regid__, rset,
+                          rset.req.reset_headers(), 'main-template')
             # We have to do this because some views modify the
             # resultset's syntax tree
             rset = backup_rset
         for action in self.list_actions_for(rset):
-            yield InnerTest(self._testname(rset, action.__regid__, 'action'),
-                            self._test_action, action)
+            with self.subTest(name=self._testname(rset, action.__regid__, 'action')):
+                self._test_action(action)
         for box in self.list_boxes_for(rset):
             w = [].append
-            yield InnerTest(self._testname(rset, box.__regid__, 'box'), box.render, w)
+            with self.subTest(self._testname(rset, box.__regid__, 'box')):
+                box.render(w)
 
     @staticmethod
     def _testname(rset, objid, objtype):
@@ -1277,19 +1288,18 @@
     def test_one_each_config(self):
         self.auto_populate(1)
         for rset in self.iter_automatic_rsets(limit=1):
-            for testargs in self._test_everything_for(rset):
-                yield testargs
+            self._test_everything_for(rset)
 
     def test_ten_each_config(self):
         self.auto_populate(10)
         for rset in self.iter_automatic_rsets(limit=10):
-            for testargs in self._test_everything_for(rset):
-                yield testargs
+            self._test_everything_for(rset)
 
     def test_startup_views(self):
         for vid in self.list_startup_views():
             with self.admin_access.web_request() as req:
-                yield self.view, vid, None, req
+                with self.subTest(vid=vid):
+                    self.view(vid, None, req)
 
 
 # registry instrumentization ###################################################
--- a/debian/control	Tue Jan 19 10:03:16 2016 +0100
+++ b/debian/control	Thu Jan 14 18:35:07 2016 +0100
@@ -11,7 +11,7 @@
  python-six (>= 1.4.0),
  python-sphinx,
  python-logilab-common,
- python-unittest2 | python (>= 2.7),
+ python-unittest2 (>= 0.7.0) | python (>= 3.4),
  python-logilab-mtconverter,
  python-markdown,
  python-tz,