179 lines
5.4 KiB
Python
179 lines
5.4 KiB
Python
"""End-to-end test for `flow dotfiles` against the example repo.
|
|
|
|
Gated by FLOW_RUN_E2E=1 because it builds and runs a container image.
|
|
Tries podman first, falls back to docker. Skips if neither is available.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
import textwrap
|
|
|
|
import pytest
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
CONTAINERFILE = Path(__file__).parent / "Containerfile"
|
|
EXAMPLE_DIR = REPO_ROOT / "example"
|
|
IMAGE_TAG = "flow-e2e:test"
|
|
|
|
|
|
def _pick_runtime() -> str | None:
|
|
for binary in ("podman", "docker"):
|
|
if not shutil.which(binary):
|
|
continue
|
|
result = subprocess.run(
|
|
[binary, "version"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if result.returncode == 0:
|
|
return binary
|
|
return None
|
|
|
|
|
|
def _flow_script(*commands: str) -> str:
|
|
"""Build a reusable flow sandbox script for container execution."""
|
|
return textwrap.dedent(
|
|
"""
|
|
set -euo pipefail
|
|
|
|
export XDG_CONFIG_HOME="/tmp/flow-config"
|
|
export XDG_DATA_HOME="/tmp/flow-data"
|
|
export XDG_STATE_HOME="/tmp/flow-state"
|
|
export TARGET_HOSTNAME="flow-e2e"
|
|
export USER_EMAIL="e2e@example.com"
|
|
export PYTHONPATH="/opt/flow-src/src:${PYTHONPATH:-}"
|
|
|
|
rm -rf "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME"
|
|
mkdir -p "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME"
|
|
|
|
cp -r /example/dotfiles-repo "$HOME/dotfiles-src"
|
|
cp -r /example/module-repos "$HOME/module-repos"
|
|
cd "$HOME/module-repos/nvim-config"
|
|
git init -q -b main
|
|
git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" add -A
|
|
git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" commit -q -m initial
|
|
|
|
cd "$HOME/dotfiles-src"
|
|
git init -q -b main
|
|
git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" add -A
|
|
git -c user.email="$USER_EMAIL" -c user.name="Flow E2E" commit -q -m initial
|
|
cd "$HOME"
|
|
"""
|
|
).strip() + "\n" + "\n".join(commands) + "\n"
|
|
|
|
|
|
def _run_in_e2e_container(runtime: str, script: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[
|
|
runtime,
|
|
"run",
|
|
"--rm",
|
|
"-v",
|
|
f"{EXAMPLE_DIR}:/example:ro",
|
|
IMAGE_TAG,
|
|
"-c",
|
|
script,
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def runtime() -> str:
|
|
if os.environ.get("FLOW_RUN_E2E") != "1":
|
|
pytest.skip("set FLOW_RUN_E2E=1 to run")
|
|
|
|
selected_runtime = _pick_runtime()
|
|
if selected_runtime is None:
|
|
pytest.skip("neither podman nor docker is available")
|
|
|
|
return selected_runtime
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def image(runtime: str):
|
|
build = subprocess.run(
|
|
[
|
|
runtime, "build",
|
|
"-f", str(CONTAINERFILE),
|
|
"-t", IMAGE_TAG,
|
|
str(REPO_ROOT),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if build.returncode != 0:
|
|
pytest.fail(f"image build failed:\n{build.stdout}\n{build.stderr}")
|
|
|
|
try:
|
|
yield IMAGE_TAG
|
|
finally:
|
|
subprocess.run(
|
|
[runtime, "rmi", "-f", IMAGE_TAG],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
os.environ.get("FLOW_RUN_E2E") != "1",
|
|
reason="set FLOW_RUN_E2E=1 to run",
|
|
)
|
|
def test_dotfiles_init_and_link_in_container(runtime: str, image: str):
|
|
result = _run_in_e2e_container(
|
|
runtime,
|
|
_flow_script(
|
|
"flow dotfiles init --repo \"$HOME/dotfiles-src\"",
|
|
"flow dotfiles link --profile linux-auto --skip _root",
|
|
"test -L \"$HOME/.zshrc\"",
|
|
"test -L \"$HOME/.gitconfig\"",
|
|
"readlink \"$HOME/.zshrc\" | grep -q '/dotfiles/_shared/zsh/.zshrc'",
|
|
"readlink \"$HOME/.gitconfig\" | grep -q '/dotfiles/_shared/git/.gitconfig'",
|
|
"flow dotfiles link --profile linux-auto --skip _root",
|
|
"flow dotfiles status",
|
|
),
|
|
)
|
|
assert result.returncode == 0, (
|
|
f"e2e run failed (rc={result.returncode}):\n"
|
|
f"stdout: {result.stdout}\n"
|
|
f"stderr: {result.stderr}"
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
os.environ.get("FLOW_RUN_E2E") != "1",
|
|
reason="set FLOW_RUN_E2E=1 to run",
|
|
)
|
|
def test_cli_paths_run_in_disposable_container(runtime: str, image: str):
|
|
result = _run_in_e2e_container(
|
|
runtime,
|
|
_flow_script(
|
|
"flow --version | tee /tmp/version.txt",
|
|
"grep -q \"flow\" /tmp/version.txt",
|
|
"flow --help | tee /tmp/help.txt",
|
|
"test -s /tmp/help.txt",
|
|
"flow completion zsh | tee /tmp/completion.zsh",
|
|
"test -s /tmp/completion.zsh",
|
|
"flow dotfiles init --repo \"$HOME/dotfiles-src\"",
|
|
"flow dotfiles link --profile linux-auto --skip _root",
|
|
"flow dotfiles status",
|
|
"flow packages list --all | tee /tmp/packages.txt",
|
|
"test -s /tmp/packages.txt",
|
|
"flow packages install --profile linux-auto --dry-run | tee /tmp/package-dry-run.txt",
|
|
"test -s /tmp/package-dry-run.txt",
|
|
"flow setup show linux-auto | tee /tmp/setup.txt",
|
|
"test -s /tmp/setup.txt",
|
|
),
|
|
)
|
|
assert result.returncode == 0, (
|
|
f"e2e run failed (rc={result.returncode}):\n"
|
|
f"stdout: {result.stdout}\n"
|
|
f"stderr: {result.stderr}"
|
|
)
|