headroom/tests/test_package_init_lazy.py
Parideboy 3ccdad6c67
Pin ORT dylib on Windows; init Python logging (#1010)
## Description

On Windows, headroom's Rust core resolves `onnxruntime.dll` at runtime
via `ort-load-dynamic`. Without an explicit `ORT_DYLIB_PATH`, the bare
DLL search can land on `C:\Windows\System32\onnxruntime.dll`, the
Windows ML OS component, and `Session::new()` can deadlock instead of
returning an error. Since a hang is not an `Err`, the tiered fallback
cannot engage until the proxy-level timeout fires.

This PR pins `ORT_DYLIB_PATH` to the pip-installed `onnxruntime` DLL at
import time, and wires Rust `tracing` events into Python logging so the
proxy log surfaces these failures when they occur.

Closes #928

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Added `headroom/_ort.py` with a Windows-only, idempotent
`ensure_ort_dylib_pinned()` resolver that respects an existing
`ORT_DYLIB_PATH`.
- Call the pin from `headroom/__init__.py` before importing `_core`
consumers.
- Log the effective ORT dylib path from the content router startup path
on Windows.
- Enable Rust tracing-to-log compatibility and initialize `pyo3-log` in
the `_core` module.
- Add timeout diagnostics in the Magika detector with the effective
`ORT_DYLIB_PATH`.
- Document `ORT_DYLIB_PATH` and `HEADROOM_MAGIKA_INIT_TIMEOUT_SECS`.
- Add unit coverage for the resolver behavior.

## Testing

- [x] Unit tests pass (`python -m pytest
tests/test_transforms/test_ort_dylib.py -q`)
- [x] Linting passes (`ruff check headroom/_ort.py headroom/__init__.py
headroom/transforms/content_router.py
tests/test_transforms/test_ort_dylib.py`)
- [x] Formatting passes (`ruff format --check headroom/_ort.py
headroom/__init__.py headroom/transforms/content_router.py
tests/test_transforms/test_ort_dylib.py`)
- [x] New tests added for new functionality
- [x] Manual testing performed

### Test Output

```text
$ python -m pytest tests/test_transforms/test_ort_dylib.py -q
7 passed in 0.19s

$ ruff check headroom/_ort.py headroom/__init__.py headroom/transforms/content_router.py tests/test_transforms/test_ort_dylib.py
All checks passed!

$ ruff format --check headroom/_ort.py headroom/__init__.py headroom/transforms/content_router.py tests/test_transforms/test_ort_dylib.py
4 files already formatted

$ cargo check -p headroom-py
cargo: The term 'cargo' is not recognized as a name of a cmdlet, function, script file, or executable program.
```

## Real Behavior Proof

- Environment: Windows 11 24H2, Python 3.13, RTX 4080
- Exact command / steps: `python -c "import headroom; from
headroom._core import detect_content_type as d;
print(d(open('headroom/compress.py').read()).content_type)"`
- Observed result: `source_code` in 301ms, clean exit, `Magika: ENABLED`
in proxy log
- Not tested: macOS/Linux manual runtime behavior; `_ort.py` is a no-op
outside Windows, and CI covers cross-platform build/test behavior.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable (N/A: repo uses
release-please)

## Additional Notes

The branch was rebased onto current `main` and the commit subject was
updated to satisfy commitlint. Local Rust verification could not be run
on this Windows machine because `cargo` is not installed; GitHub CI
should be treated as the Rust build verification for the `pyo3-log`
dependency and workspace lockfile changes.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 07:46:24 -05:00

215 lines
6.5 KiB
Python

"""Regression tests for lightweight package bootstrap."""
from __future__ import annotations
import json
import os
import subprocess
import sys
import textwrap
from importlib.metadata import PackageNotFoundError
from pathlib import Path
from unittest.mock import patch
import headroom._version as version_module
def test_headroom_import_stays_lazy() -> None:
script = textwrap.dedent(
"""
import json
import sys
import headroom
print(json.dumps({
"version": headroom.__version__,
"cache_loaded": "headroom.cache" in sys.modules,
"models_registry_loaded": "headroom.models.registry" in sys.modules,
"memory_loaded": "headroom.memory" in sys.modules,
}))
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
check=True,
)
data = json.loads(result.stdout.strip())
# Version is a non-empty string; don't hardcode a specific value.
assert isinstance(data["version"], str) and data["version"]
assert data["cache_loaded"] is False
assert data["models_registry_loaded"] is False
assert data["memory_loaded"] is False
def test_version_prefers_installed_distribution_metadata() -> None:
with (
patch.object(version_module, "_source_root", return_value=None),
patch.object(version_module, "version", return_value="9.8.7") as package_version,
):
assert version_module.get_version() == "9.8.7"
package_version.assert_called_once_with("headroom-ai")
def test_version_reports_unknown_when_distribution_metadata_is_missing() -> None:
with (
patch.object(version_module, "_source_root", return_value=None),
patch.object(version_module, "version", side_effect=PackageNotFoundError),
):
assert version_module.get_version() == version_module.UNKNOWN_VERSION
def test_version_prefers_source_tree_release_history() -> None:
with (
patch.object(version_module, "_source_root", return_value=Path(".")),
patch.object(version_module, "_source_tree_version", return_value="0.21.17"),
patch.object(version_module, "version", return_value="0.9.1") as package_version,
):
assert version_module.get_version() == "0.21.17"
package_version.assert_not_called()
def test_proxy_package_import_does_not_eagerly_load_server() -> None:
script = textwrap.dedent(
"""
import json
import sys
import headroom.proxy
print(json.dumps({
"server_loaded": "headroom.proxy.server" in sys.modules,
}))
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
check=True,
)
data = json.loads(result.stdout.strip())
assert data["server_loaded"] is False
def test_proxy_server_import_skips_litellm_backend() -> None:
script = textwrap.dedent(
"""
import json
import sys
import headroom.proxy.server
print(json.dumps({
"litellm_backend_loaded": "headroom.backends.litellm" in sys.modules,
"anyllm_backend_loaded": "headroom.backends.anyllm" in sys.modules,
"litellm_loaded": "litellm" in sys.modules,
}))
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
check=True,
)
data = json.loads(result.stdout.strip())
assert data["litellm_backend_loaded"] is False
assert data["anyllm_backend_loaded"] is False
assert data["litellm_loaded"] is False
def test_dynamic_detector_import_skips_optional_ml_dependencies(tmp_path: Path) -> None:
(tmp_path / "spacy.py").write_text("", encoding="utf-8")
(tmp_path / "numpy.py").write_text("", encoding="utf-8")
(tmp_path / "torch.py").write_text("", encoding="utf-8")
sentence_transformers_dir = tmp_path / "sentence_transformers"
sentence_transformers_dir.mkdir()
(sentence_transformers_dir / "__init__.py").write_text(
"import torch\n\nclass SentenceTransformer:\n pass\n",
encoding="utf-8",
)
script = textwrap.dedent(
"""
import json
import sys
import headroom.cache.dynamic_detector
print(json.dumps({
"spacy_loaded": "spacy" in sys.modules,
"sentence_transformers_loaded": "sentence_transformers" in sys.modules,
"torch_loaded": "torch" in sys.modules,
}))
"""
)
env = os.environ.copy()
env["PYTHONPATH"] = str(tmp_path)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
check=True,
cwd=Path(__file__).resolve().parents[1],
env=env,
)
data = json.loads(result.stdout.strip())
assert data["spacy_loaded"] is False
assert data["sentence_transformers_loaded"] is False
assert data["torch_loaded"] is False
def test_compress_spreadsheet_public_import_survives_ort_pin() -> None:
"""`from headroom import compress_spreadsheet` stays eagerly exported, and the
Windows ORT dylib pin still runs before the `.compress` import.
The pin (`ensure_ort_dylib_pinned`) was inserted above the eager `.compress`
import; restoring `compress_spreadsheet` to that line must not reorder it
relative to the pin. The `__dict__` check distinguishes the eager import from
the lazy `_LAZY_EXPORTS` fallback, which would also resolve the name.
"""
script = textwrap.dedent(
"""
import json
import headroom
from headroom import compress_spreadsheet
print(json.dumps({
"eager": "compress_spreadsheet" in headroom.__dict__,
"callable": callable(compress_spreadsheet),
}))
"""
)
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
check=True,
)
data = json.loads(result.stdout.strip())
assert data["eager"] is True
assert data["callable"] is True
# ORT pin must precede the `.compress` import, which must still list the helper.
src = (Path(version_module.__file__).parent / "__init__.py").read_text(encoding="utf-8")
pin = src.index("ensure_ort_dylib_pinned()")
compress_import = src.index("from .compress import")
assert pin < compress_import
assert "compress_spreadsheet" in src[compress_import : compress_import + 120]