[Piglit] [PATCH 02/20] utils.py: add boilerplate to work around nose bug

Dylan Baker baker.dylan.c at gmail.com
Sat Jun 14 08:05:11 PDT 2014

The gist of the bug is that the way nose uses generators to create tests
it yields the same instance multiple times, and it uses an instance
attribute to set the name of the test. It then prints the results of the
tests all at once, meaning that the final assignment of that instance
attribute is displayed for all failing tests.

The class is a proxy object that is used to create a unique instance for
each test. This works around the bug since the attribute that stores the
test description isn't overwritten each time a new test is generated.

The decorator is used for generators to transparently replace the
function yielded by the test generator with a proxy object. This allows
generated tests to return the correct name; all without any modification
to the test generator. This means that if nose ever solves this problem
making use of nose's solution is a single deleted line per generator.

Signed-off-by: Dylan Baker <baker.dylan.c at gmail.com>
 framework/tests/utils.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/framework/tests/utils.py b/framework/tests/utils.py
index f337b1e..e4a6cd4 100644
--- a/framework/tests/utils.py
+++ b/framework/tests/utils.py
@@ -28,11 +28,13 @@ in a single place.
 import os
 import shutil
 import tempfile
+import functools
 from contextlib import contextmanager
     import simplejson as json
 except ImportError:
     import json
+import nose.tools as nt
 __all__ = [
@@ -106,3 +108,59 @@ def tempdir():
     tdir = tempfile.mkdtemp()
     yield tdir
+ at nt.nottest
+class GeneratedTestWrapper(object):
+    """ An object proxy for nose test instances
+    Nose uses python generators to create test generators, the drawback of this
+    is that unless the generator is very specifically engineered it yeilds the
+    same instance multiple times. Since nose uses an instance attribute to
+    display the name of the test on a failure, and it prints the failure
+    dialogue after the run is complete all failing tests from a single
+    generator will end up with the name of the last test generated. This
+    generator is used in conjunction with the nose_generator() decorator to
+    create multiple objects each with a unique description attribute, working
+    around the bug.
+    Upstream bug: https://code.google.com/p/python-nose/issues/detail?id=244
+    This uses functoos.update_wrapper to proxy the underlying object, and
+    provides a __call__ method (which allows it to be called like a function)
+    that calls the underling 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.
+    Arguments:
+    wrapped -- A function or function-like-class
+    """
+    def __init__(self, wrapped):
+        self._wrapped = wrapped
+        functools.update_wrapper(self, self._wrapped)
+    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)
+def nose_generator(func):
+    """ Decorator for nose test generators to us GeneratedTestWrapper
+    This decorator replaces each function yeilded by a test generator with a
+    GeneratedTestWrapper reverse-proxy object
+    """
+    def test_wrapper(*args, **kwargs):
+        for x in func(*args, **kwargs):
+            x = list(x)
+            x[0] = GeneratedTestWrapper(x[0])
+            yield tuple(x)  # This must be a tuple for some reason
+    return test_wrapper

More information about the Piglit mailing list