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 <[email protected]> --- 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 try: 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 shutil.rmtree(tdir) + + [email protected] +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 -- 2.0.0 _______________________________________________ Piglit mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/piglit
