Public Access
Post-merge / detect-type (push) Successful in 10s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 17s
Post-merge / vikunja (push) Successful in 18s
Post-merge / release (push) Successful in 36s
Post-merge / publish (push) Successful in 20s
Post-merge / badges (push) Successful in 35s
557 lines
21 KiB
Python
557 lines
21 KiB
Python
"""Unit tests for scripts/ci/molecule_ci_guard.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess # nosec B404
|
|
import time
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import click
|
|
import pytest
|
|
import requests
|
|
|
|
from devx.molecule.molecule_ci_guard import (
|
|
any_other_runner_failed,
|
|
build_env_for_pair,
|
|
build_molecule_cmd,
|
|
cli,
|
|
get_running_jobs,
|
|
parse_pair,
|
|
poll_for_other_failures,
|
|
resolve_role_dir,
|
|
)
|
|
|
|
|
|
class TestGetRunningJobs:
|
|
def test_returns_jobs(self) -> None:
|
|
with patch("devx.molecule.molecule_ci_guard.requests.get") as mock_get:
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {
|
|
"jobs": [
|
|
{"name": "molecule-tests (0)", "conclusion": "success"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_get.return_value = mock_response
|
|
|
|
jobs = get_running_jobs("https://gitea.example", "owner", "repo", "token", 123)
|
|
assert len(jobs) == 2
|
|
mock_get.assert_called_once()
|
|
|
|
def test_raises_on_request_error(self) -> None:
|
|
with patch("devx.molecule.molecule_ci_guard.requests.get") as mock_get:
|
|
mock_get.side_effect = requests.RequestException("boom")
|
|
with pytest.raises(requests.RequestException):
|
|
get_running_jobs("https://gitea.example", "owner", "repo", "token", 123)
|
|
|
|
|
|
class TestAnyOtherRunnerFailed:
|
|
def test_detects_other_failure(self) -> None:
|
|
jobs = [
|
|
{"name": "molecule-tests (0)", "conclusion": "success"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
{"name": "molecule-tests (2)", "conclusion": "running"},
|
|
]
|
|
assert any_other_runner_failed(jobs, "molecule-tests", 0) is True
|
|
|
|
def test_ignores_current_runner(self) -> None:
|
|
jobs = [
|
|
{"name": "molecule-tests (0)", "conclusion": "failure"},
|
|
{"name": "molecule-tests (1)", "conclusion": "success"},
|
|
]
|
|
assert any_other_runner_failed(jobs, "molecule-tests", 0) is False
|
|
|
|
def test_ignores_non_molecule_jobs(self) -> None:
|
|
jobs = [
|
|
{"name": "quality", "conclusion": "failure"},
|
|
{"name": "molecule-tests (1)", "conclusion": "success"},
|
|
]
|
|
assert any_other_runner_failed(jobs, "molecule-tests", 0) is False
|
|
|
|
|
|
class TestBuildMoleculeCmd:
|
|
def test_default_scenario(self) -> None:
|
|
assert build_molecule_cmd("default") == ["molecule", "test"]
|
|
|
|
def test_named_scenario(self) -> None:
|
|
assert build_molecule_cmd("lifecycle") == ["molecule", "test", "-s", "lifecycle"]
|
|
|
|
|
|
class TestBuildEnvForPair:
|
|
def test_with_command(self) -> None:
|
|
env = build_env_for_pair("default|ubuntu-2204|img:latest|/lib/systemd/systemd", {})
|
|
assert env["MOLECULE_PLATFORM_NAME"] == "ubuntu-2204"
|
|
assert env["MOLECULE_PLATFORM_IMAGE"] == "img:latest"
|
|
assert env["MOLECULE_PLATFORM_COMMAND"] == "/lib/systemd/systemd"
|
|
assert env["ANSIBLE_ALLOW_BROKEN_CONDITIONALS"] == "true"
|
|
|
|
def test_without_command(self) -> None:
|
|
env = build_env_for_pair("default|ubuntu-2204|img:latest|", {})
|
|
assert env["MOLECULE_PLATFORM_NAME"] == "ubuntu-2204"
|
|
assert "MOLECULE_PLATFORM_COMMAND" not in env
|
|
|
|
def test_without_command_removes_existing(self) -> None:
|
|
env = build_env_for_pair("default|ubuntu-2204|img:latest|", {"MOLECULE_PLATFORM_COMMAND": "old"})
|
|
assert "MOLECULE_PLATFORM_COMMAND" not in env
|
|
|
|
def test_preserves_existing_molecule_home(self) -> None:
|
|
"""When MOLECULE_HOME is already set, it is not overridden."""
|
|
env = build_env_for_pair("default|ubuntu-2204|img:latest|", {"MOLECULE_HOME": "/custom/home"})
|
|
assert env["MOLECULE_HOME"] == "/custom/home"
|
|
|
|
|
|
class TestPollForOtherFailures:
|
|
def test_sets_failed_event_when_other_runner_fails(self) -> None:
|
|
stop_event = MagicMock()
|
|
failed_event = MagicMock()
|
|
|
|
def side_effect(*args, **kwargs):
|
|
if stop_event.wait.call_count < 1:
|
|
return [
|
|
{"name": "molecule-tests (0)", "conclusion": "success"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
return []
|
|
|
|
with patch("devx.molecule.molecule_ci_guard.get_running_jobs") as mock_get_jobs:
|
|
mock_get_jobs.side_effect = side_effect
|
|
stop_event.is_set.side_effect = [False, False]
|
|
stop_event.wait.return_value = True
|
|
|
|
poll_for_other_failures(
|
|
"https://gitea.example",
|
|
"owner",
|
|
"repo",
|
|
"token",
|
|
123,
|
|
"molecule-tests",
|
|
0,
|
|
stop_event,
|
|
failed_event,
|
|
)
|
|
|
|
failed_event.set.assert_called_once()
|
|
|
|
def test_poll_warns_on_api_error(self) -> None:
|
|
stop_event = MagicMock()
|
|
failed_event = MagicMock()
|
|
|
|
with patch("devx.molecule.molecule_ci_guard.get_running_jobs") as mock_get_jobs:
|
|
mock_get_jobs.side_effect = requests.RequestException("boom")
|
|
stop_event.is_set.side_effect = [False, True]
|
|
stop_event.wait.return_value = True
|
|
|
|
poll_for_other_failures(
|
|
"https://gitea.example",
|
|
"owner",
|
|
"repo",
|
|
"token",
|
|
123,
|
|
"molecule-tests",
|
|
0,
|
|
stop_event,
|
|
failed_event,
|
|
)
|
|
|
|
failed_event.set.assert_not_called()
|
|
|
|
|
|
class TestCli:
|
|
def test_all_pass(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
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,
|
|
)
|
|
|
|
@patch("devx.molecule.molecule_ci_guard.get_ci_token", side_effect=click.ClickException("no token"))
|
|
def test_missing_token_runs_without_polling(self, mock_token: MagicMock) -> None:
|
|
"""When no token is available, cross-runner polling is skipped."""
|
|
from click.testing import CliRunner
|
|
|
|
with (
|
|
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.poll_for_other_failures") as mock_poll,
|
|
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
|
|
mock_poll.assert_not_called()
|
|
|
|
def test_invalid_pair_format_raises(self) -> None:
|
|
"""Pair with fewer than 2 parts should raise."""
|
|
from click.testing import CliRunner
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["invalid_no_pipe"])
|
|
assert result.exit_code != 0
|
|
assert "Invalid pair format" in result.output
|
|
|
|
def test_failure_exits_nonzero(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
with (
|
|
patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen,
|
|
patch("time.sleep"),
|
|
):
|
|
proc = MagicMock()
|
|
proc.poll.return_value = 1
|
|
proc.returncode = 1
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
assert "FAILED" in result.output
|
|
|
|
def test_exits_before_starting_when_already_failed(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
"GITEA_URL": "https://gitea.example",
|
|
"CI_GITEA_TOKEN": "token",
|
|
"RUN_ID": "123",
|
|
"JOB_NAME": "molecule-tests",
|
|
"MATRIX_INDEX": "0",
|
|
"GITEA_REPOSITORY": "my-org/my-repo",
|
|
"PATH": os.environ.get("PATH", ""),
|
|
},
|
|
clear=True,
|
|
),
|
|
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.get_running_jobs") as mock_get_jobs,
|
|
patch("time.sleep"),
|
|
):
|
|
mock_get_jobs.return_value = [
|
|
{"name": "molecule-tests (0)", "conclusion": "running"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
proc = MagicMock()
|
|
proc.poll.return_value = 0
|
|
proc.returncode = 0
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
assert "Another molecule runner failed" in result.output
|
|
|
|
def test_keyboard_interrupt_kills_process(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
with (
|
|
patch("devx.molecule.molecule_ci_guard.subprocess.Popen") as mock_popen,
|
|
patch("time.sleep", side_effect=KeyboardInterrupt),
|
|
patch("os.killpg") as mock_killpg,
|
|
patch("os.getpgid") as mock_getpgid,
|
|
):
|
|
mock_getpgid.return_value = 123
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
proc.wait.return_value = 0
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
mock_killpg.assert_called()
|
|
|
|
def test_exits_when_other_runner_fails(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
real_sleep = time.sleep
|
|
call_count = [0]
|
|
|
|
def get_jobs_side_effect(*args, **kwargs):
|
|
call_count[0] += 1
|
|
if call_count[0] < 2:
|
|
return [{"name": "molecule-tests (1)", "conclusion": "running"}]
|
|
return [
|
|
{"name": "molecule-tests (0)", "conclusion": "running"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
"GITEA_URL": "https://gitea.example",
|
|
"CI_GITEA_TOKEN": "token",
|
|
"RUN_ID": "123",
|
|
"JOB_NAME": "molecule-tests",
|
|
"MATRIX_INDEX": "0",
|
|
"GITEA_REPOSITORY": "my-org/my-repo",
|
|
"PATH": os.environ.get("PATH", ""),
|
|
},
|
|
clear=True,
|
|
),
|
|
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.get_running_jobs", side_effect=get_jobs_side_effect),
|
|
patch("os.killpg") as mock_killpg,
|
|
patch("os.getpgid") as mock_getpgid,
|
|
patch("time.sleep", side_effect=lambda x: real_sleep(0)),
|
|
):
|
|
mock_getpgid.return_value = 123
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
proc.wait.return_value = 0
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
mock_killpg.assert_called()
|
|
|
|
def test_default_owner_repo_fallback(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
real_sleep = time.sleep
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
"GITEA_URL": "https://gitea.example",
|
|
"CI_GITEA_TOKEN": "token",
|
|
"RUN_ID": "123",
|
|
"JOB_NAME": "molecule-tests",
|
|
"MATRIX_INDEX": "0",
|
|
"GITEA_REPOSITORY": "invalid",
|
|
"PATH": os.environ.get("PATH", ""),
|
|
},
|
|
clear=True,
|
|
),
|
|
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)),
|
|
):
|
|
mock_get_jobs.return_value = [{"name": "molecule-tests (1)", "conclusion": "success"}]
|
|
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
|
|
|
|
def test_exits_when_other_runner_fails_with_process_lookup_error(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
real_sleep = time.sleep
|
|
call_count = [0]
|
|
|
|
def get_jobs_side_effect(*args, **kwargs):
|
|
call_count[0] += 1
|
|
if call_count[0] < 2:
|
|
return [{"name": "molecule-tests (1)", "conclusion": "running"}]
|
|
return [
|
|
{"name": "molecule-tests (0)", "conclusion": "running"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
"GITEA_URL": "https://gitea.example",
|
|
"CI_GITEA_TOKEN": "token",
|
|
"RUN_ID": "123",
|
|
"JOB_NAME": "molecule-tests",
|
|
"MATRIX_INDEX": "0",
|
|
"GITEA_REPOSITORY": "my-org/my-repo",
|
|
"PATH": os.environ.get("PATH", ""),
|
|
},
|
|
clear=True,
|
|
),
|
|
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.get_running_jobs", side_effect=get_jobs_side_effect),
|
|
patch("os.killpg") as mock_killpg,
|
|
patch("os.getpgid") as mock_getpgid,
|
|
patch("time.sleep", side_effect=lambda x: real_sleep(0)),
|
|
):
|
|
mock_getpgid.return_value = 123
|
|
mock_killpg.side_effect = ProcessLookupError("no such process")
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
proc.wait.return_value = 0
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
|
|
def test_exits_when_other_runner_fails_with_timeout(self) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
real_sleep = time.sleep
|
|
call_count = [0]
|
|
|
|
def get_jobs_side_effect(*args, **kwargs):
|
|
call_count[0] += 1
|
|
if call_count[0] < 2:
|
|
return [{"name": "molecule-tests (1)", "conclusion": "running"}]
|
|
return [
|
|
{"name": "molecule-tests (0)", "conclusion": "running"},
|
|
{"name": "molecule-tests (1)", "conclusion": "failure"},
|
|
]
|
|
|
|
with (
|
|
patch.dict(
|
|
os.environ,
|
|
{
|
|
"GITEA_URL": "https://gitea.example",
|
|
"CI_GITEA_TOKEN": "token",
|
|
"RUN_ID": "123",
|
|
"JOB_NAME": "molecule-tests",
|
|
"MATRIX_INDEX": "0",
|
|
"GITEA_REPOSITORY": "my-org/my-repo",
|
|
"PATH": os.environ.get("PATH", ""),
|
|
},
|
|
clear=True,
|
|
),
|
|
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.get_running_jobs", side_effect=get_jobs_side_effect),
|
|
patch("os.killpg") as mock_killpg,
|
|
patch("os.getpgid") as mock_getpgid,
|
|
patch("time.sleep", side_effect=lambda x: real_sleep(0)),
|
|
):
|
|
mock_getpgid.return_value = 123
|
|
mock_killpg.side_effect = [None, ProcessLookupError("no such process")]
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
proc.wait.side_effect = [subprocess.TimeoutExpired("cmd", 10)]
|
|
mock_popen.return_value = proc
|
|
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["default|ubuntu-2204|img:latest|"])
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_main_module_block() -> None:
|
|
import devx.molecule.molecule_ci_guard as mg
|
|
|
|
with open(mg.__file__) as f:
|
|
source = f.read()
|
|
source = source.replace('if __name__ == "__main__":\n cli()\n', "")
|
|
namespace = dict(mg.__dict__)
|
|
exec(compile(source, mg.__file__, "exec"), namespace)
|
|
assert callable(namespace["cli"])
|
|
|
|
|
|
class TestParsePair:
|
|
def test_single_role_4_part(self) -> None:
|
|
role, scenario, name, image, cmd = parse_pair("default|ubuntu-2204|ubuntu:22.04|")
|
|
assert role == ""
|
|
assert scenario == "default"
|
|
assert name == "ubuntu-2204"
|
|
assert image == "ubuntu:22.04"
|
|
assert cmd == ""
|
|
|
|
def test_multi_role_5_part(self) -> None:
|
|
role, scenario, name, image, cmd = parse_pair("gitea-runner|default|ubuntu-2204|ubuntu:22.04|")
|
|
assert role == "gitea-runner"
|
|
assert scenario == "default"
|
|
assert name == "ubuntu-2204"
|
|
assert image == "ubuntu:22.04"
|
|
assert cmd == ""
|
|
|
|
def test_multi_role_with_command(self) -> None:
|
|
role, scenario, name, image, cmd = parse_pair(
|
|
"docker-base|lifecycle|archlinux|archlinux:latest|/usr/lib/systemd/systemd"
|
|
)
|
|
assert role == "docker-base"
|
|
assert scenario == "lifecycle"
|
|
assert cmd == "/usr/lib/systemd/systemd"
|
|
|
|
def test_invalid_pair_raises(self) -> None:
|
|
with pytest.raises(click.ClickException, match="Invalid pair format"):
|
|
parse_pair("only|two|parts")
|
|
|
|
def test_too_many_parts_raises(self) -> None:
|
|
with pytest.raises(click.ClickException, match="Invalid pair format"):
|
|
parse_pair("a|b|c|d|e|f")
|
|
|
|
|
|
class TestResolveRoleDir:
|
|
def test_multi_role_with_roles_root(self, tmp_path: Path) -> None:
|
|
roles_root = tmp_path / "ansible" / "roles"
|
|
roles_root.mkdir(parents=True)
|
|
result = resolve_role_dir("gitea-runner", roles_root, tmp_path)
|
|
assert result == roles_root / "gitea-runner"
|
|
|
|
def test_multi_role_default_roles_root(self, tmp_path: Path) -> None:
|
|
result = resolve_role_dir("docker-base", None, tmp_path)
|
|
assert result == tmp_path / "ansible" / "roles" / "docker-base"
|
|
|
|
def test_single_role_uses_default(self, tmp_path: Path) -> None:
|
|
result = resolve_role_dir("", None, tmp_path)
|
|
assert result == tmp_path / "ansible" / "roles" / "gitea-runner"
|
|
|
|
|
|
class TestCliMultiRole:
|
|
def test_multi_role_pair_passes(self, tmp_path: Path) -> None:
|
|
from click.testing import CliRunner
|
|
|
|
roles_root = tmp_path / "ansible" / "roles"
|
|
(roles_root / "gitea-runner").mkdir(parents=True)
|
|
|
|
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,
|
|
["--roles-root", str(roles_root), "gitea-runner|default|ubuntu-2204|ubuntu:22.04|"],
|
|
)
|
|
assert result.exit_code == 0
|
|
assert "All molecule tests passed" in result.output
|