shader_runner: Take spirv_replaces_glsl into account for current_config
[piglit.git] / framework / wflinfo.py
blob8c7da084a8f8c385fc3f95fdcb13c757e7c34f0e
1 # Copyright (c) 2015-2016 Intel Corporation
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to deal
5 # in the Software without restriction, including without limitation the rights
6 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 # copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
10 # The above copyright notice and this permission notice shall be included in
11 # all copies or substantial portions of the Software.
13 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 # SOFTWARE.
21 from __future__ import (
22 absolute_import, division, print_function, unicode_literals
24 import errno
25 import os
26 import subprocess
27 import sys
29 import six
31 from framework import exceptions, core
32 from framework.options import OPTIONS
33 from framework.test import piglit_test
36 class StopWflinfo(exceptions.PiglitException):
37 """Exception called when wlfinfo getter should stop."""
38 def __init__(self, reason):
39 super(StopWflinfo, self).__init__()
40 self.reason = reason
43 class WflInfo(object):
44 """Class representing platform information as provided by wflinfo.
46 The design of this is odd to say the least, it's basically a bag with some
47 lazy property evaluators in it, used to avoid calculating the values
48 provided by wflinfo more than once.
50 The problems:
51 - Needs to be shared with all subclasses
52 - Needs to evaluate only once
53 - cannot evaluate until user sets OPTIONS.env['PIGLIT_PLATFORM']
55 This solves all of that.
57 """
58 __shared_state = {}
59 def __new__(cls, *args, **kwargs):
60 # Implement the borg pattern:
61 # https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/
63 # This is something like a singleton, but much easier to implement
64 self = super(WflInfo, cls).__new__(cls, *args, **kwargs)
65 self.__dict__ = cls.__shared_state
66 return self
68 @staticmethod
69 def __call_wflinfo(opts):
70 """Helper to call wflinfo and reduce code duplication.
72 This catches and handles CalledProcessError and OSError.ernno == 2
73 gracefully: it passes them to allow platforms without a particular
74 gl/gles version or wflinfo (resepctively) to work.
76 Arguments:
77 opts -- arguments to pass to wflinfo other than verbose and platform
79 """
80 with open(os.devnull, 'w') as d:
81 try:
82 # Get the piglit platform string and, if needed, convert it
83 # to something that wflinfo understands.
84 platform = OPTIONS.env['PIGLIT_PLATFORM']
85 if platform == "mixed_glx_egl":
86 platform = "glx"
88 if sys.platform in ['windows', 'cygwin']:
89 bin = 'wflinfo.exe'
90 else:
91 bin = 'wflinfo'
93 cmd = [bin, '--platform', platform] + opts
95 # setup execution environment where we extend the PATH env var
96 # to include the piglit TEST_BIN_DIR
97 new_env = os.environ
98 new_env['PATH'] = ':'.join([piglit_test.TEST_BIN_DIR,
99 os.environ['PATH']])
101 raw = subprocess.check_output(cmd, env=new_env, stderr=d)
103 except subprocess.CalledProcessError:
104 # When we hit this error it usually going to be because we have
105 # an incompatible platform/profile combination
106 raise StopWflinfo('Called')
107 except OSError as e:
108 # If we get a 'no wflinfo' warning then just return
109 print("wflinfo utility not found.", file=sys.stderr)
110 if e.errno == errno.ENOENT:
111 raise StopWflinfo('OSError')
112 raise
113 return raw.decode('utf-8')
115 @staticmethod
116 def __getline(lines, name):
117 """Find a line in a list return it."""
118 for line in lines:
119 if line.startswith(name):
120 return line
121 raise Exception('Unreachable')
123 @core.lazy_property
124 def gl_extensions(self):
125 """Call wflinfo to get opengl extensions.
127 This provides a very conservative set of extensions, it provides every
128 extension from gles1, 2 and 3 and from GL both core and compat profile
129 as a single set. This may let a few tests execute that will still skip
130 manually, but it helps to ensure that this method never skips when it
131 shouldn't.
134 _trim = len('OpenGL extensions: ')
135 all_ = set()
137 def helper(const, vars_):
138 """Helper function to reduce code duplication."""
139 # This is a pretty fragile function but it really does help with
140 # duplication
141 for var in vars_:
142 try:
143 ret = self.__call_wflinfo(const + [var])
144 except StopWflinfo as e:
145 # This means the particular api or profile is unsupported
146 if e.reason == 'Called':
147 continue
148 else:
149 raise
150 all_.update(set(self.__getline(
151 ret.split('\n'), 'OpenGL extensions')[_trim:].split()))
153 try:
154 helper(['--verbose', '--api'], ['gles1', 'gles2', 'gles3'])
155 helper(['--verbose', '--api', 'gl', '--profile'],
156 ['core', 'compat', 'none'])
157 except StopWflinfo as e:
158 # Handle wflinfo not being installed by returning an empty set. This
159 # will essentially make FastSkipMixin a no-op.
160 if e.reason == 'OSError':
161 return set()
162 raise
164 # Don't return a set with only WFLINFO_GL_ERROR.
165 ret = {e.strip() for e in all_}
166 if ret == {'WFLINFO_GL_ERROR'}:
167 return set()
168 return ret
170 @core.lazy_property
171 def gl_version(self):
172 """Calculate the maximum opengl version.
174 This will try (in order): core, compat, and finally no profile,
175 stopping when it finds a profile. It assumes that most implementations
176 will have core and compat as equals, or core as superior to compat in
177 terms of support.
180 ret = None
181 for profile in ['core', 'compat', 'none']:
182 try:
183 raw = self.__call_wflinfo(['--api', 'gl', '--profile', profile])
184 except StopWflinfo as e:
185 if e.reason == 'Called':
186 continue
187 elif e.reason == 'OSError':
188 break
189 raise
190 else:
191 try:
192 # Grab the GL version string, trim any release_number values
193 ret = float(self.__getline(
194 raw.split('\n'),
195 'OpenGL version string').split()[3][:3])
196 except (IndexError, ValueError):
197 # This is caused by wlfinfo returning an error
198 pass
199 break
200 return ret
202 @core.lazy_property
203 def gles_version(self):
204 """Calculate the maximum opengl es version.
206 The design of this function isn't 100% correct. GLES1 and GLES2+ behave
207 differently, since 2+ can be silently promoted, but 1 cannot. This
208 means that a driver can implement 2, 3, 3.1, etc, but never have 1
209 support.
211 I don't think this is a big deal for a couple of reasons. First, piglit
212 has a very small set of GLES1 tests, so they shouldn't have big impact
213 on runtime, and second, the design of the FastSkipMixin is
214 conservative: it would rather run a few tests that should be skipped
215 than skip a few tests that should be run.
218 ret = None
219 for api in ['gles3', 'gles2', 'gles1']:
220 try:
221 raw = self.__call_wflinfo(['--api', api])
222 except StopWflinfo as e:
223 if e.reason == 'Called':
224 continue
225 elif e.reason == 'OSError':
226 break
227 raise
228 else:
229 try:
230 # Yes, search for "OpenGL version string" in GLES
231 # GLES doesn't support patch versions.
232 ret = float(self.__getline(
233 raw.split('\n'),
234 'OpenGL version string').split()[5])
235 except (IndexError, ValueError):
236 # This is caused by wlfinfo returning an error
237 pass
238 break
239 return ret
241 @core.lazy_property
242 def glsl_version(self):
243 """Calculate the maximum OpenGL Shader Language version."""
244 ret = None
245 for profile in ['core', 'compat', 'none']:
246 try:
247 raw = self.__call_wflinfo(
248 ['--verbose', '--api', 'gl', '--profile', profile])
249 except StopWflinfo as e:
250 if e.reason == 'Called':
251 continue
252 elif e.reason == 'OSError':
253 break
254 raise
255 else:
256 try:
257 # GLSL versions are M.mm formatted
258 line = self.__getline(raw.split('\n'), 'OpenGL shading language')
259 ret = float(line.split(":")[1][:5])
260 except (IndexError, ValueError):
261 # This is caused by wflinfo returning an error
262 pass
263 break
264 return ret
266 @core.lazy_property
267 def glsl_es_version(self):
268 """Calculate the maximum OpenGL ES Shader Language version."""
269 ret = None
270 for api in ['gles3', 'gles2']:
271 try:
272 raw = self.__call_wflinfo(['--verbose', '--api', api])
273 except StopWflinfo as e:
274 if e.reason == 'Called':
275 continue
276 elif e.reason == 'OSError':
277 break
278 raise
279 else:
280 try:
281 # GLSL ES version numbering is insane.
282 # For version >= 3 the numbers are 3.00, 3.10, etc.
283 # For version 2, they are 1.0.xx
284 ret = float(self.__getline(
285 raw.split('\n'),
286 'OpenGL shading language').split()[-1][:3])
287 except (IndexError, ValueError):
288 # Handle wflinfo internal errors
289 pass
290 break
291 return ret