feat(solver-bootstrap): step 0 — harness-self-tests
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from scripts.msvc_harness.config import ConfigError, load_config
|
||||||
|
|
||||||
|
|
||||||
|
def _write_config(root, data):
|
||||||
|
config_dir = root / ".harness"
|
||||||
|
config_dir.mkdir()
|
||||||
|
(config_dir / "config.json").write_text(
|
||||||
|
json.dumps(data),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_config_uses_defaults(tmp_path):
|
||||||
|
config = load_config(tmp_path)
|
||||||
|
|
||||||
|
assert config.version == 1
|
||||||
|
assert config.project_type == "auto"
|
||||||
|
assert config.cmake.source_dir == tmp_path.resolve()
|
||||||
|
assert config.cmake.binary_dir is None
|
||||||
|
assert config.tdd.test_roots == (
|
||||||
|
(tmp_path / "tests").resolve(),
|
||||||
|
(tmp_path / "test").resolve(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_config_rejects_path_outside_repository(tmp_path):
|
||||||
|
root = tmp_path / "repo"
|
||||||
|
root.mkdir()
|
||||||
|
_write_config(
|
||||||
|
root,
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"cmake": {
|
||||||
|
"sourceDir": "../outside",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigError, match="outside the repository"):
|
||||||
|
load_config(root)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"missing_field",
|
||||||
|
["binaryDir", "configurePreset", "buildPreset", "testPreset"],
|
||||||
|
)
|
||||||
|
def test_load_config_requires_all_cmake_preset_fields(tmp_path, missing_field):
|
||||||
|
cmake = {
|
||||||
|
"binaryDir": "out/build/windows-debug",
|
||||||
|
"configurePreset": "windows-debug",
|
||||||
|
"buildPreset": "windows-debug",
|
||||||
|
"testPreset": "windows-debug",
|
||||||
|
}
|
||||||
|
del cmake[missing_field]
|
||||||
|
_write_config(
|
||||||
|
tmp_path,
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"cmake": cmake,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ConfigError, match="must be specified together"):
|
||||||
|
load_config(tmp_path)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from scripts.msvc_harness.config import load_config
|
||||||
|
from scripts.msvc_harness.discovery import DiscoveryError, discover_project
|
||||||
|
from scripts.msvc_harness.models import ProjectKind
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_project_skips_empty_repository(tmp_path):
|
||||||
|
result = discover_project(tmp_path, load_config(tmp_path))
|
||||||
|
|
||||||
|
assert result.selection is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_project_prefers_cmake(tmp_path):
|
||||||
|
cmake_lists = tmp_path / "CMakeLists.txt"
|
||||||
|
cmake_lists.write_text("cmake_minimum_required(VERSION 3.25)\n", encoding="utf-8")
|
||||||
|
(tmp_path / "fallback.sln").write_text("", encoding="utf-8")
|
||||||
|
|
||||||
|
result = discover_project(tmp_path, load_config(tmp_path))
|
||||||
|
|
||||||
|
assert result.selection is not None
|
||||||
|
assert result.selection.kind is ProjectKind.CMAKE
|
||||||
|
assert result.selection.project_file == cmake_lists
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_project_rejects_orphan_cpp_project(tmp_path):
|
||||||
|
(tmp_path / "orphan.cpp").write_text("int value = 0;\n", encoding="utf-8")
|
||||||
|
|
||||||
|
with pytest.raises(DiscoveryError, match="C/C\\+\\+ files exist"):
|
||||||
|
discover_project(tmp_path, load_config(tmp_path))
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from scripts.msvc_harness.models import (
|
||||||
|
CommandSpec,
|
||||||
|
ProjectKind,
|
||||||
|
ResultCheck,
|
||||||
|
ResultCheckKind,
|
||||||
|
ValidationPlan,
|
||||||
|
ValidationStep,
|
||||||
|
)
|
||||||
|
from scripts.msvc_harness.process import ValidationFailure, execute_plan
|
||||||
|
|
||||||
|
|
||||||
|
def _ctest_plan(root):
|
||||||
|
command = CommandSpec(
|
||||||
|
("ctest", "--show-only=json-v1"),
|
||||||
|
root,
|
||||||
|
"CTest discovery",
|
||||||
|
)
|
||||||
|
check = ResultCheck(ResultCheckKind.CTEST_HAS_TESTS)
|
||||||
|
return ValidationPlan(
|
||||||
|
ProjectKind.CMAKE,
|
||||||
|
(ValidationStep(command, (check,)),),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_plan_rejects_ctest_json_with_no_tests(tmp_path):
|
||||||
|
def fake_run(*args, **kwargs):
|
||||||
|
return SimpleNamespace(returncode=0, stdout='{"tests": []}', stderr="")
|
||||||
|
|
||||||
|
with pytest.raises(ValidationFailure, match="CTest discovered no tests"):
|
||||||
|
execute_plan(
|
||||||
|
_ctest_plan(tmp_path),
|
||||||
|
tmp_path,
|
||||||
|
deadline=10,
|
||||||
|
run=fake_run,
|
||||||
|
clock=lambda: 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_plan_accepts_ctest_json_with_tests_without_shell(tmp_path):
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_run(*args, **kwargs):
|
||||||
|
calls.append((args, kwargs))
|
||||||
|
return SimpleNamespace(
|
||||||
|
returncode=0,
|
||||||
|
stdout='{"tests": [{"name": "unit"}]}',
|
||||||
|
stderr="",
|
||||||
|
)
|
||||||
|
|
||||||
|
results = execute_plan(
|
||||||
|
_ctest_plan(tmp_path),
|
||||||
|
tmp_path,
|
||||||
|
deadline=10,
|
||||||
|
run=fake_run,
|
||||||
|
clock=lambda: 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(results) == 1
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
(["ctest", "--show-only=json-v1"],),
|
||||||
|
{
|
||||||
|
"cwd": tmp_path.resolve(),
|
||||||
|
"env": None,
|
||||||
|
"capture_output": True,
|
||||||
|
"shell": False,
|
||||||
|
"timeout": 10,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user