diff --git a/backend/tests/test_bench_sandbox_provider.py b/backend/tests/test_bench_sandbox_provider.py index 133e81ab86..c3bfccd622 100644 --- a/backend/tests/test_bench_sandbox_provider.py +++ b/backend/tests/test_bench_sandbox_provider.py @@ -1,10 +1,13 @@ from __future__ import annotations import importlib.util +import os import sys from pathlib import Path from typing import Any +import pytest + def _load_module(name: str, relative: str): path = Path(__file__).resolve().parents[1] / relative @@ -276,6 +279,10 @@ def test_boxlite_factory_restores_module_state(monkeypatch): assert _FactoryProvider._create_box is original_create_box +@pytest.mark.skipif( + os.name == "nt", + reason="asserts POSIX exec-bit semantics (os.chmod cannot toggle execute bits on Windows); boxlite sandboxes are Linux-only", +) def test_boxlite_shim_workaround_retries_after_fixing_permissions(monkeypatch, tmp_path): boxes_dir = tmp_path / "boxes" shim = boxes_dir / "deadbeef" / "bin" / "boxlite-shim" diff --git a/backend/tests/test_extension_dependency_sync.py b/backend/tests/test_extension_dependency_sync.py index 22f7194b27..1fb7362727 100644 --- a/backend/tests/test_extension_dependency_sync.py +++ b/backend/tests/test_extension_dependency_sync.py @@ -4,6 +4,7 @@ from __future__ import annotations import os import re +import shutil import subprocess import tomllib import zipfile @@ -14,6 +15,12 @@ import pytest REPO_ROOT = Path(__file__).resolve().parents[2] BACKEND_ROOT = REPO_ROOT / "backend" +_MAKE_ON_PATH = shutil.which("make") is not None +_skip_without_make = pytest.mark.skipif( + not _MAKE_ON_PATH, + reason="these contract tests dry-run the root Makefile through 'make -n'; GNU make is not installed on this host", +) + def _make_recipe(path: Path, target: str) -> str: content = path.read_text(encoding="utf-8") @@ -202,6 +209,7 @@ def test_root_extension_shortcuts_are_cross_platform_and_keep_trust_confirmation assert "--yes" not in _make_recipe(makefile, "extension-upgrade") +@_skip_without_make def test_root_extension_shortcuts_reject_ambient_environment_arguments() -> None: environment = os.environ.copy() @@ -233,6 +241,7 @@ def test_root_extension_shortcuts_reject_ambient_environment_arguments() -> None ("extension-remove", "NAME", "--name-env __deerflow_extension_name__"), ], ) +@_skip_without_make def test_root_extension_shortcuts_keep_command_line_arguments_out_of_the_shell_recipe( target: str, variable: str, @@ -266,6 +275,7 @@ def test_root_extension_shortcuts_keep_command_line_arguments_out_of_the_shell_r ("extension-remove", "NAME", "--name-env __deerflow_extension_name__"), ], ) +@_skip_without_make def test_root_extension_shortcuts_keep_values_out_of_the_cmd_recipe_on_windows( target: str, variable: str, diff --git a/backend/tests/test_lark_broker.py b/backend/tests/test_lark_broker.py index 77f557b380..67a2c0a4d3 100644 --- a/backend/tests/test_lark_broker.py +++ b/backend/tests/test_lark_broker.py @@ -17,6 +17,16 @@ import pytest from deerflow.integrations import lark_broker from deerflow.integrations.lark_broker import BrokerConfig, run_lark_cli, serve +# Several tests execute the broker's POSIX artifacts directly: the fake +# ``lark-cli`` binary, the shim, and the launcher are ``#!`` scripts (the real +# ones target POSIX sandbox images), and Windows CreateProcess cannot execute a +# shebang script (WinError 193 / ENOEXEC). CI runs on Linux and keeps the +# coverage; Windows dev hosts skip these instead of hard-failing. +_skip_windows = pytest.mark.skipif( + os.name == "nt", + reason="spawns '#!' scripts (fake lark-cli / shim / launcher) that only a POSIX host can execute", +) + def _fake_lark_cli(tmp_path: Path) -> str: """A stub 'lark-cli' that echoes argv, stdin, and the credential env. @@ -58,6 +68,7 @@ def _config(tmp_path: Path, port: int = 0) -> BrokerConfig: # ── run_lark_cli (in-process, no server) ─────────────────────────────────── +@_skip_windows def test_run_lark_cli_forwards_argv_stdin_and_credential_env(tmp_path: Path) -> None: config = _config(tmp_path) result = run_lark_cli(config, ["auth", "status", "--json"], b"piped-input") @@ -72,11 +83,13 @@ def test_run_lark_cli_forwards_argv_stdin_and_credential_env(tmp_path: Path) -> assert result.stderr == b"ERR:auth status --json\n" +@_skip_windows def test_run_lark_cli_propagates_exit_code(tmp_path: Path) -> None: result = run_lark_cli(_config(tmp_path), ["do", "--boom"], b"") assert result.exit_code == 7 +@_skip_windows def test_run_lark_cli_never_shell_interprets_args(tmp_path: Path) -> None: # A shell metacharacter must reach the binary as one literal arg, not run a # second command (shell=False, argv list). @@ -120,6 +133,7 @@ def _post_exec(host: str, port: int, body: dict) -> tuple[int, dict]: return resp.status, parsed +@_skip_windows def test_exec_endpoint_round_trips(broker_server) -> None: host, port = broker_server status, body = _post_exec(host, port, {"args": ["ping"], "stdin_b64": base64.b64encode(b"hi").decode()}) @@ -130,6 +144,7 @@ def test_exec_endpoint_round_trips(broker_server) -> None: assert payload["stdin"] == "hi" +@_skip_windows def test_exec_endpoint_ignores_client_supplied_credential_paths(broker_server) -> None: host, port = broker_server # Even if a malicious client tries to smuggle env-like args, the broker sets @@ -171,6 +186,7 @@ def test_exec_endpoint_returns_500_json_on_unexpected_error(broker_server, monke # ── Shim script ───────────────────────────────────────────────────────────── +@_skip_windows def test_shim_forwards_and_replays_exit_code(broker_server, tmp_path: Path) -> None: host, port = broker_server shim = tmp_path / "lark-cli" @@ -230,6 +246,7 @@ def test_install_shim_writes_runtime_layout(tmp_path: Path) -> None: assert marker == {"version": "v1.0.65", "kind": "shim"} +@_skip_windows def test_launcher_resolves_python_and_forwards(broker_server, tmp_path: Path) -> None: """The /bin/sh launcher finds python3 on PATH and execs the shim body.""" host, port = broker_server @@ -253,6 +270,7 @@ def test_launcher_resolves_python_and_forwards(broker_server, tmp_path: Path) -> assert b"ERR:do --boom" in completed.stderr +@_skip_windows def test_launcher_can_pin_interpreter_via_env(broker_server, tmp_path: Path) -> None: """DEERFLOW_LARK_BROKER_PYTHON pins the interpreter for images with no python3 on PATH (the launcher must not silently ENOEXEC).""" @@ -276,6 +294,7 @@ def test_launcher_can_pin_interpreter_via_env(broker_server, tmp_path: Path) -> assert completed.returncode == 0 +@_skip_windows def test_launcher_fails_loudly_without_python(tmp_path: Path) -> None: """With no python interpreter resolvable, the launcher exits 127 with an actionable message rather than an opaque ENOEXEC.""" @@ -344,6 +363,7 @@ def test_denied_subcommand_matches_through_leading_flags(tmp_path: Path) -> None assert result.exit_code == 126 +@_skip_windows def test_allowed_subcommand_still_runs_with_denylist(tmp_path: Path) -> None: config = BrokerConfig( lark_cli_path=_fake_lark_cli(tmp_path),