diff --git a/.taskid b/.taskid index ab29330..9d89668 100644 --- a/.taskid +++ b/.taskid @@ -1 +1 @@ -DEVX-29 \ No newline at end of file +DEVX-30 diff --git a/src/devx/molecule/molecule_ci_guard.py b/src/devx/molecule/molecule_ci_guard.py index 31b3b44..eb8ffa8 100644 --- a/src/devx/molecule/molecule_ci_guard.py +++ b/src/devx/molecule/molecule_ci_guard.py @@ -321,6 +321,17 @@ def cli(pairs: tuple[str, ...], junit_output: str | None, roles_root: Path | Non click.echo(_("PASSED: {pair}", pair=pair)) + # Prune Docker data between scenarios to prevent disk exhaustion + # in Docker-in-Docker molecule containers (each scenario pulls + # hundreds of MB of images that accumulate across pairs). + with contextlib.suppress(subprocess.SubprocessError, OSError): + subprocess.run( # nosec B603, B607 + ["docker", "system", "prune", "-af", "--volumes"], + check=False, + capture_output=True, + timeout=60, + ) + click.echo(_("All molecule tests passed.")) if junit_output: write_junit_report(junit_output, testcases, current_index) diff --git a/tests/unit/test_molecule_ci_guard.py b/tests/unit/test_molecule_ci_guard.py index 16a4ba8..542a435 100644 --- a/tests/unit/test_molecule_ci_guard.py +++ b/tests/unit/test_molecule_ci_guard.py @@ -162,17 +162,26 @@ class TestCli: with ( patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen, + patch("devx.molecule.molecule_ci_guard.subprocess.run") as mock_run, patch("time.sleep"), ): proc = MagicMock() proc.poll.return_value = 0 proc.returncode = 0 mock_popen.return_value = proc + mock_run.return_value = MagicMock(returncode=0) runner = CliRunner() result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"]) assert result.exit_code == 0 assert "All molecule tests passed" in result.output + # Verify Docker prune was called between scenarios + mock_run.assert_called_once_with( + ["docker", "system", "prune", "-af", "--volumes"], + check=False, + capture_output=True, + timeout=60, + ) def test_invalid_pair_format_raises(self) -> None: """Pair with fewer than 2 parts should raise.""" @@ -324,6 +333,7 @@ class TestCli: ), patch("devx.molecule.molecule_ci_guard.POLL_INTERVAL", 0.01), patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen, + patch("devx.molecule.molecule_ci_guard.subprocess.run") as mock_run, patch("devx.molecule.molecule_ci_guard.get_running_jobs") as mock_get_jobs, patch("time.sleep", side_effect=lambda x: real_sleep(0.05)), ): @@ -332,6 +342,7 @@ class TestCli: proc.poll.return_value = 0 proc.returncode = 0 mock_popen.return_value = proc + mock_run.return_value = MagicMock(returncode=0) runner = CliRunner() result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"]) @@ -536,12 +547,14 @@ class TestCliMultiRole: with ( patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen, + patch("devx.molecule.molecule_ci_guard.subprocess.run") as mock_run, patch("time.sleep"), ): proc = MagicMock() proc.poll.return_value = 0 proc.returncode = 0 mock_popen.return_value = proc + mock_run.return_value = MagicMock(returncode=0) runner = CliRunner() result = runner.invoke( @@ -560,12 +573,14 @@ class TestCliMultiRole: with ( patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen, + patch("devx.molecule.molecule_ci_guard.subprocess.run") as mock_run, patch("time.sleep"), ): proc = MagicMock() proc.poll.return_value = 0 proc.returncode = 0 mock_popen.return_value = proc + mock_run.return_value = MagicMock(returncode=0) runner = CliRunner() result = runner.invoke(