-
Notifications
You must be signed in to change notification settings - Fork 0
Harden cross command construction #258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
bfde4d8
097e542
6e58d24
20f672b
2f947ae
6c87ca3
8373efc
3fc35ef
b6019e8
adcdcd8
3ac29df
b514cf2
8974705
42dc87b
cfd0888
2561caa
af7c002
07a4264
f8e4d6e
e659b74
bb7f5e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| """Regression tests for rust-build-release command construction.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import importlib.util | ||
| import os | ||
| import typing as typ | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from plumbum.commands.processes import ProcessExecutionError | ||
| from rust_build_release_test_helpers import assert_no_toolchain_override | ||
|
|
||
| SRC_DIR = Path(__file__).resolve().parents[1] / "src" | ||
| REPO_ROOT = Path(__file__).resolve().parents[4] | ||
|
|
||
| if typ.TYPE_CHECKING: | ||
| from types import ModuleType | ||
|
|
||
|
|
||
| def _make_cross_executable(tmp_path: Path) -> Path: | ||
| cross_path = tmp_path / "cross" | ||
| cross_path.write_text("#!/bin/sh\n", encoding="utf-8") | ||
| cross_path.chmod(cross_path.stat().st_mode | os.X_OK) | ||
| return cross_path | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def _load_main_module(monkeypatch: pytest.MonkeyPatch) -> ModuleType: | ||
| monkeypatch.setenv("GITHUB_ACTION_PATH", str(REPO_ROOT)) | ||
| monkeypatch.syspath_prepend(str(SRC_DIR)) | ||
|
|
||
| import packaging | ||
| import packaging.version as packaging_version | ||
|
|
||
| spec = importlib.util.spec_from_file_location( | ||
| "rbr_main_commands", SRC_DIR / "main.py" | ||
| ) | ||
| if spec is None or spec.loader is None: | ||
| msg = f"failed to load main.py from {SRC_DIR}" | ||
| raise RuntimeError(msg) | ||
|
|
||
| module = importlib.util.module_from_spec(spec) | ||
| try: | ||
| spec.loader.exec_module(module) | ||
| finally: | ||
| if getattr(packaging, "version", None) is packaging_version: | ||
| delattr(packaging, "version") | ||
| return module | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def test_build_cross_command_never_injects_toolchain_override( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| tmp_path: Path, | ||
| ) -> None: | ||
| """Cross commands start with cross build and omit +toolchain arguments.""" | ||
| main_module = _load_main_module(monkeypatch) | ||
| cross_path = _make_cross_executable(tmp_path) | ||
| decision = main_module._CrossDecision( | ||
| cross_path=str(cross_path), | ||
| cross_version="0.2.5", | ||
|
leynos marked this conversation as resolved.
|
||
| use_cross=True, | ||
| cargo_toolchain_spec="+bogus-nightly", | ||
| use_cross_local_backend=False, | ||
| docker_present=True, | ||
| podman_present=False, | ||
| has_container=True, | ||
| container_engine="docker", | ||
| requires_cross_container=False, | ||
| ) | ||
|
|
||
| cmd = main_module._build_cross_command( | ||
| decision, | ||
| "aarch64-unknown-linux-gnu", | ||
| tmp_path / "Cargo.toml", | ||
| "", | ||
| ) | ||
|
|
||
| parts = list(cmd.formulate()) | ||
| assert parts[0] == "cross" | ||
| assert_no_toolchain_override(parts) | ||
|
|
||
|
|
||
| def test_cross_container_fallback_keeps_cargo_toolchain_override( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| tmp_path: Path, | ||
| ) -> None: | ||
| """The container-error fallback remains cargo-only and keeps +toolchain.""" | ||
| main_module = _load_main_module(monkeypatch) | ||
| calls: list[list[str]] = [] | ||
|
|
||
| def fake_run_cmd(cmd: object) -> None: | ||
| formulated = list(cmd.formulate()) | ||
|
leynos marked this conversation as resolved.
Outdated
|
||
| if formulated: | ||
| formulated[0] = Path(formulated[0]).name | ||
| calls.append(formulated) | ||
|
|
||
| monkeypatch.setattr(main_module, "run_cmd", fake_run_cmd) | ||
| decision = main_module._CrossDecision( | ||
| cross_path="/usr/bin/cross", | ||
| cross_version="0.2.5", | ||
| use_cross=True, | ||
| cargo_toolchain_spec="+bogus-nightly", | ||
| use_cross_local_backend=False, | ||
| docker_present=True, | ||
| podman_present=False, | ||
| has_container=True, | ||
| container_engine="docker", | ||
| requires_cross_container=False, | ||
| ) | ||
| exc = ProcessExecutionError(["cross", "build"], 125, "", "") | ||
|
|
||
| main_module._handle_cross_container_error( | ||
| exc, | ||
| decision, | ||
| "aarch64-unknown-linux-gnu", | ||
| tmp_path / "Cargo.toml", | ||
| "", | ||
| ) | ||
|
|
||
| assert calls == [ | ||
| [ | ||
| "cargo", | ||
| "+bogus-nightly", | ||
| "build", | ||
| "--manifest-path", | ||
| str(tmp_path / "Cargo.toml"), | ||
| "--release", | ||
| "--target", | ||
| "aarch64-unknown-linux-gnu", | ||
| ] | ||
| ] | ||
|
|
||
|
|
||
| def test_cross_command_guard_rejects_toolchain_override( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| """The cross argv guard catches accidental +toolchain injection.""" | ||
| main_module = _load_main_module(monkeypatch) | ||
| with pytest.raises( | ||
| ValueError, | ||
| match=r"cross command must not include a \+<toolchain> override", | ||
| ): | ||
| main_module._assert_cross_command_has_no_toolchain_override( | ||
| ["cross", "+bogus-nightly", "build"] | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,10 @@ clean: ## Remove transient artefacts | |
| BUILD_JOBS ?= | ||
| MDLINT ?= markdownlint | ||
| NIXIE ?= nixie | ||
| ACTION_VALIDATOR_CANDIDATES := $(wildcard $(HOME)/.cargo/bin/action-validator /usr/local/bin/action-validator /usr/bin/action-validator) | ||
| ACTION_VALIDATOR ?= $(if $(ACTION_VALIDATOR_CANDIDATES),$(firstword $(ACTION_VALIDATOR_CANDIDATES)),action-validator) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| RUFF_CANDIDATES := $(wildcard $(CURDIR)/.venv/bin/ruff $(HOME)/.local/bin/ruff /usr/local/bin/ruff /usr/bin/ruff) | ||
| RUFF ?= $(if $(RUFF_CANDIDATES),$(firstword $(RUFF_CANDIDATES)),uv tool run ruff) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: When using uv tool run (or its alias uvx) without specifying a version (i.e., unpinned tool name like ruff), uv resolves and installs the latest compatible version of the tool package and its dependencies into a temporary, cached virtual environment isolated from any project. By default, uv's resolution strategy prefers the highest (latest) compatible version for each package, both direct (the tool) and transitive (its dependencies). This is the standard behavior across uv commands like uv pip install, uv run --with, and uv tool run. For uv tool run specifically: - On first run, it performs full resolution to select the latest versions satisfying the tool's declared requirements. - Subsequent runs reuse the cached environment unless the cache is pruned/refreshed, a different version is requested (e.g., ruff@latest or --from ruff==0.3.0), or --isolated is used. - The cache is disposable (in ~/.cache/uv), unlike installed tools via uv tool install which are persistent. To override the default "highest" resolution (e.g., for testing lowest versions), use the --resolution flag, which applies to uv tool run as it inherits from uv's run interface: - --resolution lowest: Lowest compatible versions for all dependencies. - --resolution lowest-direct: Lowest for direct (tool), highest for transitive. Example: uv tool run --resolution lowest-direct ruff check . Official documentation confirms uv resolves unpinned requirements to latest versions by default, with configurable strategies via --resolution. For reproducible exact versions, specify the tool version explicitly (e.g., uvx ruff@0.3.0) or use uv tool install first. No dedicated lockfile for tools; caching provides reproducibility until refreshed. Citations:
Pin the Ruff fallback version. The unpinned Apply a deterministic Ruff fallback+RUFF_VERSION ?= 0.11.13
-RUFF ?= $(if $(RUFF_CANDIDATES),$(firstword $(RUFF_CANDIDATES)),uv tool run ruff)
+RUFF ?= $(if $(RUFF_CANDIDATES),$(firstword $(RUFF_CANDIDATES)),uv tool run --from ruff==$(RUFF_VERSION) ruff)🤖 Prompt for AI Agents |
||
| RUFF_FIX_RULES ?= D202,I001 | ||
|
|
||
| test: .venv ## Run tests | ||
|
|
@@ -30,9 +34,9 @@ endif | |
| uv sync --group dev | ||
|
|
||
| lint: ## Check test scripts and actions | ||
| uvx ruff check | ||
| $(RUFF) check | ||
| find .github/actions -type f \( -name 'action.yml' -o -name 'action.yaml' \) -print0 \ | ||
| | xargs -r -0 -n1 action-validator | ||
| | xargs -r -0 -n1 $(ACTION_VALIDATOR) | ||
|
|
||
| typecheck: .venv ## Run static type checking with Ty | ||
| ./.venv/bin/ty check \ | ||
|
|
@@ -58,12 +62,12 @@ typecheck: .venv ## Run static type checking with Ty | |
| --extra-search-path .github/actions/macos-package/scripts \ | ||
| .github/actions/macos-package/scripts | ||
| fmt: ## Format Python files and auto-fix selected lint rules | ||
| uvx ruff format | ||
| uvx ruff check --select $(RUFF_FIX_RULES) --fix | ||
| $(RUFF) format | ||
| $(RUFF) check --select $(RUFF_FIX_RULES) --fix | ||
|
|
||
| check-fmt: ## Check Python formatting without modifying files | ||
| uvx ruff format --check | ||
| uvx ruff check --select $(RUFF_FIX_RULES) | ||
| $(RUFF) format --check | ||
| $(RUFF) check --select $(RUFF_FIX_RULES) | ||
|
|
||
| markdownlint: ## Lint Markdown files | ||
| find . -type f -name '*.md' -not -path './target/*' -print0 | xargs -0 -- $(MDLINT) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.