项目文件夹

文件
wehub-resource-sync 3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:37 +08:00

244 行
11 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# SPDX-License-Identifier: MIT
"""Parse-and-grep guard for install.sh's post-create provisioning (0.6.0 item B).
After the provision-unify cleanup, install.sh's post-`winpodx setup`
provisioning is the single ``winpodx provision`` command — NOT a bash copy
of the wait-ready → /health poll → 6× discovery retry → host-open chain.
These tests fail loudly if a regression reintroduces an inline bash copy,
which is exactly the duplication the unification killed.
"""
from __future__ import annotations
from pathlib import Path
import pytest
_INSTALL_SH = Path(__file__).resolve().parent.parent / "install.sh"
def _strip_comments(text: str) -> str:
"""Drop comment lines so prose explaining the unification doesn't trip the
grep guards. A comment line is one whose first non-space char is ``#``
(the shebang counts as a comment too). Inline trailing comments are rare
in install.sh and not load-bearing for these assertions, so a line-level
strip is sufficient and avoids mangling quoted ``#`` inside strings."""
kept = []
for line in text.splitlines():
if line.lstrip().startswith("#"):
continue
kept.append(line)
return "\n".join(kept)
@pytest.fixture(scope="module")
def script() -> str:
assert _INSTALL_SH.is_file(), f"install.sh not found at {_INSTALL_SH}"
return _strip_comments(_INSTALL_SH.read_text(encoding="utf-8"))
def test_invokes_winpodx_provision(script: str) -> None:
# The post-create step runs a single winpodx command chosen by the
# fresh/upgrade branch (PROVISION_CMD array), invoked through the symlink.
assert "PROVISION_CMD=(provision --require-agent)" in script
assert '"$SYMLINK" "${PROVISION_CMD[@]}"' in script
def test_installs_podman_compose_with_podman(script: str) -> None:
# #580/#503: winpodx hard-requires the standalone podman-compose, which the
# podman package doesn't pull in. install.sh must detect it and add it to
# the install set so pod creation doesn't fail with "compose command not
# found".
assert "PODMAN_COMPOSE_PRESENT" in script
assert 'MISSING+=("podman-compose")' in script
def test_atomic_rpm_path_honours_explicit_source_override(script: str) -> None:
# #548: on Atomic (rpm-ostree) install.sh defaults to the OBS RPM, but an
# explicit --main/--ref/--source/--image-tar must win and fall through to
# the git/venv flow (the OBS RPM only ships tagged releases). The gate must
# therefore require all three override vars to be empty before taking the
# rpm-ostree path.
lines = script.splitlines()
# The gate opens with `if command -v rpm-ostree ... \` and continues the
# compound `&& [ -z ... ]` condition on the next line.
gate = next(
(i for i, ln in enumerate(lines) if ln.lstrip().startswith("if command -v rpm-ostree")),
None,
)
assert gate is not None, "rpm-ostree gate must open the Atomic install path"
cond = " ".join(lines[gate : gate + 2])
assert '-z "$WINPODX_REF"' in cond
assert '-z "$WINPODX_SOURCE"' in cond
assert '-z "$WINPODX_IMAGE_TAR"' in cond
def test_no_inline_health_curl_poll(script: str) -> None:
# The 30× `curl .../health` settle poll moved into finish_provisioning.
# (install.sh still uses curl elsewhere — e.g. the GitHub release check —
# so we assert the absence of the agent /health endpoint specifically,
# not curl in general.)
assert "/health" not in script
assert "127.0.0.1:8765" not in script
assert "8765/health" not in script
def test_no_inline_six_times_discovery_retry(script: str) -> None:
# The 6× `app refresh` retry loop is gone from bash.
assert "for attempt in 1 2 3 4 5 6" not in script
assert '"$SYMLINK" app refresh' not in script
def test_no_inline_host_open_listener_start(script: str) -> None:
# Reverse-open listener start + refresh moved into finish_provisioning.
assert "host-open start-listener" not in script
assert "host-open refresh" not in script
def test_fresh_uses_provision_require_agent(script: str) -> None:
# Fresh install (no prior config): provision with the agent-first gate
# (#271) so discovery/apply defer instead of racing FreeRDP into
# install.bat's autologon session.
assert "PROVISION_CMD=(provision --require-agent)" in script
def test_upgrade_uses_migrate(script: str) -> None:
# Upgrade (prior config existed): migrate FIRST syncs the refreshed guest
# scripts (guest_sync) + pins the image, THEN runs the apply -> discovery
# -> reverse-open chain. A blind `provision`-for-both (the first item-B
# cut) left upgraded guests on stale agent.ps1 / OEM scripts.
assert "PROVISION_CMD=(migrate --non-interactive)" in script
def test_provision_step_branches_on_fresh_vs_upgrade(script: str) -> None:
# The two flows are gated on the pre-setup IS_FRESH_INSTALL snapshot.
assert 'if [ "$IS_FRESH_INSTALL" = "1" ]; then' in script
def test_create_only_flag_is_gone(script: str) -> None:
# setup --create-only was removed; install.sh must not pass it.
assert "--create-only" not in script
def test_setup_skips_its_own_provision_tail(script: str) -> None:
# install.sh runs the chain once via the explicit `winpodx provision`,
# so it tells `winpodx setup` to skip its own full-provision tail.
assert "WINPODX_NO_PROVISION=1" in script
def test_provision_call_disarms_err_trap(script: str) -> None:
# bash fires the ERR trap on a failing *pipeline* even under `set +e`, so
# the provision call must explicitly `trap - ERR` before it and re-arm
# `trap rollback_and_exit_err ERR` after — otherwise a deferred (exit 5)
# provision rolls back the whole fresh install before the rc handling runs.
assert "trap - ERR" in script
assert "trap rollback_and_exit_err ERR" in script
# Re-arm must come back after the disarm (ordering sanity).
assert script.index("trap - ERR") < script.rindex("trap rollback_and_exit_err ERR")
def test_deferred_provision_is_not_a_rollback(script: str) -> None:
# Exit 4 (wait-ready ran long) / 5 (agent-first discovery deferred) are
# NOT failures: Windows is downloaded + booted, so they record pending and
# keep the install instead of rolling back ~15 min of ISO download.
assert '[ "$PROVISION_RC" -eq 4 ] || [ "$PROVISION_RC" -eq 5 ]' in script
# The deferred branch points the user at the recovery command.
assert "winpodx app refresh" in script
def test_mode_prompt_reads_from_tty_so_curl_bash_can_choose(script: str) -> None:
# Under `curl ... | bash`, stdin is the script pipe, so the R/A/C/N mode
# menu (and Custom sub-prompts) must read from the controlling terminal
# /dev/tty — otherwise the menu is unreachable via the canonical install
# path and silently defaults to Recommended.
assert "true </dev/tty" in script # interactivity also detected via /dev/tty
assert "TTY_DEV" in script
# Every interactive prompt reads from $TTY_DEV, not bare stdin.
for bare in (
"read -r mode_answer\n",
"read -r be_answer\n",
"read -r gui_answer\n",
"read -r answer\n",
):
assert bare not in script, f"prompt still reads bare stdin: {bare!r}"
assert script.count('read -r mode_answer < "$TTY_DEV"') == 1
def test_custom_mode_picks_freerdp_source(script: str) -> None:
# Custom mode lets the user pick the FreeRDP client source (native/flatpak/
# auto), not just the backend + GUI.
assert "WINPODX_FREERDP_SOURCE" in script
assert '[ "$INSTALL_MODE" = "c" ]' in script
# The resolved source is handed to setup so the launcher honours it.
assert "--freerdp-source" in script
def test_freerdp_flatpak_not_installed_redundantly(script: str) -> None:
# When a FreeRDP client (native or Flatpak) is already present, no client is
# pulled. With NO client present, auto installs the NATIVE package (native
# is preferred); only an explicit `--freerdp-source flatpak` installs the
# Flatpak (INSTALL_FREERDP_FLATPAK).
assert "FREERDP_FLATPAK_PRESENT" in script
assert "FREERDP_NATIVE_PRESENT" in script
assert "INSTALL_FREERDP_FLATPAK" in script
assert "com.freerdp.FreeRDP" in script
def test_prints_install_plan_before_acting(script: str) -> None:
# After the mode + dependency sources are resolved, install.sh prints a
# plan summary (mode / backend / FreeRDP action / GUI / packages / VM)
# before installing anything, so the run is transparent.
assert "install plan" in script
# The plan must be emitted before the package-install loop runs.
assert script.index("install plan") < script.index("if [ ${#MISSING[@]} -gt 0 ]; then")
def test_upgrade_skips_the_mode_prompt(script: str) -> None:
# On an upgrade / re-run (a config already exists) the R/A/C/N mode prompt
# is pointless — install.sh reuses the existing config and runs migrate,
# so the prompt is gated on IS_FRESH_INSTALL.
assert '[ "$IS_FRESH_INSTALL" != "1" ]' in script
# The mode-prompt heredoc must come after that upgrade gate.
assert script.index('[ "$IS_FRESH_INSTALL" != "1" ]') < script.index("Install mode?")
def test_too_old_podman_guard_blocks_before_install(script: str) -> None:
# #271 ask 3: when the resolved backend is podman but podman is < 4 (Ubuntu
# 22.04 ships 3.4), Recommended mode / explicit --backend podman would
# otherwise proceed and fail at provisioning AFTER installing packages. A
# guard refuses to blindly continue — and it must run BEFORE any package
# install so an abort leaves the system unmodified.
guard = '[ "$WINPODX_ALLOW_OLD_PODMAN" != "1" ]'
assert guard in script
# Gated on the podman-too-old condition + the podman backend.
assert '[ "$WINPODX_BACKEND" = "podman" ] && [ "$PODMAN_TOO_OLD" = true ]' in script
# The guard sits before the install-plan / package-install phase.
assert script.index(guard) < script.index("install plan")
def test_too_old_podman_guard_has_override(script: str) -> None:
# An out-of-band podman upgrade can make the probe stale, so the guard is
# bypassable via env var and a matching flag.
assert "WINPODX_ALLOW_OLD_PODMAN" in script
assert "--allow-old-podman" in script
def test_too_old_podman_noninteractive_exits_clean(script: str) -> None:
# Non-interactive runs can't prompt, so the guard exits cleanly with
# guidance rather than silently switching backend or proceeding.
assert "aborted before modifying the system" in script.lower()
def test_no_inline_chain_steps_survive(script: str) -> None:
"""After `winpodx setup`, the post-create chain is driven by ONE winpodx
command (provision on fresh, migrate on upgrade, both via PROVISION_CMD).
The individual chain commands the old ~140-line bash copy invoked
directly must NOT survive as separate `"$SYMLINK" <cmd>` calls."""
forbidden = (
'"$SYMLINK" pod wait-ready',
'"$SYMLINK" app refresh',
'"$SYMLINK" host-open',
)
for cmd in forbidden:
assert cmd not in script, f"inline bash chain step still present: {cmd!r}"