/* Self tests for parallel_for_each Copyright (C) 2021-2025 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "gdbsupport/selftest.h" #include "gdbsupport/parallel-for.h" #if CXX_STD_THREAD #include "gdbsupport/thread-pool.h" namespace selftests { namespace parallel_for { struct save_restore_n_threads { save_restore_n_threads () : n_threads (gdb::thread_pool::g_thread_pool->thread_count ()) { } ~save_restore_n_threads () { gdb::thread_pool::g_thread_pool->set_thread_count (n_threads); } int n_threads; }; using foreach_callback_t = gdb::function_view range)>; using do_foreach_t = gdb::function_view; /* Run one parallel-for-each test on the range [1, UPPER_BOUND) using the parallel-for-each implementation DO_FOREACH. */ static void test_one (do_foreach_t do_foreach, int upper_bound) { std::vector input; for (int i = 0; i < upper_bound; ++i) input.emplace_back (i); std::vector output; std::mutex mtx; /* The (unfortunate) reason why we don't use std::vector::iterator as the parallel-for-each iterator type is that std::atomic won't work with that type when building with -D_GLIBCXX_DEBUG. */ do_foreach (input.data (), input.data () + input.size (), [&] (iterator_range range) { /* We shouldn't receive empty ranges. */ SELF_CHECK (!range.empty ()); std::lock_guard lock (mtx); for (int i : range) output.emplace_back (i * 2); }); /* Verify that each item was processed exactly once. */ SELF_CHECK (output.size () == upper_bound); std::sort (output.begin (), output.end ()); for (int i = 0; i < output.size (); ++i) SELF_CHECK (output[i] == i * 2); } /* Run all tests on the parallel-for-each implementation DO_FOREACH. */ static void test_one_function (int n_threads, do_foreach_t do_foreach) { save_restore_n_threads saver; gdb::thread_pool::g_thread_pool->set_thread_count (n_threads); /* Test with a few arbitrary number of items. */ test_one (do_foreach, 0); test_one (do_foreach, 1); test_one (do_foreach, 1000); } static void test_parallel_for_each () { struct test_worker { /* DUMMY is there to test passing multiple arguments to the worker constructor. */ test_worker (foreach_callback_t callback, int dummy) : m_callback (callback) { } void operator() (iterator_range range) { return m_callback (range); } private: foreach_callback_t m_callback; }; const std::vector for_each_functions { /* Test gdb::parallel_for_each. */ [] (int *start, int *end, foreach_callback_t callback) { gdb::parallel_for_each<1, int *, test_worker> (start, end, callback, 0); }, /* Test gdb::parallel_for_each_async. */ [] (int *start, int *end, foreach_callback_t callback) { bool done_flag = false; std::condition_variable cv; std::mutex mtx; gdb::parallel_for_each_async<1, int *, test_worker> (start, end, [&mtx, &done_flag, &cv] () { std::lock_guard lock (mtx); done_flag = true; cv.notify_one(); }, callback, 0); /* Wait for the async parallel-for to complete. */ std::unique_lock lock (mtx); cv.wait (lock, [&done_flag] () { return done_flag; }); }, /* Test gdb::sequential_for_each. */ [] (int *start, int *end, foreach_callback_t callback) { gdb::sequential_for_each (start, end, callback, 0); }, }; int default_thread_count = gdb::thread_pool::g_thread_pool->thread_count (); for (int n_threads : { 0, 1, 3, default_thread_count }) for (const auto &for_each_function : for_each_functions) test_one_function (n_threads, for_each_function); } } /* namespace parallel_for */ } /* namespace selftests */ #endif /* CXX_STD_THREAD */ INIT_GDB_FILE (parallel_for_selftests) { #ifdef CXX_STD_THREAD selftests::register_test ("parallel_for", selftests::parallel_for::test_parallel_for_each); #endif /* CXX_STD_THREAD */ }