[Piglit] [PATCH 05/20] unittests: Make skip less invasive

Dylan Baker dylan at pnwbakers.com
Wed Jun 1 23:50:12 UTC 2016


I prefer that skip is just a decorator so that the test itself doesn't
need to contain code to skip.
---
 tox.ini                               |   1 +
 unittests/base_tests.py               |  31 +++-----
 unittests/compressed_backend_tests.py |  14 +---
 unittests/core_tests.py               |   6 +-
 unittests/json_tests.py               |  12 +--
 unittests/junit_backends_tests.py     |   2 +-
 unittests/monitoring_tests.py         |   8 +-
 unittests/profile_tests.py            |   4 +-
 unittests/utils/nose.py               | 135 ++++++++++++++++++++--------------
 9 files changed, 110 insertions(+), 103 deletions(-)

diff --git a/tox.ini b/tox.ini
index 901432e..d6a845f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,6 +9,7 @@ setenv =
 deps =
     nose
     coverage
+    wrapt
     six==1.5.2
     accel: simplejson
     accel-nix: lxml
diff --git a/unittests/base_tests.py b/unittests/base_tests.py
index 1933d71..2058703 100644
--- a/unittests/base_tests.py
+++ b/unittests/base_tests.py
@@ -31,6 +31,10 @@ try:
     from unittest import mock
 except ImportError:
     import mock
+try:
+    import subprocess32 as subprocess
+except ImportError:
+    import subprocess
 
 import six
 import nose.tools as nt
@@ -87,6 +91,8 @@ def test_run_return_early():
 
 
 @attr('slow')
+ at utils.nose.Skip.module('psutil', available=True)
+ at utils.nose.Skip.backport(3.3, 'subprocess32')
 @nt.timed(6)
 def test_timeout_kill_children():
     """test.base.Test: kill children if terminate fails
@@ -97,13 +103,6 @@ def test_timeout_kill_children():
     This test could leave processes running if it fails.
 
     """
-    utils.nose.module_check('psutil')
-    if six.PY2:
-        utils.nose.module_check('subprocess32')
-        import subprocess32 as subprocess  # pylint: disable=import-error
-    elif six.PY3:
-        import subprocess
-
     class PopenProxy(object):
         """An object that proxies Popen, and saves the Popen instance as an
         attribute.
@@ -173,6 +172,8 @@ def test_timeout_kill_children():
 
 
 @attr('slow')
+ at utils.nose.Skip.backport(3.3, 'subprocess32')
+ at utils.nose.Skip.binary('sleep')
 @nt.timed(6)
 def test_timeout():
     """test.base.Test: Stops running test after timeout expires
