"""Test-file generation for migrated C++ code. Strategy (B + C combined, no pre-existing test infrastructure): - QTest for Qt-heavy translation units (QObject / signals / slots) - gtest for pure-logic / non-UI code - Smoke CTest for every file (build + link + default-construction check) Generated files are written to ``{worktree_root}/tests_generated/`` and registered in that directory's CMakeLists.txt (idempotent via a guard comment). """ from __future__ import annotations import asyncio from pathlib import Path import httpx from migration_pipeline.api_client import call_api # --------------------------------------------------------------------------- # System prompts # --------------------------------------------------------------------------- _QTEST_SYSTEM = ( "Du bist ein QA-Ingenieur für Qt6/C++20. " "Schreibe einen eigenständigen QTest Unit-Test für den folgenden migrierten Qt6-Code. " "Verwende QCOMPARE, QVERIFY und QTEST_MAIN. " "Gib NUR den C++ Quellcode zurück, ohne Markdown oder Erklärungen." ) _GTEST_SYSTEM = ( "Du bist ein QA-Ingenieur für C++20. " "Schreibe einen eigenständigen Google Test (gtest) Unit-Test für den folgenden C++ Code. " "Gib NUR den C++ Quellcode zurück, ohne Markdown oder Erklärungen." ) _SMOKE_SYSTEM = ( "Du bist ein QA-Ingenieur. " "Schreibe einen minimalen Smoke-Test mit Google Test, der sicherstellt, dass die Datei " "kompiliert, linkt und die Default-Konstruktion der Hauptklasse nicht crasht. " "Gib NUR den C++ Quellcode zurück, ohne Markdown oder Erklärungen." ) # --------------------------------------------------------------------------- # Qt-content heuristic # --------------------------------------------------------------------------- _QT_MARKERS = ( "QObject", "QWidget", "QApplication", "QMainWindow", "emit ", "signals:", "slots:", "Q_OBJECT", "Q_EMIT", ) def _is_qt_heavy(code: str) -> bool: return any(marker in code for marker in _QT_MARKERS) # --------------------------------------------------------------------------- # Public API # --------------------------------------------------------------------------- async def generate_tests( client: httpx.AsyncClient, model: str, source_file_path: str, migrated_code: str, worktree_root: Path, track_id: str, ) -> str: """Generate unit + smoke tests for *migrated_code*. Returns the CTest test-name prefix (e.g. ``test_track_a_conservative_MyClass``). Both the unit test and smoke test are registered under that prefix. """ base_name = Path(source_file_path).stem test_prefix = f"test_{track_id}_{base_name}" tests_dir = worktree_root / "tests_generated" tests_dir.mkdir(parents=True, exist_ok=True) use_qtest = _is_qt_heavy(migrated_code) unit_system = _QTEST_SYSTEM if use_qtest else _GTEST_SYSTEM framework = "Qt6::Core Qt6::Test" if use_qtest else "GTest::gtest_main" # Generate unit test and smoke test in parallel. unit_code, smoke_code = await asyncio.gather( call_api( client, model, unit_system, f"Erstelle einen Unit-Test für folgenden migrierten Qt6-Code:\n\n{migrated_code}", ), call_api( client, model, _SMOKE_SYSTEM, f"Erstelle einen Smoke-Test für:\n\n{migrated_code}", ), ) unit_file = tests_dir / f"{test_prefix}_unit.cpp" smoke_file = tests_dir / f"{test_prefix}_smoke.cpp" unit_file.write_text(unit_code) smoke_file.write_text(smoke_code) _register_tests_in_cmake( tests_dir, test_prefix, unit_file.name, smoke_file.name, framework, ) return test_prefix # --------------------------------------------------------------------------- # CMakeLists.txt registration (idempotent) # --------------------------------------------------------------------------- _GUARD_PREFIX = "# MIGRATION_PIPELINE:" def _register_tests_in_cmake( tests_dir: Path, test_prefix: str, unit_filename: str, smoke_filename: str, framework_libs: str, ) -> None: cmake_path = tests_dir / "CMakeLists.txt" guard = f"{_GUARD_PREFIX}{test_prefix}" # Read existing content to avoid duplicate registrations. existing = cmake_path.read_text() if cmake_path.exists() else "" if guard in existing: return # already registered entry = ( f"\n{guard}\n" f"add_executable({test_prefix}_unit {unit_filename})\n" f"target_link_libraries({test_prefix}_unit PRIVATE {framework_libs})\n" f"add_test(NAME {test_prefix}_unit COMMAND {test_prefix}_unit)\n" f"\nadd_executable({test_prefix}_smoke {smoke_filename})\n" f"target_link_libraries({test_prefix}_smoke PRIVATE GTest::gtest_main)\n" f"add_test(NAME {test_prefix}_smoke COMMAND {test_prefix}_smoke)\n" ) with cmake_path.open("a") as fh: fh.write(entry)