Mir

Merge lp:~robertcarr/mir/refactor-input-acceptance-take-1 into lp:mir

Proposed by Robert Carr
Status: Merged
Approved by: Robert Carr
Approved revision: no longer in the source branch.
Merged at revision: 1210
Proposed branch: lp:~robertcarr/mir/refactor-input-acceptance-take-1
Merge into: lp:mir
Prerequisite: lp:~robertcarr/mir/client-input-report
Diff against target: 1323 lines (+479/-665)
4 files modified
include/test/mir_test/client_event_matchers.h (+119/-0)
include/test/mir_test_framework/input_testing_server_configuration.h (+2/-10)
tests/acceptance-tests/test_client_input.cpp (+358/-543)
tests/mir_test_framework/input_testing_server_options.cpp (+0/-112)
To merge this branch: bzr merge lp:~robertcarr/mir/refactor-input-acceptance-take-1
Reviewer Review Type Date Requested Status
Alexandros Frantzis (community) Approve
Alan Griffiths Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+194265@code.launchpad.net

This proposal supersedes a proposal from 2013-11-07.

Commit message

Some cleanups to test_client_input.cpp

Description of the change

First take at simplifying input acceptance tests with a better shared fixture.

Merged client-input-report to debug an issue, so hopefully we can merge this after.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Looks sane in general. I am not familiar enough with the functionality to be sure everything is covered adequately.

review: Approve
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

Looks good.

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

The tests are basically identical. Mostly about.

