[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