[Piglit] [PATCH] python tests: Adds an object proxy to make generator names correct

Dylan Baker baker.dylan.c at gmail.com
Thu May 29 13:37:49 PDT 2014


This adds a class that wraps python functions to work around a bug in
nose. The bug has to do with the way nose test generators and python
instances interact, but the short is that all generated tests that raise
an error will get the description of the last test run, rather than
their own. This works around that bug by creating a class instance per
generated test, so they get the right description.

---

The problem with this solution is it requires adding more boilerplate to
each generator, a better solution would be to use a decorator, of course,
there is a problem with the way nose handles generators which makes
decorating generators difficult, and I don't have the time to dig into it.


 framework/tests/core_tests.py    |  5 ++---
 framework/tests/dmesg_tests.py   | 12 ++++++----
 framework/tests/log_tests.py     |  3 ++-
 framework/tests/status_tests.py  |  3 ++-
 framework/tests/summary_tests.py |  7 +++---
 framework/tests/test_lists.py    |  5 +++--
 framework/tests/utils.py         | 47 ++++++++++++++++++++++++++++++++++++++++
 7 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/framework/tests/core_tests.py b/framework/tests/core_tests.py
index 44462ce..de424ad 100644
--- a/framework/tests/core_tests.py
+++ b/framework/tests/core_tests.py
@@ -47,10 +47,9 @@ def test_generate_initialize():
     even work?
 
     """
-    yieldable = check_initialize
 
-    for target in [core.Environment, core.TestrunResult, core.TestResult,
-                   core.PiglitJSONEncoder]:
+    for target in [core.Environment, core.PiglitJSONEncoder,]:
+        yieldable = utils.GeneratedTestWrapper(check_initialize)
         yieldable.description = "Test that {} initializes".format(
             target.__name__)
         yield yieldable, target
diff --git a/framework/tests/dmesg_tests.py b/framework/tests/dmesg_tests.py
index ccc3144..13ea705 100644
--- a/framework/tests/dmesg_tests.py
+++ b/framework/tests/dmesg_tests.py
@@ -32,6 +32,7 @@ from framework.exectest import PiglitTest
 from framework.gleantest import GleanTest
 from framework.shader_test import ShaderTest
 from framework.glsl_parser_test import GLSLParserTest
+import framework.tests.utils as utils
 
 
 def _get_dmesg():
@@ -180,10 +181,11 @@ def test_update_result_replace():
         new_result = dmesg.update_result(create_test_result(res))
 
         # Create a yieldable and set the description for useful per-test names
-        yieldable = check_update_result
+        yieldable = utils.GeneratedTestWrapper(check_update_result)
         yieldable.description = "Test update_result: {0}".format(res)
         yield yieldable, new_result['result'], res
 
+        yieldable = utils.GeneratedTestWrapper(check_update_result)
         yieldable.description = "Test update_result subtest: {0}".format(res)
         yield yieldable, new_result['subtest']['test'], res
 
@@ -193,7 +195,7 @@ def test_update_result_replace():
         _write_dev_kmesg()
         new_result = dmesg.update_result(create_test_result(res))
 
-        yieldable = check_equal_result
+        yieldable = utils.GeneratedTestWrapper(check_equal_result)
         yieldable.description = ("Test update_result with non-matching regex: "
                                  "{0}".format(res))
         yield yieldable, new_result['result'], res
@@ -204,11 +206,12 @@ def test_update_result_replace():
         _write_dev_kmesg()
         new_result = dmesg.update_result(create_test_result(res))
 
-        yieldable = check_update_result
+        yieldable = utils.GeneratedTestWrapper(check_update_result)
         yieldable.description = ("Test update_result with matching regex: "
                                 "{0} ".format(res))
         yield yieldable, new_result['result'], res
 
+
 def check_equal_result(result, status):
     """ Tests that the result and status are equal
 
@@ -220,6 +223,7 @@ def check_equal_result(result, status):
     nt.assert_equal(result, status, msg="status should not have changed "
                                         "from {} to {}".format(status, result))
 
