root / trunk / twisted / python / monkey.py

Revision 21800, 2.1 kB (checked in by jml, 2 years ago)

Add support for monkey patching, both in code and in tests.

  • Author: jml
  • Reviewer: therve
  • Fixes #2598

Monkey patching is a necessary evil, particularly useful for testing legacy
code. This branch provides support for monkey patching in tests in a way that
significantly reduces the chance of forgetting to unapply your patch.

Line 
1 # -*- test-case-name: twisted.test.test_monkey -*-
2
3 # Copyright (c) 2007 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 class MonkeyPatcher(object):
8     """
9     Cover up attributes with new objects. Neat for monkey-patching things for
10     unit-testing purposes.
11     """
12
13     def __init__(self, *patches):
14         # List of patches to apply in (obj, name, value).
15         self._patchesToApply = []
16         # List of the original values for things that have been patched.
17         # (obj, name, value) format.
18         self._originals = []
19         for patch in patches:
20             self.addPatch(*patch)
21
22
23     def addPatch(self, obj, name, value):
24         """
25         Add a patch so that the attribute C{name} on C{obj} will be assigned to
26         C{value} when C{patch} is called or during C{runWithPatches}.
27
28         You can restore the original values with a call to restore().
29         """
30         self._patchesToApply.append((obj, name, value))
31
32
33     def _alreadyPatched(self, obj, name):
34         """
35         Has the C{name} attribute of C{obj} already been patched by this
36         patcher?
37         """
38         for o, n, v in self._originals:
39             if (o, n) == (obj, name):
40                 return True
41         return False
42
43
44     def patch(self):
45         """
46         Apply all of the patches that have been specified with L{addPatch}.
47         Reverse this operation using L{restore}.
48         """
49         for obj, name, value in self._patchesToApply:
50             if not self._alreadyPatched(obj, name):
51                 self._originals.append((obj, name, getattr(obj, name)))
52             setattr(obj, name, value)
53
54
55     def restore(self):
56         """
57         Restore all original values to any patched objects.
58         """
59         while self._originals:
60             obj, name, value = self._originals.pop()
61             setattr(obj, name, value)
62
63
64     def runWithPatches(self, f, *args, **kw):
65         """
66         Apply each patch already specified. Then run the function f with the
67         given args and kwargs. Restore everything when done.
68         """
69         self.patch()
70         try:
71             return f(*args, **kw)
72         finally:
73             self.restore()
Note: See TracBrowser for help on using the browser.