From cc6f2cde2d304ca0f1064f8c0805e3e4d914f27b Mon Sep 17 00:00:00 2001 From: omsherikar Date: Sun, 5 Apr 2026 17:39:45 +0530 Subject: [PATCH 1/3] test: add ArxMain edge-case tests Cover default output path, single- and multi-file AST, codegen guard, invalid link_mode, main detection, and show_ast fallbacks. --- tests/test_app_paths.py | 223 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/tests/test_app_paths.py b/tests/test_app_paths.py index 51197d6..32b90e5 100644 --- a/tests/test_app_paths.py +++ b/tests/test_app_paths.py @@ -22,6 +22,8 @@ from arx.docstrings import validate_docstring from arx.exceptions import CodeGenException, ParserException from arx.io import ArxBuffer, ArxFile, ArxIO +from arx.lexer import Lexer +from arx.parser import Parser def test_builtins_helpers() -> None: @@ -389,6 +391,227 @@ def test_main_module_name_helper() -> None: ) +def test_arxmain_resolve_output_file_empty_inputs() -> None: + """ + title: Default output base is a.out when no inputs and no output set. + """ + app = main_module.ArxMain(input_files=[], output_file="") + assert app._resolve_output_file() == "a.out" + + +def test_arxmain_get_astx_single_file_returns_module(tmp_path: Path) -> None: + """ + title: Single input file yields an astx.Module from _get_astx. + parameters: + tmp_path: + type: Path + """ + src = tmp_path / "one.x" + src.write_text( + "```\ntitle: One\n```\nfn main() -> i32:\n return 0\n", + encoding="utf-8", + ) + app = main_module.ArxMain(input_files=[str(src)]) + tree = app._get_astx() + assert isinstance(tree, astx.Module) + assert tree.name == "one" + + +def test_arxmain_get_codegen_rejects_multi_module_block() -> None: + """ + title: Codegen entry rejects a Block with multiple modules. + """ + app = main_module.ArxMain() + bundle = astx.Block() + bundle.nodes.extend([astx.Module("a"), astx.Module("b")]) + + def fake_get_astx() -> astx.Block: + """ + title: Return a multi-module block. + returns: + type: astx.Block + """ + return bundle + + app._get_astx = fake_get_astx # type: ignore[method-assign] + + with pytest.raises(ValueError, match="multiple input files"): + app._get_codegen_astx() + + +def test_arxmain_run_invalid_link_mode() -> None: + """ + title: run() rejects unknown link_mode values. + """ + app = main_module.ArxMain() + with pytest.raises(ValueError, match="Invalid link mode"): + app.run(input_files=[], link_mode="bogus") + + +def test_arxmain_has_main_entry_block_of_modules() -> None: + """ + title: _has_main_entry finds main inside a Block of Module nodes. + """ + ArxIO.string_to_buffer( + "```\ntitle: M\n```\nfn main() -> i32:\n return 0\n" + ) + lexer = Lexer() + parser = Parser() + mod = parser.parse(lexer.lex(), "boxed") + + app = main_module.ArxMain() + assert app._has_main_entry(mod) is True + + block = astx.Block() + block.nodes.append(mod) + assert app._has_main_entry(block) is True + + empty = astx.Block() + empty.nodes.append(astx.Module("empty")) + assert app._has_main_entry(empty) is False + + +def test_arxmain_format_ast_fallback_walks_simple_values() -> None: + """ + title: Fallback AST formatter handles non-AST roots and simple fields. + """ + app = main_module.ArxMain() + assert "42" in app._format_ast_fallback(42) + + +def test_arxmain_format_ast_fallback_cycle_and_exotic_fields() -> None: + """ + title: Fallback walker reports cycles and unknown field value types. + """ + ArxIO.string_to_buffer( + "```\ntitle: M\n```\nfn main() -> i32:\n return 0\n" + ) + lexer = Lexer() + parser = Parser() + mod = parser.parse(lexer.lex(), "walk") + + outer = astx.Block() + inner = astx.Block() + outer.nodes.append(inner) + inner.nodes.append(outer) + + app = main_module.ArxMain() + out = app._format_ast_fallback(outer) + assert "cycle" in out + + setattr(mod, "meta", object()) + meta_out = app._format_ast_fallback(mod) + assert "meta" in meta_out + assert "object" in meta_out + + +def test_arxmain_show_ast_prefers_to_json_when_repr_fails( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """ + title: show_ast prints to_json when repr fails but to_json works. + parameters: + monkeypatch: + type: pytest.MonkeyPatch + capsys: + type: pytest.CaptureFixture[str] + """ + app = main_module.ArxMain(input_files=["irrelevant.x"]) + + class JsonTree(astx.Module): + """ + title: Module-like node with failing repr and working to_json. + """ + + def __init__(self) -> None: + """ + title: Initialize the synthetic tree root. + """ + super().__init__("j") + + def __repr__(self) -> str: + """ + title: Force repr failure. + returns: + type: str + """ + raise RuntimeError("repr unavailable") + + def to_json(self) -> str: + """ + title: Return deterministic JSON text. + returns: + type: str + """ + return '{"via": "json"}' + + def fake_get_astx_json() -> JsonTree: + """ + title: Supply JsonTree for show_ast coverage. + returns: + type: JsonTree + """ + return JsonTree() + + monkeypatch.setattr(app, "_get_astx", fake_get_astx_json) + app.show_ast() + assert '{"via": "json"}' in capsys.readouterr().out + + +def test_arxmain_show_ast_fallback_tree_formatter_when_repr_fails( + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] +) -> None: + """ + title: show_ast uses _format_ast_fallback when repr and to_json fail. + parameters: + monkeypatch: + type: pytest.MonkeyPatch + capsys: + type: pytest.CaptureFixture[str] + """ + app = main_module.ArxMain(input_files=["irrelevant.x"]) + + class BadReprModule(astx.Module): + """ + title: Real AST node with broken repr and failing to_json. + """ + + def __init__(self) -> None: + """ + title: Initialize the synthetic module node. + """ + super().__init__("badrepr") + + def __repr__(self) -> str: + """ + title: Force repr failure for AST nodes. + returns: + type: str + """ + raise RuntimeError("repr broken") + + def to_json(self) -> str: + """ + title: Force the JSON path to fail after repr fails. + returns: + type: str + """ + raise RuntimeError("json also broken") + + def fake_get_astx_badrepr() -> BadReprModule: + """ + title: Supply BadReprModule for fallback formatter coverage. + returns: + type: BadReprModule + """ + return BadReprModule() + + monkeypatch.setattr(app, "_get_astx", fake_get_astx_badrepr) + app.show_ast() + out = capsys.readouterr().out + assert "Module" in out or "badrepr" in out + + def test_arxmain_get_astx_uses_all_inputs( monkeypatch: pytest.MonkeyPatch, ) -> None: From 1108217993cb240d7536db6a2f9efc32eeecbec3 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Sun, 5 Apr 2026 17:51:01 +0530 Subject: [PATCH 2/3] fix: match astx to_json in ArxMain tests Satisfy mypy override checks for test AST subclasses; drop unused type ignore. --- tests/test_app_paths.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_app_paths.py b/tests/test_app_paths.py index 32b90e5..bc7ba87 100644 --- a/tests/test_app_paths.py +++ b/tests/test_app_paths.py @@ -433,7 +433,7 @@ def fake_get_astx() -> astx.Block: """ return bundle - app._get_astx = fake_get_astx # type: ignore[method-assign] + app._get_astx = fake_get_astx with pytest.raises(ValueError, match="multiple input files"): app._get_codegen_astx() @@ -537,12 +537,16 @@ def __repr__(self) -> str: """ raise RuntimeError("repr unavailable") - def to_json(self) -> str: + def to_json(self, simplified: bool = False) -> str: """ title: Return deterministic JSON text. + parameters: + simplified: + type: bool returns: type: str """ + del simplified return '{"via": "json"}' def fake_get_astx_json() -> JsonTree: @@ -590,12 +594,16 @@ def __repr__(self) -> str: """ raise RuntimeError("repr broken") - def to_json(self) -> str: + def to_json(self, simplified: bool = False) -> str: """ title: Force the JSON path to fail after repr fails. + parameters: + simplified: + type: bool returns: type: str """ + del simplified raise RuntimeError("json also broken") def fake_get_astx_badrepr() -> BadReprModule: From 7a615a0ae7df65572438b7594455b05a08d21244 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Sun, 5 Apr 2026 17:55:34 +0530 Subject: [PATCH 3/3] fix: avoid mypy method-assign in multi-module test Use setattr to stub _get_astx for CI mypy strict. --- tests/test_app_paths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_app_paths.py b/tests/test_app_paths.py index bc7ba87..287aac6 100644 --- a/tests/test_app_paths.py +++ b/tests/test_app_paths.py @@ -425,15 +425,15 @@ def test_arxmain_get_codegen_rejects_multi_module_block() -> None: bundle = astx.Block() bundle.nodes.extend([astx.Module("a"), astx.Module("b")]) - def fake_get_astx() -> astx.Block: + def fake_get_astx() -> astx.AST: """ title: Return a multi-module block. returns: - type: astx.Block + type: astx.AST """ return bundle - app._get_astx = fake_get_astx + setattr(app, "_get_astx", fake_get_astx) with pytest.raises(ValueError, match="multiple input files"): app._get_codegen_astx()