@@ -181,37 +182,29 @@ def test_timeout():
     if the test runs 5 seconds it's run too long
 
     """
-    if six.PY2:
-        utils.nose.module_check('subprocess32')
-    utils.nose.binary_check('sleep', 1)
-
     test = TimeoutTest(['sleep', '60'])
     test.timeout = 1
     test.run()
 
 
 @attr('slow')
+ at utils.nose.Skip.backport(3.3, 'subprocess32')
+ at utils.nose.Skip.binary('sleep')
 @nt.timed(6)
 def test_timeout_timeout():
     """test.base.Test: Sets status to 'timeout' when timeout exceeded"""
-    if six.PY2:
-        utils.nose.module_check('subprocess32')
-    utils.nose.binary_check('sleep', 1)
-
     test = TimeoutTest(['sleep', '60'])
     test.timeout = 1
     test.run()
     nt.eq_(test.result.result, 'timeout')
 
 
+ at utils.nose.Skip.backport(3.3, 'subprocess32')
+ at utils.nose.Skip.binary('true')
 @nt.timed(2)
 def test_timeout_pass():
     """test.base.Test: Doesn't change status when timeout not exceeded
     """
-    if six.PY2:
-        utils.nose.module_check('subprocess32')
-    utils.nose.binary_check('true')
-
     test = TimeoutTest(['true'])
     test.timeout = 1
     test.result.result = 'pass'
diff --git a/unittests/compressed_backend_tests.py b/unittests/compressed_backend_tests.py
index c3be896..18786c1 100644
--- a/unittests/compressed_backend_tests.py
+++ b/unittests/compressed_backend_tests.py
@@ -32,8 +32,6 @@ import os
 import functools
 
 import nose.tools as nt
-from nose.plugins.skip import SkipTest
-import six
 
 from . import utils
 from framework import results
@@ -233,20 +231,12 @@ def test_update_piglit_conf():
     nt.eq_(compression.get_mode(), 'foobar')
 
 
+ at utils.nose.Skip.py3
+ at utils.nose.Skip.module('backports.lzma', available=False)
 @utils.nose.set_env(PIGLIT_COMPRESSION='xz')
 @utils.nose.test_in_tempdir
 def test_xz_shell_override():
     """framework.backends.compression: the xz shell utility path can overwrite"""
-    if six.PY3:
-        raise SkipTest('Test is irrelvent on python 3')
-
-    try:
-        import backports.lzma  # pylint: disable=unused-variable
-    except ImportError:
-        pass
-    else:
-        raise SkipTest('Test requires shell path, not backports.lzma path.')
-
     with open('foo.json.xz', 'w') as f:
         f.write('foo')
 
diff --git a/unittests/core_tests.py b/unittests/core_tests.py
index 4d83e73..8a160e7 100644
--- a/unittests/core_tests.py
+++ b/unittests/core_tests.py
@@ -33,9 +33,9 @@ import textwrap
 # There is a very high potential that one of these will raise an ImportError
 # pylint: disable=import-error
 try:
-    from unittest import mock
-except ImportError:
     import mock
+except ImportError:
+    from unittest import mock
 # pylint: enable=import-error
 
 import nose.tools as nt
@@ -332,7 +332,7 @@ def test_check_dir_handler():
                        handler=mock.Mock(side_effect=utils.nose.SentinalException))
 
 
- at utils.nose.skip(not six.PY3, 'Test is only relevant on python 3.x')
+ at utils.nose.Skip.py2
 def test_check_dir_stat_FileNotFoundError():
     """core.check_dir: FileNotFoundError is raised and failifexsits is False continue"""
     with mock.patch('framework.core.os.stat',
diff --git a/unittests/json_tests.py b/unittests/json_tests.py
index 0d17f19..7c7451e 100644
--- a/unittests/json_tests.py
+++ b/unittests/json_tests.py
@@ -97,22 +97,22 @@ class TestJsonOutput(utils.nose.StaticDirectory):
         """JSON: tests is a root key."""
         nt.assert_in('tests', self.json)
 
+    @utils.nose.Skip.platform('linux')
+    @utils.nose.Skip.binary('lspci')
     def test_root_lspci(self):
         """JSON: lspci is a root key."""
-        utils.nose.platform_check('linux')
-        utils.nose.binary_check('lspci')
         nt.assert_in('lspci', self.json)
 
+    @utils.nose.Skip.platform('linux')
+    @utils.nose.Skip.binary('uname')
     def test_root_uname(self):
         """JSON: uname is a root key."""
-        utils.nose.platform_check('linux')
-        utils.nose.binary_check('uname')
         nt.assert_in('uname', self.json)
 
+    @utils.nose.Skip.platform('linux')
+    @utils.nose.Skip.binary('glxinfo')
     def test_root_glxinfo(self):
         """JSON: glxinfo is a root key."""
-        utils.nose.platform_check('linux')
-        utils.nose.binary_check('glxinfo')
         nt.assert_in('glxinfo', self.json)
 
     def test_root_time_elapsed(self):
diff --git a/unittests/junit_backends_tests.py b/unittests/junit_backends_tests.py
index f034a55..aaad5bf 100644
--- a/unittests/junit_backends_tests.py
+++ b/unittests/junit_backends_tests.py
@@ -113,9 +113,9 @@ class TestJUnitSingleTest(TestJunitNoTests):
         """backends.junit.JUnitBackend.write_test(): (once) produces well formed xml"""
         super(TestJUnitSingleTest, self).test_xml_well_formed()
 
+    @utils.nose.Skip.module('lxml', available=True)
     def test_xml_valid(self):
         """backends.junit.JUnitBackend.write_test(): (once) produces valid xml"""
-        utils.nose.module_check('lxml')
         schema = etree.XMLSchema(file=JUNIT_SCHEMA)
         with open(self.test_file, 'r') as f:
             nt.ok_(schema.validate(etree.parse(f)), msg='xml is not valid')
diff --git a/unittests/monitoring_tests.py b/unittests/monitoring_tests.py
index 817ee55..158fcb5 100644
--- a/unittests/monitoring_tests.py
+++ b/unittests/monitoring_tests.py
@@ -151,11 +151,9 @@ class TestMonitoring(object):
 
         nt.assert_equal(self.monitoring.abort_needed, False)
 
+    @utils.nose.Skip.platform('linux')
     def test_Monitoring_dmesg_error(self):
         """monitoring.Monitoring: error found on the dmesg."""
-
-        utils.nose.platform_check('linux')
-
         mock_out = mock.Mock(return_value=b'[1.0]This\n[2.0]is\n[3.0]dmesg')
         with mock.patch('framework.dmesg.subprocess.check_output', mock_out):
             self.monitoring.add_rule('no_error_file',
@@ -170,11 +168,9 @@ class TestMonitoring(object):
 
         nt.assert_equal(self.monitoring.abort_needed, True)
 
+    @utils.nose.Skip.platform('linux')
     def test_Monitoring_dmesg_no_error(self):
         """monitoring.Monitoring: no error found on the dmesg."""
-
-        utils.nose.platform_check('linux')
-
         mock_out = mock.Mock(return_value=b'[1.0]This\n[2.0]is\n[3.0]dmesg')
         with mock.patch('framework.dmesg.subprocess.check_output', mock_out):
             self.monitoring.add_rule('no_error_file',
diff --git a/unittests/profile_tests.py b/unittests/profile_tests.py
index 1a2d450..d190feb 100644
--- a/unittests/profile_tests.py
+++ b/unittests/profile_tests.py
@@ -77,17 +77,17 @@ def test_testprofile_default_dmesg():
     nt.ok_(isinstance(profile_.dmesg, dmesg.DummyDmesg))
 
 
+ at utils.nose.Skip.platform('linux')
 def test_testprofile_set_dmesg_true():
     """profile.TestProfile: Dmesg returns an appropriate dmesg is set to True"""
-    utils.nose.platform_check('linux')
     profile_ = profile.TestProfile()
     profile_.dmesg = True
     nt.ok_(isinstance(profile_.dmesg, dmesg.LinuxDmesg))
 
 
+ at utils.nose.Skip.platform('linux')
 def test_testprofile_set_dmesg_false():
     """profile.TestProfile: Dmesg returns a DummyDmesg if set to False"""
-    utils.nose.platform_check('linux')
     profile_ = profile.TestProfile()
     profile_.dmesg = True
     profile_.dmesg = False
diff --git a/unittests/utils/nose.py b/unittests/utils/nose.py
index 573b573..739089c 100644
--- a/unittests/utils/nose.py
+++ b/unittests/utils/nose.py
@@ -42,6 +42,7 @@ from contextlib import contextmanager
 
 from nose.plugins.skip import SkipTest
 import six
+import wrapt
 try:
     from six.moves import getcwd
 except ImportError:
@@ -244,40 +245,91 @@ def generator(func):
     return test_wrapper
 
 
-def binary_check(bin_, errno_=None):
-    """Check for the existence of a binary or raise SkipTest.
+ at wrapt.decorator
+def passthrough(wrapped, _, args, kwargs):
+    return wrapped(*args, **kwargs)
 
