fix: validate optional Python resources and isolate downloads

This commit is contained in:
yudshj committed 2026-10-09 15:52:59 +08:00
1 parent 305021fcd6
commit 4fd85be130
19 files changed
+335 -149

No files matched your search

@@ -136,7 +136,6 @@ def _run(arguments: list[str], options: OfficeOptions | None, result_type: type[
process = run_office_process(
[*deepseek_harness_runtime.resolve_office_launch_args(), *arguments,
*(options._arguments() if options is not None else [])],
capture_output=True,
)
stdout, stderr = process.stdout, process.stderr
if process.returncode != 0:
+106
View File
@@ -0,0 +1,106 @@
"""Office interruption terminates the owned process tree before returning to callers."""
from __future__ import annotations
import ntpath
import os
import signal
import subprocess
import sys
import time
from pathlib import Path
from types import SimpleNamespace
import pytest
from deepseek_harness_runtime import _resources
def wait_for_file(path: Path, process: subprocess.Popen, timeout: float = 30) -> None:
deadline = time.monotonic() + timeout
while not path.is_file():
if process.poll() is not None:
raise AssertionError(f"fixture exited before {path.name}: {process.returncode}")
if time.monotonic() >= deadline:
raise TimeoutError(f"fixture never produced {path}")
time.sleep(0.01)
@pytest.mark.skipif(os.name == "nt", reason="POSIX process groups and SIGINT delivery")
def test_second_interrupt_waits_for_the_entire_office_process_tree(tmp_path: Path) -> None:
fixture = tmp_path / "office.py"
ready, interrupted, pidfile = (tmp_path / name for name in ("ready", "interrupted", "pid"))
fixture.write_text("""import json,os,signal,subprocess,sys
from pathlib import Path
root=Path(sys.argv[1])
(root/'pid').write_text(str(os.getpid()))
signal.signal(signal.SIGINT,lambda *_:(root/'interrupted').touch())
child=subprocess.Popen([sys.executable,'-c',
"import signal,sys; from pathlib import Path; signal.signal(signal.SIGINT,signal.SIG_IGN); Path(sys.argv[1]).touch(); signal.pause()",str(root/'child-ready')])
(root/'ready').touch()
child.wait()
""", encoding="utf-8")
code = ("import sys; from deepseek_harness_runtime._resources import run_office_process; "
f"args=[sys.executable,{str(fixture)!r},{str(tmp_path)!r}]\n"
"try: run_office_process(args)\n"
"except KeyboardInterrupt: print('tree terminated',flush=True); sys.exit(73)\n")
process = subprocess.Popen([sys.executable, "-c", code], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
start_new_session=True, env={**os.environ,
"PYTHONPATH": str(Path(_resources.__file__).resolve().parents[1])})
try:
wait_for_file(ready, process)
wait_for_file(tmp_path / "child-ready", process)
os.kill(process.pid, signal.SIGINT)
wait_for_file(interrupted, process)
os.kill(process.pid, signal.SIGINT)
# The grandchild inherits the captured streams; communicate can finish only after it closes them.
stdout, stderr = process.communicate(timeout=30)
assert process.returncode == 73, stderr
assert stdout == b"tree terminated\n"
finally:
if pidfile.is_file():
try:
os.killpg(int(pidfile.read_text()), signal.SIGKILL)
except ProcessLookupError as error:
# The successful interruption path already terminated this group.
pass
if process.poll() is None:
process.kill()
process.communicate(timeout=30)
def test_windows_second_interrupt_uses_absolute_taskkill_and_waits(monkeypatch: pytest.MonkeyPatch) -> None:
calls = []
class Process:
pid = 73
returncode = -9
attempts = 0
def __enter__(self):
return self
def __exit__(self, *_):
pass
def communicate(self):
self.attempts += 1
if self.attempts < 3:
raise KeyboardInterrupt
return b"", b""
process = Process()
def popen(arguments, **options):
assert options == {"stdin": subprocess.DEVNULL, "stdout": subprocess.PIPE,
"stderr": subprocess.PIPE, "start_new_session": False}
return process
monkeypatch.setattr(_resources, "os", SimpleNamespace(name="nt", path=ntpath, environ={"SystemRoot": r"C:\Windows"}))
monkeypatch.setattr(_resources, "subprocess", SimpleNamespace(
Popen=popen, DEVNULL=subprocess.DEVNULL, PIPE=subprocess.PIPE,
run=lambda arguments, **options: calls.append((arguments, options)),
))
with pytest.raises(KeyboardInterrupt):
_resources.run_office_process(["node.exe", "cli.js"])
assert process.attempts == 3
assert calls == [([r"C:\Windows\System32\taskkill.exe", "/PID", "73", "/T", "/F"],
{"stdin": subprocess.DEVNULL, "capture_output": True, "check": False})]
+69 -8
View File
@@ -332,24 +332,57 @@ def test_runtime_starts_independently_of_office_engine(
assert runtime.bundled_runtime_path() == executable
@pytest.mark.parametrize("function,flag", [
(runtime.download_office, "--download-office"),
(runtime.download_primary_runtime, "--download-primary-runtime"),
@pytest.mark.parametrize("function,resource", [
(runtime.download_office, "office"),
(runtime.download_primary_runtime, "primary"),
])
def test_explicit_download_uses_runtime_without_a_harness_home(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, function, flag: str,
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, function, resource: str,
) -> None:
root = tmp_path / "中文缓存"
root.mkdir()
_resource_sidecars(root / "deepseek-harness-sdk-runtime-linux-x64")
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
script = tmp_path / "download.py"
destination = tmp_path / "downloaded"
script.write_text("import json,sys\n" + f"assert sys.argv[1] == {flag!r}\nprint(json.dumps({str(destination)!r}))\n")
destination = root / "linux-x64/primary-runtime" if resource == "primary" else root
script.write_text("import json,os,sys\n" + f"assert os.environ['DSH_RUNTIME_DOWNLOAD'] == {resource!r}\n"
+ f"sys.stdout.buffer.write(json.dumps({str(destination)!r}, ensure_ascii=False).encode('utf-8'))\n", encoding="utf-8")
monkeypatch.delenv("DSH_HOME", raising=False)
monkeypatch.setattr(runtime, "resolve_bundled_launch_args", lambda: (sys.executable, str(script)))
assert function() == destination
script.write_text("import sys\nprint('checksum mismatch', file=sys.stderr)\nsys.exit(2)\n")
with pytest.raises(RuntimeError, match="checksum mismatch"):
script.write_text("import sys\nsys.stderr.buffer.write('下载失败'.encode('utf-8'))\nsys.exit(2)\n", encoding="utf-8")
with pytest.raises(RuntimeError, match="下载失败"):
function()
@pytest.mark.skipif(os.name == "nt", reason="LC_ALL=C is a POSIX locale fixture")
def test_download_decodes_utf8_in_an_ascii_locale(tmp_path: Path) -> None:
script = tmp_path / "download.py"
destination = tmp_path / "中文缓存"
script.write_text("import sys\nsys.stdout.buffer.write(" + json.dumps(str(destination), ensure_ascii=False).encode('utf-8').__repr__() + ")\n", encoding="utf-8")
code = ("import sys,locale,deepseek_harness_runtime as r; "
"assert locale.getpreferredencoding(False).lower() not in ('utf-8','utf8'); "
f"r.resolve_bundled_launch_args=lambda:(sys.executable,{str(script)!r}); "
f"assert str(r.download_office())=={str(destination)!r}")
result = subprocess.run([sys.executable, "-c", code], capture_output=True, encoding="utf-8", env={
**os.environ, "LC_ALL": "C", "PYTHONUTF8": "0", "PYTHONCOERCECLOCALE": "0",
"PYTHONPATH": str(Path(runtime.__file__).resolve().parents[1]),
}, timeout=30)
assert result.returncode == 0, result.stderr
@pytest.mark.parametrize("missing", ["primary-runtime/runtime.json", "primary-runtime/dependencies/python/bin/python3",
"primary-runtime/dependencies/node/bin/node", "office-skills/scripts/check_office.py"])
def test_primary_download_rejects_an_incomplete_payload(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, missing: str) -> None:
_resource_sidecars(tmp_path / "deepseek-harness-sdk-runtime-linux-x64")
root = tmp_path / "linux-x64"
(root / missing).unlink()
monkeypatch.setattr(runtime, "_download", lambda resource: root / "primary-runtime")
monkeypatch.setattr(runtime, "_current_platform_tag", lambda: "linux-x64")
with pytest.raises(FileNotFoundError):
runtime.download_primary_runtime()
def test_office_resolution_requires_explicit_download(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
from deepseek_harness_runtime._resources import office_launch_args
@@ -366,4 +399,32 @@ def test_office_resolution_requires_explicit_download(tmp_path: Path, monkeypatc
for path in (node, cli):
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
with pytest.raises(FileNotFoundError, match="incomplete"):
office_launch_args(tmp_path / "linux-x64", "linux-x64")
(office / "complete").write_text("wrong", encoding="utf-8")
with pytest.raises(ValueError, match="identity mismatch"):
office_launch_args(tmp_path / "linux-x64", "linux-x64")
(office / "complete").write_text("a" * 64, encoding="utf-8")
assert office_launch_args(tmp_path / "linux-x64", "linux-x64") == (str(node), str(cli))
(office / "complete").unlink()
monkeypatch.setenv("DSH_OFFICE_SIDECAR", str(office))
assert office_launch_args(tmp_path / "linux-x64", "linux-x64") == (str(node), str(cli))
@pytest.mark.parametrize("invalid", ["target", "identity", "office", "pnpm", "skills"])
def test_download_manifest_rejects_invalid_wheel_resources(tmp_path: Path, invalid: str) -> None:
from deepseek_harness_runtime._resources import validate_downloads
_resource_sidecars(tmp_path / "deepseek-harness-sdk-runtime-linux-x64")
root = tmp_path / "linux-x64"
path = root / "downloads.json"
manifest = json.loads(path.read_text(encoding="utf-8"))
if invalid == "skills":
(root / "office-skills/scripts/check_office.py").unlink()
with pytest.raises(FileNotFoundError, match="Office skill"):
validate_downloads(root, "linux-x64")
else:
manifest[invalid] = {"target": "win-x64", "identity": "z" * 64, "office": [], "pnpm": []}[invalid]
path.write_text(json.dumps(manifest), encoding="utf-8")
with pytest.raises(ValueError, match="invalid runtime download manifest"):
validate_downloads(root, "linux-x64")