--- /dev/null
+# mock.py\r
+# Test tools for mocking and patching.\r
+# Copyright (C) 2007-2009 Michael Foord\r
+# E-mail: fuzzyman AT voidspace DOT org DOT uk\r
+\r
+# mock 0.6.0\r
+# http://www.voidspace.org.uk/python/mock/\r
+\r
+# Released subject to the BSD License\r
+# Please see http://www.voidspace.org.uk/python/license.shtml\r
+\r
+# 2009-11-25: Licence downloaded from above URL.\r
+# BEGIN DOWNLOADED LICENSE\r
+#\r
+# Copyright (c) 2003-2009, Michael Foord\r
+# All rights reserved.\r
+# E-mail : fuzzyman AT voidspace DOT org DOT uk\r
+# \r
+# Redistribution and use in source and binary forms, with or without\r
+# modification, are permitted provided that the following conditions are\r
+# met:\r
+# \r
+# \r
+# * Redistributions of source code must retain the above copyright\r
+# notice, this list of conditions and the following disclaimer.\r
+# \r
+# * Redistributions in binary form must reproduce the above\r
+# copyright notice, this list of conditions and the following\r
+# disclaimer in the documentation and/or other materials provided\r
+# with the distribution.\r
+# \r
+# * Neither the name of Michael Foord nor the name of Voidspace\r
+# may be used to endorse or promote products derived from this\r
+# software without specific prior written permission.\r
+# \r
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+#\r
+# END DOWNLOADED LICENSE\r
+\r
+# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml\r
+# Comments, suggestions and bug reports welcome.\r
+\r
+\r
+__all__ = (\r
+ 'Mock',\r
+ 'patch',\r
+ 'patch_object',\r
+ 'sentinel',\r
+ 'DEFAULT'\r
+)\r
+\r
+__version__ = '0.6.0'\r
+\r
+class SentinelObject(object):\r
+ def __init__(self, name):\r
+ self.name = name\r
+ \r
+ def __repr__(self):\r
+ return '<SentinelObject "%s">' % self.name\r
+\r
+\r
+class Sentinel(object):\r
+ def __init__(self):\r
+ self._sentinels = {}\r
+ \r
+ def __getattr__(self, name):\r
+ return self._sentinels.setdefault(name, SentinelObject(name))\r
+ \r
+ \r
+sentinel = Sentinel()\r
+\r
+DEFAULT = sentinel.DEFAULT\r
+\r
+class OldStyleClass:\r
+ pass\r
+ClassType = type(OldStyleClass)\r
+\r
+def _is_magic(name):\r
+ return '__%s__' % name[2:-2] == name\r
+\r
+def _copy(value):\r
+ if type(value) in (dict, list, tuple, set):\r
+ return type(value)(value)\r
+ return value\r
+\r
+\r
+class Mock(object):\r
+\r
+ def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, \r
+ name=None, parent=None, wraps=None):\r
+ self._parent = parent\r
+ self._name = name\r
+ if spec is not None and not isinstance(spec, list):\r
+ spec = [member for member in dir(spec) if not _is_magic(member)]\r
+ \r
+ self._methods = spec\r
+ self._children = {}\r
+ self._return_value = return_value\r
+ self.side_effect = side_effect\r
+ self._wraps = wraps\r
+ \r
+ self.reset_mock()\r
+ \r
+\r
+ def reset_mock(self):\r
+ self.called = False\r
+ self.call_args = None\r
+ self.call_count = 0\r
+ self.call_args_list = []\r
+ self.method_calls = []\r
+ for child in self._children.itervalues():\r
+ child.reset_mock()\r
+ if isinstance(self._return_value, Mock):\r
+ self._return_value.reset_mock()\r
+ \r
+ \r
+ def __get_return_value(self):\r
+ if self._return_value is DEFAULT:\r
+ self._return_value = Mock()\r
+ return self._return_value\r
+ \r
+ def __set_return_value(self, value):\r
+ self._return_value = value\r
+ \r
+ return_value = property(__get_return_value, __set_return_value)\r
+\r
+\r
+ def __call__(self, *args, **kwargs):\r
+ self.called = True\r
+ self.call_count += 1\r
+ self.call_args = (args, kwargs)\r
+ self.call_args_list.append((args, kwargs))\r
+ \r
+ parent = self._parent\r
+ name = self._name\r
+ while parent is not None:\r
+ parent.method_calls.append((name, args, kwargs))\r
+ if parent._parent is None:\r
+ break\r
+ name = parent._name + '.' + name\r
+ parent = parent._parent\r
+ \r
+ ret_val = DEFAULT\r
+ if self.side_effect is not None:\r
+ if (isinstance(self.side_effect, Exception) or \r
+ isinstance(self.side_effect, (type, ClassType)) and\r
+ issubclass(self.side_effect, Exception)):\r
+ raise self.side_effect\r
+ \r
+ ret_val = self.side_effect(*args, **kwargs)\r
+ if ret_val is DEFAULT:\r
+ ret_val = self.return_value\r
+ \r
+ if self._wraps is not None and self._return_value is DEFAULT:\r
+ return self._wraps(*args, **kwargs)\r
+ if ret_val is DEFAULT:\r
+ ret_val = self.return_value\r
+ return ret_val\r
+ \r
+ \r
+ def __getattr__(self, name):\r
+ if self._methods is not None:\r
+ if name not in self._methods:\r
+ raise AttributeError("Mock object has no attribute '%s'" % name)\r
+ elif _is_magic(name):\r
+ raise AttributeError(name)\r
+ \r
+ if name not in self._children:\r
+ wraps = None\r
+ if self._wraps is not None:\r
+ wraps = getattr(self._wraps, name)\r
+ self._children[name] = Mock(parent=self, name=name, wraps=wraps)\r
+ \r
+ return self._children[name]\r
+ \r
+ \r
+ def assert_called_with(self, *args, **kwargs):\r
+ assert self.call_args == (args, kwargs), 'Expected: %s\nCalled with: %s' % ((args, kwargs), self.call_args)\r
+ \r
+\r
+def _dot_lookup(thing, comp, import_path):\r
+ try:\r
+ return getattr(thing, comp)\r
+ except AttributeError:\r
+ __import__(import_path)\r
+ return getattr(thing, comp)\r
+\r
+\r
+def _importer(target):\r
+ components = target.split('.')\r
+ import_path = components.pop(0)\r
+ thing = __import__(import_path)\r
+\r
+ for comp in components:\r
+ import_path += ".%s" % comp\r
+ thing = _dot_lookup(thing, comp, import_path)\r
+ return thing\r
+\r
+\r
+class _patch(object):\r
+ def __init__(self, target, attribute, new, spec, create):\r
+ self.target = target\r
+ self.attribute = attribute\r
+ self.new = new\r
+ self.spec = spec\r
+ self.create = create\r
+ self.has_local = False\r
+\r
+\r
+ def __call__(self, func):\r
+ if hasattr(func, 'patchings'):\r
+ func.patchings.append(self)\r
+ return func\r
+\r
+ def patched(*args, **keywargs):\r
+ # don't use a with here (backwards compatability with 2.5)\r
+ extra_args = []\r
+ for patching in patched.patchings:\r
+ arg = patching.__enter__()\r
+ if patching.new is DEFAULT:\r
+ extra_args.append(arg)\r
+ args += tuple(extra_args)\r
+ try:\r
+ return func(*args, **keywargs)\r
+ finally:\r
+ for patching in getattr(patched, 'patchings', []):\r
+ patching.__exit__()\r
+\r
+ patched.patchings = [self]\r
+ patched.__name__ = func.__name__ \r
+ patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", \r
+ func.func_code.co_firstlineno)\r
+ return patched\r
+\r
+\r
+ def get_original(self):\r
+ target = self.target\r
+ name = self.attribute\r
+ create = self.create\r
+ \r
+ original = DEFAULT\r
+ if _has_local_attr(target, name):\r
+ try:\r
+ original = target.__dict__[name]\r
+ except AttributeError:\r
+ # for instances of classes with slots, they have no __dict__\r
+ original = getattr(target, name)\r
+ elif not create and not hasattr(target, name):\r
+ raise AttributeError("%s does not have the attribute %r" % (target, name))\r
+ return original\r
+\r
+ \r
+ def __enter__(self):\r
+ new, spec, = self.new, self.spec\r
+ original = self.get_original()\r
+ if new is DEFAULT:\r
+ # XXXX what if original is DEFAULT - shouldn't use it as a spec\r
+ inherit = False\r
+ if spec == True:\r
+ # set spec to the object we are replacing\r
+ spec = original\r
+ if isinstance(spec, (type, ClassType)):\r
+ inherit = True\r
+ new = Mock(spec=spec)\r
+ if inherit:\r
+ new.return_value = Mock(spec=spec)\r
+ self.temp_original = original\r
+ setattr(self.target, self.attribute, new)\r
+ return new\r
+\r
+\r
+ def __exit__(self, *_):\r
+ if self.temp_original is not DEFAULT:\r
+ setattr(self.target, self.attribute, self.temp_original)\r
+ else:\r
+ delattr(self.target, self.attribute)\r
+ del self.temp_original\r
+ \r
+ \r
+def patch_object(target, attribute, new=DEFAULT, spec=None, create=False):\r
+ return _patch(target, attribute, new, spec, create)\r
+\r
+\r
+def patch(target, new=DEFAULT, spec=None, create=False):\r
+ try:\r
+ target, attribute = target.rsplit('.', 1) \r
+ except (TypeError, ValueError):\r
+ raise TypeError("Need a valid target to patch. You supplied: %r" % (target,))\r
+ target = _importer(target)\r
+ return _patch(target, attribute, new, spec, create)\r
+\r
+\r
+\r
+def _has_local_attr(obj, name):\r
+ try:\r
+ return name in vars(obj)\r
+ except TypeError:\r
+ # objects without a __dict__\r
+ return hasattr(obj, name)\r
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from modules.scm import CommitMessage
+
class MockBugzilla():
- patch1 = { "id": 197, "bug_id": 42, "url": "http://example.com/197" }
- patch2 = { "id": 128, "bug_id": 42, "url": "http://example.com/128" }
+ patch1 = { "id": 197, "bug_id": 42, "url": "http://example.com/197", "is_obsolete": False }
+ patch2 = { "id": 128, "bug_id": 42, "url": "http://example.com/128", "is_obsolete": False }
def fetch_bug_ids_from_commit_queue(self):
return [42, 75]
return [self.patch1, self.patch2]
return None
+ def fetch_attachments_from_bug(self, bug_id):
+ if bug_id == 42:
+ return [self.patch1, self.patch2]
+ return None
+
+ def fetch_patches_from_bug(self, bug_id):
+ if bug_id == 42:
+ return [self.patch1, self.patch2]
+ return None
+
def close_bug_as_fixed(self, bug_id, comment_text=None):
pass
+ def obsolete_attachment(self, attachment_id, comment_text=None):
+ pass
+
+ def add_patch_to_bug(self, bug_id, patch_file_object, description, comment_text=None, mark_for_review=False, mark_for_commit_queue=False):
+ pass
+
class MockBuildBot():
def builder_statuses(self):
}]
+class MockSCM():
+ def create_patch(self):
+ return "Patch1"
+
+ def commit_ids_from_commitish_arguments(self, args):
+ return ["Commitish1", "Commitish2"]
+
+ def commit_message_for_local_commit(self, commit_id):
+ if commit_id == "Commitish1":
+ return CommitMessage("CommitMessage1\nhttps://bugs.example.org/show_bug.cgi?id=42\n")
+ if commit_id == "Commitish2":
+ return CommitMessage("CommitMessage2\nhttps://bugs.example.org/show_bug.cgi?id=75\n")
+ raise Exception("Bogus commit_id in commit_message_for_local_commit.")
+
+ def create_patch_from_local_commit(self, commit_id):
+ if commit_id == "Commitish1":
+ return "Patch1"
+ if commit_id == "Commitish2":
+ return "Patch2"
+ raise Exception("Bogus commit_id in commit_message_for_local_commit.")
+
+
class MockBugzillaTool():
bugs = MockBugzilla()
buildbot = MockBuildBot()
+
+ _scm = MockSCM()
+ def scm(self):
+ return self._scm