+
 def check_update_result(result, status):
     """ Tests that update result replaces results correctly
 
@@ -272,9 +276,9 @@ def test_testclasses_dmesg():
              (GLSLParserTest, 'tests/glslparsertest/shaders/main1.vert',
               'GLSLParserTest')]
 
-    yieldable = check_classes_dmesg
 
     for tclass, tfile, desc in lists:
+        yieldable = utils.GeneratedTestWrapper(check_classes_dmesg)
         yieldable.description = "Test dmesg in {}".format(desc)
         yield yieldable, tclass, tfile
 
diff --git a/framework/tests/log_tests.py b/framework/tests/log_tests.py
index e53b95b..10ad86e 100644
--- a/framework/tests/log_tests.py
+++ b/framework/tests/log_tests.py
@@ -25,6 +25,7 @@ import itertools
 from types import *  # This is a special * safe module
 import nose.tools as nt
 from framework.log import Log
+import framework.tests.utils as utils
 
 valid_statuses = ('pass', 'fail', 'crash', 'warn', 'dmesg-warn',
                   'dmesg-fail', 'skip', 'dry-run')
@@ -73,9 +74,9 @@ def check_post_log_increment_summary(stat):
 
 def test_post_log_increment_summary():
     """ Generator that creates tests for self.__summary """
-    yieldable = check_post_log_increment_summary
 
     for stat in valid_statuses:
+        yieldable = utils.GeneratedTestWrapper(check_post_log_increment_summary)
         yieldable.description = ("Test that Log.post_log increments "
                                  "self._summary[{}]".format(stat))
         yield yieldable, stat
diff --git a/framework/tests/status_tests.py b/framework/tests/status_tests.py
index 599225f..c8f2f4a 100644
--- a/framework/tests/status_tests.py
+++ b/framework/tests/status_tests.py
@@ -28,6 +28,7 @@ etc
 import itertools
 import nose.tools as nt
 import framework.status as status
+import framework.tests.utils as utils
 
 # Statuses from worst to last. NotRun is intentionally not in this list and
 # tested separately because of upcoming features for it
@@ -73,9 +74,9 @@ def check_lookup(stat):
 
 def test_gen_lookup():
     """ Generator that attempts to do a lookup on all statuses """
-    yieldable = check_lookup
 
     for stat in STATUSES + ['skip', 'notrun']:
+        yieldable = utils.GeneratedTestWrapper(check_lookup)
         yieldable.description = "Lookup: {}".format(stat)
         yield yieldable, stat
 
diff --git a/framework/tests/summary_tests.py b/framework/tests/summary_tests.py
index 8961e1a..438b481 100644
--- a/framework/tests/summary_tests.py
+++ b/framework/tests/summary_tests.py
@@ -56,10 +56,11 @@ def test_summary_add_to_set():
                                ('timeout', 'pass', 'fixes'),
                                ('pass', 'timeout', 'regressions'),
                                ('pass', 'timeout', 'problems')]:
-        check_sets.description = "{0} -> {1} should be added to {2}".format(
-                ostat, nstat, set_)
+        yieldable = utils.GeneratedTestWrapper(check_sets)
+        yieldable.description = "{0} -> {1} should be added to {2}".format(
+            ostat, nstat, set_)
 
-        yield check_sets, old, ostat, new, nstat, set_
+        yield yieldable, old, ostat, new, nstat, set_
 
 
 def check_sets(old, ostat, new, nstat, set_):
diff --git a/framework/tests/test_lists.py b/framework/tests/test_lists.py
index fe5ec13..97b5bc3 100644
--- a/framework/tests/test_lists.py
+++ b/framework/tests/test_lists.py
@@ -29,19 +29,20 @@ es3conform, etc)
 import importlib
 import os.path as path
 from nose.plugins.skip import SkipTest
+import framework.tests.utils as utils
 
 
 def gen_test_import():
     """ Generates a bunch of tests to import the various test modules """
-    yieldable = check_import
-
     # Test the various OpenGL modules
     for module in ['all', 'quick', 'gpu', 'sanity', 'r500', 'r300']:
+        yieldable = utils.GeneratedTestWrapper(check_import)
         yieldable.description = "Test import of tests.{}".format(module)
         yield yieldable, "tests." + module
 
     # Test the various OpenCL modules
     for module in ['cl', 'all_cl', 'quick_cl']:
+        yieldable = utils.GeneratedTestWrapper(check_import)
         yieldable.description = "Test import of tests.{}".format(module)
         yield yieldable, "tests." + module
 
diff --git a/framework/tests/utils.py b/framework/tests/utils.py
index f337b1e..2b47052 100644
--- a/framework/tests/utils.py
+++ b/framework/tests/utils.py
@@ -33,6 +33,7 @@ try:
     import simplejson as json
 except ImportError:
     import json
+import nose.tools as nt
 
 
 __all__ = [
@@ -106,3 +107,49 @@ def tempdir():
     tdir = tempfile.mkdtemp()
     yield tdir
     shutil.rmtree(tdir)
+
+
+ at nt.nottest
+class GeneratedTestWrapper(object):
+    """ This class is used to work around a bug in nose
+
+    There is a bug in nose that causes generated tests to all be the same
+    instance, which means they all have the same description. This class works
+    around that by wrapping the test, and then being itself an initialized
+    object, and acting as an object proxy.
+
+    It generally passes through the attribues of the wrapped function, and
+    provides a __call__ method that returns the value of calling the underlying
+    function.
+
+    This class can also be used to wrap a class, but that class needs to
+    provide a __call__ method, and use that to return results.
+
+    Argument:
+    wrapped -- A function or function-like-class
+
+    """
+    def __init__(self, wrapped):
+        self._wrapped = wrapped
+
+        # set the __name__ attribute to be the __name__ of the wrapped function
+        try:
+            self.__name__ = wrapped.__name__
+        except AttributeError:
+            pass
+
+    @property
+    def __class__(self):
+        return self._wrapped.__class__
+
+    def __getattr__(self, name):
+        return getattr(self._wrapped, name)
+
+    def __call__(self, *args, **kwargs):
+        """ calls the wrapped function
+
+        Arguments:
+        *args -- arguments to be passed to the wrapped function
+        **kwargs -- keyword arguments to be passed to the wrapped function
+        """
+        return self._wrapped(*args, **kwargs)
-- 
2.0.0.rc4



More information about the Piglit mailing list