-    If an errno_ is provided then a skip test will be raised unless the error
-    number provided is raised, or no error is raised.
+
+class Skip(object):
+    """Class providing conditional skipping support.
+
+    Provides a number of class methods as alternate constructors for special
+    skip conditions, these are merely convenience methods.
 
     """
-    with open(os.devnull, 'w') as null:
-        try:
-            subprocess.check_call([bin_], stdout=null, stderr=null)
-        except OSError as e:
-            if e.errno == errno.ENOENT:
-                raise SkipTest('Binary {} not available'.format(bin_))
-        except subprocess.CalledProcessError as e:
-            if errno_ is not None and e.returncode == errno_:
-                pass
-            else:
-                raise SkipTest(
-                    'Binary provided bad returncode of {} (wanted {})'.format(
-                        e.returncode, errno_))
-
-
-def module_check(module):
-    """Check that an external module is available or skip."""
-    try:
-        importlib.import_module(module)
-    except ImportError:
-        raise SkipTest('Required module {} not available.'.format(module))
+    def __init__(self, condition, description):
+        self.__skip = condition
+        self.__description = description
+
+    @classmethod
+    def py3(cls, _=None):
+        """Skip if the interpreter python 3."""
+        return cls(six.PY3, 'Test is not relevant on python 3.x')
+
+    @classmethod
+    def py2(cls, _=None):
+        """Skip if the interpreter is python 2."""
+        return cls(six.PY2, 'Test is not relevant on python 2.x')
+
+    @classmethod
+    def module(cls, name, available=False):
+        """Skip if the provided external module is (not) available."""
+        assert isinstance(available, bool), 'avilable must be bool'
+        def check():
+            try:
+                importlib.import_module(name)
+            except ImportError:
+                return False
+            return True
+
+        return cls(check() is not available,
+                   'Test requires that module {} is {}available'.format(
+                       name, '' if available else 'not '))
+
+    @classmethod
+    def backport(cls, version, name):
+        """Skip if the interpreter needs a backport that isn't available.
+
+        Arguments:
+        version -- The minimum version that doesn't require the backport
+        name -- the name of the required module
+
+        """
+        assert isinstance(version, float), 'version must be float'
+        if float('.'.join(str(v) for v in sys.version_info[:2])) < version:
+            return cls.module(name, True)
+        return passthrough
 
