Files
debianito-post-install/tests/desktops.bats
T
stornic56 9a11ab8981 GNOME: use ptyxis instead of gnome-terminal on trixie+
gnome-core declares the OR-dependency "gnome-terminal (>= 3.56)
or gnome-console (>= 46) or ptyxis (>= 47)" (packages.debian.org
/trixie/gnome-core, verified 2026-10-16) and apt resolves an
unset OR-dep with the FIRST alternative, so the desktop pull would
install gnome-terminal by default. Listing the terminal package
explicitly in the same apt transaction steers the resolver to
ptyxis (48.5-1~deb13u1, GTK4) on trixie.

- _gnome_terminal_pkg(): trixie -> ptyxis, anything else or
  empty -> gnome-terminal (ptyxis does not exist in bookworm).
- _install_gnome_full/_install_gnome_core list the resolved
  package in the pre-apply summary and in the apt command.
- No purge or apt-mark: an already-installed gnome-terminal is
  left alone and both can coexist.
- 4 new BATS tests capture the apt command via the existing
  sudo/_run_cmd mocks: ptyxis on trixie (Full and Core),
  gnome-terminal fallback on bookworm and on empty codename.
2026-10-21 15:29:26 -05:00

257 lines
8.5 KiB
Bash

#!/usr/bin/env bash
# desktops.bats — Offline tests for the desktop helpers in
# modules/desktop_display.sh (SP2) and the desktop module
# wiring in _load_module (SP2.5).
#
# No `set -euo pipefail` here: BATS controls error handling
# per test (see utils.bats).
#
# State-mutating functions are called DIRECTLY (a `run`
# subshell would lose their globals); pure-output helpers
# use `run`.
#
# 100% offline: dpkg and whiptail come from the PATH mocks
# in test_helper.bash, systemctl uses the helper mock,
# /etc/X11/default-display-manager is redirected through the
# DM_FILE fixture, and the real audio installer is replaced
# by a call-counter stub.
load test_helper.bash
# Stub for modules/system/audio.sh (never loaded here):
# count calls instead of running apt. Counter reset in setup().
_install_pipewire_standard() {
PIPEWIRE_INSTALL_CALLS=$((PIPEWIRE_INSTALL_CALLS + 1))
}
setup() {
mock_reset
load_utils
# load_utils() sources utils.sh inside a function, so the
# top-level `declare -A _LOADED_MODULES` (utils.sh:48) was
# function-local and vanished when it returned — the same
# caveat the helper documents for the ETH_* arrays.
# Re-declare it globally for the _load_module tests.
declare -gA _LOADED_MODULES=()
# Only function definitions at the top level: safe to source.
# Only function definitions at the top level: safe to source.
source "${TEST_PROJECT_ROOT}/modules/desktop_display.sh"
# The GNOME installers are pure function definitions too;
# desktop_display.sh (their DM/pipewire collaborators) is
# already sourced above.
source "${TEST_PROJECT_ROOT}/modules/desktops/desktop_gnome.sh"
# Fixture redirect for /etc/X11/default-display-manager:
# the file exists but is empty, so `-s` fails and the
# systemctl fallback is exercised deterministically.
DM_FILE="$(mktemp)"
export DM_FILE
PIPEWIRE_INSTALL_CALLS=0
}
teardown() {
if [ -n "${DM_FILE:-}" ]; then
rm -f "$DM_FILE"
fi
if [ -n "${MOCK_SUDO_LOG:-}" ]; then
rm -f "$MOCK_SUDO_LOG"
fi
if [ -n "${MOCK_APT_ROOT:-}" ]; then
rm -rf "$MOCK_APT_ROOT"
fi
}
# ── _existing_display_manager ────────────────────────
@test "_existing_display_manager reads the default DM file" {
printf '/usr/sbin/gdm3\n' >"$DM_FILE"
run _existing_display_manager
[ "$status" -eq 0 ]
[ "$output" = "gdm3" ]
}
@test "_existing_display_manager prefers the DM file over an enabled unit" {
printf '/usr/sbin/sddm\n' >"$DM_FILE"
export MOCK_SYSTEMCTL_ENABLED="gdm3"
run _existing_display_manager
[ "$status" -eq 0 ]
[ "$output" = "sddm" ]
}
@test "_existing_display_manager falls back to an enabled systemd unit" {
# DM_FILE exists but is empty: `-s` must fail, not `-e`.
export MOCK_SYSTEMCTL_ENABLED="lightdm"
run _existing_display_manager
[ "$status" -eq 0 ]
[ "$output" = "lightdm" ]
}
@test "_existing_display_manager evaluates units in candidate order" {
# gdm3 → lightdm → sddm → greetd → gdm, whatever order
# systemctl reports them in.
export MOCK_SYSTEMCTL_ENABLED="sddm lightdm"
run _existing_display_manager
[ "$status" -eq 0 ]
[ "$output" = "lightdm" ]
}
@test "_existing_display_manager falls back when the DM file is missing" {
rm -f "$DM_FILE"
export MOCK_SYSTEMCTL_ENABLED="greetd"
run _existing_display_manager
[ "$status" -eq 0 ]
[ "$output" = "greetd" ]
}
@test "_existing_display_manager prints nothing when nothing is configured" {
run _existing_display_manager
[ "$status" -eq 0 ]
[ -z "$output" ]
}
# ── _offer_pipewire_after_desktop ────────────────────
@test "_offer_pipewire_after_desktop skips without prompting when pipewire-audio is installed" {
MOCK_DPKG_INSTALLED="pipewire-audio"
WHIPTAIL_CALLS=0
whiptail() { WHIPTAIL_CALLS=$((WHIPTAIL_CALLS + 1)); }
_offer_pipewire_after_desktop
[ "$WHIPTAIL_CALLS" -eq 0 ]
[ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ]
}
@test "_offer_pipewire_after_desktop installs the stack when accepted" {
export MOCK_WHIPTAIL_RC=0 # user answers "Yes"
_offer_pipewire_after_desktop
[ "$PIPEWIRE_INSTALL_CALLS" -eq 1 ]
}
@test "_offer_pipewire_after_desktop returns 0 without installing when declined" {
export MOCK_WHIPTAIL_RC=1 # user answers "No"
offer_rc=0
_offer_pipewire_after_desktop || offer_rc=$?
[ "$offer_rc" -eq 0 ]
[ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ]
}
@test "_offer_pipewire_after_desktop checks the pipewire package on Bullseye" {
export DEBIAN_VERSION="11"
MOCK_DPKG_INSTALLED="pipewire" # Bullseye package name
_offer_pipewire_after_desktop
[ "$PIPEWIRE_INSTALL_CALLS" -eq 0 ]
}
@test "_offer_pipewire_after_desktop ignores pipewire-audio on Bullseye" {
export DEBIAN_VERSION="11"
MOCK_DPKG_INSTALLED="pipewire-audio" # wrong name on 11
export MOCK_WHIPTAIL_RC=0 # user accepts
_offer_pipewire_after_desktop
[ "$PIPEWIRE_INSTALL_CALLS" -eq 1 ]
}
# ── _load_module: desktop wiring (SP2.5) ─────────────
# The dependency cascade is not under test here: mark the
# transitive deps of the desktop modules as loaded so only
# the module file itself is sourced.
_load_module_with_deps_marked() {
local module="$1"
declare -gA _LOADED_MODULES=(
[desktop_display]=1
[audio]=1
[repos]=1
[repo_detect]=1
)
_load_module "$module"
}
@test "_load_module xfce sources modules/desktops/desktop_xfce.sh" {
_load_module_with_deps_marked xfce
type xfce_menu >/dev/null
type _install_xfce_custom >/dev/null
type _xfce_polkit_rules >/dev/null
}
@test "_load_module lxde sources modules/desktops/desktop_lxde.sh" {
_load_module_with_deps_marked lxde
type lxde_menu >/dev/null
type _install_lxde_full >/dev/null
}
@test "_load_module gnome sources modules/desktops/desktop_gnome.sh" {
_load_module_with_deps_marked gnome
type gnome_menu >/dev/null
type _install_gnome_full >/dev/null
}
@test "_load_module kde sources modules/desktops/desktop_kde.sh" {
_load_module_with_deps_marked kde
type kde_menu >/dev/null
type _install_kde_minimal >/dev/null
}
@test "_load_module returns 1 for an unknown module" {
run _load_module no_such_module
[ "$status" -eq 1 ]
}
# ── terminal selection: ptyxis on trixie ──────────────
# gnome-core declares the OR-dep "gnome-terminal or
# gnome-console or ptyxis"; apt resolves an unset OR-dep
# with the first alternative, so listing the terminal
# explicitly in the same apt transaction is what steers
# the resolver. These tests assert the exact package
# that lands in the apt command.
#
# _gnome_capture_apt_cmd runs one installer with every
# external effect stubbed: _run_cmd is replaced so the
# apt command is captured instead of executed, and the
# PATH sudo mock stays in dry-run mode so debconf and
# systemctl invocations are logged, not executed.
_gnome_capture_apt_cmd() {
local installer="$1"
GNOME_APT_CMD=""
_confirm_install_list() { return 0; }
_ensure_apt_updated() { :; }
_run_cmd() { GNOME_APT_CMD="$2"; }
"$installer"
printf '%s\n' "$GNOME_APT_CMD"
}
@test "GNOME Full installs ptyxis on trixie" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 ptyxis"* ]]
[[ "$output" != *gnome-terminal* ]]
}
@test "GNOME Core installs ptyxis on trixie" {
export DEBIAN_CODENAME="trixie"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_core
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome-core gdm3 ptyxis"* ]]
[[ "$output" != *gnome-terminal* ]]
}
@test "GNOME Full falls back to gnome-terminal off trixie" {
export DEBIAN_CODENAME="bookworm"
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 gnome-terminal"* ]]
[[ "$output" != *ptyxis* ]]
}
@test "GNOME falls back to gnome-terminal when the codename is empty" {
# /etc/os-release missing or without VERSION_CODENAME
# leaves DEBIAN_CODENAME unset: the safe default is the
# historical behaviour.
export DEBIAN_CODENAME=""
export MOCK_SUDO_DRY_RUN=1
run _gnome_capture_apt_cmd _install_gnome_full
[ "$status" -eq 0 ]
[[ "$output" == *"apt install -y gnome gdm3 gnome-terminal"* ]]
[[ "$output" != *ptyxis* ]]
}