Mir

Merge lp:~robertcarr/mir/improve-input-acceptance into lp:~mir-team/mir/trunk

Proposed by Robert Carr
Status: Superseded
Proposed branch: lp:~robertcarr/mir/improve-input-acceptance
Merge into: lp:~mir-team/mir/trunk
Diff against target: 977 lines (+472/-253)
4 files modified
include/test/mir_test_framework/input_testing_server_configuration.h (+97/-0)
tests/acceptance-tests/test_client_input.cpp (+126/-253)
tests/mir_test_framework/CMakeLists.txt (+2/-0)
tests/mir_test_framework/input_testing_server_options.cpp (+247/-0)
To merge this branch: bzr merge lp:~robertcarr/mir/improve-input-acceptance
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Daniel van Vugt Needs Fixing
Review via email: mp+169311@code.launchpad.net

This proposal has been superseded by a proposal from 2013-06-14.

Commit message

Refactor input acceptance test fixtures.

Description of the change

Work on making it easier to define a variety of input acceptance tests by pulling out and refactoring the input test fixture from test_client_input.cpp, there is also some cleanup on the client side. Hope to find time to continue this soon and add client placement support used in "multiple_clients_receive_motion_in_windows" to the fixture. From here it should be easy to write a lot of input acceptance tests for various scenarios!

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:745
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~robertcarr/mir/improve-input-acceptance/+merge/169311/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/mir-ci/728/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-android-raring-i386-build/905
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-clang-raring-amd64-build/787
    FAILURE: http://jenkins.qa.ubuntu.com/job/mir-raring-amd64-ci/213/console

Click here to trigger a rebuild:
http://s-jenkins:8080/job/mir-ci/728/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

I feel overwhelmed by class names that make no sense to me (e.g. InputRegistrarListener). But that's not really anything new from this proposal.

In other cases, the names are unnecessarily verbose. For example "InputReceivingClient" could be "InputClient". Repeated in multiple locations, it makes the code easier to read if it's shorter.

But some more concrete issues:

1. Lines 38-58: Might be about time to indent the namespace declarations so they're readable.

2. This test hangs(!):
        Start 5: acceptance-tests.TestClientInput.*
and specifically hangs at:

[----------] 5 tests from TestClientInput
[ RUN ] TestClientInput.clients_receive_key_input
[ OK ] TestClientInput.clients_receive_key_input (15 ms)
[ RUN ] TestClientInput.clients_receive_us_english_mapped_keys
[ OK ] TestClientInput.clients_receive_us_english_mapped_keys (14 ms)
[ RUN ] TestClientInput.clients_receive_motion_inside_window
[ OK ] TestClientInput.clients_receive_motion_inside_window (12 ms)
[ RUN ] TestClientInput.clients_receive_button_events_inside_window
[ OK ] TestClientInput.clients_receive_button_events_inside_window (16 ms)
[ RUN ] TestClientInput.multiple_clients_receive_motion_inside_windows

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

Whoops at the hang! r745 was supposed to be a harmless cleanup which of course broke everything.Fixed

I made some renames, InputRegistrarListener->SurfaceReadinessListener.

>> Lines 38-58: Might be about time to indent the namespace declarations so they're readable.

I don't want to have a different style only in this file. Perhaps it is indicative that mir_test_framework should be mir::test::framework though.

Revision history for this message
Robert Carr (robertcarr) wrote :

