diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ff8dc..f7e41d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- Make development bootstrap repair a stale globally registered coverage repository URL before + selecting it for the repository-local switch. + ## [1.1.0] - 2026-08-26 - Reset the scenario, journal, strategy, execution-configuration, CLI, and diagnostic contracts to diff --git a/scripts/bootstrap-development-environment b/scripts/bootstrap-development-environment index 35a9ca2..4f3bf46 100755 --- a/scripts/bootstrap-development-environment +++ b/scripts/bootstrap-development-environment @@ -35,10 +35,19 @@ if [ ! -f "$repository_root/_opam/.opam-switch/switch-config" ]; then fi printf '%s\n' "Registering the checksummed OCaml coverage package..." -opam repository add "$coverage_repository_name" "$coverage_repository" \ - --rank 1 \ - --switch "$repository_root" \ - --yes +if opam repository list --all --short --color never 2>/dev/null \ + | grep -Fqx "$coverage_repository_name"; then + opam repository set-url "$coverage_repository_name" "$coverage_repository" --yes + opam repository add "$coverage_repository_name" \ + --rank 1 \ + --switch "$repository_root" \ + --yes +else + opam repository add "$coverage_repository_name" "$coverage_repository" \ + --rank 1 \ + --switch "$repository_root" \ + --yes +fi if opam pin list --switch "$repository_root" --short | grep -Fqx "bisect_ppx"; then opam pin remove bisect_ppx \ diff --git a/test/dune b/test/dune index 34999af..f9cbec6 100644 --- a/test/dune +++ b/test/dune @@ -117,6 +117,7 @@ (alias runtest) (deps test_development_environment.py + ../scripts/bootstrap-development-environment ../scripts/check-schema-environment.py) (action (run python3 %{dep:test_development_environment.py}))) diff --git a/test/test_development_environment.py b/test/test_development_environment.py index 4534a41..5b5cdd8 100644 --- a/test/test_development_environment.py +++ b/test/test_development_environment.py @@ -1,7 +1,10 @@ from __future__ import annotations import importlib.util +import os import pathlib +import shutil +import subprocess import tempfile import unittest @@ -53,5 +56,93 @@ def test_reports_missing_mismatched_and_unexpected_packages(self) -> None: ) +class BootstrapEnvironmentTest(unittest.TestCase): + def test_repairs_a_registered_repository_before_selecting_it(self) -> None: + result, commands, repository_root = self.run_bootstrap(repository_registered=True) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn( + f"repository set-url trading-engine-coverage {repository_root / 'opam-repository'} --yes", + commands, + ) + self.assertIn( + "repository add trading-engine-coverage --rank 1 " + f"--switch {repository_root} --yes", + commands, + ) + + def test_registers_a_missing_repository_with_its_local_url(self) -> None: + result, commands, repository_root = self.run_bootstrap(repository_registered=False) + + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(any("repository set-url" in command for command in commands)) + self.assertIn( + "repository add trading-engine-coverage " + f"{repository_root / 'opam-repository'} --rank 1 " + f"--switch {repository_root} --yes", + commands, + ) + + def run_bootstrap( + self, *, repository_registered: bool + ) -> tuple[subprocess.CompletedProcess[str], list[str], pathlib.Path]: + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + repository_root = pathlib.Path(temporary.name) + scripts = repository_root / "scripts" + scripts.mkdir() + bootstrap = scripts / "bootstrap-development-environment" + shutil.copy2(REPOSITORY_ROOT / "scripts" / bootstrap.name, bootstrap) + self.write_executable( + scripts / "check-development-environment", "#!/bin/sh\nexit 0\n" + ) + (repository_root / "_opam/.opam-switch").mkdir(parents=True) + (repository_root / "_opam/.opam-switch/switch-config").touch() + schema_bin = repository_root / ".venv-schema/bin" + schema_bin.mkdir(parents=True) + self.write_executable(schema_bin / "python", "#!/bin/sh\nexit 0\n") + (repository_root / "requirements").mkdir() + (repository_root / "requirements/schema.lock").touch() + (repository_root / "opam-repository").mkdir() + + fake_bin = repository_root / "fake-bin" + fake_bin.mkdir() + opam_log = repository_root / "opam.log" + self.write_executable( + fake_bin / "opam", + "#!/bin/sh\n" + 'printf \'%s\\n\' "$*" >> "$OPAM_LOG"\n' + 'if [ "$1" = repository ] && [ "$2" = list ] ' + '&& [ "$REPOSITORY_REGISTERED" = 1 ]; then\n' + " printf '%s\\n' trading-engine-coverage\n" + "fi\n", + ) + self.write_executable(fake_bin / "uv", "#!/bin/sh\nexit 0\n") + self.write_executable(fake_bin / "python3", "#!/bin/sh\nexit 0\n") + environment = os.environ.copy() + environment.update( + { + "OPAM_LOG": str(opam_log), + "PATH": f"{fake_bin}:{environment['PATH']}", + "REPOSITORY_REGISTERED": "1" if repository_registered else "0", + } + ) + + result = subprocess.run( + [str(bootstrap)], + cwd=repository_root, + env=environment, + check=False, + capture_output=True, + text=True, + ) + return result, opam_log.read_text(encoding="utf-8").splitlines(), repository_root + + @staticmethod + def write_executable(path: pathlib.Path, contents: str) -> None: + path.write_text(contents, encoding="utf-8") + path.chmod(0o755) + + if __name__ == "__main__": unittest.main()