blob: d0a2ec9fddb866d28e5ad1e17537d7ab9c776964 [file] [log] [blame]
Anthony Baxterc2a5a632004-08-02 06:10:11 +00001import unittest
Anthony Baxterc2a5a632004-08-02 06:10:11 +00002
3def funcattrs(**kwds):
4 def decorate(func):
5 func.__dict__.update(kwds)
6 return func
7 return decorate
8
9class MiscDecorators (object):
10 @staticmethod
11 def author(name):
12 def decorate(func):
13 func.__dict__['author'] = name
14 return func
15 return decorate
16
17# -----------------------------------------------
18
19class DbcheckError (Exception):
20 def __init__(self, exprstr, func, args, kwds):
21 # A real version of this would set attributes here
22 Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" %
23 (exprstr, func, args, kwds))
Tim Peters6db15d72004-08-04 02:36:18 +000024
25
Anthony Baxterc2a5a632004-08-02 06:10:11 +000026def dbcheck(exprstr, globals=None, locals=None):
27 "Decorator to implement debugging assertions"
28 def decorate(func):
Neal Norwitz221085d2007-02-25 20:55:47 +000029 expr = compile(exprstr, "dbcheck-%s" % func.__name__, "eval")
Anthony Baxterc2a5a632004-08-02 06:10:11 +000030 def check(*args, **kwds):
31 if not eval(expr, globals, locals):
32 raise DbcheckError(exprstr, func, args, kwds)
33 return func(*args, **kwds)
34 return check
35 return decorate
36
37# -----------------------------------------------
38
39def countcalls(counts):
40 "Decorator to count calls to a function"
41 def decorate(func):
Neal Norwitz221085d2007-02-25 20:55:47 +000042 func_name = func.__name__
Michael W. Hudson0ccff072004-08-17 17:29:16 +000043 counts[func_name] = 0
Anthony Baxterc2a5a632004-08-02 06:10:11 +000044 def call(*args, **kwds):
Michael W. Hudson0ccff072004-08-17 17:29:16 +000045 counts[func_name] += 1
Anthony Baxterc2a5a632004-08-02 06:10:11 +000046 return func(*args, **kwds)
Neal Norwitz221085d2007-02-25 20:55:47 +000047 call.__name__ = func_name
Anthony Baxterc2a5a632004-08-02 06:10:11 +000048 return call
49 return decorate
50
51# -----------------------------------------------
52
53def memoize(func):
54 saved = {}
55 def call(*args):
56 try:
57 return saved[args]
58 except KeyError:
59 res = func(*args)
60 saved[args] = res
61 return res
62 except TypeError:
63 # Unhashable argument
64 return func(*args)
Neal Norwitz221085d2007-02-25 20:55:47 +000065 call.__name__ = func.__name__
Anthony Baxterc2a5a632004-08-02 06:10:11 +000066 return call
Tim Peters6db15d72004-08-04 02:36:18 +000067
Anthony Baxterc2a5a632004-08-02 06:10:11 +000068# -----------------------------------------------
69
70class TestDecorators(unittest.TestCase):
71
72 def test_single(self):
73 class C(object):
74 @staticmethod
75 def foo(): return 42
76 self.assertEqual(C.foo(), 42)
77 self.assertEqual(C().foo(), 42)
78
Anthony Baxter270fe882004-08-02 11:34:10 +000079 def test_staticmethod_function(self):
80 @staticmethod
Tim Peters6db15d72004-08-04 02:36:18 +000081 def notamethod(x):
Anthony Baxter270fe882004-08-02 11:34:10 +000082 return x
83 self.assertRaises(TypeError, notamethod, 1)
84
Anthony Baxterc2a5a632004-08-02 06:10:11 +000085 def test_dotted(self):
86 decorators = MiscDecorators()
87 @decorators.author('Cleese')
88 def foo(): return 42
89 self.assertEqual(foo(), 42)
90 self.assertEqual(foo.author, 'Cleese')
91
92 def test_argforms(self):
93 # A few tests of argument passing, as we use restricted form
94 # of expressions for decorators.
Tim Peters6db15d72004-08-04 02:36:18 +000095
Anthony Baxterc2a5a632004-08-02 06:10:11 +000096 def noteargs(*args, **kwds):
97 def decorate(func):
98 setattr(func, 'dbval', (args, kwds))
99 return func
100 return decorate
101
102 args = ( 'Now', 'is', 'the', 'time' )
103 kwds = dict(one=1, two=2)
104 @noteargs(*args, **kwds)
105 def f1(): return 42
106 self.assertEqual(f1(), 42)
107 self.assertEqual(f1.dbval, (args, kwds))
108
109 @noteargs('terry', 'gilliam', eric='idle', john='cleese')
110 def f2(): return 84
111 self.assertEqual(f2(), 84)
112 self.assertEqual(f2.dbval, (('terry', 'gilliam'),
113 dict(eric='idle', john='cleese')))
114
115 @noteargs(1, 2,)
116 def f3(): pass
117 self.assertEqual(f3.dbval, ((1, 2), {}))
118
119 def test_dbcheck(self):
120 @dbcheck('args[1] is not None')
121 def f(a, b):
122 return a + b
123 self.assertEqual(f(1, 2), 3)
124 self.assertRaises(DbcheckError, f, 1, None)
125
126 def test_memoize(self):
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000127 counts = {}
Michael W. Hudson0ccff072004-08-17 17:29:16 +0000128
Brett Cannonadd33602004-08-15 07:21:25 +0000129 @memoize
130 @countcalls(counts)
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000131 def double(x):
132 return x * 2
Neal Norwitz221085d2007-02-25 20:55:47 +0000133 self.assertEqual(double.__name__, 'double')
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000134
135 self.assertEqual(counts, dict(double=0))
136
137 # Only the first call with a given argument bumps the call count:
138 #
139 self.assertEqual(double(2), 4)
140 self.assertEqual(counts['double'], 1)
141 self.assertEqual(double(2), 4)
142 self.assertEqual(counts['double'], 1)
143 self.assertEqual(double(3), 6)
144 self.assertEqual(counts['double'], 2)
145
146 # Unhashable arguments do not get memoized:
147 #
148 self.assertEqual(double([10]), [10, 10])
149 self.assertEqual(counts['double'], 3)
150 self.assertEqual(double([10]), [10, 10])
151 self.assertEqual(counts['double'], 4)
152
153 def test_errors(self):
154 # Test syntax restrictions - these are all compile-time errors:
155 #
156 for expr in [ "1+2", "x[3]", "(1, 2)" ]:
157 # Sanity check: is expr is a valid expression by itself?
158 compile(expr, "testexpr", "exec")
Tim Peters6db15d72004-08-04 02:36:18 +0000159
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000160 codestr = "@%s\ndef f(): pass" % expr
161 self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
162
Michael W. Hudson0ccff072004-08-17 17:29:16 +0000163 # You can't put multiple decorators on a single line:
164 #
165 self.assertRaises(SyntaxError, compile,
166 "@f1 @f2\ndef f(): pass", "test", "exec")
167
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000168 # Test runtime errors
169
170 def unimp(func):
171 raise NotImplementedError
172 context = dict(nullval=None, unimp=unimp)
Tim Peters6db15d72004-08-04 02:36:18 +0000173
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000174 for expr, exc in [ ("undef", NameError),
175 ("nullval", TypeError),
176 ("nullval.attr", AttributeError),
177 ("unimp", NotImplementedError)]:
178 codestr = "@%s\ndef f(): pass\nassert f() is None" % expr
179 code = compile(codestr, "test", "exec")
180 self.assertRaises(exc, eval, code, context)
181
182 def test_double(self):
183 class C(object):
184 @funcattrs(abc=1, xyz="haha")
185 @funcattrs(booh=42)
186 def foo(self): return 42
187 self.assertEqual(C().foo(), 42)
188 self.assertEqual(C.foo.abc, 1)
189 self.assertEqual(C.foo.xyz, "haha")
190 self.assertEqual(C.foo.booh, 42)
191
192 def test_order(self):
Brett Cannoncaa97982004-08-19 03:48:24 +0000193 # Test that decorators are applied in the proper order to the function
194 # they are decorating.
195 def callnum(num):
196 """Decorator factory that returns a decorator that replaces the
197 passed-in function with one that returns the value of 'num'"""
198 def deco(func):
199 return lambda: num
200 return deco
201 @callnum(2)
202 @callnum(1)
203 def foo(): return 42
204 self.assertEqual(foo(), 2,
205 "Application order of decorators is incorrect")
Brett Cannonadd33602004-08-15 07:21:25 +0000206
Michael W. Hudson0ccff072004-08-17 17:29:16 +0000207 def test_eval_order(self):
208 # Evaluating a decorated function involves four steps for each
209 # decorator-maker (the function that returns a decorator):
210 #
211 # 1: Evaluate the decorator-maker name
212 # 2: Evaluate the decorator-maker arguments (if any)
213 # 3: Call the decorator-maker to make a decorator
214 # 4: Call the decorator
215 #
216 # When there are multiple decorators, these steps should be
217 # performed in the above order for each decorator, but we should
218 # iterate through the decorators in the reverse of the order they
219 # appear in the source.
220
221 actions = []
222
223 def make_decorator(tag):
224 actions.append('makedec' + tag)
225 def decorate(func):
226 actions.append('calldec' + tag)
227 return func
228 return decorate
229
230 class NameLookupTracer (object):
231 def __init__(self, index):
232 self.index = index
233
234 def __getattr__(self, fname):
235 if fname == 'make_decorator':
236 opname, res = ('evalname', make_decorator)
237 elif fname == 'arg':
238 opname, res = ('evalargs', str(self.index))
239 else:
240 assert False, "Unknown attrname %s" % fname
241 actions.append('%s%d' % (opname, self.index))
242 return res
243
244 c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])
245
246 expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
247 'evalname2', 'evalargs2', 'makedec2',
248 'evalname3', 'evalargs3', 'makedec3',
249 'calldec3', 'calldec2', 'calldec1' ]
250
251 actions = []
252 @c1.make_decorator(c1.arg)
253 @c2.make_decorator(c2.arg)
254 @c3.make_decorator(c3.arg)
255 def foo(): return 42
256 self.assertEqual(foo(), 42)
257
258 self.assertEqual(actions, expected_actions)
259
260 # Test the equivalence claim in chapter 7 of the reference manual.
261 #
262 actions = []
263 def bar(): return 42
264 bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
265 self.assertEqual(bar(), 42)
266 self.assertEqual(actions, expected_actions)
Anthony Baxterc2a5a632004-08-02 06:10:11 +0000267
Guido van Rossumd59da4b2007-05-22 18:11:13 +0000268class TestClassDecorators(unittest.TestCase):
269
270 def test_simple(self):
271 def plain(x):
272 x.extra = 'Hello'
273 return x
274 @plain
275 class C(object): pass
276 self.assertEqual(C.extra, 'Hello')
277
278 def test_double(self):
279 def ten(x):
280 x.extra = 10
281 return x
282 def add_five(x):
283 x.extra += 5
284 return x
285
286 @add_five
287 @ten
288 class C(object): pass
289 self.assertEqual(C.extra, 15)
290
291 def test_order(self):
292 def applied_first(x):
293 x.extra = 'first'
294 return x
295 def applied_second(x):
296 x.extra = 'second'
297 return x
298 @applied_second
299 @applied_first
300 class C(object): pass
301 self.assertEqual(C.extra, 'second')
302
Zachary Ware38c707e2015-04-13 15:00:43 -0500303if __name__ == "__main__":
304 unittest.main()