-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: a test harness for window managers + tests for the MinimalWindowManager #3416
Conversation
96ab7f1
to
813e812
Compare
@mattkae any idea why "Coverage" failed with:
It isn't like it runs different tests? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, this is the sort of thing I imagined. Not finished looking through the detail yet. But mentioning some nits before my EOD
class StubShellReport : public mir::shell::ShellReport | ||
{ | ||
public: | ||
void opened_session(mir::scene::Session const&) override {} | ||
void closing_session(mir::scene::Session const&) override {} | ||
void created_surface(mir::scene::Session const&, mir::scene::Surface const&) override {} | ||
void update_surface( | ||
mir::scene::Session const&, mir::scene::Surface const&, | ||
mir::shell::SurfaceSpecification const&) override {} | ||
void update_surface( | ||
mir::scene::Session const&, mir::scene::Surface const&, | ||
MirWindowAttrib, int) override {} | ||
void destroying_surface(mir::scene::Session const&, mir::scene::Surface const&) override {} | ||
void started_prompt_session(mir::scene::PromptSession const&, mir::scene::Session const&) override {} | ||
void added_prompt_provider(mir::scene::PromptSession const&, mir::scene::Session const&) override {} | ||
void stopping_prompt_session(mir::scene::PromptSession const&) override {} | ||
void adding_display(mir::geometry::Rectangle const&) override {} | ||
void removing_display(mir::geometry::Rectangle const&) override {} | ||
void input_focus_set_to(mir::scene::Session const*, mir::scene::Surface const*) override {} | ||
void surfaces_raised(mir::shell::SurfaceSet const&) override {} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very similar to the existing mir::report::null::ShellReport
implementation. Do we need both?
No idea at all as of yet... I've been trying to track that down to no avail |
OK, my problems here are not so much with the test harness itself, but with the restructuring needed to support it. And that actually comes from the legacy structure of the project.
I need to think about what the options are and either propose a solution or reconcile myself to the "ugliness". |
b2d4d96
to
3427ed9
Compare
Symbols issue will be fixed by #3423 |
I have moved the tests to their own suite, so let me know what you want to do in this domain. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3416 +/- ##
==========================================
+ Coverage 77.24% 77.46% +0.22%
==========================================
Files 1071 1073 +2
Lines 68467 68759 +292
==========================================
+ Hits 52886 53263 +377
+ Misses 15581 15496 -85 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still experimenting with ideas to clean up using our library internals for testing
tests/include/mir_test_framework/window_management_test_harness.h
Outdated
Show resolved
Hide resolved
tests/include/mir_test_framework/window_management_test_harness.h
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good start (I've some code-style nits with not using our auto foo() -> bar
default in places).
This is not quite what I was expecting - rather than validating that the expected WM hooks are called at the appropriate time, I was expecting that the WM state would be validated at the end. Something like
<create windows>
EXPECT_THAT(stacking_order(), Eq({window1, window2, window3, window4});
<emit alt-tab sequence>
EXPECT_THAT(stacking_order(), Eq({window2, window1, window3, window4})
and suchlike.
I think that would be both simpler to implement and would be more stable over code changes (we're not testing the implementation details of how WM happens, we're testing what window management happens).
This PR also basically implements that, right? You'd turn get_shell
et al into a set of focused_surface()
, stacking_order()
, etc methods, as Alan suggests, and then maybe (re)move a bunch of code (like the whole WindowManagementVerifier
infrastructure) into a separate set of tests?
Yeah I agree. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. I think this could be dramatically simplified by using mtf::HeadlessTest
and mtf::add_fake_input_device
?
What the WindowManagementTestHarness
requires is the ability to inject input events (provided by mtf::FakeInputDevice
) and access to the_shell()
and the_surface_stack()
, both of which are already provided by the mir::Server
you can get from mtf::HeadlessTest
. Oh, and to set the actual window manager under test, which you can do with mir::Server::override_the_window_manager_builder
?
That way you wouldn't need to expose a bunch of extra stuff, or do dummy implementations of some of the stuff.
tests/include/mir_test_framework/window_management_test_harness.h
Outdated
Show resolved
Hide resolved
Hm, that is a really good point! We could indeed just wrap |
It is a good point: I hadn't recognised the commonality |
@RAOF and @AlanGriffiths : Yup, using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great simplification. I think this can be simplified even further (and could be slightly nicer stylistically by using the ms::
etc namespace aliases consistently throughout).
The only blocking thing here is the comment in ::SetUp()
.
mir_test_framework::HeadlessInProcessServer::SetUp(); | ||
self = std::make_shared<Self>(this); | ||
|
||
auto fake_display = std::make_unique<mtd::FakeDisplay>(get_output_rectangles()); | ||
self->display = fake_display.get(); | ||
preset_display(std::move(fake_display)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. Doesn't preset_display
need to be called before HeadlessInProcessServer::SetUp()
? ::SetUp()
will start the server, which will include setting up the display?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests/miral/active_outputs.cpp
has the same order for some reason? I was following the example, but I would imagine that you are correct!
b8b0cdb
to
d7787a1
Compare
tests/include/mir_test_framework/window_management_test_harness.h
Outdated
Show resolved
Hide resolved
9e92db1
to
fc10eda
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You appear to have accidentally a test 😉
The workqueue still appears weird to me; the only use is to have client_surface_close_requested
call destroy_surface
in a deferred fashion? So the only time it's actually interesting is for publish_event
? And that matters because the WM takes a lock when processing the input event, and shell->destroy_surface
would also try to take that lock.
Right?
TEST_F(MinimalWindowManagerTest, alt_f4_closes_active_window) | ||
{ | ||
auto const app = open_application("test"); | ||
msh::SurfaceSpecification spec; | ||
spec.width = geom::Width {100}; | ||
spec.height = geom::Height{100}; | ||
spec.depth_layer = mir_depth_layer_application; | ||
auto window = create_window(app, spec); | ||
|
||
// Process an alt + f4 input | ||
std::chrono::nanoseconds const event_timestamp = std::chrono::system_clock::now().time_since_epoch(); | ||
MirKeyboardAction const action{mir_keyboard_action_down}; | ||
xkb_keysym_t const keysym{0}; | ||
int const scan_code{KEY_F4}; | ||
MirInputEventModifiers const modifiers{mir_input_event_modifier_alt}; | ||
auto const event = mir::events::make_key_event( | ||
mir_input_event_type_key, | ||
event_timestamp, | ||
action, | ||
keysym, | ||
scan_code, | ||
modifiers); | ||
publish_event(*event); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the check that the window has been closed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True 😉
auto const& f = work_queue.front(); | ||
f(); | ||
work_queue.pop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can result in infinite recursion, right? Almost all usages of the workqueue are of the form queue.enqueue(...); queue.run()
, and if that gets run from the workqueue it'll push something to the back of the queue and then run itself again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I agree that it's kind of nasty... I am trying to find a good way to avoid both the nastiness of the queue and the nastiness of asynchronicity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After thinking about this deeper, it can result in recursion, but this isn't necessarily a bad thing. The WorkQueue
will just continually be run recursively, which will work.
If you end up in a situation where the recursion is infinite, then that suggests that your WM is doing something wrong. e.g. if every time you resize a window, you also move it, and every time you move a window, you also resize it, then that is on you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the problem I'm talking about is running f()
before running work_queue.pop()
. If f()
happens to run work_queue.run()
, then work_queue.run()
will call work_queue.front()
, receive the same f
again, and recurse.
If you popped before calling f()
(and std::move()
d out of front()
), that wouldn't happen.
Yes exactly. (Although... I wish it wasn't the case. Let me see if there's any way around it one last time) |
@RAOF: I see no clever way around the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the WorkQueue
seems like it could change from a work queue into just a std::vector<std::shared_ptr<ms::Surface>> pending_surfaces_to_destroy;
plus a loop at the end of publish_event
destroying those surfaces.
Neither request_resize
nor request_move
actually use the work_queue
, right? They push a single functor into the queue and then immediately pop that functor off the front and run it - there's no way for client_surface_close_requested
to be called from either of those codepaths?
I won't block on this, but I feel like that would be simpler?
auto const& f = work_queue.front(); | ||
f(); | ||
work_queue.pop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the problem I'm talking about is running f()
before running work_queue.pop()
. If f()
happens to run work_queue.run()
, then work_queue.run()
will call work_queue.front()
, receive the same f
again, and recurse.
If you popped before calling f()
(and std::move()
d out of front()
), that wouldn't happen.
I can agree with that 👍 Really only the destroy code needs it for now |
@RAOF I also better understand the issue now, misunderstood before. I will pop before calling f Edit: Maybe you're all-around right though, and the |
@RAOF: Now that I fully grasp your point, you were right all along 😄 |
4273f01
to
3a26b1e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One nit, but otherwise good to go
|
||
void mir_test_framework::WindowManagementTestHarness::SetUp() | ||
{ | ||
self = std::make_shared<Self>(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surely this initialization can happen in the constructor?
${PROJECT_SOURCE_DIR}/include/miral | ||
${PROJECT_SOURCE_DIR}/src/miral |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
${PROJECT_SOURCE_DIR}/src/miral
is not needed
3a26b1e
to
a19d741
Compare
This PR establishes an initial test harness that is suitable for
MinimalWindowManager
. We might need more harnessing forFloatingWindowManager
, but that should come as a follow up if we need it.What's new?
WindowManagementTestHarness
MinimalWindowManager