1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
# 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:
#
# 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 AUTHOR(S) 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.
""" Provides Profiles for test groups
Each set of tests, both native Piglit profiles and external suite integration,
are represented by a TestProfile or a TestProfile derived object.
"""
from __future__ import print_function
import os
import sys
import multiprocessing
import multiprocessing.dummy
import importlib
from framework.dmesg import get_dmesg
from framework.log import LogManager
import framework.exectest
__all__ = [
'TestProfile',
'load_test_profile',
'merge_test_profiles'
]
class TestProfile(object):
""" Class that holds a list of tests for execution
This class provides a container for storing tests in either a nested
dictionary structure (deprecated), or a flat dictionary structure with '/'
delimited groups.
Once a TestProfile object is created tests can be added to the test_list
name as a key/value pair, the key should be a fully qualified name for the
test, including it's group hierarchy and should be '/' delimited, with no
leading or trailing '/', the value should be an exectest.Test derived
object.
When the test list is filled calling TestProfile.run() will set the
execution of these tests off, and will flatten the nested group hierarchy
of self.tests and merge it with self.test_list
"""
def __init__(self):
# Self.tests is deprecated, see above
self.tests = {}
self.test_list = {}
self.filters = []
# Sets a default of a Dummy
self._dmesg = None
self.dmesg = False
self.results_dir = None
@property
def dmesg(self):
""" Return dmesg """
return self._dmesg
@dmesg.setter
def dmesg(self, not_dummy):
""" Set dmesg
Arguments:
not_dummy -- if Truthy dmesg will try to get a PosixDmesg, if Falsy it
will get a DummyDmesg
"""
self._dmesg = get_dmesg(not_dummy)
def _flatten_group_hierarchy(self):
""" Flatten nested dictionary structure
Convert Piglit's old hierarchical Group() structure into a flat
dictionary mapping from fully qualified test names to "Test" objects.
For example,
self.tests['spec']['glsl-1.30']['preprocessor']['compiler']['void.frag']
would become:
self.test_list['spec/glsl-1.30/preprocessor/compiler/void.frag']
"""
def f(prefix, group, test_dict):
""" Recursively flatter nested dictionary tree """
for key, value in group.iteritems():
fullkey = os.path.join(prefix, key)
if isinstance(value, dict):
f(fullkey, value, test_dict)
else:
test_dict[fullkey] = value
f('', self.tests, self.test_list)
# Clear out the old Group()
self.tests = {}
def _prepare_test_list(self, opts):
""" Prepare tests for running
Flattens the nested group hierarchy into a flat dictionary using '/'
delimited groups by calling self.flatten_group_hierarchy(), then
runs it's own filters plus the filters in the self.filters name
Arguments:
opts - a core.Options instance
"""
self._flatten_group_hierarchy()
def matches_any_regexp(x, re_list):
return any(r.search(x) for r in re_list)
# The extra argument is needed to match check_all's API
def test_matches(path, test):
"""Filter for user-specified restrictions"""
return ((not opts.filter or matches_any_regexp(path, opts.filter))
and not path in opts.exclude_tests and
not matches_any_regexp(path, opts.exclude_filter))
filters = self.filters + [test_matches]
def check_all(item):
""" Checks group and test name against all filters """
path, test = item
for f in filters:
if not f(path, test):
return False
return True
# Filter out unwanted tests
self.test_list = dict(item for item in self.test_list.iteritems()
if check_all(item))
def _pre_run_hook(self):
""" Hook executed at the start of TestProfile.run
To make use of this hook one will need to subclass TestProfile, and
set this to do something, as be default it will no-op
"""
pass
def _post_run_hook(self):
""" Hook executed at the end of TestProfile.run
To make use of this hook one will need to subclass TestProfile, and
set this to do something, as be default it will no-op
"""
pass
def run(self, opts, logger, backend):
""" Runs all tests using Thread pool
When called this method will flatten out self.tests into
self.test_list, then will prepare a logger, pass opts to the Test
class, and begin executing tests through it's Thread pools.
Based on the value of opts.concurrent it will either run all the tests
concurrently, all serially, or first the thread safe tests then the
serial tests.
Finally it will print a final summary of the tests
Arguments:
opts -- a core.Options instance
backend -- a results.Backend derived instance
"""
self._pre_run_hook()
framework.exectest.Test.OPTS = opts
chunksize = 1
self._prepare_test_list(opts)
log = LogManager(logger, len(self.test_list))
def test(pair):
""" Function to call test.execute from .map
Adds opts which are needed by Test.execute()
"""
name, test = pair
test.execute(name, log.get(), self.dmesg)
backend.write_test(name, test.result)
def run_threads(pool, testlist):
""" Open a pool, close it, and join it """
pool.imap(test, testlist, chunksize)
pool.close()
pool.join()
# Multiprocessing.dummy is a wrapper around Threading that provides a
# multiprocessing compatible API
#
# The default value of pool is the number of virtual processor cores
single = multiprocessing.dummy.Pool(1)
multi = multiprocessing.dummy.Pool()
if opts.concurrent == "all":
run_threads(multi, self.test_list.iteritems())
elif opts.concurrent == "none":
run_threads(single, self.test_list.iteritems())
else:
# Filter and return only thread safe tests to the threaded pool
run_threads(multi, (x for x in self.test_list.iteritems()
if x[1].run_concurrent))
# Filter and return the non thread safe tests to the single pool
run_threads(single, (x for x in self.test_list.iteritems()
if not x[1].run_concurrent))
log.get().summary()
self._post_run_hook()
def filter_tests(self, function):
"""Filter out tests that return false from the supplied function
Arguments:
function -- a callable that takes two parameters: path, test and
returns whether the test should be included in the test
run or not.
"""
self.filters.append(function)
def update(self, *profiles):
""" Updates the contents of this TestProfile instance with another
This method overwrites key:value pairs in self with those in the
provided profiles argument. This allows multiple TestProfiles to be
called in the same run; which could be used to run piglit and external
suites at the same time.
Arguments:
profiles -- one or more TestProfile-like objects to be merged.
"""
for profile in profiles:
self.tests.update(profile.tests)
self.test_list.update(profile.test_list)
def load_test_profile(filename):
""" Load a python module and return it's profile attribute
All of the python test files provide a profile attribute which is a
TestProfile instance. This loads that module and returns it or raises an
error.
This method doesn't care about file extensions as a way to be backwards
compatible with script wrapping piglit. 'tests/quick', 'tests/quick.tests',
and 'tests/quick.py' are all equally valid for filename
Arguments:
filename -- the name of a python module to get a 'profile' from
"""
mod = importlib.import_module('tests.{0}'.format(
os.path.splitext(os.path.basename(filename))[0]))
try:
return mod.profile
except AttributeError:
print("Error: There is not profile attribute in module {0}."
"Did you specify the right file?".format(filename))
sys.exit(1)
def merge_test_profiles(profiles):
""" Helper for loading and merging TestProfile instances
Takes paths to test profiles as arguments and returns a single merged
TestProfile instance.
Arguments:
profiles -- a list of one or more paths to profile files.
"""
profile = load_test_profile(profiles.pop())
for p in profiles:
profile.update(load_test_profile(p))
return profile
|