diff options
-rw-r--r-- | framework/profile.py | 85 | ||||
-rw-r--r-- | framework/test/base.py | 6 | ||||
-rw-r--r-- | framework/tests/profile_tests.py | 96 | ||||
-rw-r--r-- | tests/quick.py | 15 |
4 files changed, 183 insertions, 19 deletions
diff --git a/framework/profile.py b/framework/profile.py index 1384bbdfe..d5d140c4c 100644 --- a/framework/profile.py +++ b/framework/profile.py @@ -32,7 +32,6 @@ import sys import multiprocessing import multiprocessing.dummy import importlib -import types import contextlib import itertools @@ -48,12 +47,23 @@ __all__ = [ ] +class TestDictError(Exception): + pass + + class TestDict(dict): # pylint: disable=too-few-public-methods """A special kind of dict for tests. This dict lowers the names of keys by default """ + def __init__(self, *args, **kwargs): + # This counter is incremented once when the allow_reassignment context + # manager is opened, and decremented each time it is closed. This + # allows stacking of the context manager + self.__allow_reassignment = 0 + super(TestDict, self).__init__(*args, **kwargs) + def __setitem__(self, key, value): """Enforce types on set operations. @@ -67,14 +77,37 @@ class TestDict(dict): # pylint: disable=too-few-public-methods filesystems. """ - assert isinstance(key, basestring), \ - "Keys must be strings, but was {}".format(type(key)) - # None is required to make empty assignment work: - # foo = Tree['a'] - assert isinstance(value, (Test, types.NoneType)), \ - "Values must be either a Test, but was {}".format(type(value)) - - super(TestDict, self).__setitem__(key.lower(), value) + # keys should be strings + if not isinstance(key, basestring): + raise TestDictError("Keys must be strings, but was {}".format( + type(key))) + + # Values should either be more Tests + if not isinstance(value, Test): + raise TestDictError( + "Values must be a Test, but was a {}".format(type(value))) + + # This must be lowered before the following test, or the test can pass + # in error if the key has capitals in it. + key = key.lower() + + # If there is already a test of that value in the tree it is an error + if not self.__allow_reassignment and key in self: + if self[key] != value: + error = ( + 'Further, the two tests are not the same,\n' + 'The original test has this command: "{0}"\n' + 'The new test has this command: "{1}"'.format( + ' '.join(self[key].command), ' '.join(value.command)) + ) + else: + error = "and both tests are the same." + + raise TestDictError( + "A test has already been asigned the name: {}\n{}".format( + key, error)) + + super(TestDict, self).__setitem__(key, value) def __getitem__(self, key): """Lower the value before returning.""" @@ -84,6 +117,25 @@ class TestDict(dict): # pylint: disable=too-few-public-methods """Lower the value before returning.""" return super(TestDict, self).__delitem__(key.lower()) + @property + @contextlib.contextmanager + def allow_reassignment(self): + """Context manager that allows keys to be reassigned. + + Normally reassignment happens in error, but sometimes one actually + wants to do reassignment, say to add extra options in a reduced + profile. This method allows reassignment, but only within its context, + making it an explict choice to do so. + + It is safe to nest this contextmanager. + + It is not safe to use this context manager in a threaded application + + """ + self.__allow_reassignment += 1 + yield + self.__allow_reassignment -= 1 + class TestProfile(object): """ Class that holds a list of tests for execution @@ -354,6 +406,13 @@ class TestProfile(object): yield adder + @property + @contextlib.contextmanager + def allow_reassignment(self): + """A convenience wrapper around self.test_list.allow_reassignment.""" + with self.test_list.allow_reassignment: + yield + def load_test_profile(filename): """ Load a python module and return it's profile attribute @@ -370,16 +429,18 @@ def load_test_profile(filename): 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: + mod = importlib.import_module('tests.{0}'.format( + os.path.splitext(os.path.basename(filename))[0])) return mod.profile except AttributeError: print("Error: There is not profile attribute in module {0}." "Did you specify the right file?".format(filename), file=sys.stderr) sys.exit(2) + except TestDictError as e: + print("Error: {}".format(e.message), file=sys.stderr) + sys.exit(1) def merge_test_profiles(profiles): diff --git a/framework/test/base.py b/framework/test/base.py index efc20cbe3..8a4bf99b7 100644 --- a/framework/test/base.py +++ b/framework/test/base.py @@ -337,6 +337,12 @@ class Test(object): return error + def __eq__(self, other): + return self.command == other.command + + def __ne__(self, other): + return not self == other + class WindowResizeMixin(object): """ Mixin class that deals with spurious window resizes diff --git a/framework/tests/profile_tests.py b/framework/tests/profile_tests.py index 74a255e80..95f8ddafb 100644 --- a/framework/tests/profile_tests.py +++ b/framework/tests/profile_tests.py @@ -255,3 +255,99 @@ def test_testprofile_groupmanager_name_str(): g('abc') nt.ok_(grouptools.join('foo', 'abc') in prof.test_list) + + +@nt.raises(profile.TestDictError) +def test_testdict_key_not_string(): + """TestDict: If key value isn't a string an exception is raised. + + This throws a few different things at the key value and expects an error to + be raised. It isn't a perfect test, but it was the best I could come up + with. + + """ + test = profile.TestDict() + + for x in [None, utils.Test(['foo']), ['a'], {'a': 1}]: + test[x] = utils.Test(['foo']) + + +@nt.raises(profile.TestDictError) +def test_testdict_value_not_valid(): + """TestDict: If the value isn't a Tree, Test, or None an exception is raised. + + Like the key test this isn't perfect, but it does try the common mistakes. + + """ + test = profile.TestDict() + + for x in [{}, 'a']: + test['foo'] = x + + +@nt.raises(profile.TestDictError) +def test_testdict_reassignment(): + """TestDict: reassigning a key raises an exception.""" + test = profile.TestDict() + test['foo'] = utils.Test(['foo']) + test['foo'] = utils.Test(['foo', 'bar']) + + +@nt.raises(profile.TestDictError) +def test_testdict_reassignment_lower(): + """TestDict: reassigning a key raises an exception (capitalization is ignored).""" + test = profile.TestDict() + test['foo'] = utils.Test(['foo']) + test['Foo'] = utils.Test(['foo', 'bar']) + + +def test_testdict_allow_reassignment(): + """TestDict: allow_reassignment works.""" + test = profile.TestDict() + test['a'] = utils.Test(['foo']) + with test.allow_reassignment: + test['a'] = utils.Test(['bar']) + + nt.ok_(test['a'].command == ['bar']) + + +def test_testprofile_allow_reassignment(): + """TestProfile: allow_reassignment wrapper works.""" + prof = profile.TestProfile() + prof.test_list['a'] = utils.Test(['foo']) + with prof.allow_reassignment: + prof.test_list['a'] = utils.Test(['bar']) + + nt.ok_(prof.test_list['a'].command == ['bar']) + + +def test_testprofile_allow_reassignment_with_groupmanager(): + """TestProfile: allow_reassignment wrapper works with groupmanager.""" + testname = grouptools.join('a', 'b') + prof = profile.TestProfile() + prof.test_list[testname] = utils.Test(['foo']) + with prof.allow_reassignment: + with prof.group_manager(utils.Test, 'a') as g: + g(['bar'], 'b') + + nt.ok_(prof.test_list[testname].command == ['bar']) + + +@utils.no_error +def test_testprofile_allow_reassignemnt_stacked(): + """profile.TestDict.allow_reassignment: check stacking cornercase. + + There is an odd corner case in the original (obvious) implmentation of this + function, If one opens two context managers and then returns from the inner + one assignment will not be allowed, even though one is still inside the + first context manager. + + """ + test = profile.TestDict() + test['a'] = utils.Test(['foo']) + with test.allow_reassignment: + with test.allow_reassignment: + pass + test['a'] = utils.Test(['bar']) + + nt.ok_(test['a'].command == ['bar']) diff --git a/tests/quick.py b/tests/quick.py index 5393a2be9..d0aca0215 100644 --- a/tests/quick.py +++ b/tests/quick.py @@ -15,13 +15,14 @@ GleanTest.GLOBAL_PARAMS += ["--quick"] with profile.group_manager( PiglitGLTest, grouptools.join('spec', 'arb_shader_image_load_store')) as g: - g(['arb_shader_image_load_store-coherency', '--quick'], 'coherency') - g(['arb_shader_image_load_store-host-mem-barrier', '--quick'], - 'host-mem-barrier') - g(['arb_shader_image_load_store-max-size', '--quick'], 'max-size') - g(['arb_shader_image_load_store-semantics', '--quick'], 'semantics') - g(['arb_shader_image_load_store-shader-mem-barrier', '--quick'], - 'shader-mem-barrier') + with profile.allow_reassignment: + g(['arb_shader_image_load_store-coherency', '--quick'], 'coherency') + g(['arb_shader_image_load_store-host-mem-barrier', '--quick'], + 'host-mem-barrier') + g(['arb_shader_image_load_store-max-size', '--quick'], 'max-size') + g(['arb_shader_image_load_store-semantics', '--quick'], 'semantics') + g(['arb_shader_image_load_store-shader-mem-barrier', '--quick'], + 'shader-mem-barrier') # These take too long profile.filter_tests(lambda n, _: '-explosion' not in n) |