diff --git a/ports/shared/tui.txt b/ports/shared/tui.txt index b591a1294..2ad8c88c9 100644 --- a/ports/shared/tui.txt +++ b/ports/shared/tui.txt @@ -542,6 +542,15 @@ rb_font_restore() { RB_FONT_BACK="" } +# Whether a job this shell started is still running. +rb_job_running() { + local job + for job in $(jobs -rp); do + [ "$job" = "$1" ] && return 0 + done + return 1 +} + # The number of the console in front, which a switch has to come back to. rb_active_vt() { local active @@ -558,13 +567,15 @@ rb_active_vt() { # chvt, and whether the console really came to the front. chvt waits for the # switch (VT_WAITACTIVE): a compositor that keeps the display would hold it -# forever, so it gets 3 s. +# forever, so it gets 3 s. The switch is done once chvt has ended too: the +# console to go back to is often in front already, and returning on that +# alone left the launcher ending before a chvt started late had run. rb_chvt() { local pid waited=0 $ESUDO chvt "$1" >> "$RB_LOG" 2>&1 & pid=$! while [ "$waited" -lt 12 ]; do - [ "$(rb_active_vt)" = "$1" ] && return 0 + [ "$(rb_active_vt)" = "$1" ] && ! rb_job_running "$pid" && return 0 rb_nap 0.25 waited=$((waited + 1)) done diff --git a/tests/test_handheld_launchers.py b/tests/test_handheld_launchers.py index d0d0cf8e4..835376c61 100644 --- a/tests/test_handheld_launchers.py +++ b/tests/test_handheld_launchers.py @@ -625,6 +625,8 @@ printf '%s\\n' "$(basename "$0") $*" >> "$FAKE_STATE/tools.log" # compositor that keeps the display (FAKE_CHVT_REFUSED) leaves chvt waiting # in VT_WAITACTIVE and the console in front unchanged. FAKE_CHVT = """#!/bin/sh +# A loaded machine starts the process late (FAKE_CHVT_DELAY seconds). +[ -n "$FAKE_CHVT_DELAY" ] && sleep "$FAKE_CHVT_DELAY" printf '%s\\n' "chvt $*" >> "$FAKE_STATE/tools.log" [ -n "$FAKE_CHVT_REFUSED" ] && exec sleep 30 [ -n "$RB_VT_ACTIVE" ] && printf 'tty%s\\n' "$1" > "$RB_VT_ACTIVE" @@ -1509,13 +1511,13 @@ class TheBatoceraPort(unittest.TestCase): self.assertEqual(chvt, [f"chvt {self.number}", f"chvt {self.home}"]) device.assert_console_restored() - def test_a_refused_console_switch_shows_nothing_on_an_unseen_console(self): + def test_a_refused_console_switch_shows_nothing_on_an_unseen_console(self, **slow): """The compositor keeps the display: chvt waits and the console in front stays the same. The launcher switches back and stops, instead of an interface no key reaches.""" device, ports = self.device(FAKE_PGREP="0") proc = device.run(["/bin/bash", str(ports / "RetroBIOS.sh")], FAKE_CHVT_REFUSED="1", - **self.console_switch(device)) + **self.console_switch(device), **slow) self.assertEqual(proc.returncode, 125) self.assertLess(device.elapsed, 20) self.assertEqual(device.tui_calls(), []) @@ -1526,6 +1528,23 @@ class TheBatoceraPort(unittest.TestCase): self.assertIn(f"console {self.number} did not come to the front", log) self.assertNotIn("\x1bc", device.screen) + def test_the_switch_back_is_waited_for_on_a_slow_machine(self): + """The console to go back to was already in front, so rb_chvt + returned before its chvt had even started, and the launcher ended + with the switch back still pending: under load the log missed + "chvt 2". A chvt that starts 1.2 s late made it fail every time.""" + self.test_a_refused_console_switch_shows_nothing_on_an_unseen_console( + FAKE_CHVT_DELAY="1.2") + + def test_a_slow_console_switch_still_comes_back(self): + device, ports = self.device(FAKE_PGREP="0") + proc = device.run(["/bin/bash", str(ports / "RetroBIOS.sh")], FAKE_CHVT_DELAY="1.2", + **self.console_switch(device)) + self.assertEqual(proc.returncode, 0, proc.stderr.decode()) + chvt = [line for line in device.tools() if line.startswith("chvt")] + self.assertEqual(chvt, [f"chvt {self.number}", f"chvt {self.home}"]) + self.assertEqual((device.root / "active").read_text(), f"tty{self.home}\n") + def test_xterm_that_cannot_open_hands_over_to_the_console(self): device, ports = self.device(DISPLAY=":0", FAKE_PGREP="0") device.tool("xterm", FAKE_TERMAPP)