[Testing] Get tests running (dirty hack for linux)

This commit is contained in:
Herman S.
2025-10-14 14:19:12 +09:00
parent 14c2814654
commit 09a15fbedc
13 changed files with 209 additions and 46 deletions

View File

@@ -91,7 +91,7 @@ DEFINE_bool(mount_scratch, false, "Enable scratch mount", "Storage");
DEFINE_bool(mount_cache, true, "Enable cache mount", "Storage");
UPDATE_from_bool(mount_cache, 2024, 8, 31, 20, false);
DEFINE_bool(force_mount_devkit, false, "Force devkit mount", "Storage");
DECLARE_bool(force_mount_devkit);
DEFINE_transient_path(target, "",
"Specifies the target .xex or .iso to execute.",

View File

@@ -36,7 +36,9 @@ namespace amd64 {
static uint64_t g_feature_flags = 0U;
static bool g_did_initialize_feature_flags = false;
uint64_t GetFeatureFlags() {
xenia_assert(g_did_initialize_feature_flags);
if (!g_did_initialize_feature_flags) {
InitFeatureFlags();
}
return g_feature_flags;
}
XE_COLD

View File

@@ -1185,7 +1185,12 @@ static bool IsPossibleMMIOInstruction(X64Emitter& e, const hir::Instr* i) {
return false;
}
auto flags = e.GuestModule()->GetInstructionAddressFlags(guestaddr);
auto guest_module = e.GuestModule();
if (!guest_module) {
return false;
}
auto flags = guest_module->GetInstructionAddressFlags(guestaddr);
return flags && flags->accessed_mmio;
}

View File

@@ -3,7 +3,7 @@ test_vmaddfp_1:
# 1.0, 1.5, 1.1, 1.9
vmaddfp v3, v4, v4, v4
blr
#_ REGISTER_OUT v3 [40000000, 40700000, 4013d70a, 40b051eb]
#_ REGISTER_OUT v3 [40000000, 40700000, 4013d70b, 40b051eb]
#_ REGISTER_OUT v4 [3f800000, 3fc00000, 3f8ccccd, 3ff33333]
# 2.0, 3.75, 2.31, 5.51
# 40b051eb is actually 5.50999975, not 5.51?

View File

@@ -27,6 +27,9 @@
#if XE_COMPILER_MSVC
#include "xenia/base/platform_win.h"
#else
#include <sys/wait.h>
#include <unistd.h>
#endif // XE_COMPILER_MSVC
DEFINE_path(test_path, "src/xenia/cpu/ppc/testing/",
@@ -191,6 +194,10 @@ class TestRunner {
}
bool Setup(TestSuite& suite) {
// Reset thread state first so it can properly deinitialize with the
// existing processor before we destroy the processor.
thread_state_.reset();
// Reset memory.
memory_->Reset();
@@ -245,14 +252,17 @@ class TestRunner {
bool Run(TestCase& test_case) {
// Setup test state from annotations.
if (!SetupTestState(test_case)) {
XELOGE("Test setup failed");
fprintf(stderr, " [%s] Test setup failed\n", test_case.name.c_str());
fflush(stderr);
return false;
}
// Execute test.
auto fn = processor_->ResolveFunction(test_case.address);
if (!fn) {
XELOGE("Entry function not found");
fprintf(stderr, " [%s] Entry function not found\n",
test_case.name.c_str());
fflush(stderr);
return false;
}
@@ -316,9 +326,13 @@ class TestRunner {
if (!ppc_context->CompareRegWithString(
reg_name.c_str(), reg_value.c_str(), actual_value)) {
any_failed = true;
XELOGE("Register {} assert failed:\n", reg_name);
XELOGE(" Expected: {} == {}\n", reg_name, reg_value);
XELOGE(" Actual: {} == {}\n", reg_name, actual_value);
fprintf(stderr, " [%s] Register %s assert failed:\n",
test_case.name.c_str(), reg_name.c_str());
fprintf(stderr, " Expected: %s == %s\n", reg_name.c_str(),
reg_value.c_str());
fprintf(stderr, " Actual: %s == %s\n", reg_name.c_str(),
actual_value.c_str());
fflush(stderr);
}
} else if (it.first == "MEMORY_OUT") {
size_t space_pos = it.second.find(" ");
@@ -355,9 +369,11 @@ class TestRunner {
++p;
}
if (failed) {
XELOGE("Memory {} assert failed:\n", address_str);
XELOGE(" Expected:{}\n", expecteds.to_string());
XELOGE(" Actual:{}\n", actuals.to_string());
fprintf(stderr, " [%s] Memory %s assert failed:\n",
test_case.name.c_str(), address_str.c_str());
fprintf(stderr, " Expected:%s\n", expecteds.to_string().c_str());
fprintf(stderr, " Actual:%s\n", actuals.to_string().c_str());
fflush(stderr);
}
}
}
@@ -375,7 +391,11 @@ bool DiscoverTests(const std::filesystem::path& test_path,
auto file_infos = xe::filesystem::ListFiles(test_path);
for (auto& file_info : file_infos) {
if (file_info.name.extension() == ".s") {
test_files.push_back(test_path / file_info.name);
// Only include test files (instr_*.s), not helper files
auto filename = file_info.name.filename().string();
if (filename.find("instr_") == 0) {
test_files.push_back(test_path / file_info.name);
}
}
}
return true;
@@ -390,27 +410,128 @@ int filter(unsigned int code) {
}
#endif // XE_COMPILER_MSVC
#if !XE_COMPILER_MSVC
// Run test in isolated child process to catch crashes
enum class TestResult {
kPassed,
kFailed,
kCrashed,
};
TestResult RunTestInChildProcess(TestSuite& test_suite, TestCase& test_case) {
pid_t pid = fork();
if (pid == -1) {
// Fork failed
fprintf(stderr, " [%s] TEST FAILED (fork failed)\n",
test_case.name.c_str());
fflush(stderr);
return TestResult::kFailed;
}
if (pid == 0) {
// Child process - create a fresh TestRunner to avoid inherited state issues
TestRunner child_runner;
if (!child_runner.Setup(test_suite)) {
_exit(2); // Setup failure
}
if (child_runner.Run(test_case)) {
_exit(0); // Test passed
} else {
_exit(1); // Test failed
}
}
// Parent process - wait for child
int status;
pid_t result = waitpid(pid, &status, 0);
if (result == -1) {
fprintf(stderr, " [%s] TEST FAILED (waitpid failed, pid %d)\n",
test_case.name.c_str(), pid);
fflush(stderr);
return TestResult::kFailed;
}
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
if (exit_code == 0) {
// Test passed - don't print anything
return TestResult::kPassed;
} else if (exit_code == 2) {
fprintf(stderr, " [%s] FAILED SETUP (exit code %d)\n",
test_case.name.c_str(), exit_code);
fflush(stderr);
return TestResult::kFailed;
} else {
fprintf(stderr, " [%s] FAILED (exit code %d)\n", test_case.name.c_str(),
exit_code);
fflush(stderr);
return TestResult::kFailed;
}
}
if (WIFSIGNALED(status)) {
int signal = WTERMSIG(status);
const char* signal_name = "UNKNOWN";
switch (signal) {
case SIGSEGV:
signal_name = "SIGSEGV";
break;
case SIGILL:
signal_name = "SIGILL";
break;
case SIGFPE:
signal_name = "SIGFPE";
break;
case SIGBUS:
signal_name = "SIGBUS";
break;
case SIGABRT:
signal_name = "SIGABRT";
break;
}
fprintf(stderr, " [%s] CRASHED (%s)\n", test_case.name.c_str(),
signal_name);
fflush(stderr);
return TestResult::kCrashed;
}
fprintf(stderr, " [%s] FAILED (unknown reason)\n", test_case.name.c_str());
fflush(stderr);
return TestResult::kFailed;
}
#endif // !XE_COMPILER_MSVC
void ProtectedRunTest(TestSuite& test_suite, TestRunner& runner,
TestCase& test_case, int& failed_count,
int& passed_count) {
#if XE_COMPILER_MSVC
__try {
#endif // XE_COMPILER_MSVC
if (!runner.Setup(test_suite)) {
XELOGE(" TEST FAILED SETUP");
XELOGE(" [{}] TEST FAILED SETUP", test_case.name);
++failed_count;
return;
}
if (runner.Run(test_case)) {
++passed_count;
} else {
XELOGE(" TEST FAILED");
XELOGE(" [{}] TEST FAILED", test_case.name);
++failed_count;
}
#if XE_COMPILER_MSVC
} __except (filter(GetExceptionCode())) {
XELOGE(" TEST FAILED (UNSUPPORTED INSTRUCTION)");
XELOGE(" [{}] TEST FAILED (UNSUPPORTED INSTRUCTION)", test_case.name);
++failed_count;
}
#else
// Use fork to isolate crashes on POSIX systems
// Note: runner parameter is not used on POSIX
(void)runner; // Suppress unused parameter warning
TestResult result = RunTestInChildProcess(test_suite, test_case);
if (result == TestResult::kPassed) {
++passed_count;
} else {
++failed_count;
}
#endif // XE_COMPILER_MSVC
@@ -456,23 +577,27 @@ bool RunTests(const std::string_view test_name) {
}
XELOGI("{} tests loaded.", test_suites.size());
#if XE_COMPILER_MSVC
// On Windows, use a single shared test runner
TestRunner runner;
#else
// On POSIX, each test will create its own runner in a forked process
// Pass a dummy value that won't be used
TestRunner* runner_ptr = nullptr;
TestRunner& runner = *runner_ptr; // Never dereferenced on POSIX
#endif
for (auto& test_suite : test_suites) {
XELOGI("{}.s:", test_suite.name());
for (auto& test_case : test_suite.test_cases()) {
XELOGI(" - {}", test_case.name);
ProtectedRunTest(test_suite, runner, test_case, failed_count,
passed_count);
}
XELOGI("");
}
XELOGI("");
XELOGI("Total tests: {}", failed_count + passed_count);
XELOGI("Passed: {}", passed_count);
XELOGI("Failed: {}", failed_count);
fprintf(stderr, "\n");
fprintf(stderr, "Total tests: %d\n", failed_count + passed_count);
fprintf(stderr, "Passed: %d\n", passed_count);
fprintf(stderr, "Failed: %d\n", failed_count);
fflush(stderr);
return failed_count ? false : true;
}

View File

@@ -411,7 +411,10 @@ bool DiscoverTests(std::wstring& test_path,
for (auto& file_info : file_infos) {
if (file_info.name != L"." && file_info.name != L".." &&
file_info.name.rfind(L".s") == file_info.name.size() - 2) {
test_files.push_back(xe::join_paths(test_path, file_info.name));
// Only include test files (instr_*.s), not helper files
if (file_info.name.find(L"instr_") == 0) {
test_files.push_back(xe::join_paths(test_path, file_info.name));
}
}
}
return true;

View File

@@ -7,17 +7,25 @@ project("xenia-cpu-ppc-tests")
kind("ConsoleApp")
language("C++")
links({
"capstone", -- cpu-backend-x64
"fmt",
"mspack",
"imgui",
"xenia-apu",
"xenia-apu-nop",
"xenia-base",
"xenia-core",
"xenia-cpu",
"xenia-gpu",
"xenia-base",
"xenia-gpu-null",
"xenia-hid",
"xenia-hid-nop",
"xenia-kernel",
"xenia-patcher",
"xenia-hid-skylander",
"xenia-ui",
"xenia-vfs",
})
links({
"capstone",
"fmt",
"imgui",
"mspack",
})
files({
"ppc_testing_main.cc",
@@ -41,10 +49,7 @@ project("xenia-cpu-ppc-tests")
"1>scratch/stdout-testing.txt",
})
-- xenia-base needs this
links({
"xenia-ui",
})
filter({})
if ARCH == "ppc64" or ARCH == "powerpc64" then

View File

@@ -85,8 +85,13 @@ Symbol::Status TestModule::DeclareFunction(uint32_t address,
// Reset() all caching when we leave.
xe::make_reset_scope(compiler_);
xe::make_reset_scope(assembler_);
xe::make_reset_scope(builder_);
// Set the HIRBuilder as current for this thread
builder_->MakeCurrent();
if (!generate_(*builder_.get())) {
builder_->RemoveCurrent();
function->set_status(Symbol::Status::kFailed);
return Symbol::Status::kFailed;
}
@@ -97,6 +102,9 @@ Symbol::Status TestModule::DeclareFunction(uint32_t address,
// Assemble the function.
assembler_->Assemble(function, builder_.get(), 0, nullptr);
// Remove the HIRBuilder as current for this thread
builder_->RemoveCurrent();
status = Symbol::Status::kDefined;
function->set_status(status);
}

View File

@@ -20,7 +20,7 @@
DECLARE_bool(debug);
DECLARE_bool(force_mount_devkit);
DEFINE_bool(force_mount_devkit, false, "Force devkit mount", "Storage");
namespace xe {
namespace kernel {

View File

@@ -28,9 +28,14 @@ XbdmModule::XbdmModule(Emulator* emulator, KernelState* kernel_state)
#undef XE_MODULE_EXPORT_GROUP
}
std::vector<xe::cpu::Export*> xbdm_exports(4096);
// Use construct-on-first-use idiom to avoid static initialization order issues
std::vector<xe::cpu::Export*>& GetXbdmExports() {
static std::vector<xe::cpu::Export*> xbdm_exports(4096);
return xbdm_exports;
}
xe::cpu::Export* RegisterExport_xbdm(xe::cpu::Export* export_entry) {
auto& xbdm_exports = GetXbdmExports();
assert_true(export_entry->ordinal < xbdm_exports.size());
xbdm_exports[export_entry->ordinal] = export_entry;
return export_entry;
@@ -45,6 +50,7 @@ void XbdmModule::RegisterExportTable(xe::cpu::ExportResolver* export_resolver) {
#include "xenia/kernel/xbdm/xbdm_table.inc"
};
#include "xenia/kernel/util/export_table_post.inc"
auto& xbdm_exports = GetXbdmExports();
for (size_t i = 0; i < xe::countof(xbdm_export_table); ++i) {
auto& export_entry = xbdm_export_table[i];
assert_true(export_entry.ordinal < xbdm_exports.size());

View File

@@ -21,7 +21,7 @@ SNAPSHOT_FILE=binutils-$SNAPSHOT_VERSION.tar.gz
if [ ! -f $SNAPSHOT_FILE ]; then
echo ""
echo "Grabbing binutils snapshot $SNAPSHOT_FILE..."
curl -nv http://mirrors.kernel.org/sourceware/binutils/releases/$SNAPSHOT_FILE > $SNAPSHOT_FILE
curl -L http://mirrors.kernel.org/sourceware/binutils/releases/$SNAPSHOT_FILE -o $SNAPSHOT_FILE
else
echo ""
echo "Reusing existing binutils snapshot file $SNAPSHOT_FILE..."

View File

@@ -15,6 +15,7 @@
#include "xenia/base/console_app_main.h"
#include "xenia/base/cvar.h"
#include "xenia/base/platform.h"
#define CATCH_CONFIG_RUNNER
#include "third_party/catch/single_include/catch2/catch.hpp"

View File

@@ -539,7 +539,7 @@ def run_premake(target_os, action, cc=None):
os.path.join("tools", "build", "premake.py"),
"--file=premake5.lua",
f"--os={target_os}",
#"--test-suite-mode=combined",
"--test-suite-mode=combined",
"--verbose",
action,
]
@@ -1218,12 +1218,20 @@ class TestCommand(BaseBuildCommand):
print_error(f"Unable to find {test_targets[i]} - build it.")
return 1
# Prepare environment with Qt bin directory in PATH if available
test_env = dict(os.environ)
qt_dir = os.environ.get("QT_DIR")
if qt_dir and sys.platform == "win32":
qt_bin = os.path.join(qt_dir, "bin")
if os.path.exists(qt_bin):
test_env["PATH"] = f"{qt_bin}{os.pathsep}{test_env['PATH']}"
print(f"- Qt bin directory added to PATH: {qt_bin}\n")
# Run tests.
any_failed = False
for test_executable in test_executables:
print(f"- {test_executable}")
result = shell_call([test_executable] + pass_args,
throw_on_error=False)
result = subprocess.call([test_executable] + pass_args, env=test_env)
if result:
any_failed = True
if args["continue"]: