Merge lp:~jimbaker/pyjuju/standardize-log-testing into lp:pyjuju
- standardize-log-testing
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kapil Thangavelu |
Approved revision: | 258 |
Merged at revision: | 258 |
Proposed branch: | lp:~jimbaker/pyjuju/standardize-log-testing |
Merge into: | lp:pyjuju |
Diff against target: |
557 lines (+62/-166) 7 files modified
ensemble/control/tests/test_status.py (+5/-5) ensemble/hooks/invoker.py (+1/-1) ensemble/hooks/tests/test_arguments.py (+2/-5) ensemble/hooks/tests/test_cli.py (+7/-12) ensemble/hooks/tests/test_communications.py (+12/-13) ensemble/hooks/tests/test_invoker.py (+35/-49) ensemble/lib/testing.py (+0/-81) |
To merge this branch: | bzr merge lp:~jimbaker/pyjuju/standardize-log-testing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kapil Thangavelu (community) | Approve | ||
Gustavo Niemeyer | Approve | ||
Review via email: mp+64568@code.launchpad.net |
Commit message
Description of the change
This branch standardizes the log testing as seen in hook tests and
test_status and makes it more robust as follows:
* Removed usage and definition of save_logging, reset_logging, and
assertInDefa
capture_logging and assertIn, including the use of custom
formatters as appropriate. It seems like that the use of
save_logging and related functions was to ensure that any changes
in formatting or log levels for a log would not interfere with
other tests. However, capture_logging fully ensures the
independence of such operations from test to test.
* Refactored test_status to use our cooperative inheritance
convention.
* HookProtocol now uses processEnded to ensure that output streams
are ready for reading. This was the problem that prompted the
investigation to begin with: why are some hook tests not able to
loop properly. This change fixes this problem.
* Removed unnecessary test timeout override in client-server
communications tests. It's not clear why a smaller timeout here vs
in any other place is useful.
Jim Baker (jimbaker) wrote : | # |
On Tue, Jun 14, 2011 at 1:44 PM, Gustavo Niemeyer <email address hidden>wrote:
> Review: Needs Information
> This looks like a nice clean up, thanks Jim.
>
> One question:
>
> [1]
>
> 8 - yield ServiceStateMan
> 9 - yield ControlToolTest
> 10 - self.save_logging()
> 11 + yield super(StatusTes
> 12 + self.log = self.capture_
>
> Have you checked that _all_ of the base classes conform to this protocol,
> yielding on
> every call to setUp? If one does not, we'll have issues.
>
I have rechecked that, and yes, they all conform to the cooperative
inheritance protocol. This was addressed in r120 in trunk for all ancestor
classes here. We may want to scan other test classes to see if there's been
other slippage since then, of course with any possible fixes in a different
branch.
Gustavo Niemeyer (niemeyer) wrote : | # |
LGTM then, +1!
Jim Baker (jimbaker) wrote : | # |
Some things to add, especially with respect to our new weekly report:
* Removing the non-standardized log testing initially created more
timing issues around test teardown - certain tests would now
routinely fail. Further investigation revealed that this was
because of the use of processExit instead of processEnded. However,
I didn't look into detail as to why the removed log testing code
was obscuring this problem; perhaps, it was due to the extra
synchronization around the logging lock or some other aspect seen
in saving and restoring settings.
* Ensuring the output was ready before reading actually fixed a
user-visible bug. In the previous version of the code, it was
possible that output from hook invocation would potentially be
lost.
Gustavo Niemeyer (niemeyer) wrote : | # |
Putting comments in a merge proposal is not enough for Ahmed to note it in the
weekly report. The technical lingo also has to be tamed for it to make sense
for the report. I barely understand what you mean, let alone a user who's
interested in managing formulas.
Jim Baker (jimbaker) wrote : | # |
Oh sure, I don't expect Ahmed to pick it up from this merge proposal
discussion, or that it is even in a form that is suitable for such a report
for formula writers. However, the bug was initiated in part to do some sort
of reporting back to a mailing list. So if this is of interest, I can shape
the writing of these conclusions into an appropriate form for such a report.
This can also help address the theme that this development phase is, in
part, working on addressing technical debt.
On the other hand, this might be considered too obscure for a general
audience (and not even that interesting, except to a narrow group of
Twisted-loving developers). Ordinarily that would be my thought on a set of
bug fixes like this.
Please tell me what you think works best!
On Tue, Jun 14, 2011 at 3:18 PM, Gustavo Niemeyer <email address hidden>wrote:
> Putting comments in a merge proposal is not enough for Ahmed to note it in
> the
> weekly report. The technical lingo also has to be tamed for it to make
> sense
> for the report. I barely understand what you mean, let alone a user who's
> interested in managing formulas.
> --
>
> https:/
> You are the owner of lp:~jimbaker/ensemble/standardize-log-testing.
>
Kapil Thangavelu (hazmat) wrote : | # |
This looks good to me, thanks for looking into this, one comment though.
+ yield self.sleep(1)
looks suspicious. does it really need to sleep for a whole second?
Jim Baker (jimbaker) wrote : | # |
Yikes, that was from my debugging the timing issues. About to be removed!
On Wed, Jun 15, 2011 at 4:26 PM, Kapil Thangavelu <
<email address hidden>> wrote:
> Review: Approve
> This looks good to me, thanks for looking into this, one comment though.
>
> + yield self.sleep(1)
>
> looks suspicious. does it really need to sleep for a whole second?
> --
>
> https:/
> You are the owner of lp:~jimbaker/ensemble/standardize-log-testing.
>
Preview Diff
1 | === modified file 'ensemble/control/tests/test_status.py' |
2 | --- ensemble/control/tests/test_status.py 2011-04-18 17:06:34 +0000 |
3 | +++ ensemble/control/tests/test_status.py 2011-06-15 22:43:24 +0000 |
4 | @@ -24,9 +24,8 @@ |
5 | |
6 | @inlineCallbacks |
7 | def setUp(self): |
8 | - yield ServiceStateManagerTestBase.setUp(self) |
9 | - yield ControlToolTest.setUp(self) |
10 | - self.save_logging() |
11 | + yield super(StatusTestBase, self).setUp() |
12 | + self.log = self.capture_logging() |
13 | |
14 | config = { |
15 | "ensemble": "environments", |
16 | @@ -385,8 +384,9 @@ |
17 | |
18 | yield status.collect( |
19 | None, self.provider, self.client, logging.getLogger()) |
20 | - self.assertInDefaultLog( |
21 | - "Machine provider information missing: machine 7") |
22 | + self.assertIn( |
23 | + "Machine provider information missing: machine 7", |
24 | + self.log.getvalue()) |
25 | |
26 | @inlineCallbacks |
27 | def test_render_yaml(self): |
28 | |
29 | === modified file 'ensemble/hooks/invoker.py' |
30 | --- ensemble/hooks/invoker.py 2011-04-04 15:04:50 +0000 |
31 | +++ ensemble/hooks/invoker.py 2011-06-15 22:43:24 +0000 |
32 | @@ -28,7 +28,7 @@ |
33 | def errReceived(self, data): |
34 | self._log.error(data) |
35 | |
36 | - def processExited(self, reason): |
37 | + def processEnded(self, reason): |
38 | exit_code = reason.value.exitCode |
39 | if exit_code in (0, None): |
40 | self.deferred.callback(exit_code) |
41 | |
42 | === modified file 'ensemble/hooks/tests/test_arguments.py' |
43 | --- ensemble/hooks/tests/test_arguments.py 2011-06-01 17:43:29 +0000 |
44 | +++ ensemble/hooks/tests/test_arguments.py 2011-06-15 22:43:24 +0000 |
45 | @@ -34,9 +34,6 @@ |
46 | verify that the socket argument is accepted from a command |
47 | line flag, or the environment or raises an error. |
48 | """ |
49 | - self.save_logging() |
50 | - self.reset_logging() |
51 | - |
52 | self.setup_environment() |
53 | os.environ.pop("ENSEMBLE_AGENT_SOCKET", None) |
54 | |
55 | @@ -57,8 +54,8 @@ |
56 | error = self.failUnlessRaises(SystemExit, cli.parse_args) |
57 | self.assertEquals("No ENSEMBLE_AGENT_SOCKET/-s option found", |
58 | str(error)) |
59 | - self.assertInDefaultLog("No ENSEMBLE_AGENT_SOCKET/-s option found", |
60 | - err) |
61 | + self.assertIn("No ENSEMBLE_AGENT_SOCKET/-s option found", |
62 | + err.getvalue()) |
63 | |
64 | def test_single_keyvalue(self): |
65 | """ |
66 | |
67 | === modified file 'ensemble/hooks/tests/test_cli.py' |
68 | --- ensemble/hooks/tests/test_cli.py 2011-06-09 17:19:26 +0000 |
69 | +++ ensemble/hooks/tests/test_cli.py 2011-06-15 22:43:24 +0000 |
70 | @@ -234,7 +234,7 @@ |
71 | error = self.failUnlessRaises(SystemExit, cli, |
72 | "test_unit foobar".split()) |
73 | self.assertEquals(error.code, 2) |
74 | - self.assertInDefaultLog("No ENSEMBLE_AGENT_SOCKET", error_log) |
75 | + self.assertIn("No ENSEMBLE_AGENT_SOCKET", error_log.getvalue()) |
76 | |
77 | def test_cli_no_client_id(self): |
78 | # don't set up the environment with a socket |
79 | @@ -252,13 +252,11 @@ |
80 | error = self.failUnlessRaises(SystemExit, cli, |
81 | "test_unit foobar".split()) |
82 | self.assertEquals(error.code, 2) |
83 | - self.assertInDefaultLog("No ENSEMBLE_CLIENT_ID", error_log) |
84 | + self.assertIn("No ENSEMBLE_CLIENT_ID", error_log.getvalue()) |
85 | |
86 | def test_log_level(self): |
87 | self.setup_environment() |
88 | self.change_args(__file__) |
89 | - self.save_logging() |
90 | - self.reset_logging() |
91 | |
92 | cli = GetCli() |
93 | cli.manage_connection = False |
94 | @@ -266,9 +264,10 @@ |
95 | self.mocker.replay() |
96 | |
97 | # bad log level |
98 | + log = self.capture_logging() |
99 | cli.setup_parser() |
100 | cli.parse_args("--log-level XYZZY test_unit".split()) |
101 | - self.assertInDefaultLog("Invalid log level") |
102 | + self.assertIn("Invalid log level", log.getvalue()) |
103 | # still get a default |
104 | self.assertEqual(cli.options.log_level, logging.INFO) |
105 | |
106 | @@ -283,8 +282,6 @@ |
107 | def test_log_format(self): |
108 | self.setup_environment() |
109 | self.change_args(__file__) |
110 | - self.save_logging() |
111 | - self.reset_logging() |
112 | |
113 | cli = NoopCli() |
114 | |
115 | @@ -301,14 +298,12 @@ |
116 | self.setup_exit(0) |
117 | self.mocker.replay() |
118 | cli("--format missing".split()) |
119 | - self.assertInDefaultLog("missing", err) |
120 | - self.assertInDefaultLog("Namespace", out) |
121 | + self.assertIn("missing", err.getvalue()) |
122 | + self.assertIn("Namespace", out.getvalue()) |
123 | |
124 | def test_render_error(self): |
125 | self.setup_environment() |
126 | self.change_args(__file__) |
127 | - self.save_logging() |
128 | - self.reset_logging() |
129 | |
130 | cli = ErrorCli() |
131 | # bad log level |
132 | @@ -318,7 +313,7 @@ |
133 | self.mocker.replay() |
134 | cli("") |
135 | # make sure we got a traceback on stderr |
136 | - self.assertInDefaultLog("Checking render error", err) |
137 | + self.assertIn("Checking render error", err.getvalue()) |
138 | |
139 | def test_parse_log_level(self): |
140 | self.assertEquals(parse_log_level("INFO"), logging.INFO) |
141 | |
142 | === modified file 'ensemble/hooks/tests/test_communications.py' |
143 | --- ensemble/hooks/tests/test_communications.py 2011-06-04 03:24:31 +0000 |
144 | +++ ensemble/hooks/tests/test_communications.py 2011-06-15 22:43:24 +0000 |
145 | @@ -191,9 +191,13 @@ |
146 | """ |
147 | client_protocol = UnitAgentClient |
148 | server_protocol = UnitAgentServer |
149 | - # short timeout, these are not long running requests when |
150 | - # correct |
151 | - timeout = 1.0 |
152 | + |
153 | + @defer.inlineCallbacks |
154 | + def setUp(self): |
155 | + yield super(TestCommunications, self).setUp() |
156 | + self.log = self.capture_logging( |
157 | + level=logging.DEBUG, |
158 | + formatter=logging.Formatter("%(levelname)s %(message)s")) |
159 | |
160 | @defer.inlineCallbacks |
161 | def test_relation_get_command(self): |
162 | @@ -252,18 +256,14 @@ |
163 | |
164 | @defer.inlineCallbacks |
165 | def test_log_command(self): |
166 | - self.save_logging() |
167 | # This is the default calling convention from clients |
168 | yield self.client.log(logging.WARNING, ["This", "is", "a", "WARNING"]) |
169 | yield self.client.log(logging.INFO, "This is INFO") |
170 | yield self.client.log(logging.CRITICAL, ["This is CRITICAL"]) |
171 | |
172 | - self.assertInDefaultLog("WARNING This is a WARNING", |
173 | - self.server_factory._agent_io) |
174 | - self.assertInDefaultLog("INFO This is INFO", |
175 | - self.server_factory._agent_io) |
176 | - self.assertInDefaultLog("CRITICAL This is CRITICAL", |
177 | - self.server_factory._agent_io) |
178 | + self.assertIn("WARNING This is a WARNING", self.log.getvalue()) |
179 | + self.assertIn("INFO This is INFO", self.log.getvalue()) |
180 | + self.assertIn("CRITICAL This is CRITICAL", self.log.getvalue()) |
181 | |
182 | @defer.inlineCallbacks |
183 | def test_config_get_command(self): |
184 | @@ -299,7 +299,6 @@ |
185 | |
186 | @defer.inlineCallbacks |
187 | def test_port_commands(self): |
188 | - self.save_logging() |
189 | mock_service_unit_state = MockServiceState().get_unit_state("mock/0") |
190 | yield self.client.open_port("client-id", 80, "tcp") |
191 | self.assertEqual(mock_service_unit_state.ports, set([(80, "tcp")])) |
192 | @@ -308,9 +307,9 @@ |
193 | self.assertEqual(mock_service_unit_state.ports, set([(53, "udp")])) |
194 | yield self.client.close_port("client-id", 53, "udp") |
195 | self.assertEqual(mock_service_unit_state.ports, set()) |
196 | - self.assertInDefaultLog( |
197 | + self.assertIn( |
198 | "DEBUG opened 80/tcp\n" |
199 | "DEBUG opened 53/udp\n" |
200 | "DEBUG closed 80/tcp\n" |
201 | "DEBUG closed 53/udp\n", |
202 | - self.server_factory._agent_io) |
203 | + self.log.getvalue()) |
204 | |
205 | === modified file 'ensemble/hooks/tests/test_invoker.py' |
206 | --- ensemble/hooks/tests/test_invoker.py 2011-06-04 03:24:31 +0000 |
207 | +++ ensemble/hooks/tests/test_invoker.py 2011-06-15 22:43:24 +0000 |
208 | @@ -8,6 +8,7 @@ |
209 | import yaml |
210 | |
211 | from twisted.internet import defer |
212 | +from twisted.python.log import PythonLoggingObserver |
213 | |
214 | import ensemble |
215 | from ensemble import errors |
216 | @@ -318,6 +319,10 @@ |
217 | self.update_invoker_env("mysql/0", "wordpress/0") |
218 | self.socket_path = self.makeFile() |
219 | self.ua = MockUnitAgent(self.client, self.socket_path) |
220 | + self.log = self.capture_logging( |
221 | + formatter=logging.Formatter( |
222 | + "%(name)s:%(levelname)s:: %(message)s"), |
223 | + level=logging.DEBUG) |
224 | |
225 | @defer.inlineCallbacks |
226 | def tearDown(self): |
227 | @@ -358,14 +363,10 @@ |
228 | self.assertEqual(env["ENSEMBLE_UNIT_NAME"], "mysql/0") |
229 | |
230 | def test_missing_hook(self): |
231 | - self.save_logging() |
232 | - |
233 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
234 | self.failUnlessRaises(errors.FileNotFound, exe, "hook-missing") |
235 | |
236 | def test_noexec_hook(self): |
237 | - self.save_logging() |
238 | - |
239 | exe = self.ua.get_invoker("database", "add", "mysql/0", |
240 | self.relation) |
241 | hook = self.get_test_hook("noexec-hook") |
242 | @@ -377,29 +378,25 @@ |
243 | @defer.inlineCallbacks |
244 | def test_spawn_success(self): |
245 | """Validate hook with success from exit.""" |
246 | - self.save_logging() |
247 | - self.reset_logging() |
248 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
249 | result = yield exe(self.get_test_hook("success-hook")) |
250 | self.assertEqual(result, 0) |
251 | - self.assertInDefaultLog("WIN") |
252 | - self.assertInDefaultLog("exit code 0") |
253 | + self.assertIn("WIN", self.log.getvalue()) |
254 | + self.assertIn("exit code 0", self.log.getvalue()) |
255 | |
256 | @defer.inlineCallbacks |
257 | def test_spawn_fail(self): |
258 | """Validate hook with fail from exit.""" |
259 | - self.save_logging() |
260 | - self.reset_logging() |
261 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
262 | d = exe(self.get_test_hook("fail-hook")) |
263 | result = yield self.assertFailure(d, errors.FormulaInvocationError) |
264 | self.assertEqual(result.exit_code, 1) |
265 | # ERROR indicate the level name, we are checking that the |
266 | # proper level was logged here |
267 | - self.assertInDefaultLog("ERROR") |
268 | + self.assertIn("ERROR", self.log.getvalue()) |
269 | # and the message |
270 | - self.assertInDefaultLog("FAIL") |
271 | - self.assertInDefaultLog("exit code 1") |
272 | + self.assertIn("FAIL", self.log.getvalue()) |
273 | + self.assertIn("exit code 1", self.log.getvalue()) |
274 | |
275 | def test_path_setup(self): |
276 | """Validate that the path allows finding the executable.""" |
277 | @@ -460,8 +457,6 @@ |
278 | |
279 | def test_get_remote_unit_arg(self): |
280 | """Simple test around remote arg parsing.""" |
281 | - self.save_logging() |
282 | - |
283 | self.change_environment(ENSEMBLE_UNIT_NAME="mysql/0", |
284 | ENSEMBLE_CLIENT_ID="client_id", |
285 | ENSEMBLE_AGENT_SOCKET=self.socket_path) |
286 | @@ -516,8 +511,6 @@ |
287 | |
288 | @defer.inlineCallbacks |
289 | def test_logging(self): |
290 | - self.save_logging() |
291 | - self.reset_logging() |
292 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
293 | |
294 | # The echo hook will echo out the value |
295 | @@ -533,21 +526,19 @@ |
296 | result = yield exe(self.get_test_hook("echo-hook")) |
297 | self.assertEqual(result, 0) |
298 | |
299 | - self.assertInDefaultLog(message) |
300 | + self.assertIn(message, self.log.getvalue()) |
301 | # The 'error' was sent via ensemble-log |
302 | # to the UA. Our test UA has a fake log stream |
303 | # which we can check now |
304 | output = self.ua.log_file.getvalue() |
305 | - self.assertInDefaultLog("ERROR:: " + error) |
306 | - self.assertInDefaultLog("INFO:: " + default) |
307 | + self.assertIn("ERROR:: " + error, self.log.getvalue()) |
308 | + self.assertIn("INFO:: " + default, self.log.getvalue()) |
309 | |
310 | assert message not in output, """Log includes unintended messages""" |
311 | |
312 | @defer.inlineCallbacks |
313 | def test_spawn_cli_list_hook_smart(self): |
314 | """Validate the get hook can transmit values to the hook.""" |
315 | - self.save_logging() |
316 | - self.reset_logging() |
317 | exe = self.ua.get_invoker("database", "add", "mysql/0", |
318 | self.relation, |
319 | client_id="client_id") |
320 | @@ -568,13 +559,11 @@ |
321 | "--format=smart")) |
322 | |
323 | self.assertEqual(result, 0) |
324 | - self.assertInDefaultLog("alpha/0\nbeta/0\n") |
325 | + self.assertIn("alpha/0\nbeta/0\n", self.log.getvalue()) |
326 | |
327 | @defer.inlineCallbacks |
328 | def test_spawn_cli_list_hook_eval(self): |
329 | """Validate the get hook can transmit values to the hook.""" |
330 | - self.save_logging() |
331 | - self.reset_logging() |
332 | exe = self.ua.get_invoker("database", "add", "mysql/0", |
333 | self.relation, |
334 | client_id="client_id") |
335 | @@ -594,13 +583,11 @@ |
336 | "--format=eval")) |
337 | |
338 | self.assertEqual(result, 0) |
339 | - self.assertInDefaultLog("alpha/0 beta/0") |
340 | + self.assertIn("alpha/0 beta/0", self.log.getvalue()) |
341 | |
342 | @defer.inlineCallbacks |
343 | def test_spawn_cli_list_hook_json(self): |
344 | """Validate the get hook can transmit values to the hook.""" |
345 | - self.save_logging() |
346 | - self.reset_logging() |
347 | exe = self.ua.get_invoker("database", "add", "mysql/0", |
348 | self.relation, |
349 | client_id="client_id") |
350 | @@ -619,14 +606,14 @@ |
351 | result = yield exe(self.create_hook("relation-list", "--format json")) |
352 | |
353 | self.assertEqual(result, 0) |
354 | - self.assertInDefaultLog('["alpha/0", "beta/0"]') |
355 | + self.assertIn('["alpha/0", "beta/0"]', self.log.getvalue()) |
356 | |
357 | |
358 | class PortCommandsTest(RelationInvokerTestBase): |
359 | |
360 | def assertLogLines(self, expected): |
361 | """Asserts that the lines of `expected` exist in order in the log.""" |
362 | - logged = self.log_stream.getvalue().split("\n") |
363 | + logged = self.log.getvalue().split("\n") |
364 | expected = iter(expected) |
365 | for line in logged: |
366 | expected, peekat = itertools.tee(expected) |
367 | @@ -651,9 +638,6 @@ |
368 | @defer.inlineCallbacks |
369 | def test_open_and_close_ports(self): |
370 | """Verify that port hook commands run and changes are immediate.""" |
371 | - self.save_logging() |
372 | - self.reset_logging() |
373 | - |
374 | unit_state = self.mysql_states["unit"] |
375 | self.assertEqual((yield unit_state.get_open_ports()), []) |
376 | |
377 | @@ -705,8 +689,6 @@ |
378 | @defer.inlineCallbacks |
379 | def test_open_port_args(self): |
380 | """Verify that open-port properly reports arg parse errors.""" |
381 | - self.save_logging() |
382 | - self.reset_logging() |
383 | unit_state = self.mysql_states["unit"] |
384 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
385 | |
386 | @@ -714,31 +696,32 @@ |
387 | exe(self.create_hook("open-port", "80/invalid-protocol")), |
388 | errors.FormulaInvocationError) |
389 | self.assertEqual(result.exit_code, 2) |
390 | - self.assertInDefaultLog( |
391 | + self.assertIn( |
392 | "open-port: error: argument PORT[/PROTOCOL]: " |
393 | - "Invalid protocol, must be 'tcp' or 'udp', got 'invalid-protocol'") |
394 | + "Invalid protocol, must be 'tcp' or 'udp', got 'invalid-protocol'", |
395 | + self.log.getvalue()) |
396 | |
397 | result = yield self.assertFailure( |
398 | exe(self.create_hook("open-port", "0/tcp")), |
399 | errors.FormulaInvocationError) |
400 | self.assertEqual(result.exit_code, 2) |
401 | - self.assertInDefaultLog( |
402 | + self.assertIn( |
403 | "open-port: error: argument PORT[/PROTOCOL]: " |
404 | - "Invalid port, must be from 1 to 65535, got 0") |
405 | + "Invalid port, must be from 1 to 65535, got 0", |
406 | + self.log.getvalue()) |
407 | |
408 | result = yield self.assertFailure( |
409 | exe(self.create_hook("open-port", "80/udp/extra-info")), |
410 | errors.FormulaInvocationError) |
411 | self.assertEqual(result.exit_code, 2) |
412 | - self.assertInDefaultLog( |
413 | + self.assertIn( |
414 | "open-port: error: argument PORT[/PROTOCOL]: " |
415 | - "Invalid format for port/protocol, got '80/udp/extra-info") |
416 | + "Invalid format for port/protocol, got '80/udp/extra-info", |
417 | + self.log.getvalue()) |
418 | |
419 | @defer.inlineCallbacks |
420 | def test_close_port_args(self): |
421 | """Verify that close-port properly reports arg parse errors.""" |
422 | - self.save_logging() |
423 | - self.reset_logging() |
424 | unit_state = self.mysql_states["unit"] |
425 | exe = self.ua.get_invoker("database", "add", "mysql/0", self.relation) |
426 | |
427 | @@ -746,22 +729,25 @@ |
428 | exe(self.create_hook("close-port", "80/invalid-protocol")), |
429 | errors.FormulaInvocationError) |
430 | self.assertEqual(result.exit_code, 2) |
431 | - self.assertInDefaultLog( |
432 | + self.assertIn( |
433 | "close-port: error: argument PORT[/PROTOCOL]: " |
434 | - "Invalid protocol, must be 'tcp' or 'udp', got 'invalid-protocol'") |
435 | + "Invalid protocol, must be 'tcp' or 'udp', got 'invalid-protocol'", |
436 | + self.log.getvalue()) |
437 | |
438 | result = yield self.assertFailure( |
439 | exe(self.create_hook("close-port", "0/tcp")), |
440 | errors.FormulaInvocationError) |
441 | self.assertEqual(result.exit_code, 2) |
442 | - self.assertInDefaultLog( |
443 | + self.assertIn( |
444 | "close-port: error: argument PORT[/PROTOCOL]: " |
445 | - "Invalid port, must be from 1 to 65535, got 0") |
446 | + "Invalid port, must be from 1 to 65535, got 0", |
447 | + self.log.getvalue()) |
448 | |
449 | result = yield self.assertFailure( |
450 | exe(self.create_hook("close-port", "80/udp/extra-info")), |
451 | errors.FormulaInvocationError) |
452 | self.assertEqual(result.exit_code, 2) |
453 | - self.assertInDefaultLog( |
454 | + self.assertIn( |
455 | "close-port: error: argument PORT[/PROTOCOL]: " |
456 | - "Invalid format for port/protocol, got '80/udp/extra-info") |
457 | + "Invalid format for port/protocol, got '80/udp/extra-info", |
458 | + self.log.getvalue()) |
459 | |
460 | === modified file 'ensemble/lib/testing.py' |
461 | --- ensemble/lib/testing.py 2011-03-18 21:18:49 +0000 |
462 | +++ ensemble/lib/testing.py 2011-06-15 22:43:24 +0000 |
463 | @@ -6,7 +6,6 @@ |
464 | from twisted.internet.defer import Deferred, inlineCallbacks, returnValue |
465 | from twisted.internet import reactor |
466 | from twisted.trial.unittest import TestCase as TrialTestCase |
467 | -from twisted.python.log import PythonLoggingObserver |
468 | |
469 | from txzookeeper import ZookeeperClient |
470 | |
471 | @@ -55,86 +54,6 @@ |
472 | |
473 | return log_file |
474 | |
475 | - log_format = "%(name)s:%(levelname)s:: %(message)s" |
476 | - |
477 | - def save_logging(self): |
478 | - """ |
479 | - Save stdlib's logging settings (which are kept in globals) |
480 | - and restore them after a test run. |
481 | - """ |
482 | - logger_dict = logging.getLogger().manager.loggerDict |
483 | - logging._acquireLock() |
484 | - try: |
485 | - self.saved_handlers = logging._handlers.copy() |
486 | - self.saved_handler_list = logging._handlerList[:] |
487 | - self.saved_loggers = logger_dict.copy() |
488 | - self.saved_level_names = logging._levelNames.copy() |
489 | - finally: |
490 | - logging._releaseLock() |
491 | - |
492 | - self.root_logger = logging.getLogger("") |
493 | - self.original_logging_level = self.root_logger.getEffectiveLevel() |
494 | - |
495 | - self.log_stream = StringIO.StringIO() |
496 | - self.root_logger.setLevel(logging.DEBUG) |
497 | - self.root_hdlr = logging.StreamHandler(self.log_stream) |
498 | - self.root_formatter = logging.Formatter(self.log_format) |
499 | - self.root_hdlr.setFormatter(self.root_formatter) |
500 | - self.root_logger.addHandler(self.root_hdlr) |
501 | - self.log_observer = PythonLoggingObserver() |
502 | - self.log_observer.start() |
503 | - |
504 | - @self.addCleanup |
505 | - def restore_logging(): |
506 | - self.log_stream.close() |
507 | - self.root_logger.removeHandler(self.root_hdlr) |
508 | - while self.root_logger.handlers: |
509 | - h = self.root_logger.handlers[0] |
510 | - self.root_logger.removeHandler(h) |
511 | - h.close() |
512 | - self.root_logger.setLevel(self.original_logging_level) |
513 | - |
514 | - logging._acquireLock() |
515 | - try: |
516 | - logging._levelNames.clear() |
517 | - logging._levelNames.update(self.saved_level_names) |
518 | - logging._handlers.clear() |
519 | - logging._handlers.update(self.saved_handlers) |
520 | - logging._handlerList[:] = self.saved_handler_list |
521 | - loggerDict = logging.getLogger().manager.loggerDict |
522 | - loggerDict.clear() |
523 | - loggerDict.update(self.saved_loggers) |
524 | - finally: |
525 | - logging._releaseLock() |
526 | - self.log_observer.stop() |
527 | - |
528 | - def reset_logging(self): |
529 | - """ |
530 | - reset all logging to a normalized state. |
531 | - """ |
532 | - |
533 | - logger_dict = logging.getLogger().manager.loggerDict |
534 | - logging._acquireLock() |
535 | - try: |
536 | - logger_dict.clear() |
537 | - logging._handlers.clear() |
538 | - del logging._handlerList[:] |
539 | - logging.getLogger().manager.loggerDict = {} |
540 | - finally: |
541 | - logging._releaseLock() |
542 | - |
543 | - def assertInDefaultLog(self, expected_value, iobuffer=None): |
544 | - """ |
545 | - assertInDefaultLog(value, [iobuffer]) |
546 | - |
547 | - used in conjunction with self.save_logging() this will test |
548 | - for expected values in the default logging stream. An |
549 | - alternative StringIO buffer may be passed as a second value. |
550 | - """ |
551 | - stream = iobuffer or self.log_stream |
552 | - content = stream.getvalue() |
553 | - return self.assertIn(expected_value, content) |
554 | - |
555 | _missing_attr = object() |
556 | |
557 | def patch(self, object, attr, value): |
This looks like a nice clean up, thanks Jim.
One question:
[1]
8 - yield ServiceStateMan agerTestBase. setUp(self) .setUp( self) tBase, self).setUp() logging( )
9 - yield ControlToolTest
10 - self.save_logging()
11 + yield super(StatusTes
12 + self.log = self.capture_
Have you checked that _all_ of the base classes conform to this protocol, yielding on
every call to setUp? If one does not, we'll have issues.