[Piglit] [PATCH 04/11] framework/test/opengl.py: Add FastSkipMixin which checks extensions

Dylan Baker baker.dylan.c at gmail.com
Sat Jan 2 15:44:14 PST 2016


Not off the top of my head. But I do have a patch Jose reviewed that would
probably cover the problem, (falling back to not fast skipping). What test
is it you're running?

Sent from my Nexus 6.
I'm on mobile, please excuse autocorrect fail.
On Jan 1, 2016 16:59, "Marek Olšák" <maraeo at gmail.com> wrote:

> 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/piglit/attachments/20160102/15f574be/attachment-0001.html>


More information about the Piglit mailing list