README: add Vulkan into the generic description
[piglit.git] / framework / wflinfo.py
blob5058a01844183e080aa3c46d16b69df7e9461a1b
1 # coding=utf-8
2 # Copyright (c) 2015-2016, 2019 Intel Corporation
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 # SOFTWARE.
22 import errno
23 import os
24 import subprocess
25 import sys
26 import threading
28 from framework import exceptions
29 from framework.core import lazy_property
30 from framework.options import OPTIONS
31 # from framework.test import piglit_test
34 class StopWflinfo(exceptions.PiglitException):
35 """Exception called when wlfinfo getter should stop."""
36 def __init__(self, reason):
37 super(StopWflinfo, self).__init__()
38 self.reason = reason
41 class ProfileInfo(object):
42 """Information about a single profile (core, compat, es1, es2, etc)."""
44 def __init__(self, shader_version, language_version, extensions):
45 self.shader_version = shader_version
46 self.api_version = language_version
47 self.extensions = extensions
50 class WflInfo(object):
51 """Class representing platform information as provided by wflinfo.
53 The design of this is odd to say the least, it's basically a bag with some
54 lazy property evaluators in it, used to avoid calculating the values
55 provided by wflinfo more than once.
57 The problems:
58 - Needs to be shared with all subclasses
59 - Needs to evaluate only once
60 - cannot evaluate until user sets OPTIONS.env['PIGLIT_PLATFORM']
62 This solves all of that.
64 """
65 __shared_state = {}
66 __core_init = False
67 __core_lock = threading.Lock()
68 __compat_init = False
69 __compat_lock = threading.Lock()
70 __es1_init = False
71 __es1_lock = threading.Lock()
72 __es2_init = False
73 __es2_lock = threading.Lock()
75 def __new__(cls, *args, **kwargs):
76 # Implement the borg pattern:
77 # https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/
79 # This is something like a singleton, but much easier to implement
80 self = super(WflInfo, cls).__new__(cls, *args, **kwargs)
81 self.__dict__ = cls.__shared_state
82 return self
84 @staticmethod
85 def __call_wflinfo(opts):
86 """Helper to call wflinfo and reduce code duplication.
88 This catches and handles CalledProcessError and OSError.ernno == 2
89 gracefully: it passes them to allow platforms without a particular
90 gl/gles version or wflinfo (resepctively) to work.
92 Arguments:
93 opts -- arguments to pass to wflinfo other than verbose and platform
95 """
96 with open(os.devnull, 'w') as d:
97 try:
98 # Get the piglit platform string and, if needed, convert it
99 # to something that wflinfo understands.
100 platform = OPTIONS.env['PIGLIT_PLATFORM']
101 if platform == "mixed_glx_egl":
102 platform = "glx"
104 if sys.platform in ['windows', 'cygwin']:
105 bin = 'wflinfo.exe'
106 else:
107 bin = 'wflinfo'
109 cmd = [bin, '--platform', platform] + opts
111 # setup execution environment where we extend the PATH env var
112 # to include the piglit TEST_BIN_DIR
113 new_env = os.environ.copy()
114 # new_env['PATH'] = ':'.join([piglit_test.TEST_BIN_DIR,
115 # os.environ['PATH']])
117 raw = subprocess.check_output(cmd, env=new_env, stderr=d)
119 except subprocess.CalledProcessError:
120 # When we hit this error it usually going to be because we have
121 # an incompatible platform/profile combination
122 raise StopWflinfo('Called')
123 except OSError as e:
124 # If we get a 'no wflinfo' warning then just return
125 print("wflinfo utility not found.", file=sys.stderr)
126 if e.errno == errno.ENOENT:
127 raise StopWflinfo('OSError')
128 raise
129 return raw.decode('utf-8')
131 @staticmethod
132 def __getline(lines, name):
133 """Find a line in a list return it."""
134 for line in lines:
135 if line.startswith(name):
136 return line
137 raise Exception('Unreachable')
139 def __get_shader_version(self, profile):
140 """Calculate the maximum OpenGL Shader Language version."""
141 ret = 0.0
142 if profile in ['core', 'compat', 'none']:
143 try:
144 raw = self.__call_wflinfo(
145 ['--verbose', '--api', 'gl', '--profile', profile])
146 except StopWflinfo as e:
147 if e.reason not in ['Called', 'OSError']:
148 raise
149 else:
150 try:
151 # GLSL versions are M.mm formatted
152 line = self.__getline(raw.split('\n'), 'OpenGL shading language')
153 ret = float(line.split(":")[1][:5])
154 except (IndexError, ValueError):
155 # This is caused by wflinfo returning an error
156 pass
157 elif profile in ['gles2', 'gles3']:
158 try:
159 raw = self.__call_wflinfo(['--verbose', '--api', profile])
160 except StopWflinfo as e:
161 if e.reason not in ['Called', 'OSError']:
162 raise
163 else:
164 try:
165 # GLSL ES version numbering is insane.
166 # For version >= 3 the numbers are 3.00, 3.10, etc.
167 # For version 2, they are 1.0.xx
168 ret = float(self.__getline(
169 raw.split('\n'),
170 'OpenGL shading language').split()[-1][:3])
171 except (IndexError, ValueError):
172 # Handle wflinfo internal errors
173 pass
174 return ret
176 def __get_language_version(self, profile):
177 ret = 0.0
178 if profile in ['core', 'compat', 'none']:
179 try:
180 raw = self.__call_wflinfo(['--api', 'gl', '--profile', profile])
181 except StopWflinfo as e:
182 if e.reason not in ['Called', 'OSError']:
183 raise
184 else:
185 try:
186 # Grab the GL version string, trim any release_number values
187 ret = float(self.__getline(
188 raw.split('\n'),
189 'OpenGL version string').split()[3][:3])
190 except (IndexError, ValueError):
191 # This is caused by wlfinfo returning an error
192 pass
193 else:
194 try:
195 raw = self.__call_wflinfo(['--api', profile])
196 except StopWflinfo as e:
197 if e.reason not in ['Called', 'OSError']:
198 raise
199 else:
200 try:
201 # Yes, search for "OpenGL version string" in GLES
202 # GLES doesn't support patch versions.
203 ret = float(self.__getline(
204 raw.split('\n'),
205 'OpenGL version string').split()[5])
206 except (IndexError, ValueError):
207 # This is caused by wlfinfo returning an error
208 pass
209 return ret
211 def __get_extensions(self, profile):
212 """Call wflinfo to get opengl extensions.
214 This provides a very conservative set of extensions, it provides every
215 extension from gles1, 2 and 3 and from GL both core and compat profile
216 as a single set. This may let a few tests execute that will still skip
217 manually, but it helps to ensure that this method never skips when it
218 shouldn't.
221 _trim = len('OpenGL extensions: ')
222 all_ = set()
224 def helper(args):
225 """Helper function to reduce code duplication."""
226 # This is a pretty fragile function but it really does help with
227 # duplication
228 ret = self.__call_wflinfo(args)
229 all_.update(set(self.__getline(
230 ret.split('\n'), 'OpenGL extensions')[_trim:].split()))
232 try:
233 if profile in ['core', 'compat', 'none']:
234 helper(['--verbose', '--api', 'gl', '--profile', profile])
235 else:
236 helper(['--verbose', '--api', profile])
237 except StopWflinfo as e:
238 # Handle wflinfo not being installed by returning an empty set. This
239 # will essentially make FastSkipMixin a no-op.
240 if e.reason in ['OSError', 'Called']:
241 return set()
242 raise
244 # Don't return a set with only WFLINFO_GL_ERROR.
245 ret = {e.strip() for e in all_}
246 if ret == {'WFLINFO_GL_ERROR'}:
247 return set()
248 return ret
250 def __build_info(self, profile):
251 return ProfileInfo(
252 self.__get_shader_version(profile),
253 self.__get_language_version(profile),
254 self.__get_extensions(profile)
257 @lazy_property
258 def core(self):
259 with self.__core_lock:
260 if not self.__core_init:
261 self.__core_init = True
262 return self.__build_info('core')
263 return self.core
265 @lazy_property
266 def compat(self):
267 with self.__compat_lock:
268 if not self.__compat_init:
269 self.__compat_init = True
270 comp = self.__build_info('compat')
271 if comp.api_version == 0.0:
272 # In this case there are not compat profiles, try again
273 # with a "legacy" profile, which could be promoted to
274 # compat
275 return self.__build_info('none')
276 return comp
277 return self.compat
279 @lazy_property
280 def es1(self):
281 with self.__es1_lock:
282 if not self.__es1_init:
283 self.__es1_init = True
284 return self.__build_info('gles1')
285 return self.es1
287 @lazy_property
288 def es2(self):
289 with self.__es2_lock:
290 if not self.__es2_init:
291 self.__es2_init = True
292 return self.__build_info('gles2')
293 return self.es2
295 @lazy_property
296 def es3(self):
297 return self.es2