From 9a06a19eac017e6e915d1d26236da2926501b30f Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 21 Mar 2026 23:49:32 +0900 Subject: [PATCH] [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. --- src/xenia/cpu/ppc/testing/ppc_testing_main.cc | 172 +++++++++++++++--- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc index 121bf1217..4655608e5 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc @@ -22,6 +22,7 @@ #include "xenia/cpu/raw_module.h" #include +#include #include #include #include @@ -659,16 +660,16 @@ bool RunTests(const std::vector& 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> 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& 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 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(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(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 completed{0}; + }; + std::unordered_map suite_map; + std::vector> suite_info; + std::vector 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(); + 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(suite_info.size()); + std::atomic suites_completed{0}; + std::atomic tests_completed{0}; + size_t total_tests = all_tests.size(); + + std::mutex result_mutex; + std::atomic 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 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(done * 100 / total_tests); + std::lock_guard 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(done * 100 / total_tests); + std::lock_guard lock(result_mutex); + fprintf(stdout, " ... %s %zu/%zu %d%%\n", si.suite->name().c_str(), + suite_done, si.total, pct); + fflush(stdout); + } + } + }; + + std::vector 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(end_time - start_time) + .count(); + int minutes = static_cast(elapsed_sec / 60); + int seconds = static_cast(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;