From e06d70e80918e82f871bee14b39518247b033c04 Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 00:01:06 -0800 Subject: [PATCH 1/3] Add SerialProgramsTests, a unit test runner without the GUI SerialProgramsTests runs the same unit tests as --command-line-test-mode, but opens no window, reads and writes no settings, and does not scan serial ports or cameras, so it can run while SerialPrograms is open. --filter (repeatable) selects tests by name substring, --list prints the selected names, and the exit code is the number of failed tests (255 for a bad argument or no matching test). CommandLineUnitTestRunner::run() takes the filters and returns the failure count; --command-line-test-mode still returns 0 or 1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- SerialPrograms/CMakeLists.txt | 7 + .../ComputerPrograms_UnitTestRunner.cpp | 32 ++- .../ComputerPrograms_UnitTestRunner.h | 14 +- .../Source/Tests/CommandLineTests.cpp | 9 +- .../Source/Tests/CommandLineTests.h | 11 +- .../Source/Tests/TestRunnerExecutable.cmake | 19 ++ .../Source/Tests/TestRunner_Main.cpp | 183 ++++++++++++++++++ 7 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 SerialPrograms/Source/Tests/TestRunnerExecutable.cmake create mode 100644 SerialPrograms/Source/Tests/TestRunner_Main.cpp diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 35bb597e32..7923bcd16e 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -63,6 +63,9 @@ find_package(Threads REQUIRED) # Define a toggle option (ON by default) option(COMMAND_LINE_EXE "Build the optional executable" ON) +# Build SerialProgramsTests, which runs the unit tests without the GUI. +option(TEST_RUNNER_EXE "Build the unit test executable, SerialProgramsTests" ON) + # Build only the GUI-free core: CoreLib and SerialProgramsCommandLine. This needs # no Qt, OpenCV, ONNX Runtime, Tesseract, DPP or Discord SDK, so none of them are # searched for. @@ -336,6 +339,10 @@ if(NOT EXISTS "${DEST_DIR}/Scripts") ) endif() +if(TEST_RUNNER_EXE) + include(Source/Tests/TestRunnerExecutable.cmake) +endif() + if (WIN32) add_library(OpenCV_lib IMPORTED UNKNOWN) target_include_directories(SerialProgramsLib SYSTEM PRIVATE ../3rdParty/opencv-4.12.0/) diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 5b3e421dfd..83bd88dc9b 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -86,6 +86,28 @@ const UnitTestDatabase& UNIT_TESTS_ALL(){ static UnitTestDatabase database = make_UNIT_TESTS_ALL(); return database; } +namespace{ +bool matches_any(const std::string& test_name, const std::vector& filters){ + if (filters.empty()){ + return true; + } + for (const std::string& filter : filters){ + if (test_name.contains(filter)){ + return true; + } + } + return false; +} +} +std::vector unit_test_names(const std::vector& filters){ + std::vector ret; + for (const auto& test : UNIT_TESTS_ALL()){ + if (matches_any(test.first, filters)){ + ret.emplace_back(test.first); + } + } + return ret; +} StringSelectDatabase make_TEST_DATABASE(){ StringSelectDatabase database; for (auto& test : UNIT_TESTS_ALL()){ @@ -355,7 +377,7 @@ void UnitTestRunner::on_test_finished( -bool CommandLineUnitTestRunner::run(bool augment_color_distortions){ +size_t CommandLineUnitTestRunner::run(bool augment_color_distortions, const std::vector& filters){ if (augment_color_distortions){ // Load the distortion data before any test runs. Each `ImageUnitTest` checks the global // manager when it runs, and only runs with distortions if it is loaded. @@ -363,7 +385,7 @@ bool CommandLineUnitTestRunner::run(bool augment_color_distortions){ initialize_test_image_distortions(UNIT_TEST_RESOURCE_PATH()); }catch (Exception& e){ m_logger.log("Unable to load the test image distortions: " + e.to_str(), COLOR_RED); - return true; + return 1; } const TestImageDistortionManager& manager = *test_image_distortion_manager(); std::string labels; @@ -380,7 +402,9 @@ bool CommandLineUnitTestRunner::run(bool augment_color_distortions){ ); runner.add_listener(*this); for (const auto& test : UNIT_TESTS_ALL()){ - runner.add_test(test.second); + if (matches_any(test.first, filters)){ + runner.add_test(test.second); + } } runner.run(); @@ -390,7 +414,7 @@ bool CommandLineUnitTestRunner::run(bool augment_color_distortions){ "\n Failed: " + tostr_u_commas(m_failed_tests.load(std::memory_order_acquire)) + "\n Skipped: " + tostr_u_commas(m_skipped_tests.load(std::memory_order_acquire)) ); - return m_failed_tests.load(std::memory_order_acquire) > 0; + return m_failed_tests.load(std::memory_order_acquire); } void CommandLineUnitTestRunner::on_test_finished( std::shared_ptr test, diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h index c3aeb94f07..aa21e48a8d 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h @@ -8,6 +8,8 @@ #define PokemonAutomation_ComputerPrograms_UnitTestRunner_H #include +#include +#include #include "Common/Cpp/TestRunners/ParallelUnitTestRunner.h" #include "Common/Cpp/Options/StaticTextOption.h" #include "Common/Cpp/Options/EnumDropdownOption.h" @@ -89,12 +91,13 @@ class CommandLineUnitTestRunner : public PokemonAutomation::UnitTestRunner::List , m_failed_tests(0) {} - // Run all the unit tests. Return true if any test failed. + // Run the unit tests whose names contain any of `filters`, or all of them if `filters` is + // empty. Return the number of failed tests. // If `augment_color_distortions` is true, also run each image test under different color // distortion augmentation to widen the test coverage, using the distortion data in the // CaptureCardCalibration/ folder of the test repo. If that data can't be loaded, log the - // error and return true without running any test. - bool run(bool augment_color_distortions); + // error and return 1 without running any test. + size_t run(bool augment_color_distortions, const std::vector& filters); private: @@ -112,6 +115,11 @@ class CommandLineUnitTestRunner : public PokemonAutomation::UnitTestRunner::List }; +// The names of the unit tests that contain any of `filters`, or of all the tests if `filters` +// is empty, in name order. +std::vector unit_test_names(const std::vector& filters); + + } diff --git a/SerialPrograms/Source/Tests/CommandLineTests.cpp b/SerialPrograms/Source/Tests/CommandLineTests.cpp index bed7f8997e..2e032358c4 100644 --- a/SerialPrograms/Source/Tests/CommandLineTests.cpp +++ b/SerialPrograms/Source/Tests/CommandLineTests.cpp @@ -19,6 +19,10 @@ namespace PokemonAutomation{ int run_command_line_tests(bool augment_color_distortions){ + return run_unit_tests(augment_color_distortions, {}) == 0 ? 0 : 1; +} + +size_t run_unit_tests(bool augment_color_distortions, const std::vector& filters){ install_crash_backtrace_handlers(); // Keep stdout line-buffered: when piped, stdio defaults to full // buffering, and the last few KB of test output - including the lines @@ -28,8 +32,9 @@ int run_command_line_tests(bool augment_color_distortions){ setvbuf(stdout, nullptr, _IOLBF, 8192); cout << "Running parallel unit tests..." << endl; ComputerPrograms::CommandLineUnitTestRunner runner(global_logger_command_line()); - if (runner.run(augment_color_distortions)){ - return 1; + size_t failed = runner.run(augment_color_distortions, filters); + if (failed > 0){ + return failed; } cout << "Running parallel unit tests... Done!" << endl; return 0; diff --git a/SerialPrograms/Source/Tests/CommandLineTests.h b/SerialPrograms/Source/Tests/CommandLineTests.h index 010de0c81f..058014af75 100644 --- a/SerialPrograms/Source/Tests/CommandLineTests.h +++ b/SerialPrograms/Source/Tests/CommandLineTests.h @@ -5,7 +5,9 @@ * Run all the unit tests from the command line, without launching the GUI. * * How to run: - * Launch the program with the argument "--command-line-test-mode", or set + * Run the SerialProgramsTests executable (Tests/TestRunner_Main.cpp): it runs only the tests, + * with no GUI, no settings and no device scans, and takes "--filter " and "--list". + * Or launch the program with the argument "--command-line-test-mode", or set * "20-GlobalSettings": "COMMAND_LINE_TESTS": "RUN" to true in SerialPrograms-Settings.json. * * How the tests work: @@ -40,6 +42,8 @@ #ifndef PokemonAutomation_Tests_CommandLineTests_H #define PokemonAutomation_Tests_CommandLineTests_H +#include +#include namespace PokemonAutomation{ @@ -51,6 +55,11 @@ namespace PokemonAutomation{ // Return 0 if all tests are passed, and 1 if any test failed or the distortion data can't be loaded. int run_command_line_tests(bool augment_color_distortions); +// Run the unit tests whose names contain any of `filters`, or all of them if `filters` is empty, +// and print the results to stdout. Return the number of failed tests, or 1 if +// `augment_color_distortions` is set and the distortion data can't be loaded. +size_t run_unit_tests(bool augment_color_distortions, const std::vector& filters); + } diff --git a/SerialPrograms/Source/Tests/TestRunnerExecutable.cmake b/SerialPrograms/Source/Tests/TestRunnerExecutable.cmake new file mode 100644 index 0000000000..f57621abf3 --- /dev/null +++ b/SerialPrograms/Source/Tests/TestRunnerExecutable.cmake @@ -0,0 +1,19 @@ +# This cmake file is to be included by CMakeLists.txt to build SerialProgramsTests, an executable +# that runs the unit tests without the GUI (see Source/Tests/TestRunner_Main.cpp). + +add_executable(SerialProgramsTests Source/Tests/TestRunner_Main.cpp) + +# The tests are registered by explicit calls, not by static constructors, so the linker may +# leave out the parts of the library that the tests don't use. +target_link_libraries(SerialProgramsTests PRIVATE SerialProgramsLib) +apply_common_target_properties(SerialProgramsTests) + +# The tests read Resources/ from in or above the executable's folder, like SerialPrograms. +add_dependencies(SerialProgramsTests clone_packages) +if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/Resources") + add_custom_command( + TARGET SerialProgramsTests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "[Deploy] Copying resources ${RESOURCES_DIR} to $/Resources" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${RESOURCES_DIR}" "$/Resources" + ) +endif() diff --git a/SerialPrograms/Source/Tests/TestRunner_Main.cpp b/SerialPrograms/Source/Tests/TestRunner_Main.cpp new file mode 100644 index 0000000000..6f353a2386 --- /dev/null +++ b/SerialPrograms/Source/Tests/TestRunner_Main.cpp @@ -0,0 +1,183 @@ +/* Unit Test Runner Main + * + * From: https://github.com/PokemonAutomation/ + * + * SerialProgramsTests: an executable that runs the unit tests and nothing else. It runs the + * same tests as "SerialPrograms --command-line-test-mode" (see Tests/CommandLineTests.h), but + * opens no window, reads and writes no settings, and does not look for serial ports or cameras. + * So it can run while SerialPrograms is open on the same computer. + * + * Usage: + * SerialProgramsTests [--filter ]... [--list] [--augment-color-distortions] + * + * --filter Select the tests whose names contain . Repeat it to + * select the tests that match any of them. Default: all tests. + * --list Print the names of the selected tests instead of running them. + * --augment-color-distortions Also run the image tests with color distortions. + * + * Exit code: the number of failed tests (at most 254), or 255 for a bad argument or when no + * test matches the filters. + * + * It finds Resources/ and the CommandLineTests repo the same way SerialPrograms does: in or + * above the folder that holds the executable. + */ + +#include +#include +#include +#include +#include +#include +#include "Common/Cpp/Concurrency/Qt6.9ThreadBugWorkaround.h" +#include "Common/Cpp/Concurrency/FireForgetDispatcher.h" +#include "Common/Cpp/Concurrency/PeriodicRunner.h" +#include "Common/Cpp/Concurrency/Watchdog.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Logging/FileLogger.h" +#include "Common/Cpp/Logging/GlobalLogger.h" +#include "Common/Cpp/Logging/LastLogTracker.h" +#include "Common/Cpp/Logging/MultiOutputLogger.h" +#include "Common/Qt/GlobalThreadPoolsQt.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/GlobalServices.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "ComputerPrograms/ComputerPrograms_UnitTestRunner.h" +#include "Tests/CommandLineTests.h" + +using namespace PokemonAutomation; + +namespace PokemonAutomation{ + +bool use_qt_ui(){ + return false; +} + +} + + +namespace{ + +constexpr int EXIT_BAD_ARGUMENTS = 255; +constexpr size_t MAX_FAILURE_EXIT_CODE = 254; + +struct TestRunnerArguments{ + std::vector filters; + bool list = false; + bool augment_color_distortions = false; +}; + +// Return nothing if an argument is unknown or incomplete. +std::optional parse_arguments(int argc, char* argv[]){ + TestRunnerArguments arguments; + for (int i = 1; i < argc; i++){ + const std::string arg = argv[i]; + if (arg == "--filter" && i + 1 < argc){ + arguments.filters.emplace_back(argv[++i]); + }else if (arg == "--list"){ + arguments.list = true; + }else if (arg == "--augment-color-distortions"){ + arguments.augment_color_distortions = true; + }else{ + std::cerr << "Unknown or incomplete argument: " << arg << "\n" + << "Usage: SerialProgramsTests [--filter ]... [--list] [--augment-color-distortions]" + << std::endl; + return std::nullopt; + } + } + return arguments; +} + +// The full test logs go to "SerialProgramsTests.log" in this executable's own user folder, +// not in the folder of SerialPrograms. +const std::string& test_log_path(){ + static const std::string path = + USER_FILE_PATH() + Filesystem::application_binary_name().stem().string() + ".log"; + return path; +} +FileLogger& test_file_logger(){ + static FileLogger logger( + GlobalThreadPools::unlimited_normal(), + FileLoggerConfig{ + .file_path = test_log_path(), + } + ); + return logger; +} + +int run_tests(const TestRunnerArguments& arguments){ + std::vector names = ComputerPrograms::unit_test_names(arguments.filters); + if (arguments.list){ + for (const std::string& name : names){ + std::cout << name << "\n"; + } + std::cout << names.size() << " tests" << std::endl; + return names.empty() ? EXIT_BAD_ARGUMENTS : 0; + } + if (names.empty()){ + std::cerr << "No test matches the filters." << std::endl; + return EXIT_BAD_ARGUMENTS; + } + + Logger& logger = global_logger_command_line(); + logger.log("Resources folder: " + RESOURCE_PATH()); + logger.log("Unit test resources folder: " + UNIT_TEST_RESOURCE_PATH()); + logger.log("Log file: " + test_log_path()); + + size_t failed = run_unit_tests(arguments.augment_color_distortions, arguments.filters); + return (int)std::min(failed, MAX_FAILURE_EXIT_CODE); +} + +// Same order as the end of main() in CommonFramework/Main.cpp, for the services the tests use. +void stop_global_services(){ + OCR::clear_ocr_cache(); + global_periodic_runner().stop(); + global_watchdog().stop(); + test_file_logger().stop(); +} +void stop_thread_pools(){ + GlobalThreadPools::computation_realtime().stop(); + GlobalThreadPools::computation_normal().stop(); + GlobalThreadPools::unlimited_realtime().stop(); + GlobalThreadPools::unlimited_normal().stop(); + global_dispatcher.stop(); + + GlobalThreadPools::qt_worker_threadpool().stop(); + GlobalThreadPools::qt_event_threadpool().stop(); +} + +} + + +int main(int argc, char* argv[]){ + std::optional arguments = parse_arguments(argc, argv); + if (!arguments){ + return EXIT_BAD_ARGUMENTS; + } + + // The tests use Qt (images, thread pools, events) but no window. + QCoreApplication application(argc, argv); + + { + MultiOutputLogger& logger = global_multi_logger(); + logger.add_listener(global_last_log_history()); + logger.add_listener(test_file_logger()); + } + + // Construct the Qt thread pools on the main thread, as SerialPrograms does. + GlobalThreadPools::qt_worker_threadpool(); + GlobalThreadPools::qt_event_threadpool(); + + int ret = run_tests(*arguments); + + stop_global_services(); +#ifdef PA_ENABLE_QT_ADOPTION_WORKAROUND + // See the Qt 6.9 workaround at the end of main() in CommonFramework/Main.cpp. + std::cout.flush(); + std::quick_exit(ret); +#endif + stop_thread_pools(); + return ret; +} From ad6aa40ea4fc8ad42b91879167fbcf0dd67cadaf Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 11:11:31 -0800 Subject: [PATCH 2/3] SerialProgramsTests: address review (span filters, namespaces, docs) - Pass test-name filters as std::span; the runner keeps views of argv instead of copying strings. - Put the unnamed namespaces at the top, inside namespace PokemonAutomation, closed with "} // namespace"; drop the using-directive and let main() call PokemonAutomation::test_runner_main(). - AGENTS.md: how to build and run SerialProgramsTests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- AGENTS.md | 5 +++ .../ComputerPrograms_UnitTestRunner.cpp | 34 +++++++++-------- .../ComputerPrograms_UnitTestRunner.h | 6 ++- .../Source/Tests/CommandLineTests.cpp | 2 +- .../Source/Tests/CommandLineTests.h | 7 ++-- .../Source/Tests/TestRunner_Main.cpp | 37 +++++++++++-------- 6 files changed, 54 insertions(+), 37 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3fc0e4cd52..7b538a4f3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,6 +126,11 @@ migrate a file to the wrappers when you're already touching it and it's straight Windows/Linux; `~/Library/Application Support/SerialPrograms/DownloadedResources` on macOS). - Headless Linux: `QT_QPA_PLATFORM=offscreen` and `TESSDATA_PREFIX=/Resources/Tesseract`. +- `SerialProgramsTests` (built by default next to `SerialPrograms`; `-DTEST_RUNNER_EXE=OFF` + turns it off) runs the same tests without the GUI, settings or device scans, so it can run + while SerialPrograms is open: `ninja SerialProgramsTests`, then + `SerialProgramsTests --filter ` (repeatable) or `--list`. Its exit code is the number + of failed tests. Same requirements as above. - Test framework: test classes derive `UnitTest` (`Common/Cpp/TestRunners/UnitTest.h`) and are collected via per-module `add_tests(UnitTestDatabase&)` — see `SerialPrograms/Source/Tests/CommandLineTests.h`. diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 3d1bc73c6a..62aec7e400 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -44,6 +44,23 @@ namespace PokemonAutomation{ namespace ComputerPrograms{ +namespace{ + +bool matches_any(std::string_view test_name, std::span filters){ + if (filters.empty()){ + return true; + } + for (std::string_view filter : filters){ + if (test_name.contains(filter)){ + return true; + } + } + return false; +} + +} // namespace + + @@ -86,20 +103,7 @@ const UnitTestDatabase& UNIT_TESTS_ALL(){ static UnitTestDatabase database = make_UNIT_TESTS_ALL(); return database; } -namespace{ -bool matches_any(const std::string& test_name, const std::vector& filters){ - if (filters.empty()){ - return true; - } - for (const std::string& filter : filters){ - if (test_name.contains(filter)){ - return true; - } - } - return false; -} -} -std::vector unit_test_names(const std::vector& filters){ +std::vector unit_test_names(std::span filters){ std::vector ret; for (const auto& test : UNIT_TESTS_ALL()){ if (matches_any(test.first, filters)){ @@ -379,7 +383,7 @@ void UnitTestRunner::on_test_finished( -size_t CommandLineUnitTestRunner::run(bool augment_color_distortions, const std::vector& filters){ +size_t CommandLineUnitTestRunner::run(bool augment_color_distortions, std::span filters){ if (augment_color_distortions){ // Load the distortion data before any test runs. Each `ImageUnitTest` checks the global // manager when it runs, and only runs with distortions if it is loaded. diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h index aa21e48a8d..10f69c8ab8 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.h @@ -8,7 +8,9 @@ #define PokemonAutomation_ComputerPrograms_UnitTestRunner_H #include +#include #include +#include #include #include "Common/Cpp/TestRunners/ParallelUnitTestRunner.h" #include "Common/Cpp/Options/StaticTextOption.h" @@ -97,7 +99,7 @@ class CommandLineUnitTestRunner : public PokemonAutomation::UnitTestRunner::List // distortion augmentation to widen the test coverage, using the distortion data in the // CaptureCardCalibration/ folder of the test repo. If that data can't be loaded, log the // error and return 1 without running any test. - size_t run(bool augment_color_distortions, const std::vector& filters); + size_t run(bool augment_color_distortions, std::span filters); private: @@ -117,7 +119,7 @@ class CommandLineUnitTestRunner : public PokemonAutomation::UnitTestRunner::List // The names of the unit tests that contain any of `filters`, or of all the tests if `filters` // is empty, in name order. -std::vector unit_test_names(const std::vector& filters); +std::vector unit_test_names(std::span filters); diff --git a/SerialPrograms/Source/Tests/CommandLineTests.cpp b/SerialPrograms/Source/Tests/CommandLineTests.cpp index 2e032358c4..4c73b74376 100644 --- a/SerialPrograms/Source/Tests/CommandLineTests.cpp +++ b/SerialPrograms/Source/Tests/CommandLineTests.cpp @@ -22,7 +22,7 @@ int run_command_line_tests(bool augment_color_distortions){ return run_unit_tests(augment_color_distortions, {}) == 0 ? 0 : 1; } -size_t run_unit_tests(bool augment_color_distortions, const std::vector& filters){ +size_t run_unit_tests(bool augment_color_distortions, std::span filters){ install_crash_backtrace_handlers(); // Keep stdout line-buffered: when piped, stdio defaults to full // buffering, and the last few KB of test output - including the lines diff --git a/SerialPrograms/Source/Tests/CommandLineTests.h b/SerialPrograms/Source/Tests/CommandLineTests.h index 058014af75..b42c0b0c6b 100644 --- a/SerialPrograms/Source/Tests/CommandLineTests.h +++ b/SerialPrograms/Source/Tests/CommandLineTests.h @@ -42,8 +42,9 @@ #ifndef PokemonAutomation_Tests_CommandLineTests_H #define PokemonAutomation_Tests_CommandLineTests_H -#include -#include +#include +#include +#include namespace PokemonAutomation{ @@ -58,7 +59,7 @@ int run_command_line_tests(bool augment_color_distortions); // Run the unit tests whose names contain any of `filters`, or all of them if `filters` is empty, // and print the results to stdout. Return the number of failed tests, or 1 if // `augment_color_distortions` is set and the distortion data can't be loaded. -size_t run_unit_tests(bool augment_color_distortions, const std::vector& filters); +size_t run_unit_tests(bool augment_color_distortions, std::span filters); diff --git a/SerialPrograms/Source/Tests/TestRunner_Main.cpp b/SerialPrograms/Source/Tests/TestRunner_Main.cpp index 6f353a2386..5f2d32e557 100644 --- a/SerialPrograms/Source/Tests/TestRunner_Main.cpp +++ b/SerialPrograms/Source/Tests/TestRunner_Main.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include "Common/Cpp/Concurrency/Qt6.9ThreadBugWorkaround.h" @@ -47,24 +48,15 @@ #include "ComputerPrograms/ComputerPrograms_UnitTestRunner.h" #include "Tests/CommandLineTests.h" -using namespace PokemonAutomation; - namespace PokemonAutomation{ - -bool use_qt_ui(){ - return false; -} - -} - - namespace{ constexpr int EXIT_BAD_ARGUMENTS = 255; constexpr size_t MAX_FAILURE_EXIT_CODE = 254; struct TestRunnerArguments{ - std::vector filters; + // Views of argv, which outlives every use. + std::vector filters; bool list = false; bool augment_color_distortions = false; }; @@ -73,7 +65,7 @@ struct TestRunnerArguments{ std::optional parse_arguments(int argc, char* argv[]){ TestRunnerArguments arguments; for (int i = 1; i < argc; i++){ - const std::string arg = argv[i]; + const std::string_view arg = argv[i]; if (arg == "--filter" && i + 1 < argc){ arguments.filters.emplace_back(argv[++i]); }else if (arg == "--list"){ @@ -148,10 +140,8 @@ void stop_thread_pools(){ GlobalThreadPools::qt_event_threadpool().stop(); } -} - - -int main(int argc, char* argv[]){ +// The body of main(). `argc` is a reference because QCoreApplication keeps one. +int test_runner_main(int& argc, char* argv[]){ std::optional arguments = parse_arguments(argc, argv); if (!arguments){ return EXIT_BAD_ARGUMENTS; @@ -181,3 +171,18 @@ int main(int argc, char* argv[]){ stop_thread_pools(); return ret; } + +} // namespace + + +bool use_qt_ui(){ + return false; +} + + +} // namespace PokemonAutomation + + +int main(int argc, char* argv[]){ + return PokemonAutomation::test_runner_main(argc, argv); +} From c213cac0c7c8497e56cca1b44368785e7b34b210 Mon Sep 17 00:00:00 2001 From: dayfine Date: Tue, 3 Nov 2026 13:43:42 -0800 Subject: [PATCH 3/3] Unit test runner: blank lines around unit_test_names() Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AKYzhrMnPK61XHWEch4a6E --- .../Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 62aec7e400..8c8f740da7 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -103,6 +103,7 @@ const UnitTestDatabase& UNIT_TESTS_ALL(){ static UnitTestDatabase database = make_UNIT_TESTS_ALL(); return database; } + std::vector unit_test_names(std::span filters){ std::vector ret; for (const auto& test : UNIT_TESTS_ALL()){ @@ -112,6 +113,7 @@ std::vector unit_test_names(std::span filte } return ret; } + StringSelectDatabase make_TEST_DATABASE(){ StringSelectDatabase database; for (auto& test : UNIT_TESTS_ALL()){