Merge lp:~andreas-pokorny/mir/add-timer-to-main-loop into lp:mir
- add-timer-to-main-loop
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | kevin gunn |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1626 |
Proposed branch: | lp:~andreas-pokorny/mir/add-timer-to-main-loop |
Merge into: | lp:mir |
Diff against target: |
909 lines (+694/-34) 12 files modified
include/server/mir/asio_main_loop.h (+5/-0) include/server/mir/main_loop.h (+2/-1) include/server/mir/time/alarm.h (+86/-0) include/server/mir/time/timer.h (+70/-0) include/test/mir_test/wait_object.h (+58/-0) include/test/mir_test_doubles/stub_timer.h (+70/-0) src/server/asio_main_loop.cpp (+138/-0) tests/integration-tests/client/test_screencast.cpp (+5/-32) tests/mir_test/CMakeLists.txt (+2/-1) tests/mir_test/wait_object.cpp (+35/-0) tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp (+1/-0) tests/unit-tests/test_asio_main_loop.cpp (+222/-0) |
To merge this branch: | bzr merge lp:~andreas-pokorny/mir/add-timer-to-main-loop |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Alberto Aguirre (community) | Approve | ||
Alexandros Frantzis (community) | Approve | ||
Daniel van Vugt | Needs Fixing | ||
Chris Halse Rogers | Approve | ||
Alan Griffiths | Abstain | ||
Kevin DuBois (community) | Needs Information | ||
Review via email: mp+216881@code.launchpad.net |
Commit message
Shameless Copy of Christopher Halse Rogers Timer extension
This version is an untouched copy of the Timer/Alarm changes proposed
with the 1hz-rendering-
for a clean implementation of the ANR detection when sending input events
Description of the change
This is Christophers Timer code which was provided in the 1hz-rendering-
This functionality is helpful for implementing the application-
PS Jenkins bot (ps-jenkins) wrote : | # |
Alan Griffiths (alan-griffiths) wrote : | # |
I can't help wondering why Timer & Alarm are not in namespace mir::time
But I suspect the answer will be "I'm just copying code".
Alan Griffiths (alan-griffiths) wrote : | # |
108 + virtual bool reschedule_
It would probably be useful to have "reschedule_
Alan Griffiths (alan-griffiths) wrote : | # |
I'm a bit concerned about using real time intervals in tests - and not mocking out the clock. (And the above)
Will think more prescriptively tomorrow.
Andreas Pokorny (andreas-pokorny) wrote : | # |
> I'm a bit concerned about using real time intervals in tests - and not mocking
> out the clock. (And the above)
>
> Will think more prescriptively tomorrow.
We could mock it in unit tests and have some that uses the clock in integration tests.
Andreas Pokorny (andreas-pokorny) wrote : | # |
So far addressed apart from:
> I'm a bit concerned about using real time intervals in tests - and not mocking
> out the clock. (And the above)
Need to dig deeper into asio to understand if that is possible with reasonable effort.
Alan Griffiths (alan-griffiths) wrote : | # |
> Will think more prescriptively tomorrow.
Sorry, been sidetracked by trust session work.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1569
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1570
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Kevin DuBois (kdub) wrote : | # |
could the branch be stacked upon the 1hz renderering branch somehow? some of the review comments there apply here too.
Chris Halse Rogers (raof) wrote : | # |
I'll rebase the 1hz rendering branch on this, when it's not a public holiday.
Chris Halse Rogers (raof) wrote : | # |
I'm not sure we need reschedule_for / notify_at (which is why I didn't add them), but they're obvious things we might want to do in the future. +0.
590 + MOCK_METHOD2(
591 + mir::time:
592 + std::function<
593 +
594 + std::unique_
595 + std::function<
596 + {
597 + return std::unique_
598 + }
I have recently discovered that we have mir_test/
816 + // Somewhat counterintuitively we need some work on the mainloop to keep it running.
817 + auto dummy_alarm = ml.notify_
The comment (and all the other ones to that effect) is no longer true; you've pulled in the test and io::work that resolves it.
Alexandros Frantzis (afrantzis) wrote : | # |
320 + void notify_done();
323 + template<typename rep, typename period>
324 + bool wait_for(
I got confused trying to figure out how these operations are related to the WatchDog object lifecycle, and it seems the answer is that they are not related at all, they are there only for convenience. It would be cleaner to move them to a separate object, e.g. a WaitObject (see tests/integrati
761 + runner.
Not needed.
704 + EXPECT_
729 + EXPECT_
...
I am wary of these expectations. It's conceivable that heavy loads in our CI builders could cause significant delays, enough for these tests to fail.
Alexandros Frantzis (afrantzis) wrote : | # |
> I am wary of these expectations. It's conceivable that heavy loads in our CI builders could
> cause significant delays, enough for these tests to fail.
This is not a blocker, but we should keep an eye out for such failures and react accordingly.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1571
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alberto Aguirre (albaguirre) wrote : | # |
Not blocking:
691 +TEST(AsioMainL
865 +TEST(AsioMainL
The test names are misleading as there are no expectations setup to actually test what the name implies; but I can see that would be tricky to do and satisfy the expectations under valgrind and CI.
750 + EXPECT_
766 + EXPECT_
784 + EXPECT_
833 + ASSERT_
835 + EXPECT_
838 + EXPECT_
861 + EXPECT_
These would read better as EXPECT_
841 +TEST(AsioMainL
857 + EXPECT_
858 + EXPECT_
859 +
860 + EXPECT_
861 + EXPECT_
862 + EXPECT_EQ(1, call_count);
863 +}
What if the previous scheduling fires? Line 860-862 could still run and satisfy the expectations. Given the current implementation this again requires checking time intervals so not blocking for this.
Alan Griffiths (alan-griffiths) wrote : | # |
79 + enum State
80 + {
81 + Pending, /**< Will trigger the callback at some point in the future */
82 + Cancelled, /**< The callback has been cancelled before being triggered */
83 + Triggered /**< The callback has been called */
84 + };
Should be "pending", "cancelled" & "triggered"
http://
Alan Griffiths (alan-griffiths) wrote : | # |
101 + virtual bool cancel() = 0;
I wonder if it would be more useful to return the "after" state. (Also for other similar functions)
Chris Halse Rogers (raof) wrote : | # |
@Alan - the state is guaranteed to be either cancelled or triggered, so returning the “after” state is no more useful.
That said, I think it would be more useful if it returned whether or not the cancel had any effect - ie: did it transition from Pending to Cancelled or not?
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1573
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alan Griffiths (alan-griffiths) wrote : | # |
I'm not going to block on this, but I am going to mention a concern: This adds another collection of unit tests that are slower than *any* acceptance tests. Vis:
$ ctest | grep sec$ | sort -n -b -k 7,7 -r | head -n 5
73/191 Test #73: mir_unit_
137/191 Test #137: mir_unit_
69/191 Test #69: mir_unit_
181/191 Test #181: mir_unit_
16/191 Test #16: mir_acceptance_
"Unit tests" that take more than milliseconds are a cause for concern. As are tests that rely on the system clock.
Chris Halse Rogers (raof) wrote : | # |
This seems OK to me.
While it would be nicer to divorce this from the system clock, I don't think it's really feasible. It looks like it might be possible to wedge in a fake timesource into boost::asio but that looks fraught with fraughtness.
Daniel van Vugt (vanvugt) wrote : | # |
Text conflict in include/
Text conflict in include/
Text conflict in src/server/
Text conflict in tests/unit-
4 conflicts encountered.
Alexandros Frantzis (afrantzis) wrote : | # |
@WaitObject
I recently discovered that we have mt::WaitCondition which provides similar functionality, perhaps we should merge the two classes (not a blocker, we can do this in an upcoming MP).
> I am wary of these expectations. It's conceivable that heavy loads in our CI builders could cause significant delays, enough for these tests to fail.
I am still wary... but let's see...
+class Timer
Our usual style is to make the constructor and deleted CopyAssign protected (not that it makes any practical difference).
Alberto Aguirre (albaguirre) wrote : | # |
Needs rebasing as Daniel pointed out.
kevin gunn (kgunn72) wrote : | # |
i believe this covers off duflu's "needs fixing" as well.
small note, still missing api to create an unscheduled alarm, but landing now as its a prereq for power changes
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1574
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'include/server/mir/asio_main_loop.h' |
2 | --- include/server/mir/asio_main_loop.h 2014-05-08 16:15:26 +0000 |
3 | +++ include/server/mir/asio_main_loop.h 2014-05-13 19:41:32 +0000 |
4 | @@ -49,6 +49,10 @@ |
5 | std::initializer_list<int> fd, |
6 | std::function<void(int)> const& handler); |
7 | |
8 | + std::unique_ptr<time::Alarm> notify_in(std::chrono::milliseconds delay, |
9 | + std::function<void()> callback) override; |
10 | + std::unique_ptr<time::Alarm> notify_at(mir::time::Timestamp time_point, |
11 | + std::function<void()> callback) override; |
12 | void enqueue(void const* owner, ServerAction const& action); |
13 | void pause_processing_for(void const* owner); |
14 | void resume_processing_for(void const* owner); |
15 | @@ -60,6 +64,7 @@ |
16 | void process_server_actions(); |
17 | |
18 | boost::asio::io_service io; |
19 | + boost::asio::io_service::work work; |
20 | std::vector<std::unique_ptr<SignalHandler>> signal_handlers; |
21 | std::vector<std::unique_ptr<FDHandler>> fd_handlers; |
22 | std::mutex server_actions_mutex; |
23 | |
24 | === modified file 'include/server/mir/main_loop.h' |
25 | --- include/server/mir/main_loop.h 2014-05-08 16:15:26 +0000 |
26 | +++ include/server/mir/main_loop.h 2014-05-13 19:41:32 +0000 |
27 | @@ -20,12 +20,13 @@ |
28 | #define MIR_MAIN_LOOP_H_ |
29 | |
30 | #include "mir/graphics/event_handler_register.h" |
31 | +#include "mir/time/timer.h" |
32 | #include "mir/server_action_queue.h" |
33 | |
34 | namespace mir |
35 | { |
36 | |
37 | -class MainLoop : public graphics::EventHandlerRegister, |
38 | +class MainLoop : public graphics::EventHandlerRegister, public time::Timer, |
39 | public ServerActionQueue |
40 | { |
41 | public: |
42 | |
43 | === added file 'include/server/mir/time/alarm.h' |
44 | --- include/server/mir/time/alarm.h 1970-01-01 00:00:00 +0000 |
45 | +++ include/server/mir/time/alarm.h 2014-05-13 19:41:32 +0000 |
46 | @@ -0,0 +1,86 @@ |
47 | +/* |
48 | + * Copyright © 2014 Canonical Ltd. |
49 | + * |
50 | + * This program is free software: you can redistribute it and/or modify it |
51 | + * under the terms of the GNU General Public License version 3, |
52 | + * as published by the Free Software Foundation. |
53 | + * |
54 | + * This program is distributed in the hope that it will be useful, |
55 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
56 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
57 | + * GNU General Public License for more details. |
58 | + * |
59 | + * You should have received a copy of the GNU General Public License |
60 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
61 | + * |
62 | + * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com> |
63 | + */ |
64 | + |
65 | +#ifndef MIR_TIME_ALARM_H_ |
66 | +#define MIR_TIME_ALARM_H_ |
67 | + |
68 | +#include "mir/time/clock.h" |
69 | + |
70 | +namespace mir |
71 | +{ |
72 | +namespace time |
73 | +{ |
74 | + |
75 | +/** |
76 | + * A one-shot, resettable handle to trigger a callback at a later time |
77 | + * \note All members of Alarm are threadsafe |
78 | + */ |
79 | +class Alarm |
80 | +{ |
81 | +public: |
82 | + enum State |
83 | + { |
84 | + pending, /**< Will trigger the callback at some point in the future */ |
85 | + cancelled, /**< The callback has been cancelled before being triggered */ |
86 | + triggered /**< The callback has been called */ |
87 | + }; |
88 | + |
89 | + |
90 | + Alarm() = default; |
91 | + /** |
92 | + * \note Destruction of the Alarm guarantees that the callback will not subsequently be called |
93 | + */ |
94 | + virtual ~Alarm() = default; |
95 | + |
96 | + /** |
97 | + * Cancels a pending alarm |
98 | + * |
99 | + * \note Has no effect if the Alarm is in the Triggered state. |
100 | + * \note cancel() is idempotent |
101 | + * |
102 | + * \return True iff the state of the Alarm is now Cancelled |
103 | + */ |
104 | + virtual bool cancel() = 0; |
105 | + virtual State state() const = 0; |
106 | + |
107 | + /** |
108 | + * Reschedule the alarm |
109 | + * \param delay Delay, in milliseconds, before the Alarm will be triggered |
110 | + * \return True if this reschedule supercedes a previous not-yet-triggered timeout |
111 | + * |
112 | + * \note This cancels any previous timeout set. |
113 | + */ |
114 | + virtual bool reschedule_in(std::chrono::milliseconds delay) = 0; |
115 | + |
116 | + /** |
117 | + * Reschedule the alarm |
118 | + * \param timeout Time point when the alarm should be triggered |
119 | + * \return True if this reschedule supercedes a previous not-yet-triggered timeout |
120 | + * |
121 | + * \note This cancels any previous timeout set. |
122 | + */ |
123 | + virtual bool reschedule_for(Timestamp timeout) = 0; |
124 | + |
125 | + Alarm(Alarm const&) = delete; |
126 | + Alarm& operator=(Alarm const&) = delete; |
127 | +}; |
128 | + |
129 | +} |
130 | +} |
131 | +#endif |
132 | + |
133 | |
134 | === added file 'include/server/mir/time/timer.h' |
135 | --- include/server/mir/time/timer.h 1970-01-01 00:00:00 +0000 |
136 | +++ include/server/mir/time/timer.h 2014-05-13 19:41:32 +0000 |
137 | @@ -0,0 +1,70 @@ |
138 | +/* |
139 | + * Copyright © 2014 Canonical Ltd. |
140 | + * |
141 | + * This program is free software: you can redistribute it and/or modify it |
142 | + * under the terms of the GNU General Public License version 3, |
143 | + * as published by the Free Software Foundation. |
144 | + * |
145 | + * This program is distributed in the hope that it will be useful, |
146 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
147 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
148 | + * GNU General Public License for more details. |
149 | + * |
150 | + * You should have received a copy of the GNU General Public License |
151 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
152 | + * |
153 | + * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com> |
154 | + */ |
155 | + |
156 | + |
157 | +#ifndef MIR_TIME_TIMER_H_ |
158 | +#define MIR_TIME_TIMER_H_ |
159 | + |
160 | +#include "mir/time/alarm.h" |
161 | + |
162 | +#include <chrono> |
163 | +#include <functional> |
164 | +#include <memory> |
165 | + |
166 | +namespace mir |
167 | +{ |
168 | +namespace time |
169 | +{ |
170 | + |
171 | +class Alarm; |
172 | + |
173 | +class Timer |
174 | +{ |
175 | +public: |
176 | + Timer() = default; |
177 | + virtual ~Timer() = default; |
178 | + /** |
179 | + * \brief Create an Alarm that calls the callback after the specified delay |
180 | + * |
181 | + * \param delay Time from now, in milliseconds, that the callback will fire |
182 | + * \param callback Function to call when the Alarm signals |
183 | + * |
184 | + * \return A handle to an Alarm that will fire after delay ms. |
185 | + */ |
186 | + virtual std::unique_ptr<Alarm> notify_in(std::chrono::milliseconds delay, |
187 | + std::function<void()> callback) = 0; |
188 | + /** |
189 | + * \brief Create an Alarm that calls the callback at the specified time |
190 | + * |
191 | + * \param time_point Time point when the alarm should be triggered |
192 | + * \param callback Function to call when the Alarm signals |
193 | + * |
194 | + * \return A handle to an Alarm that will fire after delay ms. |
195 | + */ |
196 | + virtual std::unique_ptr<Alarm> notify_at(Timestamp time_point, |
197 | + std::function<void()> callback) = 0; |
198 | + |
199 | + |
200 | + Timer(Timer const&) = delete; |
201 | + Timer& operator=(Timer const&) = delete; |
202 | +}; |
203 | + |
204 | +} |
205 | +} |
206 | + |
207 | +#endif // MIR_TIME_TIMER_H_ |
208 | |
209 | === added file 'include/test/mir_test/wait_object.h' |
210 | --- include/test/mir_test/wait_object.h 1970-01-01 00:00:00 +0000 |
211 | +++ include/test/mir_test/wait_object.h 2014-05-13 19:41:32 +0000 |
212 | @@ -0,0 +1,58 @@ |
213 | +/* |
214 | + * Copyright © 2014 Canonical Ltd. |
215 | + * |
216 | + * This program is free software: you can redistribute it and/or modify |
217 | + * it under the terms of the GNU General Public License version 3 as |
218 | + * published by the Free Software Foundation. |
219 | + * |
220 | + * This program is distributed in the hope that it will be useful, |
221 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
222 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
223 | + * GNU General Public License for more details. |
224 | + * |
225 | + * You should have received a copy of the GNU General Public License |
226 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
227 | + * |
228 | + * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
229 | + * Andreas Pokorny <andreas.pokorny@canonical.com> |
230 | +*/ |
231 | + |
232 | +#ifndef MIR_TEST_WAIT_OBJECT_H_ |
233 | +#define MIR_TEST_WAIT_OBJECT_H_ |
234 | + |
235 | +#include <mutex> |
236 | +#include <condition_variable> |
237 | +#include <chrono> |
238 | +#include <boost/throw_exception.hpp> |
239 | + |
240 | +namespace mir |
241 | +{ |
242 | +namespace test |
243 | +{ |
244 | + |
245 | +class WaitObject |
246 | +{ |
247 | +public: |
248 | + void notify_ready(); |
249 | + void wait_until_ready(); |
250 | + template<typename Representation, typename Period> |
251 | + void wait_until_ready(std::chrono::duration<Representation, Period> const& span) |
252 | + { |
253 | + std::unique_lock<std::mutex> lock{mutex}; |
254 | + if (!cv.wait_for(lock, span, [this] { return ready; })) |
255 | + { |
256 | + BOOST_THROW_EXCEPTION(std::runtime_error("WaitObject timed out")); |
257 | + } |
258 | + } |
259 | + |
260 | +private: |
261 | + std::mutex mutex; |
262 | + std::condition_variable cv; |
263 | + bool ready = false; |
264 | +}; |
265 | + |
266 | +} |
267 | +} |
268 | + |
269 | +#endif |
270 | + |
271 | |
272 | === added file 'include/test/mir_test_doubles/stub_timer.h' |
273 | --- include/test/mir_test_doubles/stub_timer.h 1970-01-01 00:00:00 +0000 |
274 | +++ include/test/mir_test_doubles/stub_timer.h 2014-05-13 19:41:32 +0000 |
275 | @@ -0,0 +1,70 @@ |
276 | +/* |
277 | + * Copyright © 2014 Canonical Ltd. |
278 | + * |
279 | + * This program is free software: you can redistribute it and/or modify it |
280 | + * under the terms of the GNU General Public License version 3, |
281 | + * as published by the Free Software Foundation. |
282 | + * |
283 | + * This program is distributed in the hope that it will be useful, |
284 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
285 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
286 | + * GNU General Public License for more details. |
287 | + * |
288 | + * You should have received a copy of the GNU General Public License |
289 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
290 | + * |
291 | + * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com> |
292 | + */ |
293 | + |
294 | +#ifndef MIR_TEST_DOUBLES_STUB_TIMER_H_ |
295 | +#define MIR_TEST_DOUBLES_STUB_TIMER_H_ |
296 | + |
297 | +#include "mir/time/alarm.h" |
298 | +#include "mir/time/timer.h" |
299 | + |
300 | +namespace mir |
301 | +{ |
302 | +namespace test |
303 | +{ |
304 | +namespace doubles |
305 | +{ |
306 | + |
307 | +class StubAlarm : public mir::time::Alarm |
308 | +{ |
309 | + bool cancel() override |
310 | + { |
311 | + return false; |
312 | + } |
313 | + State state() const override |
314 | + { |
315 | + return Cancelled; |
316 | + } |
317 | + bool reschedule_in(std::chrono::milliseconds) override |
318 | + { |
319 | + return false; |
320 | + } |
321 | + bool reschedule_for(mir::time::Timestamp) override |
322 | + { |
323 | + return false; |
324 | + } |
325 | +}; |
326 | + |
327 | +class StubTimer : public mir::time::Timer |
328 | +{ |
329 | + std::unique_ptr<mir::time::Alarm> notify_in(std::chrono::milliseconds, std::function<void(void)>) override |
330 | + { |
331 | + return std::unique_ptr<mir::time::Alarm>{new StubAlarm}; |
332 | + } |
333 | + |
334 | + std::unique_ptr<mir::time::Alarm> notify_at(mir::time::Timestamp, std::function<void(void)>) override |
335 | + { |
336 | + return std::unique_ptr<mir::time::Alarm>{new StubAlarm}; |
337 | + } |
338 | +}; |
339 | + |
340 | +} |
341 | +} |
342 | +} |
343 | + |
344 | + |
345 | +#endif // MIR_TEST_DOUBLES_STUB_TIMER_H_ |
346 | |
347 | === modified file 'src/server/asio_main_loop.cpp' |
348 | --- src/server/asio_main_loop.cpp 2014-05-08 16:15:26 +0000 |
349 | +++ src/server/asio_main_loop.cpp 2014-05-13 19:41:32 +0000 |
350 | @@ -18,7 +18,11 @@ |
351 | |
352 | #include "mir/asio_main_loop.h" |
353 | |
354 | +#include "boost/date_time/posix_time/conversion.hpp" |
355 | + |
356 | #include <cassert> |
357 | +#include <mutex> |
358 | +#include <condition_variable> |
359 | |
360 | class mir::AsioMainLoop::SignalHandler |
361 | { |
362 | @@ -109,6 +113,7 @@ |
363 | * to compile. |
364 | */ |
365 | mir::AsioMainLoop::AsioMainLoop() |
366 | + : work{io} |
367 | { |
368 | } |
369 | |
370 | @@ -154,6 +159,139 @@ |
371 | fd_handlers.push_back(std::move(fd_handler)); |
372 | } |
373 | |
374 | +namespace |
375 | +{ |
376 | +class AlarmImpl : public mir::time::Alarm |
377 | +{ |
378 | +public: |
379 | + AlarmImpl(boost::asio::io_service& io, |
380 | + std::chrono::milliseconds delay, |
381 | + std::function<void(void)> callback); |
382 | + |
383 | + AlarmImpl(boost::asio::io_service& io, |
384 | + mir::time::Timestamp time_point, |
385 | + std::function<void(void)> callback); |
386 | + |
387 | + ~AlarmImpl() noexcept override; |
388 | + |
389 | + bool cancel() override; |
390 | + State state() const override; |
391 | + |
392 | + bool reschedule_in(std::chrono::milliseconds delay) override; |
393 | + bool reschedule_for(mir::time::Timestamp time_point) override; |
394 | +private: |
395 | + void update_timer(); |
396 | + struct InternalState |
397 | + { |
398 | + explicit InternalState(std::function<void(void)> callback) |
399 | + : callback{callback} |
400 | + { |
401 | + } |
402 | + |
403 | + mutable std::mutex m; |
404 | + std::function<void(void)> callback; |
405 | + State state; |
406 | + }; |
407 | + |
408 | + boost::asio::deadline_timer timer; |
409 | + std::shared_ptr<InternalState> data; |
410 | +}; |
411 | + |
412 | +AlarmImpl::AlarmImpl(boost::asio::io_service& io, |
413 | + std::chrono::milliseconds delay, |
414 | + std::function<void ()> callback) |
415 | + : timer{io}, |
416 | + data{std::make_shared<InternalState>(callback)} |
417 | +{ |
418 | + reschedule_in(delay); |
419 | +} |
420 | + |
421 | +AlarmImpl::AlarmImpl(boost::asio::io_service& io, |
422 | + mir::time::Timestamp time_point, |
423 | + std::function<void ()> callback) |
424 | + : timer{io}, |
425 | + data{std::make_shared<InternalState>(callback)} |
426 | +{ |
427 | + reschedule_for(time_point); |
428 | +} |
429 | + |
430 | +AlarmImpl::~AlarmImpl() noexcept |
431 | +{ |
432 | + AlarmImpl::cancel(); |
433 | +} |
434 | + |
435 | +bool AlarmImpl::cancel() |
436 | +{ |
437 | + std::lock_guard<decltype(data->m)> lock(data->m); |
438 | + if (data->state == triggered) |
439 | + return false; |
440 | + |
441 | + data->state = cancelled; |
442 | + timer.cancel(); |
443 | + return true; |
444 | +} |
445 | + |
446 | +mir::time::Alarm::State AlarmImpl::state() const |
447 | +{ |
448 | + std::lock_guard<decltype(data->m)> lock(data->m); |
449 | + |
450 | + return data->state; |
451 | +} |
452 | + |
453 | +bool AlarmImpl::reschedule_in(std::chrono::milliseconds delay) |
454 | +{ |
455 | + bool cancelling = timer.expires_from_now(boost::posix_time::milliseconds{delay.count()}); |
456 | + update_timer(); |
457 | + return cancelling; |
458 | +} |
459 | + |
460 | +bool AlarmImpl::reschedule_for(mir::time::Timestamp time_point) |
461 | +{ |
462 | + bool cancelling = |
463 | + timer.expires_at(boost::posix_time::from_time_t( |
464 | + std::chrono::high_resolution_clock::to_time_t(time_point) |
465 | + )); |
466 | + update_timer(); |
467 | + return cancelling; |
468 | +} |
469 | + |
470 | +void AlarmImpl::update_timer() |
471 | +{ |
472 | + std::lock_guard<decltype(data->m)> lock(data->m); |
473 | + // Awkwardly, we can't stop the async_wait handler from being called |
474 | + // on a destroyed AlarmImpl. This means we need to wedge a shared_ptr |
475 | + // into the async_wait callback. |
476 | + std::weak_ptr<InternalState> possible_data = data; |
477 | + timer.async_wait([possible_data](boost::system::error_code const& ec) |
478 | + { |
479 | + auto data = possible_data.lock(); |
480 | + if (!data) |
481 | + return; |
482 | + |
483 | + std::unique_lock<decltype(data->m)> lock(data->m); |
484 | + if (!ec && data->state == pending) |
485 | + { |
486 | + data->state = triggered; |
487 | + lock.unlock(); |
488 | + data->callback(); |
489 | + } |
490 | + }); |
491 | + data->state = pending; |
492 | +} |
493 | +} |
494 | + |
495 | +std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_in(std::chrono::milliseconds delay, |
496 | + std::function<void()> callback) |
497 | +{ |
498 | + return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, delay, callback}}; |
499 | +} |
500 | + |
501 | +std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_at(mir::time::Timestamp time_point, |
502 | + std::function<void()> callback) |
503 | +{ |
504 | + return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, time_point, callback}}; |
505 | + |
506 | +} |
507 | void mir::AsioMainLoop::enqueue(void const* owner, ServerAction const& action) |
508 | { |
509 | { |
510 | |
511 | === modified file 'tests/integration-tests/client/test_screencast.cpp' |
512 | --- tests/integration-tests/client/test_screencast.cpp 2014-03-06 18:57:08 +0000 |
513 | +++ tests/integration-tests/client/test_screencast.cpp 2014-05-13 19:41:32 +0000 |
514 | @@ -23,13 +23,11 @@ |
515 | #include "mir_test/test_protobuf_server.h" |
516 | #include "mir_test/stub_server_tool.h" |
517 | #include "mir_test/pipe.h" |
518 | +#include "mir_test/wait_object.h" |
519 | |
520 | #include <gtest/gtest.h> |
521 | |
522 | #include <thread> |
523 | -#include <mutex> |
524 | -#include <condition_variable> |
525 | -#include <boost/throw_exception.hpp> |
526 | |
527 | namespace mcl = mir::client; |
528 | namespace mt = mir::test; |
529 | @@ -37,31 +35,6 @@ |
530 | namespace |
531 | { |
532 | |
533 | -class WaitObject |
534 | -{ |
535 | -public: |
536 | - void notify_ready() |
537 | - { |
538 | - std::unique_lock<std::mutex> lock{mutex}; |
539 | - ready = true; |
540 | - cv.notify_all(); |
541 | - } |
542 | - |
543 | - void wait_until_ready() |
544 | - { |
545 | - std::unique_lock<std::mutex> lock{mutex}; |
546 | - if (!cv.wait_for(lock, std::chrono::seconds{10}, [this] { return ready; })) |
547 | - { |
548 | - BOOST_THROW_EXCEPTION(std::runtime_error("WaitObject timed out")); |
549 | - } |
550 | - } |
551 | - |
552 | -private: |
553 | - std::mutex mutex; |
554 | - std::condition_variable cv; |
555 | - bool ready = false; |
556 | -}; |
557 | - |
558 | struct StubScreencastServerTool : mt::StubServerTool |
559 | { |
560 | void create_screencast( |
561 | @@ -129,13 +102,13 @@ |
562 | protobuf_parameters.set_pixel_format(0); |
563 | mir::protobuf::Screencast protobuf_screencast; |
564 | |
565 | - WaitObject wait_rpc; |
566 | + mt::WaitObject wait_rpc; |
567 | |
568 | protobuf_server->create_screencast( |
569 | nullptr, |
570 | &protobuf_parameters, |
571 | &protobuf_screencast, |
572 | - google::protobuf::NewCallback(&wait_rpc, &WaitObject::notify_ready)); |
573 | + google::protobuf::NewCallback(&wait_rpc, &mt::WaitObject::notify_ready)); |
574 | |
575 | wait_rpc.wait_until_ready(); |
576 | |
577 | @@ -167,13 +140,13 @@ |
578 | protobuf_screencast_id.set_value(0); |
579 | mir::protobuf::Buffer protobuf_buffer; |
580 | |
581 | - WaitObject wait_rpc; |
582 | + mt::WaitObject wait_rpc; |
583 | |
584 | protobuf_server->screencast_buffer( |
585 | nullptr, |
586 | &protobuf_screencast_id, |
587 | &protobuf_buffer, |
588 | - google::protobuf::NewCallback(&wait_rpc, &WaitObject::notify_ready)); |
589 | + google::protobuf::NewCallback(&wait_rpc, &mt::WaitObject::notify_ready)); |
590 | |
591 | wait_rpc.wait_until_ready(); |
592 | |
593 | |
594 | === modified file 'tests/mir_test/CMakeLists.txt' |
595 | --- tests/mir_test/CMakeLists.txt 2014-04-30 21:25:02 +0000 |
596 | +++ tests/mir_test/CMakeLists.txt 2014-05-13 19:41:32 +0000 |
597 | @@ -6,4 +6,5 @@ |
598 | cross_process_action.cpp |
599 | spin_wait.cpp |
600 | popen.cpp |
601 | -) |
602 | \ No newline at end of file |
603 | + wait_object.cpp |
604 | +) |
605 | |
606 | === added file 'tests/mir_test/wait_object.cpp' |
607 | --- tests/mir_test/wait_object.cpp 1970-01-01 00:00:00 +0000 |
608 | +++ tests/mir_test/wait_object.cpp 2014-05-13 19:41:32 +0000 |
609 | @@ -0,0 +1,35 @@ |
610 | +/* |
611 | + * Copyright © 2014 Canonical Ltd. |
612 | + * |
613 | + * This program is free software: you can redistribute it and/or modify |
614 | + * it under the terms of the GNU General Public License version 3 as |
615 | + * published by the Free Software Foundation. |
616 | + * |
617 | + * This program is distributed in the hope that it will be useful, |
618 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
619 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
620 | + * GNU General Public License for more details. |
621 | + * |
622 | + * You should have received a copy of the GNU General Public License |
623 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
624 | + * |
625 | + * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
626 | + * Andreas Pokorny <andreas.pokorny@canonical.com> |
627 | + */ |
628 | + |
629 | +#include "mir_test/wait_object.h" |
630 | + |
631 | +namespace mt = mir::test; |
632 | + |
633 | +void mt::WaitObject::notify_ready() |
634 | +{ |
635 | + std::unique_lock<std::mutex> lock{mutex}; |
636 | + ready = true; |
637 | + cv.notify_all(); |
638 | +} |
639 | + |
640 | +void mt::WaitObject::wait_until_ready() |
641 | +{ |
642 | + wait_until_ready(std::chrono::seconds{10}); |
643 | +} |
644 | + |
645 | |
646 | === modified file 'tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp' |
647 | --- tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-06 14:44:57 +0000 |
648 | +++ tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-13 19:41:32 +0000 |
649 | @@ -22,6 +22,7 @@ |
650 | |
651 | #include "mir_test/fake_shared.h" |
652 | #include "mir_test_doubles/mock_display_report.h" |
653 | +#include "mir_test/gmock_fixes.h" |
654 | |
655 | #include <gtest/gtest.h> |
656 | #include <gmock/gmock.h> |
657 | |
658 | === modified file 'tests/unit-tests/test_asio_main_loop.cpp' |
659 | --- tests/unit-tests/test_asio_main_loop.cpp 2014-05-08 16:15:26 +0000 |
660 | +++ tests/unit-tests/test_asio_main_loop.cpp 2014-05-13 19:41:32 +0000 |
661 | @@ -17,19 +17,48 @@ |
662 | */ |
663 | |
664 | #include "mir/asio_main_loop.h" |
665 | +#include "mir/time/high_resolution_clock.h" |
666 | #include "mir_test/pipe.h" |
667 | +#include "mir_test/auto_unblock_thread.h" |
668 | +#include "mir_test/wait_object.h" |
669 | |
670 | #include <gtest/gtest.h> |
671 | #include <gmock/gmock.h> |
672 | |
673 | #include <thread> |
674 | #include <atomic> |
675 | +#include <functional> |
676 | +#include <mutex> |
677 | +#include <condition_variable> |
678 | +#include <array> |
679 | +#include <boost/throw_exception.hpp> |
680 | |
681 | #include <sys/types.h> |
682 | #include <unistd.h> |
683 | |
684 | namespace mt = mir::test; |
685 | |
686 | +namespace |
687 | +{ |
688 | + |
689 | +class AsioMainLoopAlarmTest : public ::testing::Test |
690 | +{ |
691 | +public: |
692 | + mir::AsioMainLoop ml; |
693 | + int call_count{0}; |
694 | + mt::WaitObject wait; |
695 | + |
696 | + struct UnblockMainLoop : mt::AutoUnblockThread |
697 | + { |
698 | + UnblockMainLoop(mir::AsioMainLoop & loop) |
699 | + : mt::AutoUnblockThread([&loop]() {loop.stop();}, |
700 | + [&loop]() {loop.run();}) |
701 | + {} |
702 | + }; |
703 | +}; |
704 | + |
705 | +} |
706 | + |
707 | TEST(AsioMainLoopTest, signal_handled) |
708 | { |
709 | int const signum{SIGUSR1}; |
710 | @@ -294,6 +323,199 @@ |
711 | EXPECT_EQ(elems_to_send[2], elems_read[2]); |
712 | } |
713 | |
714 | +TEST_F(AsioMainLoopAlarmTest, main_loop_runs_until_stop_called) |
715 | +{ |
716 | + std::mutex checkpoint_mutex; |
717 | + std::condition_variable checkpoint; |
718 | + bool hit_checkpoint{false}; |
719 | + |
720 | + auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0}, |
721 | + [&checkpoint_mutex, &checkpoint, &hit_checkpoint]() |
722 | + { |
723 | + std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex); |
724 | + hit_checkpoint = true; |
725 | + checkpoint.notify_all(); |
726 | + }); |
727 | + |
728 | + UnblockMainLoop unblocker(ml); |
729 | + |
730 | + { |
731 | + std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex); |
732 | + ASSERT_TRUE(checkpoint.wait_for(lock, std::chrono::milliseconds{10}, [&hit_checkpoint]() { return hit_checkpoint; })); |
733 | + } |
734 | + |
735 | + auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [this] |
736 | + { |
737 | + wait.notify_ready(); |
738 | + }); |
739 | + |
740 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{50})); |
741 | + |
742 | + ml.stop(); |
743 | + // Main loop should be stopped now |
744 | + |
745 | + hit_checkpoint = false; |
746 | + auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0}, |
747 | + [&checkpoint_mutex, &checkpoint, &hit_checkpoint]() |
748 | + { |
749 | + std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex); |
750 | + hit_checkpoint = true; |
751 | + checkpoint.notify_all(); |
752 | + }); |
753 | + |
754 | + std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex); |
755 | + EXPECT_FALSE(checkpoint.wait_for(lock, std::chrono::milliseconds{50}, [&hit_checkpoint]() { return hit_checkpoint; })); |
756 | +} |
757 | + |
758 | +TEST_F(AsioMainLoopAlarmTest, alarm_fires_with_correct_delay) |
759 | +{ |
760 | + auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]() |
761 | + { |
762 | + wait.notify_ready(); |
763 | + }); |
764 | + |
765 | + UnblockMainLoop unblocker(ml); |
766 | + |
767 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100})); |
768 | +} |
769 | + |
770 | +TEST_F(AsioMainLoopAlarmTest, multiple_alarms_fire) |
771 | +{ |
772 | + int const alarm_count{10}; |
773 | + std::atomic<int> call_count{0}; |
774 | + std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms; |
775 | + |
776 | + for (auto& alarm : alarms) |
777 | + { |
778 | + alarm = ml.notify_in(std::chrono::milliseconds{50}, [this, &call_count]() |
779 | + { |
780 | + call_count.fetch_add(1); |
781 | + if (call_count == alarm_count) |
782 | + wait.notify_ready(); |
783 | + }); |
784 | + } |
785 | + |
786 | + UnblockMainLoop unblocker(ml); |
787 | + |
788 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100})); |
789 | + for (auto& alarm : alarms) |
790 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
791 | +} |
792 | + |
793 | + |
794 | +TEST_F(AsioMainLoopAlarmTest, alarm_changes_to_triggered_state) |
795 | +{ |
796 | + auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]() |
797 | + { |
798 | + wait.notify_ready(); |
799 | + }); |
800 | + |
801 | + UnblockMainLoop unblocker(ml); |
802 | + |
803 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100})); |
804 | + |
805 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
806 | +} |
807 | + |
808 | +TEST_F(AsioMainLoopAlarmTest, alarm_starts_in_pending_state) |
809 | +{ |
810 | + auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]() {}); |
811 | + |
812 | + UnblockMainLoop unblocker(ml); |
813 | + |
814 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
815 | +} |
816 | + |
817 | +TEST_F(AsioMainLoopAlarmTest, cancelled_alarm_doesnt_fire) |
818 | +{ |
819 | + auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]() |
820 | + { |
821 | + wait.notify_ready(); |
822 | + }); |
823 | + |
824 | + UnblockMainLoop unblocker(ml); |
825 | + |
826 | + EXPECT_TRUE(alarm->cancel()); |
827 | + EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error); |
828 | + EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state()); |
829 | +} |
830 | + |
831 | +TEST_F(AsioMainLoopAlarmTest, destroyed_alarm_doesnt_fire) |
832 | +{ |
833 | + auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]() |
834 | + { |
835 | + wait.notify_ready(); |
836 | + }); |
837 | + |
838 | + UnblockMainLoop unblocker(ml); |
839 | + |
840 | + alarm.reset(nullptr); |
841 | + |
842 | + EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error); |
843 | +} |
844 | + |
845 | +TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_fires_again) |
846 | +{ |
847 | + std::mutex m; |
848 | + std::condition_variable called; |
849 | + |
850 | + auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [this, &m, &called]() |
851 | + { |
852 | + std::unique_lock<decltype(m)> lock(m); |
853 | + call_count++; |
854 | + if (call_count == 2) |
855 | + wait.notify_ready(); |
856 | + called.notify_all(); |
857 | + }); |
858 | + |
859 | + UnblockMainLoop unblocker(ml); |
860 | + |
861 | + { |
862 | + std::unique_lock<decltype(m)> lock(m); |
863 | + ASSERT_TRUE(called.wait_for(lock, |
864 | + std::chrono::milliseconds{50}, |
865 | + [this](){ return call_count == 1; })); |
866 | + } |
867 | + |
868 | + ASSERT_EQ(mir::time::Alarm::triggered, alarm->state()); |
869 | + alarm->reschedule_in(std::chrono::milliseconds{100}); |
870 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
871 | + |
872 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500})); |
873 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
874 | +} |
875 | + |
876 | +TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling) |
877 | +{ |
878 | + auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [this]() |
879 | + { |
880 | + call_count++; |
881 | + wait.notify_ready(); |
882 | + }); |
883 | + |
884 | + UnblockMainLoop unblocker(ml); |
885 | + |
886 | + EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{150})); |
887 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
888 | + |
889 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500})); |
890 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
891 | + EXPECT_EQ(1, call_count); |
892 | +} |
893 | + |
894 | +TEST_F(AsioMainLoopAlarmTest, alarm_fires_at_correct_time_point) |
895 | +{ |
896 | + mir::time::HighResolutionClock clock; |
897 | + |
898 | + mir::time::Timestamp real_soon = clock.sample() + std::chrono::microseconds{120}; |
899 | + |
900 | + auto alarm = ml.notify_at(real_soon, [this]() { wait.notify_ready(); }); |
901 | + |
902 | + UnblockMainLoop unblocker(ml); |
903 | + |
904 | + EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{200})); |
905 | +} |
906 | + |
907 | TEST(AsioMainLoopTest, dispatches_action) |
908 | { |
909 | using namespace testing; |
PASSED: Continuous integration, rev:1568 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/1384/ jenkins. qa.ubuntu. com/job/ mir-android- trusty- i386-build/ 1682 jenkins. qa.ubuntu. com/job/ mir-clang- trusty- amd64-build/ 1680 jenkins. qa.ubuntu. com/job/ mir-mediumtests -trusty- touch/1255 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- amd64-ci/ 1116 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- amd64-ci/ 1116/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 1121 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- trusty- armhf-ci/ 1121/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/1257 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- trusty- armhf/1257/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/1160 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 6311
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/1384/ rebuild
http://