ext_gpu_shader4: add compiler tests for everything
[piglit.git] / framework / wflinfo.py
bloba6bb9e85532fe608507550a56773f80b595316e9
1 # coding=utf-8
2 # Copyright (c) 2015-2016 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 from __future__ import (
23 absolute_import, division, print_function, unicode_literals
25 import errno
26 import os
27 import subprocess
28 import sys
29 import threading
31 import six
33 from framework import exceptions
34 from framework.core import lazy_property
35 from framework.options import OPTIONS
36 # from framework.test import piglit_test
39 class StopWflinfo(exceptions.PiglitException):
40 """Exception called when wlfinfo getter should stop."""
41 def __init__(self, reason):
42 super(StopWflinfo, self).__init__()
43 self.reason = reason
46 class ProfileInfo(object):
47 """Information about a single profile (core, compat, es1, es2, etc)."""
49 def __init__(self, shader_version, language_version, extensions):
50 self.shader_version = shader_version
51 self.api_version = language_version
52 self.extensions = extensions
55 class WflInfo(object):
56 """Class representing platform information as provided by wflinfo.
58 The design of this is odd to say the least, it's basically a bag with some
59 lazy property evaluators in it, used to avoid calculating the values
60 provided by wflinfo more than once.
62 The problems:
63 - Needs to be shared with all subclasses
64 - Needs to evaluate only once
65 - cannot evaluate until user sets OPTIONS.env['PIGLIT_PLATFORM']
67 This solves all of that.
69 """
70 __shared_state = {}
71 __core_init = False
72 __core_lock = threading.Lock()
73 __compat_init = False
74 __compat_lock = threading.Lock()
75 __es1_init = False
76 __es1_lock = threading.Lock()
77 __es2_init = False
78 __es2_lock = threading.Lock()
80 def __new__(cls, *args, **kwargs):
81 # Implement the borg pattern:
82 # https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/
84 # This is something like a singleton, but much easier to implement
85 self = super(WflInfo, cls).__new__(cls, *args, **kwargs)
86 self.__dict__ = cls.__shared_state
87 return self
89 @staticmethod
90 def __call_wflinfo(opts):
91 """Helper to call wflinfo and reduce code duplication.
93 This catches and handles CalledProcessError and OSError.ernno == 2
94 gracefully: it passes them to allow platforms without a particular
95 gl/gles version or wflinfo (resepctively) to work.
97 Arguments:
98 opts -- arguments to pass to wflinfo other than verbose and platform
101 with open(os.devnull, 'w') as d:
102 try:
103 # Get the piglit platform string and, if needed, convert it
104 # to something that wflinfo understands.
105 platform = OPTIONS.env['PIGLIT_PLATFORM']
106 if platform == "mixed_glx_egl":
107 platform = "glx"
109 if sys.platform in ['windows', 'cygwin']:
110 bin = 'wflinfo.exe'
111 else:
112 bin = 'wflinfo'
114 cmd = [bin, '--platform', platform] + opts
116 # setup execution environment where we extend the PATH env var
117 # to include the piglit TEST_BIN_DIR
118 new_env = os.environ.copy()
119 # new_env['PATH'] = ':'.join([piglit_test.TEST_BIN_DIR,
120 # os.environ['PATH']])
122 raw = subprocess.check_output(cmd, env=new_env, stderr=d)
124 except subprocess.CalledProcessError:
125 # When we hit this error it usually going to be because we have
126 # an incompatible platform/profile combination
127 raise StopWflinfo('Called')
128 except OSError as e:
129 # If we get a 'no wflinfo' warning then just return
130 print("wflinfo utility not found.", file=sys.stderr)
131 if e.errno == errno.ENOENT:
132 raise StopWflinfo('OSError')
133 raise
134 return raw.decode('utf-8')
136 @staticmethod
137 def __getline(lines, name):
138 """Find a line in a list return it."""
139 for line in lines:
140 if line.startswith(name):
141 return line
142 raise Exception('Unreachable')
144 def __get_shader_version(self, profile):
145 """Calculate the maximum OpenGL Shader Language version."""
146 ret = 0.0
147 if profile in ['core', 'compat', 'none']:
148 try:
149 raw = self.__call_wflinfo(
150 ['--verbose', '--api', 'gl', '--profile', profile])
151 except StopWflinfo as e:
152 if e.reason not in ['Called', 'OSError']:
153 raise
154 else:
155 try:
156 # GLSL versions are M.mm formatted
157 line = self.__getline(raw.split('\n'), 'OpenGL shading language')
158 ret = float(line.split(":")[1][:5])
159 except (IndexError, ValueError):
160 # This is caused by wflinfo returning an error
161 pass
162 elif profile in ['gles2', 'gles3']:
163 try:
164 raw = self.__call_wflinfo(['--verbose', '--api', profile])
165 except StopWflinfo as e:
166 if e.reason not in ['Called', 'OSError']:
167 raise
168 else:
169 try:
170 # GLSL ES version numbering is insane.
171 # For version >= 3 the numbers are 3.00, 3.10, etc.
172 # For version 2, they are 1.0.xx
173 ret = float(self.__getline(
174 raw.split('\n'),
175 'OpenGL shading language').split()[-1][:3])
176 except (IndexError, ValueError):
177 # Handle wflinfo internal errors
178 pass
179 return ret
181 def __get_language_version(self, profile):
182 ret = 0.0
183 if profile in ['core', 'compat', 'none']:
184 try:
185 raw = self.__call_wflinfo(['--api', 'gl', '--profile', profile])
186 except StopWflinfo as e:
187 if e.reason not in ['Called', 'OSError']:
188 raise
189 else:
190 try:
191 # Grab the GL version string, trim any release_number values
192 ret = float(self.__getline(
193 raw.split('\n'),
194 'OpenGL version string').split()[3][:3])
195 except (IndexError, ValueError):
196 # This is caused by wlfinfo returning an error
197 pass
198 else:
199 try:
200 raw = self.__call_wflinfo(['--api', profile])
201 except StopWflinfo as e:
202 if e.reason not in ['Called', 'OSError']:
203 raise
204 else:
205 try:
206 # Yes, search for "OpenGL version string" in GLES
207 # GLES doesn't support patch versions.
208 ret = float(self.__getline(
209 raw.split('\n'),
210 'OpenGL version string').split()[5])
211 except (IndexError, ValueError):
212 # This is caused by wlfinfo returning an error
213 pass
214 return ret
216 def __get_extensions(self, profile):
217 """Call wflinfo to get opengl extensions.
219 This provides a very conservative set of extensions, it provides every
220 extension from gles1, 2 and 3 and from GL both core and compat profile
221 as a single set. This may let a few tests execute that will still skip
222 manually, but it helps to ensure that this method never skips when it
223 shouldn't.
226 _trim = len('OpenGL extensions: ')
227 all_ = set()
229 def helper(args):
230 """Helper function to reduce code duplication."""
231 # This is a pretty fragile function but it really does help with
232 # duplication
233 ret = self.__call_wflinfo(args)
234 all_.update(set(self.__getline(
235 ret.split('\n'), 'OpenGL extensions')[_trim:].split()))
237 try:
238 if profile in ['core', 'compat', 'none']:
239 helper(['--verbose', '--api', 'gl', '--profile', profile])
240 else:
241 helper(['--verbose', '--api', profile])
242 except StopWflinfo as e:
243 # Handle wflinfo not being installed by returning an empty set. This
244 # will essentially make FastSkipMixin a no-op.
245 if e.reason in ['OSError', 'Called']:
246 return set()
247 raise
249 # Don't return a set with only WFLINFO_GL_ERROR.
250 ret = {e.strip() for e in all_}
251 if ret == {'WFLINFO_GL_ERROR'}:
252 return set()
253 return ret
255 def __build_info(self, profile):
256 return ProfileInfo(
257 self.__get_shader_version(profile),
258 self.__get_language_version(profile),
259 self.__get_extensions(profile)
262 @lazy_property
263 def core(self):
264 with self.__core_lock:
265 if not self.__core_init:
266 self.__core_init = True
267 return self.__build_info('core')
268 return self.core
270 @lazy_property
271 def compat(self):
272 with self.__compat_lock:
273 if not self.__compat_init:
274 self.__compat_init = True
275 comp = self.__build_info('compat')
276 if comp.api_version == 0.0:
277 # In this case there are not compat profiles, try agian
278 # with a "legacy" profile, which could be promoted to
279 # compat
280 return self.__build_info('none')
281 return comp
282 return self.compat
284 @lazy_property
285 def es1(self):
286 with self.__es1_lock:
287 if not self.__es1_init:
288 self.__es1_init = True
289 return self.__build_info('gles1')
290 return self.es1
292 @lazy_property
293 def es2(self):
294 with self.__es2_lock:
295 if not self.__es2_init:
296 self.__es2_init = True
297 return self.__build_info('gles2')
298 return self.es2
300 @lazy_property
301 def es3(self):
302 return self.es2