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 + + +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@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/piglit