DEVX-12: feat: add opentofu helpers, CLI entry points, shared utility, and CI improvements
This commit is contained in:
@@ -8,13 +8,19 @@ import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from devx.molecule.distribute_molecule import (
|
||||
DEFAULT_ROLES_ROOT,
|
||||
MOLECULE_ROOT,
|
||||
PLATFORMS,
|
||||
MultiRoleTestPair,
|
||||
TestPair,
|
||||
build_multi_role_pairs,
|
||||
build_pairs,
|
||||
cli,
|
||||
discover_multi_role_scenarios,
|
||||
discover_scenarios,
|
||||
distribute,
|
||||
distribute_multi_role,
|
||||
multi_role_pairs_for_runner,
|
||||
pairs_for_runner,
|
||||
)
|
||||
|
||||
@@ -259,3 +265,197 @@ def test_main_module_block() -> None:
|
||||
namespace = dict(dm.__dict__)
|
||||
exec(compile(source, dm.__file__, "exec"), namespace)
|
||||
assert callable(namespace["cli"])
|
||||
|
||||
|
||||
class TestDiscoverMultiRole:
|
||||
def test_discovers_role_scenario_pairs(self, tmp_path: Path) -> None:
|
||||
roles = tmp_path / "roles"
|
||||
for scenario in ["default", "binary"]:
|
||||
(roles / "gitea-runner" / "molecule" / scenario).mkdir(parents=True)
|
||||
(roles / "gitea-runner" / "molecule" / "common").mkdir(parents=True)
|
||||
(roles / "gitea-runner" / "molecule" / "_shared").mkdir(parents=True)
|
||||
(roles / "docker-base" / "molecule" / "default").mkdir(parents=True)
|
||||
(roles / "no-molecule").mkdir(parents=True)
|
||||
result = discover_multi_role_scenarios(roles)
|
||||
assert ("docker-base", "default") in result
|
||||
assert ("gitea-runner", "default") in result
|
||||
assert ("gitea-runner", "binary") in result
|
||||
assert ("gitea-runner", "common") not in result
|
||||
assert ("gitea-runner", "_shared") not in result
|
||||
assert len(result) == 3
|
||||
|
||||
def test_raises_when_dir_missing(self, tmp_path: Path) -> None:
|
||||
with pytest.raises(click.ClickException) as exc:
|
||||
discover_multi_role_scenarios(tmp_path / "nonexistent")
|
||||
assert "not found" in str(exc.value)
|
||||
|
||||
def test_default_roles_root_raises_when_missing(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Calling with no args uses DEFAULT_ROLES_ROOT which doesn't exist in tests."""
|
||||
with pytest.raises(click.ClickException):
|
||||
discover_multi_role_scenarios()
|
||||
|
||||
def test_default_roles_root_constant(self) -> None:
|
||||
assert Path("ansible/roles") == DEFAULT_ROLES_ROOT
|
||||
|
||||
|
||||
class TestMultiRoleTestPair:
|
||||
def test_encode_roundtrip(self) -> None:
|
||||
pair = MultiRoleTestPair(
|
||||
"docker-base", "default", {"name": "ubuntu-2204", "image": "ubuntu:22.04", "command": ""}
|
||||
)
|
||||
encoded = pair.encode()
|
||||
assert encoded == "docker-base|default|ubuntu-2204|ubuntu:22.04|"
|
||||
decoded = MultiRoleTestPair.decode(encoded)
|
||||
assert decoded.role == "docker-base"
|
||||
assert decoded.scenario == "default"
|
||||
assert decoded.platform["name"] == "ubuntu-2204"
|
||||
|
||||
|
||||
class TestBuildMultiRolePairs:
|
||||
def test_cross_product(self) -> None:
|
||||
role_scenarios = [("role-a", "default"), ("role-b", "binary")]
|
||||
platforms = [{"name": "p1", "image": "i1", "command": ""}]
|
||||
pairs = build_multi_role_pairs(role_scenarios, platforms)
|
||||
assert len(pairs) == 2
|
||||
assert pairs[0].role == "role-a"
|
||||
assert pairs[1].role == "role-b"
|
||||
|
||||
def test_default_platforms(self) -> None:
|
||||
pairs = build_multi_role_pairs([("r", "s")])
|
||||
assert len(pairs) == len(PLATFORMS)
|
||||
|
||||
|
||||
class TestDistributeMultiRole:
|
||||
def test_even_split(self) -> None:
|
||||
pairs = [MultiRoleTestPair(f"r{i}", "s", {"name": "p", "image": "i", "command": ""}) for i in range(6)]
|
||||
groups = distribute_multi_role(pairs, 3)
|
||||
assert all(len(g) == 2 for g in groups)
|
||||
|
||||
def test_out_of_range_raises(self) -> None:
|
||||
pairs = [MultiRoleTestPair("r", "s", {"name": "p", "image": "i", "command": ""})]
|
||||
with pytest.raises(click.ClickException):
|
||||
multi_role_pairs_for_runner(pairs, 5, 3)
|
||||
|
||||
|
||||
class TestCliMultiRole:
|
||||
def test_roles_root_list(self, tmp_path: Path) -> None:
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
(roles / "role-b" / "molecule" / "binary").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--list"])
|
||||
assert result.exit_code == 0
|
||||
assert "role-a|default" in result.output
|
||||
assert "role-b|binary" in result.output
|
||||
|
||||
def test_roles_root_runner_index(self, tmp_path: Path) -> None:
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--runner-index", "1", "--max-runners", "3"])
|
||||
assert result.exit_code == 0
|
||||
assert "role-a|default|" in result.output
|
||||
|
||||
def test_roles_root_github_env(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
gh_file = tmp_path / "env.txt"
|
||||
monkeypatch.setenv("GITHUB_ENV", str(gh_file))
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
["--roles-root", str(roles), "--runner-index", "1", "--max-runners", "3", "--github-env"],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
content = gh_file.read_text()
|
||||
assert "TEST_PAIRS=" in content
|
||||
assert "SKIP=false" in content
|
||||
|
||||
def test_roles_root_skip_if_excess(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
gh_file = tmp_path / "env.txt"
|
||||
monkeypatch.setenv("GITHUB_ENV", str(gh_file))
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"--roles-root",
|
||||
str(roles),
|
||||
"--runner-index",
|
||||
"5",
|
||||
"--max-runners",
|
||||
"2",
|
||||
"--github-env",
|
||||
"--skip-if-excess",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
content = gh_file.read_text()
|
||||
assert "SKIP=true" in content
|
||||
|
||||
def test_molecule_root_option(self, tmp_path: Path) -> None:
|
||||
root = tmp_path / "custom-molecule"
|
||||
(root / "alpha").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--molecule-root", str(root), "--list"])
|
||||
assert result.exit_code == 0
|
||||
assert "alpha" in result.output
|
||||
|
||||
def test_roles_root_list_platforms(self, tmp_path: Path) -> None:
|
||||
"""--roles-root --list-platforms prints platforms."""
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--list-platforms"])
|
||||
assert result.exit_code == 0
|
||||
assert "ubuntu-2204" in result.output
|
||||
|
||||
def test_roles_root_no_runner_index_prints_groups(self, tmp_path: Path) -> None:
|
||||
"""--roles-root without --runner-index prints all groups."""
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
(roles / "role-b" / "molecule" / "binary").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--max-runners", "2"])
|
||||
assert result.exit_code == 0
|
||||
assert "Runner 0:" in result.output
|
||||
assert "Runner 1:" in result.output
|
||||
|
||||
def test_roles_root_skips_non_dir_role(self, tmp_path: Path) -> None:
|
||||
"""Non-directory entries in roles root are skipped."""
|
||||
roles = tmp_path / "roles"
|
||||
roles.mkdir(parents=True)
|
||||
(roles / "README.md").write_text("not a role")
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
result = discover_multi_role_scenarios(roles)
|
||||
assert ("role-a", "default") in result
|
||||
assert len(result) == 1
|
||||
|
||||
def test_roles_root_skips_non_dir_scenario(self, tmp_path: Path) -> None:
|
||||
"""Non-directory entries in molecule dir are skipped."""
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule").mkdir(parents=True)
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
(roles / "role-a" / "molecule" / "file.txt").write_text("not a scenario")
|
||||
result = discover_multi_role_scenarios(roles)
|
||||
assert ("role-a", "default") in result
|
||||
assert len(result) == 1
|
||||
|
||||
def test_roles_root_skips_role_without_molecule(self, tmp_path: Path) -> None:
|
||||
"""Roles without a molecule/ directory are skipped."""
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
(roles / "no-molecule").mkdir(parents=True)
|
||||
result = discover_multi_role_scenarios(roles)
|
||||
assert ("role-a", "default") in result
|
||||
assert len(result) == 1
|
||||
|
||||
def test_roles_root_runner_index_zero_raises(self, tmp_path: Path) -> None:
|
||||
"""--roles-root --runner-index 0 should raise."""
|
||||
roles = tmp_path / "roles"
|
||||
(roles / "role-a" / "molecule" / "default").mkdir(parents=True)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["--roles-root", str(roles), "--runner-index", "0", "--max-runners", "3"])
|
||||
assert result.exit_code != 0
|
||||
assert "out of range" in result.output
|
||||
|
||||
Reference in New Issue
Block a user