From 77972744acf51eeb8b2a1dc775a49347f312de91 Mon Sep 17 00:00:00 2001 From: Avi Avraham Date: Mon, 8 Jun 2026 16:34:54 +0300 Subject: [PATCH 1/3] fix: skip test files and test functions in Python type annotations check (#385) Test files (test_*.py, *_test.py, conftest.py, tests/ dirs) and test functions (test_*) were penalizing repos for missing type hints on code that doesn't benefit from annotations. Aligns Python behavior with the existing Go _test.go exclusion. Co-Authored-By: Claude Opus 4.6 --- docs/attributes.md | 1 + src/agentready/assessors/code_quality.py | 26 +++- tests/unit/test_assessors_code_quality.py | 138 ++++++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/docs/attributes.md b/docs/attributes.md index 2d1e9c00..c298a0f1 100644 --- a/docs/attributes.md +++ b/docs/attributes.md @@ -267,6 +267,7 @@ Type annotations give agents reliable information about what a function expects - Generic types from `typing` module used appropriately - Coverage: >80% of functions typed - **Strict mode bonus** (+15 pts): type checker configured in strict mode. Checked configs: `mypy.ini`/`.mypy.ini` (`strict = true` or `disallow_untyped_defs = true`), `setup.cfg` `[mypy]`, `pyproject.toml` `[tool.mypy]`, `pyrightconfig.json` (`typeCheckingMode: "strict"`), `pyproject.toml` `[tool.pyright]` +- Test files and test functions (`test_*`) are excluded from scoring - Tools: mypy, pyright **TypeScript**: diff --git a/src/agentready/assessors/code_quality.py b/src/agentready/assessors/code_quality.py index 271cd508..fa9065d5 100644 --- a/src/agentready/assessors/code_quality.py +++ b/src/agentready/assessors/code_quality.py @@ -97,11 +97,16 @@ def _assess_python_types(self, repository: Repository) -> Finding: timeout=30, check=True, ) - python_files = [f for f in result.stdout.strip().split("\n") if f] + python_files = [ + f + for f in result.stdout.strip().split("\n") + if f and not self._is_python_test_file(f) + ] except Exception: python_files = [ str(f.relative_to(repository.path)) for f in repository.path.rglob("*.py") + if not self._is_python_test_file(str(f.relative_to(repository.path))) ] total_functions = 0 @@ -110,7 +115,7 @@ def _assess_python_types(self, repository: Repository) -> Finding: for file_path in python_files: full_path = repository.path / file_path try: - with open(full_path, "r", encoding="utf-8") as f: + with open(full_path, encoding="utf-8") as f: content = f.read() # Parse the file with AST @@ -119,6 +124,8 @@ def _assess_python_types(self, repository: Repository) -> Finding: # Walk the AST and count functions with type annotations for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): + if node.name.startswith("test_"): + continue total_functions += 1 # Check if function has type annotations # Return type annotation: node.returns is not None @@ -246,6 +253,21 @@ def _check_python_strict_mode( return 0.0, [] + @staticmethod + def _is_python_test_file(file_path: str) -> bool: + """Check if a Python file is a test file based on path and name conventions.""" + from pathlib import PurePosixPath + + parts = PurePosixPath(file_path).parts + name = parts[-1] if parts else "" + if ( + name.startswith("test_") + or name.endswith("_test.py") + or name == "conftest.py" + ): + return True + return any(p in ("tests", "test") for p in parts) + def _assess_typescript_types(self, repository: Repository) -> Finding: """Assess TypeScript type configuration across all tsconfig.json files. diff --git a/tests/unit/test_assessors_code_quality.py b/tests/unit/test_assessors_code_quality.py index 4629dd03..6171ef84 100644 --- a/tests/unit/test_assessors_code_quality.py +++ b/tests/unit/test_assessors_code_quality.py @@ -4,6 +4,7 @@ import subprocess from unittest.mock import patch +import pytest from agentready.assessors.code_quality import ( CyclomaticComplexityAssessor, TypeAnnotationsAssessor, @@ -422,3 +423,140 @@ def test_radon_exception_returns_error(self, tmp_path): assert finding.status == "error" assert "radon crashed" in finding.error_message + + +# ============================================================================= +# TypeAnnotationsAssessor — Python test file/function skipping (#385) +# ============================================================================= + + +def _make_py_repo(tmp_path, languages=None): + """Create a test Python repository with git init.""" + subprocess.run(["git", "init"], cwd=tmp_path, capture_output=True, check=True) + return Repository( + path=tmp_path, + name="test-py-repo", + url=None, + branch="main", + commit_hash="abc123", + languages=languages or {"Python": 20}, + total_files=30, + total_lines=5000, + ) + + +def _git_add(tmp_path, *files): + """Stage files in git so git ls-files finds them.""" + for f in files: + subprocess.run( + ["git", "add", str(f)], + cwd=tmp_path, + capture_output=True, + check=True, + ) + + +class TestIsTestFile: + """Unit tests for _is_python_test_file static method.""" + + @pytest.mark.parametrize( + "path", + [ + "tests/test_foo.py", + "test/test_bar.py", + "tests/unit/test_baz.py", + "src/tests/test_helpers.py", + "test_something.py", + "foo_test.py", + "conftest.py", + "tests/conftest.py", + ], + ) + def test_identifies_test_files(self, path): + assert TypeAnnotationsAssessor._is_python_test_file(path) is True + + @pytest.mark.parametrize( + "path", + [ + "src/app.py", + "src/utils/helpers.py", + "main.py", + "lib/testing_utils.py", + ], + ) + def test_identifies_non_test_files(self, path): + assert TypeAnnotationsAssessor._is_python_test_file(path) is False + + +class TestPythonTypeAnnotationsSkipTests: + """Integration tests: test files and test functions are excluded from scoring.""" + + def test_test_files_excluded_from_scoring(self, tmp_path): + """Test files in tests/ should not affect type annotation scoring.""" + repo = _make_py_repo(tmp_path) + + # Source file: fully typed + src = tmp_path / "src" + src.mkdir() + (src / "app.py").write_text("def greet(name: str) -> str:\n return name\n") + + # Test file: untyped (should be excluded) + tests = tmp_path / "tests" + tests.mkdir() + (tests / "test_app.py").write_text("def test_greet():\n assert True\n") + + _git_add(tmp_path, src / "app.py", tests / "test_app.py") + + assessor = TypeAnnotationsAssessor() + finding = assessor._assess_python_types(repo) + + assert finding.status == "pass" + assert "1/1" in finding.evidence[0] + + def test_test_functions_excluded_in_source_files(self, tmp_path): + """Test functions (test_*) inside source files should be excluded.""" + repo = _make_py_repo(tmp_path) + + (tmp_path / "app.py").write_text( + "def greet(name: str) -> str:\n" + " return name\n" + "\n" + "def test_greet():\n" + " assert greet('hi') == 'hi'\n" + ) + + _git_add(tmp_path, tmp_path / "app.py") + + assessor = TypeAnnotationsAssessor() + finding = assessor._assess_python_types(repo) + + assert finding.status == "pass" + assert "1/1" in finding.evidence[0] + + def test_untyped_source_still_fails(self, tmp_path): + """Non-test source without annotations should still fail.""" + repo = _make_py_repo(tmp_path) + + (tmp_path / "app.py").write_text("def greet(name):\n return name\n") + + _git_add(tmp_path, tmp_path / "app.py") + + assessor = TypeAnnotationsAssessor() + finding = assessor._assess_python_types(repo) + + assert finding.status == "fail" + + def test_only_test_files_returns_not_applicable(self, tmp_path): + """Repo with only test files should return not_applicable.""" + repo = _make_py_repo(tmp_path) + + tests = tmp_path / "tests" + tests.mkdir() + (tests / "test_foo.py").write_text("def test_foo():\n assert True\n") + + _git_add(tmp_path, tests / "test_foo.py") + + assessor = TypeAnnotationsAssessor() + finding = assessor._assess_python_types(repo) + + assert finding.status == "not_applicable" From 0c044c1191791939dd7227b12fd7c85290bdd904 Mon Sep 17 00:00:00 2001 From: Avi Avraham Date: Mon, 8 Jun 2026 16:46:36 +0300 Subject: [PATCH 2/3] fix: normalize backslash path separators in test file detection Addresses CodeRabbit review: PurePosixPath doesn't split Windows backslash separators, so normalize before classification. Co-Authored-By: Claude Opus 4.6 --- src/agentready/assessors/code_quality.py | 3 ++- tests/unit/test_assessors_code_quality.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agentready/assessors/code_quality.py b/src/agentready/assessors/code_quality.py index fa9065d5..126cb351 100644 --- a/src/agentready/assessors/code_quality.py +++ b/src/agentready/assessors/code_quality.py @@ -258,7 +258,8 @@ def _is_python_test_file(file_path: str) -> bool: """Check if a Python file is a test file based on path and name conventions.""" from pathlib import PurePosixPath - parts = PurePosixPath(file_path).parts + normalized = file_path.replace("\\", "/") + parts = PurePosixPath(normalized).parts name = parts[-1] if parts else "" if ( name.startswith("test_") diff --git a/tests/unit/test_assessors_code_quality.py b/tests/unit/test_assessors_code_quality.py index 6171ef84..e022ae15 100644 --- a/tests/unit/test_assessors_code_quality.py +++ b/tests/unit/test_assessors_code_quality.py @@ -470,6 +470,8 @@ class TestIsTestFile: "foo_test.py", "conftest.py", "tests/conftest.py", + "tests\\test_foo.py", + "test\\test_bar.py", ], ) def test_identifies_test_files(self, path): From dac2946dbfa71025f0f7cf1e96a4134f885e23bb Mon Sep 17 00:00:00 2001 From: Avi Avraham Date: Thu, 25 Jun 2026 15:28:43 +0300 Subject: [PATCH 3/3] fix: correct import sorting for CI compatibility Co-Authored-By: Claude Opus 4.6 --- tests/unit/test_assessors_code_quality.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_assessors_code_quality.py b/tests/unit/test_assessors_code_quality.py index e022ae15..eba0f9df 100644 --- a/tests/unit/test_assessors_code_quality.py +++ b/tests/unit/test_assessors_code_quality.py @@ -5,6 +5,7 @@ from unittest.mock import patch import pytest + from agentready.assessors.code_quality import ( CyclomaticComplexityAssessor, TypeAnnotationsAssessor,