Skip to content
Open
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
109 changes: 109 additions & 0 deletions tests/test_codegen_file_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import shutil

from pathlib import Path
from typing import Any, Literal, cast

import astx
import pytest

from arx.codegen import ArxBuilder
Expand All @@ -17,6 +19,23 @@
TMP_PATH.mkdir(exist_ok=True)
HAS_CLANG = shutil.which("clang") is not None

_MIN_FLOAT_MAIN = "fn main() -> f32:\n return 1.0 + 1.0\n"


def _parse_min_module(code: str = _MIN_FLOAT_MAIN) -> astx.Module:
"""
title: Parse a minimal one-line main module for codegen tests.
parameters:
code:
type: str
returns:
type: astx.Module
"""
ArxIO.string_to_buffer(code)
tree = Parser().parse(Lexer().lex())
assert isinstance(tree, astx.Module)
return tree


@pytest.mark.parametrize(
"code",
Expand Down Expand Up @@ -44,3 +63,93 @@ def test_object_generation(code: str) -> None:
bin_path = TMP_PATH / "testtmp"
ir.build(module_ast, str(bin_path))
bin_path.unlink()


def test_build_without_link_writes_object_bytes(
tmp_path: Path,
) -> None:
"""
title: link=False writes the LLVM object to output_file and skips clang.
parameters:
tmp_path:
type: Path
"""
module_ast = _parse_min_module()
out = tmp_path / "only.o"
ArxBuilder().build(module_ast, str(out), link=False)
assert out.is_file()
assert out.stat().st_size > 0


def test_build_rejects_unknown_link_mode(tmp_path: Path) -> None:
"""
title: Unknown link_mode raises before invoking the linker.
parameters:
tmp_path:
type: Path
"""
module_ast = _parse_min_module()
invalid: Any = "not-a-link-mode"
with pytest.raises(ValueError, match="Invalid link mode"):
ArxBuilder().build(
module_ast,
str(tmp_path / "never"),
link=True,
link_mode=invalid,
)


@pytest.mark.parametrize(
("mode", "expect_flag"),
[
("pie", "-pie"),
("no-pie", "-no-pie"),
],
)
@pytest.mark.skipif(not HAS_CLANG, reason="clang is required for object build")
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_build_passes_explicit_link_flags monkeypatches arx.codegen.xh.clang, so it no longer needs a real clang binary on PATH. The current @skipif(not HAS_CLANG, ...) will unnecessarily skip this coverage in environments without clang and the skip reason is misleading; consider removing the skip or changing the condition/reason to reflect the actual prerequisite (e.g., only skip when LLVM object emission isn’t available).

Suggested change
@pytest.mark.skipif(not HAS_CLANG, reason="clang is required for object build")

Copilot uses AI. Check for mistakes.
def test_build_passes_explicit_link_flags(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
mode: str,
expect_flag: str,
) -> None:
"""
title: pie and no-pie link modes forward the matching flag to clang.
parameters:
tmp_path:
type: Path
monkeypatch:
type: pytest.MonkeyPatch
mode:
type: str
expect_flag:
type: str
"""
module_ast = _parse_min_module()
exe = tmp_path / f"out-{mode}"
recorded: dict[str, object] = {}

def fake_clang(*args: object, **_kwargs: object) -> None:
"""
title: Record clang argv and create the declared output artifact.
parameters:
args:
type: object
variadic: positional
_kwargs:
type: object
variadic: keyword
"""
recorded["args"] = args
Path(str(args[-1])).write_bytes(b"")

monkeypatch.setattr("arx.codegen.xh.clang", fake_clang)

link_mode = cast(Literal["auto", "pie", "no-pie"], mode)
ArxBuilder().build(module_ast, str(exe), link=True, link_mode=link_mode)

args_tuple = recorded["args"]
assert isinstance(args_tuple, tuple)
assert expect_flag in args_tuple
assert args_tuple[-1] == str(exe)
assert exe.is_file()
Loading