[Piglit] [PATCH 1/2] framework: make options a global variable.

baker.dylan.c at gmail.com baker.dylan.c at gmail.com
Fri Oct 30 16:21:44 PDT 2015


From: Dylan Baker <baker.dylan.c at gmail.com>

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.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 framework/options.py             | 186 +++++++++++++++++++++++++++++++++++++++
 framework/tests/options_tests.py | 178 +++++++++++++++++++++++++++++++++++++
 tox.ini                          |   2 +
 3 files changed, 366 insertions(+)
 create mode 100644 framework/options.py
 create mode 100644 framework/tests/options_tests.py

diff --git a/framework/options.py b/framework/options.py
new file mode 100644
index 0000000..b46e31c
--- /dev/null
+++ b/framework/options.py
@@ -0,0 +1,186 @@
+# 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):
+    """Descriptor for handling re lists.
+
+    This descriptor is a little limited in that it enforces that
+
+    """
+    def __init__(self, iterable=None):
+        self._wrapped = []
+        if iterable is not None:
+            self.extend(iterable)
+
+    @staticmethod
+    def __compile(value):
+        # Normally we wouldn't need this, but re.compile can't change the falgs
+        # on a compiled object
+        if not isinstance(value, _RETYPE):
+            return re.compile(value, 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 f 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))
+
+
+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 commmand 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 0000000..fed6136
--- /dev/null
+++ b/framework/tests/options_tests.py
@@ -0,0 +1,178 @@
+# 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(''))
+
+
+ at 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)
+
+
+ at 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)
+
+
+ at 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_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)
+
+
+def test_relist_insert():
+    """options._ReList.len: inserts value as expected"""
+    test = options._ReList(['foo'])
+    obj = re.compile('bar')
+    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)))
diff --git a/tox.ini b/tox.ini
index d1f1f16..51c231a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -14,6 +14,7 @@ commands = nosetests generated_tests/test_generators.py
 passenv=HOME
 deps =
     mako
+    mock
     nose
     coverage
     mock
@@ -23,6 +24,7 @@ commands = nosetests framework/tests --attr="!privileged" --with-cover --cover-p
 passenv=HOME
 deps =
     mako
+    mock
     nose
     coverage
     mock
-- 
2.6.2



More information about the Piglit mailing list