Skip to content

Commit 0a035de

Browse files
authored
Merge pull request #19 from axiomantic/elijahr/subprocess-plugin
Migrate subprocess mocks to bigfoot and address Gemini feedback
2 parents d2c826f + 34f044b commit 0a035de

11 files changed

Lines changed: 816 additions & 666 deletions

File tree

.github/workflows/test-install-libclang.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- uses: actions/checkout@v6
2323
- uses: actions/setup-python@v6
2424
with:
25-
python-version: "3.10"
25+
python-version: "3.11"
2626
cache: 'pip'
2727

2828
- name: Install package

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
13+
python-version: ["3.11", "3.12", "3.13", "3.14"]
1414
os: [ubuntu-latest, macos-latest, windows-latest]
1515
steps:
1616
- uses: actions/checkout@v6

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Changed
11+
12+
- Test suite now uses [bigfoot](https://github.com/axiomantic/bigfoot) for subprocess interception. `subprocess.run` and `shutil.which` mocks in `test_install_libclang.py`, `test_version_detect.py`, `test_libclang.py`, and `test_windows_detection.py` are replaced with `bigfoot.subprocess_mock`, which enforces strict FIFO ordering and fails fast on unexpected calls.
13+
- Integration test writer assertions extracted into shared helpers (`_check_ctypes_write`, `_check_cython_write`, etc.) in `test_real_headers.py`, eliminating repeated assertion logic across the five library test classes.
14+
815
## [0.8.1] - 2026-03-04
916

1017
### Removed

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ classifiers = [
1919
"Topic :: Software Development :: Code Generators",
2020
"Topic :: Software Development :: Compilers",
2121
"Programming Language :: Python :: 3 :: Only",
22-
"Programming Language :: Python :: 3.10",
2322
"Programming Language :: Python :: 3.11",
2423
"Programming Language :: Python :: 3.12",
2524
"Programming Language :: Python :: 3.13",
@@ -30,7 +29,7 @@ classifiers = [
3029
"License :: OSI Approved :: Apache Software License",
3130
"Typing :: Typed",
3231
]
33-
requires-python = ">=3.10"
32+
requires-python = ">=3.11"
3433
dependencies = []
3534

3635
[project.urls]
@@ -43,15 +42,16 @@ Changelog = "https://github.com/axiomantic/headerkit/blob/main/CHANGELOG.md"
4342
headerkit = "headerkit._cli:main"
4443

4544
[project.optional-dependencies]
46-
toml = ["tomli>=1.1.0; python_version < '3.11'"]
47-
test = ["pytest", "pytest-timeout"]
45+
toml = []
46+
test = ["pytest", "pytest-timeout", "bigfoot>=0.3.0"]
4847
lint = ["ruff", "mypy"]
4948
docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.27", "mike>=2.1"]
5049
dev = ["headerkit[test,lint,docs,toml]"]
5150

5251
[tool.hatch.build.targets.wheel]
5352
packages = ["headerkit"]
5453

54+
5555
[tool.ruff]
5656
line-length = 120
5757
target-version = "py310"

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# bigfoot.pytest_plugin is registered automatically via the pytest11 entry point.

tests/test_backends/test_libclang.py

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import os
66
import shutil
77
import subprocess
8-
from unittest.mock import MagicMock, patch
8+
from unittest.mock import patch
99

10+
import bigfoot
1011
import pytest
1112

1213
from headerkit.backends.libclang import (
@@ -728,59 +729,84 @@ def test_c_and_cxx_cached_separately(self):
728729

729730
def test_clang_not_found_returns_empty(self):
730731
"""When clang is not on PATH, returns empty list."""
732+
import sys
733+
731734
import headerkit.backends.libclang as mod
732735

736+
null_file = "NUL" if sys.platform == "win32" else "/dev/null"
733737
mod._system_include_cache_c = None
734-
with patch("headerkit.backends.libclang.subprocess.run", side_effect=FileNotFoundError):
738+
bigfoot.subprocess_mock.mock_run(
739+
["clang", "-v", "-x", "c", "-E", null_file],
740+
raises=FileNotFoundError(),
741+
)
742+
with bigfoot.sandbox():
735743
result = get_system_include_dirs()
736-
assert result == []
744+
assert result == []
745+
bigfoot.assert_interaction(bigfoot.subprocess_mock.run, command=["clang", "-v", "-x", "c", "-E", null_file])
737746

738747
def test_clang_timeout_returns_empty(self):
739748
"""When clang times out, returns empty list."""
749+
import sys
750+
740751
import headerkit.backends.libclang as mod
741752

753+
null_file = "NUL" if sys.platform == "win32" else "/dev/null"
742754
mod._system_include_cache_c = None
743-
with patch(
744-
"headerkit.backends.libclang.subprocess.run",
745-
side_effect=subprocess.TimeoutExpired(cmd="clang", timeout=10),
746-
):
755+
bigfoot.subprocess_mock.mock_run(
756+
["clang", "-v", "-x", "c", "-E", null_file],
757+
raises=subprocess.TimeoutExpired(cmd="clang", timeout=10),
758+
)
759+
with bigfoot.sandbox():
747760
result = get_system_include_dirs()
748-
assert result == []
761+
assert result == []
762+
bigfoot.assert_interaction(bigfoot.subprocess_mock.run, command=["clang", "-v", "-x", "c", "-E", null_file])
749763

750764
def test_parses_include_search_paths(self):
751765
"""Parses clang -v output to extract include search paths."""
766+
import sys
767+
752768
import headerkit.backends.libclang as mod
753769

770+
null_file = "NUL" if sys.platform == "win32" else "/dev/null"
754771
mod._system_include_cache_c = None
755-
mock_result = MagicMock()
756-
mock_result.stderr = (
757-
"clang version 18.0.0\n"
758-
"#include <...> search starts here:\n"
759-
" /usr/lib/clang/18/include\n"
760-
" /usr/include\n"
761-
"End of search list.\n"
772+
bigfoot.subprocess_mock.mock_run(
773+
["clang", "-v", "-x", "c", "-E", null_file],
774+
returncode=0,
775+
stderr=(
776+
"clang version 18.0.0\n"
777+
"#include <...> search starts here:\n"
778+
" /usr/lib/clang/18/include\n"
779+
" /usr/include\n"
780+
"End of search list.\n"
781+
),
762782
)
763-
with patch("headerkit.backends.libclang.subprocess.run", return_value=mock_result):
783+
with bigfoot.sandbox():
764784
result = get_system_include_dirs()
765-
assert "-isystem/usr/lib/clang/18/include" in result
766-
assert "-isystem/usr/include" in result
785+
assert result == ["-isystem/usr/lib/clang/18/include", "-isystem/usr/include"]
786+
bigfoot.assert_interaction(bigfoot.subprocess_mock.run, command=["clang", "-v", "-x", "c", "-E", null_file])
767787

768788
def test_skips_framework_directories(self):
769789
"""Framework directories are excluded from the result."""
790+
import sys
791+
770792
import headerkit.backends.libclang as mod
771793

794+
null_file = "NUL" if sys.platform == "win32" else "/dev/null"
772795
mod._system_include_cache_c = None
773-
mock_result = MagicMock()
774-
mock_result.stderr = (
775-
"#include <...> search starts here:\n"
776-
" /usr/include\n"
777-
" /System/Library/Frameworks (framework directory)\n"
778-
"End of search list.\n"
796+
bigfoot.subprocess_mock.mock_run(
797+
["clang", "-v", "-x", "c", "-E", null_file],
798+
returncode=0,
799+
stderr=(
800+
"#include <...> search starts here:\n"
801+
" /usr/include\n"
802+
" /System/Library/Frameworks (framework directory)\n"
803+
"End of search list.\n"
804+
),
779805
)
780-
with patch("headerkit.backends.libclang.subprocess.run", return_value=mock_result):
806+
with bigfoot.sandbox():
781807
result = get_system_include_dirs()
782-
assert "-isystem/usr/include" in result
783-
assert len(result) == 1 # framework dir excluded
808+
assert result == ["-isystem/usr/include"]
809+
bigfoot.assert_interaction(bigfoot.subprocess_mock.run, command=["clang", "-v", "-x", "c", "-E", null_file])
784810

785811

786812
@libclang

0 commit comments

Comments
 (0)