diff --git a/tests/RHI/D3D12/integration/CMakeLists.txt b/tests/RHI/D3D12/integration/CMakeLists.txt index 86de12dd..ee9a1c72 100644 --- a/tests/RHI/D3D12/integration/CMakeLists.txt +++ b/tests/RHI/D3D12/integration/CMakeLists.txt @@ -4,6 +4,10 @@ project(D3D12_Integration) set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/../engine) +find_package(Python3 REQUIRED) + +enable_testing() + # Minimal test - just verifies initialization and render loop add_executable(D3D12_Minimal WIN32 @@ -106,3 +110,35 @@ add_custom_command(TARGET D3D12_RenderModel POST_BUILD ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm $/GT.ppm ) + +# Copy run_integration_test.py to output directories +add_custom_command(TARGET D3D12_Minimal POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py + $/run_integration_test.py +) + +add_custom_command(TARGET D3D12_RenderModel POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py + $/run_integration_test.py +) + +# Register integration tests with CTest +add_test(NAME D3D12_Minimal_Integration + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py + $ + minimal.ppm + ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm + 5 + WORKING_DIRECTORY $ +) + +add_test(NAME D3D12_RenderModel_Integration + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_integration_test.py + $ + screenshot.ppm + ${CMAKE_CURRENT_SOURCE_DIR}/GT.ppm + 5 + WORKING_DIRECTORY $ +) diff --git a/tests/RHI/D3D12/integration/run_integration_test.py b/tests/RHI/D3D12/integration/run_integration_test.py new file mode 100644 index 00000000..6c63e978 --- /dev/null +++ b/tests/RHI/D3D12/integration/run_integration_test.py @@ -0,0 +1,124 @@ +import sys +import os +import subprocess +import time +import shutil + + +def run_integration_test(exe_path, output_ppm, gt_ppm, threshold, timeout=120): + """ + Run a D3D12 integration test and compare output with golden template. + + Args: + exe_path: Path to the test executable + output_ppm: Filename of the output screenshot + gt_ppm: Path to the golden template PPM file + threshold: Pixel difference threshold for comparison + timeout: Maximum time to wait for test completion (seconds) + + Returns: + 0 on success, non-zero on failure + """ + exe_dir = os.path.dirname(os.path.abspath(exe_path)) + output_path = os.path.join(exe_dir, output_ppm) + + print(f"[Integration Test] Starting: {exe_path}") + print(f"[Integration Test] Working directory: {exe_dir}") + print(f"[Integration Test] Expected output: {output_path}") + + if not os.path.exists(exe_path): + print(f"[Integration Test] ERROR: Executable not found: {exe_path}") + return 1 + + if not os.path.exists(gt_ppm): + print(f"[Integration Test] ERROR: Golden template not found: {gt_ppm}") + return 1 + + if os.path.exists(output_path): + print(f"[Integration Test] Removing old output: {output_path}") + os.remove(output_path) + + try: + print(f"[Integration Test] Launching process...") + start_time = time.time() + process = subprocess.Popen( + [exe_path], + cwd=exe_dir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=subprocess.CREATE_NEW_CONSOLE if os.name == "nt" else 0, + ) + + returncode = None + while time.time() - start_time < timeout: + returncode = process.poll() + if returncode is not None: + break + time.sleep(0.5) + + if returncode is None: + print(f"[Integration Test] ERROR: Process timed out after {timeout}s") + process.kill() + return 1 + + elapsed = time.time() - start_time + print( + f"[Integration Test] Process finished in {elapsed:.1f}s with exit code: {returncode}" + ) + + if returncode != 0: + print(f"[Integration Test] ERROR: Process returned non-zero exit code") + stdout, stderr = process.communicate(timeout=5) + if stdout: + print( + f"[Integration Test] STDOUT:\n{stdout.decode('utf-8', errors='replace')}" + ) + if stderr: + print( + f"[Integration Test] STDERR:\n{stderr.decode('utf-8', errors='replace')}" + ) + return 1 + + except Exception as e: + print(f"[Integration Test] ERROR: Failed to run process: {e}") + return 1 + + if not os.path.exists(output_path): + print(f"[Integration Test] ERROR: Output file not created: {output_path}") + return 1 + + print(f"[Integration Test] Running image comparison...") + script_dir = os.path.dirname(os.path.abspath(__file__)) + compare_script = os.path.join(script_dir, "compare_ppm.py") + + try: + result = subprocess.run( + [sys.executable, compare_script, output_path, gt_ppm, str(threshold)], + cwd=exe_dir, + capture_output=True, + text=True, + ) + print(result.stdout) + if result.stderr: + print(f"[Integration Test] Comparison STDERR: {result.stderr}") + return result.returncode + + except Exception as e: + print(f"[Integration Test] ERROR: Failed to run comparison: {e}") + return 1 + + +if __name__ == "__main__": + if len(sys.argv) != 5: + print( + "Usage: run_integration_test.py " + ) + sys.exit(1) + + exe_path = sys.argv[1] + output_ppm = sys.argv[2] + gt_ppm = sys.argv[3] + threshold = int(sys.argv[4]) + + exit_code = run_integration_test(exe_path, output_ppm, gt_ppm, threshold) + sys.exit(exit_code)