mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
152 lines
6.3 KiB
Python
152 lines
6.3 KiB
Python
"""Evaluate the actual SCons graph with fake nodes; never execute compilers."""
|
|
|
|
import ctypes.util
|
|
import shlex
|
|
from pathlib import Path
|
|
import sys
|
|
from types import ModuleType, SimpleNamespace
|
|
from unittest.mock import MagicMock, Mock
|
|
|
|
import pytest
|
|
|
|
|
|
SOURCE = Path(__file__).resolve().parents[3] / "selfdrive/modeld/SConscript"
|
|
EXPECTED_TARGETS = {
|
|
"dmonitoring_model_tinygrad.pkl",
|
|
"driving_warp_1344x760_tinygrad.pkl", "driving_warp_1928x1208_tinygrad.pkl",
|
|
"dm_warp_1344x760_tinygrad.pkl", "dm_warp_1928x1208_tinygrad.pkl",
|
|
}
|
|
|
|
|
|
class Returned(Exception):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def graph(monkeypatch, tmp_path):
|
|
script = ModuleType("SCons.Script")
|
|
script.Action = lambda command, description: command
|
|
script.Value = lambda value: value
|
|
monkeypatch.setitem(sys.modules, script.__name__, script)
|
|
helpers = ModuleType("openpilot.selfdrive.modeld.helpers")
|
|
helpers.chestnut_present = Mock(return_value=False)
|
|
monkeypatch.setitem(sys.modules, helpers.__name__, helpers)
|
|
monkeypatch.delenv("COMMA_LAPTOP_CROSS_BUILD", raising=False)
|
|
env = MagicMock()
|
|
build_env = {"ENV": {}}
|
|
env.__getitem__.side_effect = build_env.__getitem__
|
|
env.Clone.return_value = env
|
|
|
|
def node(path):
|
|
relative = path.removeprefix("#")
|
|
return SimpleNamespace(abspath=str(tmp_path / relative), relpath=relative)
|
|
|
|
env.Dir.side_effect = node
|
|
for compiler in ("tinygrad_repo", "tinygrad_model_runtime/legacy9d/tinygrad_repo"):
|
|
marker = tmp_path / compiler / "compiler_dependency.py"
|
|
marker.parent.mkdir(parents=True)
|
|
marker.write_text("# compiler dependency witness\n")
|
|
|
|
def stop():
|
|
raise Returned()
|
|
|
|
def evaluate(arch):
|
|
scope = {"env": env, "arch": arch, "Import": Mock(), "Return": stop, "Dir": node, "File": node}
|
|
try:
|
|
exec(compile(SOURCE.read_text(), str(SOURCE), "exec"), scope)
|
|
except Returned:
|
|
pass
|
|
return scope
|
|
|
|
return evaluate, env, helpers.chestnut_present
|
|
|
|
|
|
def test_missing_llvm_pc_skips_graph_before_probe_or_compilation(graph, monkeypatch, capsys):
|
|
evaluate, env, present = graph
|
|
llvm = Mock(return_value=None)
|
|
monkeypatch.setattr(ctypes.util, "find_library", llvm)
|
|
evaluate("aarch64")
|
|
llvm.assert_called_once_with("LLVM")
|
|
env.Clone.assert_not_called()
|
|
env.Command.assert_not_called()
|
|
present.assert_not_called()
|
|
assert "Install LLVM to compile models" in capsys.readouterr().out
|
|
|
|
|
|
@pytest.mark.parametrize("arch,llvm,backend", [
|
|
("aarch64", "libLLVM.so", "DEV=CPU:LLVM"),
|
|
("Darwin", None, "DEV=METAL HCQ_RUNTIME_DEV=CPU:CLANG JIT=2"),
|
|
("comma_arm64", None, "DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"),
|
|
])
|
|
def test_supported_backend_retains_models_and_camera_warp_graph(graph, monkeypatch, arch, llvm, backend):
|
|
evaluate, env, present = graph
|
|
library = Mock(return_value=llvm)
|
|
monkeypatch.setattr(ctypes.util, "find_library", library)
|
|
evaluate(arch)
|
|
assert library.call_count == (1 if arch == "aarch64" else 0)
|
|
present.assert_not_called()
|
|
calls = env.Command.call_args_list
|
|
assert len(calls) == 5
|
|
targets = [Path(call.args[0]).name for call in calls]
|
|
assert set(targets) == EXPECTED_TARGETS
|
|
assert targets[0] == "dmonitoring_model_tinygrad.pkl"
|
|
assert "driving_tinygrad.pkl" not in targets
|
|
assert sum(name.startswith("driving_warp_") for name in targets) == 2
|
|
assert sum(name.startswith("dm_warp_") for name in targets) == 2
|
|
compiler = "tinygrad_model_runtime/legacy9d/tinygrad_repo" if arch == "comma_arm64" else "tinygrad_repo"
|
|
compiler_root = Path(calls[0].args[0]).parent.parent / compiler
|
|
# Commands and their dependency Values must describe the same selected compiler.
|
|
# Token checks retain every required flag without requiring adjacent assignments.
|
|
for call in calls:
|
|
target, dependencies, command = call.args
|
|
tokens = shlex.split(command)
|
|
assert set(shlex.split(backend)) <= set(tokens)
|
|
assert str(compiler_root / "examples/openpilot" / ("compile_onnx.py" if target.endswith("dmonitoring_model_tinygrad.pkl")
|
|
else "compile_warp.py")) in tokens
|
|
assert "#" + str(Path(compiler) / "compiler_dependency.py") in dependencies
|
|
assert command in dependencies
|
|
if "_warp_" in target:
|
|
warp_dependency = "openpilot/system/camerad/cameras/nv12_info.py"
|
|
assert any(getattr(source, "relpath", None) == warp_dependency and
|
|
getattr(source, "abspath", None) == str(Path(target).parent.parent / warp_dependency)
|
|
for source in dependencies)
|
|
assert "--frame" in tokens and "--warp-to" in tokens
|
|
dimensions = Path(target).name.split("_warp_", 1)[1].split("_tinygrad.pkl", 1)[0]
|
|
assert tokens[tokens.index("--frame") + 1].split(",")[:2] == dimensions.split("x")
|
|
assert tokens[tokens.index("--output") + 1] == target
|
|
assert tokens[tokens.index("--layout") + 1] == ("luma" if Path(target).name.startswith("dm_") else "yuv420")
|
|
else:
|
|
assert str(Path(target).parent / "dmonitoring_model.onnx") in dependencies
|
|
env.PrependENVPath.assert_called_once_with("PYTHONPATH", str(compiler_root))
|
|
if arch == "comma_arm64":
|
|
assert env["ENV"]["PYTHONDONTWRITEBYTECODE"] == "1"
|
|
assert env["ENV"]["CACHEDB"].endswith("/9d0446a4ba8a532c8b674fb6ad795af015cd9dcf/tinygrad.db")
|
|
assert env["ENV"]["XDG_CACHE_HOME"].endswith("/9d0446a4ba8a532c8b674fb6ad795af015cd9dcf/xdg")
|
|
else:
|
|
assert env["ENV"] == {}
|
|
env.Execute.assert_not_called()
|
|
|
|
|
|
def test_llvm_probe_error_does_not_schedule_compilation(graph, monkeypatch):
|
|
evaluate, env, present = graph
|
|
monkeypatch.setattr(ctypes.util, "find_library", Mock(side_effect=OSError("probe failed")))
|
|
with pytest.raises(OSError, match="probe failed"):
|
|
evaluate("aarch64")
|
|
env.Command.assert_not_called()
|
|
present.assert_not_called()
|
|
|
|
|
|
def test_build_does_not_probe_chestnut_or_regenerate_shipped_big_artifacts(graph, monkeypatch):
|
|
evaluate, env, present = graph
|
|
present.return_value = True
|
|
library = Mock()
|
|
monkeypatch.setattr(ctypes.util, "find_library", library)
|
|
evaluate("Darwin")
|
|
library.assert_not_called()
|
|
present.assert_not_called()
|
|
assert env.Command.call_count == 5
|
|
assert {Path(call.args[0]).name for call in env.Command.call_args_list} == EXPECTED_TARGETS
|
|
assert not any(Path(call.args[0]).name.startswith("big_driving_") for call in env.Command.call_args_list)
|
|
env.SideEffect.assert_not_called()
|
|
env.Execute.assert_not_called()
|