vuls: init at 0.27.0
[NixPkgs.git] / nixos / tests / lomiri.nix
blob1ff68e85b94dc46f7d72bd39dd9a65d3ead00f66
1 let
2   makeTest = import ./make-test-python.nix;
3   # Just to make sure everything is the same, need it for OCR & navigating greeter
4   user = "alice";
5   description = "Alice Foobar";
6   password = "foobar";
8   # tmpfiles setup to make OCRing on terminal output more reliable
9   terminalOcrTmpfilesSetup =
10     {
11       pkgs,
12       lib,
13       config,
14     }:
15     let
16       white = "255, 255, 255";
17       black = "0, 0, 0";
18       colorSection = color: {
19         Color = color;
20         Bold = true;
21         Transparency = false;
22       };
23       terminalColors = pkgs.writeText "customized.colorscheme" (
24         lib.generators.toINI { } {
25           Background = colorSection white;
26           Foreground = colorSection black;
27           Color2 = colorSection black;
28           Color2Intense = colorSection black;
29         }
30       );
31       terminalConfig = pkgs.writeText "terminal.ubports.conf" (
32         lib.generators.toINI { } {
33           General = {
34             colorScheme = "customized";
35             fontSize = "16";
36             fontStyle = "Inconsolata";
37           };
38         }
39       );
40       confBase = "${config.users.users.${user}.home}/.config";
41       userDirArgs = {
42         mode = "0700";
43         user = user;
44         group = "users";
45       };
46     in
47     {
48       "${confBase}".d = userDirArgs;
49       "${confBase}/terminal.ubports".d = userDirArgs;
50       "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}";
51       "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}";
52     };
55   greeter = makeTest (
56     { pkgs, lib, ... }:
57     {
58       name = "lomiri-greeter";
60       meta = {
61         maintainers = lib.teams.lomiri.members;
62       };
64       nodes.machine =
65         { config, ... }:
66         {
67           imports = [ ./common/user-account.nix ];
69           virtualisation.memorySize = 2047;
71           users.users.${user} = {
72             inherit description password;
73           };
75           services.desktopManager.lomiri.enable = lib.mkForce true;
76           services.displayManager.defaultSession = lib.mkForce "lomiri";
78           # Help with OCR
79           fonts.packages = [ pkgs.inconsolata ];
80         };
82       enableOCR = true;
84       testScript =
85         { nodes, ... }:
86         ''
87           def wait_for_text(text):
88               """
89               Wait for on-screen text, and try to optimise retry count for slow hardware.
90               """
91               machine.sleep(10)
92               machine.wait_for_text(text)
94           start_all()
95           machine.wait_for_unit("multi-user.target")
97           # Lomiri in greeter mode should work & be able to start a session
98           with subtest("lomiri greeter works"):
99               machine.wait_for_unit("display-manager.service")
100               machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
102               # Start page shows current time
103               wait_for_text(r"(AM|PM)")
104               machine.screenshot("lomiri_greeter_launched")
106               # Advance to login part
107               machine.send_key("ret")
108               wait_for_text("${description}")
109               machine.screenshot("lomiri_greeter_login")
111               # Login
112               machine.send_chars("${password}\n")
113               machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
115               # Output rendering from Lomiri has started when it starts printing performance diagnostics
116               machine.wait_for_console_text("Last frame took")
117               # Look for datetime's clock, one of the last elements to load
118               wait_for_text(r"(AM|PM)")
119               machine.screenshot("lomiri_launched")
120         '';
121     }
122   );
124   desktop-basics = makeTest (
125     { pkgs, lib, ... }:
126     {
127       name = "lomiri-desktop-basics";
129       meta = {
130         maintainers = lib.teams.lomiri.members;
131       };
133       nodes.machine =
134         { config, ... }:
135         {
136           imports = [
137             ./common/auto.nix
138             ./common/user-account.nix
139           ];
141           virtualisation.memorySize = 2047;
143           users.users.${user} = {
144             inherit description password;
145           };
147           test-support.displayManager.auto = {
148             enable = true;
149             inherit user;
150           };
152           # To control mouse via scripting
153           programs.ydotool.enable = true;
155           services.desktopManager.lomiri.enable = lib.mkForce true;
156           services.displayManager.defaultSession = lib.mkForce "lomiri";
158           # Help with OCR
159           fonts.packages = [ pkgs.inconsolata ];
161           environment = {
162             # Help with OCR
163             etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
164               font = rec {
165                 normal.family = "Inconsolata";
166                 bold.family = normal.family;
167                 italic.family = normal.family;
168                 bold_italic.family = normal.family;
169                 size = 16;
170               };
171               colors = rec {
172                 primary = {
173                   foreground = "0x000000";
174                   background = "0xffffff";
175                 };
176                 normal = {
177                   green = primary.foreground;
178                 };
179               };
180             };
182             systemPackages = with pkgs; [
183               # Forcing alacritty to run as an X11 app when opened from the starter menu
184               (symlinkJoin {
185                 name = "x11-${alacritty.name}";
187                 paths = [ alacritty ];
189                 nativeBuildInputs = [ makeWrapper ];
191                 postBuild = ''
192                   wrapProgram $out/bin/alacritty \
193                     --set WINIT_UNIX_BACKEND x11 \
194                     --set WAYLAND_DISPLAY ""
195                 '';
197                 inherit (alacritty) meta;
198               })
199             ];
200           };
202           # Help with OCR
203           systemd.tmpfiles.settings = {
204             "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
205           };
206         };
208       enableOCR = true;
210       testScript =
211         { nodes, ... }:
212         ''
213           def wait_for_text(text):
214               """
215               Wait for on-screen text, and try to optimise retry count for slow hardware.
216               """
217               machine.sleep(10)
218               machine.wait_for_text(text)
220           def mouse_click(xpos, ypos):
221               """
222               Move the mouse to a screen location and hit left-click.
223               """
225               # Need to reset to top-left, --absolute doesn't work?
226               machine.execute("ydotool mousemove -- -10000 -10000")
227               machine.sleep(2)
229               # Move
230               machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
231               machine.sleep(2)
233               # Click (C0 - left button: down & up)
234               machine.execute("ydotool click 0xC0")
235               machine.sleep(2)
237           def open_starter():
238               """
239               Open the starter, and ensure it's opened.
240               """
242               # Using the keybind has a chance of instantly closing the menu again? Just click the button
243               mouse_click(20, 30)
245           start_all()
246           machine.wait_for_unit("multi-user.target")
248           # The session should start, and not be stuck in i.e. a crash loop
249           with subtest("lomiri starts"):
250               machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
251               # Output rendering from Lomiri has started when it starts printing performance diagnostics
252               machine.wait_for_console_text("Last frame took")
253               # Look for datetime's clock, one of the last elements to load
254               wait_for_text(r"(AM|PM)")
255               machine.screenshot("lomiri_launched")
257           # Working terminal keybind is good
258           with subtest("terminal keybind works"):
259               machine.send_key("ctrl-alt-t")
260               wait_for_text(r"(${user}|machine)")
261               machine.screenshot("terminal_opens")
263               # lomiri-terminal-app has a separate VM test to test its basic functionality
265               machine.send_key("alt-f4")
267           # We want the ability to launch applications
268           with subtest("starter menu works"):
269               open_starter()
270               machine.screenshot("starter_opens")
272               # Just try the terminal again, we know that it should work
273               machine.send_chars("Terminal\n")
274               wait_for_text(r"(${user}|machine)")
275               machine.send_key("alt-f4")
277           # We want support for X11 apps
278           with subtest("xwayland support works"):
279               open_starter()
280               machine.send_chars("Alacritty\n")
281               wait_for_text(r"(${user}|machine)")
282               machine.screenshot("alacritty_opens")
283               machine.send_key("alt-f4")
285           # Morph is how we go online
286           with subtest("morph browser works"):
287               open_starter()
288               machine.send_chars("Morph\n")
289               wait_for_text(r"(Bookmarks|address|site|visited any)")
290               machine.screenshot("morph_open")
292               # morph-browser has a separate VM test to test its basic functionalities
294               machine.send_key("alt-f4")
296           # LSS provides DE settings
297           with subtest("system settings open"):
298               open_starter()
299               machine.send_chars("System Settings\n")
300               wait_for_text("Rotation Lock")
301               machine.screenshot("settings_open")
303               # lomiri-system-settings has a separate VM test to test its basic functionalities
305               machine.send_key("alt-f4")
306         '';
307     }
308   );
310   desktop-appinteractions = makeTest (
311     { pkgs, lib, ... }:
312     {
313       name = "lomiri-desktop-appinteractions";
315       meta = {
316         maintainers = lib.teams.lomiri.members;
317       };
319       nodes.machine =
320         { config, ... }:
321         {
322           imports = [
323             ./common/auto.nix
324             ./common/user-account.nix
325           ];
327           virtualisation.memorySize = 2047;
329           users.users.${user} = {
330             inherit description password;
331             # polkit agent test
332             extraGroups = [ "wheel" ];
333           };
335           test-support.displayManager.auto = {
336             enable = true;
337             inherit user;
338           };
340           # To control mouse via scripting
341           programs.ydotool.enable = true;
343           services.desktopManager.lomiri.enable = lib.mkForce true;
344           services.displayManager.defaultSession = lib.mkForce "lomiri";
346           # Help with OCR
347           fonts.packages = [ pkgs.inconsolata ];
349           environment = {
350             # Help with OCR
351             etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
352               font = rec {
353                 normal.family = "Inconsolata";
354                 bold.family = normal.family;
355                 italic.family = normal.family;
356                 bold_italic.family = normal.family;
357                 size = 16;
358               };
359               colors = rec {
360                 primary = {
361                   foreground = "0x000000";
362                   background = "0xffffff";
363                 };
364                 normal = {
365                   green = primary.foreground;
366                 };
367               };
368             };
370             variables = {
371               # So we can test what lomiri-content-hub is working behind the scenes
372               LOMIRI_CONTENT_HUB_LOGGING_LEVEL = "2";
373             };
375             systemPackages = with pkgs; [
376               # For a convenient way of kicking off lomiri-content-hub peer collection
377               lomiri.lomiri-content-hub.examples
378             ];
379           };
381           # Help with OCR
382           systemd.tmpfiles.settings = {
383             "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
384           };
385         };
387       enableOCR = true;
389       testScript =
390         { nodes, ... }:
391         ''
392           def wait_for_text(text):
393               """
394               Wait for on-screen text, and try to optimise retry count for slow hardware.
395               """
396               machine.sleep(10)
397               machine.wait_for_text(text)
399           def toggle_maximise():
400               """
401               Maximise the current window.
402               """
403               machine.send_key("ctrl-meta_l-up")
405               # For some reason, Lomiri in these VM tests very frequently opens the starter menu a few seconds after sending the above.
406               # Because this isn't 100% reproducible all the time, and there is no command to await when OCR doesn't pick up some text,
407               # the best we can do is send some Escape input after waiting some arbitrary time and hope that it works out fine.
408               machine.sleep(5)
409               machine.send_key("esc")
410               machine.sleep(5)
412           def mouse_click(xpos, ypos):
413               """
414               Move the mouse to a screen location and hit left-click.
415               """
417               # Need to reset to top-left, --absolute doesn't work?
418               machine.execute("ydotool mousemove -- -10000 -10000")
419               machine.sleep(2)
421               # Move
422               machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
423               machine.sleep(2)
425               # Click (C0 - left button: down & up)
426               machine.execute("ydotool click 0xC0")
427               machine.sleep(2)
429           def open_starter():
430               """
431               Open the starter, and ensure it's opened.
432               """
434               # Using the keybind has a chance of instantly closing the menu again? Just click the button
435               mouse_click(20, 30)
437           start_all()
438           machine.wait_for_unit("multi-user.target")
440           # The session should start, and not be stuck in i.e. a crash loop
441           with subtest("lomiri starts"):
442               machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
443               # Output rendering from Lomiri has started when it starts printing performance diagnostics
444               machine.wait_for_console_text("Last frame took")
445               # Look for datetime's clock, one of the last elements to load
446               wait_for_text(r"(AM|PM)")
447               machine.screenshot("lomiri_launched")
449           # Working terminal keybind is good
450           with subtest("terminal keybind works"):
451               machine.send_key("ctrl-alt-t")
452               wait_for_text(r"(${user}|machine)")
453               machine.screenshot("terminal_opens")
455               # lomiri-terminal-app has a separate VM test to test its basic functionality
457               # for the LSS lomiri-content-hub test to work reliably, we need to kick off peer collecting
458               machine.send_chars("lomiri-content-hub-test-importer\n")
459               wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from lomiri-content-hub
460               machine.send_key("ctrl-c")
462               # Doing this here, since we need an in-session shell & separately starting a terminal again wastes time
463               with subtest("polkit agent works"):
464                   machine.send_chars("pkexec touch /tmp/polkit-test\n")
465                   # There's an authentication notification here that gains focus, but we struggle with OCRing it
466                   # Just hope that it's up after a short wait
467                   machine.sleep(10)
468                   machine.screenshot("polkit_agent")
469                   machine.send_chars("${password}")
470                   machine.sleep(2) # Hopefully enough delay to make sure all the password characters have been registered? Maybe just placebo
471                   machine.send_chars("\n")
472                   machine.wait_for_file("/tmp/polkit-test", 10)
474               machine.send_key("alt-f4")
476           # LSS provides DE settings
477           with subtest("system settings open"):
478               open_starter()
479               machine.send_chars("System Settings\n")
480               wait_for_text("Rotation Lock")
481               machine.screenshot("settings_open")
483               # lomiri-system-settings has a separate VM test, only test Lomiri-specific lomiri-content-hub functionalities here
485               # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio
486               toggle_maximise()
488               # Load Background plugin
489               machine.send_key("tab")
490               machine.send_key("tab")
491               machine.send_key("tab")
492               machine.send_key("tab")
493               machine.send_key("tab")
494               machine.send_key("tab")
495               machine.send_key("ret")
496               wait_for_text("Background image")
498               # Try to load custom background
499               machine.send_key("shift-tab")
500               machine.send_key("shift-tab")
501               machine.send_key("shift-tab")
502               machine.send_key("shift-tab")
503               machine.send_key("shift-tab")
504               machine.send_key("shift-tab")
505               machine.send_key("ret")
507               # Peers should be loaded
508               wait_for_text("Morph") # or Gallery, but Morph is already packaged
509               machine.screenshot("settings_lomiri-content-hub_peers")
511               # Select Morph as content source
512               mouse_click(370, 100)
514               # Expect Morph to be brought into the foreground, with its Downloads page open
515               wait_for_text("No downloads")
517               # If lomiri-content-hub encounters a problem, it may have crashed the original application issuing the request.
518               # Check that it's still alive
519               machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
521               machine.screenshot("lomiri-content-hub_exchange")
523               # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
524               machine.send_key("esc")
526               machine.sleep(2) # sleep a tiny bit so morph can close & the focus can return to LSS
527               machine.send_key("alt-f4")
528         '';
529     }
530   );
532   desktop-ayatana-indicators = makeTest (
533     { pkgs, lib, ... }:
534     {
535       name = "lomiri-desktop-ayatana-indicators";
537       meta = {
538         maintainers = lib.teams.lomiri.members;
539       };
541       nodes.machine =
542         { config, ... }:
543         {
544           imports = [
545             ./common/auto.nix
546             ./common/user-account.nix
547           ];
549           virtualisation.memorySize = 2047;
551           users.users.${user} = {
552             inherit description password;
553           };
555           test-support.displayManager.auto = {
556             enable = true;
557             inherit user;
558           };
560           # To control mouse via scripting
561           programs.ydotool.enable = true;
563           services.desktopManager.lomiri.enable = lib.mkForce true;
564           services.displayManager.defaultSession = lib.mkForce "lomiri";
566           # Help with OCR
567           fonts.packages = [ pkgs.inconsolata ];
569           environment.systemPackages = with pkgs; [ qt5.qttools ];
570         };
572       enableOCR = true;
574       testScript =
575         { nodes, ... }:
576         ''
577           def wait_for_text(text):
578               """
579               Wait for on-screen text, and try to optimise retry count for slow hardware.
580               """
581               machine.sleep(10)
582               machine.wait_for_text(text)
584           def mouse_click(xpos, ypos):
585               """
586               Move the mouse to a screen location and hit left-click.
587               """
589               # Need to reset to top-left, --absolute doesn't work?
590               machine.execute("ydotool mousemove -- -10000 -10000")
591               machine.sleep(2)
593               # Move
594               machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
595               machine.sleep(2)
597               # Click (C0 - left button: down & up)
598               machine.execute("ydotool click 0xC0")
599               machine.sleep(2)
601           start_all()
602           machine.wait_for_unit("multi-user.target")
604           # The session should start, and not be stuck in i.e. a crash loop
605           with subtest("lomiri starts"):
606               machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
607               # Output rendering from Lomiri has started when it starts printing performance diagnostics
608               machine.wait_for_console_text("Last frame took")
609               # Look for datetime's clock, one of the last elements to load
610               wait_for_text(r"(AM|PM)")
611               machine.screenshot("lomiri_launched")
613           # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
614           # There's a test app we could use that also displays their contents, but it's abit inconsistent.
615           with subtest("ayatana indicators work"):
616               mouse_click(735, 0) # the cog in the top-right, for the session indicator
617               wait_for_text(r"(Notifications|Rotation|Battery|Sound|Time|Date|System)")
618               machine.screenshot("indicators_open")
620               # Indicator order within the menus *should* be fixed based on per-indicator order setting
621               # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
622               machine.send_key("left")
623               machine.send_key("left")
624               machine.send_key("left")
625               machine.send_key("left")
626               machine.send_key("left")
627               machine.send_key("left")
628               # Notifications are usually empty, nothing to check there
630               with subtest("ayatana indicator display works"):
631                   # We start on this, don't go right
632                   wait_for_text("Lock")
633                   machine.screenshot("indicators_display")
635               with subtest("ayatana indicator bluetooth works"):
636                   machine.send_key("right")
637                   wait_for_text("Bluetooth settings")
638                   machine.screenshot("indicators_bluetooth")
640               with subtest("lomiri indicator network works"):
641                   machine.send_key("right")
642                   wait_for_text(r"(Flight|Wi-Fi)")
643                   machine.screenshot("indicators_network")
645               with subtest("ayatana indicator sound works"):
646                   machine.send_key("right")
647                   wait_for_text(r"(Silent|Volume)")
648                   machine.screenshot("indicators_sound")
650               with subtest("ayatana indicator power works"):
651                   machine.send_key("right")
652                   wait_for_text(r"(Charge|Battery settings)")
653                   machine.screenshot("indicators_power")
655               with subtest("ayatana indicator datetime works"):
656                   machine.send_key("right")
657                   wait_for_text("Time and Date Settings")
658                   machine.screenshot("indicators_timedate")
660               with subtest("ayatana indicator session works"):
661                   machine.send_key("right")
662                   wait_for_text("Log Out")
663                   machine.screenshot("indicators_session")
665                   # We should be able to log out and return to the greeter
666                   mouse_click(720, 280) # "Log Out"
667                   mouse_click(400, 240) # confirm logout
668                   machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
669         '';
670     }
671   );
673   keymap =
674     let
675       pwInput = "qwerty";
676       pwOutput = "qwertz";
677     in
678     makeTest (
679       { pkgs, lib, ... }:
680       {
681         name = "lomiri-keymap";
683         meta = {
684           maintainers = lib.teams.lomiri.members;
685         };
687         nodes.machine =
688           { config, ... }:
689           {
690             imports = [ ./common/user-account.nix ];
692             virtualisation.memorySize = 2047;
694             users.users.${user} = {
695               inherit description;
696               password = lib.mkForce pwOutput;
697             };
699             services.desktopManager.lomiri.enable = lib.mkForce true;
700             services.displayManager.defaultSession = lib.mkForce "lomiri";
702             # Help with OCR
703             fonts.packages = [ pkgs.inconsolata ];
705             services.xserver.xkb.layout = lib.strings.concatStringsSep "," [
706               # Start with a non-QWERTY keymap to test keymap patch
707               "de"
708               # Then a QWERTY one to test switching
709               "us"
710             ];
712             # Help with OCR
713             systemd.tmpfiles.settings = {
714               "10-lomiri-test-setup" = terminalOcrTmpfilesSetup { inherit pkgs lib config; };
715             };
716           };
718         enableOCR = true;
720         testScript =
721           { nodes, ... }:
722           ''
723             def wait_for_text(text):
724                 """
725                 Wait for on-screen text, and try to optimise retry count for slow hardware.
726                 """
727                 machine.sleep(10)
728                 machine.wait_for_text(text)
730             start_all()
731             machine.wait_for_unit("multi-user.target")
733             # Lomiri in greeter mode should use the correct keymap
734             with subtest("lomiri greeter keymap works"):
735                 machine.wait_for_unit("display-manager.service")
736                 machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
738                 # Start page shows current time
739                 wait_for_text(r"(AM|PM)")
740                 machine.screenshot("lomiri_greeter_launched")
742                 # Advance to login part
743                 machine.send_key("ret")
744                 wait_for_text("${description}")
745                 machine.screenshot("lomiri_greeter_login")
747                 # Login
748                 machine.send_chars("${pwInput}\n")
749                 machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
751                 # Output rendering from Lomiri has started when it starts printing performance diagnostics
752                 machine.wait_for_console_text("Last frame took")
753                 # Look for datetime's clock, one of the last elements to load
754                 wait_for_text(r"(AM|PM)")
755                 machine.screenshot("lomiri_launched")
757             # Lomiri in desktop mode should use the correct keymap
758             with subtest("lomiri session keymap works"):
759                 machine.send_key("ctrl-alt-t")
760                 wait_for_text(r"(${user}|machine)")
761                 machine.screenshot("terminal_opens")
763                 machine.send_chars("touch ${pwInput}\n")
764                 machine.wait_for_file("/home/alice/${pwOutput}", 10)
766                 # Issues with this keybind: input leaks to focused surface, may open launcher
767                 # Don't have the keyboard indicator to handle this better
768                 machine.send_key("meta_l-spc")
769                 machine.wait_for_console_text('SET KEYMAP "us"')
771                 # Handle keybind fallout
772                 machine.sleep(10) # wait for everything to settle
773                 machine.send_key("esc") # close launcher in case it was opened
774                 machine.sleep(2) # wait for animation to finish
775                 # Make sure input leaks are gone
776                 machine.send_key("backspace")
777                 machine.send_key("backspace")
778                 machine.send_key("backspace")
779                 machine.send_key("backspace")
780                 machine.send_key("backspace")
781                 machine.send_key("backspace")
782                 machine.send_key("backspace")
783                 machine.send_key("backspace")
784                 machine.send_key("backspace")
785                 machine.send_key("backspace")
787                 machine.send_chars("touch ${pwInput}\n")
788                 machine.wait_for_file("/home/alice/${pwInput}", 10)
790                 machine.send_key("alt-f4")
791           '';
792       }
793     );