This commit is contained in:
2026-05-18 03:14:15 +03:00
parent 8ae59e40b2
commit 082468e2bd
13 changed files with 291 additions and 95 deletions

View File

@@ -10,13 +10,14 @@ 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_REPO = REPO_ROOT / "example" / "dotfiles-repo"
EXAMPLE_DIR = REPO_ROOT / "example"
IMAGE_TAG = "flow-e2e:test"
@@ -34,15 +35,69 @@ def _pick_runtime() -> str | None:
return None
@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 = _pick_runtime()
if runtime is 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",
@@ -57,49 +112,67 @@ def test_dotfiles_init_and_link_in_container():
pytest.fail(f"image build failed:\n{build.stdout}\n{build.stderr}")
try:
# Run flow inside the container against the mounted example repo.
# `flow dotfiles init` clones, so we need a real git remote — turn
# the read-only example mount into a bare-ish working repo first.
# --skip system avoids the _root/ paths which would try to sudo-link
# over /etc/hostname; we already cover the link path on non-system
# packages.
script = (
"set -eux; "
"cp -r /example /home/flowuser/dotfiles-src; "
"cd /home/flowuser/dotfiles-src; "
"git init -q -b main; "
"git -c user.email=e2e@example.com -c user.name=e2e add -A; "
"git -c user.email=e2e@example.com -c user.name=e2e commit -q -m initial; "
"cd /home/flowuser; "
"flow dotfiles init --repo /home/flowuser/dotfiles-src; "
"flow dotfiles link --profile linux-auto --skip system; "
# Verify real symlinks were created and point into the dotfiles dir.
"test -L /home/flowuser/.zshrc; "
"test -L /home/flowuser/.gitconfig; "
"readlink /home/flowuser/.zshrc | grep -q '/dotfiles/_shared/zsh/.zshrc'; "
"readlink /home/flowuser/.gitconfig | grep -q '/dotfiles/_shared/git/.gitconfig'; "
# Idempotency: rerun should be a no-op.
"flow dotfiles link --profile linux-auto --skip system; "
"flow dotfiles status"
)
result = subprocess.run(
[
runtime, "run", "--rm",
"-v", f"{EXAMPLE_REPO}:/example:ro",
IMAGE_TAG,
"-c", script,
],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"e2e run failed (rc={result.returncode}):\n"
f"stdout: {result.stdout}\n"
f"stderr: {result.stderr}"
)
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}"
)