[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:
@@ -22,6 +22,7 @@
|
|||||||
#include "xenia/cpu/raw_module.h"
|
#include "xenia/cpu/raw_module.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
@@ -659,16 +660,16 @@ bool RunTests(const std::vector<std::string>& test_names) {
|
|||||||
|
|
||||||
XELOGI("{} tests loaded.", test_suites.size());
|
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;
|
int skipped_count = 0;
|
||||||
size_t total_cases = 0;
|
|
||||||
for (auto& test_suite : test_suites) {
|
for (auto& test_suite : test_suites) {
|
||||||
for (auto& test_case : test_suite.test_cases()) {
|
for (auto& test_case : test_suite.test_cases()) {
|
||||||
if (skip_list.find(test_case.name) != skip_list.end()) {
|
if (skip_list.find(test_case.name) != skip_list.end()) {
|
||||||
++skipped_count;
|
++skipped_count;
|
||||||
} else {
|
continue;
|
||||||
++total_cases;
|
|
||||||
}
|
}
|
||||||
|
all_tests.push_back({&test_suite, &test_case});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,40 +678,157 @@ bool RunTests(const std::vector<std::string>& test_names) {
|
|||||||
skipped_count);
|
skipped_count);
|
||||||
}
|
}
|
||||||
fprintf(stderr, "Running %zu test suites, %zu test cases...\n",
|
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
|
#if XE_COMPILER_MSVC
|
||||||
// On Windows, use a single shared test runner
|
// On Windows, run tests serially grouped by suite
|
||||||
TestRunner runner;
|
TestRunner runner;
|
||||||
#else
|
int suite_index = 0;
|
||||||
// On POSIX, each test will create its own runner in a forked process
|
int suite_total = 0;
|
||||||
// Pass a dummy value that won't be used
|
size_t tests_done = 0;
|
||||||
TestRunner* runner_ptr = nullptr;
|
size_t total_tests = all_tests.size();
|
||||||
TestRunner& runner = *runner_ptr; // Never dereferenced on POSIX
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Run tests grouped by suite, printing a dot after each suite completes
|
|
||||||
for (auto& test_suite : test_suites) {
|
for (auto& test_suite : test_suites) {
|
||||||
bool suite_has_tests = false;
|
|
||||||
for (auto& test_case : test_suite.test_cases()) {
|
for (auto& test_case : test_suite.test_cases()) {
|
||||||
if (skip_list.find(test_case.name) != skip_list.end()) {
|
if (skip_list.find(test_case.name) == skip_list.end()) {
|
||||||
continue;
|
++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");
|
int pct =
|
||||||
fflush(stdout);
|
total_tests ? static_cast<int>(tests_done * 100 / total_tests) : 0;
|
||||||
fprintf(stderr, "Total tests: %d\n", failed_count + passed_count);
|
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, "Passed: %d\n", passed_count);
|
||||||
fprintf(stderr, "Failed: %d\n", failed_count);
|
fprintf(stderr, "Failed: %d\n", failed_count);
|
||||||
|
fprintf(stderr, "Time: %dm %ds\n", minutes, seconds);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
||||||
return failed_count ? false : true;
|
return failed_count ? false : true;
|
||||||
|
|||||||
Reference in New Issue
Block a user