fbo-mrt-alphatest: Actually require MRTs to be available.
[piglit.git] / framework / core.py
blobfaa9a12a4336a9b18940d9622b6e1a6ece190511
1 # coding=utf-8
2 # Copyright (c) 2014-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 """A collection of various bits that don't seem to belong anywhere else.
24 This is the classic catchall "utils" module from most projects, that for
25 historically reasons is called "core" in piglit.
27 """
29 import configparser
30 import errno
31 import functools
32 import os
33 import subprocess
34 import time
36 from framework import exceptions
38 __all__ = [
39 'PIGLIT_CONFIG',
40 'PLATFORMS',
41 'PiglitConfig',
42 'collect_system_info',
43 'get_option',
44 'parse_listfile',
47 PLATFORMS = ["glx", "x11_egl", "wayland", "gbm", "mixed_glx_egl", "wgl", "surfaceless_egl"]
50 class PiglitConfig(configparser.ConfigParser):
51 """Custom Config parser that provides a few extra helpers."""
52 def __init__(self, *args, **kwargs):
53 super().__init__(*args, **kwargs)
54 self.filename = None
56 def read_file(self, f, source=None):
57 super().read_file(f, source)
58 self.filename = os.path.abspath(source or f.name)
60 def safe_get(self, section, option, fallback=None, **kwargs):
61 """A version of self.get that doesn't raise NoSectionError or
62 NoOptionError.
64 This is equivalent to passing if the option isn't found. It will return
65 None if an error is caught
67 """
68 try:
69 return self.get(section, option, **kwargs)
70 except (configparser.NoOptionError, configparser.NoSectionError):
71 return fallback
73 def required_get(self, section, option, **kwargs):
74 """A version of self.get that raises PiglitFatalError.
76 If self.get returns NoSectionError or NoOptionError then this will
77 raise a PiglitFatalException, aborting the program.
79 """
80 try:
81 return self.get(section, option, **kwargs)
82 except configparser.NoSectionError:
83 raise exceptions.PiglitFatalError(
84 'No Section "{}" in file "{}".\n'
85 'This section is required.'.format(
86 section, self.filename))
87 except configparser.NoOptionError:
88 raise exceptions.PiglitFatalError(
89 'No option "{}" from section "{}" in file "{}".\n'
90 'This option is required.'.format(
91 option, section, self.filename))
94 PIGLIT_CONFIG = PiglitConfig(allow_no_value=True)
97 def get_config(arg=None):
98 if arg:
99 PIGLIT_CONFIG.read_file(arg)
100 else:
101 # Load the piglit.conf. First try looking in the current directory,
102 # then trying the XDG_CONFIG_HOME, then $HOME/.config/, finally try the
103 # piglit root dir
104 for d in ['.',
105 os.environ.get('XDG_CONFIG_HOME',
106 os.path.expandvars('$HOME/.config')),
107 os.path.join(os.path.dirname(__file__), '..')]:
108 try:
109 with open(os.path.join(d, 'piglit.conf'), 'r') as f:
110 PIGLIT_CONFIG.read_file(f)
111 break
112 except IOError:
113 pass
116 def get_option(env_varname, config_option, default=None, required=False):
117 """Query the given environment variable and then piglit.conf for the option.
119 Return the value of the default argument if opt is None.
122 opt = os.environ.get(env_varname, None)
123 if opt is not None:
124 return opt
126 opt = PIGLIT_CONFIG.safe_get(config_option[0], config_option[1])
128 if required and not opt:
129 raise exceptions.PiglitFatalError(
130 'Cannot get env "{}" or conf value "{}:{}"'.format(
131 env_varname, config_option[0], config_option[1]))
132 return opt or default
135 def check_dir(dirname, failifexists=False, handler=None):
136 """Check for the existence of a directory and create it if possible.
138 This function will check for the existence of a directory. If that
139 directory doesn't exist it will try to create it. If the directory does
140 exist than it does one of two things.
141 1) If "failifexists" is False (default): it will just return
142 2) If "failifexists" is True it will raise an PiglitException, it is the
143 job of the caller using failifexists=True to handle this exception
145 Both failifexists and handler can be passed, but failifexists will have
146 precedence.
148 Arguments:
149 dirname -- the name of the directory to check
151 Keyword Arguments:
152 failifexists -- If True and the directory exists then PiglitException will
153 be raised (default: False)
154 handler -- a callable that is passed dirname if the thing to check exists.
157 try:
158 os.stat(dirname)
159 except OSError as e:
160 # If the error is not "no file or directory" or "not a dir", then
161 # either raise an exception, call the handler function, or return
162 if e.errno not in [errno.ENOENT, errno.ENOTDIR]:
163 if failifexists:
164 raise exceptions.PiglitException
165 elif handler is not None:
166 handler(dirname)
168 try:
169 # makedirs is expensive, so check before # calling it.
170 if not os.path.exists(dirname):
171 os.makedirs(dirname)
172 except OSError as e:
173 if e.errno != errno.EEXIST:
174 raise
177 def collect_system_info():
178 """ Get relevant information about the system running piglit
180 This method runs through a list of tuples, where element 1 is the name of
181 the program being run, and element 2 is a command to run (in a form accepted
182 by subprocess.Popen)
185 progs = [('wglinfo', ['wglinfo']),
186 ('glxinfo', ['glxinfo']),
187 ('uname', ['uname', '-a']),
188 ('clinfo', ['clinfo']),
189 ('lspci', ['lspci', '-nn'])]
191 result = {}
193 for name, command in progs:
194 try:
195 out = subprocess.check_output(command, stderr=subprocess.STDOUT)
196 result[name] = out.decode('utf-8')
197 except OSError as e:
198 # If we get the 'no file or directory' error then pass, that means
199 # that the binary isn't installed or isn't relevant to the system.
200 # If it's any other OSError, then raise
201 if e.errno != errno.ENOENT and e.errno != errno.EACCES:
202 raise
203 except subprocess.CalledProcessError:
204 # If the binary is installed by doesn't work on the window system
205 # (glxinfo) it will raise this error. go on
206 pass
208 return result
211 def parse_listfile(filename):
213 Parses a newline-seperated list in a text file and returns a python list
214 object. It will expand tildes on Unix-like system to the users home
215 directory.
217 ex file.txt:
218 ~/tests1
219 ~/tests2/main
220 /tmp/test3
222 returns:
223 ['/home/user/tests1', '/home/users/tests2/main', '/tmp/test3']
225 with open(filename, 'r') as file:
226 return [os.path.expanduser(i.strip()) for i in file.readlines()]
229 class lazy_property(object): # pylint: disable=invalid-name,too-few-public-methods
230 """Descriptor that replaces the function it wraps with the value generated.
232 This makes a property that is truly lazy, it is calculated once on demand,
233 and stored. This makes this very useful for values that you might want to
234 calculate and reuse, but they cannot change.
236 This works by very cleverly shadowing itself with the calculated value. It
237 adds the value to the instance, which pushes itself up the MRO and will
238 never be queried again.
241 def __init__(self, func):
242 self.__func = func
244 def __get__(self, instance, cls):
245 value = self.__func(instance)
246 setattr(instance, self.__func.__name__, value)
247 return value
250 def timer_ms(func):
251 """A decorator function that measures the runtime of a given function in
252 milliseconds and prints the result.
253 The function name and runtime in ms will be printed in the format
254 "Finished [function name] in [runtime] ms".
257 @functools.wraps(func)
258 def wrapper(*args, **kwargs):
259 start_time: float = time.monotonic()
260 value = func(*args, **kwargs)
261 end_time: float = time.monotonic()
262 run_time: float = end_time - start_time
263 print(f"Finished {func.__name__} in {run_time.__round__(6) * 1000} ms")
264 return value
266 return wrapper