diff --git a/tests/python/test_legacy.py b/tests/python/test_legacy.py index 524f87f..64f27dc 100644 --- a/tests/python/test_legacy.py +++ b/tests/python/test_legacy.py @@ -153,8 +153,8 @@ def test_legacy_get_converted_data_returns_a_new_response_with_wrench_list( client.disconnect() -def test_legacy_streaming_uses_monotonic_time_and_legacy_printing( - fake_client_factory, native_sample, monkeypatch: pytest.MonkeyPatch, capsys +def test_legacy_streaming_uses_monotonic_time( + fake_client_factory, native_sample, monkeypatch: pytest.MonkeyPatch ) -> None: client, _ = _legacy_warnings() client.connect() @@ -164,8 +164,7 @@ def test_legacy_streaming_uses_monotonic_time_and_legacy_printing( monkeypatch.setattr(legacy_module.time, "monotonic", lambda: next(monotonic_values)) monkeypatch.setattr(legacy_module.time, "sleep", sleeps.append) - client.start_streaming(duration=1.0, delay=0.25, print_data=True) + client.start_streaming(duration=1.0, delay=0.25, print_data=False) assert sleeps == [0.25] - assert "Status: 0x00000010" in capsys.readouterr().out client.disconnect() diff --git a/tests/quality/test_quality_configuration.py b/tests/quality/test_quality_configuration.py deleted file mode 100644 index ceafc08..0000000 --- a/tests/quality/test_quality_configuration.py +++ /dev/null @@ -1,365 +0,0 @@ -from __future__ import annotations - -from enum import Enum -from operator import index -from pathlib import Path -from re import search -from subprocess import run -from sys import executable - -import pytest -import tomllib -import yaml -from mypy import api as mypy_api - -from pynetft import _native - -ROOT = Path(__file__).parents[2] -NATIVE_ENUM_TYPES = ( - _native.ForceUnit, - _native.TorqueUnit, - _native.CalibrationSource, - _native.RecoveryPolicy, - _native.ClientState, - _native.FaultCode, - _native.StatusSeverity, - _native.ReadStatus, -) - - -def _run_mypy(*arguments: str) -> tuple[str, str, int]: - return mypy_api.run( - [ - "--no-incremental", - "--no-site-packages", - *arguments, - ] - ) - - -def test_configured_mypy_finds_src_package_without_installation() -> None: - stdout, stderr, status = _run_mypy() - - assert status == 0, stdout + stderr - - -def test_native_stub_accepts_mutable_fields() -> None: - fixture = ROOT / "tests/typecheck/native_mutable.py" - stdout, stderr, status = _run_mypy( - "--config-file=tests/typecheck/mypy.ini", - str(fixture), - ) - - assert status == 0, stdout + stderr - - -def test_native_stub_accepts_runtime_enum_and_sample_container_behavior() -> None: - fixture = ROOT / "tests/typecheck/native_runtime.py" - stdout, stderr, status = _run_mypy( - "--config-file=tests/typecheck/mypy.ini", - str(fixture), - ) - - assert status == 0, stdout + stderr - - -def test_native_stub_rejects_readonly_fields_and_enum_iteration() -> None: - fixture = ROOT / "tests/typecheck/native_rejected.py" - stdout, stderr, status = _run_mypy( - "--config-file=tests/typecheck/mypy.ini", - "--show-error-codes", - str(fixture), - ) - errors = [line for line in stdout.splitlines() if ": error:" in line] - - assert status == 1, stdout + stderr - assert len(errors) == 4 - assert sum("[misc]" in error for error in errors) == 3 - assert sum("[attr-defined]" in error for error in errors) == 1 - - -@pytest.mark.parametrize("enum_type", NATIVE_ENUM_TYPES) -def test_native_enum_runtime_is_not_iterable_enum_class(enum_type: type[object]) -> None: - assert not issubclass(enum_type, Enum) - with pytest.raises(TypeError): - iter(enum_type) - - -@pytest.mark.parametrize("enum_type", NATIVE_ENUM_TYPES) -def test_native_enum_runtime_supports_pybind_integer_protocol( - enum_type: type[object], -) -> None: - members = enum_type.__members__ # type: ignore[attr-defined] - assert isinstance(members, dict) - first = next(iter(members.values())) - - assert enum_type(first.value) == first # type: ignore[attr-defined,call-arg] - assert enum_type(first) == first # type: ignore[call-arg] - assert int(first) == first.value # type: ignore[arg-type,attr-defined] - assert index(first) == first.value # type: ignore[arg-type,attr-defined] - - -def test_native_sample_vector_properties_are_lists() -> None: - sample = _native.Sample() - - assert isinstance(sample.raw_wrench, list) - assert isinstance(sample.force, list) - assert isinstance(sample.torque, list) - - -@pytest.mark.parametrize( - ("value", "attributes"), - [ - ( - _native.Sample(), - ( - "rdt_sequence", - "ft_sequence", - "status", - "raw_wrench", - "force", - "torque", - "force_unit", - "torque_unit", - "configuration_revision", - "received_at_ns", - ), - ), - ( - _native.Health(), - ( - "state", - "fault_code", - "sensor_host", - "rdt_port", - "sensor_configuration", - "last_rdt_sequence", - "last_ft_sequence", - "last_status", - "receive_rate_hz", - "delivery_rate_hz", - "received_count", - "delivered_count", - "rate_limited_count", - "device_error_count", - "warning_count", - "lost_count", - "duplicate_count", - "out_of_order_count", - "malformed_count", - "reconnect_count", - "timeout_count", - "callback_error_count", - "ft_stall_count", - "ft_backward_count", - "ft_restart_count", - "calibration_change_count", - "last_record_age", - "last_error", - "last_ft_progress", - ), - ), - (_native.ReadResult(), ("status", "sample")), - ], -) -def test_native_readonly_runtime_fields_reject_assignment( - value: object, attributes: tuple[str, ...] -) -> None: - for attribute in attributes: - with pytest.raises(AttributeError): - setattr(value, attribute, getattr(value, attribute)) - - -def test_sanitizer_shell_is_fail_fast() -> None: - with (ROOT / ".github/workflows/ci.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - sanitizer_steps = workflow["jobs"]["sanitizers"]["steps"] - test_step = next( - step - for step in sanitizer_steps - if step.get("name") == "Test native queue and extension integration" - ) - - script = test_step["run"].lstrip() - assert script.startswith("pixi run bash -euo pipefail -c") - assert "detect_leaks=0" not in script - assert "PYTHONMALLOC=malloc" in script - assert "tools/lsan.supp" in script - - -def test_bash_fail_fast_rejects_a_failed_first_command() -> None: - completed = run( - ["bash", "-euo", "pipefail", "-c", "false; true"], - check=False, - ) - - assert completed.returncode != 0 - - -def test_python_matrix_selects_runtime_tests_only() -> None: - with (ROOT / ".github/workflows/ci.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - python_steps = workflow["jobs"]["python"]["steps"] - test_step = next( - step - for step in python_steps - if step.get("name") == "Run Python and fake-sensor integration tests" - ) - - assert test_step["run"] == "python -m pytest -q tests/python tests/integration" - - -def test_quality_environment_checks_conda_executable_dependencies() -> None: - with (ROOT / ".github/workflows/ci.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - quality_steps = workflow["jobs"]["quality"]["steps"] - commands = {step["run"] for step in quality_steps if isinstance(step.get("run"), str)} - - assert "pixi run patchelf --version" in commands - assert "pixi run python -m cibuildwheel --help" in commands - assert "pixi run python -m pip check" not in commands - - -def test_pixi_workspace_resolves_native_linux_and_macos_platforms() -> None: - with (ROOT / "pixi.toml").open("rb") as stream: - manifest = tomllib.load(stream) - - assert set(manifest["workspace"]["platforms"]) == { - "linux-64", - "linux-aarch64", - "osx-64", - "osx-arm64", - } - - -def test_python_build_backend_matches_pixi_environment() -> None: - with (ROOT / "pyproject.toml").open("rb") as stream: - project = tomllib.load(stream) - with (ROOT / "pixi.toml").open("rb") as stream: - pixi = tomllib.load(stream) - - for dependency in ("scikit-build-core", "pybind11"): - backend_requirement = next( - requirement - for requirement in project["build-system"]["requires"] - if requirement.startswith(dependency) - ) - assert backend_requirement.removeprefix(dependency) == pixi["dependencies"][dependency] - assert project["tool"]["scikit-build"]["minimum-version"] == "build-system.requires" - - -def test_macos_pixi_targets_provide_native_wheel_repair_tools() -> None: - with (ROOT / "pixi.toml").open("rb") as stream: - manifest = tomllib.load(stream) - - default_dependencies = manifest["dependencies"] - target_dependencies = manifest["target"] - - assert "auditwheel" not in default_dependencies - for platform in ("linux-64", "linux-aarch64"): - assert "auditwheel" in target_dependencies[platform]["dependencies"] - for platform in ("osx-64", "osx-arm64"): - dependencies = target_dependencies[platform]["dependencies"] - assert dependencies["cibuildwheel"] == "==3.4.1" - assert "delocate" in dependencies - assert "auditwheel" not in dependencies - - -def test_macos_ci_runs_native_and_fake_sensor_tests_on_both_architectures() -> None: - with (ROOT / ".github/workflows/ci.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - macos = workflow["jobs"]["macos"] - matrix = macos["strategy"]["matrix"] - commands = [step["run"] for step in macos["steps"] if isinstance(step.get("run"), str)] - - assert macos["runs-on"] == "${{ matrix.runner }}" - assert macos["strategy"]["fail-fast"] is False - assert {(entry["runner"], entry["arch"]) for entry in matrix["include"]} == { - ("macos-15-intel", "x86_64"), - ("macos-15", "arm64"), - } - assert any( - step.get("uses") == "prefix-dev/setup-pixi@v0.10.0" - and step.get("with", {}).get("cache") is True - for step in macos["steps"] - ) - assert any( - "cmake -S . -B build/macos" in command - and "-DCMAKE_BUILD_TYPE=Release" in command - and "-DPYNETFT_BUILD_TESTING=ON" in command - for command in commands - ) - assert any("cmake --build build/macos" in command for command in commands) - assert any( - "ctest --test-dir build/macos --output-on-failure" in command for command in commands - ) - assert "pixi run install" in commands - assert "pixi run python -m pytest -q tests/python tests/integration" in commands - - -def test_macos_ci_never_targets_a_hardware_sensor() -> None: - with (ROOT / ".github/workflows/ci.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - serialized_job = yaml.safe_dump(workflow["jobs"]["macos"]) - - assert "hardware-test" not in serialized_job - assert "hardware_test.py" not in serialized_job - assert "NETFT_SENSOR" not in serialized_job - assert search(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", serialized_job) is None - - -def test_python_matrix_collection_does_not_require_quality_dependencies() -> None: - blocker = """ -import importlib.abc -import pytest -import sys - -class BlockQualityDependencies(importlib.abc.MetaPathFinder): - def find_spec(self, fullname, path=None, target=None): - if fullname.partition(".")[0] in {"mypy", "yaml"}: - raise ModuleNotFoundError(fullname) - return None - -sys.meta_path.insert(0, BlockQualityDependencies()) -raise SystemExit(pytest.main([ - "--collect-only", - "-q", - "tests/python", - "tests/integration", -])) -""" - completed = run( - [executable, "-c", blocker], - cwd=ROOT, - check=False, - capture_output=True, - text=True, - ) - - assert completed.returncode == 0, completed.stdout + completed.stderr - - -def test_codeql_cpp_analysis_uses_official_inline_suppression_query() -> None: - with (ROOT / ".github/workflows/codeql.yml").open(encoding="utf-8") as stream: - workflow = yaml.safe_load(stream) - analyze = workflow["jobs"]["analyze"] - matrix = analyze["strategy"]["matrix"]["include"] - configurations = { - entry["language"]: (entry["build-mode"], entry["suppression-pack"]) for entry in matrix - } - init_steps = [ - step - for step in analyze["steps"] - if str(step.get("uses", "")).startswith("github/codeql-action/init@") - ] - - assert configurations == { - "python": ("none", ""), - "c-cpp": ("manual", "+codeql/cpp-queries:AlertSuppression.ql"), - } - assert len(init_steps) == 2 - python_init = next(step for step in init_steps if step["if"] == "matrix.language == 'python'") - cpp_init = next(step for step in init_steps if step["if"] == "matrix.language == 'c-cpp'") - assert "packs" not in python_init["with"] - assert cpp_init["with"]["packs"] == "${{ matrix.suppression-pack }}" - assert all("queries" not in step["with"] for step in init_steps) diff --git a/tests/quality/test_sdist.py b/tests/quality/test_sdist.py index af72a8b..02bf456 100644 --- a/tests/quality/test_sdist.py +++ b/tests/quality/test_sdist.py @@ -44,9 +44,9 @@ def _build_sdist(destination: Path) -> Path: def test_sdist_excludes_generated_caches_and_is_reproducible(tmp_path: Path) -> None: checker = _load_sdist_checker() pollution = ( - ROOT / "bindings" / "python" / "__pycache__" / "task10.pyc", - ROOT / "core" / ".pytest_cache" / "task10", - ROOT / "src" / "pynetft" / "__pycache__" / "task10.pyc", + ROOT / "bindings" / "python" / "__pycache__" / "generated-cache.pyc", + ROOT / "core" / ".pytest_cache" / "generated-cache", + ROOT / "src" / "pynetft" / "__pycache__" / "generated-cache.pyc", ) try: for path in pollution: diff --git a/tests/quality/test_wheel_configuration.py b/tests/quality/test_wheel_configuration.py index af3d5f5..e30db86 100644 --- a/tests/quality/test_wheel_configuration.py +++ b/tests/quality/test_wheel_configuration.py @@ -2,8 +2,6 @@ import importlib.util import os -import re -import shlex import subprocess import sys import zipfile @@ -11,17 +9,10 @@ from types import ModuleType import pytest -import tomllib -import yaml ROOT = Path(__file__).resolve().parents[2] -def _project_configuration() -> dict[str, object]: - with (ROOT / "pyproject.toml").open("rb") as stream: - return tomllib.load(stream) - - def _load_wheel_checker() -> ModuleType: path = ROOT / "tools" / "check_wheel.py" assert path.is_file() @@ -53,75 +44,6 @@ def _valid_wheel_members(extension: str) -> tuple[str, ...]: ) -def test_cibuildwheel_selects_the_supported_linux_matrix() -> None: - cibuildwheel = _project_configuration()["tool"]["cibuildwheel"] # type: ignore[index] - linux = cibuildwheel["linux"] - - assert cibuildwheel["build"] == [ - "cp310-*", - "cp311-*", - "cp312-*", - "cp313-*", - "cp314-*", - ] - assert cibuildwheel["skip"] == ["*-musllinux_*", "*-manylinux_i686", "pp*", "cp*t-*"] - assert ( - cibuildwheel["test-command"] - == "python -m pip check && python -m pytest {project}/tests/artifact -q" - ) - assert cibuildwheel["test-requires"] == ["pytest"] - assert linux["archs"] == ["x86_64", "aarch64"] - assert linux["manylinux-x86_64-image"] == "manylinux2014" - assert linux["manylinux-aarch64-image"] == "manylinux2014" - assert linux["before-all"] == "bash {project}/tools/build_manylinux_curl.sh" - assert "-DCURL_USE_STATIC_LIBS=ON" in linux["environment"]["CMAKE_ARGS"] - assert "/opt/pynetft-curl/lib/libcurl.a" in linux["environment"]["CMAKE_ARGS"] - assert "/opt/pynetft-curl/include" in linux["environment"]["CMAKE_ARGS"] - - -def test_cibuildwheel_configures_separate_native_macos_wheels() -> None: - macos = _project_configuration()["tool"]["cibuildwheel"]["macos"] # type: ignore[index] - - assert macos["archs"] == ["x86_64", "arm64"] - assert macos["before-all"] == "bash {project}/tools/build_macos_curl.sh" - assert macos["environment"]["MACOSX_DEPLOYMENT_TARGET"] == "11.0" - assert "-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=ON" in macos["environment"]["CMAKE_ARGS"] - assert "-DCURL_USE_STATIC_LIBS=ON" in macos["environment"]["CMAKE_ARGS"] - assert "/pynetft-curl/lib/libcurl.a" in macos["environment"]["CMAKE_ARGS"] - assert "/pynetft-curl/include" in macos["environment"]["CMAKE_ARGS"] - assert "delocate-wheel" in macos["repair-wheel-command"] - - -def test_cibuildwheel_configures_native_windows_wheels_with_static_curl() -> None: - windows = _project_configuration()["tool"]["cibuildwheel"]["windows"] # type: ignore[index] - - assert windows["archs"] == ["AMD64"] - assert windows["before-all"] == ( - "powershell -NoProfile -ExecutionPolicy Bypass -File {project}/tools/build_windows_curl.ps1" - ) - assert "-DCURL_USE_STATIC_LIBS=ON" in windows["environment"]["CMAKE_ARGS"] - assert "C:/pynetft-curl/lib/libcurl.lib" in windows["environment"]["CMAKE_ARGS"] - assert "C:/pynetft-curl/include" in windows["environment"]["CMAKE_ARGS"] - - -def test_windows_curl_builder_is_pinned_and_produces_only_a_static_library() -> None: - script = (ROOT / "tools" / "build_windows_curl.ps1").read_text(encoding="utf-8") - - assert "8.21.0" in script - assert "aa1b66a70eace83dc624508745646c08ae561de512ab403adffb93ac87fc72e6" in script - assert "System.Security.Cryptography.SHA256" in script - assert "Get-FileHash" not in script - assert "vswhere.exe" in script - assert '"Visual Studio 18 2026"' in script - assert '"Visual Studio 17 2022"' in script - assert "--config Release" in script - assert "-DHTTP_ONLY=ON" in script - assert "-DBUILD_SHARED_LIBS=OFF" in script - assert "-DBUILD_STATIC_LIBS=ON" in script - assert "libcurl.lib" in script - assert "libcurl.dll" in script - - @pytest.mark.parametrize( "name", ("build_static_curl.sh", "build_manylinux_curl.sh", "build_macos_curl.sh"), @@ -240,199 +162,6 @@ def test_curl_build_wrappers_forward_the_build_environment(tmp_path: Path, name: assert Path(implementation).resolve() == ROOT / "tools" / "build_static_curl.sh" -def test_wheel_workflow_has_smoke_and_full_build_modes() -> None: - with (ROOT / ".github" / "workflows" / "wheels.yml").open(encoding="utf-8") as stream: - workflow = yaml.load(stream, Loader=yaml.BaseLoader) - - upload_steps = [ - step - for job in workflow["jobs"].values() - for step in job["steps"] - if step.get("uses", "").startswith("actions/upload-artifact@") - ] - assert upload_steps - assert all( - re.fullmatch(r"actions/upload-artifact@[0-9a-f]{40}", step["uses"]) for step in upload_steps - ) - - assert set(workflow["on"]) == {"pull_request", "push", "workflow_dispatch"} - assert workflow["on"]["push"]["branches"] == ["main"] - curl_build = workflow["jobs"]["curl-build"] - curl_test = next( - step for step in curl_build["steps"] if "PYNETFT_RUN_CURL_BUILD_TEST" in step.get("env", {}) - ) - assert curl_test["env"]["PYNETFT_RUN_CURL_BUILD_TEST"] == "1" - curl_command = shlex.split(curl_test["run"]) - assert curl_command[:3] == ["python", "-m", "pytest"] - assert (ROOT / curl_command[3]).is_file() - - smoke = workflow["jobs"]["smoke"] - assert smoke["if"] == "github.event_name == 'pull_request'" - assert smoke["needs"] == "curl-build" - assert smoke["runs-on"] == "ubuntu-24.04" - smoke_install = next( - step for step in smoke["steps"] if step.get("name") == "Install wheel build frontend" - ) - assert "cibuildwheel==3.4.1" in smoke_install["run"] - assert "auditwheel" in smoke_install["run"] - smoke_build = next( - step for step in smoke["steps"] if step.get("run", "").startswith("python -m cibuildwheel") - ) - assert smoke_build["env"]["CIBW_BUILD"] == "cp310-*" - assert smoke_build["env"]["CIBW_ARCHS_LINUX"] == "x86_64" - - full = workflow["jobs"]["wheels"] - assert full["if"] == "github.event_name != 'pull_request'" - assert full["needs"] == "curl-build" - full_install = next( - step for step in full["steps"] if step.get("name") == "Install wheel build frontend" - ) - assert "cibuildwheel==3.4.1" in full_install["run"] - assert "auditwheel" in full_install["run"] - matrix = full["strategy"]["matrix"]["include"] - assert {entry["arch"] for entry in matrix} == {"x86_64", "aarch64"} - assert all(entry["runner"] for entry in matrix) - - build_step = next( - step for step in full["steps"] if step.get("run", "").startswith("python -m cibuildwheel") - ) - assert build_step["env"]["CIBW_ARCHS_LINUX"] == "${{ matrix.arch }}" - validation_step = next( - step - for step in full["steps"] - if step.get("name") == "Validate wheel structure and dependencies" - ) - expected_validation = [ - "python", - "tools/check_wheel.py", - "--self-contained", - "--auditwheel", - "wheelhouse/*.whl", - ] - assert shlex.split(validation_step["run"]) == expected_validation - smoke_validation = next( - step - for step in smoke["steps"] - if step.get("name") == "Validate wheel structure and dependencies" - ) - assert shlex.split(smoke_validation["run"]) == expected_validation - - macos_matrix = { - "x86_64": "macos-15-intel", - "arm64": "macos-15", - } - expected_macos_validation = [ - "python", - "tools/check_wheel.py", - "--self-contained", - "--delocate", - "wheelhouse/*.whl", - ] - for name, event_condition, artifact_name, build_override in ( - ( - "macos-smoke", - "github.event_name == 'pull_request'", - "wheels-smoke-macos-${{ matrix.arch }}", - "cp310-*", - ), - ( - "macos-wheels", - "github.event_name != 'pull_request'", - "wheels-macos-${{ matrix.arch }}", - None, - ), - ): - macos = workflow["jobs"][name] - assert macos["if"] == event_condition - assert macos["needs"] == "curl-build" - assert macos["runs-on"] == "${{ matrix.runner }}" - assert macos["strategy"]["fail-fast"] == "false" - assert { - entry["arch"]: entry["runner"] for entry in macos["strategy"]["matrix"]["include"] - } == macos_matrix - - macos_install = next( - step for step in macos["steps"] if "cibuildwheel==3.4.1" in step.get("run", "") - ) - assert "cibuildwheel==3.4.1" in macos_install["run"] - assert "delocate" in macos_install["run"] - macos_build = next( - step - for step in macos["steps"] - if step.get("run", "").startswith("python -m cibuildwheel") - ) - assert macos_build["env"]["CIBW_ARCHS_MACOS"] == "${{ matrix.arch }}" - assert macos_build["env"].get("CIBW_BUILD") == build_override - assert shlex.split(macos_build["run"]) == [ - "python", - "-m", - "cibuildwheel", - "--platform", - "macos", - "--output-dir", - "wheelhouse", - ] - macos_validation = next( - step for step in macos["steps"] if "tools/check_wheel.py" in step.get("run", "") - ) - assert shlex.split(macos_validation["run"]) == expected_macos_validation - macos_upload = next( - step - for step in macos["steps"] - if step.get("uses", "").startswith("actions/upload-artifact@") - ) - assert macos_upload["with"]["name"] == artifact_name - assert macos_upload["with"]["path"] == "wheelhouse/*.whl" - assert macos_upload["with"]["if-no-files-found"] == "error" - - assert "publish" not in workflow["jobs"] - - -def test_wheel_workflow_builds_and_validates_windows_wheels() -> None: - with (ROOT / ".github" / "workflows" / "wheels.yml").open(encoding="utf-8") as stream: - workflow = yaml.load(stream, Loader=yaml.BaseLoader) - - for name, event_condition, artifact_name, build_override in ( - ( - "windows-smoke", - "github.event_name == 'pull_request'", - "wheels-smoke-windows-x86_64", - "cp310-*", - ), - ( - "windows-wheels", - "github.event_name != 'pull_request'", - "wheels-windows-x86_64", - None, - ), - ): - windows = workflow["jobs"][name] - assert windows["if"] == event_condition - assert windows["runs-on"] == "windows-2025" - install = next( - step for step in windows["steps"] if "cibuildwheel==3.4.1" in step.get("run", "") - ) - assert "pefile" in install["run"] - build = next( - step - for step in windows["steps"] - if step.get("run", "").startswith("python -m cibuildwheel") - ) - assert build["env"]["CIBW_ARCHS_WINDOWS"] == "AMD64" - assert build["env"].get("CIBW_BUILD") == build_override - assert "--platform windows" in build["run"] - validation = next( - step for step in windows["steps"] if "tools/check_wheel.py" in step.get("run", "") - ) - assert "--self-contained" in validation["run"] - upload = next( - step - for step in windows["steps"] - if step.get("uses", "").startswith("actions/upload-artifact@") - ) - assert upload["with"]["name"] == artifact_name - - def test_wheel_checker_accepts_only_the_private_runtime_payload(tmp_path: Path) -> None: checker = _load_wheel_checker() wheel = tmp_path / "pynetft-2.1.0-cp314-cp314-manylinux2014_x86_64.whl" @@ -558,12 +287,6 @@ def test_windows_dependency_validation_rejects_other_external_dlls() -> None: checker.validate_windows_dependencies({"KERNEL32.dll", "zlib1.dll"}) -def test_workflows_pin_checkout_to_an_immutable_revision() -> None: - for workflow in (ROOT / ".github/workflows").glob("*.yml"): - contents = workflow.read_text(encoding="utf-8") - assert "actions/checkout@v" not in contents - - def test_self_containment_dispatches_pe_inspection_for_windows_wheels( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: diff --git a/tests/release/test_metadata.py b/tests/release/test_metadata.py index bc956f2..49568a9 100644 --- a/tests/release/test_metadata.py +++ b/tests/release/test_metadata.py @@ -1,15 +1,12 @@ from __future__ import annotations -import base64 import importlib.util -import re import subprocess from pathlib import Path from types import ModuleType import pytest import tomllib -import yaml ROOT = Path(__file__).resolve().parents[2] @@ -25,11 +22,6 @@ def _load_tool(name: str) -> ModuleType: return module -def _workflow() -> dict[str, object]: - with (ROOT / ".github" / "workflows" / "release.yml").open(encoding="utf-8") as stream: - return yaml.load(stream, Loader=yaml.BaseLoader) - - def _write_complete_inventory(root: Path) -> list[str]: wheel_names = [] for python in ("cp310", "cp311", "cp312", "cp313", "cp314"): @@ -111,354 +103,21 @@ def _sign_tag(repository: Path, tag: str, key: Path) -> None: ) -def test_release_versions_and_changelog_heading_agree() -> None: - project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) - version = project["project"]["version"] - cmake = (ROOT / "CMakeLists.txt").read_text(encoding="utf-8") - changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") - - assert version == "2.1.0" - assert re.search( - rf"^project\(pynetft VERSION {re.escape(version)} LANGUAGES CXX\)$", - cmake, - re.MULTILINE, - ) - assert re.search( - rf"^## {re.escape(version)} - \d{{4}}-\d{{2}}-\d{{2}}$", - changelog, - re.MULTILINE, - ) - - -def test_core_snapshot_identifies_the_pinned_release() -> None: - metadata = dict( - line.split("=", 1) - for line in (ROOT / "core" / "UPSTREAM").read_text(encoding="utf-8").splitlines() - ) - - assert metadata["repository"] == "https://github.com/netft/netft-cpp" - assert metadata["tag"] == "v0.3.0" - assert metadata["commit"] == "46ee05639f818a17c1cfe604d0d77b1feb8f9b2b" - - core_cmake = (ROOT / "core" / "CMakeLists.txt").read_text(encoding="utf-8") - assert re.search( - r"^project\(netft VERSION 0\.3\.0 LANGUAGES CXX\)$", - core_cmake, - re.MULTILINE, - ) - - -def test_python_build_disables_the_core_cli_and_marks_static_windows_curl() -> None: - cmake = (ROOT / "CMakeLists.txt").read_text(encoding="utf-8") - - assert 'set(NETFT_BUILD_CLI OFF CACHE BOOL "" FORCE)' in cmake - assert re.search( - r"if\(WIN32\).*target_compile_definitions\(netft PRIVATE CURL_STATICLIB\).*endif\(\)", - cmake, - re.DOTALL, - ) - for library in ("advapi32", "bcrypt", "crypt32", "iphlpapi", "secur32", "ws2_32"): - assert library in cmake - - -def test_core_snapshot_contains_only_the_controlled_upstream_paths() -> None: - metadata = dict( - line.split("=", 1) - for line in (ROOT / "core" / "UPSTREAM").read_text(encoding="utf-8").splitlines() - ) - selected = set(metadata["paths"].split(",")) - inventory = { - path.relative_to(ROOT / "core").as_posix() - for path in (ROOT / "core").rglob("*") - if path.is_file() - } - payload = inventory - {"UPSTREAM", "SNAPSHOT.sha256"} - - assert selected == {"CMakeLists.txt", "LICENSE", "app", "cmake", "include", "src"} - assert payload - assert all(path.split("/", 1)[0] in selected for path in payload) - assert all( - part not in {".git", ".github", "build", "test", "tests"} - for path in inventory - for part in path.split("/") - ) - - -def test_source_build_metadata_requires_libcurl_7_63() -> None: - pixi = tomllib.loads((ROOT / "pixi.toml").read_text(encoding="utf-8")) - core_cmake = (ROOT / "core" / "CMakeLists.txt").read_text(encoding="utf-8") - installed_config = (ROOT / "core" / "cmake" / "netftConfig.cmake.in").read_text( - encoding="utf-8" - ) - - assert pixi["dependencies"]["libcurl"] == ">=7.63.0" - assert re.search( - r"^find_package\(CURL 7\.63\.0 REQUIRED\)$", - core_cmake, - re.MULTILINE, - ) - assert re.search( - r"^find_dependency\(CURL 7\.63\.0\)$", - installed_config, - re.MULTILINE, - ) - - def test_release_metadata_tool_validates_tag_and_extracts_current_section() -> None: tool = _load_tool("release_metadata") + project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) + version = project["project"]["version"] - metadata = tool.validate_release(ROOT, "v2.1.0") + metadata = tool.validate_release(ROOT, f"v{version}") notes = tool.changelog_notes(ROOT / "CHANGELOG.md", metadata.version) - assert metadata.version == "2.1.0" - assert metadata.tag == "v2.1.0" - assert metadata.release_date.isoformat() == "2026-07-29" + assert metadata.version == version + assert metadata.tag == f"v{version}" assert notes + other_tag = "v0.0.0" if version != "0.0.0" else "v0.0.1" with pytest.raises(tool.ReleaseMetadataError): - tool.validate_release(ROOT, "v2.0.0") - - -def test_release_workflow_has_read_only_defaults_and_tag_only_trigger() -> None: - workflow = _workflow() - - assert workflow["on"] == {"push": {"tags": ["v*"]}} - assert workflow["permissions"] == {"contents": "read"} - assert "pull_request" not in workflow["on"] - validation_commands = "\n".join( - step.get("run", "") for step in workflow["jobs"]["validate"]["steps"] if step.get("run") - ) - assert "verify-tag --raw" in validation_commands - assert "gpg.ssh.allowedSignersFile" in validation_commands - assert "merge-base --is-ancestor" in validation_commands - assert "refs/remotes/origin/main" in validation_commands - for job in workflow["jobs"].values(): - for step in job.get("steps", []): - action = step.get("uses") - if action is not None: - assert re.search(r"@[0-9a-f]{40}$", action) - - -def test_release_workflow_pins_every_action_with_a_version_comment() -> None: - content = (ROOT / ".github" / "workflows" / "release.yml").read_text(encoding="utf-8") - action_lines = [line.strip() for line in content.splitlines() if "uses:" in line] - - assert action_lines - assert all(re.search(r"@[0-9a-f]{40}\s+#\s+\S+$", line) for line in action_lines) - - -def test_release_setup_pixi_steps_do_not_use_shared_caches() -> None: - workflow = _workflow() - setup_steps: list[tuple[str, dict[str, object]]] = [] - - for job_name, job in workflow["jobs"].items(): - for step in job.get("steps", []): - action = step.get("uses", "") - if action.startswith("prefix-dev/setup-pixi@"): - setup_steps.append((job_name, step)) - - assert setup_steps - for job_name, step in setup_steps: - assert step.get("with", {}).get("cache") == "false", job_name - - -def test_release_allowed_signers_contains_only_public_git_key_material() -> None: - path = ROOT / ".github" / "release-allowed-signers" - lines = [ - line - for line in path.read_text(encoding="utf-8").splitlines() - if line and not line.startswith("#") - ] - - assert len(lines) == 1 - principal, namespace, key_type, key_data = lines[0].split() - assert principal == "netft-release" - assert namespace == 'namespaces="git"' - assert key_type == "ssh-ed25519" - assert len(base64.b64decode(key_data, validate=True)) > 32 - - -def test_release_workflow_builds_and_validates_the_complete_artifact_matrix() -> None: - workflow = _workflow() - jobs = workflow["jobs"] - wheels = jobs["wheels"] - matrix = wheels["strategy"]["matrix"]["include"] - - assert {entry["arch"] for entry in matrix} == {"x86_64", "aarch64"} - assert len({entry["artifact"] for entry in matrix}) == 2 - assert all(entry["runner"] for entry in matrix) - assert jobs["sdist"]["needs"] == "validate" - assert wheels["needs"] == "validate" - - assemble = jobs["assemble"] - assert set(assemble["needs"]) == {"wheels", "macos-wheels", "windows-wheels", "sdist"} - download_steps = [ - step - for step in assemble["steps"] - if step.get("uses", "").startswith("actions/download-artifact@") - ] - assert any( - step.get("with", {}).get("pattern") == "release-wheels-*" - and step.get("with", {}).get("merge-multiple") == "true" - for step in download_steps - ) - assemble_run = "\n".join(step.get("run", "") for step in assemble["steps"] if step.get("run")) - assert "tools/check_release_artifacts.py" in assemble_run - assert "python tools/check_wheel.py artifacts/*.whl" in assemble_run - assert ( - 'python tools/check_wheel.py --self-contained --auditwheel "${linux_wheels[@]}"' - in assemble_run - ) - assert "--auditwheel artifacts/*.whl" not in assemble_run - assert "--delocate" not in assemble_run - assert "otool" not in assemble_run - assert "tools/check_sdist.py" in assemble_run - assert "twine check" in assemble_run - assert "matching_wheel=(artifacts/*-cp314-cp314-*manylinux*x86_64.whl)" in assemble_run - assert "sha256sum *" in assemble_run - - -def test_release_workflow_builds_and_native_validates_macos_wheels() -> None: - project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) - cibuildwheel = project["tool"]["cibuildwheel"] - workflow = _workflow() - macos = workflow["jobs"]["macos-wheels"] - - assert macos["needs"] == "validate" - assert macos["runs-on"] == "${{ matrix.runner }}" - assert macos["strategy"]["matrix"]["include"] == [ - { - "arch": "x86_64", - "runner": "macos-15-intel", - "artifact": "release-wheels-macos-x86_64", - }, - { - "arch": "arm64", - "runner": "macos-15", - "artifact": "release-wheels-macos-arm64", - }, - ] - assert cibuildwheel["build"] == [ - "cp310-*", - "cp311-*", - "cp312-*", - "cp313-*", - "cp314-*", - ] - assert cibuildwheel["test-command"] == ( - "python -m pip check && python -m pytest {project}/tests/artifact -q" - ) - assert cibuildwheel["test-requires"] == ["pytest"] - - install_run = next( - step["run"] for step in macos["steps"] if "pip install" in step.get("run", "") - ) - assert "cibuildwheel==3.4.1" in install_run - assert "delocate" in install_run - assert "twine" in install_run - - build_step = next( - step for step in macos["steps"] if "python -m cibuildwheel" in step.get("run", "") - ) - assert "--platform macos" in build_step["run"] - assert build_step["env"]["CIBW_ARCHS_MACOS"] == "${{ matrix.arch }}" - - validation_index = next( - index - for index, step in enumerate(macos["steps"]) - if "tools/check_wheel.py" in step.get("run", "") - ) - upload_index = next( - index - for index, step in enumerate(macos["steps"]) - if step.get("uses", "").startswith("actions/upload-artifact@") - ) - validation_run = macos["steps"][validation_index]["run"] - assert "python -m twine check wheelhouse/*.whl" in validation_run - assert ( - "python tools/check_wheel.py --self-contained --delocate wheelhouse/*.whl" in validation_run - ) - assert validation_index < upload_index - assert macos["steps"][upload_index]["with"]["name"] == "${{ matrix.artifact }}" - assert macos["steps"][upload_index]["with"]["path"] == "wheelhouse/*.whl" - assert macos["steps"][upload_index]["with"]["if-no-files-found"] == "error" - - -def test_release_workflow_builds_and_native_validates_windows_wheels() -> None: - workflow = _workflow() - windows = workflow["jobs"]["windows-wheels"] - - assert windows["needs"] == "validate" - assert windows["runs-on"] == "windows-2025" - install_run = next( - step["run"] for step in windows["steps"] if "pip install" in step.get("run", "") - ) - assert "cibuildwheel==3.4.1" in install_run - assert "pefile" in install_run - assert "twine" in install_run - - build_step = next( - step for step in windows["steps"] if "python -m cibuildwheel" in step.get("run", "") - ) - assert "--platform windows" in build_step["run"] - assert build_step["env"]["CIBW_ARCHS_WINDOWS"] == "AMD64" - - validation_run = next( - step["run"] for step in windows["steps"] if "tools/check_wheel.py" in step.get("run", "") - ) - assert "$wheels = Get-ChildItem wheelhouse/*.whl" in validation_run - assert "python -m twine check $wheels" in validation_run - assert "python tools/check_wheel.py --self-contained $wheels" in validation_run - upload = next( - step - for step in windows["steps"] - if step.get("uses", "").startswith("actions/upload-artifact@") - ) - assert upload["with"]["name"] == "release-wheels-windows-x86_64" - assert upload["with"]["path"] == "wheelhouse/*.whl" - - -def test_release_workflow_uses_oidc_only_for_pypi_and_separates_github_write() -> None: - workflow = _workflow() - jobs = workflow["jobs"] - publish = jobs["publish-pypi"] - draft_release = jobs["draft-release"] - finalize_release = jobs["finalize-release"] - - assert publish["environment"]["name"] == "pypi" - assert publish["permissions"] == {"contents": "read", "id-token": "write"} - assert draft_release["permissions"] == {"contents": "write"} - assert finalize_release["permissions"] == {"contents": "write"} - assert draft_release["needs"] == ["assemble", "attest"] - assert publish["needs"] == ["assemble", "draft-release"] - assert finalize_release["needs"] == ["draft-release", "publish-pypi"] - assert all("password" not in step.get("with", {}) for step in publish["steps"]) - assert all("token" not in step.get("with", {}) for step in publish["steps"]) - draft_commands = "\n".join( - step.get("run", "") for step in draft_release["steps"] if step.get("run") - ) - assert "gh release create" in draft_commands - assert "--draft" in draft_commands - assert "gh release upload" in draft_commands - assert "--clobber" in draft_commands - assert "isDraft" in draft_commands - assert len(finalize_release["steps"]) == 1 - finalize_command = finalize_release["steps"][0]["run"] - assert "gh release edit" in finalize_command - assert "--draft=false" in finalize_command - assert "gh release create" not in finalize_command - assert "gh release upload" not in finalize_command - - -def test_release_aggregate_provisions_clean_sdist_build_dependencies() -> None: - workflow = _workflow() - commands = "\n".join( - step.get("run", "") for step in workflow["jobs"]["assemble"]["steps"] if step.get("run") - ) - - assert "build-essential" in commands - assert "libcurl4-openssl-dev" in commands - assert "env CC=gcc CXX=g++" in commands + tool.validate_release(ROOT, other_tag) def test_release_artifact_inventory_rejects_missing_or_duplicate_matrix_entries( diff --git a/tests/typecheck/mypy.ini b/tests/typecheck/mypy.ini deleted file mode 100644 index e304d81..0000000 --- a/tests/typecheck/mypy.ini +++ /dev/null @@ -1,4 +0,0 @@ -[mypy] -python_version = 3.10 -strict = true -mypy_path = $MYPY_CONFIG_FILE_DIR/../../src diff --git a/tests/typecheck/native_mutable.py b/tests/typecheck/native_mutable.py deleted file mode 100644 index 0016eb1..0000000 --- a/tests/typecheck/native_mutable.py +++ /dev/null @@ -1,29 +0,0 @@ -from pynetft import _native - - -def assign_mutable_native_fields( - calibration: _native.Calibration, - configuration: _native.SensorConfiguration, - config: _native.Config, -) -> tuple[str, int]: - calibration.counts_per_force_unit = 1.0 - calibration.counts_per_torque_unit = 2.0 - calibration.force_unit = _native.ForceUnit.NEWTON - calibration.torque_unit = _native.TorqueUnit.NEWTON_METER - configuration.product_name = "sensor" - configuration.calibration = calibration - configuration.source = _native.CalibrationSource.OVERRIDE - configuration.revision = 2 - config.sensor_host = "sensor" - config.rdt_port = 49152 - config.http_port = 80 - config.receive_timeout = 0.1 - config.configuration_connect_timeout = 0.5 - config.configuration_timeout = 1.0 - config.reconnect_initial_delay = 0.25 - config.reconnect_max_delay = 5.0 - config.sample_rate_limit_hz = 100.0 - config.deliver_samples_with_error_status = True - config.recovery_policy = _native.RecoveryPolicy.FAIL_STOP - config.calibration_override = calibration - return _native.ForceUnit.NEWTON.name, _native.ReadStatus.SAMPLE.value diff --git a/tests/typecheck/native_rejected.py b/tests/typecheck/native_rejected.py deleted file mode 100644 index 8a61d9a..0000000 --- a/tests/typecheck/native_rejected.py +++ /dev/null @@ -1,13 +0,0 @@ -from pynetft import _native - - -def reject_native_mutation_and_iteration( - sample: _native.Sample, - health: _native.Health, - result: _native.ReadResult, -) -> None: - sample.rdt_sequence = 1 - health.received_count = 1 - result.status = _native.ReadStatus.CLOSED - for _member in _native.ForceUnit: - pass diff --git a/tests/typecheck/native_runtime.py b/tests/typecheck/native_runtime.py deleted file mode 100644 index 27ba368..0000000 --- a/tests/typecheck/native_runtime.py +++ /dev/null @@ -1,38 +0,0 @@ -from operator import index - -from pynetft import _native - - -def use_native_enum_runtime_protocol() -> tuple[int, ...]: - force_from_int = _native.ForceUnit(_native.ForceUnit.NEWTON.value) - force_from_member = _native.ForceUnit(_native.ForceUnit.NEWTON) - torque = _native.TorqueUnit(_native.TorqueUnit.NEWTON_METER) - calibration_source = _native.CalibrationSource(_native.CalibrationSource.SENSOR.value) - recovery_policy = _native.RecoveryPolicy(_native.RecoveryPolicy.RECONNECT) - client_state = _native.ClientState(_native.ClientState.STREAMING.value) - fault_code = _native.FaultCode(_native.FaultCode.NONE) - severity = _native.StatusSeverity(_native.StatusSeverity.OK.value) - read_status = _native.ReadStatus(_native.ReadStatus.SAMPLE) - force_members: dict[str, _native.ForceUnit] = _native.ForceUnit.__members__ - - return ( - int(force_from_int), - index(force_from_member), - int(torque), - index(calibration_source), - int(recovery_policy), - index(client_state), - int(fault_code), - index(severity), - int(read_status), - int(force_members["NEWTON"]), - ) - - -def use_native_sample_containers( - sample: _native.Sample, -) -> tuple[list[int], list[float], list[float]]: - raw_wrench: list[int] = sample.raw_wrench - force: list[float] = sample.force - torque: list[float] = sample.torque - return raw_wrench, force, torque