InputReceivingClient->InputClient

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/test/mir_test_framework/input_testing_server_configuration.h'
2--- include/test/mir_test_framework/input_testing_server_configuration.h 1970-01-01 00:00:00 +0000
3+++ include/test/mir_test_framework/input_testing_server_configuration.h 2013-06-14 16:12:27 +0000
4@@ -0,0 +1,97 @@
5+/*
6+ * Copyright © 2013 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Robert Carr <robert.carr@canonical.com>
21+ */
22+
23+
24+#ifndef MIR_TEST_INPUT_TESTING_SERVER_CONFIGURATION_H_
25+#define MIR_TEST_INPUT_TESTING_SERVER_CONFIGURATION_H_
26+
27+#include "mir_test_framework/testing_server_configuration.h"
28+
29+#include "mir/geometry/rectangle.h"
30+
31+#include <map>
32+#include <string>
33+#include <mutex>
34+#include <condition_variable>
35+#include <atomic>
36+#include <thread>
37+
38+namespace mir
39+{
40+namespace input
41+{
42+namespace android
43+{
44+class FakeEventHub;
45+}
46+}
47+namespace graphics
48+{
49+class ViewableArea;
50+}
51+namespace test
52+{
53+namespace doubles
54+{
55+class FakeEventHubInputConfiguration;
56+}
57+}
58+}
59+
60+namespace mir_test_framework
61+{
62+
63+enum ClientLifecycleState { starting, appeared, vanished };
64+
65+class InputTestingServerConfiguration : public TestingServerConfiguration
66+{
67+public:
68+ InputTestingServerConfiguration();
69+
70+ void exec();
71+ void on_exit();
72+
73+ std::shared_ptr<mir::input::InputConfiguration> the_input_configuration() override;
74+ std::shared_ptr<mir::surfaces::InputRegistrar> the_input_registrar() override;
75+ std::shared_ptr<mir::graphics::ViewableArea> the_viewable_area() override;
76+
77+protected:
78+ virtual mir::geometry::Rectangle the_screen_geometry();
79+
80+ virtual void inject_input() = 0;
81+ mir::input::android::FakeEventHub* fake_event_hub;
82+
83+ void wait_until_client_appears(std::string const& surface_name);
84+ void wait_until_client_vanishes(std::string const& surface_name);
85+
86+private:
87+ std::mutex lifecycle_lock;
88+
89+ std::condition_variable lifecycle_condition;
90+ std::map<std::string, ClientLifecycleState> client_lifecycles;
91+
92+ std::thread input_injection_thread;
93+
94+ std::shared_ptr<mir::test::doubles::FakeEventHubInputConfiguration> input_configuration;
95+ std::shared_ptr<mir::surfaces::InputRegistrar> input_registrar;
96+ std::shared_ptr<mir::graphics::ViewableArea> view_area;
97+};
98+
99+}
100+
101+#endif /* MIR_TEST_INPUT_TESTING_SERVER_CONFIGURATION_H_ */
102
103=== modified file 'tests/acceptance-tests/test_client_input.cpp'
104--- tests/acceptance-tests/test_client_input.cpp 2013-05-30 19:24:29 +0000
105+++ tests/acceptance-tests/test_client_input.cpp 2013-06-14 16:12:27 +0000
106@@ -27,11 +27,11 @@
107 #include "mir_toolkit/mir_client_library.h"
108
109 #include "mir_test/fake_shared.h"
110-#include "mir_test/fake_event_hub_input_configuration.h"
111 #include "mir_test/fake_event_hub.h"
112 #include "mir_test/event_factory.h"
113 #include "mir_test/wait_condition.h"
114 #include "mir_test_framework/display_server_test_fixture.h"
115+#include "mir_test_framework/input_testing_server_configuration.h"
116
117 #include <gtest/gtest.h>
118 #include <gmock/gmock.h>
119@@ -60,86 +60,12 @@
120 char const* const mir_test_socket = mtf::test_socket_file().c_str();
121 }
122
123-#include "mir/input/surface_target.h"
124-
125 namespace
126 {
127-struct FocusNotifyingInputTargeter : public msh::InputTargeter
128-{
129- FocusNotifyingInputTargeter(std::shared_ptr<msh::InputTargeter> const& real_targeter,
130- std::function<void(void)> const& focus_set)
131- : real_targeter(real_targeter),
132- focus_set(focus_set)
133- {
134-
135- }
136- virtual ~FocusNotifyingInputTargeter() noexcept(true) {}
137-
138- void focus_changed(std::shared_ptr<mi::SurfaceTarget const> const& surface) override
139- {
140- real_targeter->focus_changed(surface);
141-
142- // We need a synchronization primitive inorder to halt test event injection
143- // until after a surface has taken focus (lest the events be discarded).
144- focus_set();
145- }
146- void focus_cleared()
147- {
148- real_targeter->focus_cleared();
149- }
150-
151- std::shared_ptr<msh::InputTargeter> const real_targeter;
152- std::function<void(void)> focus_set;
153-};
154-
155-struct FakeInputServerConfiguration : public mir_test_framework::TestingServerConfiguration
156-{
157- FakeInputServerConfiguration()
158- : input_config(the_event_filters(), the_display(), std::shared_ptr<mi::CursorListener>(), the_input_report())
159- {
160- }
161-
162- virtual void inject_input_after_focus()
163- {
164- }
165-
166- std::shared_ptr<mi::InputConfiguration> the_input_configuration() override
167- {
168- fake_event_hub = input_config.the_fake_event_hub();
169- fake_event_hub->synthesize_builtin_keyboard_added();
170- fake_event_hub->synthesize_builtin_cursor_added();
171- fake_event_hub->synthesize_device_scan_complete();
172-
173- return mt::fake_shared(input_config);
174- }
175-
176- std::shared_ptr<mi::InputManager> the_input_manager() override
177- {
178- return DefaultServerConfiguration::the_input_manager();
179- }
180- std::shared_ptr<ms::InputRegistrar> the_input_registrar() override
181- {
182- return DefaultServerConfiguration::the_input_registrar();
183- }
184-
185- std::shared_ptr<msh::InputTargeter>
186- the_input_targeter() override
187- {
188- return input_targeter(
189- [this]()
190- {
191- return std::make_shared<FocusNotifyingInputTargeter>(DefaultServerConfiguration::the_input_targeter(), std::bind(std::mem_fn(&FakeInputServerConfiguration::inject_input_after_focus), this));
192- });
193- }
194-
195- mtd::FakeEventHubInputConfiguration input_config;
196- mia::FakeEventHub* fake_event_hub;
197-};
198-
199-
200-struct ClientConfigCommon : TestingClientConfiguration
201-{
202- ClientConfigCommon() :
203+
204+struct ClientConfig : mtf::TestingClientConfiguration
205+{
206+ ClientConfig() :
207 connection(0),
208 surface(0)
209 {
210@@ -147,19 +73,19 @@
211
212 static void connection_callback(MirConnection* connection, void* context)
213 {
214- ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
215+ ClientConfig* config = reinterpret_cast<ClientConfig *>(context);
216 config->connection = connection;
217 }
218
219 static void create_surface_callback(MirSurface* surface, void* context)
220 {
221- ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
222+ ClientConfig* config = reinterpret_cast<ClientConfig *>(context);
223 config->surface_created(surface);
224 }
225
226 static void release_surface_callback(MirSurface* surface, void* context)
227 {
228- ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
229+ ClientConfig* config = reinterpret_cast<ClientConfig *>(context);
230 config->surface_released(surface);
231 }
232
233@@ -184,29 +110,24 @@
234
235 struct MockInputHandler
236 {
237- MOCK_METHOD1(handle_input, bool(MirEvent const*));
238+ MOCK_METHOD1(handle_input, void(MirEvent const*));
239 };
240
241-struct InputReceivingClient : ClientConfigCommon
242+struct InputClient : ClientConfig
243 {
244- InputReceivingClient(int events_to_receive)
245- : events_to_receive(events_to_receive),
246- events_received(0)
247+ InputClient(std::string const& surface_name)
248+ : surface_name(surface_name)
249 {
250- assert(events_to_receive <= max_events_to_receive);
251 }
252
253 static void handle_input(MirSurface* /* surface */, MirEvent const* ev, void* context)
254 {
255- auto client = static_cast<InputReceivingClient *>(context);
256+ auto client = static_cast<InputClient *>(context);
257
258- if (client->handler->handle_input(ev))
259- {
260- client->event_received[client->events_received].wake_up_everyone();
261- client->events_received++;
262- }
263+ client->handler->handle_input(ev);
264 }
265- virtual void expect_input()
266+
267+ virtual void expect_input(mt::WaitCondition&)
268 {
269 }
270
271@@ -214,7 +135,7 @@
272 {
273 MirSurfaceParameters const request_params =
274 {
275- __PRETTY_FUNCTION__,
276+ surface_name.c_str(),
277 surface_width, surface_height,
278 mir_pixel_format_abgr_8888,
279 mir_buffer_usage_hardware
280@@ -226,7 +147,7 @@
281 {
282 handler = std::make_shared<MockInputHandler>();
283
284- expect_input();
285+ expect_input(events_received);
286
287 mir_wait_for(mir_connect(
288 mir_test_socket,
289@@ -245,9 +166,7 @@
290
291 mir_surface_set_event_handler(surface, &event_delegate);
292
293-
294- for (int i = 0; i < events_to_receive; i++)
295- event_received[i].wait_for_at_most_seconds(5);
296+ events_received.wait_for_at_most_seconds(60);
297
298 mir_surface_release_sync(surface);
299
300@@ -259,12 +178,10 @@
301 }
302
303 std::shared_ptr<MockInputHandler> handler;
304- static int const max_events_to_receive = 4;
305- mt::WaitCondition event_received[max_events_to_receive];
306-
307- int events_to_receive;
308- int events_received;
309-
310+ mt::WaitCondition events_received;
311+
312+ std::string const surface_name;
313+
314 static int const surface_width = 100;
315 static int const surface_height = 100;
316 };
317@@ -343,13 +260,13 @@
318 using namespace ::testing;
319
320 int const num_events_produced = 3;
321+ static std::string const test_client_name = "1";
322
323- struct InputProducingServerConfiguration : FakeInputServerConfiguration
324+ struct ServerConfiguration : mtf::InputTestingServerConfiguration
325 {
326- void inject_input_after_focus()
327+ void inject_input()
328 {
329- // We send multiple events in order to verify the client is handling them
330- // and server dispatch does not become backed up.
331+ wait_until_client_appears(test_client_name);
332 for (int i = 0; i < num_events_produced; i++)
333 fake_event_hub->synthesize_event(mis::a_key_down_event()
334 .of_scancode(KEY_ENTER));
335@@ -357,14 +274,17 @@
336 } server_config;
337 launch_server_process(server_config);
338
339- struct KeyReceivingClient : InputReceivingClient
340+ struct KeyReceivingClient : InputClient
341 {
342- KeyReceivingClient() : InputReceivingClient(num_events_produced) {}
343- void expect_input()
344+ KeyReceivingClient() : InputClient(test_client_name) {}
345+ void expect_input(mt::WaitCondition& events_received) override
346 {
347 using namespace ::testing;
348- EXPECT_CALL(*handler, handle_input(KeyDownEvent())).Times(num_events_produced)
349- .WillRepeatedly(Return(true));
350+ InSequence seq;
351+
352+ EXPECT_CALL(*handler, handle_input(KeyDownEvent())).Times(2);
353+ EXPECT_CALL(*handler, handle_input(KeyDownEvent())).Times(1)
354+ .WillOnce(mt::WakeUp(&events_received));
355 }
356 } client_config;
357 launch_client_process(client_config);
358@@ -373,11 +293,14 @@
359 TEST_F(TestClientInput, clients_receive_us_english_mapped_keys)
360 {
361 using namespace ::testing;
362+ static std::string const test_client_name = "1";
363
364- struct InputProducingServerConfiguration : FakeInputServerConfiguration
365+ struct ServerConfiguration : mtf::InputTestingServerConfiguration
366 {
367- void inject_input_after_focus()
368+ void inject_input()
369 {
370+ wait_until_client_appears(test_client_name);
371+
372 fake_event_hub->synthesize_event(mis::a_key_down_event()
373 .of_scancode(KEY_LEFTSHIFT));
374 fake_event_hub->synthesize_event(mis::a_key_down_event()
375@@ -387,55 +310,57 @@
376 } server_config;
377 launch_server_process(server_config);
378
379- struct KeyReceivingClient : InputReceivingClient
380+ struct KeyReceivingClient : InputClient
381 {
382- KeyReceivingClient() : InputReceivingClient(2) {}
383+ KeyReceivingClient() : InputClient(test_client_name) {}
384
385- void expect_input()
386+ void expect_input(mt::WaitCondition& events_received) override
387 {
388 using namespace ::testing;
389
390 InSequence seq;
391- EXPECT_CALL(*handler, handle_input(AllOf(KeyDownEvent(), KeyOfSymbol(XKB_KEY_Shift_L)))).Times(1)
392- .WillOnce(Return(true));
393+ EXPECT_CALL(*handler, handle_input(AllOf(KeyDownEvent(), KeyOfSymbol(XKB_KEY_Shift_L)))).Times(1);
394 EXPECT_CALL(*handler, handle_input(AllOf(KeyDownEvent(), KeyOfSymbol(XKB_KEY_dollar)))).Times(1)
395- .WillOnce(Return(true));
396+ .WillOnce(mt::WakeUp(&events_received));
397 }
398 } client_config;
399 launch_client_process(client_config);
400 }
401
402-// TODO: This assumes that clients are placed by shell at 0,0. Which probably isn't quite safe!
403 TEST_F(TestClientInput, clients_receive_motion_inside_window)
404 {
405 using namespace ::testing;
406+ static std::string const test_client_name = "1";
407
408- struct InputProducingServerConfiguration : FakeInputServerConfiguration
409+ struct ServerConfiguration : public mtf::InputTestingServerConfiguration
410 {
411- void inject_input_after_focus()
412+ void inject_input()
413 {
414- fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(InputReceivingClient::surface_width,
415- InputReceivingClient::surface_height));
416+ wait_until_client_appears(test_client_name);
417+
418+ fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(InputClient::surface_width,
419+ InputClient::surface_height));
420 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(2,2));
421 }
422 } server_config;
423 launch_server_process(server_config);
424
425- struct MotionReceivingClient : InputReceivingClient
426+ struct MotionReceivingClient : InputClient
427 {
428- MotionReceivingClient() : InputReceivingClient(2) {}
429+ MotionReceivingClient() : InputClient(test_client_name) {}
430
431- void expect_input()
432+ void expect_input(mt::WaitCondition& events_received) override
433 {
434 using namespace ::testing;
435
436 InSequence seq;
437
438 // We should see the cursor enter
439- EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1).WillOnce(Return(true));
440+ EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);
441 EXPECT_CALL(*handler, handle_input(
442- MotionEventWithPosition(InputReceivingClient::surface_width,
443- InputReceivingClient::surface_height))).Times(1).WillOnce(Return(true));
444+ MotionEventWithPosition(InputClient::surface_width,
445+ InputClient::surface_height))).Times(1)
446+ .WillOnce(mt::WakeUp(&events_received));
447 // But we should not receive an event for the second movement outside of our surface!
448 }
449 } client_config;
450@@ -446,27 +371,31 @@
451 {
452 using namespace ::testing;
453
454- struct InputProducingServerConfiguration : FakeInputServerConfiguration
455+ static std::string const test_client_name = "1";
456+
457+ struct ServerConfiguration : public mtf::InputTestingServerConfiguration
458 {
459- void inject_input_after_focus()
460+ void inject_input()
461 {
462+ wait_until_client_appears(test_client_name);
463 fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));
464 }
465 } server_config;
466 launch_server_process(server_config);
467
468- struct ButtonReceivingClient : InputReceivingClient
469+ struct ButtonReceivingClient : InputClient
470 {
471- ButtonReceivingClient() : InputReceivingClient(1) {}
472+ ButtonReceivingClient() : InputClient(test_client_name) {}
473
474- void expect_input()
475+ void expect_input(mt::WaitCondition& events_received) override
476 {
477 using namespace ::testing;
478
479 InSequence seq;
480
481 // The cursor starts at (0, 0).
482- EXPECT_CALL(*handler, handle_input(ButtonDownEvent(0, 0))).Times(1).WillOnce(Return(true));
483+ EXPECT_CALL(*handler, handle_input(ButtonDownEvent(0, 0))).Times(1)
484+ .WillOnce(mt::WakeUp(&events_received));
485 }
486 } client_config;
487 launch_client_process(client_config);
488@@ -474,44 +403,26 @@
489
490 namespace
491 {
492-struct StubViewableArea : public mg::ViewableArea
493-{
494- StubViewableArea(int width, int height) :
495- width(width),
496- height(height),
497- x(0),
498- y(0)
499- {
500- }
501-
502- geom::Rectangle view_area() const
503- {
504- return geom::Rectangle{geom::Point{x, y},
505- geom::Size{width, height}};
506- }
507-
508- geom::Width const width;
509- geom::Height const height;
510- geom::X const x;
511- geom::Y const y;
512-};
513-
514-typedef std::map<std::string, geom::Point> PositionList;
515+typedef std::map<std::string, geom::Rectangle> GeometryList;
516
517 struct StaticPlacementStrategy : public msh::PlacementStrategy
518 {
519- StaticPlacementStrategy(PositionList positions)
520- : surface_positions_by_name(positions)
521+ StaticPlacementStrategy(GeometryList const& positions)
522+ : surface_geometry_by_name(positions)
523 {
524 }
525
526 msh::SurfaceCreationParameters place(msh::SurfaceCreationParameters const& request_parameters)
527 {
528 auto placed = request_parameters;
529- placed.top_left = surface_positions_by_name[request_parameters.name];
530+ auto geometry = surface_geometry_by_name[request_parameters.name];
531+
532+ placed.top_left = geometry.top_left;
533+ placed.size = geometry.size;
534+
535 return placed;
536 }
537- PositionList surface_positions_by_name;
538+ GeometryList surface_geometry_by_name;
539 };
540
541 }
542@@ -520,115 +431,77 @@
543 {
544 using namespace ::testing;
545
546- int const screen_width = 1000;
547- int const screen_height = 800;
548- int const client_width = screen_width/2;
549- int const client_height = screen_height;
550- std::string const surface1_name = "1";
551- std::string const surface2_name = "2";
552+ static int const screen_width = 1000;
553+ static int const screen_height = 800;
554+ static int const client_height = screen_width/2;
555+ static int const client_width = screen_width/2;
556+ static std::string const test_client_1 = "1";
557+ static std::string const test_client_2 = "2";
558
559- PositionList positions;
560- positions[surface1_name] = geom::Point{geom::X{0}, geom::Y{0}};
561- positions[surface2_name] = geom::Point{geom::X{screen_width/2}, geom::Y{0}};
562-
563- struct TestServerConfiguration : FakeInputServerConfiguration
564+ struct ServerConfiguration : mtf::InputTestingServerConfiguration
565 {
566- TestServerConfiguration(int screen_width, int screen_height, PositionList positions)
567- : screen_width(screen_width),
568- screen_height(screen_height),
569- positions(positions),
570- clients_ready(0)
571- {
572- }
573-
574- std::shared_ptr<mg::ViewableArea> the_viewable_area() override
575- {
576- return std::make_shared<StubViewableArea>(screen_width, screen_height);
577- }
578 std::shared_ptr<msh::PlacementStrategy> the_shell_placement_strategy() override
579 {
580+ static GeometryList positions;
581+ positions[test_client_1] = geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
582+ geom::Size{geom::Width{client_width}, geom::Height{client_height}}};
583+ positions[test_client_2] = geom::Rectangle{geom::Point{geom::X{screen_width/2}, geom::Y{0}},
584+ geom::Size{geom::Width{client_width}, geom::Height{client_height}}};
585+
586 return std::make_shared<StaticPlacementStrategy>(positions);
587 }
588+
589+ geom::Rectangle the_screen_geometry() override
590+ {
591+ return geom::Rectangle{geom::Point{geom::X{0}, geom::Y{0}},
592+ geom::Size{geom::Width{screen_width}, geom::Height{screen_height}}};
593+ }
594
595- void inject_input_after_focus() override
596+ void inject_input() override
597 {
598- clients_ready++;
599- if (clients_ready != 2) // We wait until both clients have connected and registered with the input stack.
600- return;
601-
602+ wait_until_client_appears(test_client_1);
603+ wait_until_client_appears(test_client_2);
604 // In the bounds of the first surface
605 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2-1, 0));
606 // In the bounds of the second surface
607 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2, 0));
608 }
609- int screen_width, screen_height;
610- PositionList positions;
611-
612- int clients_ready;
613- } server_config(screen_width, screen_height, positions);
614+ } server_config;
615
616 launch_server_process(server_config);
617
618- MirSurfaceParameters const surface1_params =
619- {
620- surface1_name.c_str(),
621- client_width, client_height,
622- mir_pixel_format_abgr_8888,
623- mir_buffer_usage_hardware
624- };
625- MirSurfaceParameters const surface2_params =
626- {
627- surface2_name.c_str(),
628- client_width, client_height,
629- mir_pixel_format_abgr_8888,
630- mir_buffer_usage_hardware
631- };
632- struct ParameterizedClient : InputReceivingClient
633+ struct InputClientOne : InputClient
634 {
635- ParameterizedClient(MirSurfaceParameters params, int events_to_expect) :
636- InputReceivingClient(events_to_expect),
637- params(params)
638+ InputClientOne()
639+ : InputClient(test_client_1)
640 {
641 }
642
643- MirSurfaceParameters parameters() override
644+ void expect_input(mt::WaitCondition& events_received) override
645 {
646- return params;
647+ InSequence seq;
648+ EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);
649+ EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(client_width - 1, 0))).Times(1);
650+ EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(1)
651+ .WillOnce(mt::WakeUp(&events_received));
652 }
653+ } client_1;
654
655- MirSurfaceParameters params;
656- };
657- struct InputClientOne : ParameterizedClient
658- {
659- InputClientOne(MirSurfaceParameters params) :
660- ParameterizedClient(params, 3)
661- {
662- }
663-
664- void expect_input() override
665- {
666- InSequence seq;
667- EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1).WillOnce(Return(true));
668- EXPECT_CALL(*handler, handle_input(
669- MotionEventWithPosition(params.width - 1, 0))).Times(1).WillOnce(Return(true));
670- EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(1).WillOnce(Return(true));
671- }
672- } client_1(surface1_params);
673- struct InputClientTwo : ParameterizedClient
674- {
675- InputClientTwo(MirSurfaceParameters params) :
676- ParameterizedClient(params, 2)
677- {
678- }
679-
680- void expect_input() override
681- {
682- InSequence seq;
683- EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1).WillOnce(Return(true));
684- EXPECT_CALL(*handler, handle_input(
685- MotionEventWithPosition(params.width - 1, 0))).Times(1).WillOnce(Return(true));
686- }
687- } client_2(surface2_params);
688+ struct InputClientTwo : InputClient
689+ {
690+ InputClientTwo()
691+ : InputClient(test_client_2)
692+ {
693+ }
694+
695+ void expect_input(mt::WaitCondition& events_received) override
696+ {
697+ InSequence seq;
698+ EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);
699+ EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(client_width - 1, 0))).Times(1)
700+ .WillOnce(mt::WakeUp(&events_received));
701+ }
702+ } client_2;
703
704 launch_client_process(client_1);
705 launch_client_process(client_2);
706
707=== modified file 'tests/mir_test_framework/CMakeLists.txt'
708--- tests/mir_test_framework/CMakeLists.txt 2013-04-24 05:22:20 +0000
709+++ tests/mir_test_framework/CMakeLists.txt 2013-06-14 16:12:27 +0000
710@@ -8,6 +8,7 @@
711 TEST_FRAMEWORK_SRCS
712
713 testing_server_options.cpp
714+ input_testing_server_options.cpp
715 testing_process_manager.cpp
716 display_server_test_fixture.cpp
717 process.cpp
718@@ -20,6 +21,7 @@
719 add_library(
720 mir-test-framework STATIC
721 ${TEST_FRAMEWORK_SRCS})
722+uses_android_input(mir-test-framework)
723
724 target_link_libraries(
725 mir-test-framework
726
727=== added file 'tests/mir_test_framework/input_testing_server_options.cpp'
728--- tests/mir_test_framework/input_testing_server_options.cpp 1970-01-01 00:00:00 +0000
729+++ tests/mir_test_framework/input_testing_server_options.cpp 2013-06-14 16:12:27 +0000
730@@ -0,0 +1,247 @@
731+/*
732+ * Copyright © 2013 Canonical Ltd.
733+ *
734+ * This program is free software: you can redistribute it and/or modify it
735+ * under the terms of the GNU General Public License version 3,
736+ * as published by the Free Software Foundation.
737+ *
738+ * This program is distributed in the hope that it will be useful,
739+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
740+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
741+ * GNU General Public License for more details.
742+ *
743+ * You should have received a copy of the GNU General Public License
744+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
745+ *
746+ * Authored by: Robert Carr <robert.carr@canonical.com>
747+ */
748+
749+#include "mir_test_framework/input_testing_server_configuration.h"
750+
751+#include "mir/input/surface_target.h"
752+#include "mir/surfaces/input_registrar.h"
753+#include "mir/graphics/display.h"
754+#include "mir/graphics/viewable_area.h"
755+
756+#include "mir_test/fake_event_hub.h"
757+#include "mir_test/fake_event_hub_input_configuration.h"
758+
759+#include <boost/throw_exception.hpp>
760+
761+#include <functional>
762+#include <stdexcept>
763+
764+#include <time.h>
765+
766+namespace mtf = mir_test_framework;
767+
768+namespace ms = mir::surfaces;
769+namespace mg = mir::graphics;
770+namespace mi = mir::input;
771+namespace mia = mi::android;
772+namespace geom = mir::geometry;
773+namespace mtd = mir::test::doubles;
774+
775+namespace
776+{
777+class SurfaceReadinessListener
778+{
779+public:
780+ virtual ~SurfaceReadinessListener() = default;
781+
782+ virtual void surface_ready_for_input(std::string const& surface_name) = 0;
783+ virtual void surface_finished_for_input(std::string const& surface_name) = 0;
784+
785+protected:
786+ SurfaceReadinessListener() = default;
787+ SurfaceReadinessListener(SurfaceReadinessListener const&) = delete;
788+ SurfaceReadinessListener& operator=(SurfaceReadinessListener const&) = delete;
789+};
790+
791+class ProxyInputRegistrar : public ms::InputRegistrar
792+{
793+public:
794+ ProxyInputRegistrar(std::shared_ptr<ms::InputRegistrar> const underlying_registrar,
795+ std::shared_ptr<SurfaceReadinessListener> const listener)
796+ : underlying_registrar(underlying_registrar),
797+ listener(listener)
798+ {
799+ }
800+
801+ ~ProxyInputRegistrar() noexcept(true) = default;
802+
803+ void input_surface_opened(std::shared_ptr<mi::SurfaceTarget> const& opened_surface)
804+ {
805+ underlying_registrar->input_surface_opened(opened_surface);
806+ listener->surface_ready_for_input(opened_surface->name());
807+ }
808+ void input_surface_closed(std::shared_ptr<mi::SurfaceTarget> const& closed_surface)
809+ {
810+ underlying_registrar->input_surface_closed(closed_surface);
811+ listener->surface_finished_for_input(closed_surface->name());
812+ }
813+
814+private:
815+ std::shared_ptr<ms::InputRegistrar> const underlying_registrar;
816+ std::shared_ptr<SurfaceReadinessListener> const listener;
817+};
818+
819+struct SizedViewArea : public mg::ViewableArea
820+{
821+ SizedViewArea(geom::Rectangle const& area)
822+ : area(area)
823+ {
824+ }
825+ geom::Rectangle view_area() const override
826+ {
827+ return area;
828+ }
829+ geom::Rectangle area;
830+};
831+}
832+
833+mtf::InputTestingServerConfiguration::InputTestingServerConfiguration()
834+{
835+}
836+
837+void mtf::InputTestingServerConfiguration::exec()
838+{
839+ input_injection_thread = std::thread(std::mem_fn(&mtf::InputTestingServerConfiguration::inject_input), this);
840+}
841+
842+void mtf::InputTestingServerConfiguration::on_exit()
843+{
844+ input_injection_thread.join();
845+}
846+
847+std::shared_ptr<mi::InputConfiguration> mtf::InputTestingServerConfiguration::the_input_configuration()
848+{
849+ if (!input_configuration)
850+ {
851+ std::shared_ptr<mi::CursorListener> null_cursor_listener{nullptr};
852+
853+ input_configuration = std::make_shared<mtd::FakeEventHubInputConfiguration>(the_event_filters(),
854+ the_display(),
855+ null_cursor_listener,
856+ the_input_report());
857+ fake_event_hub = input_configuration->the_fake_event_hub();
858+
859+ fake_event_hub->synthesize_builtin_keyboard_added();
860+ fake_event_hub->synthesize_builtin_cursor_added();
861+ fake_event_hub->synthesize_device_scan_complete();
862+ }
863+
864+ return input_configuration;
865+}
866+
867+std::shared_ptr<ms::InputRegistrar> mtf::InputTestingServerConfiguration::the_input_registrar()
868+{
869+ struct LifecycleTracker : public SurfaceReadinessListener
870+ {
871+ LifecycleTracker(std::mutex& lifecycle_lock,
872+ std::condition_variable &lifecycle_condition,
873+ std::map<std::string, mtf::ClientLifecycleState> &client_lifecycles)
874+ : lifecycle_lock(lifecycle_lock),
875+ lifecycle_condition(lifecycle_condition),
876+ client_lifecycles(client_lifecycles)
877+ {
878+ }
879+ void surface_ready_for_input(std::string const& surface_name)
880+ {
881+ std::unique_lock<std::mutex> lg(lifecycle_lock);
882+ client_lifecycles[surface_name] = mtf::ClientLifecycleState::appeared;
883+ lifecycle_condition.notify_all();
884+ }
885+
886+ void surface_finished_for_input(std::string const& surface_name)
887+ {
888+ std::unique_lock<std::mutex> lg(lifecycle_lock);
889+ client_lifecycles[surface_name] = mtf::ClientLifecycleState::vanished;
890+ lifecycle_condition.notify_all();
891+ }
892+ std::mutex &lifecycle_lock;
893+ std::condition_variable &lifecycle_condition;
894+ std::map<std::string, mtf::ClientLifecycleState> &client_lifecycles;
895+ };
896+
897+ if (!input_registrar)
898+ {
899+ auto registrar_listener = std::make_shared<LifecycleTracker>(lifecycle_lock,
900+ lifecycle_condition,
901+ client_lifecycles);
902+ input_registrar = std::make_shared<ProxyInputRegistrar>(the_input_configuration()->the_input_registrar(),
903+ registrar_listener);
904+ }
905+
906+ return input_registrar;
907+}
908+
909+geom::Rectangle mtf::InputTestingServerConfiguration::the_screen_geometry()
910+{
911+ static geom::Rectangle const default_geometry{geom::Point{geom::X{0}, geom::Y{0}},
912+ geom::Size{geom::Width{1600}, geom::Height{1600}}};
913+ return default_geometry;
914+}
915+
916+std::shared_ptr<mg::ViewableArea> mtf::InputTestingServerConfiguration::the_viewable_area()
917+{
918+ if (!view_area)
919+ view_area = std::make_shared<SizedViewArea>(the_screen_geometry());
920+ return view_area;
921+}
922+
923+void mtf::InputTestingServerConfiguration::wait_until_client_appears(std::string const& surface_name)
924+{
925+ std::unique_lock<std::mutex> lg(lifecycle_lock);
926+
927+ time_t end_time = ::time(NULL) + 60;
928+
929+ if (client_lifecycles[surface_name] == appeared)
930+ {
931+ return;
932+ }
933+ else if (client_lifecycles[surface_name] == vanished)
934+ {
935+ BOOST_THROW_EXCEPTION(std::runtime_error("Waiting for a client (" + surface_name + ") to appear but it has already vanished"));
936+ }
937+ else
938+ {
939+ while (::time(NULL) < end_time)
940+ {
941+ lifecycle_condition.wait_for(lg, std::chrono::seconds(60));
942+ if (client_lifecycles[surface_name] == appeared)
943+ {
944+ return;
945+ }
946+ }
947+ BOOST_THROW_EXCEPTION(std::runtime_error("Timed out waiting for client (" + surface_name + ") to appear"));
948+ }
949+}
950+
951+void mtf::InputTestingServerConfiguration::wait_until_client_vanishes(std::string const& surface_name)
952+{
953+ std::unique_lock<std::mutex> lg(lifecycle_lock);
954+
955+ time_t end_time = ::time(NULL) + 60;
956+
957+ if (client_lifecycles[surface_name] == vanished)
958+ {
959+ return;
960+ }
961+ else if (client_lifecycles[surface_name] == starting)
962+ {
963+ BOOST_THROW_EXCEPTION(std::runtime_error("Waiting for a client (" + surface_name + ") to vanish but it has not started"));
964+ }
965+ else
966+ {
967+ while (::time(NULL) < end_time)
968+ {
969+ lifecycle_condition.wait_for(lg, std::chrono::seconds(60));
970+ if (client_lifecycles[surface_name] == vanished)
971+ {
972+ return;
973+ }
974+ }
975+ BOOST_THROW_EXCEPTION(std::runtime_error("Timed out waiting for client (" + surface_name + ") to vanish"));
976+ }
977+}

Subscribers

People subscribed via source and target branches