[Piglit] [PATCH 3/6] framework: move WflInfo class into new wflinfo.py module

Brian Paul brianp at vmware.com
Mon Oct 23 17:37:31 UTC 2017


On 10/18/2017 06:39 PM, Dylan Baker wrote:
> Quoting Brian Paul (2017-10-12 21:23:07)
>> Reduce the clutter in opengl.py
>> ---
>>   framework/test/opengl.py | 269 +------------------------------------------
>>   framework/wflinfo.py     | 290 +++++++++++++++++++++++++++++++++++++++++++++++
>>   tests/all.py             |   4 +-
>>   3 files changed, 294 insertions(+), 269 deletions(-)
>>   create mode 100644 framework/wflinfo.py
>>
>> diff --git a/framework/test/opengl.py b/framework/test/opengl.py
>> index 20e1c4f..81de933 100644
>> --- a/framework/test/opengl.py
>> +++ b/framework/test/opengl.py
>> @@ -23,17 +23,12 @@
>>   from __future__ import (
>>       absolute_import, division, print_function, unicode_literals
>>   )
>> -import errno
>>   import os
>> -import subprocess
>>   import warnings
>>
>> -import six
>> -
>> -from framework import exceptions, core
>> +from framework import exceptions, core, wflinfo
>>   from framework.options import OPTIONS
>>   from .base import TestIsSkip
>> -from framework.test import piglit_test
>>
>>   # pylint: disable=too-few-public-methods
>>
>> @@ -47,266 +42,6 @@ __all__ = [
>>   _DISABLED = bool(os.environ.get('PIGLIT_NO_FAST_SKIP', False))
>>
>>
>> -class StopWflinfo(exceptions.PiglitException):
>> -    """Exception called when wlfinfo getter should stop."""
>> -    def __init__(self, reason):
>> -        super(StopWflinfo, self).__init__()
>> -        self.reason = reason
>> -
>> -
>> -def find_wflinfo():
>> -    """Find location of the wflinfo executable."""
>> -    # First check if it's in our piglit bin/ directory
>> -    wflinfo = os.path.join(piglit_test.TEST_BIN_DIR, "wflinfo.exe")
>> -    if os.path.exists(wflinfo):
>> -        return wflinfo
>> -    else:
>> -        # Assume it's in $PATH
>> -        return "wflinfo"
>> -
>> -
>> -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.
>> -
>> -    """
>> -    __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:
>> -                # Get the piglit platform string and, if needed, convert it
>> -                # to something that wflinfo understands.
>> -                platform = OPTIONS.env['PIGLIT_PLATFORM']
>> -                if platform == "mixed_glx_egl":
>> -                    platform = "glx"
>> -
>> -                wflinfo = find_wflinfo()
>> -
>> -                raw = subprocess.check_output(
>> -                    [wflinfo, '--platform', 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
>> -                print("wflinfo utility not found.")
>> -                if e.errno == errno.ENOENT:
>> -                    raise StopWflinfo('OSError')
>> -                raise
>> -        return raw.decode('utf-8')
>> -
>> -    @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
>> -
>> -        # Don't return a set with only WFLINFO_GL_ERROR.
>> -        ret = {e.strip() for e in all_}
>> -        if ret == {'WFLINFO_GL_ERROR'}:
>> -            return set()
>> -        return ret
>> -
>> -    @core.lazy_property
>> -    def gl_version(self):
>> -        """Calculate the maximum opengl version.
>> -
>> -        This will try (in order): core, compat, and finally no profile,
>> -        stopping when it finds a profile. It assumes that most implementations
>> -        will have core and compat as equals, or core as superior to compat in
>> -        terms of support.
>> -
>> -        """
>> -        ret = None
>> -        for profile in ['core', 'compat', 'none']:
>> -            try:
>> -                raw = self.__call_wflinfo(['--api', 'gl', '--profile', profile])
>> -            except StopWflinfo as e:
>> -                if e.reason == 'Called':
>> -                    continue
>> -                elif e.reason == 'OSError':
>> -                    break
>> -                raise
>> -            else:
>> -                try:
>> -                    # Grab the GL version string, trim any release_number values
>> -                    ret = float(self.__getline(
>> -                        raw.split('\n'),
>> -                        'OpenGL version string').split()[3][:3])
>> -                except (IndexError, ValueError):
>> -                    # This is caused by wlfinfo returning an error
>> -                    pass
>> -                break
>> -        return ret
>> -
>> -    @core.lazy_property
>> -    def gles_version(self):
>> -        """Calculate the maximum opengl es version.
>> -
>> -        The design of this function isn't 100% correct. GLES1 and GLES2+ behave
>> -        differently, since 2+ can be silently promoted, but 1 cannot. This
>> -        means that a driver can implement 2, 3, 3.1, etc, but never have 1
>> -        support.
>> -
>> -        I don't think this is a big deal for a couple of reasons. First, piglit
>> -        has a very small set of GLES1 tests, so they shouldn't have big impact
>> -        on runtime, and second, the design of the FastSkipMixin is
>> -        conservative: it would rather run a few tests that should be skipped
>> -        than skip a few tests that should be run.
>> -
>> -        """
>> -        ret = None
>> -        for api in ['gles3', 'gles2', 'gles1']:
>> -            try:
>> -                raw = self.__call_wflinfo(['--api', api])
>> -            except StopWflinfo as e:
>> -                if e.reason == 'Called':
>> -                    continue
>> -                elif e.reason == 'OSError':
>> -                    break
>> -                raise
>> -            else:
>> -                try:
>> -                    # Yes, search for "OpenGL version string" in GLES
>> -                    # GLES doesn't support patch versions.
>> -                    ret = float(self.__getline(
>> -                        raw.split('\n'),
>> -                        'OpenGL version string').split()[5])
>> -                except (IndexError, ValueError):
>> -                    # This is caused by wlfinfo returning an error
>> -                    pass
>> -                break
>> -        return ret
>> -
>> -    @core.lazy_property
>> -    def glsl_version(self):
>> -        """Calculate the maximum OpenGL Shader Language version."""
>> -        ret = None
>> -        for profile in ['core', 'compat', 'none']:
>> -            try:
>> -                raw = self.__call_wflinfo(
>> -                    ['--verbose', '--api', 'gl', '--profile', profile])
>> -            except StopWflinfo as e:
>> -                if e.reason == 'Called':
>> -                    continue
>> -                elif e.reason == 'OSError':
>> -                    break
>> -                raise
>> -            else:
>> -                try:
>> -                    # GLSL versions are M.mm formatted
>> -                    ret = float(self.__getline(
>> -                        raw.split('\n'),
>> -                        'OpenGL shading language').split()[-1][:4])
>> -                except (IndexError, ValueError):
>> -                    # This is caused by wflinfo returning an error
>> -                    pass
>> -                break
>> -        return ret
>> -
>> -    @core.lazy_property
>> -    def glsl_es_version(self):
>> -        """Calculate the maximum OpenGL ES Shader Language version."""
>> -        ret = None
>> -        for api in ['gles3', 'gles2']:
>> -            try:
>> -                raw = self.__call_wflinfo(['--verbose', '--api', api])
>> -            except StopWflinfo as e:
>> -                if e.reason == 'Called':
>> -                    continue
>> -                elif e.reason == 'OSError':
>> -                    break
>> -                raise
>> -            else:
>> -                try:
>> -                    # GLSL ES version numbering is insane.
>> -                    # For version >= 3 the numbers are 3.00, 3.10, etc.
>> -                    # For version 2, they are 1.0.xx
>> -                    ret = float(self.__getline(
>> -                        raw.split('\n'),
>> -                        'OpenGL shading language').split()[-1][:3])
>> -                except (IndexError, ValueError):
>> -                    # Handle wflinfo internal errors
>> -                    pass
>> -                break
>> -        return ret
>> -
>> -
>>   class FastSkip(object):
>>       """A class for testing OpenGL requirements.
>>
>> @@ -335,7 +70,7 @@ class FastSkip(object):
>>       __slots__ = ['gl_required', 'gl_version', 'gles_version', 'glsl_version',
>>                    'glsl_es_version']
>>
>> -    info = WflInfo()
>> +    info = wflinfo.WflInfo()
>>
>>       def __init__(self, gl_required=None, gl_version=None, gles_version=None,
>>                    glsl_version=None, glsl_es_version=None):
>> diff --git a/framework/wflinfo.py b/framework/wflinfo.py
>> new file mode 100644
>> index 0000000..20ef2e1
>> --- /dev/null
>> +++ b/framework/wflinfo.py
>> @@ -0,0 +1,290 @@
>> +# Copyright (c) 2015-2016 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.
>> +
>> +"""Functions for using the wflinfo utility"""
>> +
>> +import os
>> +import subprocess
>> +
>> +from framework import exceptions, core
>> +from framework.options import OPTIONS
>> +from framework.test import piglit_test
>
> I think it makes sense to put TEST_BIN_DIR in core so that framework doesn't
> depend on test, but I'm content for that to be a follow up patch.

OK, I'll make that change later.  R-b on this patch?

-Brian


>
>> +
>> +
>> +class StopWflinfo(exceptions.PiglitException):
>> +    """Exception called when wlfinfo getter should stop."""
>> +    def __init__(self, reason):
>> +        super(StopWflinfo, self).__init__()
>> +        self.reason = reason
>> +
>> +
>> +def find_wflinfo():
>> +    """Find location of the wflinfo executable."""
>> +    # First check if it's in our piglit bin/ directory
>> +    wflinfo = os.path.join(piglit_test.TEST_BIN_DIR, "wflinfo.exe")
>> +    if os.path.exists(wflinfo):
>> +        return wflinfo
>> +    else:
>> +        # Assume it's in $PATH
>> +        return "wflinfo"
>> +
>> +
>> +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.
>> +
>> +    """
>> +    __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:
>> +                # Get the piglit platform string and, if needed, convert it
>> +                # to something that wflinfo understands.
>> +                platform = OPTIONS.env['PIGLIT_PLATFORM']
>> +                if platform == "mixed_glx_egl":
>> +                    platform = "glx"
>> +
>> +                wflinfo = find_wflinfo()
>> +
>> +                raw = subprocess.check_output(
>> +                    [wflinfo, '--platform', 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
>> +                print("wflinfo utility not found.")
>> +                if e.errno == errno.ENOENT:
>> +                    raise StopWflinfo('OSError')
>> +                raise
>> +        return raw.decode('utf-8')
>> +
>> +    @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
>> +
>> +        # Don't return a set with only WFLINFO_GL_ERROR.
>> +        ret = {e.strip() for e in all_}
>> +        if ret == {'WFLINFO_GL_ERROR'}:
>> +            return set()
>> +        return ret
>> +
>> +    @core.lazy_property
>> +    def gl_version(self):
>> +        """Calculate the maximum opengl version.
>> +
>> +        This will try (in order): core, compat, and finally no profile,
>> +        stopping when it finds a profile. It assumes that most implementations
>> +        will have core and compat as equals, or core as superior to compat in
>> +        terms of support.
>> +
>> +        """
>> +        ret = None
>> +        for profile in ['core', 'compat', 'none']:
>> +            try:
>> +                raw = self.__call_wflinfo(['--api', 'gl', '--profile', profile])
>> +            except StopWflinfo as e:
>> +                if e.reason == 'Called':
>> +                    continue
>> +                elif e.reason == 'OSError':
>> +                    break
>> +                raise
>> +            else:
>> +                try:
>> +                    # Grab the GL version string, trim any release_number values
>> +                    ret = float(self.__getline(
>> +                        raw.split('\n'),
>> +                        'OpenGL version string').split()[3][:3])
>> +                except (IndexError, ValueError):
>> +                    # This is caused by wlfinfo returning an error
>> +                    pass
>> +                break
>> +        return ret
>> +
>> +    @core.lazy_property
>> +    def gles_version(self):
>> +        """Calculate the maximum opengl es version.
>> +
>> +        The design of this function isn't 100% correct. GLES1 and GLES2+ behave
>> +        differently, since 2+ can be silently promoted, but 1 cannot. This
>> +        means that a driver can implement 2, 3, 3.1, etc, but never have 1
>> +        support.
>> +
>> +        I don't think this is a big deal for a couple of reasons. First, piglit
>> +        has a very small set of GLES1 tests, so they shouldn't have big impact
>> +        on runtime, and second, the design of the FastSkipMixin is
>> +        conservative: it would rather run a few tests that should be skipped
>> +        than skip a few tests that should be run.
>> +
>> +        """
>> +        ret = None
>> +        for api in ['gles3', 'gles2', 'gles1']:
>> +            try:
>> +                raw = self.__call_wflinfo(['--api', api])
>> +            except StopWflinfo as e:
>> +                if e.reason == 'Called':
>> +                    continue
>> +                elif e.reason == 'OSError':
>> +                    break
>> +                raise
>> +            else:
>> +                try:
>> +                    # Yes, search for "OpenGL version string" in GLES
>> +                    # GLES doesn't support patch versions.
>> +                    ret = float(self.__getline(
>> +                        raw.split('\n'),
>> +                        'OpenGL version string').split()[5])
>> +                except (IndexError, ValueError):
>> +                    # This is caused by wlfinfo returning an error
>> +                    pass
>> +                break
>> +        return ret
>> +
>> +    @core.lazy_property
>> +    def glsl_version(self):
>> +        """Calculate the maximum OpenGL Shader Language version."""
>> +        ret = None
>> +        for profile in ['core', 'compat', 'none']:
>> +            try:
>> +                raw = self.__call_wflinfo(
>> +                    ['--verbose', '--api', 'gl', '--profile', profile])
>> +            except StopWflinfo as e:
>> +                if e.reason == 'Called':
>> +                    continue
>> +                elif e.reason == 'OSError':
>> +                    break
>> +                raise
>> +            else:
>> +                try:
>> +                    # GLSL versions are M.mm formatted
>> +                    ret = float(self.__getline(
>> +                        raw.split('\n'),
>> +                        'OpenGL shading language').split()[-1][:4])
>> +                except (IndexError, ValueError):
>> +                    # This is caused by wflinfo returning an error
>> +                    pass
>> +                break
>> +        return ret
>> +
>> +    @core.lazy_property
>> +    def glsl_es_version(self):
>> +        """Calculate the maximum OpenGL ES Shader Language version."""
>> +        ret = None
>> +        for api in ['gles3', 'gles2']:
>> +            try:
>> +                raw = self.__call_wflinfo(['--verbose', '--api', api])
>> +            except StopWflinfo as e:
>> +                if e.reason == 'Called':
>> +                    continue
>> +                elif e.reason == 'OSError':
>> +                    break
>> +                raise
>> +            else:
>> +                try:
>> +                    # GLSL ES version numbering is insane.
>> +                    # For version >= 3 the numbers are 3.00, 3.10, etc.
>> +                    # For version 2, they are 1.0.xx
>> +                    ret = float(self.__getline(
>> +                        raw.split('\n'),
>> +                        'OpenGL shading language').split()[-1][:3])
>> +                except (IndexError, ValueError):
>> +                    # Handle wflinfo internal errors
>> +                    pass
>> +                break
>> +        return ret
>> +
>> +
>> diff --git a/tests/all.py b/tests/all.py
>> index 8c9e33d..7f31a2f 100644
>> --- a/tests/all.py
>> +++ b/tests/all.py
>> @@ -13,7 +13,7 @@ import six
>>   from six.moves import range
>>
>>   from framework import grouptools
>> -from framework.test import opengl
>> +from framework import wflinfo
>>   from framework import options
>>   from framework.profile import TestProfile
>>   from framework.driver_classifier import DriverClassifier
>> @@ -257,7 +257,7 @@ profile = TestProfile()  # pylint: disable=invalid-name
>>
>>   shader_tests = collections.defaultdict(list)
>>
>> -wfl_info = opengl.WflInfo()
>> +wfl_info = wflinfo.WflInfo()
>>
>>
>>   # Find and add all shader tests.
>> --
>> 1.9.1
>>
>> _______________________________________________
>> Piglit mailing list
>> Piglit at lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/piglit



More information about the Piglit mailing list