test: Add RHI integration test framework
Add integration tests for RHI module: - Add tests/RHI/integration/ directory with CMakeLists.txt - Add RHIIntegrationFixture for shared test utilities - Add minimal integration test (window creation, basic rendering) - Add compare_ppm.py for image comparison - Add run_integration_test.py test runner script These integration tests verify the complete rendering pipeline by comparing rendered output against ground truth PPM files.
This commit is contained in:
7
tests/RHI/integration/CMakeLists.txt
Normal file
7
tests/RHI/integration/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_subdirectory(minimal)
|
||||
75
tests/RHI/integration/compare_ppm.py
Normal file
75
tests/RHI/integration/compare_ppm.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def read_ppm(filename):
|
||||
with open(filename, "rb") as f:
|
||||
header = f.readline()
|
||||
if header != b"P6\n":
|
||||
raise ValueError(f"Not a P6 PPM file: {filename}")
|
||||
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line.startswith(b"#"):
|
||||
break
|
||||
|
||||
dims = line.split()
|
||||
width, height = int(dims[0]), int(dims[1])
|
||||
|
||||
line = f.readline()
|
||||
maxval = int(line.strip())
|
||||
|
||||
data = f.read()
|
||||
return width, height, data
|
||||
|
||||
|
||||
def compare_ppm(file1, file2, threshold):
|
||||
w1, h1, d1 = read_ppm(file1)
|
||||
w2, h2, d2 = read_ppm(file2)
|
||||
|
||||
if w1 != w2 or h1 != h2:
|
||||
print(f"ERROR: Size mismatch - {file1}: {w1}x{h1}, {file2}: {w2}x{h2}")
|
||||
return False
|
||||
|
||||
total_pixels = w1 * h1
|
||||
diff_count = 0
|
||||
|
||||
for i in range(len(d1)):
|
||||
diff = abs(d1[i] - d2[i])
|
||||
if diff > threshold:
|
||||
diff_count += 1
|
||||
|
||||
diff_percent = (diff_count / (total_pixels * 3)) * 100
|
||||
|
||||
print(f"Image 1: {file1} ({w1}x{h1})")
|
||||
print(f"Image 2: {file2} ({w2}x{h2})")
|
||||
print(f"Threshold: {threshold}")
|
||||
print(f"Different pixels: {diff_count} / {total_pixels * 3} ({diff_percent:.2f}%)")
|
||||
|
||||
if diff_percent <= 1.0:
|
||||
print("PASS: Images match!")
|
||||
return True
|
||||
else:
|
||||
print("FAIL: Images differ!")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: python compare_ppm.py <file1.ppm> <file2.ppm> <threshold>")
|
||||
sys.exit(1)
|
||||
|
||||
file1 = sys.argv[1]
|
||||
file2 = sys.argv[2]
|
||||
threshold = int(sys.argv[3])
|
||||
|
||||
if not os.path.exists(file1):
|
||||
print(f"ERROR: File not found: {file1}")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(file2):
|
||||
print(f"ERROR: File not found: {file2}")
|
||||
sys.exit(1)
|
||||
|
||||
result = compare_ppm(file1, file2, threshold)
|
||||
sys.exit(0 if result else 1)
|
||||
188
tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
Normal file
188
tests/RHI/integration/fixtures/RHIIntegrationFixture.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include "RHIIntegrationFixture.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "XCEngine/RHI/D3D12/D3D12Device.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12SwapChain.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12Texture.h"
|
||||
#include "XCEngine/RHI/D3D12/D3D12CommandList.h"
|
||||
#include "XCEngine/RHI/RHIFence.h"
|
||||
#include "XCEngine/RHI/RHIScreenshot.h"
|
||||
#include "XCEngine/RHI/RHIEnums.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
namespace Integration {
|
||||
|
||||
void RHIIntegrationFixture::SetUp() {
|
||||
WNDCLASSEXW wc = {};
|
||||
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = DefWindowProcW;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.lpszClassName = L"XCEngine_RHI_Integration_Test";
|
||||
RegisterClassExW(&wc);
|
||||
|
||||
const int width = 1280;
|
||||
const int height = 720;
|
||||
RECT rect = { 0, 0, width, height };
|
||||
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
mWindow = CreateWindowExW(
|
||||
0,
|
||||
L"XCEngine_RHI_Integration_Test",
|
||||
L"RHI Integration Test",
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
NULL, NULL, GetModuleHandle(nullptr), NULL
|
||||
);
|
||||
|
||||
ASSERT_NE(mWindow, nullptr);
|
||||
|
||||
mDevice = RHIFactory::CreateRHIDevice(GetParam());
|
||||
ASSERT_NE(mDevice, nullptr);
|
||||
|
||||
bool initResult = false;
|
||||
if (GetParam() == RHIType::D3D12) {
|
||||
RHIDeviceDesc desc = {};
|
||||
desc.enableDebugLayer = false;
|
||||
desc.enableGPUValidation = false;
|
||||
initResult = mDevice->Initialize(desc);
|
||||
} else if (GetParam() == RHIType::OpenGL) {
|
||||
auto* oglDevice = static_cast<OpenGLDevice*>(mDevice);
|
||||
initResult = oglDevice->InitializeWithExistingWindow(mWindow);
|
||||
}
|
||||
ASSERT_TRUE(initResult);
|
||||
|
||||
SwapChainDesc swapDesc = {};
|
||||
swapDesc.windowHandle = mWindow;
|
||||
swapDesc.width = width;
|
||||
swapDesc.height = height;
|
||||
swapDesc.bufferCount = 2;
|
||||
mSwapChain = mDevice->CreateSwapChain(swapDesc);
|
||||
ASSERT_NE(mSwapChain, nullptr);
|
||||
|
||||
CommandQueueDesc queueDesc = {};
|
||||
queueDesc.queueType = static_cast<uint32_t>(CommandQueueType::Direct);
|
||||
mCommandQueue = mDevice->CreateCommandQueue(queueDesc);
|
||||
ASSERT_NE(mCommandQueue, nullptr);
|
||||
|
||||
CommandListDesc cmdDesc = {};
|
||||
cmdDesc.commandListType = static_cast<uint32_t>(CommandQueueType::Direct);
|
||||
mCommandList = mDevice->CreateCommandList(cmdDesc);
|
||||
ASSERT_NE(mCommandList, nullptr);
|
||||
|
||||
mScreenshot = RHIScreenshot::Create(GetParam());
|
||||
ASSERT_NE(mScreenshot, nullptr);
|
||||
|
||||
ShowWindow(mWindow, SW_SHOW);
|
||||
UpdateWindow(mWindow);
|
||||
}
|
||||
|
||||
void RHIIntegrationFixture::BeginRender() {
|
||||
mCurrentBackBufferIndex = mSwapChain->GetCurrentBackBufferIndex();
|
||||
}
|
||||
|
||||
void RHIIntegrationFixture::EndRender() {
|
||||
}
|
||||
|
||||
void RHIIntegrationFixture::TearDown() {
|
||||
if (mScreenshot) {
|
||||
mScreenshot->Shutdown();
|
||||
delete mScreenshot;
|
||||
mScreenshot = nullptr;
|
||||
}
|
||||
|
||||
if (mCommandList) {
|
||||
mCommandList->Shutdown();
|
||||
delete mCommandList;
|
||||
mCommandList = nullptr;
|
||||
}
|
||||
|
||||
if (mCommandQueue) {
|
||||
mCommandQueue->Shutdown();
|
||||
delete mCommandQueue;
|
||||
mCommandQueue = nullptr;
|
||||
}
|
||||
|
||||
if (mSwapChain) {
|
||||
mSwapChain->Shutdown();
|
||||
delete mSwapChain;
|
||||
mSwapChain = nullptr;
|
||||
}
|
||||
|
||||
if (mDevice) {
|
||||
mDevice->Shutdown();
|
||||
delete mDevice;
|
||||
mDevice = nullptr;
|
||||
}
|
||||
|
||||
if (mWindow) {
|
||||
DestroyWindow(mWindow);
|
||||
mWindow = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RHIIntegrationFixture::WaitForGPU() {
|
||||
if (mDevice == nullptr || mCommandQueue == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetParam() == RHIType::D3D12) {
|
||||
FenceDesc fenceDesc = {};
|
||||
fenceDesc.initialValue = 0;
|
||||
fenceDesc.flags = 0;
|
||||
auto* fence = mDevice->CreateFence(fenceDesc);
|
||||
if (fence) {
|
||||
mCommandQueue->Signal(fence, 1);
|
||||
fence->Wait(1);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (fence->GetCompletedValue() >= 1) {
|
||||
break;
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
fence->Shutdown();
|
||||
delete fence;
|
||||
}
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
bool RHIIntegrationFixture::TakeScreenshot(const char* filename) {
|
||||
if (!mScreenshot || !mDevice || !mSwapChain) {
|
||||
return false;
|
||||
}
|
||||
return mScreenshot->Capture(mDevice, mSwapChain, filename);
|
||||
}
|
||||
|
||||
bool RHIIntegrationFixture::CompareWithGoldenTemplate(const char* outputPpm, const char* gtPpm, float threshold) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path exeDir = fs::current_path();
|
||||
fs::path outputPath = exeDir / outputPpm;
|
||||
fs::path gtPath = exeDir / gtPpm;
|
||||
|
||||
if (!fs::exists(outputPath)) {
|
||||
std::cerr << "Output file not found: " << outputPath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fs::exists(gtPath)) {
|
||||
std::cerr << "Golden template not found: " << gtPath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cmd = "python compare_ppm.py \"" + outputPath.string() + "\" \"" + gtPath.string() + "\" " + std::to_string(static_cast<int>(threshold));
|
||||
int result = system(cmd.c_str());
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
} // namespace Integration
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
55
tests/RHI/integration/fixtures/RHIIntegrationFixture.h
Normal file
55
tests/RHI/integration/fixtures/RHIIntegrationFixture.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include <windows.h>
|
||||
|
||||
#include "XCEngine/RHI/RHIFactory.h"
|
||||
#include "XCEngine/RHI/RHIDevice.h"
|
||||
#include "XCEngine/RHI/RHICommandQueue.h"
|
||||
#include "XCEngine/RHI/RHICommandList.h"
|
||||
#include "XCEngine/RHI/RHISwapChain.h"
|
||||
#include "XCEngine/RHI/RHIScreenshot.h"
|
||||
#include "XCEngine/RHI/RHIEnums.h"
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLDevice.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
namespace Integration {
|
||||
|
||||
class RHIIntegrationFixture : public ::testing::TestWithParam<RHIType> {
|
||||
protected:
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
void WaitForGPU();
|
||||
void BeginRender();
|
||||
void EndRender();
|
||||
|
||||
RHIDevice* GetDevice() { return mDevice; }
|
||||
RHISwapChain* GetSwapChain() { return mSwapChain; }
|
||||
RHICommandQueue* GetCommandQueue() { return mCommandQueue; }
|
||||
RHICommandList* GetCommandList() { return mCommandList; }
|
||||
RHIScreenshot* GetScreenshot() { return mScreenshot; }
|
||||
RHIType GetBackendType() const { return GetParam(); }
|
||||
HWND GetWindowHandle() const { return mWindow; }
|
||||
int GetCurrentBackBufferIndex() const { return mCurrentBackBufferIndex; }
|
||||
|
||||
virtual void RenderFrame() {}
|
||||
|
||||
bool TakeScreenshot(const char* filename);
|
||||
bool CompareWithGoldenTemplate(const char* outputPpm, const char* gtPpm, float threshold = 0.0f);
|
||||
|
||||
private:
|
||||
RHIDevice* mDevice = nullptr;
|
||||
RHISwapChain* mSwapChain = nullptr;
|
||||
RHICommandQueue* mCommandQueue = nullptr;
|
||||
RHICommandList* mCommandList = nullptr;
|
||||
RHIScreenshot* mScreenshot = nullptr;
|
||||
HWND mWindow = nullptr;
|
||||
int mCurrentBackBufferIndex = 0;
|
||||
};
|
||||
|
||||
} // namespace Integration
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
58
tests/RHI/integration/minimal/CMakeLists.txt
Normal file
58
tests/RHI/integration/minimal/CMakeLists.txt
Normal file
@@ -0,0 +1,58 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
project(rhi_integration_minimal)
|
||||
|
||||
set(ENGINE_ROOT_DIR ${CMAKE_SOURCE_DIR}/engine)
|
||||
set(PACKAGE_DIR ${CMAKE_SOURCE_DIR}/tests/opengl/package)
|
||||
|
||||
get_filename_component(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
|
||||
|
||||
add_executable(rhi_integration_minimal
|
||||
main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../fixtures/RHIIntegrationFixture.cpp
|
||||
${PACKAGE_DIR}/src/glad.c
|
||||
)
|
||||
|
||||
target_include_directories(rhi_integration_minimal PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../fixtures
|
||||
${ENGINE_ROOT_DIR}/include
|
||||
${PACKAGE_DIR}/include
|
||||
${PROJECT_ROOT_DIR}/engine/src
|
||||
)
|
||||
|
||||
target_link_libraries(rhi_integration_minimal PRIVATE
|
||||
d3d12
|
||||
dxgi
|
||||
d3dcompiler
|
||||
winmm
|
||||
opengl32
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
)
|
||||
|
||||
target_compile_definitions(rhi_integration_minimal PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
XCENGINE_SUPPORT_OPENGL
|
||||
)
|
||||
|
||||
add_custom_command(TARGET rhi_integration_minimal POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/integration/compare_ppm.py
|
||||
$<TARGET_FILE_DIR:rhi_integration_minimal>/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/D3D12/integration/minimal/GT.ppm
|
||||
$<TARGET_FILE_DIR:rhi_integration_minimal>/GT_D3D12.ppm
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/tests/RHI/OpenGL/integration/minimal/GT.ppm
|
||||
$<TARGET_FILE_DIR:rhi_integration_minimal>/GT_OpenGL.ppm
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${ENGINE_ROOT_DIR}/third_party/renderdoc/renderdoc.dll
|
||||
$<TARGET_FILE_DIR:rhi_integration_minimal>/
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(rhi_integration_minimal)
|
||||
73
tests/RHI/integration/minimal/main.cpp
Normal file
73
tests/RHI/integration/minimal/main.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "../fixtures/RHIIntegrationFixture.h"
|
||||
|
||||
using namespace XCEngine::RHI;
|
||||
using namespace XCEngine::RHI::Integration;
|
||||
|
||||
namespace {
|
||||
|
||||
class MinimalTest : public RHIIntegrationFixture {
|
||||
protected:
|
||||
void RenderFrame() override;
|
||||
};
|
||||
|
||||
void MinimalTest::RenderFrame() {
|
||||
RHICommandList* cmdList = GetCommandList();
|
||||
RHICommandQueue* cmdQueue = GetCommandQueue();
|
||||
|
||||
cmdList->Reset();
|
||||
|
||||
Viewport viewport = { 0.0f, 0.0f, 1280.0f, 720.0f, 0.0f, 1.0f };
|
||||
Rect scissorRect = { 0, 0, 1280, 720 };
|
||||
cmdList->SetViewport(viewport);
|
||||
cmdList->SetScissorRect(scissorRect);
|
||||
|
||||
float clearColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
|
||||
cmdList->Clear(clearColor[0], clearColor[1], clearColor[2], clearColor[3], 1);
|
||||
|
||||
cmdList->Close();
|
||||
void* cmdLists[] = { cmdList };
|
||||
cmdQueue->ExecuteCommandLists(1, cmdLists);
|
||||
}
|
||||
|
||||
TEST_P(MinimalTest, RenderClear) {
|
||||
RHICommandQueue* cmdQueue = GetCommandQueue();
|
||||
RHISwapChain* swapChain = GetSwapChain();
|
||||
const int targetFrameCount = 30;
|
||||
|
||||
for (int frameCount = 0; frameCount <= targetFrameCount; ++frameCount) {
|
||||
if (frameCount > 0) {
|
||||
cmdQueue->WaitForPreviousFrame();
|
||||
}
|
||||
|
||||
BeginRender();
|
||||
RenderFrame();
|
||||
EndRender();
|
||||
|
||||
if (frameCount >= targetFrameCount) {
|
||||
cmdQueue->WaitForIdle();
|
||||
ASSERT_TRUE(TakeScreenshot("minimal.ppm"));
|
||||
ASSERT_TRUE(CompareWithGoldenTemplate("minimal.ppm",
|
||||
(GetBackendType() == RHIType::D3D12) ? "GT_D3D12.ppm" : "GT_OpenGL.ppm",
|
||||
(GetBackendType() == RHIType::D3D12) ? 0.0f : 5.0f));
|
||||
break;
|
||||
}
|
||||
|
||||
swapChain->Present(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(D3D12, MinimalTest, ::testing::Values(RHIType::D3D12));
|
||||
INSTANTIATE_TEST_SUITE_P(OpenGL, MinimalTest, ::testing::Values(RHIType::OpenGL));
|
||||
|
||||
GTEST_API_ int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
126
tests/RHI/integration/run_integration_test.py
Normal file
126
tests/RHI/integration/run_integration_test.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
def run_integration_test(exe_path, output_ppm, gt_ppm, threshold, backend, timeout=120):
|
||||
"""
|
||||
Run a RHI 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
|
||||
backend: Backend type (D3D12 or OpenGL)
|
||||
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}")
|
||||
print(f"[Integration Test] Backend: {backend}")
|
||||
|
||||
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, backend],
|
||||
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) != 6:
|
||||
print(
|
||||
"Usage: run_integration_test.py <exe_path> <output_ppm> <gt_ppm> <threshold> <backend>"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
exe_path = sys.argv[1]
|
||||
output_ppm = sys.argv[2]
|
||||
gt_ppm = sys.argv[3]
|
||||
threshold = int(sys.argv[4])
|
||||
backend = sys.argv[5]
|
||||
|
||||
exit_code = run_integration_test(exe_path, output_ppm, gt_ppm, threshold, backend)
|
||||
sys.exit(exit_code)
|
||||
Reference in New Issue
Block a user