[Piglit] [PATCH 1/5] framework/profile: Convert TestDict to a collections.MutableMapping

Dylan Baker dylan at pnwbakers.com
Mon May 2 21:21:10 UTC 2016


MutableMapping is an abstract base class that makes writing dict-like
objects easier, by providing some of the boilerplate code. This is the
recommended way to make a dict-like object, as opposed to subclassing
dict. This is because subclassing built-ins is perilous, sometimes they
call their protocol methods (or magic methods), sometimes they don't.

This change uncovered such a bug, TestDict.update would silently
overwrite other tests when calling update, without there being an code
to explicitly do that. This patch additionally fixes the test that trips
the bug and adds a test the explicitly cover it.

This is groundwork for the next patch.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 framework/profile.py       | 39 ++++++++++++++++++++++++---------------
 unittests/profile_tests.py | 22 +++++++++++++++++++---
 2 files changed, 43 insertions(+), 18 deletions(-)

diff --git a/framework/profile.py b/framework/profile.py
index ef1c993..6342eae 100644
--- a/framework/profile.py
+++ b/framework/profile.py
@@ -29,12 +29,13 @@ are represented by a TestProfile or a TestProfile derived object.
 from __future__ import (
     absolute_import, division, print_function, unicode_literals
 )
-import os
-import multiprocessing
-import multiprocessing.dummy
-import importlib
+import collections
 import contextlib
+import importlib
 import itertools
+import multiprocessing
+import multiprocessing.dummy
+import os
 
 import six
 
@@ -50,7 +51,7 @@ __all__ = [
 ]
 
 
-class TestDict(dict):  # pylint: disable=too-few-public-methods
+class TestDict(collections.MutableMapping):
     """A special kind of dict for tests.
 
     This dict lowers the names of keys by default
@@ -61,7 +62,7 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
         # 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)
+        self.__container = dict(*args, **kwargs)
 
     def __setitem__(self, key, value):
         """Enforce types on set operations.
@@ -77,7 +78,7 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
 
         """
         # keys should be strings
-        if not isinstance(key, six.string_types):
+        if not isinstance(key, six.text_type):
             raise exceptions.PiglitFatalError(
                 "TestDict keys must be strings, but was {}".format(type(key)))
 
@@ -92,13 +93,14 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
         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:
+        if not self.__allow_reassignment and key in self.__container:
+            if self.__container[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))
+                        ' '.join(self.__container[key].command),
+                        ' '.join(value.command))
                 )
             else:
                 error = "and both tests are the same."
@@ -107,15 +109,21 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
                 "A test has already been assigned the name: {}\n{}".format(
                     key, error))
 
-        super(TestDict, self).__setitem__(key, value)
+        self.__container[key] = value
 
     def __getitem__(self, key):
         """Lower the value before returning."""
-        return super(TestDict, self).__getitem__(key.lower())
+        return self.__container[key.lower()]
 
     def __delitem__(self, key):
         """Lower the value before returning."""
-        return super(TestDict, self).__delitem__(key.lower())
+        del self.__container[key.lower()]
+
+    def __len__(self):
+        return len(self.__container)
+
+    def __iter__(self):
+        return iter(self.__container)
 
     @property
     @contextlib.contextmanager
@@ -442,6 +450,7 @@ def merge_test_profiles(profiles):
 
     """
     profile = load_test_profile(profiles.pop())
-    for p in profiles:
-        profile.update(load_test_profile(p))
+    with profile.allow_reassignment:
+        for p in profiles:
+            profile.update(load_test_profile(p))
     return profile
diff --git a/unittests/profile_tests.py b/unittests/profile_tests.py
index 48e9d96..20e4d37 100644
--- a/unittests/profile_tests.py
+++ b/unittests/profile_tests.py
@@ -34,7 +34,7 @@ except ImportError:
 import nose.tools as nt
 
 from . import utils
-from framework import grouptools, dmesg, profile, exceptions, options
+from framework import grouptools, dmesg, profile, exceptions, options, exceptions
 from framework.test import GleanTest
 
 # Don't print sys.stderr to the console
@@ -107,9 +107,10 @@ def test_testprofile_update_test_list():
     profile2.test_list[group1] = utils.Test(['test3'])
     profile2.test_list[group2] = utils.Test(['test2'])
 
-    profile1.update(profile2)
+    with profile1.allow_reassignment:
+        profile1.update(profile2)
 
-    nt.assert_dict_equal(profile1.test_list, profile2.test_list)
+    nt.assert_dict_equal(dict(profile1.test_list), dict(profile2.test_list))
 
 
 class TestPrepareTestListMatches(object):
@@ -359,3 +360,18 @@ def test_testprofile_allow_reassignemnt_stacked():
         test['a'] = utils.Test(['bar'])
 
     nt.ok_(test['a'].command == ['bar'])
+
+
+ at nt.raises(exceptions.PiglitFatalError)
+def test_testdict_update_reassignment():
+    """profile.TestDict.update: Does not implictly allow reassignment"""
+    test1 = utils.Test(['test1'])
+    test2 = utils.Test(['test2'])
+
+    td1 = profile.TestDict()
+    td1['test1'] = test1
+
+    td2 = profile.TestDict()
+    td2['test1'] = test2
+
+    td1.update(td2)
-- 
2.8.2



More information about the Piglit mailing list