[Piglit] [PATCH 04/11] framework/test/opengl.py: Add FastSkipMixin which checks extensions
Marek Olšák
maraeo at gmail.com
Fri Jan 1 16:59:09 PST 2016
Hi Dylan,
-p gbm seems to be broken:
Traceback (most recent call last):ail: 62 /
File "/home/marek/dev/piglit/framework/test/base.py", line 181, in execute
self.run()
File "/home/marek/dev/piglit/framework/test/base.py", line 235, in run
self.is_skip()
File "/home/marek/dev/piglit/framework/test/opengl.py", line 322, in is_skip
if (self.__info.glsl_version is not None
File "/home/marek/dev/piglit/framework/core.py", line 204, in __get__
value = self.__func(instance)
File "/home/marek/dev/piglit/framework/test/opengl.py", line 226, in
glsl_version
raw.split('\n'), 'OpenGL shading language').split()[-1])
File "/home/marek/dev/piglit/framework/test/opengl.py", line 106, in __getline
raise Exception('Unreachable')
Exception: Unreachable
Any idea what's wrong?
Marek
On Thu, Nov 5, 2015 at 11:16 PM, <baker.dylan.c at gmail.com> wrote:
> From: Dylan Baker <baker.dylan.c at gmail.com>
>
> This Mixin provides a way for OpenGL tests to skip very fast. Currently
> it only applies to GL extensions, but will be extended to cover GLSL
> version requirements and GL version requirements (and ES)>
>
> This is split into a separate module because it's going to grow into a
> fairly large amount of code (mostly around querying wflinfo).
>
> Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
> ---
> framework/test/opengl.py | 206 ++++++++++++++++++++++++++++++++++++++++
> framework/tests/base_tests.py | 5 +-
> framework/tests/opengl_tests.py | 188 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 398 insertions(+), 1 deletion(-)
> create mode 100644 framework/test/opengl.py
> create mode 100644 framework/tests/opengl_tests.py
>
> diff --git a/framework/test/opengl.py b/framework/test/opengl.py
> new file mode 100644
> index 0000000..3485d3a
> --- /dev/null
> +++ b/framework/test/opengl.py
> @@ -0,0 +1,206 @@
> +# Copyright (c) 2015 Intel Corporation
> +
> +# Permission is hereby granted, free of charge, to any person obtaining a copy
> +# of this software and associated documentation files (the "Software"), to deal
> +# in the Software without restriction, including without limitation the rights
> +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> +# copies of the Software, and to permit persons to whom the Software is
> +# furnished to do so, subject to the following conditions:
> +
> +# The above copyright notice and this permission notice shall be included in
> +# all copies or substantial portions of the Software.
> +
> +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> +# SOFTWARE.
> +
> +"""Mixins for OpenGL derived tests."""
> +
> +from __future__ import absolute_import, division, print_function
> +import errno
> +import os
> +import subprocess
> +
> +from framework import exceptions, core
> +from framework.options import OPTIONS
> +from .base import TestIsSkip
> +
> +# pylint: disable=too-few-public-methods
> +
> +__all__ = [
> + 'FastSkipMixin',
> +]
> +
> +
> +class StopWflinfo(exceptions.PiglitException):
> + """Exception called when wlfinfo getter should stop."""
> + def __init__(self, reason):
> + super(StopWflinfo, self).__init__()
> + self.reason = reason
> +
> +
> +class WflInfo(object):
> + """Class representing platform information as provided by wflinfo.
> +
> + The design of this is odd to say the least, it's basically a bag with some
> + lazy property evaluators in it, used to avoid calculating the values
> + provided by wflinfo more than once.
> +
> + The problems:
> + - Needs to be shared with all subclasses
> + - Needs to evaluate only once
> + - cannot evaluate until user sets OPTIONS.env['PIGLIT_PLATFORM']
> +
> + This solves all of that, and is
> +
> + """
> + __shared_state = {}
> + def __new__(cls, *args, **kwargs):
> + # Implement the borg pattern:
> + # https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/
> + #
> + # This is something like a singleton, but much easier to implement
> + self = super(WflInfo, cls).__new__(cls, *args, **kwargs)
> + self.__dict__ = cls.__shared_state
> + return self
> +
> + @staticmethod
> + def __call_wflinfo(opts):
> + """Helper to call wflinfo and reduce code duplication.
> +
> + This catches and handles CalledProcessError and OSError.ernno == 2
> + gracefully: it passes them to allow platforms without a particular
> + gl/gles version or wflinfo (resepctively) to work.
> +
> + Arguments:
> + opts -- arguments to pass to wflinfo other than verbose and platform
> +
> + """
> + with open(os.devnull, 'w') as d:
> + try:
> + raw = subprocess.check_output(
> + ['wflinfo',
> + '--platform', OPTIONS.env['PIGLIT_PLATFORM']] + opts,
> + stderr=d)
> + except subprocess.CalledProcessError:
> + # When we hit this error it usually going to be because we have
> + # an incompatible platform/profile combination
> + raise StopWflinfo('Called')
> + except OSError as e:
> + # If we get a 'no wflinfo' warning then just return
> + if e.errno == errno.ENOENT:
> + raise StopWflinfo('OSError')
> + raise
> + return raw
> +
> + @staticmethod
> + def __getline(lines, name):
> + """Find a line in a list return it."""
> + for line in lines:
> + if line.startswith(name):
> + return line
> + raise Exception('Unreachable')
> +
> + @core.lazy_property
> + def gl_extensions(self):
> + """Call wflinfo to get opengl extensions.
> +
> + This provides a very conservative set of extensions, it provides every
> + extension from gles1, 2 and 3 and from GL both core and compat profile
> + as a single set. This may let a few tests execute that will still skip
> + manually, but it helps to ensure that this method never skips when it
> + shouldn't.
> +
> + """
> + _trim = len('OpenGL extensions: ')
> + all_ = set()
> +
> + def helper(const, vars_):
> + """Helper function to reduce code duplication."""
> + # This is a pretty fragile function but it really does help with
> + # duplication
> + for var in vars_:
> + try:
> + ret = self.__call_wflinfo(const + [var])
> + except StopWflinfo as e:
> + # This means tat the particular api or profile is
> + # unsupported
> + if e.reason == 'Called':
> + continue
> + else:
> + raise
> + all_.update(set(self.__getline(
> + ret.split('\n'), 'OpenGL extensions')[_trim:].split()))
> +
> + try:
> + helper(['--verbose', '--api'], ['gles1', 'gles2', 'gles3'])
> + helper(['--verbose', '--api', 'gl', '--profile'],
> + ['core', 'compat', 'none'])
> + except StopWflinfo as e:
> + # Handle wflinfo not being installed by returning an empty set. This
> + # will essentially make FastSkipMixin a no-op.
> + if e.reason == 'OSError':
> + return set()
> + raise
> +
> + return {e.strip() for e in all_}
> +
> +
> +class FastSkipMixin(object):
> + """Fast test skipping for OpenGL based suites.
> +
> + This provides an is_skip() method which will skip the test if an of it's
> + requirements are not met.
> +
> + It also provides new attributes:
> + gl_reqruied -- This is a set of extensions that are required for running
> + the extension.
> + gl_version -- A float that is the required version number for an OpenGL
> + test.
> + gles_version -- A float that is the required version number for an OpenGL
> + ES test
> + glsl_version -- A float that is the required version number of OpenGL
> + Shader Language for a test
> + glsl_ES_version -- A float that is the required version number of OpenGL ES
> + Shader Language for a test
> +
> + This requires wflinfo to be installed and accessible to provide it's
> + functionality, however, it will no-op if wflinfo is not accessible.
> +
> + The design of this function is conservative. The design goal is that it
> + it is better to run a few tests that could have been skipped, than to skip
> + all the tests that could have, but also a few that should have run.
> +
> + """
> + # XXX: This still gets called once for each thread. (4 times with 4
> + # threads), this is a synchronization issue and I don't know how to stop it
> + # other than querying each value before starting the thread pool.
> + __info = WflInfo()
> +
> + def __init__(self, *args, **kwargs):
> + super(FastSkipMixin, self).__init__(*args, **kwargs)
> + self.gl_required = set()
> + self.gl_version = None
> + self.gles_version = None
> + self.glsl_version = None
> + self.glsl_es_version = None
> +
> + def is_skip(self):
> + """Skip this test if any of it's feature requirements are unmet.
> +
> + If no extensions were calculated (if wflinfo isn't installed) then run
> + all tests.
> +
> + """
> + if self.__info.gl_extensions:
> + for extension in self.gl_required:
> + if extension not in self.__info.gl_extensions:
> + raise TestIsSkip(
> + 'Test requires extension {} '
> + 'which is not available'.format(extension))
> +
> + super(FastSkipMixin, self).is_skip()
> diff --git a/framework/tests/base_tests.py b/framework/tests/base_tests.py
> index a7afd25..c005273 100644
> --- a/framework/tests/base_tests.py
> +++ b/framework/tests/base_tests.py
> @@ -28,7 +28,10 @@ from nose.plugins.attrib import attr
>
> import framework.tests.utils as utils
> from framework.test.base import (
> - Test, WindowResizeMixin, ValgrindMixin, TestRunError
> + Test,
> + TestRunError,
> + ValgrindMixin,
> + WindowResizeMixin,
> )
> from framework.tests.status_tests import PROBLEMS, STATUSES
> from framework.options import _Options as Options
> diff --git a/framework/tests/opengl_tests.py b/framework/tests/opengl_tests.py
> new file mode 100644
> index 0000000..aa42738
> --- /dev/null
> +++ b/framework/tests/opengl_tests.py
> @@ -0,0 +1,188 @@
> +# Copyright (c) 2015 Intel Corporation
> +
> +# Permission is hereby granted, free of charge, to any person obtaining a copy
> +# of this software and associated documentation files (the "Software"), to deal
> +# in the Software without restriction, including without limitation the rights
> +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> +# copies of the Software, and to permit persons to whom the Software is
> +# furnished to do so, subject to the following conditions:
> +
> +# The above copyright notice and this permission notice shall be included in
> +# all copies or substantial portions of the Software.
> +
> +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
> +# SOFTWARE.
> +
> +"""Test the opengl module."""
> +
> +from __future__ import absolute_import, division, print_function
> +import subprocess
> +
> +import mock
> +import nose.tools as nt
> +
> +import framework.tests.utils as utils
> +from framework.test import opengl
> +from framework.test.base import TestIsSkip
> +
> +# pylint: disable=invalid-name,protected-access,line-too-long,pointless-statement,attribute-defined-outside-init
> +
> +
> +class TestWflInfo(object):
> + """Tests for the WflInfo class."""
> + __patchers = []
> +
> + def setup(self):
> + """Setup each instance, patching necissary bits."""
> + self._test = opengl.WflInfo()
> + self.__patchers.append(mock.patch.dict(
> + 'framework.test.opengl.OPTIONS.env',
> + {'PIGLIT_PLATFORM': 'foo'}))
> + self.__patchers.append(mock.patch(
> + 'framework.test.opengl.WflInfo._WflInfo__shared_state', {}))
> +
> + for f in self.__patchers:
> + f.start()
> +
> + def teardown(self):
> + for f in self.__patchers:
> + f.stop()
> +
> + def test_gl_extension(self):
> + """test.opengl.WflInfo.gl_extensions: Provides list of gl extensions"""
> + rv = 'foo\nbar\nboink\nOpenGL extensions: GL_foobar GL_ham_sandwhich\n'
> + expected = set(['GL_foobar', 'GL_ham_sandwhich'])
> +
> + with mock.patch('framework.test.opengl.subprocess.check_output',
> + mock.Mock(return_value=rv)):
> + nt.eq_(expected, self._test.gl_extensions)
> +
> +
> +class TestWflInfoSError(object):
> + """Tests for the Wflinfo functions to handle OSErrors."""
> + __patchers = []
> +
> + @classmethod
> + def setup_class(cls):
> + """Setup the class, patching as necissary."""
> + cls.__patchers.append(mock.patch.dict(
> + 'framework.test.opengl.OPTIONS.env',
> + {'PIGLIT_PLATFORM': 'foo'}))
> + cls.__patchers.append(mock.patch(
> + 'framework.test.opengl.subprocess.check_output',
> + mock.Mock(side_effect=OSError(2, 'foo'))))
> + cls.__patchers.append(mock.patch(
> + 'framework.test.opengl.WflInfo._WflInfo__shared_state', {}))
> +
> + for f in cls.__patchers:
> + f.start()
> +
> + def setup(self):
> + self.inst = opengl.WflInfo()
> +
> + @classmethod
> + def teardown_class(cls):
> + for f in cls.__patchers:
> + f.stop()
> +
> + @utils.not_raises(OSError)
> + def test_gl_extensions(self):
> + """test.opengl.WflInfo.gl_extensions: Handles OSError "no file" gracefully"""
> + self.inst.gl_extensions
> +
> +
> +class TestWflInfoCalledProcessError(object):
> + """Tests for the WflInfo functions to handle OSErrors."""
> + __patchers = []
> +
> + @classmethod
> + def setup_class(cls):
> + """Setup the class, patching as necissary."""
> + cls.__patchers.append(mock.patch.dict(
> + 'framework.test.opengl.OPTIONS.env',
> + {'PIGLIT_PLATFORM': 'foo'}))
> + cls.__patchers.append(mock.patch(
> + 'framework.test.opengl.subprocess.check_output',
> + mock.Mock(side_effect=subprocess.CalledProcessError(1, 'foo'))))
> + cls.__patchers.append(mock.patch(
> + 'framework.test.opengl.WflInfo._WflInfo__shared_state', {}))
> +
> + for f in cls.__patchers:
> + f.start()
> +
> + @classmethod
> + def teardown_class(cls):
> + for f in cls.__patchers:
> + f.stop()
> +
> + def setup(self):
> + self.inst = opengl.WflInfo()
> +
> + @utils.not_raises(subprocess.CalledProcessError)
> + def test_gl_extensions(self):
> + """test.opengl.WflInfo.gl_extensions: Handles CalledProcessError gracefully"""
> + self.inst.gl_extensions
> +
> +
> +class TestFastSkipMixin(object):
> + """Tests for the FastSkipMixin class."""
> + __patchers = []
> +
> + @classmethod
> + def setup_class(cls):
> + """Create a Class with FastSkipMixin, but patch various bits."""
> + class _Test(opengl.FastSkipMixin, utils.Test):
> + pass
> +
> + cls._class = _Test
> +
> + _mock_wflinfo = mock.Mock(spec=opengl.WflInfo)
> + _mock_wflinfo.gl_version = 3.3
> + _mock_wflinfo.gles_version = 3.0
> + _mock_wflinfo.glsl_version = 3.3
> + _mock_wflinfo.glsl_es_version = 2.0
> + _mock_wflinfo.gl_extensions = set(['bar'])
> +
> + cls.__patchers.append(mock.patch.object(
> + _Test, '_FastSkipMixin__info', _mock_wflinfo))
> +
> + for patcher in cls.__patchers:
> + patcher.start()
> +
> + @classmethod
> + def teardown_class(cls):
> + for patcher in cls.__patchers:
> + patcher.stop()
> +
> + def setup(self):
> + self.test = self._class(['foo'])
> +
> + @nt.raises(TestIsSkip)
> + def test_should_skip(self):
> + """test.opengl.FastSkipMixin.is_skip: Skips when requires is missing from extensions"""
> + self.test.gl_required.add('foobar')
> + self.test.is_skip()
> +
> + @utils.not_raises(TestIsSkip)
> + def test_should_not_skip(self):
> + """test.opengl.FastSkipMixin.is_skip: runs when requires is in extensions"""
> + self.test.gl_required.add('bar')
> + self.test.is_skip()
> +
> + @utils.not_raises(TestIsSkip)
> + def test_extension_empty(self):
> + """test.opengl.FastSkipMixin.is_skip: if extensions are empty test runs"""
> + self.test.gl_required.add('foobar')
> + with mock.patch.object(self.test._FastSkipMixin__info, 'gl_extensions', # pylint: disable=no-member
> + None):
> + self.test.is_skip()
> +
> + @utils.not_raises(TestIsSkip)
> + def test_requires_empty(self):
> + """test.opengl.FastSkipMixin.is_skip: if gl_requires is empty test runs"""
> + self.test.is_skip()
> --
> 2.6.2
>
> _______________________________________________
> Piglit mailing list
> Piglit at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/piglit
More information about the Piglit
mailing list