aboutsummaryrefslogtreecommitdiff
path: root/scripts/test_unix_installer.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/test_unix_installer.py')
-rw-r--r--scripts/test_unix_installer.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/scripts/test_unix_installer.py b/scripts/test_unix_installer.py
new file mode 100644
index 00000000..82de55c8
--- /dev/null
+++ b/scripts/test_unix_installer.py
@@ -0,0 +1,171 @@
+from __future__ import annotations
+
+import hashlib
+import json
+import os
+import shutil
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+INSTALLER = REPO_ROOT / "website" / "install.sh"
+REQUIRED_COMMANDS = ("awk", "cat", "chmod", "cp", "mkdir", "mktemp", "mv", "rm")
+
+
+class UnixInstallerTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.temp_dir = tempfile.TemporaryDirectory(prefix="herdr-installer-test-")
+ self.root = Path(self.temp_dir.name)
+ self.bin_dir = self.root / "bin"
+ self.bin_dir.mkdir()
+ self.install_dir = self.root / "install"
+ self.payload = self.root / "payload"
+ self.payload.write_bytes(b"fake-herdr-binary\n")
+ self.expected_sha256 = hashlib.sha256(self.payload.read_bytes()).hexdigest()
+
+ for command in REQUIRED_COMMANDS:
+ path = shutil.which(command)
+ if path is None:
+ self.fail(f"test host is missing required command: {command}")
+ (self.bin_dir / command).symlink_to(path)
+
+ self._write_executable(
+ "uname",
+ """#!/bin/sh
+case "$1" in
+ -s) echo Linux ;;
+ -m) echo x86_64 ;;
+ *) exit 1 ;;
+esac
+""",
+ )
+ self._write_executable(
+ "curl",
+ """#!/bin/sh
+out=""
+previous=""
+for argument in "$@"; do
+ if [ "$previous" = "-o" ]; then
+ out="$argument"
+ break
+ fi
+ previous="$argument"
+done
+if [ -n "$out" ]; then
+ cp "$FAKE_PAYLOAD" "$out"
+else
+ cat "$FAKE_MANIFEST"
+fi
+""",
+ )
+
+ def tearDown(self) -> None:
+ self.temp_dir.cleanup()
+
+ def _write_executable(self, name: str, content: str) -> None:
+ path = self.bin_dir / name
+ path.write_text(content, encoding="utf-8")
+ path.chmod(0o755)
+
+ def _select_checksum_tool(self, tool: str) -> None:
+ if tool == "sha256sum":
+ path = shutil.which("sha256sum")
+ if path is None:
+ self.fail("test host is missing sha256sum")
+ (self.bin_dir / "sha256sum").symlink_to(path)
+ return
+
+ if tool == "shasum":
+ sha256sum = shutil.which("sha256sum")
+ if sha256sum is None:
+ self.fail("test host is missing sha256sum for the shasum fixture")
+ self._write_executable(
+ "shasum",
+ f"""#!/bin/sh
+[ "$1" = "-a" ] && [ "$2" = "256" ] || exit 2
+shift 2
+exec {sha256sum} "$@"
+""",
+ )
+ return
+
+ if tool == "openssl":
+ path = shutil.which("openssl")
+ if path is None:
+ self.fail("test host is missing openssl")
+ (self.bin_dir / "openssl").symlink_to(path)
+ return
+
+ self.fail(f"unknown checksum tool fixture: {tool}")
+
+ def _write_manifest(self, checksum: str | None) -> Path:
+ manifest: dict[str, object] = {
+ "version": "9.9.9",
+ "assets": {
+ "linux-x86_64": "https://example.invalid/herdr-linux-x86_64"
+ },
+ }
+ if checksum is not None:
+ manifest["sha256"] = {"linux-x86_64": checksum}
+ path = self.root / "latest.json"
+ path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
+ return path
+
+ def _run_installer(self, checksum: str | None, tool: str = "sha256sum") -> subprocess.CompletedProcess[str]:
+ self._select_checksum_tool(tool)
+ manifest = self._write_manifest(checksum)
+ env = {
+ **os.environ,
+ "PATH": str(self.bin_dir),
+ "FAKE_MANIFEST": str(manifest),
+ "FAKE_PAYLOAD": str(self.payload),
+ "HERDR_INSTALL_DIR": str(self.install_dir),
+ }
+ return subprocess.run(
+ ["/bin/sh", str(INSTALLER)],
+ env=env,
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+ def test_valid_download_uses_each_supported_checksum_tool(self) -> None:
+ for tool in ("sha256sum", "shasum", "openssl"):
+ with self.subTest(tool=tool):
+ shutil.rmtree(self.install_dir, ignore_errors=True)
+ for name in ("sha256sum", "shasum", "openssl"):
+ (self.bin_dir / name).unlink(missing_ok=True)
+
+ result = self._run_installer(self.expected_sha256.upper(), tool)
+
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertEqual((self.install_dir / "herdr").read_bytes(), self.payload.read_bytes())
+
+ def test_checksum_mismatch_does_not_replace_existing_binary(self) -> None:
+ self.install_dir.mkdir()
+ installed = self.install_dir / "herdr"
+ installed.write_bytes(b"existing-herdr\n")
+
+ result = self._run_installer("0" * 64)
+
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn("checksum did not match", result.stderr)
+ self.assertEqual(installed.read_bytes(), b"existing-herdr\n")
+
+ def test_missing_checksum_fails_without_replacing_existing_binary(self) -> None:
+ self.install_dir.mkdir()
+ installed = self.install_dir / "herdr"
+ installed.write_bytes(b"existing-herdr\n")
+
+ result = self._run_installer(None)
+
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn("valid SHA-256 checksum", result.stderr)
+ self.assertEqual(installed.read_bytes(), b"existing-herdr\n")
+
+
+if __name__ == "__main__":
+ unittest.main()