[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