mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
`shfmt.ensure()` returned as soon as any `shfmt` was on PATH and tried the distribution package before the release pinned in roles/sys-lint/meta/services.yml. The Debian dev container therefore formats with its packaged 3.8.0 (/usr/bin/shfmt), Fedora with its packaged 3.7.0, the CentOS one with the pinned v3.14.1 (/usr/local/bin/shfmt), and they disagree on scripts/tests/deploy/swarm/utils/unmount/nfs_mounts.sh: every gate in a CentOS stack moved the `then` of the heredoc `if` below the terminator. ensure() now follows ruff's installer: it reads `shfmt --version`, keeps the binary only when it reports the pinned release, and otherwise installs the release binary into /usr/local/bin. It fails when PATH still resolves another version afterwards instead of formatting with it. The system package path is gone. nfs_mounts.sh is committed in the pinned release's format (byte-identical to the v3.14.1 output from the CentOS container), so the first gate on any distro leaves the tree clean. The install-lint stamp now also depends on roles/sys-lint/meta/services.yml, the pin file of actionlint, hadolint and shfmt, so a bumped pin reaches environments whose stamp predates it; until now only pyproject.toml and scripts/install/lint.sh invalidated it. Fixes #1155 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
208 lines
8.1 KiB
Python
208 lines
8.1 KiB
Python
"""Unit tests for :mod:`utils.install.lint.__main__`."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import time
|
|
import unittest
|
|
import unittest.mock as mock
|
|
from pathlib import Path
|
|
from tempfile import TemporaryDirectory
|
|
|
|
from utils.install.lint import __main__ as cli
|
|
|
|
|
|
def _make_repo_root(tmp: str, *, stamp_age_offset: float | None = None) -> Path:
|
|
root = Path(tmp)
|
|
(root / "scripts" / "install").mkdir(parents=True)
|
|
(root / "scripts" / "install" / "lint.sh").write_text("#\n", encoding="utf-8")
|
|
(root / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
|
|
(root / cli._PINS).parent.mkdir(parents=True)
|
|
(root / cli._PINS).write_text("---\n", encoding="utf-8")
|
|
if stamp_age_offset is not None:
|
|
stamp = root / cli._STAMP
|
|
stamp.parent.mkdir(parents=True, exist_ok=True)
|
|
stamp.write_text("", encoding="utf-8")
|
|
if stamp_age_offset:
|
|
new_time = time.time() + stamp_age_offset
|
|
os.utime(stamp, (new_time, new_time))
|
|
return root
|
|
|
|
|
|
class TestStampLogic(unittest.TestCase):
|
|
def test_fresh_stamp_short_circuits(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli.shutil, "which", return_value="/usr/bin/x"),
|
|
mock.patch.object(
|
|
cli.mermaid_cli, "browser_installed", return_value=True
|
|
),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_not_called()
|
|
|
|
def test_missing_mermaid_browser_invalidates_stamp(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli.shutil, "which", return_value="/usr/bin/x"),
|
|
mock.patch.object(
|
|
cli.mermaid_cli, "browser_installed", return_value=False
|
|
),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
|
|
def test_missing_tool_invalidates_stamp(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli.shutil, "which", return_value=None),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
|
|
def test_stale_stamp_triggers_install(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=-60.0)
|
|
time.sleep(0.01)
|
|
(root / "scripts" / "install" / "lint.sh").write_text(
|
|
"#updated\n", encoding="utf-8"
|
|
)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
self.assertTrue((root / cli._STAMP).exists())
|
|
|
|
def test_a_newer_release_pin_triggers_install(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
pins = root / cli._PINS
|
|
later = time.time() + 120.0
|
|
os.utime(pins, (later, later))
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli.shutil, "which", return_value="/usr/bin/x"),
|
|
mock.patch.object(
|
|
cli.mermaid_cli, "browser_installed", return_value=True
|
|
),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
|
|
def test_no_stamp_triggers_install_and_touches(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main([])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
self.assertTrue((root / cli._STAMP).exists())
|
|
|
|
def test_force_drops_stamp(self) -> None:
|
|
with TemporaryDirectory() as tmp, TemporaryDirectory() as cache:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
with (
|
|
mock.patch.dict(os.environ, {"PUPPETEER_CACHE_DIR": cache}),
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_all") as inst,
|
|
):
|
|
rc = cli.main(["--force"])
|
|
self.assertEqual(rc, 0)
|
|
inst.assert_called_once()
|
|
|
|
def test_force_drops_the_mermaid_browser(self) -> None:
|
|
with TemporaryDirectory() as tmp, TemporaryDirectory() as cache:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
browser = Path(cache) / "chrome-headless-shell" / "linux-1"
|
|
browser.mkdir(parents=True)
|
|
with (
|
|
mock.patch.dict(os.environ, {"PUPPETEER_CACHE_DIR": cache}),
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_all"),
|
|
):
|
|
cli.main(["--force"])
|
|
self.assertFalse(browser.exists())
|
|
|
|
|
|
class TestGroupDispatch(unittest.TestCase):
|
|
def test_group_only_bypasses_stamp(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp, stamp_age_offset=60.0)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_javascript_tools") as js,
|
|
mock.patch.object(cli, "_install_all") as inst_all,
|
|
):
|
|
rc = cli.main(["javascript"])
|
|
self.assertEqual(rc, 0)
|
|
js.assert_called_once()
|
|
inst_all.assert_not_called()
|
|
|
|
def test_group_does_not_touch_stamp(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp)
|
|
stamp = root / cli._STAMP
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli, "_install_javascript_tools"),
|
|
):
|
|
cli.main(["javascript"])
|
|
self.assertFalse(stamp.exists())
|
|
|
|
def test_ansible_group_calls_all_modules(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(cli.ansible_commands, "ensure") as a,
|
|
mock.patch.object(cli.ansible_collections, "ensure") as b,
|
|
mock.patch.object(cli.ansible_lint, "ensure") as c,
|
|
):
|
|
cli.main(["ansible"])
|
|
a.assert_called_once()
|
|
b.assert_called_once()
|
|
c.assert_called_once()
|
|
|
|
def test_unknown_group_returns_1(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp)
|
|
with mock.patch.object(cli, "PROJECT_ROOT", root):
|
|
rc = cli.main(["bogus"])
|
|
self.assertEqual(rc, 1)
|
|
|
|
def test_runtime_error_in_ensure_returns_1(self) -> None:
|
|
with TemporaryDirectory() as tmp:
|
|
root = _make_repo_root(tmp)
|
|
with (
|
|
mock.patch.object(cli, "PROJECT_ROOT", root),
|
|
mock.patch.object(
|
|
cli.eslint, "ensure", side_effect=RuntimeError("boom")
|
|
),
|
|
):
|
|
rc = cli.main(["javascript"])
|
|
self.assertEqual(rc, 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|