summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDylan Baker <baker.dylan.c@gmail.com>2015-10-27 18:46:53 -0700
committerDylan Baker <baker.dylan.c@gmail.com>2015-11-11 15:15:02 -0800
commit6f32e3fb35d83a91b689f85b1ce10c1a39d45102 (patch)
tree4b2022090b53c4118b3412d2f8c2f5efe8599f98
parentc905d5d61d163bfd45ea9be7b4d962762164ef71 (diff)
framework: make options a global variable.
This changes the behavior of the Options class. Instead of being an instance that's passed around, it's now treated as a global variable (or as close to one as python gets to having globals), anyone who needs it goes and gets it out of the options namespace and uses it. This patch does a lot of refactoring work that is needed to ensure that Options continues to work as it did before (where it was mostly initialized and then passed around), This includes a significant amount of testing for the new code around Options. v2: - fix spelling in comments and docstrings - Fix _ReList docstring, which was _ReListDescriptor's docstring - Fix _ReList.__compile to ensure RegexObject.flags was correct - Add docstring to _ReList.__compile - Add __delete__ to _ReListDescriptor as NotImplementedError, this is just a completeness issue - Add/update tests for these changes - Remove duplicate addition of 'mock' to tox.ini Signed-off-by: Dylan Baker <dylanx.c.baker@intel.com>
-rw-r--r--framework/options.py202
-rw-r--r--framework/tests/options_tests.py187
2 files changed, 389 insertions, 0 deletions
diff --git a/framework/options.py b/framework/options.py
new file mode 100644
index 000000000..4064c9c47
--- /dev/null
+++ b/framework/options.py
@@ -0,0 +1,202 @@
+# Copyright (c) 2015 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.
+
+"""Stores global piglit options.
+
+This is as close to a true global function as python gets. The only deal here
+is that while you can mutate
+
+"""
+
+from __future__ import absolute_import, division, print_function
+import collections
+import os
+import re
+
+__all__ = ['OPTIONS']
+
+# pylint: disable=too-few-public-methods
+
+
+_RETYPE = type(re.compile(''))
+
+
+class _ReList(collections.MutableSequence):
+ """A list-like container that only holds RegexObjects.
+
+ This class behaves identically to a list, except that all objects are
+ forced to be RegexObjects with a flag of re.IGNORECASE (2 if one inspects
+ the object).
+
+ If inputs do not match this object, they will be coerced to becoming such
+ an object, or they assignment will fail.
+
+ """
+ def __init__(self, iterable=None):
+ self._wrapped = []
+ if iterable is not None:
+ self.extend(iterable)
+
+ @staticmethod
+ def __compile(value):
+ """Ensure that the object is properly compiled.
+
+ If the object is not a RegexObject then compile it to one, setting the
+ proper flag. If it is a RegexObject, and the flag is incorrect
+ recompile it to have the proper flags. Otherwise return it.
+
+ """
+ if not isinstance(value, _RETYPE):
+ return re.compile(value, re.IGNORECASE)
+ elif value.flags != re.IGNORECASE:
+ return re.compile(value.pattern, re.IGNORECASE)
+ return value
+
+ def __getitem__(self, index):
+ return self._wrapped[index]
+
+ def __setitem__(self, index, value):
+ self._wrapped[index] = self.__compile(value)
+
+ def __delitem__(self, index):
+ del self._wrapped[index]
+
+ def __len__(self):
+ return len(self._wrapped)
+
+ def insert(self, index, value):
+ self._wrapped.insert(index, self.__compile(value))
+
+ def __eq__(self, other):
+ """Two ReList instances are the same if their wrapped list are equal."""
+ if isinstance(other, _ReList):
+ # There doesn't seem to be a better way to do this.
+ return self._wrapped == other._wrapped # pylint: disable=protected-access
+ raise TypeError('Cannot compare _ReList and non-_ReList object')
+
+ def __ne__(self, other):
+ return not self == other
+
+ def to_json(self):
+ """Allow easy JSON serialization.
+
+ This returns the pattern (the string or unicode used to create the re)
+ of each re object in a list rather than the RegexObject itself. This is
+ critical for JSON serialization, and thanks to the piglit_encoder this
+ is all we need to serialize this class.
+
+ """
+ return [l.pattern for l in self]
+
+
+class _ReListDescriptor(object):
+ """A Descriptor than ensures reassignment of _{in,ex}clude_filter is an
+ _ReList
+
+ Without this some behavior's can get very strange. This descriptor is
+ mostly hit by testing code, but may be of use outside of testing at some
+ point.
+
+ """
+ def __init__(self, name):
+ self.__name = name
+
+ def __get__(self, instance, cls):
+ try:
+ return getattr(instance, self.__name)
+ except AttributeError as e:
+ new = _ReList()
+ try:
+ setattr(instance, self.__name, new)
+ except Exception:
+ raise e
+ return new
+
+ def __set__(self, instance, value):
+ assert isinstance(value, (collections.Sequence, collections.Set))
+ if isinstance(value, _ReList):
+ setattr(instance, self.__name, value)
+ else:
+ setattr(instance, self.__name, _ReList(value))
+
+ def __delete__(self, instance):
+ raise NotImplementedError('Cannot delete {} from {}'.format(
+ self.__name, instance.__class__))
+
+
+class _Options(object): # pylint: disable=too-many-instance-attributes
+ """Contains all options for a piglit run.
+
+ This is used as a sort of global state object, kinda like piglit.conf. This
+ big difference here is that this object is largely generated internally,
+ and is controlled mostly through command line options rather than through
+ the configuration file.
+
+ Options are as follows:
+ concurrent -- True if concurrency is to be used
+ execute -- False for dry run
+ include_filter -- list of compiled regex which include exclusively tests
+ that match
+ exclude_filter -- list of compiled regex which exclude tests that match
+ valgrind -- True if valgrind is to be used
+ dmesg -- True if dmesg checking is desired. This forces concurrency off
+ env -- environment variables set for each test before run
+
+ """
+ include_filter = _ReListDescriptor('_include_filter')
+ exclude_filter = _ReListDescriptor('_exclude_filter')
+
+ def __init__(self):
+ self.concurrent = True
+ self.execute = True
+ self._include_filter = _ReList()
+ self._exclude_filter = _ReList()
+ self.exclude_tests = set()
+ self.valgrind = False
+ self.dmesg = False
+ self.sync = False
+
+ # env is used to set some base environment variables that are not going
+ # to change across runs, without sending them to os.environ which is
+ # fickle and easy to break
+ self.env = {
+ 'PIGLIT_SOURCE_DIR':
+ os.environ.get(
+ 'PIGLIT_SOURCE_DIR',
+ os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '..')))
+ }
+
+ def clear(self):
+ """Reinitialize all values to defaults."""
+ self.__init__()
+
+ def __iter__(self):
+ for key, values in self.__dict__.iteritems():
+ # If the values are regex compiled then yield their pattern
+ # attribute, which is the original plaintext they were compiled
+ # from, otherwise yield them normally.
+ if key in ['filter', 'exclude_filter']:
+ yield (key, [x.pattern for x in values])
+ else:
+ yield (key, values)
+
+
+OPTIONS = _Options()
diff --git a/framework/tests/options_tests.py b/framework/tests/options_tests.py
new file mode 100644
index 000000000..ec6318dd5
--- /dev/null
+++ b/framework/tests/options_tests.py
@@ -0,0 +1,187 @@
+# Copyright (c) 2015 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.
+
+"""Tests for the options module."""
+
+from __future__ import absolute_import, division, print_function
+import re
+
+import mock
+import nose.tools as nt
+
+from . import utils
+from framework import options
+
+# pylint: disable=protected-access,line-too-long
+
+_RETYPE = type(re.compile(''))
+
+
+@utils.no_error
+def test_relist_init():
+ """options._ReList: inializes"""
+ options._ReList()
+
+
+def test_relist_init_iterable():
+ """options._ReList: handles an iterable argument correctly"""
+ test = options._ReList(['foo'])
+ nt.assert_is_instance(test[0], _RETYPE)
+
+
+def test_relist_eq():
+ """options._ReList.__eq__: two ReLists are equal if their wrapped lists are equal"""
+ test1 = options._ReList(['foo'])
+ test2 = options._ReList(['foo'])
+
+ nt.eq_(test1, test2)
+
+
+@nt.raises(TypeError)
+def test_relist_eq_other():
+ """options._ReList.__eq__: raises TypeError if the other object is not an ReList"""
+ test1 = options._ReList(['foo'])
+ test2 = ['foo']
+
+ nt.eq_(test1, test2)
+
+
+def test_relist_ne():
+ """options._ReList.__ne__: two ReLists are not equal if their wrapped lists are not equal"""
+ test1 = options._ReList(['foo'])
+ test2 = options._ReList(['bar'])
+
+ nt.assert_not_equal(test1, test2)
+
+
+@nt.raises(TypeError)
+def test_relist_ne_other():
+ """options._ReList.__ne__: raises TypeError if the other object is not an ReList"""
+ test1 = options._ReList(['foo'])
+ test2 = ['bar']
+
+ nt.eq_(test1, test2)
+
+
+class TestReList(object):
+ """Some tests that can share state"""
+ @classmethod
+ def setup_class(cls):
+ cls.test = options._ReList(['foo'])
+
+ def test_getitem(self):
+ """options._ReList.__getitem__: returns expected value"""
+ nt.assert_is_instance(self.test[0], _RETYPE)
+
+ def test_flags(self):
+ """options._ReList.__getitem__: sets flags correctly"""
+ nt.eq_(self.test[0].flags, re.IGNORECASE)
+
+ def test_len(self):
+ """options._ReList.len: returns expected values"""
+ nt.eq_(len(self.test), 1)
+
+ def test_to_json(self):
+ """options._ReList.to_json: returns expected values"""
+ nt.eq_(self.test.to_json(), ['foo'])
+
+
+class TestReListDescriptor(object):
+ @classmethod
+ def setup_class(cls):
+ class _Test(object):
+ desc = options._ReListDescriptor('test_desc')
+ notexists = options._ReListDescriptor('test_notexists')
+
+ def __init__(self):
+ self.test_desc = options._ReList()
+
+ cls._test = _Test
+
+ def setup(self):
+ self.test = self._test()
+
+ def test_get_exists(self):
+ """options._ReListDescriptor.__get__: Returns value if it exists"""
+ nt.eq_(self.test.desc, self.test.test_desc)
+
+ def test_get_not_exists(self):
+ """options._ReListDescriptor.__get__: Returns new _ReList if it doesn't exists"""
+ nt.eq_(self.test.notexists, self.test.test_notexists) # pylint: disable=no-member
+
+ @mock.patch('framework.options.setattr', mock.Mock(side_effect=Exception))
+ @nt.raises(AttributeError)
+ def test_get_not_exists_fail(self):
+ """options._ReListDescriptor.__get__: Raises AttributError if name doesn't exist and cant be created"""
+ self.test.notexists # pylint: disable=pointless-statement
+
+ def test_set_relist(self):
+ """options._ReListDescriptor.__set__: assigns an ReList directoy"""
+ val = options._ReList(['foo'])
+ self.test.desc = val
+ nt.ok_(self.test.desc is val, msg='value not assigned directly')
+
+ def test_set_other(self):
+ """options._ReListDescriptor.__set__: converts other types to ReList"""
+ val = options._ReList(['foo'])
+ self.test.desc = ['foo']
+ nt.eq_(self.test.desc, val)
+
+ @nt.raises(NotImplementedError)
+ def test_delete(self):
+ """options._ReListDescriptor.__delete___: raises NotImplementedError"""
+ del self.test.desc
+
+
+def test_relist_insert():
+ """options._ReList.len: inserts value as expected"""
+ test = options._ReList(['foo'])
+ obj = re.compile('bar', re.IGNORECASE)
+ test.insert(0, obj)
+ nt.eq_(test[0], obj)
+
+
+def test_relist_delitem():
+ """options._ReList.len: removes value as expected"""
+ test = options._ReList(['foo'])
+ del test[0]
+ nt.eq_(len(test), 0)
+
+
+def test_relist_setitem():
+ """options._ReList.__setitem__: adds value as expected"""
+ canary = re.compile('bar')
+
+ test = options._ReList([canary])
+ test[0] = 'foo'
+ nt.ok_(test[0] is not canary, msg='index value 0 wasn not replaced')
+
+
+def test_options_clear():
+ """options.Options.clear(): resests options values to init state"""
+ baseline = options._Options()
+
+ test = options._Options()
+ test.execute = False
+ test.sync = True
+ test.exclude_filter.append('foo')
+ test.clear()
+
+ nt.eq_(list(iter(baseline)), list(iter(test)))