Mir

Merge lp:~alan-griffiths/mir/move-miral-test-to-mir into lp:mir

Proposed by Alan Griffiths
Status: Merged
Approved by: Gerry Boland
Approved revision: no longer in the source branch.
Merged at revision: 4236
Proposed branch: lp:~alan-griffiths/mir/move-miral-test-to-mir
Merge into: lp:mir
Prerequisite: lp:~alan-griffiths/mir/move-miral-to-mir
Diff against target: 4933 lines (+4803/-1)
23 files modified
include/common/mir/input/mir_input_config.h (+3/-1)
tests/CMakeLists.txt (+2/-0)
tests/miral/CMakeLists.txt (+57/-0)
tests/miral/active_outputs.cpp (+205/-0)
tests/miral/active_window.cpp (+412/-0)
tests/miral/client_mediated_gestures.cpp (+302/-0)
tests/miral/display_reconfiguration.cpp (+87/-0)
tests/miral/drag_active_window.cpp (+153/-0)
tests/miral/drag_and_drop.cpp (+656/-0)
tests/miral/modify_window_state.cpp (+105/-0)
tests/miral/mru_window_list.cpp (+193/-0)
tests/miral/raise_tree.cpp (+85/-0)
tests/miral/runner.cpp (+49/-0)
tests/miral/select_active_window.cpp (+121/-0)
tests/miral/test_server.cpp (+198/-0)
tests/miral/test_server.h (+90/-0)
tests/miral/test_window_manager_tools.h (+197/-0)
tests/miral/window_id.cpp (+114/-0)
tests/miral/window_placement.cpp (+554/-0)
tests/miral/window_placement_anchors_to_parent.cpp (+208/-0)
tests/miral/window_placement_client_api.cpp (+141/-0)
tests/miral/window_properties.cpp (+164/-0)
tests/miral/workspaces.cpp (+707/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/move-miral-test-to-mir
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Brandon Schaefer (community) Approve
Mir CI Bot continuous-integration Approve
Review via email: mp+329464@code.launchpad.net

Commit message

Incorporate miral project into mir source tree - part 2 (miral-test)

Description of the change

Incorporate miral project into mir source tree

This is a second-cut:

1. The utility script for generating the libmiral symbols file hasn't been ported
2. There's no attempt to remove code obsoleted by MirAL
3. There's no reworking of the generated docs to include miral

To post a comment you must log in.
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4249
https://mir-jenkins.ubuntu.com/job/mir-ci/3576/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4899/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5121
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5110
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5110
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5110
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4938/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4938/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4938
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4938/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4938
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4938/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4938/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3576/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

18:05:38 15/33 Test #15: miral-test ........................................***Failed 0.00 sec
18:05:38 /<<BUILDDIR>>/mir-1.0.0+zesty5110bzr4249/obj-x86_64-linux-gnu/bin/miral-test.bin: error while loading shared libraries: libmiral.so.2: cannot open shared object file: No such file or directory

Well... at least that proves the test is there!

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

>
> 18:05:38 15/33 Test #15: miral-test
> ........................................***Failed 0.00 sec
> 18:05:38 /<<BUILDDIR>>/mir-1.0.0+zesty5110bzr4249/obj-x86_64-linux-gnu/bin
> /miral-test.bin: error while loading shared libraries: libmiral.so.2: cannot
> open shared object file: No such file or directory
>
> Well... at least that proves the test is there!

Doh! "miral-test.bin" not "miral-test"

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4250
https://mir-jenkins.ubuntu.com/job/mir-ci/3577/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4900/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5122
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5111
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5111
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5111
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4939/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4939/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4939
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4939/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4939
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4939/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4939/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3577/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:4252
https://mir-jenkins.ubuntu.com/job/mir-ci/3578/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/4901/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5123
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5112
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5112
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5112
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4940/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4940/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4940/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4940
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4940/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3578/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:4254
https://mir-jenkins.ubuntu.com/job/mir-ci/3580/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/4903
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/5125
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=artful/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=zesty/5114
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=artful/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=mesa,release=zesty/4942/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4942
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial/4942/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/3580/rebuild

review: Approve (continuous-integration)
Revision history for this message
Brandon Schaefer (brandontschaefer) wrote :

Sounds reasonable, the other branch is ready to land. LGTM

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote :

Builds ok, tests ok.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'include/common/mir/input/mir_input_config.h'
--- include/common/mir/input/mir_input_config.h 2017-07-28 17:00:43 +0000
+++ include/common/mir/input/mir_input_config.h 2017-08-24 15:19:58 +0000
@@ -75,7 +75,9 @@
75 std::unique_ptr<Implementation> impl;75 std::unique_ptr<Implementation> impl;
76};76};
7777
78class MirInputConfig78// We use "struct", not "class" for consistency with mirclient/mir_toolkit/client_types.h:395
79// (To be nice to downstreams that use clang with its pointless warnings about this.)
80struct MirInputConfig
79{81{
80public:82public:
81 MirInputConfig();83 MirInputConfig();
8284
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2017-05-08 03:04:26 +0000
+++ tests/CMakeLists.txt 2017-08-24 15:19:58 +0000
@@ -70,6 +70,8 @@
70 add_subdirectory(privileged-tests/)70 add_subdirectory(privileged-tests/)
71endif(MIR_BUILD_PRIVILEGED_TESTS)71endif(MIR_BUILD_PRIVILEGED_TESTS)
7272
73add_subdirectory(miral)
74
73# Private test headers used by integration and unit tests75# Private test headers used by integration and unit tests
74include_directories(76include_directories(
75 include77 include
7678
=== added directory 'tests/miral'
=== added file 'tests/miral/CMakeLists.txt'
--- tests/miral/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/miral/CMakeLists.txt 2017-08-24 15:19:58 +0000
@@ -0,0 +1,57 @@
1# We can't tell which version of gtest we're building against and INSTANTIATE_TEST_CASE_P changed in
2# a way that relies on a gcc extension to support backward-compatible code, So...
3check_cxx_compiler_flag(-Wno-gnu-zero-variadic-macro-arguments MIRAL_COMPILE_WITH_W_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS)
4check_cxx_compiler_flag(-Wno-pedantic MIRAL_COMPILE_WITH_W_NO_PEDANTIC)
5if ("${CMAKE_CXX_COMPILER}" MATCHES ".*clang.*" AND MIRAL_COMPILE_WITH_W_NO_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS)
6 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments") # clang
7elseif(MIRAL_COMPILE_WITH_W_NO_PEDANTIC)
8 set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pedantic") #gcc
9endif()
10
11include_directories(
12 ${PROJECT_SOURCE_DIR}/src/miral
13 ${MIRTEST_INCLUDE_DIRS}
14 ${GMOCK_INCLUDE_DIR}
15 ${GTEST_INCLUDE_DIR}
16)
17
18if(${CMAKE_COMPILER_IS_GNUCXX})
19 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto")
20 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto")
21 set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -flto")
22 set(CMAKE_AR "gcc-ar")
23 set(CMAKE_NM "gcc-nm")
24 set(CMAKE_RANLIB "gcc-ranlib")
25endif()
26
27mir_add_wrapped_executable(miral-test NOINSTALL
28 mru_window_list.cpp
29 active_outputs.cpp
30 window_id.cpp
31 runner.cpp
32 select_active_window.cpp
33 window_placement.cpp
34 window_placement_anchors_to_parent.cpp
35 window_placement_client_api.cpp
36 window_properties.cpp
37 drag_active_window.cpp
38 modify_window_state.cpp
39 test_server.cpp test_server.h
40 test_window_manager_tools.h
41 display_reconfiguration.cpp
42 active_window.cpp
43 raise_tree.cpp
44 workspaces.cpp
45 drag_and_drop.cpp
46 client_mediated_gestures.cpp
47)
48
49target_link_libraries(miral-test
50 ${GTEST_BOTH_LIBRARIES}
51 ${GMOCK_LIBRARIES}
52 miral
53 miral-internal
54 mir-test-assist
55)
56
57add_test(miral-test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/miral-test)
058
=== added file 'tests/miral/active_outputs.cpp'
--- tests/miral/active_outputs.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/active_outputs.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,205 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "miral/active_outputs.h"
20#include "miral/output.h"
21
22#include <mir/shell/display_configuration_controller.h>
23
24#include <mir_test_framework/headless_test.h>
25
26#include <mir/test/doubles/fake_display.h>
27#include <mir/test/doubles/stub_display_configuration.h>
28#include <mir/test/fake_shared.h>
29#include <mir/test/signal.h>
30
31#include <gtest/gtest.h>
32#include <gmock/gmock.h>
33
34namespace mg = mir::graphics;
35namespace mt = mir::test;
36namespace mtd = mir::test::doubles;
37namespace mtf = mir_test_framework;
38
39using namespace miral;
40using namespace testing;
41
42namespace
43{
44struct MockActiveOutputsListener : ActiveOutputsListener
45{
46 MOCK_METHOD0(advise_output_begin, void());
47 MOCK_METHOD0(advise_output_end, void());
48
49 MOCK_METHOD1(advise_output_create, void(Output const&));
50 MOCK_METHOD2(advise_output_update, void(Output const&, Output const&));
51 MOCK_METHOD1(advise_output_delete, void(Output const&));
52};
53
54std::vector<Rectangle> const output_rects{
55 {{0,0}, {640,480}},
56 {{640,0}, {640,480}}
57};
58
59struct ActiveOutputs : mtf::HeadlessTest
60{
61 ActiveOutputs()
62 {
63 add_to_environment("MIR_SERVER_NO_FILE", "");
64 }
65
66 void SetUp() override
67 {
68 mtf::HeadlessTest::SetUp();
69 preset_display(mt::fake_shared(display));
70 active_outputs_monitor(server);
71 active_outputs_monitor.add_listener(&active_outputs_listener);
72 }
73
74 void TearDown() override
75 {
76 active_outputs_monitor.delete_listener(&active_outputs_listener);
77 mtf::HeadlessTest::TearDown();
78 }
79
80 mtd::FakeDisplay display{output_rects};
81 ActiveOutputsMonitor active_outputs_monitor;
82 NiceMock<MockActiveOutputsListener> active_outputs_listener;
83
84 void update_outputs(std::vector<Rectangle> const& displays)
85 {
86 mt::Signal signal;
87 EXPECT_CALL(active_outputs_listener, advise_output_end()).WillOnce(Invoke([&]{signal.raise(); }));
88
89 mtd::StubDisplayConfig changed_stub_display_config{displays};
90 display.emit_configuration_change_event(mt::fake_shared(changed_stub_display_config));
91
92 signal.wait_for(std::chrono::seconds(10));
93 ASSERT_TRUE(signal.raised());
94 }
95
96 void invert_outputs_in_base_configuration()
97 {
98 mt::Signal signal;
99 EXPECT_CALL(active_outputs_listener, advise_output_end()).WillOnce(Invoke([&]{signal.raise(); }));
100
101 auto configuration = server.the_display()->configuration();
102 configuration->for_each_output([](mg::UserDisplayConfigurationOutput& output)
103 {
104 output.orientation = mir_orientation_inverted;
105 });
106
107 server.the_display_configuration_controller()->set_base_configuration(std::move(configuration));
108
109 signal.wait_for(std::chrono::seconds(10));
110 ASSERT_TRUE(signal.raised());
111 }
112};
113
114struct RunServer
115{
116 RunServer(mtf::HeadlessTest* self) : self{self} { self->start_server(); }
117 ~RunServer() { self->stop_server(); }
118
119 mtf::HeadlessTest* const self;
120};
121}
122
123TEST_F(ActiveOutputs, on_startup_listener_is_advised)
124{
125 InSequence seq;
126 EXPECT_CALL(active_outputs_listener, advise_output_begin());
127 EXPECT_CALL(active_outputs_listener, advise_output_create(_)).Times(2);
128 RunServer runner{this};
129
130 Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
131}
132
133TEST_F(ActiveOutputs, when_output_unplugged_listener_is_advised)
134{
135 RunServer runner{this};
136
137 InSequence seq;
138 EXPECT_CALL(active_outputs_listener, advise_output_begin());
139 EXPECT_CALL(active_outputs_listener, advise_output_delete(_)).Times(1);
140 update_outputs({{{0,0}, {640,480}}});
141
142 Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
143}
144
145TEST_F(ActiveOutputs, when_output_added_listener_is_advised)
146{
147 RunServer runner{this};
148
149 auto new_output_rects = output_rects;
150 new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
151
152 InSequence seq;
153 EXPECT_CALL(active_outputs_listener, advise_output_begin());
154 EXPECT_CALL(active_outputs_listener, advise_output_create(_)).Times(1);
155 update_outputs(new_output_rects);
156
157 Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
158}
159
160TEST_F(ActiveOutputs, when_output_resized_listener_is_advised)
161{
162 RunServer runner{this};
163
164 auto new_output_rects = output_rects;
165 new_output_rects[1] = {Point{640,0}, Size{1080,768}};
166
167 InSequence seq;
168 EXPECT_CALL(active_outputs_listener, advise_output_begin());
169 EXPECT_CALL(active_outputs_listener, advise_output_update(_, _)).Times(1);
170 update_outputs(new_output_rects);
171
172 Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
173}
174
175TEST_F(ActiveOutputs, when_base_configuration_is_updated_listener_is_advised)
176{
177 RunServer runner{this};
178
179 InSequence seq;
180 EXPECT_CALL(active_outputs_listener, advise_output_begin());
181 EXPECT_CALL(active_outputs_listener, advise_output_update(_, _)).Times(2);
182 invert_outputs_in_base_configuration();
183
184 Mock::VerifyAndClearExpectations(&active_outputs_listener); // before shutdown
185}
186
187TEST_F(ActiveOutputs, available_to_process)
188{
189 RunServer runner{this};
190
191 active_outputs_monitor.process_outputs([](std::vector<Output> const& outputs)
192 { EXPECT_THAT(outputs.size(), Eq(output_rects.size())); });
193}
194
195TEST_F(ActiveOutputs, updates_are_available_to_process)
196{
197 RunServer runner{this};
198
199 auto new_output_rects = output_rects;
200 new_output_rects.emplace_back(Point{1280,0}, Size{640,480});
201 update_outputs(new_output_rects);
202
203 active_outputs_monitor.process_outputs([&](std::vector<Output> const& outputs)
204 { EXPECT_THAT(outputs.size(), Eq(new_output_rects.size())); });
205}
0206
=== added file 'tests/miral/active_window.cpp'
--- tests/miral/active_window.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/active_window.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,412 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_server.h"
20
21#include <mir/client/surface.h>
22#include <mir/client/window.h>
23#include <mir/client/window_spec.h>
24#include <mir_toolkit/mir_buffer_stream.h>
25
26#include <miral/application_info.h>
27
28#include <mir/test/signal.h>
29
30#include <gtest/gtest.h>
31#include <gmock/gmock.h>
32
33using namespace testing;
34using namespace mir::client;
35using namespace std::chrono_literals;
36using miral::WindowManagerTools;
37
38
39namespace
40{
41class FocusChangeSync
42{
43public:
44 void exec(std::function<void()> const& f)
45 {
46 signal.reset();
47 f();
48 signal.wait_for(100ms);
49 }
50
51 static void raise_signal_on_focus_change(MirWindow* /*surface*/, MirEvent const* event, void* context)
52 {
53 if (mir_event_get_type(event) == mir_event_type_window &&
54 mir_window_event_get_attribute(mir_event_get_window_event(event)) == mir_window_attrib_focus)
55 {
56 ((FocusChangeSync*)context)->signal.raise();
57 }
58 }
59
60 auto signal_raised() -> bool { return signal.raised(); }
61
62private:
63 mir::test::Signal signal;
64};
65
66struct TestWindow : Surface, Window
67{
68 using Surface::operator=;
69 using Window::operator=;
70};
71
72struct ActiveWindow : public miral::TestServer
73{
74 FocusChangeSync sync1;
75 FocusChangeSync sync2;
76
77 void paint(Surface const& surface)
78 {
79 mir_buffer_stream_swap_buffers_sync(
80 mir_render_surface_get_buffer_stream(surface, 50, 50, mir_pixel_format_argb_8888));
81 }
82
83 auto create_window(Connection const& connection, char const* name, FocusChangeSync& sync) -> TestWindow
84 {
85 TestWindow result;
86
87 result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
88
89 auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
90 .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
91 .add_surface(result, 50, 50, 0, 0)
92 .set_name(name);
93
94 result = Window{spec.create_window()};
95
96 sync.exec([&]{ paint(result); });
97
98 EXPECT_TRUE(sync.signal_raised());
99
100 return result;
101 }
102
103 auto create_tip(Connection const& connection, char const* name, Window const& parent, FocusChangeSync& sync) -> TestWindow
104 {
105 TestWindow result;
106 result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
107
108 MirRectangle aux_rect{10, 10, 10, 10};
109 auto const spec = WindowSpec::for_tip(connection, 50, 50, parent, &aux_rect, mir_edge_attachment_any)
110 .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
111 .add_surface(result, 50, 50, 0, 0)
112 .set_name(name);
113
114 result = Window{spec.create_window()};
115
116 // Expect this to timeout: A tip should not receive focus
117 sync.exec([&]{ paint(result); });
118 EXPECT_FALSE(sync.signal_raised());
119
120 return result;
121 }
122
123 auto create_dialog(Connection const& connection, char const* name, Window const& parent, FocusChangeSync& sync) -> TestWindow
124 {
125 TestWindow result;
126 result = Surface{mir_connection_create_render_surface_sync(connection, 50, 50)};
127
128 auto const spec = WindowSpec::for_dialog(connection, 50, 50, parent)
129 .set_event_handler(&FocusChangeSync::raise_signal_on_focus_change, &sync)
130 .add_surface(result, 50, 50, 0, 0)
131 .set_name(name);
132
133 result = Window{spec.create_window()};
134
135 sync.exec([&]{ paint(result); });
136 EXPECT_TRUE(sync.signal_raised());
137
138 return result;
139 }
140
141 void assert_no_active_window()
142 {
143 invoke_tools([&](WindowManagerTools& tools)
144 {
145 auto const window = tools.active_window();
146 ASSERT_FALSE(window);
147 });
148 }
149
150 void assert_active_window_is(char const* const name)
151 {
152 invoke_tools([&](WindowManagerTools& tools)
153 {
154 auto const window = tools.active_window();
155 ASSERT_TRUE(window);
156 auto const& window_info = tools.info_for(window);
157 ASSERT_THAT(window_info.name(), Eq(name));
158 });
159 }
160};
161
162auto const another_name = "second";
163}
164
165TEST_F(ActiveWindow, a_single_window_when_ready_becomes_active)
166{
167 char const* const test_name = __PRETTY_FUNCTION__;
168 auto const connection = connect_client(test_name);
169
170 auto const window = create_window(connection, test_name, sync1);
171
172 assert_active_window_is(test_name);
173}
174
175TEST_F(ActiveWindow, a_single_window_when_hiding_becomes_inactive)
176{
177 char const* const test_name = __PRETTY_FUNCTION__;
178 auto const connection = connect_client(test_name);
179 auto const window = create_window(connection, test_name, sync1);
180
181 sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
182
183 EXPECT_TRUE(sync1.signal_raised());
184 assert_no_active_window();
185}
186
187TEST_F(ActiveWindow, a_single_window_when_unhiding_becomes_active)
188{
189 char const* const test_name = __PRETTY_FUNCTION__;
190 auto const connection = connect_client(test_name);
191 auto const window = create_window(connection, test_name, sync1);
192
193 sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
194
195 sync1.exec([&]{ mir_window_set_state(window, mir_window_state_restored); });
196
197 EXPECT_TRUE(sync1.signal_raised());
198
199 assert_active_window_is(test_name);
200}
201
202TEST_F(ActiveWindow, a_second_window_when_ready_becomes_active)
203{
204 char const* const test_name = __PRETTY_FUNCTION__;
205 auto const connection = connect_client(test_name);
206
207 auto const first_window = create_window(connection, "first", sync1);
208 auto const window = create_window(connection, test_name, sync2);
209
210 assert_active_window_is(test_name);
211}
212
213TEST_F(ActiveWindow, a_second_window_hiding_makes_first_active)
214{
215 char const* const test_name = __PRETTY_FUNCTION__;
216 auto const connection = connect_client(test_name);
217
218 auto const first_window = create_window(connection, test_name, sync1);
219 auto const window = create_window(connection, another_name, sync2);
220
221 sync2.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
222
223 EXPECT_TRUE(sync2.signal_raised());
224 assert_active_window_is(test_name);
225}
226
227TEST_F(ActiveWindow, a_second_window_unhiding_leaves_first_active)
228{
229 char const* const test_name = __PRETTY_FUNCTION__;
230 auto const connection = connect_client(test_name);
231
232 auto const first_window = create_window(connection, test_name, sync1);
233 auto const window = create_window(connection, another_name, sync2);
234
235 sync1.exec([&]{ mir_window_set_state(window, mir_window_state_hidden); });
236
237 // Expect this to timeout
238 sync2.exec([&]{ mir_window_set_state(window, mir_window_state_restored); });
239
240 EXPECT_THAT(sync2.signal_raised(), Eq(false));
241 assert_active_window_is(test_name);
242}
243
244TEST_F(ActiveWindow, switching_from_a_second_window_makes_first_active)
245{
246 char const* const test_name = __PRETTY_FUNCTION__;
247 auto const connection = connect_client(test_name);
248
249 auto const first_window = create_window(connection, test_name, sync1);
250 auto const window = create_window(connection, another_name, sync2);
251
252 sync1.exec([&]{ invoke_tools([](WindowManagerTools& tools){ tools.focus_next_within_application(); }); });
253
254 EXPECT_TRUE(sync1.signal_raised());
255 assert_active_window_is(test_name);
256}
257
258TEST_F(ActiveWindow, switching_from_a_second_application_makes_first_active)
259{
260 char const* const test_name = __PRETTY_FUNCTION__;
261 auto const connection = connect_client(test_name);
262 auto const second_connection = connect_client(another_name);
263
264 auto const first_window = create_window(connection, test_name, sync1);
265 auto const window = create_window(second_connection, another_name, sync2);
266
267 sync1.exec([&]{ invoke_tools([](WindowManagerTools& tools){ tools.focus_next_application(); }); });
268
269 EXPECT_TRUE(sync1.signal_raised());
270 assert_active_window_is(test_name);
271}
272
273TEST_F(ActiveWindow, closing_a_second_application_makes_first_active)
274{
275 char const* const test_name = __PRETTY_FUNCTION__;
276 auto const connection = connect_client(test_name);
277
278 auto const first_window = create_window(connection, test_name, sync1);
279
280 sync1.exec([&]
281 {
282 auto const second_connection = connect_client(another_name);
283 auto const window = create_window(second_connection, another_name, sync2);
284 assert_active_window_is(another_name);
285 });
286
287 EXPECT_TRUE(sync1.signal_raised());
288 assert_active_window_is(test_name);
289}
290
291TEST_F(ActiveWindow, selecting_a_tip_makes_parent_active)
292{
293 char const* const test_name = __PRETTY_FUNCTION__;
294 auto const connection = connect_client(test_name);
295
296 auto const parent = create_window(connection, test_name, sync1);
297
298 miral::Window parent_window;
299 invoke_tools([&](WindowManagerTools& tools){ parent_window = tools.active_window(); });
300
301 // Steal the focus
302 auto second_connection = connect_client(another_name);
303 auto second_surface = create_window(second_connection, another_name, sync2);
304
305 auto const tip = create_tip(connection, "tip", parent, sync2);
306
307 sync1.exec([&]
308 {
309 invoke_tools([&](WindowManagerTools& tools)
310 { tools.select_active_window(*tools.info_for(parent_window).children().begin()); });
311 });
312 EXPECT_TRUE(sync1.signal_raised());
313
314 assert_active_window_is(test_name);
315}
316
317TEST_F(ActiveWindow, selecting_a_parent_makes_dialog_active)
318{
319 char const* const test_name = __PRETTY_FUNCTION__;
320 auto const dialog_name = "dialog";
321 auto const connection = connect_client(test_name);
322
323 auto const parent = create_window(connection, test_name, sync1);
324
325 miral::Window parent_window;
326 invoke_tools([&](WindowManagerTools& tools){ parent_window = tools.active_window(); });
327
328 auto const dialog = create_dialog(connection, dialog_name, parent, sync2);
329
330 // Steal the focus
331 auto second_connection = connect_client(another_name);
332 auto second_surface = create_window(second_connection, another_name, sync1);
333
334 sync2.exec([&]{ invoke_tools([&](WindowManagerTools& tools){ tools.select_active_window(parent_window); }); });
335
336 EXPECT_TRUE(sync2.signal_raised());
337 assert_active_window_is(dialog_name);
338}
339
340TEST_F(ActiveWindow, input_methods_are_not_focussed)
341{
342 char const* const test_name = __PRETTY_FUNCTION__;
343 auto const connection = connect_client(test_name);
344
345 auto const parent = create_window(connection, test_name, sync1);
346 auto const input_method = WindowSpec::for_input_method(connection, 50, 50, parent).create_window();
347
348 assert_active_window_is(test_name);
349
350 invoke_tools([&](WindowManagerTools& tools)
351 {
352 auto const& info = tools.info_for(tools.active_window());
353 tools.select_active_window(info.children().at(0));
354 });
355
356 assert_active_window_is(test_name);
357}
358
359TEST_F(ActiveWindow, satellites_are_not_focussed)
360{
361 char const* const test_name = __PRETTY_FUNCTION__;
362 auto const connection = connect_client(test_name);
363
364 auto const parent = create_window(connection, test_name, sync1);
365 auto const satellite = WindowSpec::for_satellite(connection, 50, 50, parent).create_window();
366
367 assert_active_window_is(test_name);
368
369 invoke_tools([&](WindowManagerTools& tools)
370 {
371 auto const& info = tools.info_for(tools.active_window());
372 tools.select_active_window(info.children().at(0));
373 });
374
375 assert_active_window_is(test_name);
376}
377
378// lp:1671072
379TEST_F(ActiveWindow, hiding_active_dialog_makes_parent_active)
380{
381 char const* const parent_name = __PRETTY_FUNCTION__;
382 auto const dialog_name = "dialog";
383 auto const connection = connect_client(parent_name);
384
385 auto const parent = create_window(connection, parent_name, sync1);
386 auto const dialog = create_dialog(connection, dialog_name, parent, sync2);
387
388 sync1.exec([&]{ mir_window_set_state(dialog, mir_window_state_hidden); });
389
390 EXPECT_TRUE(sync1.signal_raised());
391
392 assert_active_window_is(parent_name);
393}
394
395TEST_F(ActiveWindow, when_another_window_is_about_hiding_active_dialog_makes_parent_active)
396{
397 FocusChangeSync sync3;
398 char const* const parent_name = __PRETTY_FUNCTION__;
399 auto const dialog_name = "dialog";
400 auto const another_window_name = "another window";
401 auto const connection = connect_client(parent_name);
402
403 auto const parent = create_window(connection, parent_name, sync1);
404 auto const another_window = create_window(connection, another_window_name, sync2);
405 auto const dialog = create_dialog(connection, dialog_name, parent, sync3);
406
407 sync1.exec([&]{ mir_window_set_state(dialog, mir_window_state_hidden); });
408
409 EXPECT_TRUE(sync1.signal_raised());
410
411 assert_active_window_is(parent_name);
412}
0413
=== added file 'tests/miral/client_mediated_gestures.cpp'
--- tests/miral/client_mediated_gestures.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/client_mediated_gestures.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,302 @@
1/*
2 * Copyright © 2017 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <mir/client/surface.h>
20#include <mir/client/window_spec.h>
21
22#include <mir_toolkit/mir_window.h>
23#include <mir_toolkit/mir_blob.h>
24
25#include <mir/geometry/displacement.h>
26#include <mir/input/input_device_info.h>
27#include <mir/input/device_capability.h>
28#include <mir/shell/canonical_window_manager.h>
29#include <mir/shell/shell.h>
30
31#include <mir_test_framework/connected_client_with_a_window.h>
32#include <mir_test_framework/fake_input_device.h>
33#include <mir_test_framework/stub_server_platform_factory.h>
34#include <mir/test/event_factory.h>
35#include <mir/test/fake_shared.h>
36#include <mir/test/signal.h>
37
38#include <gmock/gmock.h>
39#include <gtest/gtest.h>
40
41#include <linux/input.h>
42
43#include <atomic>
44
45using namespace std::chrono_literals;
46using namespace mir::geometry;
47using namespace testing;
48using mir::test::fake_shared;
49using mir::test::Signal;
50
51namespace
52{
53class Cookie
54{
55public:
56 Cookie() = default;
57
58 explicit Cookie(MirCookie const* cookie) : self{cookie, deleter} {}
59
60 operator MirCookie const*() const { return self.get(); }
61
62 auto get() const -> MirCookie const* { return self.get(); }
63
64 void reset() { self.reset(); }
65
66 void reset(MirCookie const* cookie) { self.reset(cookie, deleter); }
67
68private:
69 static void deleter(MirCookie const* cookie) { mir_cookie_release(cookie); }
70
71 std::shared_ptr<MirCookie const> self;
72};
73
74void mir_cookie_release(Cookie const&) = delete;
75
76struct MockWindowManager : mir::shell::CanonicalWindowManager
77{
78#if defined(__clang__)
79 #pragma GCC diagnostic push
80 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
81#endif
82 using mir::shell::CanonicalWindowManager::CanonicalWindowManager;
83#if defined(__clang__)
84 #pragma GCC diagnostic pop
85#endif
86
87 MOCK_METHOD3(handle_request_move,
88 void(std::shared_ptr<mir::scene::Session> const&, std::shared_ptr<mir::scene::Surface> const&, uint64_t));
89};
90
91struct MouseMoverAndFaker
92{
93 void start_dragging_mouse()
94 {
95 using namespace mir::input::synthesis;
96 fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT));
97 }
98
99 void move_mouse(Displacement const& displacement)
100 {
101 using mir::input::synthesis::a_pointer_event;
102 fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int()));
103 }
104
105 void release_mouse()
106 {
107 using namespace mir::input::synthesis;
108 fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT));
109 }
110
111private:
112 std::unique_ptr<mir_test_framework::FakeInputDevice> fake_mouse{
113 mir_test_framework::add_fake_input_device(
114 mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})
115 };
116};
117
118Rectangle const screen_geometry{{0, 0}, {800, 600}};
119auto const receive_event_timeout = 90s;
120
121struct ClientMediatedUserGestures : mir_test_framework::ConnectedClientWithAWindow,
122 MouseMoverAndFaker
123{
124 void SetUp() override
125 {
126 initial_display_layout({screen_geometry});
127 server.override_the_window_manager_builder([this](mir::shell::FocusController* focus_controller)
128 {
129 return window_manager =
130 std::make_shared<MockWindowManager>(focus_controller, server.the_shell_display_layout());
131 });
132
133 mir_test_framework::ConnectedClientWithAWindow::SetUp();
134 mir_window_set_event_handler(window, &window_event_handler, this);
135
136 paint_window();
137
138 center_mouse();
139 }
140
141 void TearDown() override
142 {
143 reset_window_event_handler();
144 window_manager.reset();
145 surface.reset();
146 mir_test_framework::ConnectedClientWithAWindow::TearDown();
147 }
148
149 auto user_initiates_gesture() -> Cookie;
150
151 std::shared_ptr<MockWindowManager> window_manager;
152
153private:
154 void center_mouse();
155 void paint_window();
156 void set_window_event_handler(std::function<void(MirEvent const* event)> const& handler);
157 void reset_window_event_handler();
158 void invoke_window_event_handler(MirEvent const* event)
159 {
160 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
161 window_event_handler_(event);
162 }
163
164 mir::client::Surface surface;
165
166 std::mutex window_event_handler_mutex;
167 std::function<void(MirEvent const* event)> window_event_handler_ = [](MirEvent const*) {};
168
169 static void window_event_handler(MirWindow* window, MirEvent const* event, void* context);
170};
171
172void ClientMediatedUserGestures::set_window_event_handler(std::function<void(MirEvent const* event)> const& handler)
173{
174 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
175 window_event_handler_ = handler;
176}
177
178void ClientMediatedUserGestures::reset_window_event_handler()
179{
180 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
181 window_event_handler_ = [](MirEvent const*) {};
182}
183
184void ClientMediatedUserGestures::window_event_handler(MirWindow* /*window*/, MirEvent const* event, void* context)
185{
186 static_cast<ClientMediatedUserGestures*>(context)->invoke_window_event_handler(event);
187}
188
189void ClientMediatedUserGestures::paint_window()
190{
191 {
192 surface = mir::client::Surface{mir_connection_create_render_surface_sync(connection, 42, 42)};
193 auto const spec = mir::client::WindowSpec::for_changes(connection);
194 mir_window_spec_add_render_surface(spec, surface, 42, 42, 0, 0);
195 mir_window_apply_spec(window, spec);
196 }
197
198 Signal have_focus;
199
200 set_window_event_handler([&](MirEvent const* event)
201 {
202 if (mir_event_get_type(event) != mir_event_type_window)
203 return;
204
205 auto const window_event = mir_event_get_window_event(event);
206 if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus)
207 return;
208
209 if (mir_window_event_get_attribute_value(window_event))
210 have_focus.raise();
211 });
212
213 mir_buffer_stream_swap_buffers_sync(mir_render_surface_get_buffer_stream(surface, 42, 42, mir_pixel_format_argb_8888));
214
215 EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true));
216
217 reset_window_event_handler();
218}
219
220void ClientMediatedUserGestures::center_mouse()
221{
222 Signal have_mouseover;
223
224 set_window_event_handler([&](MirEvent const* event)
225 {
226 if (mir_event_get_type(event) != mir_event_type_input)
227 return;
228
229 auto const input_event = mir_event_get_input_event(event);
230
231 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
232 return;
233
234 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
235
236 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
237 return;
238
239 have_mouseover.raise();
240 });
241
242 move_mouse(0.5 * as_displacement(screen_geometry.size));
243
244// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
245// But it isn't essential for the test and we've probably waited long enough
246// for the mouse-down needed by the test to reach the window.
247// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
248 have_mouseover.wait_for(receive_event_timeout);
249
250 reset_window_event_handler();
251}
252
253auto ClientMediatedUserGestures::user_initiates_gesture() -> Cookie
254{
255 Cookie cookie;
256 Signal have_cookie;
257
258 set_window_event_handler([&](MirEvent const* event)
259 {
260 if (mir_event_get_type(event) != mir_event_type_input)
261 return;
262
263 auto const input_event = mir_event_get_input_event(event);
264
265 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
266 return;
267
268 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
269
270 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down)
271 return;
272
273 cookie = Cookie{mir_input_event_get_cookie(input_event)};
274 have_cookie.raise();
275 });
276
277 start_dragging_mouse();
278
279 EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true));
280
281 reset_window_event_handler();
282 return cookie;
283}
284}
285
286TEST_F(ClientMediatedUserGestures, when_user_initiates_gesture_client_receives_cookie)
287{
288 auto const cookie = user_initiates_gesture();
289
290 EXPECT_THAT(cookie.get(), NotNull());
291}
292
293TEST_F(ClientMediatedUserGestures, when_client_initiates_move_window_manager_handles_request)
294{
295 auto const cookie = user_initiates_gesture();
296 Signal have_request;
297 EXPECT_CALL(*window_manager, handle_request_move(_, _, _)).WillOnce(InvokeWithoutArgs([&]{ have_request.raise(); }));
298
299 mir_window_request_user_move(window, cookie);
300
301 EXPECT_THAT(have_request.wait_for(receive_event_timeout), Eq(true));
302}
0303
=== added file 'tests/miral/display_reconfiguration.cpp'
--- tests/miral/display_reconfiguration.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/display_reconfiguration.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,87 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20#include <mir/event_printer.h>
21
22using namespace miral;
23using namespace testing;
24namespace mt = mir::test;
25using mir::operator<<;
26
27namespace
28{
29X const display_left{0};
30Y const display_top{0};
31Width const display_width{640};
32Height const display_height{480};
33
34Rectangle const display_area{{display_left, display_top},
35 {display_width, display_height}};
36
37struct DisplayConfiguration : TestWindowManagerTools
38{
39 Size const initial_window_size{600, 400};
40
41 Window window;
42
43 void SetUp() override
44 {
45 basic_window_manager.add_display_for_testing(display_area);
46 basic_window_manager.add_session(session);
47 }
48
49 void create_fullscreen_window()
50 {
51 mir::scene::SurfaceCreationParameters creation_parameters;
52 creation_parameters.type = mir_window_type_normal;
53 creation_parameters.size = initial_window_size;
54 creation_parameters.state = mir_window_state_fullscreen;
55 creation_parameters.output_id = mir::graphics::DisplayConfigurationOutputId{0};
56
57 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
58 .WillOnce(
59 Invoke(
60 [this](WindowInfo const& window_info)
61 { window = window_info.window(); }));
62
63 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
64 basic_window_manager.select_active_window(window);
65
66 // Clear the expectations used to capture parent & child
67 Mock::VerifyAndClearExpectations(window_manager_policy);
68 }
69};
70}
71
72// This is the scenario behind lp:1640557
73TEST_F(DisplayConfiguration, given_fullscreen_windows_reconfiguring_displays_doesnt_crash)
74{
75 create_fullscreen_window();
76
77 WindowSpecification mods;
78 mods.state() = mir_window_state_hidden;
79 window_manager_tools.modify_window(window, mods);
80 mods.state() = mir_window_state_fullscreen;
81 window_manager_tools.modify_window(window, mods);
82
83 Rectangle const new_display{display_area.top_left+as_displacement({display_width, Height{0}}), display_area.size};
84
85 basic_window_manager.add_display_for_testing(new_display);
86 basic_window_manager.remove_display(new_display);
87}
088
=== added file 'tests/miral/drag_active_window.cpp'
--- tests/miral/drag_active_window.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/drag_active_window.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,153 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20#include <mir/event_printer.h>
21
22using namespace miral;
23using namespace testing;
24namespace mt = mir::test;
25using mir::operator<<;
26
27namespace
28{
29X const display_left{0};
30Y const display_top{0};
31Width const display_width{640};
32Height const display_height{480};
33
34Rectangle const display_area{{display_left, display_top},
35 {display_width, display_height}};
36
37struct DragActiveWindow : TestWindowManagerTools, WithParamInterface<MirWindowType>
38{
39 Size const initial_parent_size{600, 400};
40
41 Window window;
42
43 void SetUp() override
44 {
45 basic_window_manager.add_display_for_testing(display_area);
46 basic_window_manager.add_session(session);
47 }
48
49 void create_window_of_type(MirWindowType type)
50 {
51 mir::scene::SurfaceCreationParameters creation_parameters;
52 creation_parameters.type = type;
53 creation_parameters.size = initial_parent_size;
54
55 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
56 .WillOnce(
57 Invoke(
58 [this](WindowInfo const& window_info)
59 { window = window_info.window(); }));
60
61 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
62 basic_window_manager.select_active_window(window);
63
64 // Clear the expectations used to capture parent & child
65 Mock::VerifyAndClearExpectations(window_manager_policy);
66 }
67};
68
69using ForMoveableTypes = DragActiveWindow;
70using ForUnmoveableTypes = DragActiveWindow;
71
72TEST_P(ForMoveableTypes, moves)
73{
74 create_window_of_type(GetParam());
75
76 Displacement const movement{10, 10};
77 auto const initial_position = window.top_left();
78 auto const expected_position = initial_position + movement;
79
80 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
81
82 window_manager_tools.drag_active_window(movement);
83
84 EXPECT_THAT(window.top_left(), Eq(expected_position))
85 << "Type: " << GetParam();
86}
87
88TEST_P(ForUnmoveableTypes, doesnt_move)
89{
90 create_window_of_type(GetParam());
91
92 Displacement const movement{10, 10};
93 auto const expected_position = window.top_left();
94
95 EXPECT_CALL(*window_manager_policy, advise_move_to(_, _)).Times(0);
96
97 window_manager_tools.drag_active_window(movement);
98
99 EXPECT_THAT(window.top_left(), Eq(expected_position))
100 << "Type: " << GetParam();
101}
102}
103
104// When a surface is moved interactively
105// -------------------------------------
106// Regular, floating regular, dialog, and satellite surfaces should be user-movable.
107// Popups, glosses, and tips should not be.
108// Freestyle surfaces may or may not be, as specified by the app.
109// Mir and Unity: Surfaces, input, and displays (v0.3)
110INSTANTIATE_TEST_CASE_P(DragActiveWindow, ForMoveableTypes, ::testing::Values(
111 mir_window_type_normal,
112 mir_window_type_utility,
113 mir_window_type_dialog,
114// mir_window_type_gloss,
115 mir_window_type_freestyle
116// mir_window_type_menu,
117// mir_window_type_inputmethod,
118// mir_window_type_satellite,
119// mir_window_type_tip,
120// mir_window_types
121));
122
123
124INSTANTIATE_TEST_CASE_P(DragActiveWindow, ForUnmoveableTypes, ::testing::Values(
125// mir_window_type_normal,
126// mir_window_type_utility,
127// mir_window_type_dialog,
128 mir_window_type_gloss,
129// mir_window_type_freestyle,
130 mir_window_type_menu,
131 mir_window_type_inputmethod,
132// mir_window_type_satellite,
133 mir_window_type_tip
134// mir_window_types
135));
136
137using DragWindow = DragActiveWindow;
138
139TEST_F(DragWindow, can_drag_satellite)
140{
141 create_window_of_type(mir_window_type_satellite);
142
143 Displacement const movement{10, 10};
144 auto const initial_position = window.top_left();
145 auto const expected_position = initial_position + movement;
146
147 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
148
149 window_manager_tools.drag_window(window, movement);
150
151 EXPECT_THAT(window.top_left(), Eq(expected_position))
152 << "Type: " << GetParam();
153}
0\ No newline at end of file154\ No newline at end of file
1155
=== added file 'tests/miral/drag_and_drop.cpp'
--- tests/miral/drag_and_drop.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/drag_and_drop.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,656 @@
1/*
2 * Copyright © 2017 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <miral/window_management_policy_addendum2.h>
20
21#include <mir/client/blob.h>
22#include <mir/client/cookie.h>
23#include <mir/client/surface.h>
24#include <mir/client/window.h>
25#include <mir/client/window_spec.h>
26#include <mir_toolkit/mir_buffer_stream.h>
27#include <mir_toolkit/extensions/drag_and_drop.h>
28
29#include <mir/geometry/displacement.h>
30#include <mir/input/input_device_info.h>
31#include <mir/input/device_capability.h>
32#include <mir/shell/shell.h>
33
34#include "test_server.h"
35#include <mir_test_framework/fake_input_device.h>
36#include <mir_test_framework/stub_server_platform_factory.h>
37#include <mir/test/event_factory.h>
38#include <mir/test/signal.h>
39
40#include <gmock/gmock.h>
41#include <gtest/gtest.h>
42
43#include <linux/input.h>
44#include <uuid/uuid.h>
45
46#include <boost/throw_exception.hpp>
47#include <atomic>
48
49using namespace std::chrono_literals;
50using namespace mir::client;
51using namespace mir::geometry;
52using namespace testing;
53using mir::test::Signal;
54
55namespace
56{
57struct MouseMoverAndFaker
58{
59 void start_dragging_mouse()
60 {
61 using namespace mir::input::synthesis;
62 fake_mouse->emit_event(a_button_down_event().of_button(BTN_LEFT));
63 }
64
65 void move_mouse(Displacement const& displacement)
66 {
67 using mir::input::synthesis::a_pointer_event;
68 fake_mouse->emit_event(a_pointer_event().with_movement(displacement.dx.as_int(), displacement.dy.as_int()));
69 }
70
71 void release_mouse()
72 {
73 using namespace mir::input::synthesis;
74 fake_mouse->emit_event(a_button_up_event().of_button(BTN_LEFT));
75 }
76
77private:
78 std::unique_ptr<mir_test_framework::FakeInputDevice> fake_mouse{
79 mir_test_framework::add_fake_input_device(
80 mir::input::InputDeviceInfo{"mouse", "mouse-uid", mir::input::DeviceCapability::pointer})};
81};
82
83Rectangle const screen_geometry{{0,0}, {800,600}};
84auto const receive_event_timeout = 1s; //90s;
85
86struct ConnectedClientWithAWindow : miral::TestServer
87{
88 Connection connection;
89 Surface surface;
90 Window window;
91
92 void SetUp() override
93 {
94 miral::TestServer::SetUp();
95 connection = connect_client(__func__);
96 auto const width = surface_size.width.as_int();
97 auto const height = surface_size.height.as_int();
98 surface = Surface{mir_connection_create_render_surface_sync(connection, width, height)};
99 window = WindowSpec::for_normal_window(connection, width, height)
100 .set_name("ConnectedClientWithAWindow")
101 .add_surface(surface, width, height, 0, 0)
102 .create_window();
103 }
104
105 void TearDown() override
106 {
107 window.reset();
108 surface.reset();
109 connection.reset();
110 miral::TestServer::TearDown();
111 }
112
113 mir::geometry::Size const surface_size {640, 480};
114};
115
116struct DragAndDrop : ConnectedClientWithAWindow,
117 MouseMoverAndFaker
118{
119 MirDragAndDropV1 const* dnd = nullptr;
120
121 void SetUp() override
122 {
123 mir_test_framework::set_next_display_rects(std::unique_ptr<std::vector<Rectangle>>(new std::vector<Rectangle>({screen_geometry})));
124
125 ConnectedClientWithAWindow::SetUp();
126 dnd = mir_drag_and_drop_v1(connection);
127 mir_window_set_event_handler(window, &window_event_handler, this);
128 if (dnd) dnd->set_start_drag_and_drop_callback(window, &window_dnd_start_handler, this);
129
130 create_target_window();
131
132 paint_window(surface, window);
133
134 center_mouse();
135 }
136
137 void TearDown() override
138 {
139 reset_window_event_handler(target_window);
140 reset_window_event_handler(window);
141 target_window.reset();
142 target_surface.reset();
143 another_connection.reset();
144 ConnectedClientWithAWindow::TearDown();
145 }
146
147 auto user_initiates_drag() -> Cookie;
148 auto client_requests_drag(Cookie const& cookie) -> Blob;
149 auto handle_from_mouse_move() -> Blob;
150 auto handle_from_mouse_leave() -> Blob;
151 auto handle_from_mouse_enter() -> Blob;
152 auto handle_from_mouse_release() -> Blob;
153 auto count_of_handles_when_moving_mouse() -> int;
154
155private:
156 auto build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy> override;
157 void center_mouse();
158 void paint_window(MirRenderSurface* s, MirWindow* w);
159 void set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler);
160 void set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler);
161 void reset_window_event_handler(MirWindow* window);
162
163 void create_target_window()
164 {
165 another_connection = connect_client("another_connection");
166 auto const height = screen_geometry.size.height.as_int();
167 auto const width = screen_geometry.size.width.as_int();
168 target_surface = Surface{mir_connection_create_render_surface_sync(another_connection, width,height)};
169 target_window = WindowSpec::for_normal_window(another_connection, width, height)
170 .set_name("target_window")
171 .add_surface(target_surface, width, height, 0, 0)
172 .set_event_handler(&window_event_handler, this)
173 .create_window();
174
175 paint_window(target_surface, target_window);
176 }
177
178 void invoke_window_event_handler(MirWindow* window, MirEvent const* event)
179 {
180 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
181 if (window == this->window) window_event_handler_(event);
182 if (window == target_window) target_window_event_handler_(event);
183 }
184
185 void invoke_window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event)
186 {
187 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
188 if (window == this->window) window_dnd_start_(event);
189 }
190
191 std::mutex window_event_handler_mutex;
192 std::function<void(MirDragAndDropEvent const* event)> window_dnd_start_ = [](MirDragAndDropEvent const*) {};
193 std::function<void(MirEvent const* event)> window_event_handler_ = [](MirEvent const*) {};
194 std::function<void(MirEvent const* event)> target_window_event_handler_ = [](MirEvent const*) {};
195
196 static void window_event_handler(MirWindow* window, MirEvent const* event, void* context);
197 static void window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context);
198
199 Connection another_connection;
200 Surface target_surface;
201 Window target_window;
202};
203
204void DragAndDrop::set_window_event_handler(MirWindow* window, std::function<void(MirEvent const* event)> const& handler)
205{
206 std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
207 if (window == this->window) window_event_handler_ = handler;
208 if (window == target_window) target_window_event_handler_ = handler;
209}
210
211void DragAndDrop::set_window_dnd_start_handler(MirWindow* window, std::function<void(MirDragAndDropEvent const*)> const& handler)
212{
213std::lock_guard<decltype(window_event_handler_mutex)> lock{window_event_handler_mutex};
214if (window == this->window) window_dnd_start_ = handler;
215}
216
217
218void DragAndDrop::reset_window_event_handler(MirWindow* window)
219{
220 if (window == this->window) window_event_handler_ = [](MirEvent const*) {};
221 if (window == target_window) target_window_event_handler_ = [](MirEvent const*) {};
222}
223
224void DragAndDrop::paint_window(MirRenderSurface* s, MirWindow* w)
225{
226 Signal have_focus;
227
228 set_window_event_handler(w, [&](MirEvent const* event)
229 {
230 if (mir_event_get_type(event) != mir_event_type_window)
231 return;
232
233 auto const window_event = mir_event_get_window_event(event);
234 if (mir_window_event_get_attribute(window_event) != mir_window_attrib_focus)
235 return;
236
237 if (mir_window_event_get_attribute_value(window_event))
238 have_focus.raise();
239 });
240
241 int width;
242 int height;
243 mir_render_surface_get_size(s, &width, &height);
244 mir_buffer_stream_swap_buffers_sync(
245 mir_render_surface_get_buffer_stream(s, width, height, mir_pixel_format_argb_8888));
246
247 EXPECT_THAT(have_focus.wait_for(receive_event_timeout), Eq(true));
248
249 reset_window_event_handler(w);
250}
251
252void DragAndDrop::center_mouse()
253{
254 Signal have_mouseover;
255
256 set_window_event_handler(window, [&](MirEvent const* event)
257 {
258 if (mir_event_get_type(event) != mir_event_type_input)
259 return;
260
261 auto const input_event = mir_event_get_input_event(event);
262
263 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
264 return;
265
266 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
267
268 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
269 return;
270
271 have_mouseover.raise();
272 });
273
274 move_mouse(0.5 * as_displacement(screen_geometry.size));
275
276// We miss the "mouseover" occasionally (with valgrind and heavy stress about 1/20).
277// But it isn't essential for the test and we've probably waited long enough
278// for the mouse-down needed by the test to reach the window.
279// EXPECT_THAT(have_mouseover.wait_for(receive_event_timeout), Eq(true));
280 have_mouseover.wait_for(receive_event_timeout);
281
282 reset_window_event_handler(window);
283}
284
285void DragAndDrop::window_event_handler(MirWindow* window, MirEvent const* event, void* context)
286{
287 static_cast<DragAndDrop*>(context)->invoke_window_event_handler(window, event);
288}
289
290void DragAndDrop::window_dnd_start_handler(MirWindow* window, MirDragAndDropEvent const* event, void* context)
291{
292 static_cast<DragAndDrop*>(context)->invoke_window_dnd_start_handler(window, event);
293}
294
295
296auto DragAndDrop::user_initiates_drag() -> Cookie
297{
298 Cookie cookie;
299 Signal have_cookie;
300
301 set_window_event_handler(window, [&](MirEvent const* event)
302 {
303 if (mir_event_get_type(event) != mir_event_type_input)
304 return;
305
306 auto const input_event = mir_event_get_input_event(event);
307
308 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
309 return;
310
311 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
312
313 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_down)
314 return;
315
316 cookie = Cookie{mir_input_event_get_cookie(input_event)};
317 have_cookie.raise();
318 });
319
320 start_dragging_mouse();
321
322 EXPECT_THAT(have_cookie.wait_for(receive_event_timeout), Eq(true));
323
324 reset_window_event_handler(window);
325 return cookie;
326}
327
328auto DragAndDrop::client_requests_drag(Cookie const& cookie) -> Blob
329{
330 Blob blob;
331 Signal initiated;
332
333 set_window_dnd_start_handler(window, [&](MirDragAndDropEvent const* event)
334 {
335 if (dnd)
336 blob.reset(dnd->start_drag_and_drop(event));
337
338 if (blob)
339 initiated.raise();
340 });
341
342 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
343
344 if (dnd)
345 dnd->request_drag_and_drop(window, cookie);
346
347 EXPECT_TRUE(initiated.wait_for(receive_event_timeout));
348
349 reset_window_event_handler(window);
350 return blob;
351}
352
353auto DragAndDrop::handle_from_mouse_move() -> Blob
354{
355 Blob blob;
356 Signal have_blob;
357
358 set_window_event_handler(window, [&](MirEvent const* event)
359 {
360 if (mir_event_get_type(event) != mir_event_type_input)
361 return;
362
363 auto const input_event = mir_event_get_input_event(event);
364
365 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
366 return;
367
368 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
369
370 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
371
372 if (dnd)
373 blob.reset(dnd->pointer_drag_and_drop(pointer_event));
374
375 if (blob)
376 have_blob.raise();
377 });
378
379 move_mouse({1,1});
380
381 EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
382
383 reset_window_event_handler(window);
384 return blob;
385}
386
387auto DragAndDrop::handle_from_mouse_leave() -> Blob
388{
389 Blob blob;
390 Signal have_blob;
391
392 set_window_event_handler(window, [&](MirEvent const* event)
393 {
394 if (mir_event_get_type(event) != mir_event_type_input)
395 return;
396
397 auto const input_event = mir_event_get_input_event(event);
398
399 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
400 return;
401
402 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
403
404 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_leave)
405 return;
406
407 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
408
409 if (dnd)
410 blob.reset(dnd->pointer_drag_and_drop(pointer_event));
411
412 if (blob)
413 have_blob.raise();
414 });
415
416 move_mouse({1,1});
417 move_mouse(0.5 * as_displacement(surface_size));
418
419 EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
420
421 reset_window_event_handler(window);
422 return blob;
423}
424
425auto DragAndDrop::handle_from_mouse_enter() -> Blob
426{
427 Blob blob;
428 Signal have_blob;
429
430 set_window_event_handler(target_window, [&](MirEvent const* event)
431 {
432 if (mir_event_get_type(event) != mir_event_type_input)
433 return;
434
435 auto const input_event = mir_event_get_input_event(event);
436
437 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
438 return;
439
440 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
441
442 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_enter)
443 return;
444
445 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
446
447 if (dnd)
448 blob.reset(dnd->pointer_drag_and_drop(pointer_event));
449
450 if (blob)
451 have_blob.raise();
452 });
453
454 move_mouse({1,1});
455 move_mouse(0.5 * as_displacement(surface_size));
456
457 EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
458
459 reset_window_event_handler(target_window);
460 return blob;
461}
462
463auto DragAndDrop::handle_from_mouse_release() -> Blob
464{
465 Blob blob;
466 Signal have_blob;
467
468 set_window_event_handler(target_window, [&](MirEvent const* event)
469 {
470 if (mir_event_get_type(event) != mir_event_type_input)
471 return;
472
473 auto const input_event = mir_event_get_input_event(event);
474
475 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
476 return;
477
478 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
479
480 if (mir_pointer_event_action(pointer_event) != mir_pointer_action_button_up)
481 return;
482
483 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
484
485 if (dnd)
486 blob.reset(dnd->pointer_drag_and_drop(pointer_event));
487
488 if (blob)
489 have_blob.raise();
490 });
491
492 move_mouse({1,1});
493 move_mouse(0.5 * as_displacement(surface_size));
494 release_mouse();
495
496 EXPECT_TRUE(have_blob.wait_for(receive_event_timeout));
497
498 reset_window_event_handler(target_window);
499 return blob;
500}
501
502auto DragAndDrop::count_of_handles_when_moving_mouse() -> int
503{
504 Signal have_3_events;
505 std::atomic<int> events{0};
506 std::atomic<int> handles{0};
507
508 auto counter = [&](MirEvent const* event)
509 {
510 if (mir_event_get_type(event) != mir_event_type_input)
511 return;
512
513 auto const input_event = mir_event_get_input_event(event);
514
515 if (mir_input_event_get_type(input_event) != mir_input_event_type_pointer)
516 return;
517
518 auto const pointer_event = mir_input_event_get_pointer_event(input_event);
519
520 EXPECT_THAT(dnd, Ne(nullptr)) << "No Drag and Drop extension";
521
522 Blob blob;
523 if (dnd)
524 blob.reset(dnd->pointer_drag_and_drop(pointer_event));
525
526 if (blob)
527 handles.fetch_add(1);
528
529 if (events.fetch_add(1) == 2)
530 have_3_events.raise();
531 };
532
533 set_window_event_handler(window, counter);
534 set_window_event_handler(target_window, counter);
535
536 start_dragging_mouse();
537 move_mouse({1,1});
538 release_mouse();
539
540 EXPECT_TRUE(have_3_events.wait_for(receive_event_timeout));
541
542 reset_window_event_handler(window);
543 reset_window_event_handler(target_window);
544 return handles;
545}
546
547auto DragAndDrop::build_window_manager_policy(miral::WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>
548{
549 struct DnDWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WindowManagementPolicyAddendum2
550 {
551 using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy;
552
553 void handle_request_drag_and_drop(miral::WindowInfo& window_info) override
554 {
555 uuid_t uuid;
556 uuid_generate(uuid);
557 std::vector<uint8_t> const handle{std::begin(uuid), std::end(uuid)};
558
559 tools.start_drag_and_drop(window_info, handle);
560 }
561
562 void handle_request_move(miral::WindowInfo&, MirInputEvent const*) override {}
563 };
564
565 return std::make_unique<DnDWindowManagerPolicy>(tools, *this);
566}
567
568MATCHER_P(BlobContentEq, p, "")
569{
570 if (!arg || !p)
571 return false;
572 if (mir_blob_size(arg) != mir_blob_size(p))
573 return false;
574 return !memcmp(mir_blob_data(arg), mir_blob_data(p), mir_blob_size(p));
575}
576}
577
578TEST_F(DragAndDrop, when_user_initiates_drag_client_receives_cookie)
579{
580 auto const cookie = user_initiates_drag();
581
582 EXPECT_THAT(cookie, Ne(nullptr));
583}
584
585TEST_F(DragAndDrop, when_client_requests_drags_it_receives_handle)
586{
587 auto const cookie = user_initiates_drag();
588 ASSERT_THAT(cookie, Ne(nullptr));
589
590 auto const handle = client_requests_drag(cookie);
591
592 EXPECT_THAT(handle, Ne(nullptr));
593}
594
595TEST_F(DragAndDrop, during_drag_when_user_moves_mouse_client_receives_handle)
596{
597 auto const cookie = user_initiates_drag();
598 ASSERT_THAT(cookie, Ne(nullptr));
599 auto const handle_from_request = client_requests_drag(cookie);
600
601 auto const handle = handle_from_mouse_move();
602
603 EXPECT_THAT(handle, Ne(nullptr));
604 EXPECT_THAT(handle, BlobContentEq(handle_from_request));
605}
606
607TEST_F(DragAndDrop, when_drag_moves_from_window_leave_event_contains_handle)
608{
609 auto const cookie = user_initiates_drag();
610 ASSERT_THAT(cookie, Ne(nullptr));
611 auto const handle_from_request = client_requests_drag(cookie);
612
613 auto const handle = handle_from_mouse_leave();
614
615 EXPECT_THAT(handle, Ne(nullptr));
616 EXPECT_THAT(handle, BlobContentEq(handle_from_request));
617}
618
619TEST_F(DragAndDrop, when_drag_enters_target_window_enter_event_contains_handle)
620{
621 auto const cookie = user_initiates_drag();
622 ASSERT_THAT(cookie, Ne(nullptr));
623 auto const handle_from_request = client_requests_drag(cookie);
624
625 auto const handle = handle_from_mouse_enter();
626
627 EXPECT_THAT(handle, Ne(nullptr));
628 EXPECT_THAT(handle, BlobContentEq(handle_from_request));
629}
630
631TEST_F(DragAndDrop, when_drag_releases_target_window_release_event_contains_handle)
632{
633 auto const cookie = user_initiates_drag();
634 ASSERT_THAT(cookie, Ne(nullptr));
635 auto const handle_from_request = client_requests_drag(cookie);
636
637 auto const handle = handle_from_mouse_release();
638
639 EXPECT_THAT(handle, Ne(nullptr));
640 EXPECT_THAT(handle, BlobContentEq(handle_from_request));
641}
642
643TEST_F(DragAndDrop, after_drag_finishes_pointer_events_no_longer_contain_handle)
644{
645 auto const cookie = user_initiates_drag();
646 ASSERT_THAT(cookie, Ne(nullptr));
647 client_requests_drag(cookie);
648 handle_from_mouse_release();
649
650 invoke_tools([](miral::WindowManagerTools& tools) { tools.end_drag_and_drop(); });
651
652 // Allow old pointer events to drain
653 std::this_thread::sleep_for(2ms);
654
655 EXPECT_THAT(count_of_handles_when_moving_mouse(), Eq(0));
656}
0657
=== added file 'tests/miral/modify_window_state.cpp'
--- tests/miral/modify_window_state.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/modify_window_state.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,105 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20#include <mir/event_printer.h>
21
22using namespace miral;
23using namespace testing;
24namespace mt = mir::test;
25using mir::operator<<;
26
27namespace
28{
29X const display_left{0};
30Y const display_top{0};
31Width const display_width{640};
32Height const display_height{480};
33
34Rectangle const display_area{{display_left, display_top},
35 {display_width, display_height}};
36
37struct ModifyWindowState : TestWindowManagerTools, WithParamInterface<MirWindowType>
38{
39 Size const initial_parent_size{600, 400};
40
41 Window window;
42
43 void SetUp() override
44 {
45 basic_window_manager.add_display_for_testing(display_area);
46 basic_window_manager.add_session(session);
47 }
48
49 void create_window_of_type(MirWindowType type)
50 {
51 mir::scene::SurfaceCreationParameters creation_parameters;
52 creation_parameters.type = type;
53 creation_parameters.size = initial_parent_size;
54
55 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
56 .WillOnce(
57 Invoke(
58 [this](WindowInfo const& window_info)
59 { window = window_info.window(); }));
60
61 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
62 basic_window_manager.select_active_window(window);
63
64 // Clear the expectations used to capture parent & child
65 Mock::VerifyAndClearExpectations(window_manager_policy);
66 }
67};
68
69using ForNormalSurface = ModifyWindowState;
70
71TEST_P(ForNormalSurface, state)
72{
73 auto const original_state = mir_window_state_restored;
74 auto const new_state = MirWindowState(GetParam());
75 auto const state_is_visible = (new_state != mir_window_state_minimized) && (new_state != mir_window_state_hidden);
76
77 create_window_of_type(mir_window_type_normal);
78 auto const& info = window_manager_tools.info_for(window);
79
80 WindowSpecification mods;
81 mods.state() = new_state;
82 window_manager_tools.modify_window(window, mods);
83 EXPECT_THAT(std::shared_ptr<mir::scene::Surface>(window)->state(), Eq(new_state));
84 EXPECT_THAT(info.state(), Eq(new_state));
85 EXPECT_THAT(info.is_visible(), Eq(state_is_visible)) << "State is " << new_state;
86
87 mods.state() = original_state;
88 window_manager_tools.modify_window(window, mods);
89 EXPECT_THAT(std::shared_ptr<mir::scene::Surface>(window)->state(), Eq(original_state));
90 EXPECT_THAT(info.state(), Eq(original_state));
91 EXPECT_TRUE(info.is_visible());
92}
93}
94
95INSTANTIATE_TEST_CASE_P(ModifyWindowState, ForNormalSurface, ::testing::Values(
96// mir_window_state_unknown,
97 mir_window_state_restored,
98 mir_window_state_minimized,
99 mir_window_state_maximized,
100 mir_window_state_vertmaximized,
101 mir_window_state_fullscreen,
102 mir_window_state_horizmaximized,
103 mir_window_state_hidden
104// mir_window_states
105));
0106
=== added file 'tests/miral/mru_window_list.cpp'
--- tests/miral/mru_window_list.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/mru_window_list.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,193 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "mru_window_list.h"
20
21#include <mir/test/doubles/stub_surface.h>
22#include <mir/test/doubles/stub_session.h>
23
24#include <gtest/gtest.h>
25#include <gmock/gmock.h>
26
27using namespace testing;
28
29namespace
30{
31struct StubSurface : mir::test::doubles::StubSurface
32{
33 bool visible() const override { return visible_; }
34
35 bool visible_ = true;
36};
37
38struct StubSession : mir::test::doubles::StubSession
39{
40 StubSession(int number_of_surfaces)
41 {
42 for (auto i = 0; i != number_of_surfaces; ++i)
43 surfaces.push_back(std::make_shared<StubSurface>());
44 }
45
46 std::shared_ptr<mir::scene::Surface> surface(mir::frontend::SurfaceId surface) const override
47 {
48 return surfaces.at(surface.as_value());
49 }
50
51 std::vector<std::shared_ptr<StubSurface>> surfaces;
52};
53
54MATCHER(IsNullWindow, std::string("is not null"))
55{
56 return !arg;
57}
58}
59
60struct MRUWindowList : testing::Test
61{
62 static auto const window_a_id = 0;
63 static auto const window_b_id = 1;
64
65 miral::MRUWindowList mru_list;
66
67 std::shared_ptr<StubSession> const stub_session{std::make_shared<StubSession>(3)};
68 miral::Application app{stub_session};
69 miral::Window window_a{app, stub_session->surface(mir::frontend::SurfaceId{window_a_id})};
70 miral::Window window_b{app, stub_session->surface(mir::frontend::SurfaceId{window_b_id})};
71 miral::Window window_c{app, stub_session->surface(mir::frontend::SurfaceId{2})};
72
73 void hide_window(int window_id)
74 {
75 stub_session->surfaces[window_id]->visible_ = false;
76 }
77
78 void show_window(int window_id)
79 {
80 stub_session->surfaces[window_id]->visible_ = true;
81 }
82};
83
84TEST_F(MRUWindowList, when_created_is_empty)
85{
86 EXPECT_THAT(mru_list.top(), IsNullWindow());
87}
88
89TEST_F(MRUWindowList, given_empty_list_when_a_window_pushed_that_window_is_top)
90{
91 mru_list.push(window_a);
92 EXPECT_THAT(mru_list.top(), Eq(window_a));
93}
94
95TEST_F(MRUWindowList, given_non_empty_list_when_a_window_pushed_that_window_is_top)
96{
97 mru_list.push(window_a);
98 mru_list.push(window_b);
99 mru_list.push(window_c);
100 EXPECT_THAT(mru_list.top(), Eq(window_c));
101}
102
103TEST_F(MRUWindowList, given_non_empty_list_when_top_window_is_erased_that_window_is_no_longer_on_top)
104{
105 mru_list.push(window_a);
106 mru_list.push(window_b);
107 mru_list.push(window_c);
108 mru_list.erase(window_c);
109 EXPECT_THAT(mru_list.top(), Ne(window_c));
110}
111
112TEST_F(MRUWindowList, a_window_pushed_twice_is_not_enumerated_twice)
113{
114 mru_list.push(window_a);
115 mru_list.push(window_b);
116 mru_list.push(window_a);
117
118 int count{0};
119
120 mru_list.enumerate([&](miral::Window& window)
121 { if (window == window_a) ++count; return true; });
122
123 EXPECT_THAT(count, Eq(1));
124}
125
126TEST_F(MRUWindowList, after_multiple_pushes_windows_are_enumerated_in_mru_order)
127{
128 mru_list.push(window_a);
129 mru_list.push(window_b);
130 mru_list.push(window_c);
131 mru_list.push(window_a);
132 mru_list.push(window_b);
133 mru_list.push(window_a);
134
135 mru_list.push(window_c);
136 mru_list.push(window_b);
137 mru_list.push(window_a);
138
139 std::vector<miral::Window> as_enumerated;
140
141 mru_list.enumerate([&](miral::Window& window)
142 { as_enumerated.push_back(window); return true; });
143
144 EXPECT_THAT(as_enumerated, ElementsAre(window_a, window_b, window_c));
145}
146
147TEST_F(MRUWindowList, when_enumerator_returns_false_enumeration_is_short_circuited)
148{
149 mru_list.push(window_a);
150 mru_list.push(window_b);
151 mru_list.push(window_c);
152
153 int count{0};
154
155 mru_list.enumerate([&](miral::Window&) { ++count; return false; });
156
157 EXPECT_THAT(count, Eq(1));
158}
159
160TEST_F(MRUWindowList, a_hidden_window_is_not_enumerated)
161{
162 mru_list.push(window_a);
163 mru_list.push(window_b);
164 mru_list.push(window_c);
165
166 int count{0};
167
168 hide_window(window_a_id);
169 mru_list.enumerate([&](miral::Window& window)
170 { if (window == window_a) ++count; return true; });
171
172 EXPECT_THAT(count, Eq(0));
173}
174
175TEST_F(MRUWindowList, hiding_then_showing_windows_retains_order)
176{
177 mru_list.push(window_a);
178 mru_list.push(window_b);
179 mru_list.push(window_c);
180
181 hide_window(window_a_id);
182 hide_window(window_b_id);
183 show_window(window_a_id);
184 show_window(window_b_id);
185
186 std::vector<miral::Window> as_enumerated;
187
188 mru_list.enumerate([&](miral::Window& window)
189 { as_enumerated.push_back(window); return true; });
190
191 EXPECT_THAT(as_enumerated, ElementsAre(window_c, window_b, window_a));
192}
193
0194
=== added file 'tests/miral/raise_tree.cpp'
--- tests/miral/raise_tree.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/raise_tree.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,85 @@
1/*
2 * Copyright © 2017 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20
21using namespace miral;
22using namespace testing;
23namespace mt = mir::test;
24
25namespace
26{
27X const display_left{0};
28Y const display_top{0};
29Width const display_width{640};
30Height const display_height{480};
31
32Rectangle const display_area{{display_left, display_top}, {display_width, display_height}};
33
34struct RaiseTree : TestWindowManagerTools
35{
36 Size const initial_parent_size{600, 400};
37 Size const initial_child_size{300, 300};
38
39 Window parent;
40 Window child;
41 Window another_window;
42
43 void SetUp() override
44 {
45 basic_window_manager.add_display_for_testing(display_area);
46
47 mir::scene::SurfaceCreationParameters creation_parameters;
48 basic_window_manager.add_session(session);
49
50 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
51 .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
52 .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }))
53 .WillOnce(Invoke([this](WindowInfo const& window_info){ another_window = window_info.window(); }));
54
55 creation_parameters.size = initial_parent_size;
56 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
57
58 creation_parameters.type = mir_window_type_menu;
59 creation_parameters.parent = parent;
60 creation_parameters.size = initial_child_size;
61 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
62
63 creation_parameters.type = mir_window_type_normal;
64 creation_parameters.parent.reset();
65 creation_parameters.size = display_area.size;
66 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
67
68 // Clear the expectations used to capture parent & child
69 Mock::VerifyAndClearExpectations(window_manager_policy);
70 }
71};
72}
73
74TEST_F(RaiseTree, when_parent_is_raised_child_is_raised)
75{
76 EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(parent, child)));
77 basic_window_manager.raise_tree(parent);
78}
79
80TEST_F(RaiseTree, when_child_is_raised_parent_is_raised)
81{
82 EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(parent, child)));
83 EXPECT_CALL(*window_manager_policy, advise_raise(ElementsAre(child)));
84 basic_window_manager.raise_tree(child);
85}
086
=== added file 'tests/miral/runner.cpp'
--- tests/miral/runner.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/runner.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,49 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_server.h"
20
21#include <gtest/gtest.h>
22#include <gmock/gmock.h>
23
24namespace
25{
26struct Runner : miral::TestServer
27{
28 void SetUp() override
29 {
30 }
31
32 MOCK_METHOD0(callback, void());
33};
34}
35
36TEST_F(Runner, stop_callback_is_called)
37{
38 runner.add_stop_callback([this] { callback(); });
39 miral::TestServer::SetUp();
40 EXPECT_CALL(*this, callback());
41}
42
43TEST_F(Runner, start_callback_is_called)
44{
45 runner.add_start_callback([this] { callback(); });
46 EXPECT_CALL(*this, callback());
47 miral::TestServer::SetUp();
48 testing::Mock::VerifyAndClearExpectations(this);
49}
0\ No newline at end of file50\ No newline at end of file
151
=== added file 'tests/miral/select_active_window.cpp'
--- tests/miral/select_active_window.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/select_active_window.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,121 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20
21using namespace miral;
22using namespace testing;
23namespace mt = mir::test;
24
25namespace
26{
27X const display_left{0};
28Y const display_top{0};
29Width const display_width{1280};
30Height const display_height{720};
31
32Rectangle const display_area{{display_left, display_top},
33 {display_width, display_height}};
34
35struct SelectActiveWindow : TestWindowManagerTools
36{
37
38 void SetUp() override
39 {
40 basic_window_manager.add_display_for_testing(display_area);
41 basic_window_manager.add_session(session);
42 }
43
44 auto create_window(mir::scene::SurfaceCreationParameters creation_parameters) -> Window
45 {
46 Window result;
47
48 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
49 .WillOnce(
50 Invoke(
51 [&result](WindowInfo const& window_info)
52 { result = window_info.window(); }));
53
54 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
55 basic_window_manager.select_active_window(result);
56
57 // Clear the expectations used to capture parent & child
58 Mock::VerifyAndClearExpectations(window_manager_policy);
59
60 return result;
61 }
62};
63}
64
65namespace std
66{
67auto operator<<(std::ostream& out, miral::Window const& window) -> std::ostream&
68{
69 if (std::shared_ptr<mir::scene::Surface> surface = window)
70 return out << surface->name();
71 else
72 return out << "(nul)";
73}
74}
75
76// lp:1626659
77// "If the surface has a child dialog, the deepest descendant
78// dialog should receive input focus."
79TEST_F(SelectActiveWindow, given_a_child_dialog_when_selecting_the_parent_the_dialog_receives_focus)
80{
81 mir::scene::SurfaceCreationParameters creation_parameters;
82 creation_parameters.name = "parent";
83 creation_parameters.type = mir_window_type_normal;
84 creation_parameters.size = Size{600, 400};
85
86 auto parent = create_window(creation_parameters);
87
88 creation_parameters.name = "dialog";
89 creation_parameters.type = mir_window_type_dialog;
90 creation_parameters.parent = parent;
91
92 auto dialog = create_window(creation_parameters);
93
94 auto actual = basic_window_manager.select_active_window(parent);
95 EXPECT_THAT(actual, Eq(dialog))
96 << "actual=" << actual << ", expected=" << dialog;
97}
98
99TEST_F(SelectActiveWindow, given_a_hidden_child_dialog_when_selecting_the_parent_the_parent_receives_focus)
100{
101 mir::scene::SurfaceCreationParameters creation_parameters;
102 creation_parameters.name = "parent";
103 creation_parameters.type = mir_window_type_normal;
104 creation_parameters.size = Size{600, 400};
105
106 auto parent = create_window(creation_parameters);
107
108 creation_parameters.name = "dialog";
109 creation_parameters.type = mir_window_type_dialog;
110 creation_parameters.parent = parent;
111
112 auto dialog = create_window(creation_parameters);
113
114 WindowSpecification mods;
115 mods.state() = mir_window_state_hidden;
116 basic_window_manager.modify_window(basic_window_manager.info_for(dialog), mods);
117
118 auto actual = basic_window_manager.select_active_window(parent);
119 EXPECT_THAT(actual, Eq(parent))
120 << "actual=" << actual << ", expected=" << parent;
121}
0122
=== added file 'tests/miral/test_server.cpp'
--- tests/miral/test_server.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/test_server.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,198 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_server.h"
20#include "basic_window_manager.h"
21
22#include <miral/command_line_option.h>
23#include <miral/set_window_management_policy.h>
24
25#include <mir_test_framework/executable_path.h>
26#include <mir_test_framework/stub_server_platform_factory.h>
27#include <mir_test_framework/headless_display_buffer_compositor_factory.h>
28#include <mir/test/doubles/null_logger.h>
29
30#include <mir/fd.h>
31#include <mir/main_loop.h>
32#include <mir/server.h>
33#include <mir/version.h>
34
35#include <boost/throw_exception.hpp>
36
37
38using namespace miral;
39using namespace testing;
40namespace mtf = mir_test_framework;
41namespace msh = mir::shell;
42
43namespace
44{
45std::chrono::seconds const timeout{20};
46char const* dummy_args[2] = { "TestServer", nullptr };
47}
48
49miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy(
50 WindowManagerTools const& tools, TestServer& test_fixture) :
51 CanonicalWindowManagerPolicy{tools}
52{
53 test_fixture.tools = tools;
54}
55
56miral::TestServer::TestServer() :
57 runner{1, dummy_args}
58{
59 add_to_environment("MIR_SERVER_PLATFORM_GRAPHICS_LIB", mtf::server_platform("graphics-dummy.so").c_str());
60 add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str());
61 add_to_environment("MIR_SERVER_NO_FILE", "on");
62}
63
64auto miral::TestServer::build_window_manager_policy(WindowManagerTools const& tools)
65-> std::unique_ptr<TestWindowManagerPolicy>
66{
67 return std::make_unique<TestWindowManagerPolicy>(tools, *this);
68}
69
70void miral::TestServer::SetUp()
71{
72 mir::test::AutoJoinThread t([this]
73 {
74 auto init = [this](mir::Server& server)
75 {
76 server.add_init_callback([&]
77 {
78 auto const main_loop = server.the_main_loop();
79 // By enqueuing the notification code in the main loop, we are
80 // ensuring that the server has really and fully started before
81 // leaving start_mir_server().
82 main_loop->enqueue(this, [&]
83 {
84 std::lock_guard<std::mutex> lock(mutex);
85 server_running = &server;
86 started.notify_one();
87 });
88 });
89
90 server.override_the_display_buffer_compositor_factory([]
91 {
92 return std::make_shared<mtf::HeadlessDisplayBufferCompositorFactory>();
93 });
94
95 server.override_the_window_manager_builder([this, &server](msh::FocusController* focus_controller)
96 -> std::shared_ptr<msh::WindowManager>
97 {
98 auto const display_layout = server.the_shell_display_layout();
99
100 auto const persistent_surface_store = server.the_persistent_surface_store();
101
102 auto builder = [this](WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
103 {
104 return build_window_manager_policy(tools);
105 };
106
107 auto wm = std::make_shared<miral::BasicWindowManager>(
108 focus_controller,
109 display_layout,
110 persistent_surface_store,
111 *server.the_display_configuration_observer_registrar(),
112 builder);
113 window_manager = wm;
114 return wm;
115 });
116 };
117
118 try
119 {
120 namespace mtd = mir::test::doubles;
121 // Ignore the --logging flag passed to mir tests
122 CommandLineOption logging{[](bool) {}, mtd::logging_opt, mtd::logging_descr, false};
123 runner.run_with({init, logging});
124 }
125 catch (std::exception const& e)
126 {
127 FAIL() << e.what();
128 }
129
130 std::lock_guard<std::mutex> lock(mutex);
131 server_running = nullptr;
132 started.notify_one();
133 });
134
135 std::unique_lock<std::mutex> lock(mutex);
136 started.wait_for(lock, timeout, [&] { return server_running; });
137
138 if (!server_running)
139 BOOST_THROW_EXCEPTION(std::runtime_error{"Failed to start server thread"});
140
141 server_thread = std::move(t);
142}
143
144void miral::TestServer::TearDown()
145{
146 std::unique_lock<std::mutex> lock(mutex);
147
148 runner.stop();
149
150 started.wait_for(lock, timeout, [&] { return !server_running; });
151
152 if (server_running)
153 BOOST_THROW_EXCEPTION(std::logic_error{"Failed to stop server"});
154
155 server_thread.stop();
156}
157
158auto miral::TestServer::connect_client(std::string name) -> mir::client::Connection
159{
160 std::lock_guard<std::mutex> lock(mutex);
161
162 if (!server_running)
163 BOOST_THROW_EXCEPTION(std::runtime_error{"Server not running"});
164
165 char connect_string[64] = {0};
166 sprintf(connect_string, "fd://%d", dup(server_running->open_client_socket()));
167
168 return mir::client::Connection{mir_connect_sync(connect_string, name.c_str())};
169}
170
171void miral::TestServer::invoke_tools(std::function<void(WindowManagerTools& tools)> const& f)
172{
173 tools.invoke_under_lock([&]{f(tools); });
174}
175
176void miral::TestServer::invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f)
177{
178 if (auto const wm = window_manager.lock())
179 f(*wm);
180 else
181 BOOST_THROW_EXCEPTION(std::runtime_error{"Server not running"});
182
183}
184
185void miral::TestRuntimeEnvironment::add_to_environment(char const* key, char const* value)
186{
187 env.emplace_back(key, value);
188}
189
190using miral::TestServer;
191
192// Minimal test to ensure the server runs and exits
193TEST_F(TestServer, connect_client_works)
194{
195 auto const connection = connect_client(__PRETTY_FUNCTION__);
196
197 EXPECT_TRUE(mir_connection_is_valid(connection));
198}
0199
=== added file 'tests/miral/test_server.h'
--- tests/miral/test_server.h 1970-01-01 00:00:00 +0000
+++ tests/miral/test_server.h 2017-08-24 15:19:58 +0000
@@ -0,0 +1,90 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#ifndef MIRAL_TEST_SERVER_H
20#define MIRAL_TEST_SERVER_H
21
22#include <mir/client/connection.h>
23
24#include <miral/canonical_window_manager.h>
25#include <miral/runner.h>
26#include <miral/window_manager_tools.h>
27
28#include <mir/test/auto_unblock_thread.h>
29#include <mir_test_framework/temporary_environment_value.h>
30
31#include <gtest/gtest.h>
32
33#include <condition_variable>
34#include <list>
35#include <mutex>
36
37namespace mir { namespace shell { class WindowManager; }}
38
39namespace miral
40{
41class WindowManagementPolicy;
42class TestRuntimeEnvironment
43{
44public:
45 void add_to_environment(char const* key, char const* value);
46
47private:
48 std::list<mir_test_framework::TemporaryEnvironmentValue> env;
49};
50
51struct TestServer : testing::Test, private TestRuntimeEnvironment
52{
53 TestServer();
54
55 void SetUp() override;
56 void TearDown() override;
57
58 auto connect_client(std::string name) -> mir::client::Connection;
59
60 using TestRuntimeEnvironment::add_to_environment;
61
62 MirRunner runner;
63
64 void invoke_tools(std::function<void(WindowManagerTools& tools)> const& f);
65 void invoke_window_manager(std::function<void(mir::shell::WindowManager& wm)> const& f);
66
67 struct TestWindowManagerPolicy;
68 virtual auto build_window_manager_policy(WindowManagerTools const& tools) -> std::unique_ptr<TestWindowManagerPolicy>;
69
70private:
71 WindowManagerTools tools{nullptr};
72 std::weak_ptr<mir::shell::WindowManager> window_manager;
73 mir::test::AutoJoinThread server_thread;
74 std::mutex mutex;
75 std::condition_variable started;
76 mir::Server* server_running{nullptr};
77};
78
79struct TestServer::TestWindowManagerPolicy : CanonicalWindowManagerPolicy
80{
81 TestWindowManagerPolicy(WindowManagerTools const& tools, TestServer& test_fixture);
82
83 bool handle_keyboard_event(MirKeyboardEvent const*) override { return false; }
84 bool handle_pointer_event(MirPointerEvent const*) override { return false; }
85 bool handle_touch_event(MirTouchEvent const*) override { return false; }
86};
87
88}
89
90#endif //MIRAL_TEST_SERVER_H
091
=== added file 'tests/miral/test_window_manager_tools.h'
--- tests/miral/test_window_manager_tools.h 1970-01-01 00:00:00 +0000
+++ tests/miral/test_window_manager_tools.h 2017-08-24 15:19:58 +0000
@@ -0,0 +1,197 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#ifndef MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
20#define MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
21
22#include "basic_window_manager.h"
23
24#include <miral/canonical_window_manager.h>
25
26#include <mir/scene/surface_creation_parameters.h>
27#include <mir/shell/display_layout.h>
28#include <mir/shell/focus_controller.h>
29#include <mir/shell/persistent_surface_store.h>
30#include <mir/version.h>
31
32#include <mir/test/doubles/stub_session.h>
33#include <mir/test/doubles/stub_surface.h>
34#include <mir/test/fake_shared.h>
35
36#include <gtest/gtest.h>
37#include <gmock/gmock.h>
38
39#include <atomic>
40
41struct StubFocusController : mir::shell::FocusController
42{
43 void focus_next_session() override {}
44
45 auto focused_session() const -> std::shared_ptr<mir::scene::Session> override { return {}; }
46
47 void set_focus_to(
48 std::shared_ptr<mir::scene::Session> const& /*focus_session*/,
49 std::shared_ptr<mir::scene::Surface> const& /*focus_surface*/) override {}
50
51 auto focused_surface() const -> std::shared_ptr<mir::scene::Surface> override { return {}; }
52
53 void raise(mir::shell::SurfaceSet const& /*windows*/) override {}
54
55 virtual auto surface_at(mir::geometry::Point /*cursor*/) const -> std::shared_ptr<mir::scene::Surface> override
56 { return {}; }
57
58#if MIR_SERVER_VERSION >= MIR_VERSION_NUMBER(0, 27, 0)
59 void set_drag_and_drop_handle(std::vector<uint8_t> const& /*handle*/) override {}
60
61 void clear_drag_and_drop_handle() override {}
62#endif
63};
64
65struct StubDisplayLayout : mir::shell::DisplayLayout
66{
67 void clip_to_output(mir::geometry::Rectangle& /*rect*/) override {}
68
69 void size_to_output(mir::geometry::Rectangle& /*rect*/) override {}
70
71 bool place_in_output(mir::graphics::DisplayConfigurationOutputId /*id*/, mir::geometry::Rectangle& /*rect*/) override
72 { return false; }
73};
74
75struct StubPersistentSurfaceStore : mir::shell::PersistentSurfaceStore
76{
77 Id id_for_surface(std::shared_ptr<mir::scene::Surface> const& /*surface*/) override { return {}; }
78
79 auto surface_for_id(Id const& /*id*/) const -> std::shared_ptr<mir::scene::Surface> override { return {}; }
80};
81
82struct StubSurface : mir::test::doubles::StubSurface
83{
84 StubSurface(std::string name, MirWindowType type, mir::geometry::Point top_left, mir::geometry::Size size) :
85 name_{name}, type_{type}, top_left_{top_left}, size_{size} {}
86
87 std::string name() const override { return name_; };
88 MirWindowType type() const override { return type_; }
89
90 mir::geometry::Point top_left() const override { return top_left_; }
91 void move_to(mir::geometry::Point const& top_left) override { top_left_ = top_left; }
92
93 mir::geometry::Size size() const override { return size_; }
94 void resize(mir::geometry::Size const& size) override { size_ = size; }
95
96 auto state() const -> MirWindowState override { return state_; }
97 auto configure(MirWindowAttrib attrib, int value) -> int override {
98 switch (attrib)
99 {
100 case mir_window_attrib_state:
101 state_ = MirWindowState(value);
102 return state_;
103 default:
104 return value;
105 }
106 }
107
108 bool visible() const override { return state() != mir_window_state_hidden; }
109
110 std::string name_;
111 MirWindowType type_;
112 mir::geometry::Point top_left_;
113 mir::geometry::Size size_;
114 MirWindowState state_ = mir_window_state_restored;
115};
116
117struct StubStubSession : mir::test::doubles::StubSession
118{
119 mir::frontend::SurfaceId create_surface(
120 mir::scene::SurfaceCreationParameters const& params,
121 std::shared_ptr<mir::frontend::EventSink> const& /*sink*/) override
122 {
123 auto id = mir::frontend::SurfaceId{next_surface_id.fetch_add(1)};
124 auto surface = std::make_shared<StubSurface>(params.name, params.type.value(), params.top_left, params.size);
125 surfaces[id] = surface;
126 return id;
127 }
128
129 std::shared_ptr<mir::scene::Surface> surface(mir::frontend::SurfaceId surface) const override
130 {
131 return surfaces.at(surface);
132 }
133
134private:
135 std::atomic<int> next_surface_id;
136 std::map<mir::frontend::SurfaceId, std::shared_ptr<mir::scene::Surface>> surfaces;
137};
138
139struct MockWindowManagerPolicy : miral::CanonicalWindowManagerPolicy
140{
141 using miral::CanonicalWindowManagerPolicy::CanonicalWindowManagerPolicy;
142
143 bool handle_touch_event(MirTouchEvent const* /*event*/) { return false; }
144 bool handle_pointer_event(MirPointerEvent const* /*event*/) { return false; }
145 bool handle_keyboard_event(MirKeyboardEvent const* /*event*/) { return false; }
146
147 MOCK_METHOD1(advise_new_window, void (miral::WindowInfo const& window_info));
148 MOCK_METHOD2(advise_move_to, void(miral::WindowInfo const& window_info, mir::geometry::Point top_left));
149 MOCK_METHOD2(advise_resize, void(miral::WindowInfo const& window_info, mir::geometry::Size const& new_size));
150 MOCK_METHOD1(advise_raise, void(std::vector<miral::Window> const&));
151};
152
153struct StubDisplayConfigurationObserver : mir::ObserverRegistrar<mir::graphics::DisplayConfigurationObserver>
154{
155 void register_interest(std::weak_ptr<mir::graphics::DisplayConfigurationObserver> const&) override {}
156
157 void register_interest(std::weak_ptr<mir::graphics::DisplayConfigurationObserver> const&, mir::Executor&) override {}
158
159 void unregister_interest(mir::graphics::DisplayConfigurationObserver const&) override {}
160};
161
162struct TestWindowManagerTools : testing::Test
163{
164 StubFocusController focus_controller;
165 StubDisplayLayout display_layout;
166 StubPersistentSurfaceStore persistent_surface_store;
167 StubDisplayConfigurationObserver display_configuration_observer;
168 std::shared_ptr<StubStubSession> session{std::make_shared<StubStubSession>()};
169
170 MockWindowManagerPolicy* window_manager_policy{nullptr};
171 miral::WindowManagerTools window_manager_tools{nullptr};
172
173 miral::BasicWindowManager basic_window_manager{
174 &focus_controller,
175 mir::test::fake_shared(display_layout),
176 mir::test::fake_shared(persistent_surface_store),
177 display_configuration_observer,
178 [this](miral::WindowManagerTools const& tools) -> std::unique_ptr<miral::WindowManagementPolicy>
179 {
180 auto policy = std::make_unique<testing::NiceMock<MockWindowManagerPolicy>>(tools);
181 window_manager_policy = policy.get();
182 window_manager_tools = tools;
183 return std::move(policy);
184 }
185 };
186
187 static auto create_surface(
188 std::shared_ptr<mir::scene::Session> const& session,
189 mir::scene::SurfaceCreationParameters const& params) -> mir::frontend::SurfaceId
190 {
191 // This type is Mir-internal, I hope we don't need to create it here
192 std::shared_ptr<mir::frontend::EventSink> const sink;
193 return session->create_surface(params, sink);
194 }
195};
196
197#endif //MIRAL_TEST_WINDOW_MANAGER_TOOLS_H
0198
=== added file 'tests/miral/window_id.cpp'
--- tests/miral/window_id.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/window_id.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,114 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <mir/client/window_id.h>
20#include <mir/client/window.h>
21#include <mir/client/window_spec.h>
22
23#include <miral/application_info.h>
24
25#include <mir/version.h>
26
27#include <gtest/gtest.h>
28#include <gmock/gmock.h>
29
30#include "test_server.h"
31
32using namespace testing;
33
34
35struct WindowId : public miral::TestServer
36{
37 auto get_first_window(miral::WindowManagerTools& tools) -> miral::Window
38 {
39 auto app = tools.find_application([&](miral::ApplicationInfo const& /*info*/){return true;});
40 auto app_info = tools.info_for(app);
41 return app_info.windows().at(0);
42 }
43};
44
45TEST_F(WindowId, server_can_identify_window_specified_by_client)
46{
47 char const* const test_name = __PRETTY_FUNCTION__;
48 using namespace mir::client;
49
50 auto const connection = connect_client(test_name);
51 auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
52 .set_name(test_name);
53
54 Window const surface{spec.create_window()};
55
56 mir::client::WindowId client_surface_id{surface};
57
58 invoke_tools([&](miral::WindowManagerTools& tools)
59 {
60 auto const& window_info = tools.info_for_window_id(client_surface_id.c_str());
61
62 ASSERT_TRUE(window_info.window());
63 ASSERT_THAT(window_info.name(), Eq(test_name));
64 });
65}
66
67TEST_F(WindowId, server_returns_correct_id_for_window)
68{
69 char const* const test_name = __PRETTY_FUNCTION__;
70 using namespace mir::client;
71
72 auto const connection = connect_client(test_name);
73 auto const spec = WindowSpec::for_normal_window(connection, 50, 50)
74 .set_name(test_name);
75
76 Window const surface{spec.create_window()};
77
78 mir::client::WindowId client_surface_id{surface};
79
80 invoke_tools([&](miral::WindowManagerTools& tools)
81 {
82 auto window = get_first_window(tools);
83 auto id = tools.id_for_window(window);
84
85 ASSERT_THAT(client_surface_id.c_str(), Eq(id));
86 });
87}
88
89TEST_F(WindowId, server_fails_gracefully_to_identify_window_from_garbage_id)
90{
91 char const* const test_name = __PRETTY_FUNCTION__;
92 using namespace mir::client;
93
94 auto const connection = connect_client(test_name);
95 auto const spec = WindowSpec::for_normal_window(connection, 50, 50).set_name(test_name);
96
97 Window const surface{spec.create_window()};
98
99 mir::client::WindowId client_surface_id{surface};
100
101 invoke_tools([](miral::WindowManagerTools& tools)
102 {
103 EXPECT_THROW(tools.info_for_window_id("garbage"), std::exception);
104 });
105}
106
107TEST_F(WindowId, server_fails_gracefully_when_id_for_null_window_requested)
108{
109 invoke_tools([](miral::WindowManagerTools& tools)
110 {
111 miral::Window window;
112 EXPECT_THROW(tools.id_for_window(window), std::runtime_error);
113 });
114}
0115
=== added file 'tests/miral/window_placement.cpp'
--- tests/miral/window_placement.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/window_placement.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,554 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20
21using namespace miral;
22using namespace testing;
23namespace mt = mir::test;
24
25namespace
26{
27X const display_left{0};
28Y const display_top{0};
29Width const display_width{640};
30Height const display_height{480};
31
32Rectangle const display_area{{display_left, display_top}, {display_width, display_height}};
33
34auto const null_window = Window{};
35
36mir::shell::SurfaceSpecification edge_attachment(Rectangle const& aux_rect, MirEdgeAttachment attachment)
37{
38 mir::shell::SurfaceSpecification result;
39 result.aux_rect = aux_rect;
40 result.edge_attachment = attachment;
41 return result;
42}
43
44struct WindowPlacement : TestWindowManagerTools
45{
46 Size const initial_parent_size{600, 400};
47 Size const initial_child_size{300, 300};
48 Rectangle const rectangle_away_from_rhs{{20, 20}, {20, 20}};
49 Rectangle const rectangle_near_rhs{{590, 20}, {10, 20}};
50 Rectangle const rectangle_away_from_bottom{{20, 20}, {20, 20}};
51 Rectangle const rectangle_near_bottom{{20, 380}, {20, 20}};
52 Rectangle const rectangle_near_both_sides{{0, 20}, {600, 20}};
53 Rectangle const rectangle_near_both_sides_and_bottom{{0, 380}, {600, 20}};
54 Rectangle const rectangle_near_all_sides{{0, 20}, {600, 380}};
55 Rectangle const rectangle_near_both_bottom_right{{400, 380}, {200, 20}};
56
57 Window parent;
58 Window child;
59
60 WindowSpecification modification;
61
62 void SetUp() override
63 {
64 basic_window_manager.add_display_for_testing(display_area);
65
66 mir::scene::SurfaceCreationParameters creation_parameters;
67 basic_window_manager.add_session(session);
68
69 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
70 .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
71 .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }));
72
73 creation_parameters.size = initial_parent_size;
74 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
75
76 creation_parameters.type = mir_window_type_menu;
77 creation_parameters.parent = parent;
78 creation_parameters.size = initial_child_size;
79 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
80
81 // Clear the expectations used to capture parent & child
82 Mock::VerifyAndClearExpectations(window_manager_policy);
83 }
84
85 void TearDown() override
86 {
87// std::cerr << "DEBUG parent position:" << Rectangle{parent.top_left(), parent.size()} << '\n';
88// std::cerr << "DEBUG child position :" << Rectangle{child.top_left(), child.size()} << '\n';
89 }
90
91 auto aux_rect_position() -> Rectangle
92 {
93 auto const rectangle = modification.aux_rect().value();
94 return {rectangle.top_left + (parent.top_left() - Point{}), rectangle.size};
95 }
96
97 auto on_top_edge() -> Point
98 {
99 return aux_rect_position().top_left - as_displacement(child.size()).dy;
100 }
101
102 auto on_right_edge() -> Point
103 {
104 return aux_rect_position().top_right();
105 }
106
107 auto on_left_edge() -> Point
108 {
109 return aux_rect_position().top_left - as_displacement(child.size()).dx;
110 }
111
112 auto on_bottom_edge() -> Point
113 {
114 return aux_rect_position().bottom_left();
115 }
116};
117}
118
119TEST_F(WindowPlacement, fixture_sets_up_parent_and_child)
120{
121 ASSERT_THAT(parent, Ne(null_window));
122 ASSERT_THAT(parent.size(), Eq(initial_parent_size));
123 ASSERT_THAT(basic_window_manager.info_for(parent).children(), ElementsAre(child));
124 ASSERT_THAT(basic_window_manager.info_for(parent).type(), Eq(mir_window_type_normal));
125
126 ASSERT_THAT(child, Ne(null_window));
127 ASSERT_THAT(child.size(), Eq(initial_child_size));
128 ASSERT_THAT(basic_window_manager.info_for(child).parent(), Eq(parent));
129 ASSERT_THAT(basic_window_manager.info_for(child).type(), Eq(mir_window_type_menu));
130}
131
132
133/* From the Mir client API:
134 * Positioning of the surface is specified with respect to the parent surface
135 * via an adjacency rectangle. The server will attempt to choose an edge of the
136 * adjacency rectangle on which to place the surface taking in to account
137 * screen-edge proximity or similar constraints. In addition, the server can use
138 * the edge affinity hint to consider only horizontal or only vertical adjacency
139 * edges in the given rectangle.
140 */
141
142TEST_F(WindowPlacement, given_aux_rect_away_from_right_side_edge_attachment_vertical_attaches_to_right_edge)
143{
144 modification = edge_attachment(rectangle_away_from_rhs, mir_edge_attachment_vertical);
145
146 auto const expected_position = on_right_edge();
147
148 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
149 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
150 ASSERT_THAT(child.top_left(), Eq(expected_position));
151}
152
153TEST_F(WindowPlacement, given_aux_rect_near_right_side_edge_attachment_vertical_attaches_to_left_edge)
154{
155 modification = edge_attachment(rectangle_near_rhs, mir_edge_attachment_vertical);
156
157 auto const expected_position = on_left_edge();
158
159 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
160 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
161 ASSERT_THAT(child.top_left(), Eq(expected_position));
162}
163
164TEST_F(WindowPlacement, given_aux_rect_near_both_sides_edge_attachment_vertical_attaches_to_right_edge)
165{
166 modification = edge_attachment(rectangle_near_both_sides, mir_edge_attachment_vertical);
167
168 auto const expected_position = on_right_edge();
169
170 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
171 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
172 ASSERT_THAT(child.top_left(), Eq(expected_position));
173}
174
175TEST_F(WindowPlacement, given_aux_rect_away_from_bottom_edge_attachment_horizontal_attaches_to_bottom_edge)
176{
177 modification = edge_attachment(rectangle_away_from_bottom, mir_edge_attachment_horizontal);
178
179 auto const expected_position = on_bottom_edge();
180
181 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
182 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
183 ASSERT_THAT(child.top_left(), Eq(expected_position));
184}
185
186TEST_F(WindowPlacement, given_aux_rect_near_bottom_edge_attachment_horizontal_attaches_to_top_edge)
187{
188 modification = edge_attachment(rectangle_near_bottom, mir_edge_attachment_horizontal);
189
190 auto const expected_position = on_top_edge();
191
192 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
193 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
194 ASSERT_THAT(child.top_left(), Eq(expected_position));
195}
196
197TEST_F(WindowPlacement, given_aux_rect_near_both_sides_edge_attachment_any_attaches_to_bottom_edge)
198{
199 modification = edge_attachment(rectangle_near_both_sides, mir_edge_attachment_any);
200
201 auto const expected_position = on_bottom_edge();
202
203 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
204 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
205 ASSERT_THAT(child.top_left(), Eq(expected_position));
206}
207
208TEST_F(WindowPlacement, given_aux_rect_near_both_sides_and_bottom_edge_attachment_any_attaches_to_top_edge)
209{
210 modification = edge_attachment(rectangle_near_both_sides_and_bottom, mir_edge_attachment_any);
211
212 auto const expected_position = on_top_edge();
213
214 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
215 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
216 ASSERT_THAT(child.top_left(), Eq(expected_position));
217}
218
219TEST_F(WindowPlacement, given_aux_rect_near_all_sides_attachment_any_attaches_to_right_edge)
220{
221 modification = edge_attachment(rectangle_near_all_sides, mir_edge_attachment_any);
222
223 auto const expected_position = on_right_edge();
224
225 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
226 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
227 ASSERT_THAT(child.top_left(), Eq(expected_position));
228}
229
230namespace
231{
232MirPlacementGravity const all_gravities[] =
233{
234 mir_placement_gravity_northwest,
235 mir_placement_gravity_north,
236 mir_placement_gravity_northeast,
237 mir_placement_gravity_west,
238 mir_placement_gravity_center,
239 mir_placement_gravity_east,
240 mir_placement_gravity_southwest,
241 mir_placement_gravity_south,
242 mir_placement_gravity_southeast,
243};
244
245auto position_of(MirPlacementGravity rect_gravity, Rectangle rectangle) -> Point
246{
247 auto const displacement = as_displacement(rectangle.size);
248
249 switch (rect_gravity)
250 {
251 case mir_placement_gravity_northwest:
252 return rectangle.top_left;
253
254 case mir_placement_gravity_north:
255 return rectangle.top_left + Displacement{0.5 * displacement.dx, 0};
256
257 case mir_placement_gravity_northeast:
258 return rectangle.top_right();
259
260 case mir_placement_gravity_west:
261 return rectangle.top_left + Displacement{0, 0.5 * displacement.dy};
262
263 case mir_placement_gravity_center:
264 return rectangle.top_left + 0.5 * displacement;
265
266 case mir_placement_gravity_east:
267 return rectangle.top_right() + Displacement{0, 0.5 * displacement.dy};
268
269 case mir_placement_gravity_southwest:
270 return rectangle.bottom_left();
271
272 case mir_placement_gravity_south:
273 return rectangle.bottom_left() + Displacement{0.5 * displacement.dx, 0};
274
275 case mir_placement_gravity_southeast:
276 return rectangle.bottom_right();
277
278 default:
279 throw std::runtime_error("bad placement gravity");
280 }
281
282}
283}
284
285TEST_F(WindowPlacement, given_no_hints_can_attach_by_every_gravity)
286{
287 modification.aux_rect() = Rectangle{{100, 50}, { 20, 20}};
288 modification.placement_hints() = MirPlacementHints{};
289
290 for (auto const rect_gravity : all_gravities)
291 {
292 modification.aux_rect_placement_gravity() = rect_gravity;
293
294 auto const rect_anchor = position_of(rect_gravity, aux_rect_position());
295
296 for (auto const window_gravity : all_gravities)
297 {
298 modification.window_placement_gravity() = window_gravity;
299
300 EXPECT_CALL(*window_manager_policy, advise_move_to(_, _));
301 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
302
303 Rectangle child_rect{child.top_left(), child.size()};
304
305 EXPECT_THAT(position_of(window_gravity, child_rect), Eq(rect_anchor))
306 << "rect_gravity=" << rect_gravity << ", window_gravity=" << window_gravity;
307 Mock::VerifyAndClearExpectations(window_manager_policy);
308 }
309 }
310}
311
312TEST_F(WindowPlacement, given_no_hints_can_attach_by_offset_at_every_gravity)
313{
314 auto const offset = Displacement{42, 13};
315
316 modification.aux_rect() = Rectangle{{100, 50}, { 20, 20}};
317 modification.placement_hints() = MirPlacementHints{};
318 modification.aux_rect_placement_offset() = offset;
319
320 for (auto const rect_gravity : all_gravities)
321 {
322 modification.aux_rect_placement_gravity() = rect_gravity;
323
324 auto const rect_anchor = position_of(rect_gravity, aux_rect_position()) + offset;
325
326 for (auto const window_gravity : all_gravities)
327 {
328 modification.window_placement_gravity() = window_gravity;
329
330 EXPECT_CALL(*window_manager_policy, advise_move_to(_, _));
331 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
332
333 Rectangle child_rect{child.top_left(), child.size()};
334
335 EXPECT_THAT(position_of(window_gravity, child_rect), Eq(rect_anchor))
336 << "rect_gravity=" << rect_gravity << ", window_gravity=" << window_gravity;
337 Mock::VerifyAndClearExpectations(window_manager_policy);
338 }
339 }
340}
341
342TEST_F(WindowPlacement, given_aux_rect_near_right_side_and_offset_placement_is_flipped)
343{
344 DeltaX const x_offset{42};
345 DeltaY const y_offset{13};
346
347 modification.aux_rect() = rectangle_near_rhs;
348 modification.placement_hints() = mir_placement_hints_flip_x;
349 modification.aux_rect_placement_offset() = Displacement{x_offset, y_offset};
350 modification.window_placement_gravity() = mir_placement_gravity_northwest;
351 modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
352
353 auto const expected_position = on_left_edge() + Displacement{-1*x_offset, y_offset};
354
355 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
356 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
357 ASSERT_THAT(child.top_left(), Eq(expected_position));
358}
359
360TEST_F(WindowPlacement, given_aux_rect_near_bottom_and_offset_placement_is_flipped)
361{
362 DeltaX const x_offset{42};
363 DeltaY const y_offset{13};
364
365 modification.aux_rect() = rectangle_near_bottom;
366 modification.placement_hints() = mir_placement_hints_flip_y;
367 modification.aux_rect_placement_offset() = Displacement{x_offset, y_offset};
368 modification.window_placement_gravity() = mir_placement_gravity_northwest;
369 modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
370
371 auto const expected_position = on_top_edge() + Displacement{x_offset, -1*y_offset};
372
373 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
374 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
375 ASSERT_THAT(child.top_left(), Eq(expected_position));
376}
377
378TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_is_flipped_both_ways)
379{
380 Displacement const displacement{42, 13};
381
382 modification.aux_rect() = rectangle_near_both_bottom_right;
383 modification.placement_hints() = mir_placement_hints_flip_any;
384 modification.aux_rect_placement_offset() = displacement;
385 modification.window_placement_gravity() = mir_placement_gravity_northwest;
386 modification.aux_rect_placement_gravity() = mir_placement_gravity_southeast;
387
388 auto const expected_position = aux_rect_position().top_left - as_displacement(child.size()) - displacement;
389
390 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
391 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
392 ASSERT_THAT(child.top_left(), Eq(expected_position));
393}
394
395TEST_F(WindowPlacement, given_aux_rect_near_right_side_placement_can_slide_in_x)
396{
397 modification.aux_rect() = rectangle_near_rhs;
398 modification.placement_hints() = mir_placement_hints_slide_x;
399 modification.window_placement_gravity() = mir_placement_gravity_northwest;
400 modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
401
402 Point const expected_position{display_area.top_right().x - as_displacement(child.size()).dx, aux_rect_position().top_left.y};
403
404 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
405 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
406 ASSERT_THAT(child.top_left(), Eq(expected_position));
407}
408
409TEST_F(WindowPlacement, given_aux_rect_near_left_side_placement_can_slide_in_x)
410{
411 Rectangle const rectangle_near_left_side{{0, 20}, {20, 20}};
412
413 modification.aux_rect() = rectangle_near_left_side;
414 modification.placement_hints() = mir_placement_hints_slide_x;
415 modification.window_placement_gravity() = mir_placement_gravity_northeast;
416 modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
417
418 Point const expected_position{display_area.top_left.x, aux_rect_position().top_left.y};
419
420 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
421 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
422 ASSERT_THAT(child.top_left(), Eq(expected_position));
423}
424
425TEST_F(WindowPlacement, given_aux_rect_near_bottom_placement_can_slide_in_y)
426{
427 modification.aux_rect() = rectangle_near_bottom;
428 modification.placement_hints() = mir_placement_hints_slide_y;
429 modification.window_placement_gravity() = mir_placement_gravity_northwest;
430 modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
431
432 Point const expected_position{
433 aux_rect_position().top_left.x,
434 (display_area.bottom_left() - as_displacement(child.size())).y};
435
436 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
437 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
438 ASSERT_THAT(child.top_left(), Eq(expected_position));
439}
440
441TEST_F(WindowPlacement, given_aux_rect_near_top_placement_can_slide_in_y)
442{
443 modification.aux_rect() = rectangle_near_all_sides;
444 modification.placement_hints() = mir_placement_hints_slide_y;
445 modification.window_placement_gravity() = mir_placement_gravity_southwest;
446 modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
447
448 Point const expected_position{aux_rect_position().top_left.x, display_area.top_left.y};
449
450 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
451 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
452 ASSERT_THAT(child.top_left(), Eq(expected_position));
453}
454
455TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_can_slide_in_x_and_y)
456{
457 modification.aux_rect() = rectangle_near_both_bottom_right;
458 modification.placement_hints() = mir_placement_hints_slide_any;
459 modification.window_placement_gravity() = mir_placement_gravity_northwest;
460 modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
461
462 auto const expected_position = display_area.bottom_right() - as_displacement(child.size());
463
464 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
465 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
466 ASSERT_THAT(child.top_left(), Eq(expected_position));
467}
468
469TEST_F(WindowPlacement, given_aux_rect_near_right_side_placement_can_resize_in_x)
470{
471 modification.aux_rect() = rectangle_near_rhs;
472 modification.placement_hints() = mir_placement_hints_resize_x;
473 modification.window_placement_gravity() = mir_placement_gravity_northwest;
474 modification.aux_rect_placement_gravity() = mir_placement_gravity_northeast;
475
476 auto const expected_position = aux_rect_position().top_right();
477 Size const expected_size{as_size(display_area.bottom_right()-aux_rect_position().bottom_right()).width, child.size().height};
478
479 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
480 EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
481 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
482 ASSERT_THAT(child.top_left(), Eq(expected_position));
483 ASSERT_THAT(child.size(), Eq(expected_size));
484}
485
486TEST_F(WindowPlacement, given_aux_rect_near_left_side_placement_can_resize_in_x)
487{
488 Rectangle const rectangle_near_left_side{{0, 20}, {20, 20}};
489
490 modification.aux_rect() = rectangle_near_left_side;
491 modification.placement_hints() = mir_placement_hints_resize_x;
492 modification.window_placement_gravity() = mir_placement_gravity_northeast;
493 modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
494
495 Point const expected_position{display_area.top_left.x, aux_rect_position().top_left.y};
496 Size const expected_size{as_size(aux_rect_position().top_left-display_area.top_left).width, child.size().height};
497
498 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
499 EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
500 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
501 ASSERT_THAT(child.top_left(), Eq(expected_position));
502 ASSERT_THAT(child.size(), Eq(expected_size));
503}
504
505TEST_F(WindowPlacement, given_aux_rect_near_bottom_placement_can_resize_in_y)
506{
507 modification.aux_rect() = rectangle_near_bottom;
508 modification.placement_hints() = mir_placement_hints_resize_y;
509 modification.window_placement_gravity() = mir_placement_gravity_northwest;
510 modification.aux_rect_placement_gravity() = mir_placement_gravity_southwest;
511
512 auto const expected_position = aux_rect_position().bottom_left();
513 Size const expected_size{child.size().width, as_size(display_area.bottom_left()-aux_rect_position().bottom_left()).height};
514
515 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
516 EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
517 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
518 ASSERT_THAT(child.top_left(), Eq(expected_position));
519 ASSERT_THAT(child.size(), Eq(expected_size));
520}
521
522TEST_F(WindowPlacement, given_aux_rect_near_top_placement_can_resize_in_y)
523{
524 modification.aux_rect() = rectangle_near_all_sides;
525 modification.placement_hints() = mir_placement_hints_resize_y;
526 modification.window_placement_gravity() = mir_placement_gravity_southwest;
527 modification.aux_rect_placement_gravity() = mir_placement_gravity_northwest;
528
529 Point const expected_position{aux_rect_position().top_left.x, display_area.top_left.y};
530 Size const expected_size{child.size().width, as_size(aux_rect_position().top_left-display_area.top_left).height};
531
532 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
533 EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
534 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
535 ASSERT_THAT(child.top_left(), Eq(expected_position));
536 ASSERT_THAT(child.size(), Eq(expected_size));
537}
538
539TEST_F(WindowPlacement, given_aux_rect_near_bottom_right_and_offset_placement_can_resize_in_x_and_y)
540{
541 modification.aux_rect() = rectangle_near_both_bottom_right;
542 modification.placement_hints() = mir_placement_hints_resize_any;
543 modification.window_placement_gravity() = mir_placement_gravity_northwest;
544 modification.aux_rect_placement_gravity() = mir_placement_gravity_southeast;
545
546 auto const expected_position = aux_rect_position().bottom_right();
547 auto const expected_size = as_size(display_area.bottom_right()-expected_position);
548
549 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
550 EXPECT_CALL(*window_manager_policy, advise_resize(_, expected_size));
551 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
552 ASSERT_THAT(child.top_left(), Eq(expected_position));
553 ASSERT_THAT(child.size(), Eq(expected_size));
554}
0555
=== added file 'tests/miral/window_placement_anchors_to_parent.cpp'
--- tests/miral/window_placement_anchors_to_parent.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/window_placement_anchors_to_parent.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,208 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "test_window_manager_tools.h"
20
21using namespace miral;
22using namespace testing;
23namespace mt = mir::test;
24
25namespace
26{
27auto const display_area = Rectangle{{0, 0}, {800, 600}};
28auto const parent_width = 400;
29auto const parent_height = 300;
30
31auto placement(
32 Rectangle const& aux_rect,
33 MirPlacementGravity aux_rect_placement_gravity,
34 MirPlacementGravity window_placement_gravity,
35 MirPlacementHints placement_hints) -> WindowSpecification
36{
37 WindowSpecification modification;
38
39 modification.aux_rect() = aux_rect;
40 modification.aux_rect_placement_gravity() = aux_rect_placement_gravity;
41 modification.window_placement_gravity() = window_placement_gravity;
42 modification.placement_hints() = placement_hints;
43
44 return modification;
45}
46
47struct WindowPlacementAnchorsToParent : TestWindowManagerTools
48{
49 Size const parent_size{parent_width, parent_height};
50 Size const initial_child_size{100, 50};
51
52 Window parent;
53 Window child;
54
55 Point parent_position;
56 WindowSpecification modification;
57
58 void SetUp() override
59 {
60 TestWindowManagerTools::SetUp();
61
62 basic_window_manager.add_display_for_testing(display_area);
63
64 mir::scene::SurfaceCreationParameters creation_parameters;
65 basic_window_manager.add_session(session);
66
67 EXPECT_CALL(*window_manager_policy, advise_new_window(_))
68 .WillOnce(Invoke([this](WindowInfo const& window_info){ parent = window_info.window(); }))
69 .WillOnce(Invoke([this](WindowInfo const& window_info){ child = window_info.window(); }));
70
71 creation_parameters.size = parent_size;
72 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
73
74 creation_parameters.type = mir_window_type_tip;
75 creation_parameters.parent = parent;
76 creation_parameters.size = initial_child_size;
77 basic_window_manager.add_surface(session, creation_parameters, &create_surface);
78
79 // Clear the expectations used to capture parent & child
80 Mock::VerifyAndClearExpectations(window_manager_policy);
81
82 parent_position = parent.top_left();
83 }
84};
85}
86
87// there was an IRC conversation to sort this out between myself William and Thomas.
88// I think the resulting consensus was:
89//
90// 1. Mir will constrain the placement anchor of the aux_rect to the parent
91// surface. I don't think we agreed exactly how (e.g. do we "clip" the
92// rect? What happens if there is *no* intersection?)
93//
94// 2. Mir will constrain the the offset placement anchor to the parent surface.
95// Again I don't think we agreed how. (Slide it horizontally and/or vertically
96// the minimum amount?)
97// - alan_g (mir-devel, Mon, 5 Sep 2016 17:21:01 +0100)
98//
99// What we have implemented is to constrain the result of offsetting to the parent. That
100// seems to provide reasonable behaviour. Are there test cases that require something more?
101
102TEST_F(WindowPlacementAnchorsToParent, given_rect_anchor_right_of_parent_client_is_anchored_to_parent)
103{
104 auto const rect_size = 10;
105 Rectangle const overlapping_right{{parent_width-rect_size/2, parent_height/2}, {rect_size, rect_size}};
106
107 modification = placement(
108 overlapping_right,
109 mir_placement_gravity_northeast,
110 mir_placement_gravity_northwest,
111 MirPlacementHints(mir_placement_hints_slide_y|mir_placement_hints_resize_x));
112
113 auto const expected_position = parent_position + Displacement{parent_width, parent_height/2};
114
115 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
116 EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
117 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
118 ASSERT_THAT(child.top_left(), Eq(expected_position));
119 ASSERT_THAT(child.size(), Eq(initial_child_size));
120}
121
122TEST_F(WindowPlacementAnchorsToParent, given_rect_anchor_above_parent_client_is_anchored_to_parent)
123{
124 auto const rect_size = 10;
125 Rectangle const overlapping_above{{parent_width/2, -rect_size/2}, {rect_size, rect_size}};
126
127 modification = placement(
128 overlapping_above,
129 mir_placement_gravity_northeast,
130 mir_placement_gravity_southeast,
131 mir_placement_hints_slide_x);
132
133 auto const expected_position = parent_position + DeltaX{parent_width/2 + rect_size}
134 - as_displacement(initial_child_size);
135
136 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
137 EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
138 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
139 ASSERT_THAT(child.top_left(), Eq(expected_position));
140 ASSERT_THAT(child.size(), Eq(initial_child_size));
141}
142
143TEST_F(WindowPlacementAnchorsToParent, given_offset_right_of_parent_client_is_anchored_to_parent)
144{
145 auto const rect_size = 10;
146 Rectangle const mid_right{{parent_width-rect_size, parent_height/2}, {rect_size, rect_size}};
147
148 modification = placement(
149 mid_right,
150 mir_placement_gravity_northeast,
151 mir_placement_gravity_northwest,
152 MirPlacementHints(mir_placement_hints_slide_y|mir_placement_hints_resize_x));
153
154 modification.aux_rect_placement_offset() = Displacement{rect_size, 0};
155
156 auto const expected_position = parent_position + Displacement{parent_width, parent_height/2};
157
158 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
159 EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
160 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
161 ASSERT_THAT(child.top_left(), Eq(expected_position));
162 ASSERT_THAT(child.size(), Eq(initial_child_size));
163}
164
165TEST_F(WindowPlacementAnchorsToParent, given_offset_above_parent_client_is_anchored_to_parent)
166{
167 auto const rect_size = 10;
168 Rectangle const mid_top{{parent_width/2, 0}, {rect_size, rect_size}};
169
170 modification = placement(
171 mid_top,
172 mir_placement_gravity_northeast,
173 mir_placement_gravity_southeast,
174 mir_placement_hints_slide_x);
175
176 modification.aux_rect_placement_offset() = Displacement{0, -rect_size};
177
178 auto const expected_position = parent_position + DeltaX{parent_width/2 + rect_size}
179 - as_displacement(initial_child_size);
180
181 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
182 EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
183 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
184 ASSERT_THAT(child.top_left(), Eq(expected_position));
185 ASSERT_THAT(child.size(), Eq(initial_child_size));
186}
187
188TEST_F(WindowPlacementAnchorsToParent, given_rect_and_offset_below_left_parent_client_is_anchored_to_parent)
189{
190 auto const rect_size = 10;
191 Rectangle const below_left{{-rect_size, parent_height}, {rect_size, rect_size}};
192
193 modification = placement(
194 below_left,
195 mir_placement_gravity_southwest,
196 mir_placement_gravity_northeast,
197 mir_placement_hints_resize_any);
198
199 modification.aux_rect_placement_offset() = Displacement{-rect_size, rect_size};
200
201 auto const expected_position = parent_position + DeltaY{parent_height} - as_displacement(initial_child_size).dx;
202
203 EXPECT_CALL(*window_manager_policy, advise_move_to(_, expected_position));
204 EXPECT_CALL(*window_manager_policy, advise_resize(_, _)).Times(0);
205 basic_window_manager.modify_window(basic_window_manager.info_for(child), modification);
206 ASSERT_THAT(child.top_left(), Eq(expected_position));
207 ASSERT_THAT(child.size(), Eq(initial_child_size));
208}
0209
=== added file 'tests/miral/window_placement_client_api.cpp'
--- tests/miral/window_placement_client_api.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/window_placement_client_api.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,141 @@
1/*
2 * Copyright © 2016 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <mir_toolkit/events/window_placement.h>
20
21#include <mir/client/window_spec.h>
22#include <mir/client/window.h>
23
24#include <mir/test/signal.h>
25#include "test_server.h"
26
27#include <gtest/gtest.h>
28#include <gmock/gmock.h>
29
30using namespace std::literals::chrono_literals;
31
32using namespace testing;
33namespace mt = mir::test;
34namespace mtf = mir_test_framework;
35
36using namespace mir::client;
37
38namespace
39{
40struct WindowPlacementClientAPI : miral::TestServer
41{
42 void SetUp() override
43 {
44 miral::TestServer::SetUp();
45
46 char const* const test_name = __PRETTY_FUNCTION__;
47
48 connection = connect_client(test_name);
49 auto spec = WindowSpec::for_normal_window(connection, 400, 400)
50 .set_name(test_name);
51
52 parent = spec.create_window();
53 }
54
55 void TearDown() override
56 {
57 child.reset();
58 parent.reset();
59 connection.reset();
60
61 miral::TestServer::TearDown();
62 }
63
64 Connection connection;
65 Window parent;
66 Window child;
67};
68}
69
70namespace
71{
72struct CheckPlacement
73{
74 CheckPlacement(int left, int top, unsigned int width, unsigned int height) :
75 expected{left, top, width, height} {}
76
77 void check(MirWindowPlacementEvent const* placement_event)
78 {
79 auto relative_position = mir_window_placement_get_relative_position(placement_event);
80
81 EXPECT_THAT(relative_position.top, Eq(expected.top));
82 EXPECT_THAT(relative_position.left, Eq(expected.left));
83 EXPECT_THAT(relative_position.height, Eq(expected.height));
84 EXPECT_THAT(relative_position.width, Eq(expected.width));
85
86 received.raise();
87 }
88
89 static void callback(MirWindow* /*surface*/, MirEvent const* event, void* context)
90 {
91 if (mir_event_get_type(event) == mir_event_type_window_placement)
92 {
93 auto const placement_event = mir_event_get_window_placement_event(event);
94 static_cast<CheckPlacement*>(context)->check(placement_event);
95 }
96 }
97
98 ~CheckPlacement()
99 {
100 EXPECT_TRUE(received.wait_for(400ms));
101 }
102
103private:
104 MirRectangle const expected;
105 mt::Signal received;
106};
107}
108
109// It would be nice to verify creation and movement placement notifications in separate tests,
110// However, to avoid a racy test, we need to detect both anyway. This seems like a good trade-off.
111TEST_F(WindowPlacementClientAPI, given_menu_placements_away_from_edges_when_notified_result_is_as_requested)
112{
113 char const* const test_name = __PRETTY_FUNCTION__;
114 int const dx = 30;
115 int const dy = 40;
116
117 // initial placement
118 {
119 MirRectangle aux_rect{10, 20, 3, 4};
120 CheckPlacement expected{aux_rect.left+(int)aux_rect.width, aux_rect.top, dx, dy};
121
122 auto const spec = WindowSpec::
123 for_menu(connection, dx, dy, parent, &aux_rect, mir_edge_attachment_any)
124 .set_event_handler(&CheckPlacement::callback, &expected)
125 .set_name(test_name);
126
127 child = spec.create_window();
128 }
129
130 // subsequent movement
131 {
132 MirRectangle aux_rect{50, 60, 5, 7};
133 CheckPlacement expected{aux_rect.left-dx, aux_rect.top, dx, dy};
134
135 auto const spec = WindowSpec::for_changes(connection)
136 .set_event_handler(&CheckPlacement::callback, &expected)
137 .set_placement(&aux_rect, mir_placement_gravity_northwest, mir_placement_gravity_northeast, mir_placement_hints_flip_x, 0, 0);
138
139 spec.apply_to(child);
140 }
141}
0142
=== added file 'tests/miral/window_properties.cpp'
--- tests/miral/window_properties.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/window_properties.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,164 @@
1/*
2 * Copyright © 2017 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <miral/window_manager_tools.h>
20
21#include <mir/client/surface.h>
22#include <mir/client/window.h>
23#include <mir/client/window_spec.h>
24#include <mir_toolkit/mir_buffer_stream.h>
25
26#include "test_server.h"
27
28#include <gmock/gmock.h>
29#include <mir/test/signal.h>
30
31
32using namespace testing;
33using namespace mir::client;
34using namespace std::chrono_literals;
35using miral::WindowManagerTools;
36
37namespace
38{
39std::string const a_window{"a window"};
40
41struct WindowProperties;
42
43struct WindowProperties : public miral::TestServer
44{
45 void SetUp() override
46 {
47 miral::TestServer::SetUp();
48 client_connection = connect_client("WindowProperties");
49 surface = Surface{mir_connection_create_render_surface_sync(client_connection, 40, 40)};
50 }
51
52 void TearDown() override
53 {
54 surface.reset();
55 client_connection.reset();
56 miral::TestServer::TearDown();
57 }
58
59 Connection client_connection;
60 Surface surface;
61
62 std::unique_ptr<TestWindowManagerPolicy> build_window_manager_policy(WindowManagerTools const& tools) override;
63
64 void paint(Surface const& surface)
65 {
66 mir_buffer_stream_swap_buffers_sync(
67 mir_render_surface_get_buffer_stream(surface, 50, 50, mir_pixel_format_argb_8888));
68 }
69
70 mir::test::Signal window_ready;
71};
72
73auto WindowProperties::build_window_manager_policy(WindowManagerTools const& tools)
74-> std::unique_ptr<miral::TestServer::TestWindowManagerPolicy>
75{
76 struct MockWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy
77 {
78 using miral::TestServer::TestWindowManagerPolicy::TestWindowManagerPolicy;
79 MOCK_METHOD1(advise_focus_gained, void (miral::WindowInfo const& window_info));
80 };
81
82 auto result = std::make_unique<MockWindowManagerPolicy>(tools, *this);
83
84 ON_CALL(*result, advise_focus_gained(_))
85 .WillByDefault(InvokeWithoutArgs([this] { window_ready.raise(); }));
86
87 return std::move(result);
88}
89}
90
91TEST_F(WindowProperties, on_creation_default_shell_chrome_is_normal)
92{
93 auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
94 .set_name(a_window.c_str())
95 .add_surface(surface, 50, 50, 0, 0)
96 .create_window();
97
98 paint(surface);
99 ASSERT_TRUE(window_ready.wait_for(400ms));
100
101 invoke_tools([&, this](WindowManagerTools& tools)
102 {
103 EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_normal));
104 });
105}
106
107TEST_F(WindowProperties, on_creation_client_setting_shell_chrome_low_is_seen_by_window_manager)
108{
109 auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
110 .set_name(a_window.c_str())
111 .set_shell_chrome(mir_shell_chrome_low)
112 .add_surface(surface, 50, 50, 0, 0)
113 .create_window();
114
115 paint(surface);
116 ASSERT_TRUE(window_ready.wait_for(400ms));
117
118 invoke_tools([&, this](WindowManagerTools& tools)
119 {
120 EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_low));
121 });
122}
123
124TEST_F(WindowProperties, after_creation_client_setting_shell_chrome_low_is_seen_by_window_manager)
125{
126 auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
127 .set_name(a_window.c_str())
128 .add_surface(surface, 50, 50, 0, 0)
129 .create_window();
130
131 WindowSpec::for_changes(client_connection)
132 .set_shell_chrome(mir_shell_chrome_low)
133 .apply_to(window);
134
135 paint(surface);
136
137 ASSERT_TRUE(window_ready.wait_for(400ms));
138
139 invoke_tools([&, this](WindowManagerTools& tools)
140 {
141 EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_low));
142 });
143}
144
145TEST_F(WindowProperties, after_creation_client_setting_shell_chrome_normal_is_seen_by_window_manager)
146{
147 auto const window = WindowSpec::for_normal_window(client_connection, 50, 50)
148 .set_name(a_window.c_str())
149 .set_shell_chrome(mir_shell_chrome_low)
150 .add_surface(surface, 50, 50, 0, 0)
151 .create_window();
152
153 WindowSpec::for_changes(client_connection)
154 .set_shell_chrome(mir_shell_chrome_normal)
155 .apply_to(window);
156
157 paint(surface);
158 ASSERT_TRUE(window_ready.wait_for(400ms));
159
160 invoke_tools([&, this](WindowManagerTools& tools)
161 {
162 EXPECT_THAT(tools.info_for(tools.active_window()).shell_chrome(), Eq(mir_shell_chrome_normal));
163 });
164}
0165
=== added file 'tests/miral/workspaces.cpp'
--- tests/miral/workspaces.cpp 1970-01-01 00:00:00 +0000
+++ tests/miral/workspaces.cpp 2017-08-24 15:19:58 +0000
@@ -0,0 +1,707 @@
1/*
2 * Copyright © 2017 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 2 or 3 as
6 * 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: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include <miral/workspace_policy.h>
20#include <miral/window_manager_tools.h>
21
22#include <mir/client/surface.h>
23#include <mir/client/window.h>
24#include <mir/client/window_spec.h>
25#include <mir_toolkit/mir_buffer_stream.h>
26
27#include "test_server.h"
28
29#include <gmock/gmock.h>
30#include <mir/test/signal.h>
31
32
33using namespace testing;
34using namespace mir::client;
35using namespace std::chrono_literals;
36using miral::WindowManagerTools;
37
38namespace
39{
40std::string const top_level{"top level"};
41std::string const dialog{"dialog"};
42std::string const tip{"tip"};
43std::string const a_window{"a window"};
44std::string const another_window{"another window"};
45
46struct Workspaces;
47
48struct WorkspacesWindowManagerPolicy : miral::TestServer::TestWindowManagerPolicy, miral::WorkspacePolicy
49{
50 WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture);
51 ~WorkspacesWindowManagerPolicy();
52
53 void advise_new_window(miral::WindowInfo const& window_info);
54 void handle_window_ready(miral::WindowInfo& window_info);
55
56 MOCK_METHOD2(advise_adding_to_workspace,
57 void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
58
59 MOCK_METHOD2(advise_removing_from_workspace,
60 void(std::shared_ptr<miral::Workspace> const&, std::vector<miral::Window> const&));
61
62 MOCK_METHOD1(advise_focus_gained, void(miral::WindowInfo const&));
63
64 MOCK_METHOD1(advise_window_ready, void(miral::WindowInfo const&));
65
66 Workspaces& test_fixture;
67};
68
69struct TestWindow : Surface, Window
70{
71 using Surface::operator=;
72 using Window::operator=;
73};
74
75struct Workspaces : public miral::TestServer
76{
77 auto create_window(std::string const& name) -> TestWindow
78 {
79 TestWindow result;
80
81 result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
82 result = WindowSpec::for_normal_window(client_connection, 50, 50)
83 .set_name(name.c_str())
84 .add_surface(result, 50, 50, 0, 0)
85 .create_window();
86
87 client_windows[name] = result;
88 init_window(result);
89
90 return result;
91 }
92
93 void init_window(TestWindow const& window)
94 {
95 mir::test::Signal window_ready;
96 EXPECT_CALL(policy(), advise_window_ready(_)).WillOnce(InvokeWithoutArgs([&]{ window_ready.raise(); }));
97
98 mir_buffer_stream_swap_buffers_sync(
99 mir_render_surface_get_buffer_stream(window, 50, 50, mir_pixel_format_argb_8888));
100
101 EXPECT_TRUE(window_ready.wait_for(1s));
102 }
103
104 auto create_tip(std::string const& name, Window const& parent) -> TestWindow
105 {
106 TestWindow result;
107
108 result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
109
110 MirRectangle aux_rect{10, 10, 10, 10};
111 result = WindowSpec::for_tip(client_connection, 50, 50, parent, &aux_rect, mir_edge_attachment_any)
112 .set_name(name.c_str())
113 .add_surface(result, 50, 50, 0, 0)
114 .create_window();
115
116 client_windows[name] = result;
117 init_window(result);
118
119 return result;
120 }
121
122 auto create_dialog(std::string const& name, Window const& parent) -> TestWindow
123 {
124 TestWindow result;
125
126 result = Surface{mir_connection_create_render_surface_sync(client_connection, 50, 50)};
127
128 result = WindowSpec::for_dialog(client_connection, 50, 50, parent)
129 .set_name(name.c_str())
130 .add_surface(result, 50, 50, 0, 0)
131 .create_window();
132
133 client_windows[name] = result;
134 init_window(result);
135
136 return result;
137 }
138
139 auto create_workspace() -> std::shared_ptr<miral::Workspace>
140 {
141 std::shared_ptr<miral::Workspace> result;
142
143 invoke_tools([&](WindowManagerTools& tools)
144 { result = tools.create_workspace(); });
145
146 return result;
147 }
148
149 void SetUp() override
150 {
151 miral::TestServer::SetUp();
152 EXPECT_CALL(policy(), advise_adding_to_workspace(_, _)).Times(AnyNumber());
153 EXPECT_CALL(policy(), advise_removing_from_workspace(_, _)).Times(AnyNumber());
154 EXPECT_CALL(policy(), advise_focus_gained(_)).Times(AnyNumber());
155
156 client_connection = connect_client("Workspaces");
157 create_window(top_level);
158 create_dialog(dialog, client_windows[top_level]);
159 create_tip(tip, client_windows[dialog]);
160
161 EXPECT_THAT(client_windows.size(), Eq(3u));
162 EXPECT_THAT(server_windows.size(), Eq(3u));
163 }
164
165 void TearDown() override
166 {
167 client_windows.clear();
168 client_connection.reset();
169 miral::TestServer::TearDown();
170 }
171
172 Connection client_connection;
173
174 auto server_window(std::string const& key) -> miral::Window
175 {
176 std::lock_guard<decltype(mutex)> lock{mutex};
177 return server_windows[key];
178 }
179
180 auto client_window(std::string const& key) -> Window&
181 {
182 return client_windows[key];
183 }
184
185 auto windows_in_workspace(std::shared_ptr<miral::Workspace> const& workspace) -> std::vector<miral::Window>
186 {
187 std::vector<miral::Window> result;
188
189 auto enumerate = [&result](miral::Window const& window)
190 {
191 result.push_back(window);
192 };
193
194 invoke_tools([&](WindowManagerTools& tools)
195 { tools.for_each_window_in_workspace(workspace, enumerate); });
196
197 return result;
198 }
199
200 auto workspaces_containing_window(miral::Window const& window) -> std::vector<std::shared_ptr<miral::Workspace>>
201 {
202 std::vector<std::shared_ptr<miral::Workspace>> result;
203
204 auto enumerate = [&result](std::shared_ptr<miral::Workspace> const& workspace)
205 {
206 result.push_back(workspace);
207 };
208
209 invoke_tools([&](WindowManagerTools& tools)
210 { tools.for_each_workspace_containing(window, enumerate); });
211
212 return result;
213 }
214
215 auto policy() -> WorkspacesWindowManagerPolicy&
216 {
217 if (!the_policy) throw std::logic_error("the_policy isn't valid");
218 return *the_policy;
219 }
220
221private:
222 std::mutex mutable mutex;
223 std::map<std::string, TestWindow> client_windows;
224 std::map<std::string, miral::Window> server_windows;
225 WorkspacesWindowManagerPolicy* the_policy{nullptr};
226
227 friend struct WorkspacesWindowManagerPolicy;
228
229 auto build_window_manager_policy(WindowManagerTools const& tools)
230 -> std::unique_ptr<TestWindowManagerPolicy> override
231 {
232 return std::make_unique<WorkspacesWindowManagerPolicy>(tools, *this);
233 }
234};
235
236WorkspacesWindowManagerPolicy::WorkspacesWindowManagerPolicy(WindowManagerTools const& tools, Workspaces& test_fixture) :
237TestWindowManagerPolicy(tools, test_fixture), test_fixture{test_fixture}
238{
239 test_fixture.the_policy = this;
240}
241
242WorkspacesWindowManagerPolicy::~WorkspacesWindowManagerPolicy()
243{
244 test_fixture.the_policy = nullptr;
245}
246
247
248void WorkspacesWindowManagerPolicy::advise_new_window(miral::WindowInfo const& window_info)
249{
250 miral::TestServer::TestWindowManagerPolicy::advise_new_window(window_info);
251
252 std::lock_guard<decltype(test_fixture.mutex)> lock{test_fixture.mutex};
253 test_fixture.server_windows[window_info.name()] = window_info.window();
254}
255
256void WorkspacesWindowManagerPolicy::handle_window_ready(miral::WindowInfo& window_info)
257{
258 miral::CanonicalWindowManagerPolicy::handle_window_ready(window_info);
259 advise_window_ready(window_info);
260}
261}
262
263TEST_F(Workspaces, before_a_tree_is_added_to_workspace_it_is_empty)
264{
265 auto const workspace = create_workspace();
266
267 EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
268}
269
270TEST_F(Workspaces, when_a_tree_is_added_to_workspace_all_surfaces_in_tree_are_added)
271{
272 auto const workspace = create_workspace();
273 invoke_tools([&, this](WindowManagerTools& tools)
274 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
275
276 EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(3u));
277}
278
279TEST_F(Workspaces, when_a_tree_is_removed_from_workspace_all_surfaces_in_tree_are_removed)
280{
281 auto const workspace = create_workspace();
282 invoke_tools([&, this](WindowManagerTools& tools)
283 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
284
285 invoke_tools([&, this](WindowManagerTools& tools)
286 { tools.remove_tree_from_workspace(server_window(tip), workspace); });
287
288 EXPECT_THAT(windows_in_workspace(workspace).size(), Eq(0u));
289}
290
291TEST_F(Workspaces, given_a_tree_in_a_workspace_when_another_tree_is_added_and_removed_from_workspace_the_original_tree_remains)
292{
293 auto const workspace = create_workspace();
294 auto const original_tree = "original_tree";
295 auto const client_window = create_window(original_tree);
296 auto const original_window= server_window(original_tree);
297
298 invoke_tools([&, this](WindowManagerTools& tools)
299 { tools.add_tree_to_workspace(original_window, workspace); });
300
301 invoke_tools([&, this](WindowManagerTools& tools)
302 { tools.add_tree_to_workspace(server_window(top_level), workspace); });
303 invoke_tools([&, this](WindowManagerTools& tools)
304 { tools.remove_tree_from_workspace(server_window(top_level), workspace); });
305
306 EXPECT_THAT(windows_in_workspace(workspace), ElementsAre(original_window));
307}
308
309TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_all_surfaces_are_contained_in_the_workspace)
310{
311 auto const workspace = create_workspace();
312 invoke_tools([&, this](WindowManagerTools& tools)
313 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
314
315 EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
316 EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
317 EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
318}
319
320
321TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_surfaces_are_contained_in_one_workspace)
322{
323 auto const workspace = create_workspace();
324 invoke_tools([&, this](WindowManagerTools& tools)
325 {
326 tools.add_tree_to_workspace(server_window(dialog), workspace);
327 tools.add_tree_to_workspace(server_window(dialog), workspace);
328 });
329
330 EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace));
331 EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace));
332 EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace));
333
334 EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
335 EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
336 EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
337}
338
339TEST_F(Workspaces, when_a_tree_is_added_to_two_workspaces_all_surfaces_are_contained_in_two_workspaces)
340{
341 auto const workspace1 = create_workspace();
342 auto const workspace2 = create_workspace();
343 invoke_tools([&, this](WindowManagerTools& tools)
344 {
345 tools.add_tree_to_workspace(server_window(dialog), workspace1);
346 tools.add_tree_to_workspace(server_window(dialog), workspace2);
347 });
348
349 EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(2u));
350 EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(2u));
351 EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(2u));
352}
353
354TEST_F(Workspaces, when_workspace_is_closed_surfaces_are_no_longer_contained_in_it)
355{
356 auto const workspace1 = create_workspace();
357 auto workspace2 = create_workspace();
358 invoke_tools([&, this](WindowManagerTools& tools)
359 {
360 tools.add_tree_to_workspace(server_window(dialog), workspace1);
361 tools.add_tree_to_workspace(server_window(dialog), workspace2);
362 });
363
364 workspace2.reset();
365
366 EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(workspace1));
367 EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(workspace1));
368 EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(workspace1));
369
370 EXPECT_THAT(workspaces_containing_window(server_window(top_level)).size(), Eq(1u));
371 EXPECT_THAT(workspaces_containing_window(server_window(dialog)).size(), Eq(1u));
372 EXPECT_THAT(workspaces_containing_window(server_window(tip)).size(), Eq(1u));
373}
374
375TEST_F(Workspaces, when_a_tree_is_added_to_a_workspace_the_policy_is_notified)
376{
377 auto const workspace = create_workspace();
378
379 EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
380 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
381
382 invoke_tools([&, this](WindowManagerTools& tools)
383 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
384}
385
386TEST_F(Workspaces, when_a_tree_is_added_to_a_workspaces_twice_the_policy_is_notified_once)
387{
388 auto const workspace = create_workspace();
389
390 EXPECT_CALL(policy(), advise_adding_to_workspace(workspace,
391 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
392
393 invoke_tools([&, this](WindowManagerTools& tools)
394 {
395 tools.add_tree_to_workspace(server_window(dialog), workspace);
396 tools.add_tree_to_workspace(server_window(dialog), workspace);
397 });
398}
399
400TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_the_policy_is_notified)
401{
402 auto const workspace = create_workspace();
403 invoke_tools([&, this](WindowManagerTools& tools)
404 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
405
406 EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
407 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
408
409 invoke_tools([&, this](WindowManagerTools& tools)
410 { tools.remove_tree_from_workspace(server_window(tip), workspace); });
411}
412
413TEST_F(Workspaces, when_a_tree_is_removed_from_a_workspace_twice_the_policy_is_notified_once)
414{
415 auto const workspace = create_workspace();
416 invoke_tools([&, this](WindowManagerTools& tools)
417 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
418
419 EXPECT_CALL(policy(), advise_removing_from_workspace(workspace,
420 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
421
422 invoke_tools([&, this](WindowManagerTools& tools)
423 {
424 tools.remove_tree_from_workspace(server_window(top_level), workspace);
425 tools.remove_tree_from_workspace(server_window(tip), workspace);
426 });
427}
428
429TEST_F(Workspaces, a_child_window_is_added_to_workspace_of_parent)
430{
431 auto const workspace = create_workspace();
432 invoke_tools([&, this](WindowManagerTools& tools)
433 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
434
435 EXPECT_CALL(policy(), advise_adding_to_workspace(workspace, ElementsAre(_)));
436
437 create_dialog(a_window, client_window(top_level));
438}
439
440TEST_F(Workspaces, a_closing_window_is_removed_from_workspace)
441{
442 auto const workspace = create_workspace();
443 invoke_tools([&, this](WindowManagerTools& tools)
444 { tools.add_tree_to_workspace(server_window(dialog), workspace); });
445
446 create_dialog(a_window, client_window(dialog));
447
448 EXPECT_CALL(policy(), advise_removing_from_workspace(workspace, ElementsAre(server_window(a_window))));
449
450 client_window(a_window).reset();
451}
452
453TEST_F(Workspaces, when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
454{
455 auto const workspace = create_workspace();
456
457 create_window(a_window);
458 create_window(another_window);
459
460 invoke_tools([&, this](WindowManagerTools& tools)
461 {
462 tools.add_tree_to_workspace(server_window(a_window), workspace);
463 tools.add_tree_to_workspace(server_window(another_window), workspace);
464
465 tools.select_active_window(server_window(dialog));
466 tools.select_active_window(server_window(a_window));
467 });
468
469 client_window(a_window).reset();
470
471 invoke_tools([&, this](WindowManagerTools& tools)
472 {
473 EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
474 << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
475 << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
476 });
477}
478
479TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_closes_focus_remains_in_workspace)
480{
481 auto const workspace = create_workspace();
482
483 create_window(another_window);
484
485 {
486 auto const another_app = connect_client("another app");
487 TestWindow window;
488 window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
489 window = WindowSpec::for_normal_window(another_app, 50, 50)
490 .set_name(a_window.c_str())
491 .add_surface(window, 50, 50, 0, 0)
492 .create_window();
493
494 init_window(window);
495
496 invoke_tools([&, this](WindowManagerTools& tools)
497 {
498 tools.add_tree_to_workspace(server_window(top_level), workspace);
499 tools.add_tree_to_workspace(server_window(a_window), workspace);
500 });
501 }
502
503 invoke_tools([&, this](WindowManagerTools& tools)
504 {
505 EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
506 << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
507 << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
508 });
509}
510
511TEST_F(Workspaces, when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
512{
513 auto const workspace = create_workspace();
514
515 create_window(a_window);
516 create_window(another_window);
517
518 invoke_tools([&, this](WindowManagerTools& tools)
519 {
520 tools.add_tree_to_workspace(server_window(a_window), workspace);
521 tools.add_tree_to_workspace(server_window(another_window), workspace);
522
523 tools.select_active_window(server_window(dialog));
524 tools.select_active_window(server_window(a_window));
525 });
526
527 mir::test::Signal focus_changed;
528 EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
529
530 mir_window_set_state(client_window(a_window), mir_window_state_hidden);
531
532 EXPECT_TRUE(focus_changed.wait_for(1s));
533
534 invoke_tools([&, this](WindowManagerTools& tools)
535 {
536 EXPECT_THAT(tools.active_window(), Eq(server_window(another_window)))
537 << "tools.active_window() . . . .: " << tools.info_for(tools.active_window()).name() << "\n"
538 << "server_window(another_window): " << tools.info_for(server_window(another_window)).name();
539 });
540}
541
542
543TEST_F(Workspaces, with_two_applications_when_a_window_in_a_workspace_hides_focus_remains_in_workspace)
544{
545 auto const workspace = create_workspace();
546
547 create_window(another_window);
548
549 auto const another_app = connect_client("another app");
550 TestWindow window;
551 window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
552 window = WindowSpec::for_normal_window(another_app, 50, 50)
553 .set_name(a_window.c_str())
554 .add_surface(window, 50, 50, 0, 0)
555 .create_window();
556
557 init_window(window);
558
559 invoke_tools([&, this](WindowManagerTools& tools)
560 {
561 tools.add_tree_to_workspace(server_window(top_level), workspace);
562 tools.add_tree_to_workspace(server_window(a_window), workspace);
563 });
564
565
566 mir::test::Signal focus_changed;
567 EXPECT_CALL(policy(), advise_focus_gained(_)).WillOnce(InvokeWithoutArgs([&]{ focus_changed.raise(); }));
568
569 mir_window_set_state(window, mir_window_state_hidden);
570
571 EXPECT_TRUE(focus_changed.wait_for(1s));
572
573 invoke_tools([&, this](WindowManagerTools& tools)
574 {
575 EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
576 << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
577 << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
578 });
579
580 Mock::VerifyAndClearExpectations(&policy()); // before shutdown
581}
582
583TEST_F(Workspaces, focus_next_within_application_keeps_focus_in_workspace)
584{
585 auto const workspace = create_workspace();
586
587 create_window(another_window);
588 create_window(a_window);
589
590 invoke_tools([&, this](WindowManagerTools& tools)
591 {
592 tools.add_tree_to_workspace(server_window(a_window), workspace);
593 tools.add_tree_to_workspace(server_window(dialog), workspace);
594
595 tools.focus_next_within_application();
596
597 EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
598 << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
599 << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
600
601 tools.focus_next_within_application();
602
603 EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
604 << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
605 << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
606 });
607}
608
609TEST_F(Workspaces, focus_next_application_keeps_focus_in_workspace)
610{
611 auto const workspace = create_workspace();
612 create_window(another_window);
613
614 auto const another_app = connect_client("another app");
615 TestWindow window;
616 window = Surface{mir_connection_create_render_surface_sync(another_app, 50, 50)};
617 window = WindowSpec::for_normal_window(another_app, 50, 50)
618 .set_name(a_window.c_str())
619 .add_surface(window, 50, 50, 0, 0)
620 .create_window();
621
622 init_window(window);
623
624 invoke_tools([&, this](WindowManagerTools& tools)
625 {
626 tools.add_tree_to_workspace(server_window(top_level), workspace);
627 tools.add_tree_to_workspace(server_window(a_window), workspace);
628
629 tools.focus_next_application();
630
631 EXPECT_THAT(tools.active_window(), Eq(server_window(dialog)))
632 << "tools.active_window(): " << tools.info_for(tools.active_window()).name() << "\n"
633 << "server_window(dialog): " << tools.info_for(server_window(dialog)).name();
634
635 tools.focus_next_application();
636
637 EXPECT_THAT(tools.active_window(), Eq(server_window(a_window)))
638 << "tools.active_window(). : " << tools.info_for(tools.active_window()).name() << "\n"
639 << "server_window(a_window): " << tools.info_for(server_window(a_window)).name();
640 });
641}
642
643TEST_F(Workspaces, move_windows_from_one_workspace_to_another)
644{
645 auto const pre_workspace = create_workspace();
646 auto const from_workspace = create_workspace();
647 auto const to_workspace = create_workspace();
648 auto const post_workspace = create_workspace();
649
650 create_window(a_window);
651 create_window(another_window);
652
653 invoke_tools([&, this](WindowManagerTools& tools)
654 {
655 tools.add_tree_to_workspace(server_window(a_window), pre_workspace);
656 tools.add_tree_to_workspace(server_window(top_level), from_workspace);
657 tools.add_tree_to_workspace(server_window(another_window), post_workspace);
658
659 tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
660 });
661
662 EXPECT_THAT(windows_in_workspace(from_workspace).size(), Eq(0u));
663
664 EXPECT_THAT(workspaces_containing_window(server_window(a_window)), ElementsAre(pre_workspace));
665 EXPECT_THAT(workspaces_containing_window(server_window(top_level)), ElementsAre(to_workspace));
666 EXPECT_THAT(workspaces_containing_window(server_window(dialog)), ElementsAre(to_workspace));
667 EXPECT_THAT(workspaces_containing_window(server_window(tip)), ElementsAre(to_workspace));
668 EXPECT_THAT(workspaces_containing_window(server_window(another_window)), ElementsAre(post_workspace));
669}
670
671TEST_F(Workspaces, when_moving_windows_from_one_workspace_to_another_windows_only_appear_once_in_target_workspace)
672{
673 auto const from_workspace = create_workspace();
674 auto const to_workspace = create_workspace();
675
676 create_window(a_window);
677 create_window(another_window);
678
679 invoke_tools([&, this](WindowManagerTools& tools)
680 {
681 tools.add_tree_to_workspace(server_window(a_window), from_workspace);
682 tools.add_tree_to_workspace(server_window(another_window), from_workspace);
683 tools.add_tree_to_workspace(server_window(a_window), to_workspace);
684
685 tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
686 });
687
688 EXPECT_THAT(windows_in_workspace(to_workspace), ElementsAre(server_window(a_window), server_window(another_window)));
689}
690
691TEST_F(Workspaces, when_workspace_content_is_moved_the_policy_is_notified)
692{
693 auto const from_workspace = create_workspace();
694 auto const to_workspace = create_workspace();
695
696 EXPECT_CALL(policy(), advise_removing_from_workspace(from_workspace,
697 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
698
699 EXPECT_CALL(policy(), advise_adding_to_workspace(to_workspace,
700 ElementsAre(server_window(top_level), server_window(dialog), server_window(tip))));
701
702 invoke_tools([&, this](WindowManagerTools& tools)
703 {
704 tools.add_tree_to_workspace(server_window(dialog), from_workspace);
705 tools.move_workspace_content_to_workspace(to_workspace, from_workspace);
706 });
707}

Subscribers

People subscribed via source and target branches