test: de-flake the suite for parallel runs (-n10 clean 4x, ~25s)
Parallel runs (-n10, needed since -n20 overwhelms the desktop compositor) exposed load-sensitive tests as failures. Fix the fixable, mark the rest flaky (all marked tests pass serially; ./test.sh -m flaky stays green in ~33s): Systemic fixes (free on success -- poll loops exit early): - Session poll-loop timeouts retuned for loaded boxes: wait_for 15->20s, wait_any_active/active_frame 10->20s, wait_app_active/ wait_surface_mapped 10->15s, wait_active/change 3->10s, cycle_ack 5->15s; drop explicit 3s waits onto the new defaults. - Startup-crown race: wait_any_active could catch a transient crown while the other tab was still pending, so four tests now settle both maps before reading the crown; alt_shift_t quiesces maps before snapshotting the transition count. - Pointer-constraint and output-mgmt poll deadlines 5/20->15/30s (per-iteration dump+parse cost under load, not logic). Flaky marks (all pass serially; fail only via load timing -- key chords/taps landing in shifted state, overlay/pixel timing, host roundtrips over the shared desktop compositor, X11 startup races, repeat-rate wall clocks, multi-client dances): hold_alt, arrows_reorder, sub_windows x4, fullscreen x3, clipboard x3, pointer x3, screen_mode, layer x2, x11 x3, startup-crown, modal, quit, plain_delete, cycle_wraps, function_keys, alt_delete x2, tab_death, gestures_roundtrip, modifier_super, no_args, alt_shift_move, close_black_tab, output_mgmt_apply, screen_mode_sets_output_size.
This commit is contained in:
parent
7e2bb18a85
commit
d8332db375
1 changed files with 88 additions and 31 deletions
|
|
@ -211,7 +211,7 @@ class Session:
|
|||
with open(self._log, "r", errors="replace") as f:
|
||||
return f.read()
|
||||
|
||||
def wait_for(self, pattern, timeout=15.0):
|
||||
def wait_for(self, pattern, timeout=20.0):
|
||||
"""Wait until `pattern` matches somewhere in the whole log.
|
||||
|
||||
Re-scans from the start each poll: kick's log lines can arrive
|
||||
|
|
@ -350,7 +350,7 @@ class Session:
|
|||
key = DOWN if direction > 0 else UP
|
||||
self.chord(ALT, key)
|
||||
|
||||
def cycle_ack(self, direction, timeout=5.0):
|
||||
def cycle_ack(self, direction, timeout=15.0):
|
||||
"""Tap Alt+arrow and block until the compositor logs a *new*
|
||||
active-tab line reflecting the cycle, then return the fresh value.
|
||||
|
||||
|
|
@ -387,7 +387,7 @@ class Session:
|
|||
m = ACTIVE_RE.findall(self.read_log())
|
||||
return int(m[-1]) if m else None
|
||||
|
||||
def wait_active(self, want, timeout=3.0):
|
||||
def wait_active(self, want, timeout=10.0):
|
||||
since = time.monotonic()
|
||||
while time.monotonic() - since < timeout:
|
||||
if self.active() == want:
|
||||
|
|
@ -395,7 +395,7 @@ class Session:
|
|||
time.sleep(0.1)
|
||||
pytest.fail(f"never reached active app={want}")
|
||||
|
||||
def wait_active_change(self, away_from, timeout=3.0):
|
||||
def wait_active_change(self, away_from, timeout=10.0):
|
||||
"""Wait for active to differ from `away_from`; return the new value.
|
||||
|
||||
Unlike wait_active(), this ignores historical log lines: it only
|
||||
|
|
@ -410,7 +410,7 @@ class Session:
|
|||
time.sleep(0.1)
|
||||
pytest.fail(f"active app never changed away from {away_from}")
|
||||
|
||||
def wait_any_active(self, timeout=10.0):
|
||||
def wait_any_active(self, timeout=20.0):
|
||||
"""Wait until the startup-selected tab has activated (its first map),
|
||||
return its index.
|
||||
|
||||
|
|
@ -430,7 +430,7 @@ class Session:
|
|||
time.sleep(0.1)
|
||||
pytest.fail("no tab was ever activated (startup tab never mapped)")
|
||||
|
||||
def wait_active_frame(self, timeout=10.0):
|
||||
def wait_active_frame(self, timeout=20.0):
|
||||
"""Wait until the active tab has painted a real frame: the composed
|
||||
(or host-presented) frame's centre is no longer the black backdrop.
|
||||
Guards single-read pixel asserts that need black to mean "overlay
|
||||
|
|
@ -446,7 +446,7 @@ class Session:
|
|||
time.sleep(0.1)
|
||||
pytest.fail("active tab never painted a frame (centre stayed black)")
|
||||
|
||||
def wait_app_active(self, index, timeout=10.0):
|
||||
def wait_app_active(self, index, timeout=15.0):
|
||||
"""Wait until the single app `index` reports itself active (for
|
||||
sessions launched with fewer than two tabs)."""
|
||||
rx = re.compile(r"active app=%d\b" % index)
|
||||
|
|
@ -457,7 +457,7 @@ class Session:
|
|||
time.sleep(0.1)
|
||||
pytest.fail(f"app {index} never logged an active line")
|
||||
|
||||
def wait_surface_mapped(self, index, timeout=10.0):
|
||||
def wait_surface_mapped(self, index, timeout=15.0):
|
||||
"""Wait until the client surface of tab `index` has mapped.
|
||||
|
||||
``wait_any_active`` returns as soon as the startup tab logs an
|
||||
|
|
@ -906,6 +906,7 @@ def test_standalone_headless_cursor_relative_motion(standalone_session):
|
|||
assert s.proc.poll() is None
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_locked_pointer_holds():
|
||||
"""A LOCKED pointer constraint pins the visible cursor: once the
|
||||
constraint activates on pointer enter, relative motion must not carry the
|
||||
|
|
@ -941,14 +942,14 @@ def test_standalone_headless_locked_pointer_holds():
|
|||
# Warp onto the surface: the constraint activates and the cursor is
|
||||
# painted at the lock position.
|
||||
s.warp(*lock)
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if "[constraint] locked on surface" in s.read_log():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
pytest.fail("locked constraint never activated")
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if distinct(patch_at(s.dump_host(), *lock), pure_lock):
|
||||
break
|
||||
|
|
@ -958,7 +959,7 @@ def test_standalone_headless_locked_pointer_holds():
|
|||
|
||||
# Relative motion must not move the pinned cursor.
|
||||
s.ptr_motion(*[target[0] - lock[0], target[1] - lock[1]])
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
path = s.dump_host()
|
||||
if distinct(patch_at(path, *lock), pure_lock) and \
|
||||
|
|
@ -972,6 +973,7 @@ def test_standalone_headless_locked_pointer_holds():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_locked_pointer_release():
|
||||
"""Dropping a locked constraint mid-session tears it down: the destroy
|
||||
path runs and the cursor is free to move again."""
|
||||
|
|
@ -993,6 +995,7 @@ def test_standalone_headless_locked_pointer_release():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_confined_pointer_clamps():
|
||||
"""A CONFINED pointer constraint keeps the visible cursor inside the
|
||||
constraint region even when relative motion would carry it outside."""
|
||||
|
|
@ -1028,14 +1031,14 @@ def test_standalone_headless_confined_pointer_clamps():
|
|||
"clamped probe should be pure client colour"
|
||||
|
||||
s.warp(*start)
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if "[constraint] confined on surface" in s.read_log():
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
pytest.fail("confined constraint never activated")
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if distinct(patch_at(s.dump_host(), *start), pure_start):
|
||||
break
|
||||
|
|
@ -1045,7 +1048,7 @@ def test_standalone_headless_confined_pointer_clamps():
|
|||
|
||||
# A big relative motion clamps to the region edge, not the raw target.
|
||||
s.ptr_motion(500, 400)
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
path = s.dump_host()
|
||||
if distinct(patch_at(path, *clamped), pure_clamped) and \
|
||||
|
|
@ -1059,6 +1062,7 @@ def test_standalone_headless_confined_pointer_clamps():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_screen_mode_cursor_stays_aligned():
|
||||
"""--screen-mode + pointer motion must keep the visible cursor exactly on
|
||||
the warp target plus the relative delta, on a non-default output size. The
|
||||
|
|
@ -1102,7 +1106,7 @@ def test_standalone_headless_screen_mode_cursor_stays_aligned():
|
|||
"moved probe area should be pure client colour"
|
||||
|
||||
s.warp(*start)
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
if distinct(patch_at(s.dump_host(), *start), before_start):
|
||||
break
|
||||
|
|
@ -1111,7 +1115,7 @@ def test_standalone_headless_screen_mode_cursor_stays_aligned():
|
|||
pytest.fail("cursor never became visible at the start target")
|
||||
|
||||
s.ptr_motion(*delta)
|
||||
deadline = time.monotonic() + 5.0
|
||||
deadline = time.monotonic() + 15.0
|
||||
while time.monotonic() < deadline:
|
||||
path = s.dump_host()
|
||||
if distinct(patch_at(path, *moved), before_moved) and \
|
||||
|
|
@ -1575,6 +1579,7 @@ def test_standalone_headless_dialog_globals(globals_session, tmp_path):
|
|||
f"missing dialog global; advertised: {sorted(names)}"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_modal_steals_tab(tmp_path):
|
||||
"""A modal dialog mapping in a background tab takes its tab: the
|
||||
dialog client opens its modal 1.5s after boot (past settle), and the
|
||||
|
|
@ -1761,7 +1766,7 @@ def test_standalone_headless_output_mgmt_apply(tmp_path):
|
|||
"%s '#ff0000'" % WL)
|
||||
try:
|
||||
s.wait_any_active()
|
||||
deadline = time.monotonic() + 20.0
|
||||
deadline = time.monotonic() + 30.0
|
||||
while time.monotonic() < deadline:
|
||||
if out.exists() and "after " in out.read_text():
|
||||
break
|
||||
|
|
@ -1773,7 +1778,7 @@ def test_standalone_headless_output_mgmt_apply(tmp_path):
|
|||
assert m and (m.group(1), m.group(2)) == ("800", "600"), \
|
||||
f"no 800x600 after line in: {text!r}"
|
||||
# A live tab reflows into the new output size.
|
||||
deadline = time.monotonic() + 15.0
|
||||
deadline = time.monotonic() + 20.0
|
||||
geom = None
|
||||
while time.monotonic() < deadline:
|
||||
if re.search(r"\[diag\] commit app=\d+ geom=800x600",
|
||||
|
|
@ -1799,6 +1804,7 @@ def _gestures_text(log_path):
|
|||
return log_path.read_text() if log_path.exists() else ""
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_gestures_roundtrip(tmp_path):
|
||||
"""Swipe/pinch/hold injected through the test feed reach a gesture
|
||||
client holding pointer focus, with exact fingers/deltas/scales."""
|
||||
|
|
@ -2142,6 +2148,7 @@ def test_dc_bridge_primary_roundtrip(tmp_path):
|
|||
|
||||
|
||||
@pytest.mark.skipif(NEED_WL_CLIPBOARD, reason="wl-clipboard tools not installed")
|
||||
@pytest.mark.flaky
|
||||
def test_clipboard_host_steal_cancels_nested_source(session):
|
||||
"""The host takes the selection while a nested source is mirrored out:
|
||||
the bridge's host-side source is cancelled (the offering wl-copy exits)
|
||||
|
|
@ -2248,6 +2255,7 @@ def test_standalone_cursor_shape_resets_on_tab_switch():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_nested_cursor_shape_forwarded():
|
||||
"""In nested mode a client's cursor-shape request is forwarded to the host
|
||||
pointer (the host draws the cursor). Deterministic when the host honours
|
||||
|
|
@ -2282,6 +2290,7 @@ def test_standalone_headless_last_tab_exits_cleanly(standalone_session):
|
|||
assert rc == 0, f"rc={rc} (139 = SIGSEGV); log tail:\n{s.read_log()[-800:]}"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_standalone_headless_screen_mode_sets_output_size():
|
||||
"""--screen-mode WxH sizes the output; on the headless backend it becomes
|
||||
the add_output() size, observable through the dumped frame dimensions."""
|
||||
|
|
@ -2484,6 +2493,7 @@ def _poll_dc_demand(sock, out_file, token, primary=False, deadline=45.0):
|
|||
|
||||
|
||||
@pytest.mark.skipif(NEED_WL_CLIPBOARD, reason="wl-clipboard tools not installed")
|
||||
@pytest.mark.flaky
|
||||
def test_clipboard_nested_to_host(session):
|
||||
token = f"kick-n2h-{os.getpid()}-{time.monotonic_ns()}"
|
||||
copy = _dc_setter(session.socket, token)
|
||||
|
|
@ -2495,6 +2505,7 @@ def test_clipboard_nested_to_host(session):
|
|||
|
||||
|
||||
@pytest.mark.skipif(NEED_WL_CLIPBOARD, reason="wl-clipboard tools not installed")
|
||||
@pytest.mark.flaky
|
||||
def test_clipboard_host_to_nested(session, tmp_path):
|
||||
token = f"kick-h2n-{os.getpid()}-{time.monotonic_ns()}"
|
||||
copy = subprocess.Popen(
|
||||
|
|
@ -2542,6 +2553,7 @@ def test_function_keys_pass_through(session):
|
|||
assert session.proc.poll() is None
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_client_fullscreen_round_trip(session):
|
||||
"""A client's fullscreen request is honored and mirrored onto the host
|
||||
window; the host configure then propagates fullscreen to every tab.
|
||||
|
|
@ -2561,6 +2573,7 @@ def test_client_fullscreen_round_trip(session):
|
|||
assert session.proc.poll() is None
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_f11_toggles_host_fullscreen(session):
|
||||
"""prefix+F11 toggles the kick window's fullscreen state in the host;
|
||||
the resulting configure propagates to every tab exactly like a
|
||||
|
|
@ -2577,6 +2590,7 @@ def test_alt_f11_toggles_host_fullscreen(session):
|
|||
assert session.proc.poll() is None
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_f11_propagates_to_stack_members():
|
||||
"""prefix+F11's host fullscreen reaches not just each tab's main window
|
||||
but every stacked tab-group member too, and leaving fullscreen clears
|
||||
|
|
@ -2629,6 +2643,7 @@ def test_cycle_wraps(session):
|
|||
assert session.wait_active_change(other) == start
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_hold_alt_down_repeats(session):
|
||||
start = session.wait_any_active()
|
||||
assert start in (0, 1)
|
||||
|
|
@ -2733,6 +2748,7 @@ def test_active_switch_on_client_exit(session):
|
|||
f"compositor exited with {session.proc.returncode} after last tab closed"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_tab_death_activates_left_neighbor(session):
|
||||
"""With three tabs [0,1,2], killing index 2 must activate its left
|
||||
neighbor (index 1), not the leftmost tab; killing the first tab (index 0)
|
||||
|
|
@ -2817,6 +2833,10 @@ def test_alt_shift_t_becomes_active(session):
|
|||
os.environ["TERMINAL"] = f"{WL} '#ffff00'"
|
||||
try:
|
||||
session.wait_any_active()
|
||||
# Quiesce boot churn first: a late-mapping tab activating after the
|
||||
# snapshot would count as an extra transition below.
|
||||
session.wait_surface_mapped(1)
|
||||
session.wait_surface_mapped(0)
|
||||
before = session.active_count()
|
||||
session.chord(ALT | SHIFT, 20)
|
||||
deadline = time.monotonic() + 15.0
|
||||
|
|
@ -2879,6 +2899,7 @@ def test_sub_window_hidden_when_parent_not_active():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_sub_window_activates_when_parent_active():
|
||||
"""--tab-groups off: a related window whose parent tab is the one being
|
||||
shown becomes the active tab on map (the second exception to the
|
||||
|
|
@ -2897,6 +2918,7 @@ def test_sub_window_activates_when_parent_active():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_sub_windows_stack_mode_never_claims_a_slot():
|
||||
"""--tab-groups on: a second toplevel from an already-running client
|
||||
rides on its owner's tab -- it logs [sub] stack, never claims a slot of
|
||||
|
|
@ -2923,6 +2945,7 @@ def test_sub_windows_stack_mode_never_claims_a_slot():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_sub_windows_stack_mode_focus_follows_tab():
|
||||
"""--tab-groups on: a stacked window of a background tab maps
|
||||
without taking the keyboard; showing its tab hands focus to it."""
|
||||
|
|
@ -2948,6 +2971,7 @@ def test_sub_windows_stack_mode_focus_follows_tab():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_sub_windows_stack_mode_unstack_refocuses_then_close():
|
||||
"""--tab-groups on: closing the stacked window ('X' in wltest)
|
||||
refocuses the tab's own surface without killing the tab; Alt+Delete on
|
||||
|
|
@ -2978,6 +3002,7 @@ def test_sub_windows_stack_mode_unstack_refocuses_then_close():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_sub_windows_stack_mode_row_title_follows_top():
|
||||
"""--tab-groups on: the overlay row shows the topmost stacked
|
||||
window's title while it is mapped, and falls back to the tab's own
|
||||
|
|
@ -3005,6 +3030,7 @@ def test_sub_windows_stack_mode_row_title_follows_top():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_delete_closes_active_window_not_tab():
|
||||
"""--tab-groups on: Alt+Delete on a tab that holds a stacked window closes
|
||||
only that window -- the tab stays up (the compositor stays alive, no new
|
||||
|
|
@ -3145,6 +3171,7 @@ def test_quit_cancel_keeps_running():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_quit_single_window_no_confirm():
|
||||
"""With a single window there is nothing to lose, so Ctrl+Alt+Q quits
|
||||
immediately -- no confirm modal, clean exit 0."""
|
||||
|
|
@ -3164,6 +3191,7 @@ def test_quit_single_window_no_confirm():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_left_right_cycle_tab_group_members():
|
||||
"""--tab-groups on: Alt+Left / Alt+Right cycle the active tab's stacked
|
||||
windows (Left = previous, Right = next) and the tab stays up; the chord is
|
||||
|
|
@ -3255,6 +3283,7 @@ def test_alt_delete_rightmost_falls_left():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_delete_second_tab_right_neighbor_two_tabs():
|
||||
"""Two-tab session: closing tab 0 makes its right neighbor (1) active."""
|
||||
if not os.environ.get("WAYLAND_DISPLAY"):
|
||||
|
|
@ -3275,6 +3304,7 @@ def test_alt_delete_second_tab_right_neighbor_two_tabs():
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_shift_arrows_reorder_and_wrap():
|
||||
"""Alt+Shift+Up/Down move the active tab within the overlay list without
|
||||
switching the active surface; moving off an end wraps to the other end and
|
||||
|
|
@ -3345,6 +3375,7 @@ def test_alt_shift_arrows_reorder_and_wrap():
|
|||
os.environ["TERMINAL"] = old_term
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_modifier_super_flag():
|
||||
"""--modifier super moves the tab-management chords to Super/Mod4: cycle,
|
||||
reorder and close all work, while the Alt chords stop doing anything."""
|
||||
|
|
@ -3358,7 +3389,7 @@ def test_modifier_super_flag():
|
|||
|
||||
# Super+Down cycles; a plain Alt+Down must not.
|
||||
s.chord(SUPER, DOWN)
|
||||
other = s.wait_active_change(start, timeout=5.0)
|
||||
other = s.wait_active_change(start, timeout=15.0)
|
||||
assert other == 1 - start, "Super+Down did not cycle the other tab"
|
||||
s.chord(ALT, DOWN)
|
||||
time.sleep(0.3)
|
||||
|
|
@ -3439,6 +3470,7 @@ def test_alt_delete_escalates_sigkill(tmp_path):
|
|||
s.proc.terminate()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_plain_delete_not_grabbed(session):
|
||||
"""Plain Delete (no Alt) must not close the active tab."""
|
||||
session.wait_any_active()
|
||||
|
|
@ -3503,6 +3535,7 @@ def test_dash_h_prints_usage():
|
|||
assert b"--tab-groups on|off" in r.stdout
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_no_args_opens_terminal():
|
||||
"""With no command-line tabs, a single terminal tab opens and maps."""
|
||||
if not os.environ.get("WAYLAND_DISPLAY"):
|
||||
|
|
@ -3551,6 +3584,7 @@ def test_missing_arg_fails():
|
|||
assert b"not found in PATH" in (r.stdout + r.stderr)
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_close_black_tab_active_last():
|
||||
"""Alt+Delete on a black tab (its command has not connected yet) closes
|
||||
it outright: the surviving tab becomes active, the black slot is removed
|
||||
|
|
@ -3793,6 +3827,7 @@ def test_child_toolkit_backends_not_forced():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_xwayland_tab_renders_and_cycles(xwayland_session):
|
||||
"""The X11 tab cycles like any other and its surface actually reaches
|
||||
the screen: after wrapping to it, the host back buffer shows xmessage's
|
||||
|
|
@ -3816,6 +3851,7 @@ def test_xwayland_tab_renders_and_cycles(xwayland_session):
|
|||
f"X11 tab rendered black at ({cx},{cy})"
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_xwayland_alt_delete_closes_x11_tab(xwayland_session):
|
||||
"""Alt+Delete on the active X11 tab sends WM_DELETE_WINDOW; xmessage
|
||||
honors it, exits, and the escalation never needs to fire."""
|
||||
|
|
@ -4047,6 +4083,7 @@ def test_mode_autodetect_stays_nested_without_console():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_x11_leader_window_never_becomes_tab():
|
||||
"""A GTK-style X11 app (unmapped leader window created first, real frame
|
||||
second) gets exactly ONE tab -- the mapped frame, rendered live. The
|
||||
|
|
@ -4087,6 +4124,7 @@ def test_x11_leader_window_never_becomes_tab():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_x11_menu_popup_attaches_as_member():
|
||||
"""An instantly-mapped managed transient -- GTK's X11 menu shape --
|
||||
attaches as a tab-group member of its owner through the map-time
|
||||
|
|
@ -4171,6 +4209,7 @@ def test_touch_delivery_to_client():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_alt_shift_move_no_member_is_noop():
|
||||
"""Shift+Left/Right with the prefix held on a tab whose focused member
|
||||
is the main surface still gets consumed (never leaks Shift+arrow to
|
||||
|
|
@ -4204,6 +4243,7 @@ def _parse_diag_geom(log, app_index):
|
|||
return tuple(int(v) for v in best) if best else None
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_layer_exclusive_reserves_tabs():
|
||||
"""A layer surface with exclusive_zone > 0 must shrink the usable area
|
||||
that maximized tabs are sized into (platform-verifies the two-pass
|
||||
|
|
@ -4256,6 +4296,7 @@ def test_layer_exclusive_reserves_tabs():
|
|||
s.close()
|
||||
|
||||
|
||||
@pytest.mark.flaky
|
||||
def test_layer_zero_exclusive_does_not_resize():
|
||||
"""A layer surface with exclusive_zone == 0 must NOT shrink the usable
|
||||
area -- tabs keep their full output size."""
|
||||
|
|
@ -5661,13 +5702,17 @@ def test_alt_tab_walks_windows(session):
|
|||
|
||||
The session fixture's last tab is startup-active (app 1), so it is the
|
||||
top of the still-uncommitted MRU stack; one Alt+Tab previews app 0."""
|
||||
start = session.wait_any_active()
|
||||
assert start == 1, f"expected startup tab 1 active, got {start}"
|
||||
session.wait_any_active()
|
||||
# Settle first: wait_any_active can catch a transient crown while the
|
||||
# other tab is still pending, so only read the crown once both tabs
|
||||
# have mapped (no further auto-activations happen after that).
|
||||
session.wait_surface_mapped(1)
|
||||
session.wait_surface_mapped(0)
|
||||
start = session.active()
|
||||
assert start == 1, f"expected startup tab 1 active, got {start}"
|
||||
|
||||
session.chord(ALT, TAB)
|
||||
session.wait_active(0, timeout=3.0)
|
||||
session.wait_active(0)
|
||||
assert session.active() == 0
|
||||
|
||||
|
||||
|
|
@ -5677,36 +5722,48 @@ def test_alt_tab_commit_on_release(session):
|
|||
|
||||
First Alt+Tab moves from app 1 to app 0 (MRU [1,0]); release commits
|
||||
app 0 to the front (MRU [0,1]); a second Alt+Tab steps back to app 1."""
|
||||
start = session.wait_any_active()
|
||||
assert start == 1
|
||||
session.wait_any_active()
|
||||
# Settle first: wait_any_active can catch a transient crown while the
|
||||
# other tab is still pending, so only read the crown once both tabs
|
||||
# have mapped (no further auto-activations happen after that).
|
||||
session.wait_surface_mapped(1)
|
||||
session.wait_surface_mapped(0)
|
||||
start = session.active()
|
||||
assert start == 1
|
||||
|
||||
session.chord(ALT, TAB)
|
||||
session.wait_active(0, timeout=3.0)
|
||||
session.wait_active(0)
|
||||
session.chord(ALT, TAB)
|
||||
session.wait_active(1, timeout=3.0)
|
||||
session.wait_active(1)
|
||||
assert session.active() == 1
|
||||
|
||||
|
||||
def test_alt_1_synonymous_with_tab(session):
|
||||
"""Alt+1 previews the next window just like Alt+Tab."""
|
||||
start = session.wait_any_active()
|
||||
assert start == 1
|
||||
session.wait_any_active()
|
||||
# Settle first: wait_any_active can catch a transient crown while the
|
||||
# other tab is still pending, so only read the crown once both tabs
|
||||
# have mapped (no further auto-activations happen after that).
|
||||
session.wait_surface_mapped(1)
|
||||
session.wait_surface_mapped(0)
|
||||
start = session.active()
|
||||
assert start == 1
|
||||
session.chord(ALT, ONE)
|
||||
session.wait_active(0, timeout=3.0)
|
||||
session.wait_active(0)
|
||||
assert session.active() == 0
|
||||
|
||||
|
||||
def test_alt_tab_hold_repeats(session):
|
||||
"""Holding Alt+Tab keeps walking the MRU stack via synthesized repeats,
|
||||
without reshuffling the stack mid-hold."""
|
||||
start = session.wait_any_active()
|
||||
assert start == 1
|
||||
session.wait_any_active()
|
||||
# Settle first: wait_any_active can catch a transient crown while the
|
||||
# other tab is still pending, so only read the crown once both tabs
|
||||
# have mapped (no further auto-activations happen after that).
|
||||
session.wait_surface_mapped(1)
|
||||
session.wait_surface_mapped(0)
|
||||
start = session.active()
|
||||
assert start == 1
|
||||
base = session.active_count()
|
||||
session.mods(ALT)
|
||||
session.key(TAB, KEY_PRESSED)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue