Merge lp:~allenap/maas/wait-for-reset--bug-1356788 into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 2704
Proposed branch: lp:~allenap/maas/wait-for-reset--bug-1356788
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 143 lines (+108/-2)
3 files modified
src/maasserver/tests/test_eventloop.py (+1/-1)
src/maastesting/crochet.py (+102/-0)
src/maastesting/testcase.py (+5/-1)
To merge this branch: bzr merge lp:~allenap/maas/wait-for-reset--bug-1356788
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+230846@code.launchpad.net

Commit message

Unfired and unhandled EventualResults are checked for at the end of each test.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good; I take it you tested that this correctly detects the problem described in the related bug…?

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

> Looks good; I take it you tested that this correctly detects the problem
> described in the related bug…?

Yep, it sure does :) Thanks for the review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/tests/test_eventloop.py'
2--- src/maasserver/tests/test_eventloop.py 2014-08-13 21:49:35 +0000
3+++ src/maasserver/tests/test_eventloop.py 2014-08-14 16:32:52 +0000
4@@ -51,7 +51,7 @@
5 # After starting the loop, the services list is populated, and
6 # the services are started too.
7 eventloop.loop.start().wait(5)
8- self.addCleanup(eventloop.loop.reset)
9+ self.addCleanup(lambda: eventloop.loop.reset().wait(5))
10 self.assertTrue(eventloop.loop.services.running)
11 self.assertTrue(eventloop.loop.running)
12 self.assertEqual(
13
14=== added file 'src/maastesting/crochet.py'
15--- src/maastesting/crochet.py 1970-01-01 00:00:00 +0000
16+++ src/maastesting/crochet.py 2014-08-14 16:32:52 +0000
17@@ -0,0 +1,102 @@
18+# Copyright 2014 Canonical Ltd. This software is licensed under the
19+# GNU Affero General Public License version 3 (see the file LICENSE).
20+
21+"""Support for testing with `crochet`."""
22+
23+from __future__ import (
24+ absolute_import,
25+ print_function,
26+ unicode_literals,
27+ )
28+
29+str = None
30+
31+__metaclass__ = type
32+__all__ = [
33+ "EventualResultCatchingMixin",
34+ ]
35+
36+import crochet
37+from testtools.content import (
38+ Content,
39+ UTF8_TEXT,
40+ )
41+from testtools.matchers import Equals
42+
43+
44+class EventualResultCatchingMixin:
45+ """A mix-in for tests that checks for unfired/unhandled `EventualResults`.
46+
47+ It reports about all py:class:`crochet.EventualResults` that are unfired
48+ or whose results have not been retrieved. A test detail is recorded for
49+ each, then the test is force-failed at the last moment.
50+ """
51+
52+ def setUp(self):
53+ super(EventualResultCatchingMixin, self).setUp()
54+ # Every EventualResult that crochet creates is registered into this
55+ # registry. We'll check it after the test has finished.
56+ registry = crochet._main._registry
57+ # The registry stores EventualResults in a WeakSet, which means that
58+ # unfired and unhandled results can be garbage collected before we get
59+ # to see them. Here we patch in a regular set so that nothing gets
60+ # garbage collected until we've been able to check the results.
61+ self.addCleanup(setattr, registry, "_results", registry._results)
62+ registry._results = set()
63+ # While unravelling clean-ups is a good time to check the results. Any
64+ # meaningful work represented by an EventualResult should have done
65+ # should been done by now.
66+ self.addCleanup(self.__checkResults, registry._results)
67+
68+ def __checkResults(self, eventual_results):
69+ fail_count = 0
70+
71+ # Go through all the EventualResults created in this test.
72+ for eventual_result in eventual_results:
73+ # If the result has been retrieved, fine, otherwise look closer.
74+ if not eventual_result._result_retrieved:
75+ fail_count += 1
76+
77+ try:
78+ # Is there a result waiting to be retrieved?
79+ result = eventual_result.wait(timeout=0)
80+ except crochet.TimeoutError:
81+ # No result yet. This could be because the result is wired
82+ # up to a Deferred that hasn't fired yet, or because it
83+ # hasn't yet been connected.
84+ if eventual_result._deferred is None:
85+ message = [
86+ "*** EventualResult has not fired:\n",
87+ "%r\n" % (eventual_result,),
88+ "*** It was not connected to a Deferred.\n",
89+ ]
90+ else:
91+ message = [
92+ "*** EventualResult has not fired:\n",
93+ "%r\n" % (eventual_result,),
94+ "*** It was connected to a Deferred:\n",
95+ "%r\n" % (eventual_result._deferred,),
96+ ]
97+ else:
98+ # A result, but nothing has collected it. This can be
99+ # caused by forgetting to call wait().
100+ message = [
101+ "*** EventualResult has fired:\n",
102+ "%r\n" % (eventual_result,),
103+ "*** It contained the following result:\n",
104+ "%r\n" % (result,),
105+ "*** but it was not collected.\n",
106+ "*** Was result.wait() called?\n",
107+ ]
108+
109+ # Record the details with a unique name.
110+ message = [block.encode("utf-8") for block in message]
111+ self.addDetail(
112+ "Unfired/unhandled EventualResult #%d" % fail_count,
113+ Content(UTF8_TEXT, lambda: message))
114+
115+ # Use expectThat() so that other clean-up tasks run to completion
116+ # before, at the last moment, the test is failed.
117+ self.expectThat(
118+ fail_count, Equals(0), "Unfired and/or unhandled "
119+ "EventualResult(s); see test details.")
120
121=== modified file 'src/maastesting/testcase.py'
122--- src/maastesting/testcase.py 2014-02-01 04:29:17 +0000
123+++ src/maastesting/testcase.py 2014-08-14 16:32:52 +0000
124@@ -20,6 +20,7 @@
125 import doctest
126 import unittest
127
128+from maastesting.crochet import EventualResultCatchingMixin
129 from maastesting.factory import factory
130 from maastesting.fixtures import TempDirectory
131 from maastesting.scenarios import WithScenarios
132@@ -53,7 +54,10 @@
133 yield
134
135
136-class MAASTestCase(WithScenarios, testtools.TestCase):
137+class MAASTestCase(
138+ WithScenarios,
139+ EventualResultCatchingMixin,
140+ testtools.TestCase):
141 """Base `TestCase` for MAAS.
142
143 Supports `test resources`_, `test scenarios`_, and `fixtures`_.