GRM-44: fix: bridge test suite gaps — lint scripts, include integration tests
This commit is contained in:
@@ -67,10 +67,10 @@ remove:
|
|||||||
$(BIN)/grm remove $(NAME) $(if $(HOST),--host $(HOST),) $(if $(USER),--user $(USER),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
$(BIN)/grm remove $(NAME) $(if $(HOST),--host $(HOST),) $(if $(USER),--user $(USER),) $(if $(TOKEN),--token $(TOKEN),) $(if $(ASK_BECOME_PASS),--ask-become-pass,)
|
||||||
|
|
||||||
lint-ruff:
|
lint-ruff:
|
||||||
$(BIN)/ruff check src/ tests/
|
$(BIN)/ruff check src/ tests/ scripts/
|
||||||
|
|
||||||
lint-format:
|
lint-format:
|
||||||
$(BIN)/ruff format --check src/ tests/
|
$(BIN)/ruff format --check src/ tests/ scripts/
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
$(BIN)/pyright
|
$(BIN)/pyright
|
||||||
@@ -78,7 +78,7 @@ typecheck:
|
|||||||
lint: lint-ruff lint-format typecheck lint-bandit
|
lint: lint-ruff lint-format typecheck lint-bandit
|
||||||
|
|
||||||
lint-bandit:
|
lint-bandit:
|
||||||
$(BIN)/bandit -r src/ scripts/ scripts/ci/
|
$(BIN)/bandit -r src/ scripts/
|
||||||
|
|
||||||
ansible-lint:
|
ansible-lint:
|
||||||
$(BIN)/ansible-lint ansible/
|
$(BIN)/ansible-lint ansible/
|
||||||
@@ -95,7 +95,7 @@ test-integration:
|
|||||||
$(BIN)/pytest tests/integration/ -v --no-cov
|
$(BIN)/pytest tests/integration/ -v --no-cov
|
||||||
|
|
||||||
pytest-cov:
|
pytest-cov:
|
||||||
$(BIN)/pytest tests/unit/ -v --cov=src/gitea_runner_manager --cov=scripts --cov=scripts/ci --cov-report=term-missing --cov-fail-under=100
|
$(BIN)/pytest tests/ -v --cov=src/gitea_runner_manager --cov=scripts --cov-report=term-missing --cov-fail-under=100
|
||||||
|
|
||||||
MOLECULE := $(realpath $(BIN))/molecule
|
MOLECULE := $(realpath $(BIN))/molecule
|
||||||
MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE)
|
MOLECULE_BASE := cd $(CURDIR)/ansible/roles/gitea-runner && ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true ANSIBLE_INJECT_INVOCATION=1 $(MOLECULE)
|
||||||
|
|||||||
@@ -66,14 +66,8 @@ def discover_scenarios(root: Path | None = None) -> list[str]:
|
|||||||
if root is None:
|
if root is None:
|
||||||
root = MOLECULE_ROOT
|
root = MOLECULE_ROOT
|
||||||
if not root.is_dir():
|
if not root.is_dir():
|
||||||
raise click.ClickException(
|
raise click.ClickException(_("Molecule directory not found: {path}", path=str(root)))
|
||||||
_("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"
|
|
||||||
]
|
|
||||||
return sorted(scenarios)
|
return sorted(scenarios)
|
||||||
|
|
||||||
|
|
||||||
@@ -92,9 +86,7 @@ def distribute(pairs: list[TestPair], max_runners: int) -> list[list[TestPair]]:
|
|||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
||||||
def pairs_for_runner(
|
def pairs_for_runner(pairs: list[TestPair], runner_index: int, max_runners: int) -> list[TestPair]:
|
||||||
pairs: list[TestPair], runner_index: int, max_runners: int
|
|
||||||
) -> list[TestPair]:
|
|
||||||
"""Return the subset of pairs assigned to *runner_index*."""
|
"""Return the subset of pairs assigned to *runner_index*."""
|
||||||
groups = distribute(pairs, max_runners)
|
groups = distribute(pairs, max_runners)
|
||||||
if runner_index < 0 or runner_index >= len(groups):
|
if runner_index < 0 or runner_index >= len(groups):
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def extract_cli_commands() -> list[str]:
|
|||||||
content = CLI_FILE.read_text()
|
content = CLI_FILE.read_text()
|
||||||
commands: list[str] = []
|
commands: list[str] = []
|
||||||
# Find all @cli.command(...) occurrences, then the next def statement
|
# Find all @cli.command(...) occurrences, then the next def statement
|
||||||
for match in re.finditer(r'@cli\.command\b', content):
|
for match in re.finditer(r"@cli\.command\b", content):
|
||||||
# Check for explicit name="..." in the decorator arguments
|
# Check for explicit name="..." in the decorator arguments
|
||||||
decorator_end = content.find(")", match.start())
|
decorator_end = content.find(")", match.start())
|
||||||
decorator_text = content[match.start() : decorator_end + 1]
|
decorator_text = content[match.start() : decorator_end + 1]
|
||||||
@@ -63,7 +63,7 @@ def extract_cli_commands() -> list[str]:
|
|||||||
continue
|
continue
|
||||||
# Find the next def statement after this decorator
|
# Find the next def statement after this decorator
|
||||||
after = content[decorator_end:]
|
after = content[decorator_end:]
|
||||||
def_match = re.search(r'def\s+(\w+)\s*\(', after)
|
def_match = re.search(r"def\s+(\w+)\s*\(", after)
|
||||||
if def_match:
|
if def_match:
|
||||||
commands.append(def_match.group(1))
|
commands.append(def_match.group(1))
|
||||||
return commands
|
return commands
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ from gitea_runner_manager.i18n import _
|
|||||||
POLL_INTERVAL = 10
|
POLL_INTERVAL = 10
|
||||||
|
|
||||||
|
|
||||||
def get_running_jobs(
|
def get_running_jobs(gitea_url: str, owner: str, repo: str, token: str, run_id: int) -> list[dict]:
|
||||||
gitea_url: str, owner: str, repo: str, token: str, run_id: int
|
|
||||||
) -> list[dict]:
|
|
||||||
"""Return jobs for the given workflow run."""
|
"""Return jobs for the given workflow run."""
|
||||||
url = f"{gitea_url}/api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs"
|
url = f"{gitea_url}/api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs"
|
||||||
headers = {"Authorization": f"token {token}"}
|
headers = {"Authorization": f"token {token}"}
|
||||||
@@ -52,9 +50,7 @@ def get_running_jobs(
|
|||||||
return data.get("jobs", [])
|
return data.get("jobs", [])
|
||||||
|
|
||||||
|
|
||||||
def any_other_runner_failed(
|
def any_other_runner_failed(jobs: list[dict], current_job_name: str, current_index: int) -> bool:
|
||||||
jobs: list[dict], current_job_name: str, current_index: int
|
|
||||||
) -> bool:
|
|
||||||
"""Return True if any other molecule matrix job has failed."""
|
"""Return True if any other molecule matrix job has failed."""
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
name = job.get("name", "")
|
name = job.get("name", "")
|
||||||
@@ -83,11 +79,7 @@ def poll_for_other_failures(
|
|||||||
try:
|
try:
|
||||||
jobs = get_running_jobs(gitea_url, owner, repo, token, run_id)
|
jobs = get_running_jobs(gitea_url, owner, repo, token, run_id)
|
||||||
if any_other_runner_failed(jobs, job_name, current_index):
|
if any_other_runner_failed(jobs, job_name, current_index):
|
||||||
click.echo(
|
click.echo(_("Another molecule runner failed. Stopping this runner early."))
|
||||||
_(
|
|
||||||
"Another molecule runner failed. Stopping this runner early."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
failed_event.set()
|
failed_event.set()
|
||||||
return
|
return
|
||||||
except requests.RequestException as exc:
|
except requests.RequestException as exc:
|
||||||
@@ -132,11 +124,7 @@ def cli(pairs: tuple[str, ...]) -> None:
|
|||||||
owner, repo = "oblachno-oss", "grm"
|
owner, repo = "oblachno-oss", "grm"
|
||||||
|
|
||||||
if not all([gitea_url, token, run_id]):
|
if not all([gitea_url, token, run_id]):
|
||||||
click.echo(
|
click.echo(_("GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation."))
|
||||||
_(
|
|
||||||
"GITEA_URL/REPO_TOKEN/RUN_ID not set; running without cross-runner cancellation."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
repo_root = Path(__file__).resolve().parent.parent
|
repo_root = Path(__file__).resolve().parent.parent
|
||||||
role_dir = repo_root / "ansible" / "roles" / "gitea-runner"
|
role_dir = repo_root / "ansible" / "roles" / "gitea-runner"
|
||||||
@@ -173,9 +161,7 @@ def cli(pairs: tuple[str, ...]) -> None:
|
|||||||
|
|
||||||
scenario = pair.split("|")[0]
|
scenario = pair.split("|")[0]
|
||||||
platform_name = pair.split("|")[1]
|
platform_name = pair.split("|")[1]
|
||||||
click.echo(
|
click.echo(_("Running: {scenario} on {platform}", scenario=scenario, platform=platform_name))
|
||||||
_("Running: {scenario} on {platform}", scenario=scenario, platform=platform_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
cmd = build_molecule_cmd(scenario)
|
cmd = build_molecule_cmd(scenario)
|
||||||
env = build_env_for_pair(pair, base_env)
|
env = build_env_for_pair(pair, base_env)
|
||||||
@@ -208,9 +194,7 @@ def cli(pairs: tuple[str, ...]) -> None:
|
|||||||
|
|
||||||
rc = process.returncode
|
rc = process.returncode
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
click.echo(
|
click.echo(_("FAILED: {pair} exited with code {code}", pair=pair, code=rc))
|
||||||
_("FAILED: {pair} exited with code {code}", pair=pair, code=rc)
|
|
||||||
)
|
|
||||||
sys.exit(rc)
|
sys.exit(rc)
|
||||||
|
|
||||||
click.echo(_("PASSED: {pair}", pair=pair))
|
click.echo(_("PASSED: {pair}", pair=pair))
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ def _handle_http_error(e: APIError) -> None:
|
|||||||
status=e.status,
|
status=e.status,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
raise click.ClickException(
|
raise click.ClickException(_("HTTP error: {status} — {message}", status=e.status, message=e.message))
|
||||||
_("HTTP error: {status} — {message}", status=e.status, message=e.message)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ def _arch() -> str:
|
|||||||
return "amd64"
|
return "amd64"
|
||||||
if machine in {"aarch64", "arm64"}:
|
if machine in {"aarch64", "arm64"}:
|
||||||
return "arm64"
|
return "arm64"
|
||||||
raise click.ClickException(
|
raise click.ClickException(f"Unsupported architecture: {machine}")
|
||||||
f"Unsupported architecture: {machine}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _install_with_go() -> bool:
|
def _install_with_go() -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user