+    @classmethod
+    def binary(cls, name):
+        """Skip if the requested binary isn't available."""
+        def check():
+            with open(os.devnull, 'w') as null:
+                try:
+                    # Pass the bogus arg in case the program tries to read
+                    # stdin, like xz
+                    subprocess.check_call([name, 'totallymadeupdoesntexistarg'],
+                                          stdout=null, stderr=null)
+                except OSError as e:
+                    if e.errno == errno.ENOENT:
+                        return False
+                except subprocess.CalledProcessError as e:
+                    pass
+            return True
+
+        return cls(
+            check(), 'Test requires that binary {} is available'.format(name))
+
+    @classmethod
+    def platform(cls, name, is_=False):
+        return cls(sys.platform.startswith(name) is is_,
+                   'Platform {} is not supported'.format(sys.platform))
 
-def platform_check(plat):
-    """If the platform is not in the list specified skip the test."""
-    if not sys.platform.startswith(plat):
-        raise SkipTest('Platform {} is not supported'.format(sys.platform))
+    @wrapt.decorator
+    def __call__(self, wrapped, instance, args, kwargs):
+        if self.__skip:
+            raise SkipTest(self.__description)
+        return wrapped(*args, **kwargs)
 
 
 def test_in_tempdir(func):
@@ -387,28 +439,3 @@ def set_env(**envargs):
         return _inner
 
     return _decorator
-
-
-def skip(condition, description):
-    """Skip a test if the condition is met.
-
-    Arguments:
-    condition -- If this is truthy then the test will be skippped
-    description -- the message that SkipTest will display if the test is
-                   skipped
-
-    """
-
-    def _wrapper(func):
-        """The function that acutally does the wrapping."""
-
-        @functools.wraps(func)
-        def _inner(*args, **kwargs):
-            """The function that is actually called."""
-            if condition:
-                raise SkipTest(description)
-            return func(*args, **kwargs)
-
-        return _inner
-
-    return _wrapper
-- 
2.8.3



More information about the Piglit mailing list