[Testing] Parallel execution for ppc tests on posix

On posix run tests in parallel using a thread pool with fork-per-test
isolation (ported from edge).
On all platforms add progress output showing suite name and test count,
as well as elapsed time in the summary.
This commit is contained in:
Herman S.
2026-03-21 23:49:32 +09:00
parent bc37068f90
commit 9a06a19eac

View File

@@ -22,6 +22,7 @@
#include "xenia/cpu/raw_module.h"
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <unordered_set>
@@ -659,16 +660,16 @@ bool RunTests(const std::vector<std::string>& test_names) {
XELOGI("{} tests loaded.", test_suites.size());
// Count test cases across all suites, filtering out skipped tests
// Collect all test cases across all suites, filtering out skipped tests
std::vector<std::pair<TestSuite*, TestCase*>> all_tests;
int skipped_count = 0;
size_t total_cases = 0;
for (auto& test_suite : test_suites) {
for (auto& test_case : test_suite.test_cases()) {
if (skip_list.find(test_case.name) != skip_list.end()) {
++skipped_count;
} else {
++total_cases;
continue;
}
all_tests.push_back({&test_suite, &test_case});
}
}
@@ -677,40 +678,157 @@ bool RunTests(const std::vector<std::string>& test_names) {
skipped_count);
}
fprintf(stderr, "Running %zu test suites, %zu test cases...\n",
test_suites.size(), total_cases);
test_suites.size(), all_tests.size());
auto start_time = std::chrono::steady_clock::now();
#if XE_COMPILER_MSVC
// On Windows, use a single shared test runner
// On Windows, run tests serially grouped by suite
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
// Run tests grouped by suite, printing a dot after each suite completes
int suite_index = 0;
int suite_total = 0;
size_t tests_done = 0;
size_t total_tests = all_tests.size();
for (auto& test_suite : test_suites) {
bool suite_has_tests = false;
for (auto& test_case : test_suite.test_cases()) {
if (skip_list.find(test_case.name) != skip_list.end()) {
continue;
if (skip_list.find(test_case.name) == skip_list.end()) {
++suite_total;
break;
}
suite_has_tests = true;
ProtectedRunTest(test_suite, runner, test_case, failed_count,
passed_count);
}
if (suite_has_tests) {
fprintf(stdout, ".");
fflush(stdout);
}
}
for (auto& test_suite : test_suites) {
// Collect non-skipped test cases for this suite
std::vector<TestCase*> suite_tests;
for (auto& test_case : test_suite.test_cases()) {
if (skip_list.find(test_case.name) == skip_list.end()) {
suite_tests.push_back(&test_case);
}
}
if (suite_tests.empty()) continue;
++suite_index;
fprintf(stdout, "\n");
fflush(stdout);
fprintf(stderr, "Total tests: %d\n", failed_count + passed_count);
int pct =
total_tests ? static_cast<int>(tests_done * 100 / total_tests) : 0;
fprintf(stdout, "[%d/%d] %s (%zu tests) %d%%\n", suite_index, suite_total,
test_suite.name().c_str(), suite_tests.size(), pct);
fflush(stdout);
for (size_t i = 0; i < suite_tests.size(); i++) {
ProtectedRunTest(test_suite, runner, *suite_tests[i], failed_count,
passed_count);
++tests_done;
if ((i + 1) % 500 == 0 && i + 1 < suite_tests.size()) {
pct = static_cast<int>(tests_done * 100 / total_tests);
fprintf(stdout, " ... %zu/%zu %d%%\n", i + 1, suite_tests.size(), pct);
fflush(stdout);
}
}
}
#else
// On POSIX, run tests in parallel using thread pool + fork per test.
// Each thread forks one child at a time, ensuring only one Memory/shm
// instance per thread (avoids shm name collisions between concurrent
// children).
unsigned int num_cores = std::thread::hardware_concurrency();
if (num_cores == 0) num_cores = 4;
num_cores = std::max(1u, num_cores * 3 / 4);
fprintf(stderr, "Running tests in parallel using %u workers\n", num_cores);
// Per-suite tracking for progress output
struct SuiteInfo {
TestSuite* suite;
size_t total;
std::atomic<size_t> completed{0};
};
std::unordered_map<TestSuite*, size_t> suite_map;
std::vector<std::unique_ptr<SuiteInfo>> suite_info;
std::vector<size_t> test_to_suite(all_tests.size());
for (size_t i = 0; i < all_tests.size(); i++) {
auto* suite = all_tests[i].first;
auto it = suite_map.find(suite);
if (it == suite_map.end()) {
it = suite_map.emplace(suite, suite_info.size()).first;
auto si = std::make_unique<SuiteInfo>();
si->suite = suite;
si->total = 0;
suite_info.push_back(std::move(si));
}
test_to_suite[i] = it->second;
suite_info[it->second]->total++;
}
int suite_total = static_cast<int>(suite_info.size());
std::atomic<int> suites_completed{0};
std::atomic<size_t> tests_completed{0};
size_t total_tests = all_tests.size();
std::mutex result_mutex;
std::atomic<size_t> test_index{0};
auto worker = [&]() {
// Dummy runner for API compatibility (not used on POSIX)
TestRunner* runner_ptr = nullptr;
TestRunner& runner = *runner_ptr;
while (true) {
size_t idx = test_index.fetch_add(1);
if (idx >= all_tests.size()) break;
auto& [test_suite, test_case] = all_tests[idx];
int local_failed = 0;
int local_passed = 0;
ProtectedRunTest(*test_suite, runner, *test_case, local_failed,
local_passed);
{
std::lock_guard<std::mutex> lock(result_mutex);
failed_count += local_failed;
passed_count += local_passed;
}
size_t done = tests_completed.fetch_add(1) + 1;
auto& si = *suite_info[test_to_suite[idx]];
size_t suite_done = si.completed.fetch_add(1) + 1;
if (suite_done == si.total) {
int num = suites_completed.fetch_add(1) + 1;
int pct = static_cast<int>(done * 100 / total_tests);
std::lock_guard<std::mutex> lock(result_mutex);
fprintf(stdout, "[%d/%d] %s (%zu tests) %d%%\n", num, suite_total,
si.suite->name().c_str(), si.total, pct);
fflush(stdout);
} else if (suite_done % 500 == 0) {
int pct = static_cast<int>(done * 100 / total_tests);
std::lock_guard<std::mutex> lock(result_mutex);
fprintf(stdout, " ... %s %zu/%zu %d%%\n", si.suite->name().c_str(),
suite_done, si.total, pct);
fflush(stdout);
}
}
};
std::vector<std::thread> threads;
for (unsigned int i = 0; i < num_cores; ++i) {
threads.emplace_back(worker);
}
for (auto& thread : threads) {
thread.join();
}
#endif
auto end_time = std::chrono::steady_clock::now();
auto elapsed_sec =
std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time)
.count();
int minutes = static_cast<int>(elapsed_sec / 60);
int seconds = static_cast<int>(elapsed_sec % 60);
fprintf(stderr, "\nTotal tests: %d\n", failed_count + passed_count);
fprintf(stderr, "Passed: %d\n", passed_count);
fprintf(stderr, "Failed: %d\n", failed_count);
fprintf(stderr, "Time: %dm %ds\n", minutes, seconds);
fflush(stderr);
return failed_count ? false : true;