~allenap/maas/async-use-fixture

« back to all changes in this revision

Viewing changes to src/provisioningserver/utils/tests/test_twisted.py

[r=julian-edwards][bug=][author=allenap] The asynchronous decorator can now specify a time-out.

If `timeout` has been specified, the EventualResult returned from a call
from outside of the reactor thread will be waited on for up to `timeout`
seconds. This means that callers don't need to remember to wait. If
`timeout` is `None` then it will wait indefinitely, which can be useful
where the function itself handles time-outs, or where the called
function doesn't actually defer work but just needs to run in the
reactor thread.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
__all__ = []
16
16
 
17
17
import operator
18
 
from random import randint
 
18
from random import (
 
19
    randint,
 
20
    random,
 
21
    )
19
22
import re
20
23
import time
21
24
 
22
25
from crochet import EventualResult
23
 
from maastesting.matchers import MockCalledOnceWith
 
26
from maastesting.matchers import (
 
27
    IsCallable,
 
28
    MockCalledOnceWith,
 
29
    )
24
30
from maastesting.testcase import MAASTestCase
25
31
from mock import (
26
32
    Mock,
31
37
    asynchronous,
32
38
    callOut,
33
39
    deferWithTimeout,
 
40
    FOREVER,
34
41
    pause,
35
42
    reactor_sync,
36
43
    retries,
66
73
from twisted.python.failure import Failure
67
74
 
68
75
 
 
76
def return_args(*args, **kwargs):
 
77
    return args, kwargs
 
78
 
 
79
 
69
80
class TestAsynchronousDecorator(MAASTestCase):
70
81
 
71
82
    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
72
83
 
73
 
    @asynchronous
74
 
    def return_args(self, *args, **kwargs):
75
 
        return args, kwargs
76
 
 
77
84
    def test_in_reactor_thread(self):
78
 
        result = self.return_args(1, 2, three=3)
 
85
        result = asynchronous(return_args)(1, 2, three=3)
79
86
        self.assertEqual(((1, 2), {"three": 3}), result)
80
87
 
81
88
    @inlineCallbacks
82
89
    def test_in_other_thread(self):
83
90
        def do_stuff_in_thread():
84
 
            result = self.return_args(3, 4, five=5)
 
91
            result = asynchronous(return_args)(3, 4, five=5)
85
92
            self.assertThat(result, IsInstance(EventualResult))
86
93
            return result.wait()
87
94
        # Call do_stuff_in_thread() from another thread.
92
99
        self.assertEqual(((3, 4), {"five": 5}), result)
93
100
 
94
101
 
 
102
noop = lambda: None
 
103
 
 
104
 
 
105
class TestAsynchronousDecoratorWithTimeout(MAASTestCase):
 
106
 
 
107
    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
108
 
 
109
    def test_timeout_cannot_be_None(self):
 
110
        self.assertRaises(ValueError, asynchronous, noop, timeout=None)
 
111
 
 
112
    def test_timeout_cannot_be_negative(self):
 
113
        self.assertRaises(ValueError, asynchronous, noop, timeout=-1)
 
114
 
 
115
    def test_timeout_can_be_int(self):
 
116
        self.assertThat(asynchronous(noop, timeout=1), IsCallable())
 
117
 
 
118
    def test_timeout_can_be_long(self):
 
119
        self.assertThat(asynchronous(noop, timeout=1L), IsCallable())
 
120
 
 
121
    def test_timeout_can_be_float(self):
 
122
        self.assertThat(asynchronous(noop, timeout=1.0), IsCallable())
 
123
 
 
124
    def test_timeout_can_be_forever(self):
 
125
        self.assertThat(asynchronous(noop, timeout=FOREVER), IsCallable())
 
126
 
 
127
 
 
128
class TestAsynchronousDecoratorWithTimeoutDefined(MAASTestCase):
 
129
 
 
130
    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
131
 
 
132
    scenarios = (
 
133
        ("finite", {"timeout": random()}),
 
134
        ("forever", {"timeout": FOREVER}),
 
135
    )
 
136
 
 
137
    def test_in_reactor_thread(self):
 
138
        return_args_async = asynchronous(return_args, self.timeout)
 
139
        result = return_args_async(1, 2, three=3)
 
140
        self.assertEqual(((1, 2), {"three": 3}), result)
 
141
 
 
142
    @inlineCallbacks
 
143
    def test_in_other_thread(self):
 
144
        return_args_async = asynchronous(return_args, self.timeout)
 
145
        # Call self.return_args from another thread.
 
146
        result = yield deferToThread(return_args_async, 3, 4, five=5)
 
147
        # The arguments passed back match those passed in.
 
148
        self.assertEqual(((3, 4), {"five": 5}), result)
 
149
 
 
150
    @inlineCallbacks
 
151
    def test__passes_timeout_to_wait(self):
 
152
        # These mocks are going to help us tell a story of a timeout.
 
153
        run_in_reactor = self.patch(twisted_module, "run_in_reactor")
 
154
        func_in_reactor = run_in_reactor.return_value
 
155
        eventual_result = func_in_reactor.return_value
 
156
        wait = eventual_result.wait
 
157
        wait.return_value = sentinel.result
 
158
 
 
159
        # Our placeholder function, and its wrapped version.
 
160
        do_nothing = lambda: None
 
161
        do_nothing_async = asynchronous(do_nothing, timeout=self.timeout)
 
162
 
 
163
        # Call our wrapped function in a thread so that the wrapper calls back
 
164
        # into the IO thread, via the time-out logic.
 
165
        result = yield deferToThread(do_nothing_async)
 
166
        self.expectThat(result, Equals(sentinel.result))
 
167
 
 
168
        # Here's what happened, or should have:
 
169
        # 1. do_nothing was wrapped by run_in_reactor, producing
 
170
        #    func_in_reactor.
 
171
        self.assertThat(run_in_reactor, MockCalledOnceWith(do_nothing))
 
172
        # 2. func_in_reactor was called with no arguments, because we didn't
 
173
        #    pass any, producing eventual_result.
 
174
        self.assertThat(func_in_reactor, MockCalledOnceWith())
 
175
        # 3. eventual_result.wait was called...
 
176
        if self.timeout is FOREVER:
 
177
            # ...without arguments.
 
178
            self.assertThat(wait, MockCalledOnceWith())
 
179
        else:
 
180
            # ...with the timeout we passed when we wrapped do_nothing.
 
181
            self.assertThat(wait, MockCalledOnceWith(self.timeout))
 
182
 
 
183
 
95
184
class TestSynchronousDecorator(MAASTestCase):
96
185
 
97
186
    run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)