This solves a dependency loop between core and exectest, laying the groundwork for future improvements to the exectest module.
Signed-off-by: Dylan Baker <[email protected]> --- framework/core.py | 181 -------------------------------- framework/profile.py | 219 +++++++++++++++++++++++++++++++++++++++ framework/tests/core_tests.py | 4 +- framework/tests/profile_tests.py | 28 +++++ piglit-resume.py | 3 +- piglit-run.py | 3 +- tests/all.py | 2 +- tests/cl.py | 2 +- tests/es3conform.py | 2 +- tests/igt.py | 3 +- tests/oglconform.py | 2 +- tests/sanity.py | 2 +- 12 files changed, 260 insertions(+), 191 deletions(-) create mode 100644 framework/profile.py create mode 100644 framework/tests/profile_tests.py diff --git a/framework/core.py b/framework/core.py index cfe3f78..3979499 100644 --- a/framework/core.py +++ b/framework/core.py @@ -399,187 +399,6 @@ class Environment: return result -class TestProfile(object): - def __init__(self): - self.tests = {} - self.test_list = {} - self.filters = [] - # Sets a default of a Dummy - self.dmesg = False - - @property - def dmesg(self): - """ Return dmesg """ - return self.__dmesg - - @dmesg.setter - def dmesg(self, not_dummy): - """ Set dmesg - - Argumnts: - 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): - ''' - Convert Piglit's old hierarchical {} structure into a flat - dictionary mapping from fully qualified test names to "Test" objects. - - For example, - tests['spec']['glsl-1.30']['preprocessor']['compiler']['void.frag'] - would become: - test_list['spec/glsl-1.30/preprocessor/compiler/void.frag'] - ''' - - def f(prefix, group, test_dict): - for key in group: - fullkey = key if prefix == '' else os.path.join(prefix, key) - if isinstance(group[key], dict): - f(fullkey, group[key], test_dict) - else: - test_dict[fullkey] = group[key] - f('', self.tests, self.test_list) - # Clear out the old {} - self.tests = {} - - def prepare_test_list(self, env): - self.flatten_group_hierarchy() - - def matches_any_regexp(x, re_list): - return True in map(lambda r: r.search(x) is not None, re_list) - - def test_matches(path, test): - """Filter for user-specified restrictions""" - return ((not env.filter or matches_any_regexp(path, env.filter)) - and not path in env.exclude_tests and - not matches_any_regexp(path, env.exclude_filter)) - - filters = self.filters + [test_matches] - def check_all(item): - 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 run(self, env, json_writer): - ''' - Schedule all tests in profile for execution. - - See ``Test.schedule`` and ``Test.run``. - ''' - - self.prepare_test_list(env) - log = Log(len(self.test_list), env.verbose) - - def test(pair): - """ Function to call test.execute from .map - - adds env and json_writer which are needed by Test.execute() - - """ - name, test = pair - test.execute(env, name, log, json_writer, self.dmesg) - - # 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() - chunksize = 1 - - if env.concurrent == "all": - multi.imap(test, self.test_list.iteritems(), chunksize) - elif env.concurrent == "none": - single.imap(test, self.test_list.iteritems(), chunksize) - else: - # Filter and return only thread safe tests to the threaded pool - multi.imap(test, (x for x in self.test_list.iteritems() if - x[1].run_concurrent), chunksize) - # Filter and return the non thread safe tests to the single pool - single.imap(test, (x for x in self.test_list.iteritems() if not - x[1].run_concurrent), chunksize) - - # Close and join the pools - # If we don't close and the join the pools the script will exit before - # the pools finish running - multi.close() - single.close() - multi.join() - single.join() - - log.summary() - - 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 loadTestProfile(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. - - """ - 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 = loadTestProfile(profiles.pop()) - for p in profiles: - profile.update(loadTestProfile(p)) - return profile - - def load_results(filename): """ Loader function for TestrunResult class diff --git a/framework/profile.py b/framework/profile.py new file mode 100644 index 0000000..b76907c --- /dev/null +++ b/framework/profile.py @@ -0,0 +1,219 @@ +# 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 Log + + + +class TestProfile(object): + def __init__(self): + self.tests = {} + self.test_list = {} + self.filters = [] + # Sets a default of a Dummy + self.dmesg = False + + @property + def dmesg(self): + """ Return dmesg """ + return self.__dmesg + + @dmesg.setter + def dmesg(self, not_dummy): + """ Set dmesg + + Argumnts: + 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): + ''' + Convert Piglit's old hierarchical Group() structure into a flat + dictionary mapping from fully qualified test names to "Test" objects. + + For example, + tests['spec']['glsl-1.30']['preprocessor']['compiler']['void.frag'] + would become: + test_list['spec/glsl-1.30/preprocessor/compiler/void.frag'] + ''' + + def f(prefix, group, test_dict): + for key in group: + fullkey = key if prefix == '' else os.path.join(prefix, key) + if isinstance(group[key], dict): + f(fullkey, group[key], test_dict) + else: + test_dict[fullkey] = group[key] + f('', self.tests, self.test_list) + # Clear out the old Group() + self.tests = {} + + def prepare_test_list(self, env): + self.flatten_group_hierarchy() + + def matches_any_regexp(x, re_list): + return True in map(lambda r: r.search(x) is not None, re_list) + + def test_matches(path, test): + """Filter for user-specified restrictions""" + return ((not env.filter or matches_any_regexp(path, env.filter)) + and not path in env.exclude_tests and + not matches_any_regexp(path, env.exclude_filter)) + + filters = self.filters + [test_matches] + def check_all(item): + 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 run(self, env, json_writer): + ''' + Schedule all tests in profile for execution. + + See ``Test.schedule`` and ``Test.run``. + ''' + + self.prepare_test_list(env) + log = Log(len(self.test_list), env.verbose) + + def test(pair): + """ Function to call test.execute from .map + + adds env and json_writer which are needed by Test.execute() + + """ + name, test = pair + test.execute(env, name, log, json_writer, self.dmesg) + + # 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() + chunksize = 1 + + if env.concurrent == "all": + multi.imap(test, self.test_list.iteritems(), chunksize) + elif env.concurrent == "none": + single.imap(test, self.test_list.iteritems(), chunksize) + else: + # Filter and return only thread safe tests to the threaded pool + multi.imap(test, (x for x in self.test_list.iteritems() if + x[1].run_concurrent), chunksize) + # Filter and return the non thread safe tests to the single pool + single.imap(test, (x for x in self.test_list.iteritems() if not + x[1].run_concurrent), chunksize) + + # Close and join the pools + # If we don't close and the join the pools the script will exit before + # the pools finish running + multi.close() + single.close() + multi.join() + single.join() + + log.summary() + + 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 loadTestProfile(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. + + """ + 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 = loadTestProfile(profiles.pop()) + for p in profiles: + profile.update(loadTestProfile(p)) + return profile diff --git a/framework/tests/core_tests.py b/framework/tests/core_tests.py index e2ef23d..44462ce 100644 --- a/framework/tests/core_tests.py +++ b/framework/tests/core_tests.py @@ -49,8 +49,8 @@ def test_generate_initialize(): """ yieldable = check_initialize - for target in [core.TestProfile, core.Environment, core.TestrunResult, - core.TestResult, core.PiglitJSONEncoder]: + for target in [core.Environment, core.TestrunResult, core.TestResult, + core.PiglitJSONEncoder]: yieldable.description = "Test that {} initializes".format( target.__name__) yield yieldable, target diff --git a/framework/tests/profile_tests.py b/framework/tests/profile_tests.py new file mode 100644 index 0000000..c5bdf34 --- /dev/null +++ b/framework/tests/profile_tests.py @@ -0,0 +1,28 @@ +# Copyright (c) 2014 Intel Corporation + +# 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: + +# The above copyright notice and 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 +# AUTHORS OR COPYRIGHT HOLDERS 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 test for the framework.profile modules """ + +import framework.profile as profile + + +def test_initialize_testprofile(): + """ TestProfile initializes """ + profile.TestProfile() diff --git a/piglit-resume.py b/piglit-resume.py index 09d0664..590abf0 100755 --- a/piglit-resume.py +++ b/piglit-resume.py @@ -28,6 +28,7 @@ import os.path as path import argparse import framework.core as core +import framework.profile def main(): @@ -76,7 +77,7 @@ def main(): json_writer.write_dict_item(key, value) env.exclude_tests.add(key) - profile = core.merge_test_profiles(results.options['profile']) + profile = framework.profile.merge_test_profiles(results.options['profile']) if env.dmesg: profile.dmesg = env.dmesg diff --git a/piglit-run.py b/piglit-run.py index 672c057..b4b00bc 100755 --- a/piglit-run.py +++ b/piglit-run.py @@ -31,6 +31,7 @@ import time sys.path.append(path.dirname(path.realpath(sys.argv[0]))) import framework.core as core +import framework.profile def main(): @@ -157,7 +158,7 @@ def main(): for (key, value) in env.collectData().items(): json_writer.write_dict_item(key, value) - profile = core.merge_test_profiles(args.test_profile) + profile = framework.profile.merge_test_profiles(args.test_profile) json_writer.write_dict_key('tests') json_writer.open_dict() diff --git a/tests/all.py b/tests/all.py index 0945a27..42be8f4 100644 --- a/tests/all.py +++ b/tests/all.py @@ -9,7 +9,7 @@ import os.path as path import platform import shlex -from framework.core import TestProfile +from framework.profile import TestProfile from framework.exectest import PiglitTest from framework.gleantest import GleanTest from framework.glsl_parser_test import GLSLParserTest, add_glsl_parser_test, import_glsl_parser_tests diff --git a/tests/cl.py b/tests/cl.py index 86a76a8..a16fce4 100644 --- a/tests/cl.py +++ b/tests/cl.py @@ -9,7 +9,7 @@ import os.path as path from framework.opencv import add_opencv_tests -from framework.core import TestProfile +from framework.profile import TestProfile from framework.exectest import PiglitTest ###### diff --git a/tests/es3conform.py b/tests/es3conform.py index 6549f4e..0aefe58 100644 --- a/tests/es3conform.py +++ b/tests/es3conform.py @@ -26,7 +26,7 @@ import sys from os import path from glob import glob -from framework.core import TestProfile +from framework.profile import TestProfile from framework.exectest import Test, TEST_BIN_DIR __all__ = ['profile'] diff --git a/tests/igt.py b/tests/igt.py index c2c522f..1051ec8 100644 --- a/tests/igt.py +++ b/tests/igt.py @@ -28,7 +28,8 @@ import sys import subprocess from os import path -from framework.core import TestProfile, TestResult +from framework.core import TestResult +from framework.profile import TestProfile from framework.exectest import Test, TEST_BIN_DIR __all__ = ['profile'] diff --git a/tests/oglconform.py b/tests/oglconform.py index c303b3e..d62ceff 100644 --- a/tests/oglconform.py +++ b/tests/oglconform.py @@ -26,7 +26,7 @@ import re import sys import subprocess -from framework.core import TestProfile +from framework.profile import TestProfile from framework.exectest import Test, TEST_BIN_DIR from os import path diff --git a/tests/sanity.py b/tests/sanity.py index 059127c..0e0e038 100644 --- a/tests/sanity.py +++ b/tests/sanity.py @@ -2,7 +2,7 @@ # Minimal tests to check whether the installation is working # -from framework.core import TestProfile +from framework.profile import TestProfile from framework.gleantest import GleanTest __all__ = ['profile'] -- 1.9.2 _______________________________________________ Piglit mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/piglit
