##// END OF EJS Templates
fixed nose_parametrized for py25 compat
marcink -
r2556:60c48410 beta
parent child Browse files
Show More
@@ -1,238 +1,238 b''
1 import re
1 import re
2 import new
2 import new
3 import inspect
3 import inspect
4 import logging
4 import logging
5 import logging.handlers
5 import logging.handlers
6 from functools import wraps
6 from functools import wraps
7
7
8 from nose.tools import nottest
8 from nose.tools import nottest
9 from unittest import TestCase
9 from unittest import TestCase
10
10
11
11
12 def _terrible_magic_get_defining_classes():
12 def _terrible_magic_get_defining_classes():
13 """ Returns the set of parent classes of the class currently being defined.
13 """ Returns the set of parent classes of the class currently being defined.
14 Will likely only work if called from the ``parameterized`` decorator.
14 Will likely only work if called from the ``parameterized`` decorator.
15 This function is entirely @brandon_rhodes's fault, as he suggested
15 This function is entirely @brandon_rhodes's fault, as he suggested
16 the implementation: http://stackoverflow.com/a/8793684/71522
16 the implementation: http://stackoverflow.com/a/8793684/71522
17 """
17 """
18 stack = inspect.stack()
18 stack = inspect.stack()
19 if len(stack) <= 4:
19 if len(stack) <= 4:
20 return []
20 return []
21 frame = stack[3]
21 frame = stack[3]
22 code_context = frame[4][0].strip()
22 code_context = frame[4][0].strip()
23 if not code_context.startswith("class "):
23 if not code_context.startswith("class "):
24 return []
24 return []
25 _, parents = code_context.split("(", 1)
25 _, parents = code_context.split("(", 1)
26 parents, _ = parents.rsplit(")", 1)
26 parents, _ = parents.rsplit(")", 1)
27 return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
27 return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals)
28
28
29
29
30 def parameterized(input):
30 def parameterized(input):
31 """ Parameterize a test case:
31 """ Parameterize a test case:
32 >>> add1_tests = [(1, 2), (2, 3)]
32 >>> add1_tests = [(1, 2), (2, 3)]
33 >>> class TestFoo(object):
33 >>> class TestFoo(object):
34 ... @parameterized(add1_tests)
34 ... @parameterized(add1_tests)
35 ... def test_add1(self, input, expected):
35 ... def test_add1(self, input, expected):
36 ... assert_equal(add1(input), expected)
36 ... assert_equal(add1(input), expected)
37 >>> @parameterized(add1_tests)
37 >>> @parameterized(add1_tests)
38 ... def test_add1(input, expected):
38 ... def test_add1(input, expected):
39 ... assert_equal(add1(input), expected)
39 ... assert_equal(add1(input), expected)
40 >>>
40 >>>
41 """
41 """
42
42
43 if not hasattr(input, "__iter__"):
43 if not hasattr(input, "__iter__"):
44 raise ValueError("expected iterable input; got %r" % (input,))
44 raise ValueError("expected iterable input; got %r" % (input,))
45
45
46 def parameterized_helper(f):
46 def parameterized_helper(f):
47 attached_instance_method = [False]
47 attached_instance_method = [False]
48
48
49 parent_classes = _terrible_magic_get_defining_classes()
49 parent_classes = _terrible_magic_get_defining_classes()
50 if any(issubclass(cls, TestCase) for cls in parent_classes):
50 if any(issubclass(cls, TestCase) for cls in parent_classes):
51 raise Exception("Warning: '@parameterized' tests won't work "
51 raise Exception("Warning: '@parameterized' tests won't work "
52 "inside subclasses of 'TestCase' - use "
52 "inside subclasses of 'TestCase' - use "
53 "'@parameterized.expand' instead")
53 "'@parameterized.expand' instead")
54
54
55 @wraps(f)
55 @wraps(f)
56 def parameterized_helper_method(self=None):
56 def parameterized_helper_method(self=None):
57 if self is not None and not attached_instance_method[0]:
57 if self is not None and not attached_instance_method[0]:
58 # confusingly, we need to create a named instance method and
58 # confusingly, we need to create a named instance method and
59 # attach that to the class...
59 # attach that to the class...
60 cls = self.__class__
60 cls = self.__class__
61 im_f = new.instancemethod(f, None, cls)
61 im_f = new.instancemethod(f, None, cls)
62 setattr(cls, f.__name__, im_f)
62 setattr(cls, f.__name__, im_f)
63 attached_instance_method[0] = True
63 attached_instance_method[0] = True
64 for args in input:
64 for args in input:
65 if isinstance(args, basestring):
65 if isinstance(args, basestring):
66 args = [args]
66 args = [args]
67 # ... then pull that named instance method off, turning it into
67 # ... then pull that named instance method off, turning it into
68 # a bound method ...
68 # a bound method ...
69 if self is not None:
69 if self is not None:
70 args = [getattr(self, f.__name__)] + list(args)
70 args = [getattr(self, f.__name__)] + list(args)
71 else:
71 else:
72 args = [f] + list(args)
72 args = [f] + list(args)
73 # ... then yield that as a tuple. If those steps aren't
73 # ... then yield that as a tuple. If those steps aren't
74 # followed precicely, Nose gets upset and doesn't run the test
74 # followed precicely, Nose gets upset and doesn't run the test
75 # or doesn't run setup methods.
75 # or doesn't run setup methods.
76 yield tuple(args)
76 yield tuple(args)
77
77
78 f.__name__ = "_helper_for_%s" % (f.__name__,)
78 f.__name__ = "_helper_for_%s" % (f.__name__,)
79 parameterized_helper_method.parameterized_input = input
79 parameterized_helper_method.parameterized_input = input
80 parameterized_helper_method.parameterized_func = f
80 parameterized_helper_method.parameterized_func = f
81 return parameterized_helper_method
81 return parameterized_helper_method
82
82
83 return parameterized_helper
83 return parameterized_helper
84
84
85
85
86 def to_safe_name(s):
86 def to_safe_name(s):
87 return re.sub("[^a-zA-Z0-9_]", "", s)
87 return re.sub("[^a-zA-Z0-9_]", "", s)
88
88
89
89
90 def parameterized_expand_helper(func_name, func, args):
90 def parameterized_expand_helper(func_name, func, args):
91 def parameterized_expand_helper_helper(self=()):
91 def parameterized_expand_helper_helper(self=()):
92 if self != ():
92 if self != ():
93 self = (self,)
93 self = (self,)
94 return func(*(self + args))
94 return func(*(self + args))
95 parameterized_expand_helper_helper.__name__ = func_name
95 parameterized_expand_helper_helper.__name__ = func_name
96 return parameterized_expand_helper_helper
96 return parameterized_expand_helper_helper
97
97
98
98
99 def parameterized_expand(input):
99 def parameterized_expand(input):
100 """ A "brute force" method of parameterizing test cases. Creates new test
100 """ A "brute force" method of parameterizing test cases. Creates new test
101 cases and injects them into the namespace that the wrapped function
101 cases and injects them into the namespace that the wrapped function
102 is being defined in. Useful for parameterizing tests in subclasses
102 is being defined in. Useful for parameterizing tests in subclasses
103 of 'UnitTest', where Nose test generators don't work.
103 of 'UnitTest', where Nose test generators don't work.
104
104
105 >>> @parameterized.expand([("foo", 1, 2)])
105 >>> @parameterized.expand([("foo", 1, 2)])
106 ... def test_add1(name, input, expected):
106 ... def test_add1(name, input, expected):
107 ... actual = add1(input)
107 ... actual = add1(input)
108 ... assert_equal(actual, expected)
108 ... assert_equal(actual, expected)
109 ...
109 ...
110 >>> locals()
110 >>> locals()
111 ... 'test_add1_foo_0': <function ...> ...
111 ... 'test_add1_foo_0': <function ...> ...
112 >>>
112 >>>
113 """
113 """
114
114
115 def parameterized_expand_wrapper(f):
115 def parameterized_expand_wrapper(f):
116 stack = inspect.stack()
116 stack = inspect.stack()
117 frame = stack[1]
117 frame = stack[1]
118 frame_locals = frame[0].f_locals
118 frame_locals = frame[0].f_locals
119
119
120 base_name = f.__name__
120 base_name = f.__name__
121 for num, args in enumerate(input):
121 for num, args in enumerate(input):
122 name_suffix = "_%s" % (num,)
122 name_suffix = "_%s" % (num,)
123 if len(args) > 0 and isinstance(args[0], basestring):
123 if len(args) > 0 and isinstance(args[0], basestring):
124 name_suffix += "_" + to_safe_name(args[0])
124 name_suffix += "_" + to_safe_name(args[0])
125 name = base_name + name_suffix
125 name = base_name + name_suffix
126 new_func = parameterized_expand_helper(name, f, args)
126 new_func = parameterized_expand_helper(name, f, args)
127 frame_locals[name] = new_func
127 frame_locals[name] = new_func
128 return nottest(f)
128 return nottest(f)
129 return parameterized_expand_wrapper
129 return parameterized_expand_wrapper
130
130
131 parameterized.expand = parameterized_expand
131 parameterized.expand = parameterized_expand
132
132
133
133
134 def assert_contains(haystack, needle):
134 def assert_contains(haystack, needle):
135 if needle not in haystack:
135 if needle not in haystack:
136 raise AssertionError("%r not in %r" % (needle, haystack))
136 raise AssertionError("%r not in %r" % (needle, haystack))
137
137
138
138
139 def assert_not_contains(haystack, needle):
139 def assert_not_contains(haystack, needle):
140 if needle in haystack:
140 if needle in haystack:
141 raise AssertionError("%r in %r" % (needle, haystack))
141 raise AssertionError("%r in %r" % (needle, haystack))
142
142
143
143
144 def imported_from_test():
144 def imported_from_test():
145 """ Returns true if it looks like this module is being imported by unittest
145 """ Returns true if it looks like this module is being imported by unittest
146 or nose. """
146 or nose. """
147 import re
147 import re
148 import inspect
148 import inspect
149 nose_re = re.compile(r"\bnose\b")
149 nose_re = re.compile(r"\bnose\b")
150 unittest_re = re.compile(r"\bunittest2?\b")
150 unittest_re = re.compile(r"\bunittest2?\b")
151 for frame in inspect.stack():
151 for frame in inspect.stack():
152 file = frame[1]
152 file = frame[1]
153 if nose_re.search(file) or unittest_re.search(file):
153 if nose_re.search(file) or unittest_re.search(file):
154 return True
154 return True
155 return False
155 return False
156
156
157
157
158 def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
158 def assert_raises(func, exc_type, str_contains=None, repr_contains=None):
159 try:
159 try:
160 func()
160 func()
161 except exc_type as e:
161 except exc_type, e:
162 if str_contains is not None and str_contains not in str(e):
162 if str_contains is not None and str_contains not in str(e):
163 raise AssertionError("%s raised, but %r does not contain %r"
163 raise AssertionError("%s raised, but %r does not contain %r"
164 % (exc_type, str(e), str_contains))
164 % (exc_type, str(e), str_contains))
165 if repr_contains is not None and repr_contains not in repr(e):
165 if repr_contains is not None and repr_contains not in repr(e):
166 raise AssertionError("%s raised, but %r does not contain %r"
166 raise AssertionError("%s raised, but %r does not contain %r"
167 % (exc_type, repr(e), repr_contains))
167 % (exc_type, repr(e), repr_contains))
168 return e
168 return e
169 else:
169 else:
170 raise AssertionError("%s not raised" % (exc_type,))
170 raise AssertionError("%s not raised" % (exc_type,))
171
171
172
172
173 log_handler = None
173 log_handler = None
174
174
175
175
176 def setup_logging():
176 def setup_logging():
177 """ Configures a log handler which will capure log messages during a test.
177 """ Configures a log handler which will capure log messages during a test.
178 The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
178 The ``logged_messages`` and ``assert_no_errors_logged`` functions can be
179 used to make assertions about these logged messages.
179 used to make assertions about these logged messages.
180
180
181 For example::
181 For example::
182
182
183 from ensi_common.testing import (
183 from ensi_common.testing import (
184 setup_logging, teardown_logging, assert_no_errors_logged,
184 setup_logging, teardown_logging, assert_no_errors_logged,
185 assert_logged,
185 assert_logged,
186 )
186 )
187
187
188 class TestWidget(object):
188 class TestWidget(object):
189 def setup(self):
189 def setup(self):
190 setup_logging()
190 setup_logging()
191
191
192 def teardown(self):
192 def teardown(self):
193 assert_no_errors_logged()
193 assert_no_errors_logged()
194 teardown_logging()
194 teardown_logging()
195
195
196 def test_that_will_fail(self):
196 def test_that_will_fail(self):
197 log.warning("this warning message will trigger a failure")
197 log.warning("this warning message will trigger a failure")
198
198
199 def test_that_will_pass(self):
199 def test_that_will_pass(self):
200 log.info("but info messages are ok")
200 log.info("but info messages are ok")
201 assert_logged("info messages are ok")
201 assert_logged("info messages are ok")
202 """
202 """
203
203
204 global log_handler
204 global log_handler
205 if log_handler is not None:
205 if log_handler is not None:
206 logging.getLogger().removeHandler(log_handler)
206 logging.getLogger().removeHandler(log_handler)
207 log_handler = logging.handlers.BufferingHandler(1000)
207 log_handler = logging.handlers.BufferingHandler(1000)
208 formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
208 formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s")
209 log_handler.setFormatter(formatter)
209 log_handler.setFormatter(formatter)
210 logging.getLogger().addHandler(log_handler)
210 logging.getLogger().addHandler(log_handler)
211
211
212
212
213 def teardown_logging():
213 def teardown_logging():
214 global log_handler
214 global log_handler
215 if log_handler is not None:
215 if log_handler is not None:
216 logging.getLogger().removeHandler(log_handler)
216 logging.getLogger().removeHandler(log_handler)
217 log_handler = None
217 log_handler = None
218
218
219
219
220 def logged_messages():
220 def logged_messages():
221 assert log_handler, "setup_logging not called"
221 assert log_handler, "setup_logging not called"
222 return [(log_handler.format(record), record) for record in log_handler.buffer]
222 return [(log_handler.format(record), record) for record in log_handler.buffer]
223
223
224
224
225 def assert_no_errors_logged():
225 def assert_no_errors_logged():
226 for _, record in logged_messages():
226 for _, record in logged_messages():
227 if record.levelno >= logging.WARNING:
227 if record.levelno >= logging.WARNING:
228 # Assume that the nose log capture plugin is being used, so it will
228 # Assume that the nose log capture plugin is being used, so it will
229 # show the exception.
229 # show the exception.
230 raise AssertionError("an unexpected error was logged")
230 raise AssertionError("an unexpected error was logged")
231
231
232
232
233 def assert_logged(expected_msg_contents):
233 def assert_logged(expected_msg_contents):
234 for msg, _ in logged_messages():
234 for msg, _ in logged_messages():
235 if expected_msg_contents in msg:
235 if expected_msg_contents in msg:
236 return
236 return
237 raise AssertionError("no logged message contains %r"
237 raise AssertionError("no logged message contains %r"
238 % (expected_msg_contents,))
238 % (expected_msg_contents,))
General Comments 0
You need to be logged in to leave comments. Login now