_helpers.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """Shared helpers for interactive setup tests."""
  2. from __future__ import annotations
  3. import re
  4. import subprocess
  5. from pathlib import Path
  6. REPO_ROOT = Path(__file__).resolve().parents[2]
  7. PRESERVED_HEADER = (
  8. "### ----- Preserved custom environment variables from previous .env -----"
  9. )
  10. PRESERVED_NOTICE = (
  11. "### ----- Comments in this session will persist across regenerations -----"
  12. )
  13. def run_bash_process(
  14. script: str, cwd: Path | None = None, stdin: str | None = ""
  15. ) -> subprocess.CompletedProcess[str]:
  16. """Run a bash snippet and return the completed process."""
  17. return subprocess.run(
  18. ["bash", "--norc", "--noprofile", "-c", script],
  19. cwd=cwd or REPO_ROOT,
  20. input=stdin,
  21. capture_output=True,
  22. text=True,
  23. check=False,
  24. )
  25. def run_bash(script: str, cwd: Path | None = None) -> str:
  26. """Run a bash snippet and return stdout."""
  27. result = run_bash_process(script, cwd=cwd)
  28. if result.returncode != 0:
  29. raise AssertionError(f"""bash script failed with code {result.returncode}
  30. stdout:
  31. {result.stdout}
  32. stderr:
  33. {result.stderr}""")
  34. return result.stdout
  35. def parse_lines(output: str) -> dict[str, str]:
  36. """Parse KEY=value lines into a dictionary."""
  37. values: dict[str, str] = {}
  38. for line in output.splitlines():
  39. if "=" not in line:
  40. continue
  41. key, value = line.split("=", 1)
  42. values[key] = value
  43. return values
  44. def run_bash_lines(script: str, cwd: Path | None = None) -> dict[str, str]:
  45. """Run a bash snippet and parse KEY=value lines from stdout."""
  46. return parse_lines(run_bash(script, cwd=cwd))
  47. def write_text_lines(path: Path, lines: list[str]) -> Path:
  48. """Write lines to a fixture file with a trailing newline."""
  49. path.write_text("\n".join(lines) + "\n", encoding="utf-8")
  50. return path
  51. def assert_single_compose_backup(tmp_path: Path, expected_content: str) -> Path:
  52. """Assert that a single compose backup exists with the expected content."""
  53. backups = sorted(tmp_path.glob("docker-compose.backup*.yml"))
  54. assert len(backups) == 1
  55. assert re.fullmatch("docker-compose\\.backup\\d{8}_\\d{6}\\.yml", backups[0].name)
  56. assert backups[0].read_text(encoding="utf-8") == expected_content
  57. return backups[0]
  58. def write_storage_setup_files(
  59. tmp_path: Path, env_lines: list[str], compose_lines: list[str]
  60. ) -> None:
  61. """Write the minimal env/example/compose fixtures used by storage-flow tests."""
  62. write_text_lines(tmp_path / ".env", env_lines)
  63. write_text_lines(
  64. tmp_path / "env.example",
  65. (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
  66. )
  67. write_text_lines(tmp_path / "docker-compose.final.yml", compose_lines)