1. Removing some dead code (wait_until_client_appears, etc).
2. Factoring out highly repetitive code: i.e. this sort of stuff
918 - struct ServerConfiguration : mtf::InputTestingServerConfiguration
919 - {
920 - mtf::CrossProcessSync input_cb_setup_fence;
921 -
922 - ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence)
923 - : input_cb_setup_fence(input_cb_setup_fence)
--------------
0 - void inject_input()
301 - {
302 - wait_until_client_appears(test_client_name);
303 - input_cb_setup_fence.wait_for_signal_ready_for();
304 -

309 - } server_config(fence);
310 - launch_server_process(server_config);
-------------
311 -
312 - struct KeyReceivingClient : InputClient
313 - {
314 - KeyReceivingClient(const mtf::CrossProcessSync& fence) : InputClient(fence, test_client_name) {}
3. Removing some noise from the file, i.e. client_event_matchers.h

I can't quite see the end yet so am trying to make small improvements like this, but would eventually like to get to something super succint:
mtf::InputReceivingClient client;
client.expects_input(mis::a_button_down_event()).expects_input(mis::a_button_up_event()).exits();

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'include/test/mir_test/client_event_matchers.h'
--- include/test/mir_test/client_event_matchers.h 1970-01-01 00:00:00 +0000
+++ include/test/mir_test/client_event_matchers.h 2013-11-07 17:24:53 +0000
@@ -0,0 +1,119 @@
1/*
2 * Copyright © 2013 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Robert Carr <robert.carr@canonical.com>
17 */
18
19#include "mir_toolkit/event.h"
20
21#include <xkbcommon/xkbcommon.h>
22#include <xkbcommon/xkbcommon-keysyms.h>
23
24#include <gmock/gmock.h>
25
26namespace mir
27{
28namespace test
29{
30
31MATCHER(KeyDownEvent, "")
32{
33 if (arg->type != mir_event_type_key)
34 return false;
35 if (arg->key.action != mir_key_action_down) // Key down
36 return false;
37
38 return true;
39}
40MATCHER_P(KeyOfSymbol, keysym, "")
41{
42 if (static_cast<xkb_keysym_t>(arg->key.key_code) == (uint)keysym)
43 return true;
44 return false;
45}
46
47MATCHER(HoverEnterEvent, "")
48{
49 if (arg->type != mir_event_type_motion)
50 return false;
51 if (arg->motion.action != mir_motion_action_hover_enter)
52 return false;
53
54 return true;
55}
56MATCHER(HoverExitEvent, "")
57{
58 if (arg->type != mir_event_type_motion)
59 return false;
60 if (arg->motion.action != mir_motion_action_hover_exit)
61 return false;
62
63 return true;
64}
65
66MATCHER_P2(ButtonDownEvent, x, y, "")
67{
68 if (arg->type != mir_event_type_motion)
69 return false;
70 if (arg->motion.action != mir_motion_action_down)
71 return false;
72 if (arg->motion.button_state == 0)
73 return false;
74 if (arg->motion.pointer_coordinates[0].x != x)
75 return false;
76 if (arg->motion.pointer_coordinates[0].y != y)
77 return false;
78 return true;
79}
80
81MATCHER_P2(ButtonUpEvent, x, y, "")
82{
83 if (arg->type != mir_event_type_motion)
84 return false;
85 if (arg->motion.action != mir_motion_action_up)
86 return false;
87 if (arg->motion.pointer_coordinates[0].x != x)
88 return false;
89 if (arg->motion.pointer_coordinates[0].y != y)
90 return false;
91 return true;
92}
93
94MATCHER_P2(MotionEventWithPosition, x, y, "")
95{
96 if (arg->type != mir_event_type_motion)
97 return false;
98 if (arg->motion.action != mir_motion_action_move &&
99 arg->motion.action != mir_motion_action_hover_move)
100 return false;
101 if (arg->motion.pointer_coordinates[0].x != x)
102 return false;
103 if (arg->motion.pointer_coordinates[0].y != y)
104 return false;
105 return true;
106}
107
108MATCHER(MovementEvent, "")
109{
110 if (arg->type != mir_event_type_motion)
111 return false;
112 if (arg->motion.action != mir_motion_action_move &&
113 arg->motion.action != mir_motion_action_hover_move)
114 return false;
115 return true;
116}
117
118}
119}
0120
=== modified file 'include/test/mir_test_framework/input_testing_server_configuration.h'
--- include/test/mir_test_framework/input_testing_server_configuration.h 2013-08-28 03:41:48 +0000
+++ include/test/mir_test_framework/input_testing_server_configuration.h 2013-11-07 17:24:53 +0000
@@ -52,8 +52,6 @@
52namespace mir_test_framework52namespace mir_test_framework
53{53{
5454
55enum ClientLifecycleState { starting, appeared, vanished };
56
57class InputTestingServerConfiguration : public TestingServerConfiguration55class InputTestingServerConfiguration : public TestingServerConfiguration
58{56{
59public:57public:
@@ -63,24 +61,18 @@
63 void on_exit();61 void on_exit();
64 62
65 std::shared_ptr<mir::input::InputConfiguration> the_input_configuration() override;63 std::shared_ptr<mir::input::InputConfiguration> the_input_configuration() override;
66 std::shared_ptr<mir::frontend::Shell> the_frontend_shell() override;64
65 mir::input::android::FakeEventHub* fake_event_hub;
6766
68protected:67protected:
69 virtual void inject_input() = 0;68 virtual void inject_input() = 0;
70 mir::input::android::FakeEventHub* fake_event_hub;
7169
72 void wait_until_client_appears(std::string const& surface_name);70 void wait_until_client_appears(std::string const& surface_name);
7371
74private:72private:
75 std::mutex lifecycle_lock;
76
77 std::condition_variable lifecycle_condition;
78 std::map<std::string, ClientLifecycleState> client_lifecycles;
79
80 std::thread input_injection_thread;73 std::thread input_injection_thread;
81 74
82 std::shared_ptr<mir::test::doubles::FakeEventHubInputConfiguration> input_configuration;75 std::shared_ptr<mir::test::doubles::FakeEventHubInputConfiguration> input_configuration;
83 std::shared_ptr<mir::frontend::Shell> frontend_shell;
84};76};
8577
86}78}
8779
=== modified file 'tests/acceptance-tests/test_client_input.cpp'
--- tests/acceptance-tests/test_client_input.cpp 2013-10-28 21:41:27 +0000
+++ tests/acceptance-tests/test_client_input.cpp 2013-11-07 17:24:53 +0000
@@ -35,6 +35,7 @@
35#include "mir_test/fake_event_hub.h"35#include "mir_test/fake_event_hub.h"
36#include "mir_test/event_factory.h"36#include "mir_test/event_factory.h"
37#include "mir_test/wait_condition.h"37#include "mir_test/wait_condition.h"
38#include "mir_test/client_event_matchers.h"
38#include "mir_test_framework/cross_process_sync.h"39#include "mir_test_framework/cross_process_sync.h"
39#include "mir_test_framework/display_server_test_fixture.h"40#include "mir_test_framework/display_server_test_fixture.h"
40#include "mir_test_framework/input_testing_server_configuration.h"41#include "mir_test_framework/input_testing_server_configuration.h"
@@ -42,9 +43,6 @@
42#include <gtest/gtest.h>43#include <gtest/gtest.h>
43#include <gmock/gmock.h>44#include <gmock/gmock.h>
4445
45#include <xkbcommon/xkbcommon.h>
46#include <xkbcommon/xkbcommon-keysyms.h>
47
48#include <thread>46#include <thread>
49#include <functional>47#include <functional>
50#include <map>48#include <map>
@@ -213,281 +211,6 @@
213 static int const surface_height = 100;211 static int const surface_height = 100;
214};212};
215213
216MATCHER(KeyDownEvent, "")
217{
218 if (arg->type != mir_event_type_key)
219 return false;
220 if (arg->key.action != mir_key_action_down) // Key down
221 return false;
222
223 return true;
224}
225MATCHER_P(KeyOfSymbol, keysym, "")
226{
227 if (static_cast<xkb_keysym_t>(arg->key.key_code) == (uint)keysym)
228 return true;
229 return false;
230}
231
232MATCHER(HoverEnterEvent, "")
233{
234 if (arg->type != mir_event_type_motion)
235 return false;
236 if (arg->motion.action != mir_motion_action_hover_enter)
237 return false;
238
239 return true;
240}
241MATCHER(HoverExitEvent, "")
242{
243 if (arg->type != mir_event_type_motion)
244 return false;
245 if (arg->motion.action != mir_motion_action_hover_exit)
246 return false;
247
248 return true;
249}
250
251MATCHER_P2(ButtonDownEvent, x, y, "")
252{
253 if (arg->type != mir_event_type_motion)
254 return false;
255 if (arg->motion.action != mir_motion_action_down)
256 return false;
257 if (arg->motion.button_state == 0)
258 return false;
259 if (arg->motion.pointer_coordinates[0].x != x)
260 return false;
261 if (arg->motion.pointer_coordinates[0].y != y)
262 return false;
263 return true;
264}
265
266MATCHER_P2(ButtonUpEvent, x, y, "")
267{
268 if (arg->type != mir_event_type_motion)
269 return false;
270 if (arg->motion.action != mir_motion_action_up)
271 return false;
272 if (arg->motion.pointer_coordinates[0].x != x)
273 return false;
274 if (arg->motion.pointer_coordinates[0].y != y)
275 return false;
276 return true;
277}
278
279MATCHER_P2(MotionEventWithPosition, x, y, "")
280{
281 if (arg->type != mir_event_type_motion)
282 return false;
283 if (arg->motion.action != mir_motion_action_move &&
284 arg->motion.action != mir_motion_action_hover_move)
285 return false;
286 if (arg->motion.pointer_coordinates[0].x != x)
287 return false;
288 if (arg->motion.pointer_coordinates[0].y != y)
289 return false;
290 return true;
291}
292
293MATCHER(MovementEvent, "")
294{
295 if (arg->type != mir_event_type_motion)
296 return false;
297 if (arg->motion.action != mir_motion_action_move &&
298 arg->motion.action != mir_motion_action_hover_move)
299 return false;
300 return true;
301}
302
303}
304
305
306using TestClientInput = BespokeDisplayServerTestFixture;
307
308TEST_F(TestClientInput, clients_receive_key_input)
309{
310 using namespace ::testing;
311
312 int const num_events_produced = 3;
313 static std::string const test_client_name = "1";
314
315 mtf::CrossProcessSync fence;
316
317 struct ServerConfiguration : mtf::InputTestingServerConfiguration
318 {
319 mtf::CrossProcessSync input_cb_setup_fence;
320
321 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence)
322 : input_cb_setup_fence(input_cb_setup_fence)
323 {
324 }
325
326 void inject_input()
327 {
328 wait_until_client_appears(test_client_name);
329 input_cb_setup_fence.wait_for_signal_ready_for();
330
331 for (int i = 0; i < num_events_produced; i++)
332 fake_event_hub->synthesize_event(mis::a_key_down_event()
333 .of_scancode(KEY_ENTER));
334 }
335 } server_config(fence);
336 launch_server_process(server_config);
337
338 struct KeyReceivingClient : InputClient
339 {
340 KeyReceivingClient(const mtf::CrossProcessSync& fence) : InputClient(fence, test_client_name) {}
341 void expect_input(mt::WaitCondition& events_received) override
342 {
343 using namespace ::testing;
344 InSequence seq;
345
346 EXPECT_CALL(*handler, handle_input(KeyDownEvent())).Times(2);
347 EXPECT_CALL(*handler, handle_input(KeyDownEvent())).Times(1)
348 .WillOnce(mt::WakeUp(&events_received));
349 }
350 } client_config(fence);
351 launch_client_process(client_config);
352}
353
354TEST_F(TestClientInput, clients_receive_us_english_mapped_keys)
355{
356 using namespace ::testing;
357 static std::string const test_client_name = "1";
358 mtf::CrossProcessSync fence;
359
360 struct ServerConfiguration : mtf::InputTestingServerConfiguration
361 {
362 mtf::CrossProcessSync input_cb_setup_fence;
363
364 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence)
365 : input_cb_setup_fence(input_cb_setup_fence)
366 {
367 }
368
369 void inject_input()
370 {
371 wait_until_client_appears(test_client_name);
372 input_cb_setup_fence.wait_for_signal_ready_for();
373
374 fake_event_hub->synthesize_event(mis::a_key_down_event()
375 .of_scancode(KEY_LEFTSHIFT));
376 fake_event_hub->synthesize_event(mis::a_key_down_event()
377 .of_scancode(KEY_4));
378
379 }
380 } server_config{fence};
381 launch_server_process(server_config);
382
383 struct KeyReceivingClient : InputClient
384 {
385 KeyReceivingClient(const mtf::CrossProcessSync& fence) : InputClient(fence, test_client_name) {}
386
387 void expect_input(mt::WaitCondition& events_received) override
388 {
389 using namespace ::testing;
390
391 InSequence seq;
392 EXPECT_CALL(*handler, handle_input(AllOf(KeyDownEvent(), KeyOfSymbol(XKB_KEY_Shift_L)))).Times(1);
393 EXPECT_CALL(*handler, handle_input(AllOf(KeyDownEvent(), KeyOfSymbol(XKB_KEY_dollar)))).Times(1)
394 .WillOnce(mt::WakeUp(&events_received));
395 }
396 } client_config{fence};
397 launch_client_process(client_config);
398}
399
400TEST_F(TestClientInput, clients_receive_motion_inside_window)
401{
402 using namespace ::testing;
403 static std::string const test_client_name = "1";
404 mtf::CrossProcessSync fence;
405
406 struct ServerConfiguration : public mtf::InputTestingServerConfiguration
407 {
408 mtf::CrossProcessSync input_cb_setup_fence;
409
410 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence)
411 : input_cb_setup_fence(input_cb_setup_fence)
412 {
413 }
414
415 void inject_input()
416 {
417 wait_until_client_appears(test_client_name);
418 input_cb_setup_fence.wait_for_signal_ready_for();
419
420 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(InputClient::surface_width - 1,
421 InputClient::surface_height - 1));
422 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(2,2));
423 }
424 } server_config{fence};
425 launch_server_process(server_config);
426
427 struct MotionReceivingClient : InputClient
428 {
429 MotionReceivingClient(const mtf::CrossProcessSync& fence) : InputClient(fence, test_client_name) {}
430
431 void expect_input(mt::WaitCondition& events_received) override
432 {
433 using namespace ::testing;
434
435 InSequence seq;
436
437 // We should see the cursor enter
438 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);
439 EXPECT_CALL(*handler, handle_input(
440 MotionEventWithPosition(InputClient::surface_width - 1,
441 InputClient::surface_height - 1))).Times(1)
442 .WillOnce(mt::WakeUp(&events_received));
443 // But we should not receive an event for the second movement outside of our surface!
444 }
445 } client_config{fence};
446 launch_client_process(client_config);
447}
448
449TEST_F(TestClientInput, clients_receive_button_events_inside_window)
450{
451 using namespace ::testing;
452
453 static std::string const test_client_name = "1";
454 mtf::CrossProcessSync fence;
455
456 struct ServerConfiguration : public mtf::InputTestingServerConfiguration
457 {
458 mtf::CrossProcessSync input_cb_setup_fence;
459
460 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence)
461 : input_cb_setup_fence(input_cb_setup_fence)
462 {
463 }
464
465 void inject_input()
466 {
467 wait_until_client_appears(test_client_name);
468 input_cb_setup_fence.wait_for_signal_ready_for();
469
470 fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));
471 }
472 } server_config{fence};
473 launch_server_process(server_config);
474
475 struct ButtonReceivingClient : InputClient
476 {
477 ButtonReceivingClient(const mtf::CrossProcessSync& fence) : InputClient(fence, test_client_name) {}
478
479 void expect_input(mt::WaitCondition& events_received) override
480 {
481 using namespace ::testing;
482
483 InSequence seq;
484
485 // The cursor starts at (0, 0).
486 EXPECT_CALL(*handler, handle_input(ButtonDownEvent(0, 0))).Times(1)
487 .WillOnce(mt::WakeUp(&events_received));
488 }
489 } client_config{fence};
490 launch_client_process(client_config);
491}214}
492215
493namespace216namespace
@@ -497,34 +220,251 @@
497220
498struct StaticPlacementStrategy : public msh::PlacementStrategy221struct StaticPlacementStrategy : public msh::PlacementStrategy
499{222{
500 StaticPlacementStrategy(GeometryMap const& positions,223 StaticPlacementStrategy(std::shared_ptr<msh::PlacementStrategy> const& underlying_strategy,
224 GeometryMap const& positions,
501 DepthMap const& depths)225 DepthMap const& depths)
502 : surface_geometry_by_name(positions),226 : underlying_strategy(underlying_strategy),
227 surface_geometry_by_name(positions),
503 surface_depths_by_name(depths)228 surface_depths_by_name(depths)
504 {229 {
505 }230 }
506231
507 StaticPlacementStrategy(GeometryMap const& positions)232 StaticPlacementStrategy(std::shared_ptr<msh::PlacementStrategy> const& underlying_strategy,
508 : StaticPlacementStrategy(positions, DepthMap())233 GeometryMap const& positions)
234 : StaticPlacementStrategy(underlying_strategy, positions, DepthMap())
509 {235 {
510 }236 }
511237
512 msh::SurfaceCreationParameters place(msh::Session const&, msh::SurfaceCreationParameters const& request_parameters)238 msh::SurfaceCreationParameters place(msh::Session const& session, msh::SurfaceCreationParameters const& request_parameters)
513 {239 {
514 auto placed = request_parameters;240 auto placed = request_parameters;
515 auto const& name = request_parameters.name;241 auto const& name = request_parameters.name;
516 auto geometry = surface_geometry_by_name[name];242
517243 auto it = surface_geometry_by_name.find(name);
518 placed.top_left = geometry.top_left;244 if (it != surface_geometry_by_name.end())
519 placed.size = geometry.size;245 {
246 auto const& geometry = it->second;
247 placed.top_left = geometry.top_left;
248 placed.size = geometry.size;
249 }
250 else
251 {
252 placed = underlying_strategy->place(session, placed);
253 }
520 placed.depth = surface_depths_by_name[name];254 placed.depth = surface_depths_by_name[name];
521 255
522 return placed;256 return placed;
523 }257 }
258
259 std::shared_ptr<msh::PlacementStrategy> const underlying_strategy;
524 GeometryMap surface_geometry_by_name;260 GeometryMap surface_geometry_by_name;
525 DepthMap surface_depths_by_name;261 DepthMap surface_depths_by_name;
526};262};
527263
264std::shared_ptr<mtf::InputTestingServerConfiguration>
265make_event_producing_server(mtf::CrossProcessSync const& client_ready_fence,
266 int number_of_clients,
267 std::function<void(mtf::InputTestingServerConfiguration& server)> const& produce_events,
268 GeometryMap const& client_geometry_map, DepthMap const& client_depth_map)
269{
270 struct ServerConfiguration : mtf::InputTestingServerConfiguration
271 {
272 mtf::CrossProcessSync input_cb_setup_fence;
273 int const number_of_clients;
274 std::function<void(mtf::InputTestingServerConfiguration& server)> const produce_events;
275 GeometryMap const client_geometry;
276 DepthMap const client_depth;
277
278 ServerConfiguration(mtf::CrossProcessSync const& input_cb_setup_fence, int number_of_clients,
279 std::function<void(mtf::InputTestingServerConfiguration& server)> const& produce_events,
280 GeometryMap const& client_geometry, DepthMap const& client_depth)
281 : input_cb_setup_fence(input_cb_setup_fence),
282 number_of_clients(number_of_clients),
283 produce_events(produce_events),
284 client_geometry(client_geometry),
285 client_depth(client_depth)
286 {
287 }
288
289 std::shared_ptr<msh::PlacementStrategy> the_shell_placement_strategy() override
290 {
291 return std::make_shared<StaticPlacementStrategy>(InputTestingServerConfiguration::the_shell_placement_strategy(),
292 client_geometry, client_depth);
293 }
294
295 void inject_input()
296 {
297 for (int i = 1; i < number_of_clients + 1; i++)
298 EXPECT_EQ(i, input_cb_setup_fence.wait_for_signal_ready_for());
299 produce_events(*this);
300 }
301 };
302 return std::make_shared<ServerConfiguration>(client_ready_fence, number_of_clients,
303 produce_events, client_geometry_map, client_depth_map);
304}
305
306std::shared_ptr<mtf::InputTestingServerConfiguration>
307make_event_producing_server(mtf::CrossProcessSync const& client_ready_fence, int number_of_clients,
308 std::function<void(mtf::InputTestingServerConfiguration& server)> const& produce_events)
309{
310 return make_event_producing_server(client_ready_fence, number_of_clients,
311 produce_events, GeometryMap(), DepthMap());
312}
313
314std::shared_ptr<InputClient>
315make_event_expecting_client(std::string const& client_name, mtf::CrossProcessSync const& client_ready_fence,
316 std::function<void(MockInputHandler &, mt::WaitCondition&)> const& expect_input)
317{
318 struct EventReceivingClient : InputClient
319 {
320 std::function<void(MockInputHandler&, mt::WaitCondition&)> const expect_cb;
321
322 EventReceivingClient(mtf::CrossProcessSync const& client_ready_fence, std::string const& client_name,
323 std::function<void(MockInputHandler&, mt::WaitCondition&)> const& expect_cb)
324 : InputClient(client_ready_fence, client_name),
325 expect_cb(expect_cb)
326 {
327 }
328 void expect_input(mt::WaitCondition& events_received) override
329 {
330 expect_cb(*handler, events_received);
331 }
332 };
333 return std::make_shared<EventReceivingClient>(client_ready_fence, client_name, expect_input);
334}
335
336std::shared_ptr<InputClient>
337make_event_expecting_client(mtf::CrossProcessSync const& client_ready_fence,
338 std::function<void(MockInputHandler &, mt::WaitCondition&)> const& expect_input)
339{
340 return make_event_expecting_client("input-test-client", client_ready_fence, expect_input);
341}
342
343}
344
345
346using TestClientInput = BespokeDisplayServerTestFixture;
347
348TEST_F(TestClientInput, clients_receive_key_input)
349{
350 using namespace ::testing;
351
352 static std::string const test_client_name = "1";
353
354 mtf::CrossProcessSync fence;
355
356 auto server_config = make_event_producing_server(fence, 1,
357 [&](mtf::InputTestingServerConfiguration& server)
358 {
359 int const num_events_produced = 3;
360
361 for (int i = 0; i < num_events_produced; i++)
362 server.fake_event_hub->synthesize_event(mis::a_key_down_event()
363 .of_scancode(KEY_ENTER));
364 });
365 launch_server_process(*server_config);
366
367 auto client_config = make_event_expecting_client(fence,
368 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
369 {
370 using namespace ::testing;
371 InSequence seq;
372
373 EXPECT_CALL(handler, handle_input(mt::KeyDownEvent())).Times(2);
374 EXPECT_CALL(handler, handle_input(mt::KeyDownEvent())).Times(1)
375 .WillOnce(mt::WakeUp(&events_received));
376
377 });
378 launch_client_process(*client_config);
379}
380
381TEST_F(TestClientInput, clients_receive_us_english_mapped_keys)
382{
383 using namespace ::testing;
384 static std::string const test_client_name = "1";
385 mtf::CrossProcessSync fence;
386
387 auto server_config = make_event_producing_server(fence, 1,
388 [&](mtf::InputTestingServerConfiguration& server)
389 {
390 server.fake_event_hub->synthesize_event(mis::a_key_down_event()
391 .of_scancode(KEY_LEFTSHIFT));
392 server.fake_event_hub->synthesize_event(mis::a_key_down_event()
393 .of_scancode(KEY_4));
394 });
395 launch_server_process(*server_config);
396
397 auto client_config = make_event_expecting_client(fence,
398 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
399 {
400 using namespace ::testing;
401 InSequence seq;
402
403 EXPECT_CALL(handler, handle_input(AllOf(mt::KeyDownEvent(), mt::KeyOfSymbol(XKB_KEY_Shift_L)))).Times(1);
404 EXPECT_CALL(handler, handle_input(AllOf(mt::KeyDownEvent(), mt::KeyOfSymbol(XKB_KEY_dollar)))).Times(1)
405 .WillOnce(mt::WakeUp(&events_received));
406 });
407 launch_client_process(*client_config);
408}
409
410TEST_F(TestClientInput, clients_receive_motion_inside_window)
411{
412 using namespace ::testing;
413 static std::string const test_client_name = "1";
414 mtf::CrossProcessSync fence;
415
416 auto server_config = make_event_producing_server(fence, 1,
417 [&](mtf::InputTestingServerConfiguration& server)
418 {
419 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(InputClient::surface_width - 1,
420 InputClient::surface_height - 1));
421 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(2,2));
422 });
423 launch_server_process(*server_config);
424
425 auto client_config = make_event_expecting_client(fence,
426 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
427 {
428 using namespace ::testing;
429 InSequence seq;
430
431 // We should see the cursor enter
432 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(1);
433 EXPECT_CALL(handler, handle_input(
434 mt::MotionEventWithPosition(InputClient::surface_width - 1,
435 InputClient::surface_height - 1))).Times(1)
436 .WillOnce(mt::WakeUp(&events_received));
437 // But we should not receive an event for the second movement outside of our surface!
438 });
439 launch_client_process(*client_config);
440}
441
442TEST_F(TestClientInput, clients_receive_button_events_inside_window)
443{
444 using namespace ::testing;
445
446 static std::string const test_client_name = "1";
447 mtf::CrossProcessSync fence;
448
449 auto server_config = make_event_producing_server(fence, 1,
450 [&](mtf::InputTestingServerConfiguration& server)
451 {
452 server.fake_event_hub->synthesize_event(mis::a_button_down_event()
453 .of_button(BTN_LEFT).with_action(mis::EventAction::Down));
454 });
455 launch_server_process(*server_config);
456
457 auto client_config = make_event_expecting_client(fence,
458 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
459 {
460 using namespace ::testing;
461 InSequence seq;
462
463 // The cursor starts at (0, 0).
464 EXPECT_CALL(handler, handle_input(mt::ButtonDownEvent(0, 0))).Times(1)
465 .WillOnce(mt::WakeUp(&events_received));
466 });
467 launch_client_process(*client_config);
528}468}
529469
530TEST_F(TestClientInput, multiple_clients_receive_motion_inside_windows)470TEST_F(TestClientInput, multiple_clients_receive_motion_inside_windows)
@@ -539,77 +479,42 @@
539 static std::string const test_client_2 = "2";479 static std::string const test_client_2 = "2";
540 mtf::CrossProcessSync fence;480 mtf::CrossProcessSync fence;
541481
542 struct ServerConfiguration : mtf::InputTestingServerConfiguration482 static GeometryMap positions;
543 {483 positions[test_client_1] = geom::Rectangle{geom::Point{0, 0},
544 mtf::CrossProcessSync input_cb_setup_fence;484 geom::Size{client_width, client_height}};
545485 positions[test_client_2] = geom::Rectangle{geom::Point{screen_width/2, screen_height/2},
546 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence) 486 geom::Size{client_width, client_height}};
547 : input_cb_setup_fence(input_cb_setup_fence)487
548 {488 auto server_config = make_event_producing_server(fence, 2,
549 }489 [&](mtf::InputTestingServerConfiguration& server)
550490 {
551 std::shared_ptr<msh::PlacementStrategy> the_shell_placement_strategy() override
552 {
553 static GeometryMap positions;
554 positions[test_client_1] = geom::Rectangle{geom::Point{0, 0},
555 geom::Size{client_width, client_height}};
556 positions[test_client_2] = geom::Rectangle{geom::Point{screen_width/2, screen_height/2},
557 geom::Size{client_width, client_height}};
558
559 return std::make_shared<StaticPlacementStrategy>(positions);
560 }
561
562 void inject_input() override
563 {
564 wait_until_client_appears(test_client_1);
565 EXPECT_EQ(1, input_cb_setup_fence.wait_for_signal_ready_for());
566 wait_until_client_appears(test_client_2);
567 EXPECT_EQ(2, input_cb_setup_fence.wait_for_signal_ready_for());
568
569 // In the bounds of the first surface491 // In the bounds of the first surface
570 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2-1, screen_height/2-1));492 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2-1, screen_height/2-1));
571 // In the bounds of the second surface493 // In the bounds of the second surface
572 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2, screen_height/2));494 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(screen_width/2, screen_height/2));
573 }495 }, positions, DepthMap());
574 } server_config{fence};496 launch_server_process(*server_config);
575 497
576 launch_server_process(server_config);498 auto client_1 = make_event_expecting_client(test_client_1, fence,
577 499 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
578 struct InputClientOne : InputClient500 {
579 {501 InSequence seq;
580 InputClientOne(const mtf::CrossProcessSync& fence)502 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(1);
581 : InputClient(fence, test_client_1)503 EXPECT_CALL(handler, handle_input(mt::MotionEventWithPosition(client_width - 1, client_height - 1))).Times(1);
582 {504 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(1)
583 }505 .WillOnce(mt::WakeUp(&events_received));
584 506 });
585 void expect_input(mt::WaitCondition& events_received) override507 auto client_2 = make_event_expecting_client(test_client_2, fence,
586 {508 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
587 InSequence seq;509 {
588 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);510 InSequence seq;
589 EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(client_width - 1, client_height - 1))).Times(1);511 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(1);
590 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(1)512 EXPECT_CALL(handler, handle_input(mt::MotionEventWithPosition(client_width - 1, client_height - 1))).Times(1)
591 .WillOnce(mt::WakeUp(&events_received));513 .WillOnce(mt::WakeUp(&events_received));
592 }514 });
593 } client_1{fence};515
594516 launch_client_process(*client_1);
595 struct InputClientTwo : InputClient517 launch_client_process(*client_2);
596 {
597 InputClientTwo(const mtf::CrossProcessSync& fence)
598 : InputClient(fence, test_client_2)
599 {
600 }
601
602 void expect_input(mt::WaitCondition& events_received) override
603 {
604 InSequence seq;
605 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(1);
606 EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(client_width - 1, client_height - 1))).Times(1)
607 .WillOnce(mt::WakeUp(&events_received));
608 }
609 } client_2{fence};
610
611 launch_client_process(client_1);
612 launch_client_process(client_2);
613}518}
614519
615namespace520namespace
@@ -639,6 +544,7 @@
639 std::vector<geom::Rectangle> const input_rectangles;544 std::vector<geom::Rectangle> const input_rectangles;
640};545};
641}546}
547
642TEST_F(TestClientInput, clients_do_not_receive_motion_outside_input_region)548TEST_F(TestClientInput, clients_do_not_receive_motion_outside_input_region)
643{549{
644 using namespace ::testing;550 using namespace ::testing;
@@ -670,7 +576,7 @@
670 static GeometryMap positions;576 static GeometryMap positions;
671 positions[test_client_name] = screen_geometry;577 positions[test_client_name] = screen_geometry;
672 578
673 return std::make_shared<StaticPlacementStrategy>(positions);579 return std::make_shared<StaticPlacementStrategy>(InputTestingServerConfiguration::the_shell_placement_strategy(), positions);
674 }580 }
675 std::shared_ptr<msh::SurfaceFactory> the_shell_surface_factory() override581 std::shared_ptr<msh::SurfaceFactory> the_shell_surface_factory() override
676 {582 {
@@ -680,7 +586,6 @@
680 586
681 void inject_input() override587 void inject_input() override
682 {588 {
683 wait_until_client_appears(test_client_name);
684 input_cb_setup_fence.wait_for_signal_ready_for();589 input_cb_setup_fence.wait_for_signal_ready_for();
685 590
686 // First we will move the cursor in to the input region on the left side of the window. We should see a click here591 // First we will move the cursor in to the input region on the left side of the window. We should see a click here
@@ -697,35 +602,26 @@
697 fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));602 fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));
698 }603 }
699 } server_config{fence};604 } server_config{fence};
700
701 launch_server_process(server_config);605 launch_server_process(server_config);
702606
703 struct ClientConfig : InputClient607 auto client_config = make_event_expecting_client(test_client_name, fence,
704 {608 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
705 ClientConfig(const mtf::CrossProcessSync& fence)609 {
706 : InputClient(fence, test_client_name)610 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(AnyNumber());
707 {611 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(AnyNumber());
708 }612 EXPECT_CALL(handler, handle_input(mt::MovementEvent())).Times(AnyNumber());
709
710 void expect_input(mt::WaitCondition& events_received) override
711 {
712
713 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(AnyNumber());
714 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(AnyNumber());
715 EXPECT_CALL(*handler, handle_input(MovementEvent())).Times(AnyNumber());
716613
717 {614 {
718 // We should see two of the three button pairs.615 // We should see two of the three button pairs.
719 InSequence seq;616 InSequence seq;
720 EXPECT_CALL(*handler, handle_input(ButtonDownEvent(1, 1))).Times(1);617 EXPECT_CALL(handler, handle_input(mt::ButtonDownEvent(1, 1))).Times(1);
721 EXPECT_CALL(*handler, handle_input(ButtonUpEvent(1, 1))).Times(1);618 EXPECT_CALL(handler, handle_input(mt::ButtonUpEvent(1, 1))).Times(1);
722 EXPECT_CALL(*handler, handle_input(ButtonDownEvent(99, 99))).Times(1);619 EXPECT_CALL(handler, handle_input(mt::ButtonDownEvent(99, 99))).Times(1);
723 EXPECT_CALL(*handler, handle_input(ButtonUpEvent(99, 99))).Times(1)620 EXPECT_CALL(handler, handle_input(mt::ButtonUpEvent(99, 99))).Times(1)
724 .WillOnce(mt::WakeUp(&events_received));621 .WillOnce(mt::WakeUp(&events_received));
725 }622 }
726 }623 });
727 } client_config{fence};624 launch_client_process(*client_config);
728 launch_client_process(client_config);
729}625}
730626
731TEST_F(TestClientInput, surfaces_obscure_motion_events_by_stacking)627TEST_F(TestClientInput, surfaces_obscure_motion_events_by_stacking)
@@ -742,98 +638,62 @@
742 static geom::Rectangle const screen_geometry{geom::Point{0, 0},638 static geom::Rectangle const screen_geometry{geom::Point{0, 0},
743 geom::Size{screen_width, screen_height}};639 geom::Size{screen_width, screen_height}};
744640
745 struct ServerConfiguration : mtf::InputTestingServerConfiguration641 static GeometryMap positions;
746 {642 positions[test_client_name_1] = screen_geometry;
747 mtf::CrossProcessSync input_cb_setup_fence;643
748644 auto smaller_geometry = screen_geometry;
749 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence) 645 smaller_geometry.size.width = geom::Width{screen_width/2};
750 : input_cb_setup_fence(input_cb_setup_fence)646 positions[test_client_name_2] = smaller_geometry;
751 {647
752 }648 static DepthMap depths;
753649 depths[test_client_name_1] = ms::DepthId{0};
754 std::shared_ptr<msh::PlacementStrategy> the_shell_placement_strategy() override650 depths[test_client_name_2] = ms::DepthId{1};
755 {651
756 static GeometryMap positions;652 auto server_config = make_event_producing_server(fence, 2,
757 positions[test_client_name_1] = screen_geometry;653 [&](mtf::InputTestingServerConfiguration& server)
758 654 {
759 auto smaller_geometry = screen_geometry;
760 smaller_geometry.size.width = geom::Width{screen_width/2};
761 positions[test_client_name_2] = smaller_geometry;
762
763 static DepthMap depths;
764 depths[test_client_name_1] = ms::DepthId{0};
765 depths[test_client_name_2] = ms::DepthId{1};
766
767 return std::make_shared<StaticPlacementStrategy>(positions, depths);
768 }
769
770 void inject_input() override
771 {
772 wait_until_client_appears(test_client_name_1);
773 input_cb_setup_fence.wait_for_signal_ready_for();
774 wait_until_client_appears(test_client_name_2);
775 input_cb_setup_fence.wait_for_signal_ready_for();
776
777 // First we will move the cursor in to the region where client 2 obscures client 1655 // First we will move the cursor in to the region where client 2 obscures client 1
778 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 1));656 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1, 1));
779 fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));657 server.fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));
780 fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));658 server.fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));
781 // Now we move to the unobscured region of client 1659 // Now we move to the unobscured region of client 1
782 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(50, 0));660 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(50, 0));
783 fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));661 server.fake_event_hub->synthesize_event(mis::a_button_down_event().of_button(BTN_LEFT).with_action(mis::EventAction::Down));
784 fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));662 server.fake_event_hub->synthesize_event(mis::a_button_up_event().of_button(BTN_LEFT));
785 }663 }, positions, depths);
786 } server_config{fence};664 launch_server_process(*server_config);
787 665
788 launch_server_process(server_config);666 auto client_config_1 = make_event_expecting_client(test_client_name_1, fence,
789667 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
790 struct ClientConfigOne : InputClient668 {
791 {669 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(AnyNumber());
792 ClientConfigOne(const mtf::CrossProcessSync& fence)670 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(AnyNumber());
793 : InputClient(fence, test_client_name_1)671 EXPECT_CALL(handler, handle_input(mt::MovementEvent())).Times(AnyNumber());
794 {
795 }
796
797 void expect_input(mt::WaitCondition& events_received) override
798 {
799 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(AnyNumber());
800 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(AnyNumber());
801 EXPECT_CALL(*handler, handle_input(MovementEvent())).Times(AnyNumber());
802
803 {672 {
804 // We should only see one button event sequence.673 // We should only see one button event sequence.
805 InSequence seq;674 InSequence seq;
806 EXPECT_CALL(*handler, handle_input(ButtonDownEvent(51, 1))).Times(1);675 EXPECT_CALL(handler, handle_input(mt::ButtonDownEvent(51, 1))).Times(1);
807 EXPECT_CALL(*handler, handle_input(ButtonUpEvent(51, 1))).Times(1)676 EXPECT_CALL(handler, handle_input(mt::ButtonUpEvent(51, 1))).Times(1)
808 .WillOnce(mt::WakeUp(&events_received));677 .WillOnce(mt::WakeUp(&events_received));
809 }678 }
810 }679 });
811 } client_config_1{fence};680 auto client_config_2 = make_event_expecting_client(test_client_name_2, fence,
812 launch_client_process(client_config_1);681 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
813682 {
814 struct ClientConfigTwo : InputClient683 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(AnyNumber());
815 {684 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(AnyNumber());
816 ClientConfigTwo(const mtf::CrossProcessSync& fence)685 EXPECT_CALL(handler, handle_input(mt::MovementEvent())).Times(AnyNumber());
817 : InputClient(fence, test_client_name_2)
818 {
819 }
820
821 void expect_input(mt::WaitCondition& events_received) override
822 {
823 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(AnyNumber());
824 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(AnyNumber());
825 EXPECT_CALL(*handler, handle_input(MovementEvent())).Times(AnyNumber());
826
827 {686 {
828 // Likewise we should only see one button sequence.687 // Likewise we should only see one button sequence.
829 InSequence seq;688 InSequence seq;
830 EXPECT_CALL(*handler, handle_input(ButtonDownEvent(1, 1))).Times(1);689 EXPECT_CALL(handler, handle_input(mt::ButtonDownEvent(1, 1))).Times(1);
831 EXPECT_CALL(*handler, handle_input(ButtonUpEvent(1, 1))).Times(1)690 EXPECT_CALL(handler, handle_input(mt::ButtonUpEvent(1, 1))).Times(1)
832 .WillOnce(mt::WakeUp(&events_received));691 .WillOnce(mt::WakeUp(&events_received));
833 }692 }
834 }693 });
835 } client_config_2{fence};694
836 launch_client_process(client_config_2);695 launch_client_process(*client_config_1);
696 launch_client_process(*client_config_2);
837}697}
838698
839namespace699namespace
@@ -853,93 +713,48 @@
853 static std::string const test_client_name = "1";713 static std::string const test_client_name = "1";
854 static std::string const test_client_2_name = "2";714 static std::string const test_client_2_name = "2";
855 mtf::CrossProcessSync fence, first_client_ready_fence, second_client_done_fence;715 mtf::CrossProcessSync fence, first_client_ready_fence, second_client_done_fence;
856716
857 struct ServerConfiguration : public mtf::InputTestingServerConfiguration717 static DepthMap depths;
858 {718 depths[test_client_name] = ms::DepthId{0};
859 mtf::CrossProcessSync input_cb_setup_fence;719 depths[test_client_2_name] = ms::DepthId{1};
860 mtf::CrossProcessSync second_client_done_fence;720
861721 auto server_config = make_event_producing_server(fence, 2,
862 ServerConfiguration(const mtf::CrossProcessSync& input_cb_setup_fence,722 [&](mtf::InputTestingServerConfiguration& server)
863 const mtf::CrossProcessSync& second_client_done_fence) 723 {
864 : input_cb_setup_fence(input_cb_setup_fence),
865 second_client_done_fence(second_client_done_fence)
866 {
867 }
868
869 void hide_session_by_name(std::string const& session_name)
870 {
871 the_shell_session_container()->for_each([&](std::shared_ptr<msh::Session> const& session) -> void
872 {
873 if (session->name() == session_name)
874 session->hide();
875 });
876 }
877
878 void inject_input()
879 {
880 wait_until_client_appears(test_client_name);
881 wait_until_client_appears(test_client_2_name);
882 input_cb_setup_fence.wait_for_signal_ready_for();
883
884 // We send one event and then hide the surface on top before sending the next. 724 // We send one event and then hide the surface on top before sending the next.
885 // So we expect each of the two surfaces to receive one event pair.725 // So we expect each of the two surfaces to receive one even
886 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1,1));726 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1,1));
887
888 // We use a fence to ensure we do not hide the client727 // We use a fence to ensure we do not hide the client
889 // before event dispatch occurs728 // before event dispatch occurs
890 second_client_done_fence.wait_for_signal_ready_for();729 second_client_done_fence.wait_for_signal_ready_for();
891 hide_session_by_name(test_client_2_name);730
892731 server.the_shell_session_container()->for_each([&](std::shared_ptr<msh::Session> const& session) -> void
893 fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1,1));732 {
894 }733 if (session->name() == test_client_2_name)
895 } server_config{fence, second_client_done_fence};734 session->hide();
896 launch_server_process(server_config);735 });
897 736
898 struct ButtonClientOne : InputClient737 server.fake_event_hub->synthesize_event(mis::a_motion_event().with_movement(1,1));
899 {738 }, GeometryMap(), depths);
900 ButtonClientOne(const mtf::CrossProcessSync& fence)739 launch_server_process(*server_config);
901 : InputClient(fence, test_client_name)740
902 {741 auto client_config_1 = make_event_expecting_client(test_client_name, fence,
903 }742 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
904 743 {
905 void expect_input(mt::WaitCondition& events_received) override744 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(AnyNumber());
906 {745 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(AnyNumber());
907 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(AnyNumber());746 EXPECT_CALL(handler, handle_input(mt::MotionEventWithPosition(2, 2))).Times(1)
908 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(AnyNumber());
909 EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(2, 2))).Times(1)
910 .WillOnce(mt::WakeUp(&events_received));747 .WillOnce(mt::WakeUp(&events_received));
911 }748 });
912 } client_1{first_client_ready_fence};749 auto client_config_2 = make_event_expecting_client(test_client_2_name, fence,
913 struct ButtonClientTwo : InputClient750 [&](MockInputHandler& handler, mt::WaitCondition& events_received)
914 {751 {
915 mtf::CrossProcessSync first_client_ready;752 EXPECT_CALL(handler, handle_input(mt::HoverEnterEvent())).Times(AnyNumber());
916 mtf::CrossProcessSync done_fence;753 EXPECT_CALL(handler, handle_input(mt::HoverExitEvent())).Times(AnyNumber());
917754 EXPECT_CALL(handler, handle_input(mt::MotionEventWithPosition(1, 1))).Times(1)
918 ButtonClientTwo(mtf::CrossProcessSync const& fence, mtf::CrossProcessSync const& first_client_ready,755 .WillOnce(DoAll(SignalFence(&second_client_done_fence), mt::WakeUp(&events_received)));
919 mtf::CrossProcessSync const& done_fence) 756 });
920 : InputClient(fence, test_client_2_name),757
921 first_client_ready(first_client_ready),758 launch_client_process(*client_config_1);
922 done_fence(done_fence)759 launch_client_process(*client_config_2);
923 {
924 }
925 void exec()
926 {
927 // Ensure we stack on top of the first client
928 first_client_ready.wait_for_signal_ready_for();
929 InputClient::exec();
930 }
931
932 void expect_input(mt::WaitCondition& events_received) override
933 {
934 EXPECT_CALL(*handler, handle_input(HoverEnterEvent())).Times(AnyNumber());
935 EXPECT_CALL(*handler, handle_input(HoverExitEvent())).Times(AnyNumber());
936 EXPECT_CALL(*handler, handle_input(MotionEventWithPosition(1, 1))).Times(1)
937 .WillOnce(DoAll(SignalFence(&done_fence), mt::WakeUp(&events_received)));
938 }
939 } client_2{fence, first_client_ready_fence, second_client_done_fence};
940
941 // Client 2 is launched second so will be the first to receive input
942
943 launch_client_process(client_1);
944 launch_client_process(client_2);
945}760}
946761
=== modified file 'tests/mir_test_framework/input_testing_server_options.cpp'
--- tests/mir_test_framework/input_testing_server_options.cpp 2013-09-19 18:18:35 +0000
+++ tests/mir_test_framework/input_testing_server_options.cpp 2013-11-07 17:24:53 +0000
@@ -36,8 +36,6 @@
3636
37namespace mtf = mir_test_framework;37namespace mtf = mir_test_framework;
3838
39namespace ms = mir::surfaces;
40namespace msh = mir::shell;
41namespace mf = mir::frontend;39namespace mf = mir::frontend;
42namespace mg = mir::graphics;40namespace mg = mir::graphics;
43namespace mi = mir::input;41namespace mi = mir::input;
@@ -45,63 +43,6 @@
45namespace geom = mir::geometry;43namespace geom = mir::geometry;
46namespace mtd = mir::test::doubles;44namespace mtd = mir::test::doubles;
4745
48namespace
49{
50class SurfaceReadinessListener
51{
52public:
53 virtual ~SurfaceReadinessListener() = default;
54
55 virtual void channel_ready_for_input(std::string const& channel_name) = 0;
56
57protected:
58 SurfaceReadinessListener() = default;
59 SurfaceReadinessListener(SurfaceReadinessListener const&) = delete;
60 SurfaceReadinessListener& operator=(SurfaceReadinessListener const&) = delete;
61};
62
63class ProxyShell : public mf::Shell
64{
65public:
66 ProxyShell(std::shared_ptr<mf::Shell> const& underlying_shell,
67 std::shared_ptr<SurfaceReadinessListener> const listener)
68 : underlying_shell(underlying_shell),
69 listener(listener)
70 {
71 }
72
73 ~ProxyShell() noexcept(true) = default;
74
75 mf::SurfaceId create_surface_for(std::shared_ptr<mf::Session> const& session,
76 msh::SurfaceCreationParameters const& params)
77 {
78 return underlying_shell->create_surface_for(session, params);
79 }
80
81 std::shared_ptr<mf::Session> open_session(std::string const& name,
82 std::shared_ptr<mf::EventSink> const& sink)
83 {
84 return underlying_shell->open_session(name, sink);
85 }
86
87 void close_session(std::shared_ptr<mf::Session> const& session)
88 {
89 underlying_shell->close_session(session);
90 }
91
92 void handle_surface_created(std::shared_ptr<mf::Session> const& session)
93 {
94 underlying_shell->handle_surface_created(session);
95 listener->channel_ready_for_input(session->name());
96 }
97
98private:
99 std::shared_ptr<mf::Shell> const underlying_shell;
100 std::shared_ptr<SurfaceReadinessListener> const listener;
101};
102
103}
104
105mtf::InputTestingServerConfiguration::InputTestingServerConfiguration()46mtf::InputTestingServerConfiguration::InputTestingServerConfiguration()
106{47{
107}48}
@@ -136,56 +77,3 @@
136 77
137 return input_configuration;78 return input_configuration;
138}79}
139
140std::shared_ptr<mf::Shell> mtf::InputTestingServerConfiguration::the_frontend_shell()
141{
142 struct LifecycleTracker : public SurfaceReadinessListener
143 {
144 LifecycleTracker(std::mutex& lifecycle_lock,
145 std::condition_variable &lifecycle_condition,
146 std::map<std::string, mtf::ClientLifecycleState> &client_lifecycles)
147 : lifecycle_lock(lifecycle_lock),
148 lifecycle_condition(lifecycle_condition),
149 client_lifecycles(client_lifecycles)
150 {
151 }
152 void channel_ready_for_input(std::string const& channel_name)
153 {
154 std::unique_lock<std::mutex> lg(lifecycle_lock);
155 client_lifecycles[channel_name] = mtf::ClientLifecycleState::appeared;
156 lifecycle_condition.notify_all();
157 }
158
159 std::mutex &lifecycle_lock;
160 std::condition_variable &lifecycle_condition;
161 std::map<std::string, mtf::ClientLifecycleState> &client_lifecycles;
162 };
163
164 if (!frontend_shell)
165 {
166 auto readiness_listener = std::make_shared<LifecycleTracker>(lifecycle_lock,
167 lifecycle_condition,
168 client_lifecycles);
169 frontend_shell = std::make_shared<ProxyShell>(DefaultServerConfiguration::the_frontend_shell(), readiness_listener);
170 }
171
172 return frontend_shell;
173}
174
175void mtf::InputTestingServerConfiguration::wait_until_client_appears(std::string const& channel_name)
176{
177 std::unique_lock<std::mutex> lg(lifecycle_lock);
178
179 std::chrono::minutes timeout(2);
180 auto end_time = std::chrono::system_clock::now() + timeout;
181
182 if (client_lifecycles[channel_name] == vanished)
183 {
184 BOOST_THROW_EXCEPTION(std::runtime_error("Waiting for a client (" + channel_name + ") to appear but it has already vanished"));
185 }
186 while (client_lifecycles[channel_name] != appeared)
187 {
188 if (lifecycle_condition.wait_until(lg, end_time) == std::cv_status::timeout)
189 BOOST_THROW_EXCEPTION(std::runtime_error("Timed out waiting for client (" + channel_name + ") to appear"));
190 }
191}

Subscribers

People subscribed via source and target branches