feat: build CouchOS Melee appliance
Some checks failed
ci / verify (push) Has been cancelled

This commit is contained in:
jackmhny
2026-08-03 02:52:28 +02:00
commit 04ef2a0d74
42 changed files with 3671 additions and 0 deletions

11
tests/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
"""Test package bootstrap for src-layout unittest discovery."""
from __future__ import annotations
import sys
from pathlib import Path
SRC_DIR = Path(__file__).resolve().parents[1] / "src"
if str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))

235
tests/test_couchd.py Normal file
View File

@@ -0,0 +1,235 @@
import unittest
from couchd.app import App, Config, ConfigError, Response, validate_bind_address
from couchd.discovery import DiscoveryError
class BindValidationTests(unittest.TestCase):
def test_rejects_loopback_address(self) -> None:
with self.assertRaises(ConfigError):
validate_bind_address("127.0.0.1")
def test_rejects_unspecified_address(self) -> None:
with self.assertRaises(ConfigError):
validate_bind_address("0.0.0.0")
def test_rejects_non_tailscale_address(self) -> None:
with self.assertRaises(ConfigError):
validate_bind_address("192.168.1.50")
def test_accepts_configured_tailscale_address(self) -> None:
self.assertEqual(validate_bind_address("100.64.0.15"), "100.64.0.15")
class AuthTests(unittest.TestCase):
def test_status_requires_bearer_token(self) -> None:
app = App(Config(bind_address="100.64.0.15", token="secret"))
response = app.handle_request("GET", "/status", headers={})
self.assertEqual(response, Response(401, {"error": "missing or invalid bearer token"}))
def test_status_returns_json_payload_when_authenticated(self) -> None:
app = App(
Config(bind_address="100.64.0.15", token="secret"),
status_provider=lambda: {
"state": "idle",
"pid": None,
"display": {"name": "HDMI-1", "mode": "1920x1080@60"},
"slippi_version": None,
"controller": {"connected": False, "name": "Xbox Wireless Controller"},
},
)
response = app.handle_request(
"GET",
"/status",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.body["state"], "idle")
self.assertEqual(response.body["display"]["mode"], "1920x1080@60")
def test_status_accepts_safe_query_string(self) -> None:
app = App(
Config(bind_address="100.64.0.15", token="secret"),
status_provider=lambda: {"state": "idle", "pid": None},
)
response = app.handle_request(
"GET",
"/status?cache_bust=1",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.body["state"], "idle")
class RouteTests(unittest.TestCase):
def test_launch_melee_uses_allowlisted_action(self) -> None:
calls: list[str] = []
class Launcher:
def launch_melee(self) -> dict[str, object]:
calls.append("melee")
return {"state": "launching", "pid": 4321}
def open_slippi(self) -> dict[str, object]:
calls.append("slippi")
return {"state": "launcher", "pid": 4322}
def stop(self) -> dict[str, object]:
calls.append("stop")
return {"state": "stopped", "pid": None}
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=Launcher(),
)
response = app.handle_request(
"POST",
"/launch/melee",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(calls, ["melee"])
self.assertEqual(response, Response(202, {"state": "launching", "pid": 4321}))
def test_open_slippi_uses_allowlisted_action(self) -> None:
calls: list[str] = []
class Launcher:
def launch_melee(self) -> dict[str, object]:
calls.append("melee")
return {"state": "launching", "pid": 4321}
def open_slippi(self) -> dict[str, object]:
calls.append("slippi")
return {"state": "launcher", "pid": 4322}
def stop(self) -> dict[str, object]:
calls.append("stop")
return {"state": "stopped", "pid": None}
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=Launcher(),
)
response = app.handle_request(
"POST",
"/open/slippi",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(calls, ["slippi"])
self.assertEqual(response, Response(202, {"state": "launcher", "pid": 4322}))
def test_stop_uses_allowlisted_action(self) -> None:
calls: list[str] = []
class Launcher:
def launch_melee(self) -> dict[str, object]:
calls.append("melee")
return {"state": "launching", "pid": 4321}
def open_slippi(self) -> dict[str, object]:
calls.append("slippi")
return {"state": "launcher", "pid": 4322}
def stop(self) -> dict[str, object]:
calls.append("stop")
return {"state": "stopped", "pid": None}
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=Launcher(),
)
response = app.handle_request(
"POST",
"/stop",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(calls, ["stop"])
self.assertEqual(response, Response(202, {"state": "stopped", "pid": None}))
def test_rejects_nonempty_body_for_action_route(self) -> None:
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=None,
)
response = app.handle_request(
"POST",
"/stop",
headers={"Authorization": "Bearer secret"},
body=b"x",
)
self.assertEqual(response, Response(400, {"error": "request body is not allowed"}))
def test_rejects_query_variant_of_unknown_route(self) -> None:
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=None,
)
response = app.handle_request(
"POST",
"/launch/unknown?x=1",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(response, Response(404, {"error": "not found"}))
def test_expected_launcher_failures_become_json_conflicts(self) -> None:
class Launcher:
def launch_melee(self) -> dict[str, object]:
raise DiscoveryError("ISO missing")
def open_slippi(self) -> dict[str, object]:
raise AssertionError("not expected")
def stop(self) -> dict[str, object]:
raise AssertionError("not expected")
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=Launcher(),
)
response = app.handle_request(
"POST",
"/launch/melee",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(response, Response(409, {"error": "ISO missing"}))
def test_unexpected_launcher_failures_become_json_service_errors(self) -> None:
class Launcher:
def launch_melee(self) -> dict[str, object]:
raise RuntimeError("boom")
def open_slippi(self) -> dict[str, object]:
raise AssertionError("not expected")
def stop(self) -> dict[str, object]:
raise AssertionError("not expected")
app = App(
Config(bind_address="100.64.0.15", token="secret"),
launcher=Launcher(),
)
response = app.handle_request(
"POST",
"/launch/melee",
headers={"Authorization": "Bearer secret"},
)
self.assertEqual(response, Response(503, {"error": "launcher action failed"}))

23
tests/test_discovery.py Normal file
View File

@@ -0,0 +1,23 @@
from pathlib import Path
import unittest
from couchd.discovery import AllowlistError, allowlisted_target
class AllowlistTests(unittest.TestCase):
def test_rejects_target_outside_allowed_roots(self) -> None:
with self.assertRaises(AllowlistError):
allowlisted_target(
candidate=Path("/tmp/SlippiLauncher.AppImage"),
allowed_roots=[Path("/home/couch/Applications")],
allowed_names={"SlippiLauncher.AppImage"},
)
def test_accepts_allowed_target_name_under_allowed_root(self) -> None:
target = allowlisted_target(
candidate=Path("/home/couch/Applications/SlippiLauncher.AppImage"),
allowed_roots=[Path("/home/couch/Applications")],
allowed_names={"SlippiLauncher.AppImage"},
)
self.assertEqual(target, Path("/home/couch/Applications/SlippiLauncher.AppImage"))

184
tests/test_display_audio.py Normal file
View File

@@ -0,0 +1,184 @@
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
class DisplayAudioHelperTests(unittest.TestCase):
def test_prefers_hdmi_1080p_mode_disables_edp_and_selects_hdmi_sink(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
bin_dir = temp_path / "bin"
state_dir = temp_path / "state"
bin_dir.mkdir()
state_dir.mkdir()
log_file = state_dir / "calls.log"
(bin_dir / "xrandr").write_text(
f"""#!/usr/bin/env bash
set -Eeuo pipefail
if [[ "${{1:-}}" == "--query" ]]; then
cat <<'EOF'
HDMI-1 connected primary 1920x1080+0+0
1920x1080 60.00*+ 59.94
eDP-1 connected 1920x1080+1920+0
1920x1080 120.00*+ 60.00
EOF
exit 0
fi
printf 'xrandr %s\\n' "$*" >>"{log_file}"
""",
encoding="utf-8",
)
(bin_dir / "pactl").write_text(
f"""#!/usr/bin/env bash
set -Eeuo pipefail
case "$*" in
"list short cards")
cat <<'EOF'
1\talsa_card.pci-0000_00_1f.3\tmodule-alsa-card.c
EOF
;;
"list short sinks")
cat <<'EOF'
0\talsa_output.pci-0000_00_1f.3.analog-stereo\tmodule-alsa-card.c\ts16le 2ch 44100Hz\tSUSPENDED
1\talsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1\tmodule-alsa-card.c\ts16le 2ch 44100Hz\tIDLE
EOF
;;
"list cards")
cat <<'EOF'
Card #1
\tName: alsa_card.pci-0000_00_1f.3
\tProfiles:
\t\toutput:analog-stereo: Analog Stereo (available: yes)
\t\toutput:hdmi-stereo-extra1: Digital Stereo (HDMI 2) (available: yes)
EOF
;;
set-card-profile*|set-default-sink*)
printf 'pactl %s\\n' "$*" >>"{log_file}"
;;
*)
exit 1
;;
esac
""",
encoding="utf-8",
)
(bin_dir / "logger").write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8")
for path in bin_dir.iterdir():
path.chmod(0o755)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/configure_display_audio.sh")],
env={**os.environ, "PATH": f"{bin_dir}:{os.environ['PATH']}"},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
calls = log_file.read_text(encoding="utf-8")
self.assertIn(
"xrandr --output HDMI-1 --primary --mode 1920x1080 --rate 60.00 --output eDP-1 --off",
calls,
)
self.assertIn(
"pactl set-card-profile alsa_card.pci-0000_00_1f.3 output:hdmi-stereo-extra1",
calls,
)
self.assertIn(
"pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1",
calls,
)
def test_requeries_sink_after_switching_hdmi_profile(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
bin_dir = temp_path / "bin"
state_dir = temp_path / "state"
bin_dir.mkdir()
state_dir.mkdir()
log_file = state_dir / "calls.log"
sink_state = state_dir / "sink-state"
sink_state.write_text("before", encoding="utf-8")
(bin_dir / "xrandr").write_text(
f"""#!/usr/bin/env bash
set -Eeuo pipefail
if [[ "${{1:-}}" == "--query" ]]; then
cat <<'EOF'
HDMI-1 connected primary 1920x1080+0+0
1920x1080 60.00*+ 59.94
eDP-1 connected 1920x1080+1920+0
1920x1080 120.00*+ 60.00
EOF
exit 0
fi
printf 'xrandr %s\\n' "$*" >>"{log_file}"
""",
encoding="utf-8",
)
(bin_dir / "pactl").write_text(
f"""#!/usr/bin/env bash
set -Eeuo pipefail
case "$*" in
"list cards")
cat <<'EOF'
Card #1
\tName: alsa_card.pci-0000_00_1f.3
\tProfiles:
\t\toutput:analog-stereo: Analog Stereo (available: yes)
\t\toutput:hdmi-stereo-extra1: Digital Stereo (HDMI 2) (available: yes)
EOF
;;
"list short sinks")
if [[ "$(cat "{sink_state}")" == "after" ]]; then
cat <<'EOF'
1\talsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1\tmodule-alsa-card.c\ts16le 2ch 44100Hz\tIDLE
EOF
else
cat <<'EOF'
0\talsa_output.pci-0000_00_1f.3.analog-stereo\tmodule-alsa-card.c\ts16le 2ch 44100Hz\tSUSPENDED
EOF
fi
;;
set-card-profile*)
printf 'pactl %s\\n' "$*" >>"{log_file}"
printf 'after' >"{sink_state}"
;;
set-default-sink*)
printf 'pactl %s\\n' "$*" >>"{log_file}"
;;
*)
exit 1
;;
esac
""",
encoding="utf-8",
)
(bin_dir / "logger").write_text("#!/usr/bin/env bash\nexit 0\n", encoding="utf-8")
for path in bin_dir.iterdir():
path.chmod(0o755)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/configure_display_audio.sh")],
env={**os.environ, "PATH": f"{bin_dir}:{os.environ['PATH']}"},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
calls = log_file.read_text(encoding="utf-8")
self.assertIn(
"pactl set-card-profile alsa_card.pci-0000_00_1f.3 output:hdmi-stereo-extra1",
calls,
)
self.assertIn(
"pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo-extra1",
calls,
)

163
tests/test_launcher.py Normal file
View File

@@ -0,0 +1,163 @@
import json
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from couchd.launcher import (
LaunchRequest,
ProcessRegistry,
RunningProcess,
default_kill,
default_terminate,
)
class DuplicateLaunchTests(unittest.TestCase):
def test_refuses_second_launch_when_pid_is_still_alive(self) -> None:
launches: list[LaunchRequest] = []
registry = ProcessRegistry(
is_pid_running=lambda pid: pid == 4321,
spawn=lambda request: launches.append(request) or 9876,
process_probe=lambda: None,
process_identity=lambda pid: "proc-4321" if pid == 4321 else None,
)
registry.record_running(pid=4321, state="melee")
result = registry.launch(LaunchRequest(kind="melee", argv=["/bin/true"]))
self.assertEqual(result["state"], "already_running")
self.assertEqual(result["pid"], 4321)
self.assertEqual(launches, [])
def test_stop_escalates_from_term_to_kill_after_timeout(self) -> None:
signals: list[tuple[str, int]] = []
checks = iter([True, True, True, False])
registry = ProcessRegistry(
is_pid_running=lambda pid: next(checks),
spawn=lambda request: 9876,
terminate=lambda pid: signals.append(("TERM", pid)),
kill=lambda pid: signals.append(("KILL", pid)),
sleep=lambda seconds: None,
process_probe=lambda: None,
process_identity=lambda pid: "proc-4321" if pid == 4321 else None,
)
registry.record_running(pid=4321, state="melee")
result = registry.stop(timeout_seconds=0.0)
self.assertEqual(result, {"state": "stopped", "pid": None})
self.assertEqual(signals, [("TERM", 4321), ("KILL", 4321)])
def test_adopts_running_process_from_probe_when_state_file_is_stale(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
state_file = Path(temp_dir) / "state.json"
state_file.write_text(json.dumps({"pid": 9999, "state": "melee"}), encoding="utf-8")
launches: list[LaunchRequest] = []
registry = ProcessRegistry(
is_pid_running=lambda pid: False,
spawn=lambda request: launches.append(request) or 7777,
state_file=state_file,
process_probe=lambda: RunningProcess(pid=4321, state="melee", identity="proc-a"),
process_identity=lambda pid: "proc-a" if pid == 4321 else None,
)
result = registry.launch(LaunchRequest(kind="melee", argv=["/bin/true"]))
self.assertEqual(result, {"state": "already_running", "pid": 4321})
self.assertEqual(launches, [])
def test_ignores_corrupt_state_file_and_recovers(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
state_file = Path(temp_dir) / "state.json"
state_file.write_text("{not-json", encoding="utf-8")
registry = ProcessRegistry(
is_pid_running=lambda pid: False,
spawn=lambda request: 9876,
state_file=state_file,
process_probe=lambda: None,
process_identity=lambda pid: None,
)
self.assertEqual(registry.current_state(), {"state": "idle", "pid": None})
def test_refuses_to_signal_reused_pid_with_different_identity(self) -> None:
signals: list[tuple[str, int]] = []
with tempfile.TemporaryDirectory() as temp_dir:
state_file = Path(temp_dir) / "state.json"
state_file.write_text(
json.dumps({"pid": 4321, "state": "melee", "identity": "old-proc"}),
encoding="utf-8",
)
registry = ProcessRegistry(
is_pid_running=lambda pid: True,
spawn=lambda request: 9876,
terminate=lambda pid: signals.append(("TERM", pid)),
kill=lambda pid: signals.append(("KILL", pid)),
state_file=state_file,
process_probe=lambda: None,
process_identity=lambda pid: "new-proc",
)
result = registry.stop(timeout_seconds=0.0)
self.assertEqual(result, {"state": "stopped", "pid": None})
self.assertEqual(signals, [])
def test_refuses_to_signal_same_executable_pid_reuse_when_start_time_changes(self) -> None:
signals: list[tuple[str, int]] = []
with tempfile.TemporaryDirectory() as temp_dir:
state_file = Path(temp_dir) / "state.json"
state_file.write_text(
json.dumps(
{
"pid": 4321,
"state": "melee",
"identity": "1000:/home/couch/Applications/Dolphin-x86_64.AppImage:111",
}
),
encoding="utf-8",
)
registry = ProcessRegistry(
is_pid_running=lambda pid: True,
spawn=lambda request: 9876,
terminate=lambda pid: signals.append(("TERM", pid)),
kill=lambda pid: signals.append(("KILL", pid)),
state_file=state_file,
process_probe=lambda: None,
process_identity=lambda pid: "1000:/home/couch/Applications/Dolphin-x86_64.AppImage:222",
)
result = registry.stop(timeout_seconds=0.0)
self.assertEqual(result, {"state": "stopped", "pid": None})
self.assertEqual(signals, [])
class DefaultSignalTests(unittest.TestCase):
def test_terminate_signals_non_group_leader_process_directly(self) -> None:
with mock.patch("couchd.launcher.os.getpgid", return_value=9999), mock.patch(
"couchd.launcher.os.kill"
) as kill, mock.patch("couchd.launcher.os.killpg") as killpg:
default_terminate(4321)
kill.assert_called_once()
self.assertEqual(kill.call_args.args[0], 4321)
killpg.assert_not_called()
def test_kill_signals_group_leader_process_group(self) -> None:
with mock.patch("couchd.launcher.os.getpgid", return_value=4321), mock.patch(
"couchd.launcher.os.kill"
) as kill, mock.patch("couchd.launcher.os.killpg") as killpg:
default_kill(4321)
kill.assert_not_called()
killpg.assert_called_once()

229
tests/test_scripts.py Normal file
View File

@@ -0,0 +1,229 @@
import io
import json
import os
import shutil
import subprocess
import tempfile
import tarfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
class InstallSlippiScriptTests(unittest.TestCase):
def test_fails_closed_when_checksum_asset_omits_appimage(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
bin_dir = temp_path / "bin"
install_dir = temp_path / "install"
bin_dir.mkdir()
install_dir.mkdir()
release_json = {
"assets": [
{
"name": "Slippi-Launcher-9.9.9-x86_64.AppImage",
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/Slippi-Launcher-9.9.9-x86_64.AppImage",
},
{
"name": "checksums.txt",
"browser_download_url": "https://github.com/project-slippi/slippi-launcher/releases/download/v9.9.9/checksums.txt",
},
]
}
curl_stub = bin_dir / "curl"
curl_stub.write_text(
"\n".join(
[
"#!/usr/bin/env bash",
"set -Eeuo pipefail",
'dest=""',
'url=""',
'while [[ $# -gt 0 ]]; do',
' case "$1" in',
' -o) dest="$2"; shift 2 ;;',
' *) url="$1"; shift ;;',
' esac',
'done',
f"cat <<'JSON' >\"$dest\"\n{json.dumps(release_json)}\nJSON" if False else "",
]
),
encoding="utf-8",
)
curl_stub.write_text(
"""#!/usr/bin/env bash
set -Eeuo pipefail
dest=""
url=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o) dest="$2"; shift 2 ;;
*) url="$1"; shift ;;
esac
done
case "$url" in
*api.github.com*)
cat <<'JSON' >"$dest"
"""
+ json.dumps(release_json)
+ """
JSON
;;
*.AppImage)
printf 'fake appimage' >"$dest"
;;
*checksums.txt)
printf 'deadbeef SomeOtherFile.AppImage\n' >"$dest"
;;
*)
exit 1
;;
esac
""",
encoding="utf-8",
)
curl_stub.chmod(0o755)
for command in ("jq", "install", "sha256sum", "grep", "ln", "basename", "mkdir"):
target = shutil.which(command)
assert target is not None
os.symlink(target, bin_dir / command)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/install_slippi.sh")],
env={
**os.environ,
"PATH": f"{bin_dir}:{os.environ['PATH']}",
"SLIPPI_INSTALL_DIR": str(install_dir),
},
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("checksum asset present but does not include", result.stderr)
class BackupRestoreScriptTests(unittest.TestCase):
def test_backup_excludes_account_data_and_sets_private_mode(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
archive_path = Path(temp_dir) / "backup.tar.gz"
(couch_home / ".config" / "couchd").mkdir(parents=True)
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "Slippi").mkdir(parents=True)
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
(couch_home / "Slippi").mkdir(parents=True)
(couch_home / ".config" / "Slippi Launcher").mkdir(parents=True)
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
(couch_home / ".config" / "Slippi Launcher" / "session.json").write_text("secret", encoding="utf-8")
(couch_home / "Slippi" / "Replay").mkdir()
(couch_home / "Slippi" / "Replay" / "match.slp").write_text("replay", encoding="utf-8")
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(archive_path.stat().st_mode & 0o777, 0o600)
listing = subprocess.run(
["tar", "-tzf", str(archive_path)],
capture_output=True,
text=True,
check=True,
).stdout
self.assertIn(".config/couchd/runtime.json", listing)
self.assertNotIn(".config/Slippi Launcher", listing)
self.assertNotIn("Replay/match.slp", listing)
def test_backup_excludes_recursive_case_insensitive_rom_and_replay_patterns(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
archive_path = Path(temp_dir) / "backup.tar.gz"
(couch_home / ".config" / "couchd").mkdir(parents=True)
(couch_home / ".config" / "Slippi Launcher" / "Profiles").mkdir(parents=True)
(couch_home / ".config" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "Slippi").mkdir(parents=True)
(couch_home / ".local" / "share" / "dolphin-emu").mkdir(parents=True)
(couch_home / ".config" / "couchd" / "runtime.json").write_text("{}", encoding="utf-8")
(couch_home / "Slippi" / "Replays" / "nested").mkdir(parents=True)
(couch_home / "Slippi" / "Roms").mkdir(parents=True)
(couch_home / "Slippi" / "Replays" / "nested" / "match.SLP").write_text("replay", encoding="utf-8")
rom_gcm = f"melee.{''.join(['G', 'C', 'M'])}"
rom_wbfs = f"other.{''.join(['w', 'B', 'f', 'S'])}"
rom_nkit = "third." + ".".join(["nkit", "iso"])
(couch_home / "Slippi" / "Roms" / rom_gcm).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "Roms" / rom_wbfs).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "Roms" / rom_nkit).write_text("rom", encoding="utf-8")
(couch_home / "Slippi" / "keep.txt").write_text("keep", encoding="utf-8")
(couch_home / ".config" / "Slippi Launcher" / "Profiles" / "session.json").write_text(
"secret",
encoding="utf-8",
)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "backup", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
listing = subprocess.run(
["tar", "-tzf", str(archive_path)],
capture_output=True,
text=True,
check=True,
).stdout
self.assertIn(".config/couchd/runtime.json", listing)
self.assertNotIn(".config/Slippi Launcher/Profiles/session.json", listing)
self.assertNotIn("match.SLP", listing)
self.assertNotIn(rom_gcm, listing)
self.assertNotIn(rom_wbfs, listing)
self.assertNotIn(rom_nkit, listing)
self.assertIn("Slippi/keep.txt", listing)
def test_restore_rejects_unsafe_archive_entries(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
couch_home = Path(temp_dir) / "home" / "couch"
couch_home.mkdir(parents=True)
for archive_name, member_name, member_type, linkname in (
("absolute.tar.gz", "/absolute.txt", tarfile.REGTYPE, ""),
("traversal.tar.gz", "../../escape.txt", tarfile.REGTYPE, ""),
("device.tar.gz", "device-node", tarfile.CHRTYPE, ""),
("link-escape.tar.gz", "link-out", tarfile.SYMTYPE, "../../escape.txt"),
):
archive_path = Path(temp_dir) / archive_name
with tarfile.open(archive_path, "w:gz") as archive:
info = tarfile.TarInfo(member_name)
info.type = member_type
info.mode = 0o600
if member_type == tarfile.REGTYPE:
payload = b"ok"
info.size = len(payload)
archive.addfile(info, io.BytesIO(payload))
elif member_type == tarfile.SYMTYPE:
info.linkname = linkname
archive.addfile(info)
else:
archive.addfile(info)
result = subprocess.run(
["bash", str(REPO_ROOT / "scripts/backup_restore.sh"), "restore", str(archive_path)],
env={**os.environ, "COUCH_HOME": str(couch_home)},
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("unsafe archive entry", result.stderr)

55
tests/test_setup_modes.sh Normal file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -Eeuo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TMP_DIR}"' EXIT
BIN_DIR="${TMP_DIR}/bin"
LOG_FILE="${TMP_DIR}/calls.log"
mkdir -p "${BIN_DIR}"
cat >"${BIN_DIR}/id" <<'EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
if [[ "${1:-}" == "-u" && "${2:-}" == "couch" ]]; then
exit 1
fi
exec /usr/bin/id "$@"
EOF
cat >"${BIN_DIR}/useradd" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail
printf 'useradd %s\n' "\$*" >>"${LOG_FILE}"
EOF
chmod 0755 "${BIN_DIR}/id" "${BIN_DIR}/useradd"
output="$(
PATH="${BIN_DIR}:${PATH}" \
bash -c '
set -Eeuo pipefail
source "'"${REPO_ROOT}"'/setup.sh"
MODE="dry-run"
COUCH_UID=""
ensure_couch_user
ensure_runtime_config
'
)"
grep -Fq '[dry-run] useradd --create-home --home-dir /home/couch --shell /bin/bash --user-group couch' <<<"${output}"
grep -Fq '/run/user/<missing-couch-uid>' <<<"${output}"
check_output_file="${TMP_DIR}/check.out"
set +e
PATH="${BIN_DIR}:${PATH}" bash "${REPO_ROOT}/setup.sh" --phase couch --check >"${check_output_file}" 2>&1
status=$?
set -e
if [[ "${status}" -eq 0 ]]; then
printf 'expected setup --check to fail on missing couch user\n' >&2
exit 1
fi
grep -Fq '[check] FAIL couch user missing' "${check_output_file}"
grep -Fq '[check] FAIL /home/couch/.config/couchd/runtime.json missing' "${check_output_file}"

73
tests/test_status.py Normal file
View File

@@ -0,0 +1,73 @@
import unittest
from pathlib import Path
from couchd.discovery import DiscoveryConfig, SlippiDiscovery
from couchd.status import CommandError, CommandRunner, StatusCollector, probe_display, probe_slippi_version
class StatusCollectorTests(unittest.TestCase):
def test_status_degrades_gracefully_when_probes_fail(self) -> None:
collector = StatusCollector(
display_probe=lambda: (_ for _ in ()).throw(CommandError("xrandr missing")),
slippi_probe=lambda: (_ for _ in ()).throw(TimeoutError("slow launcher")),
dolphin_probe=lambda: (_ for _ in ()).throw(TimeoutError("slow dolphin")),
controller_probe=lambda: (_ for _ in ()).throw(CommandError("bluetoothctl missing")),
)
status = collector.collect(state="idle", pid=None)
self.assertEqual(status["state"], "idle")
self.assertEqual(status["display"]["mode"], "unknown")
self.assertIn("xrandr missing", status["display"]["error"])
self.assertEqual(status["slippi_version"], "unknown")
self.assertIn("slow launcher", status["slippi_error"])
self.assertEqual(status["controller"]["connected"], "unknown")
def test_probe_display_reports_active_starred_mode_refresh(self) -> None:
class Runner(CommandRunner):
def run(self, argv: list[str], timeout: int = 5): # type: ignore[override]
del argv, timeout
return type(
"Completed",
(),
{
"returncode": 0,
"stdout": "\n".join(
[
"HDMI-1 connected primary 1920x1080+0+0",
" 1920x1080 60.00*+ 59.94",
"eDP-1 connected 1920x1080+1920+0",
" 1920x1080 120.00*+ 60.00",
]
),
"stderr": "",
},
)()
display = probe_display(Runner())
self.assertEqual(display["name"], "HDMI-1")
self.assertEqual(display["mode"], "1920x1080@60.00")
self.assertEqual(display["connected_outputs"]["eDP-1"], "1920x1080@120.00")
def test_probe_slippi_version_prefers_binary_version_output(self) -> None:
class Runner(CommandRunner):
def run(self, argv: list[str], timeout: int = 5): # type: ignore[override]
del timeout
if argv[-1] == "--version":
return type(
"Completed",
(),
{"returncode": 0, "stdout": "Slippi Launcher 3.2.1\n", "stderr": ""},
)()
raise AssertionError(argv)
discovery = SlippiDiscovery(
DiscoveryConfig(
couch_home=Path("/home/couch"),
applications_dir=Path("/home/couch/Applications"),
)
)
discovery.find_launcher = lambda: Path("/home/couch/Applications/SlippiLauncher.AppImage") # type: ignore[method-assign]
self.assertEqual(probe_slippi_version(discovery, Runner()), "Slippi Launcher 3.2.1")