Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
run: uv run --no-sync mutmut export-cicd-stats

- name: Upload Mutmut JSON Report
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
name: mutmut-json-report
path: mutants/mutmut-cicd-stats.json
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
[![](https://img.shields.io/badge/created%20an%20AGI%20by%20mistake-no-3C1)](https://github.com/sebmestrallet/absurd-badges)
![Claude](https://img.shields.io/badge/Claude-D97757?style=for-the-badge&logo=claude&logoColor=white)
[![gate](https://github.com/rxdt/loopgate_harness/actions/workflows/ci.yml/badge.svg)](https://github.com/rxdt/loopgate_harness/actions/workflows/ci.yml)
[![mutation](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Frxdt%2Floopgate_harness%2Fmain%2Fmutation-score.json)](https://github.com/rxdt/loopgate_harness/actions/workflows/mutation.yml)

</div>

Expand Down
5 changes: 3 additions & 2 deletions harness/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
help="Commands to harness the loops",
no_args_is_help=True,
add_completion=False,
rich_markup_mode="rich",
rich_markup_mode=None if os.environ.get("RALPH_LOOP") else "rich",
)
console = Console(force_terminal=True)
console = Console(force_terminal=True, color_system=None if os.environ.get("RALPH_LOOP") else "256")
REPO_ROOT_STR = str(gates.repo_root)


Expand Down Expand Up @@ -198,6 +198,7 @@ def cleanup(cwd: Path, name: str | None) -> bool:
".loops.svg",
".github/workflows/publish.yml",
"CONTRIBUTING.md",
"LICENSE.md",
):
(cwd / file_name).unlink(missing_ok=True)
for directory in (cwd / "dist", cwd / "harness" / "tests"):
Expand Down
3 changes: 2 additions & 1 deletion harness/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import typer
from rich.console import Console

console = Console(force_terminal=True, color_system="256")
console = Console(force_terminal=True, color_system=None if os.environ.get("RALPH_LOOP") else "256")

try:
from preferences.preferences import preferences_violations as prefs
except ImportError: # humans can delete preferences.py
Expand Down
8 changes: 7 additions & 1 deletion harness/tests/test_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,12 +485,18 @@ def test_gate_runs_exactly_what_pyproject_configures(
(git_repo / "pyproject.toml").write_text("[project]\nname = 'x'\n", encoding="utf-8")
with pytest.raises(KeyError):
Gate(git_repo)
expected_repo_root = Path(
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], cwd=REPO_ROOT, text=True).strip()
)
assert (
Path(gate.run_git(["rev-parse", "--show-toplevel"]).strip()),
Path(gate.run_git(["rev-parse", "--show-toplevel"], REPO_ROOT).strip()),
) == (git_repo, REPO_ROOT)
) == (git_repo, expected_repo_root)
monkeypatch.setenv("GIT_DIR", str(git_repo / "no-such-dir"))
monkeypatch.delenv("RALPH_LOOP", raising=False)
monkeypatch.setattr(
gate, "console", gate.Console(force_terminal=True, color_system="256", no_color=False)
)
assert Path(gate.run_git(["rev-parse", "--show-toplevel"]).strip()) == git_repo
monkeypatch.delenv("GIT_DIR")
absent = ["rev-parse", "--verify", "refs/heads/absent"]
Expand Down
6 changes: 6 additions & 0 deletions mutation-score.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"schemaVersion": 1,
"label": "mutation",
"message": "83.0%",
"color": "#55ff00"
}
6 changes: 4 additions & 2 deletions mutation/check_mutmut.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@
from __future__ import annotations

import json
import os
from pathlib import Path

import typer
from rich import print as rprint
from rich.console import Console
from rich.table import Table

console = Console(force_terminal=True)
MINIMUM_MUTATION_SCORE = 60.0
console = Console(force_terminal=True, color_system=None if os.environ.get("RALPH_LOOP") else "256")

MINIMUM_MUTATION_SCORE = 80.0

JsonDocument = dict[str, object] | list[object] | str | int | float | bool | None

Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name = "harness"
version = "0.1.0"
requires-python = ">=3.10,<4"
dependencies = [
"mutmut>=3.7.0",
"packaging",
"tomlkit",
"typer",
Expand Down Expand Up @@ -211,8 +212,9 @@ skip_covered = false # Same behavior for older coverage config rea
# ==============================================================================
[tool.mutmut]
max-children = 2
source_paths = ["preferences", "src", "mutation"] # what to mutate
also_copy = ["harness", ".githooks"] # not mutated paths, copied for imports
source_paths = ["preferences", "src", "harness"] # what to mutate
also_copy = ["mutation", ".githooks"] # not mutated paths, copied for imports
do_not_mutate = ["harness/tests/*"]

# ==============================================================================
# Complexipy Configuration
Expand Down
16 changes: 10 additions & 6 deletions tests/mutation/test_check_mutmut.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import typer
from click import unstyle

from mutation.check_mutmut import analyze_mutmut_report
from mutation.check_mutmut import MINIMUM_MUTATION_SCORE, analyze_mutmut_report


def test_report_with_timeout_passes_and_renders(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
Expand Down Expand Up @@ -40,18 +40,22 @@ def test_report_enforces_threshold(tmp_path: Path, capsys: pytest.CaptureFixture
report = tmp_path / "mutmut-cicd-stats.json"
report.write_text(json.dumps(data), encoding="utf-8")
mutation_score = analyze_mutmut_report(str(report))
assert 60.0 <= mutation_score < 100.0
assert MINIMUM_MUTATION_SCORE <= mutation_score < 100.0

report.write_text(json.dumps({"killed": 3, "timeout": 0, "total": 5, "skipped": 0}), encoding="utf-8")
assert analyze_mutmut_report(str(report)) == pytest.approx(60.0)
passing_report = {"killed": MINIMUM_MUTATION_SCORE, "timeout": 0, "total": 100, "skipped": 0}
report.write_text(json.dumps(passing_report), encoding="utf-8")
assert analyze_mutmut_report(str(report)) == pytest.approx(MINIMUM_MUTATION_SCORE)

report.write_text(json.dumps({"killed": 59, "timeout": 0, "total": 100, "skipped": 0}), encoding="utf-8")
failing_score = MINIMUM_MUTATION_SCORE - 1
report.write_text(
json.dumps({"killed": failing_score, "timeout": 0, "total": 100, "skipped": 0}), encoding="utf-8"
)
with pytest.raises(typer.Exit) as exc_info:
analyze_mutmut_report(str(report))

assert exc_info.value.exit_code == 1
output = " ".join(unstyle(capsys.readouterr().out).split())
assert "MUTATION SCORE: 59.0" in output
assert f"MUTATION SCORE: {failing_score}" in output


def test_missing_report_fails(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
Expand Down
Loading