python-harness 0.0.3__tar.gz → 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_harness-0.0.3/python_harness.egg-info → python_harness-0.0.6}/PKG-INFO +1 -1
- {python_harness-0.0.3 → python_harness-0.0.6}/pyproject.toml +1 -1
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/cli.py +14 -1
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/hard_evaluator.py +19 -3
- {python_harness-0.0.3 → python_harness-0.0.6/python_harness.egg-info}/PKG-INFO +1 -1
- python_harness-0.0.6/tests/test_hard_evaluator.py +95 -0
- python_harness-0.0.3/tests/test_hard_evaluator.py +0 -29
- {python_harness-0.0.3 → python_harness-0.0.6}/LICENSE +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/README.md +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/__init__.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/evaluator.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/qc_evaluator.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness/soft_evaluator.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness.egg-info/SOURCES.txt +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness.egg-info/dependency_links.txt +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness.egg-info/entry_points.txt +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness.egg-info/requires.txt +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/python_harness.egg-info/top_level.txt +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/setup.cfg +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/tests/test_cli.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/tests/test_evaluator.py +0 -0
- {python_harness-0.0.3 → python_harness-0.0.6}/tests/test_soft_evaluator.py +0 -0
|
@@ -104,7 +104,7 @@ def measure(path: str = typer.Argument(".", help="The path to evaluate")) -> Non
|
|
|
104
104
|
if hard_results["mypy"]["status"] != "success":
|
|
105
105
|
output = hard_results["mypy"].get("output", "")
|
|
106
106
|
console.print(f"[red]Mypy issues found:[/red]\n{output}")
|
|
107
|
-
if hard_results["ty"]["status"]
|
|
107
|
+
if hard_results["ty"]["status"] not in ("success", "warning"):
|
|
108
108
|
output = hard_results["ty"].get("output", "")
|
|
109
109
|
# ty might print to stderr instead of stdout, or it might be missing
|
|
110
110
|
error_msg = hard_results["ty"].get("error_message", "")
|
|
@@ -116,6 +116,9 @@ def measure(path: str = typer.Argument(".", help="The path to evaluate")) -> Non
|
|
|
116
116
|
console.print(
|
|
117
117
|
"[red]Ty failed, but no standard output was captured.[/red]"
|
|
118
118
|
)
|
|
119
|
+
elif hard_results["ty"]["status"] == "warning":
|
|
120
|
+
msg = hard_results["ty"].get("error_message", "ty not found")
|
|
121
|
+
console.print(f"[yellow]Ty warning:[/yellow] {msg}")
|
|
119
122
|
if hard_results["radon_cc"]["status"] != "success":
|
|
120
123
|
issues = hard_results["radon_cc"].get("issues", [])
|
|
121
124
|
console.print(
|
|
@@ -127,6 +130,16 @@ def measure(path: str = typer.Argument(".", help="The path to evaluate")) -> Non
|
|
|
127
130
|
f" - {issue['file']}: {issue['type']} '{issue['name']}' "
|
|
128
131
|
f"has CC {issue['complexity']}"
|
|
129
132
|
)
|
|
133
|
+
|
|
134
|
+
# If radon failed for another reason
|
|
135
|
+
# (e.g. radon not installed or syntax error)
|
|
136
|
+
if not issues and hard_results["radon_cc"].get("error_message"):
|
|
137
|
+
err_msg = hard_results['radon_cc'].get('error_message')
|
|
138
|
+
console.print(f"[red]Radon CC Error:[/red] {err_msg}")
|
|
139
|
+
elif not issues:
|
|
140
|
+
console.print(
|
|
141
|
+
"[red]Radon CC failed but no specific issues were parsed.[/red]"
|
|
142
|
+
)
|
|
130
143
|
sys.exit(1)
|
|
131
144
|
|
|
132
145
|
console.print("[bold green]Hard Evaluation Passed![/bold green]")
|
|
@@ -63,6 +63,7 @@ class HardEvaluator:
|
|
|
63
63
|
def run_ty(self) -> dict[str, Any]:
|
|
64
64
|
"""
|
|
65
65
|
Run ty language server checks.
|
|
66
|
+
If ty is not installed, fail gracefully rather than crashing.
|
|
66
67
|
"""
|
|
67
68
|
try:
|
|
68
69
|
result = subprocess.run(
|
|
@@ -79,7 +80,18 @@ class HardEvaluator:
|
|
|
79
80
|
"output": output,
|
|
80
81
|
"return_code": result.returncode,
|
|
81
82
|
}
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
return {
|
|
85
|
+
"status": "warning",
|
|
86
|
+
"error_message": "ty executable not found. Skipping ty checks."
|
|
87
|
+
}
|
|
82
88
|
except Exception as e:
|
|
89
|
+
# Handle cases where ty is found but fails to run or throws other OS errors
|
|
90
|
+
if "No such file or directory: 'ty'" in str(e):
|
|
91
|
+
return {
|
|
92
|
+
"status": "warning",
|
|
93
|
+
"error_message": "ty executable not found. Skipping ty checks."
|
|
94
|
+
}
|
|
83
95
|
return {"status": "error", "error_message": str(e)}
|
|
84
96
|
|
|
85
97
|
def run_radon_cc(self) -> dict[str, Any]:
|
|
@@ -111,14 +123,18 @@ class HardEvaluator:
|
|
|
111
123
|
"complexity": block.get('complexity')
|
|
112
124
|
})
|
|
113
125
|
|
|
114
|
-
if
|
|
126
|
+
if result.returncode != 0:
|
|
127
|
+
# E.g. syntax error in target code preventing radon from parsing
|
|
128
|
+
status = "failed"
|
|
129
|
+
elif issues:
|
|
115
130
|
status = "failed"
|
|
116
131
|
|
|
117
132
|
return {
|
|
118
133
|
"status": status,
|
|
119
134
|
"issues": issues,
|
|
120
135
|
"return_code": result.returncode,
|
|
121
|
-
"output": result.stdout
|
|
136
|
+
"output": result.stdout,
|
|
137
|
+
"error_message": result.stderr if result.returncode != 0 else ""
|
|
122
138
|
}
|
|
123
139
|
except Exception as e:
|
|
124
140
|
return {"status": "error", "error_message": str(e)}
|
|
@@ -188,7 +204,7 @@ class HardEvaluator:
|
|
|
188
204
|
all_passed = (
|
|
189
205
|
ruff_res.get("status") == "success" and
|
|
190
206
|
mypy_res.get("status") == "success" and
|
|
191
|
-
ty_res.get("status")
|
|
207
|
+
ty_res.get("status") in ("success", "warning") and
|
|
192
208
|
radon_cc_res.get("status") == "success"
|
|
193
209
|
)
|
|
194
210
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for hard evaluation logic.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from python_harness.hard_evaluator import HardEvaluator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_hard_evaluator_methods() -> None:
|
|
12
|
+
"""
|
|
13
|
+
Test methods of HardEvaluator.
|
|
14
|
+
"""
|
|
15
|
+
evaluator = HardEvaluator(".")
|
|
16
|
+
|
|
17
|
+
ruff_result = evaluator.run_ruff()
|
|
18
|
+
assert "status" in ruff_result
|
|
19
|
+
|
|
20
|
+
mypy_result = evaluator.run_mypy()
|
|
21
|
+
assert "status" in mypy_result
|
|
22
|
+
|
|
23
|
+
ty_result = evaluator.run_ty()
|
|
24
|
+
assert "status" in ty_result
|
|
25
|
+
|
|
26
|
+
# pytest_result = evaluator.run_pytest() # Causes infinite loop when run in test
|
|
27
|
+
# assert "status" in pytest_result
|
|
28
|
+
|
|
29
|
+
eval_result = evaluator.evaluate()
|
|
30
|
+
assert "ruff" in eval_result
|
|
31
|
+
assert "mypy" in eval_result
|
|
32
|
+
assert "ty" in eval_result
|
|
33
|
+
|
|
34
|
+
def test_ty_fallback_behavior(monkeypatch: Any) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Test that run_ty gracefully falls back to a warning when 'ty' is not installed.
|
|
37
|
+
"""
|
|
38
|
+
# Create a mock subprocess.run that always raises FileNotFoundError
|
|
39
|
+
# to simulate 'ty' not being on the system PATH
|
|
40
|
+
def mock_run(*args: Any, **kwargs: Any) -> Any:
|
|
41
|
+
raise FileNotFoundError("[Errno 2] No such file or directory: 'ty'")
|
|
42
|
+
|
|
43
|
+
monkeypatch.setattr("subprocess.run", mock_run)
|
|
44
|
+
|
|
45
|
+
evaluator = HardEvaluator(".")
|
|
46
|
+
result = evaluator.run_ty()
|
|
47
|
+
|
|
48
|
+
assert result["status"] == "warning"
|
|
49
|
+
assert "ty executable not found" in result["error_message"]
|
|
50
|
+
|
|
51
|
+
def test_ty_fallback_behavior_oserror(monkeypatch: Any) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Test that run_ty gracefully falls back to a warning when a generic Exception
|
|
54
|
+
containing the Errno 2 string is thrown.
|
|
55
|
+
"""
|
|
56
|
+
def mock_run(*args: Any, **kwargs: Any) -> Any:
|
|
57
|
+
raise Exception("[Errno 2] No such file or directory: 'ty'")
|
|
58
|
+
|
|
59
|
+
monkeypatch.setattr("subprocess.run", mock_run)
|
|
60
|
+
|
|
61
|
+
evaluator = HardEvaluator(".")
|
|
62
|
+
result = evaluator.run_ty()
|
|
63
|
+
|
|
64
|
+
assert result["status"] == "warning"
|
|
65
|
+
assert "ty executable not found" in result["error_message"]
|
|
66
|
+
|
|
67
|
+
def test_radon_cc_syntax_error(monkeypatch: Any, tmp_path: Path) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Test that run_radon_cc correctly captures and reports stderr when radon
|
|
70
|
+
fails (e.g. due to syntax errors in the target codebase).
|
|
71
|
+
"""
|
|
72
|
+
# Create a mock subprocess.run that simulates radon exiting with code 1
|
|
73
|
+
# and writing an error to stderr (which happens when there are syntax errors)
|
|
74
|
+
import subprocess
|
|
75
|
+
original_run = subprocess.run
|
|
76
|
+
|
|
77
|
+
def mock_run(args: Any, **kwargs: Any) -> Any:
|
|
78
|
+
if args and args[0] == "radon" and args[1] == "cc":
|
|
79
|
+
# Simulate radon failing on syntax error
|
|
80
|
+
class MockResult:
|
|
81
|
+
returncode = 1
|
|
82
|
+
stdout = ""
|
|
83
|
+
stderr = "ERROR: SyntaxError in bad.py"
|
|
84
|
+
return MockResult()
|
|
85
|
+
return original_run(args, **kwargs)
|
|
86
|
+
|
|
87
|
+
monkeypatch.setattr("subprocess.run", mock_run)
|
|
88
|
+
|
|
89
|
+
evaluator = HardEvaluator(str(tmp_path))
|
|
90
|
+
result = evaluator.run_radon_cc()
|
|
91
|
+
|
|
92
|
+
assert result["status"] == "failed"
|
|
93
|
+
assert len(result["issues"]) == 0
|
|
94
|
+
# Radon should output to stderr because of the syntax error
|
|
95
|
+
assert "SyntaxError" in result["error_message"] or result["return_code"] != 0
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tests for hard evaluation logic.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from python_harness.hard_evaluator import HardEvaluator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_hard_evaluator_methods() -> None:
|
|
9
|
-
"""
|
|
10
|
-
Test methods of HardEvaluator.
|
|
11
|
-
"""
|
|
12
|
-
evaluator = HardEvaluator(".")
|
|
13
|
-
|
|
14
|
-
ruff_result = evaluator.run_ruff()
|
|
15
|
-
assert "status" in ruff_result
|
|
16
|
-
|
|
17
|
-
mypy_result = evaluator.run_mypy()
|
|
18
|
-
assert "status" in mypy_result
|
|
19
|
-
|
|
20
|
-
ty_result = evaluator.run_ty()
|
|
21
|
-
assert "status" in ty_result
|
|
22
|
-
|
|
23
|
-
# pytest_result = evaluator.run_pytest() # Causes infinite loop when run in test
|
|
24
|
-
# assert "status" in pytest_result
|
|
25
|
-
|
|
26
|
-
eval_result = evaluator.evaluate()
|
|
27
|
-
assert "ruff" in eval_result
|
|
28
|
-
assert "mypy" in eval_result
|
|
29
|
-
assert "ty" in eval_result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|