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 |
|
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