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

Ilia Mirkin imirkin at alum.mit.edu
Thu May 29 13:48:46 PDT 2014


Can you explain how your wrapper class is different from
functools.wraps? I'm sure I'm missing some detail...

On Thu, May 29, 2014 at 4:37 PM, Dylan Baker <baker.dylan.c at gmail.com> wrote:
> 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
>
> _______________________________________________
> Piglit mailing list
> Piglit at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/piglit


More information about the Piglit mailing list