Compare commits

...
3 Commits
Author SHA1 Message Date
devx-ci-bot 1a60739b5a release: v0.41.2 [skip ci] 2026-07-13 02:26:42 +00:00
emil 50dcb67083 DEVX-133: fix: auto-discover molecule root instead of hardcoding gitea-runner
Post-merge / detect-and-configure (push) Successful in 13s
Post-merge / release-and-maintain (push) Successful in 59s
2026-07-13 02:25:59 +00:00
gitea-actions-bot 7b624b0525 chore: update badge URLs to commit 9175bdc9 [skip ci] 2026-07-13 01:39:18 +00:00
11 changed files with 119 additions and 42 deletions
+6
View File
@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.41.2] - 2026-07-13
### Bug Fixes
- Auto-discover molecule root instead of hardcoding gitea-runner
## [0.41.1] - 2026-07-13 ## [0.41.1] - 2026-07-13
### Bug Fixes ### Bug Fixes
+9 -9
View File
@@ -16,12 +16,12 @@ quality badges.
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE) [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/python.svg)](https://www.python.org/downloads/)
## Why devx? ## Why devx?
@@ -87,7 +87,7 @@ extra index and list devx in your dependencies:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.41.1", "devx>=0.41.2",
] ]
[tool.pip] [tool.pip]
@@ -101,8 +101,8 @@ pip install -e .
``` ```
> **Note:** If your project requires a specific devx version, pin it in > **Note:** If your project requires a specific devx version, pin it in
> `dependencies` (for example, `"devx==0.41.1"`) or use a version constraint > `dependencies` (for example, `"devx==0.41.2"`) or use a version constraint
> (for example, `"devx>=0.41.1,<0.42"`). > (for example, `"devx>=0.41.2,<0.42"`).
### Optional extras ### Optional extras
+8 -8
View File
@@ -12,12 +12,12 @@ project to be reusable across all oblachno-oss repositories.
[![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![CI](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions/workflows/ci.yml/badge.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE) [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/src/branch/master/LICENSE)
[![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Coverage](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/coverage.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Tests](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/tests.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki) [![Docs](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/docs.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/wiki)
[![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions) [![Code Quality](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/quality.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/actions)
[![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases) [![Version](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/version.svg)](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/releases)
[![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/691cdd2c9ec9a840b92744ae89893b8588be3efe/python.svg)](https://www.python.org/downloads/) [![Python](https://git.oblachno.oblachno.fyi/oblachno-oss/devx/raw/commit/9175bdc9ea121021ef145c0233905f2ce7ce3e5c/python.svg)](https://www.python.org/downloads/)
## Overview ## Overview
@@ -74,14 +74,14 @@ Add devx to your `pyproject.toml` dependencies and configure the registry:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.41.1", "devx>=0.41.2",
] ]
[tool.pip] [tool.pip]
extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple" extra-index-url = "https://git.oblachno.oblachno.fyi/api/packages/oblachno-oss/pypi/simple"
``` ```
Pin a specific version if needed: `"devx==0.41.1"` or `"devx>=0.41.1,<0.42"`. Pin a specific version if needed: `"devx==0.41.2"` or `"devx>=0.41.2,<0.42"`.
### Optional extras ### Optional extras
+2 -2
View File
@@ -48,12 +48,12 @@ Add devx to your `pyproject.toml`:
```toml ```toml
[project] [project]
dependencies = [ dependencies = [
"devx>=0.41.1", "devx>=0.41.2",
] ]
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"devx>=0.41.1", "devx>=0.41.2",
] ]
``` ```
+1 -1
View File
@@ -1,3 +1,3 @@
"""devx — reusable development and CI/CD tools for oblachno-oss projects.""" """devx — reusable development and CI/CD tools for oblachno-oss projects."""
__version__ = "0.41.1" __version__ = "0.41.2"
+17 -3
View File
@@ -30,10 +30,24 @@ from devx.i18n import _
from devx.molecule.platforms import PLATFORMS, load_platforms from devx.molecule.platforms import PLATFORMS, load_platforms
DEFAULT_MAX_RUNNERS = 3 DEFAULT_MAX_RUNNERS = 3
MOLECULE_ROOT = Path("ansible/roles/gitea-runner/molecule")
DEFAULT_ROLES_ROOT = Path("ansible/roles") DEFAULT_ROLES_ROOT = Path("ansible/roles")
def _default_molecule_root() -> Path:
"""Auto-discover the single molecule directory under ansible/roles/.
If exactly one role has a molecule/ subdirectory, return it.
Otherwise, fall back to the first role with a molecule/ directory.
"""
roles_root = DEFAULT_ROLES_ROOT
if not roles_root.is_dir():
return roles_root / "gitea_runner" / "molecule" # sensible default for error message
mol_dirs = sorted(d / "molecule" for d in roles_root.iterdir() if (d / "molecule").is_dir())
if mol_dirs:
return mol_dirs[0]
return roles_root / "molecule" # will produce a clear "not found" error
@dataclass(frozen=True) @dataclass(frozen=True)
class TestPair: class TestPair:
"""A (scenario, platform) combination to test.""" """A (scenario, platform) combination to test."""
@@ -83,7 +97,7 @@ class MultiRoleTestPair:
def discover_scenarios(root: Path | None = None) -> list[str]: def discover_scenarios(root: Path | None = None) -> list[str]:
"""Return sorted list of molecule scenario directory names.""" """Return sorted list of molecule scenario directory names."""
if root is None: if root is None:
root = MOLECULE_ROOT root = _default_molecule_root()
if not root.is_dir(): if not root.is_dir():
raise click.ClickException(_("Molecule directory not found: {path}", path=str(root))) raise click.ClickException(_("Molecule directory not found: {path}", path=str(root)))
scenarios = [d.name for d in root.iterdir() if d.is_dir() and not d.name.startswith("_") and d.name != "common"] scenarios = [d.name for d in root.iterdir() if d.is_dir() and not d.name.startswith("_") and d.name != "common"]
@@ -318,7 +332,7 @@ def _write_github_env(key: str, value: str) -> None:
"--molecule-root", "--molecule-root",
type=click.Path(exists=True, file_okay=False, path_type=Path), type=click.Path(exists=True, file_okay=False, path_type=Path),
default=None, default=None,
help="Custom molecule directory (single-role mode). Default: ansible/roles/gitea-runner/molecule.", help="Custom molecule directory (single-role mode). Default: auto-discovered under ansible/roles/*/molecule.",
) )
@click.option( @click.option(
"--roles-root", "--roles-root",
+18 -4
View File
@@ -21,7 +21,20 @@ import click
from devx.molecule.platforms import PLATFORMS from devx.molecule.platforms import PLATFORMS
ROLE_DIR = Path("ansible/roles/gitea-runner") DEFAULT_ROLES_ROOT = Path("ansible/roles")
def _default_role_dir() -> Path:
"""Auto-discover the single role directory with molecule scenarios."""
roles_root = DEFAULT_ROLES_ROOT
if not roles_root.is_dir():
return roles_root / "gitea_runner" # sensible default for error message
role_dirs = sorted(d for d in roles_root.iterdir() if (d / "molecule").is_dir())
if role_dirs:
return role_dirs[0]
return roles_root / "role" # will produce a clear error
SCENARIOS = ["default", "multi-instance", "lifecycle", "template-content", "deregister", "update"] SCENARIOS = ["default", "multi-instance", "lifecycle", "template-content", "deregister", "update"]
@@ -72,15 +85,16 @@ def main(bin_dir: str) -> None:
if not Path(molecule_bin).exists(): if not Path(molecule_bin).exists():
raise click.ClickException(f"molecule not found at {molecule_bin}. Run 'make setup' first.") raise click.ClickException(f"molecule not found at {molecule_bin}. Run 'make setup' first.")
if not ROLE_DIR.exists(): role_dir = _default_role_dir()
raise click.ClickException(f"Role directory not found: {ROLE_DIR}") if not role_dir.exists():
raise click.ClickException(f"Role directory not found: {role_dir}")
base_env = dict(os.environ) base_env = dict(os.environ)
base_env["ANSIBLE_ALLOW_BROKEN_CONDITIONALS"] = "true" base_env["ANSIBLE_ALLOW_BROKEN_CONDITIONALS"] = "true"
base_env["ANSIBLE_INJECT_INVOCATION"] = "1" base_env["ANSIBLE_INJECT_INVOCATION"] = "1"
for platform in PLATFORMS: for platform in PLATFORMS:
rc = _run_platform(molecule_bin, platform, ROLE_DIR, SCENARIOS, base_env) rc = _run_platform(molecule_bin, platform, role_dir, SCENARIOS, base_env)
if rc != 0: if rc != 0:
click.echo(f"FAILED on platform {platform['name']}", err=True) click.echo(f"FAILED on platform {platform['name']}", err=True)
sys.exit(rc) sys.exit(rc)
+8 -2
View File
@@ -145,13 +145,19 @@ def resolve_role_dir(role: str, roles_root: Path | None, repo_root: Path) -> Pat
"""Resolve the working directory for a molecule pair. """Resolve the working directory for a molecule pair.
For multi-role pairs (role non-empty), uses ``roles_root/role``. For multi-role pairs (role non-empty), uses ``roles_root/role``.
For single-role pairs, uses ``repo_root/ansible/roles/gitea-runner``. For single-role pairs, auto-discovers the first role with a molecule/
subdirectory under ``repo_root/ansible/roles/``.
""" """
if role: if role:
if roles_root is None: if roles_root is None:
roles_root = repo_root / "ansible" / "roles" roles_root = repo_root / "ansible" / "roles"
return roles_root / role return roles_root / role
return repo_root / "ansible" / "roles" / "gitea-runner" roles_dir = repo_root / "ansible" / "roles"
if roles_dir.is_dir():
role_dirs = sorted(d for d in roles_dir.iterdir() if (d / "molecule").is_dir())
if role_dirs:
return role_dirs[0]
return roles_dir / "role" # will produce a clear "not found" error
@click.command() @click.command()
+26 -9
View File
@@ -9,10 +9,10 @@ from click.testing import CliRunner
from devx.molecule.distribute_molecule import ( from devx.molecule.distribute_molecule import (
DEFAULT_ROLES_ROOT, DEFAULT_ROLES_ROOT,
MOLECULE_ROOT,
PLATFORMS, PLATFORMS,
MultiRoleTestPair, MultiRoleTestPair,
TestPair, TestPair,
_default_molecule_root,
_load_molecule_weights, _load_molecule_weights,
_lpt_distribute, _lpt_distribute,
_scenario_weight, _scenario_weight,
@@ -43,8 +43,25 @@ class TestDiscoverScenarios:
discover_scenarios(tmp_path / "nonexistent") discover_scenarios(tmp_path / "nonexistent")
assert "not found" in str(exc.value) assert "not found" in str(exc.value)
def test_default_root_constant(self) -> None: def test_default_root_auto_discovery(self, tmp_path: Path) -> None:
assert Path("ansible/roles/gitea-runner/molecule") == MOLECULE_ROOT """_default_molecule_root auto-discovers first role with molecule/ dir."""
# When no roles exist, returns a fallback path
with patch("devx.molecule.distribute_molecule.DEFAULT_ROLES_ROOT", tmp_path / "roles"):
result = _default_molecule_root()
assert "molecule" in str(result)
# When a role has molecule/, it's discovered
(tmp_path / "roles" / "my_role" / "molecule").mkdir(parents=True)
with patch("devx.molecule.distribute_molecule.DEFAULT_ROLES_ROOT", tmp_path / "roles"):
result = _default_molecule_root()
assert result == tmp_path / "roles" / "my_role" / "molecule"
def test_default_root_no_molecule_dirs(self, tmp_path: Path) -> None:
"""When roles exist but none have molecule/, returns fallback path."""
(tmp_path / "roles" / "role_without_molecule").mkdir(parents=True)
with patch("devx.molecule.distribute_molecule.DEFAULT_ROLES_ROOT", tmp_path / "roles"):
result = _default_molecule_root()
assert result == tmp_path / "roles" / "molecule"
class TestPairEncoding: class TestPairEncoding:
@@ -150,7 +167,7 @@ class TestCli:
root = tmp_path / "molecule" root = tmp_path / "molecule"
(root / "alpha").mkdir(parents=True) (root / "alpha").mkdir(parents=True)
(root / "beta").mkdir(parents=True) (root / "beta").mkdir(parents=True)
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, ["--list"]) result = runner.invoke(cli, ["--list"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -176,7 +193,7 @@ class TestCli:
root = tmp_path / "molecule" root = tmp_path / "molecule"
for s in ["a", "b", "c"]: for s in ["a", "b", "c"]:
(root / s).mkdir(parents=True) (root / s).mkdir(parents=True)
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, ["--max-runners", "3"]) result = runner.invoke(cli, ["--max-runners", "3"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -191,7 +208,7 @@ class TestCli:
root = tmp_path / "molecule" root = tmp_path / "molecule"
(root / "alpha").mkdir(parents=True) (root / "alpha").mkdir(parents=True)
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
# 1-based index: "1" maps to internal 0 # 1-based index: "1" maps to internal 0
result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3"]) result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3"])
@@ -209,7 +226,7 @@ class TestGithubEnv:
scenario = root / "alpha" scenario = root / "alpha"
scenario.mkdir(parents=True) scenario.mkdir(parents=True)
(scenario / "molecule.yml").write_text("name: alpha\n") (scenario / "molecule.yml").write_text("name: alpha\n")
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3", "--github-env"]) result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3", "--github-env"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -224,7 +241,7 @@ class TestGithubEnv:
scenario = root / "alpha" scenario = root / "alpha"
scenario.mkdir(parents=True) scenario.mkdir(parents=True)
(scenario / "molecule.yml").write_text("name: alpha\n") (scenario / "molecule.yml").write_text("name: alpha\n")
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, ["--runner-index", "5", "--max-runners", "3", "--github-env", "--skip-if-excess"] cli, ["--runner-index", "5", "--max-runners", "3", "--github-env", "--skip-if-excess"]
@@ -240,7 +257,7 @@ class TestGithubEnv:
scenario = root / "alpha" scenario = root / "alpha"
scenario.mkdir(parents=True) scenario.mkdir(parents=True)
(scenario / "molecule.yml").write_text("name: alpha\n") (scenario / "molecule.yml").write_text("name: alpha\n")
with patch("devx.molecule.distribute_molecule.MOLECULE_ROOT", root): with patch("devx.molecule.distribute_molecule._default_molecule_root", return_value=root):
runner = CliRunner() runner = CliRunner()
result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3", "--github-env"]) result = runner.invoke(cli, ["--runner-index", "1", "--max-runners", "3", "--github-env"])
assert result.exit_code != 0 assert result.exit_code != 0
+14 -2
View File
@@ -104,12 +104,24 @@ class TestMain:
assert result.exit_code != 0 assert result.exit_code != 0
assert "Role directory not found" in result.output assert "Role directory not found" in result.output
def test_role_dir_no_molecule(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Roles dir exists but no role has molecule/ — should error."""
monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True)
(bin_dir / "molecule").touch()
(tmp_path / "ansible" / "roles" / "role_without_molecule").mkdir(parents=True)
runner = CliRunner()
result = runner.invoke(molecule_all.main, ["--bin", str(bin_dir)])
assert result.exit_code != 0
assert "Role directory not found" in result.output
def test_all_pass(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: def test_all_pass(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
bin_dir = tmp_path / ".venv" / "bin" bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True) bin_dir.mkdir(parents=True)
(bin_dir / "molecule").touch() (bin_dir / "molecule").touch()
(tmp_path / "ansible" / "roles" / "gitea-runner").mkdir(parents=True) (tmp_path / "ansible" / "roles" / "gitea-runner" / "molecule").mkdir(parents=True)
runner = CliRunner() runner = CliRunner()
with patch("devx.molecule.molecule_all._run_platform", return_value=0): with patch("devx.molecule.molecule_all._run_platform", return_value=0):
@@ -122,7 +134,7 @@ class TestMain:
bin_dir = tmp_path / ".venv" / "bin" bin_dir = tmp_path / ".venv" / "bin"
bin_dir.mkdir(parents=True) bin_dir.mkdir(parents=True)
(bin_dir / "molecule").touch() (bin_dir / "molecule").touch()
(tmp_path / "ansible" / "roles" / "gitea-runner").mkdir(parents=True) (tmp_path / "ansible" / "roles" / "gitea-runner" / "molecule").mkdir(parents=True)
runner = CliRunner() runner = CliRunner()
with patch("devx.molecule.molecule_all._run_platform", return_value=1): with patch("devx.molecule.molecule_all._run_platform", return_value=1):
+10 -2
View File
@@ -524,9 +524,17 @@ class TestResolveRoleDir:
result = resolve_role_dir("docker-base", None, tmp_path) result = resolve_role_dir("docker-base", None, tmp_path)
assert result == tmp_path / "ansible" / "roles" / "docker-base" assert result == tmp_path / "ansible" / "roles" / "docker-base"
def test_single_role_uses_default(self, tmp_path: Path) -> None: def test_single_role_auto_discovers(self, tmp_path: Path) -> None:
"""Single-role mode auto-discovers first role with molecule/ dir."""
roles_dir = tmp_path / "ansible" / "roles"
(roles_dir / "my_role" / "molecule").mkdir(parents=True)
result = resolve_role_dir("", None, tmp_path) result = resolve_role_dir("", None, tmp_path)
assert result == tmp_path / "ansible" / "roles" / "gitea-runner" assert result == roles_dir / "my_role"
def test_single_role_no_roles_returns_fallback(self, tmp_path: Path) -> None:
"""When no roles exist, returns a fallback path (will error at runtime)."""
result = resolve_role_dir("", None, tmp_path)
assert "roles" in str(result)
class TestCliMultiRole: class TestCliMultiRole: