From a5507bd358e33341bb1fb8559e43561a22dda7f9 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Fri, 11 Jul 2025 10:50:27 -0400 Subject: [PATCH] Fix issues #8 and #9 Signed-off-by: Rahul Krishna --- codeanalyzer/__main__.py | 4 ++- .../symbol_table_builder.py | 5 ++- pyproject.toml | 36 +++++++++++++++++-- test/conftest.py | 13 ++++++- test/test_cli.py | 30 ++++++++-------- 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/codeanalyzer/__main__.py b/codeanalyzer/__main__.py index 2162bab..a747b2c 100644 --- a/codeanalyzer/__main__.py +++ b/codeanalyzer/__main__.py @@ -92,8 +92,10 @@ def _write_output(artifacts, output_dir: Path, format: OutputFormat): """Write artifacts to file in the specified format.""" if format == OutputFormat.JSON: output_file = output_dir / "analysis.json" + # Use Pydantic's json() with separators for compact output + json_str = artifacts.model_dump_json(indent=None) with output_file.open("w") as f: - f.write(artifacts.model_dump_json(separators=(",", ":"))) + f.write(json_str) logger.info(f"Analysis saved to {output_file}") elif format == OutputFormat.MSGPACK: diff --git a/codeanalyzer/syntactic_analysis/symbol_table_builder.py b/codeanalyzer/syntactic_analysis/symbol_table_builder.py index 6b0a3c3..a60a68e 100644 --- a/codeanalyzer/syntactic_analysis/symbol_table_builder.py +++ b/codeanalyzer/syntactic_analysis/symbol_table_builder.py @@ -5,7 +5,6 @@ from pathlib import Path from typing import Dict, List, Optional -import astor import jedi from jedi.api import Script from jedi.api.project import Project @@ -183,7 +182,7 @@ def _add_class( f"{script.path.__str__().replace('/', '.').replace('.py', '')}.{class_node.name}", ) - code: str = astor.to_source(class_node).strip() + code: str = ast.unparse(class_node).strip() py_class = ( PyClass.builder() @@ -243,7 +242,7 @@ def visit(n: AST, class_prefix: str = ""): child, "end_lineno", start_line + len(child.body) ) code_start_line = child.body[0].lineno if child.body else start_line - code = astor.to_source(child).strip() + code: str = ast.unparse(child).strip() decorators = [ast.unparse(d) for d in child.decorator_list] try: diff --git a/pyproject.toml b/pyproject.toml index 157d137..ea5edf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,6 @@ authors = [ requires-python = ">=3.12" dependencies = [ - "astor>=0.8.1", "jedi>=0.19.2", "loguru>=0.7.3", "msgpack>=1.1.1", @@ -54,4 +53,37 @@ include = [ ] [tool.pytest.ini_options] -testpaths = ["tests"] +addopts = [ + "-p", "coverage", + "--cov=codeanalyzer", + "--cov-report=html", + "--cov-report=term-missing", + "--cov-fail-under=40" +] +testpaths = ["test"] + +[tool.coverage.run] +source = ["codeanalyzer"] +branch = true +omit = [ + "*/tests/*", + "*/test_*", + "*/__pycache__/*", + "*/venv/*", + "*/.venv/*", + "codeanalyzer/semantic_analysis/*" +] + +[tool.coverage.report] +precision = 2 +show_missing = true +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == .__main__.:" +] + +[tool.coverage.html] +directory = "htmlcov" \ No newline at end of file diff --git a/test/conftest.py b/test/conftest.py index a73f376..db6a66b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -3,7 +3,18 @@ import pytest from typer.testing import CliRunner +import logging +from rich.console import Console +from rich.logging import RichHandler +from codeanalyzer.utils import logger +# Ensure the test logger emits DEBUG +console = Console() +handler = RichHandler(console=console, show_time=True, show_level=True, show_path=False) + +logger.setLevel(logging.DEBUG) +logger.addHandler(handler) +logger.propagate = False # Avoid duplicated logs @pytest.fixture def cli_runner() -> CliRunner: @@ -17,4 +28,4 @@ def cli_runner() -> CliRunner: @pytest.fixture def project_root() -> Path: """Returns the grandparent directory of this conftest file — typically the project root.""" - return Path(__file__).resolve().parents[2] + return Path(__file__).resolve().parents[1] diff --git a/test/test_cli.py b/test/test_cli.py index 038374d..278aae0 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -1,37 +1,39 @@ +import json +from pathlib import Path from codeanalyzer.__main__ import app from codeanalyzer.utils import logger def test_cli_help(cli_runner): """Must be able to run the CLI and see help output.""" - result = cli_runner.invoke(app, ["--help"]) + result = cli_runner.invoke(app, ["--help"], env={"NO_COLOR": "1", "TERM": "dumb"}) assert result.exit_code == 0 - assert "Usage: codeanalyzer [OPTIONS] COMMAND [ARGS]..." in result.output - -def test_cli_call_symbol_table(cli_runner, project_root): +def test_cli_call_symbol_table_with_json(cli_runner, project_root): """Must be able to run the CLI with symbol table analysis.""" - - output_dir = project_root / "src" / "test" / ".output" + output_dir = project_root.joinpath("test", ".output") output_dir.mkdir(parents=True, exist_ok=True) - result = cli_runner.invoke( app, [ "--input", str(project_root), "--output", - str(project_root / "src" / "test" / ".output"), + str(output_dir), "--analysis-level", "1", "--no-codeql", "--cache-dir", - str(project_root / "src" / "test" / ".cache"), + str(project_root.joinpath("test", ".cache")), "--keep-cache", - "-v", + "--format=json", ], + env={"NO_COLOR": "1", "TERM": "dumb"}, ) - logger.debug(f"CLI result: {result.output}") - # assert result.exit_code == 0 - # assert json.load(Path(output_dir) / ".output" / "analysis.json") is not None - # assert "symbol_table" in json.load(Path(output_dir) / ".output" / "analysis.json") + assert result.exit_code == 0, "CLI command should succeed" + assert Path(output_dir).joinpath("analysis.json").exists(), "Output JSON file should be created" + json_obj = json.loads(Path(output_dir).joinpath("analysis.json").read_text()) + assert json_obj is not None, "JSON output should not be None" + assert isinstance(json_obj, dict), "JSON output should be a dictionary" + assert "symbol_table" in json_obj.keys(), "Symbol table should be present in the output" + assert len(json_obj["symbol_table"]) > 0, "Symbol table should not be empty"