From 051434b388fc6c0fc0ef5551ce7a012484649b7a Mon Sep 17 00:00:00 2001 From: "KOKO\\Mimi" Date: Thu, 30 Jul 2026 00:02:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(solver-bootstrap):=20step=200=20=E2=80=94?= =?UTF-8?q?=20harness-self-tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/harness/test_config.py | 68 ++++++++++++++++++++++++++++++ tests/harness/test_discovery.py | 30 +++++++++++++ tests/harness/test_process.py | 74 +++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 tests/harness/test_config.py create mode 100644 tests/harness/test_discovery.py create mode 100644 tests/harness/test_process.py diff --git a/tests/harness/test_config.py b/tests/harness/test_config.py new file mode 100644 index 0000000..98cec4d --- /dev/null +++ b/tests/harness/test_config.py @@ -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) diff --git a/tests/harness/test_discovery.py b/tests/harness/test_discovery.py new file mode 100644 index 0000000..281e11d --- /dev/null +++ b/tests/harness/test_discovery.py @@ -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)) diff --git a/tests/harness/test_process.py b/tests/harness/test_process.py new file mode 100644 index 0000000..f7e9ec4 --- /dev/null +++ b/tests/harness/test_process.py @@ -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, + }, + ) + ]