[Piglit] [PATCH 3/6] framework: move WflInfo class into new wflinfo.py module
Dylan Baker
dylan at pnwbakers.com
Thu Oct 19 00:39:17 UTC 2017
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.
> +
> +
> +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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: signature
URL: <https://lists.freedesktop.org/archives/piglit/attachments/20171018/51788207/attachment.sig>
More information about the Piglit
mailing list