#include "acutest/acutest.h" #include "config.hpp" #include "str.hpp" #include "thread_pool.hpp" #include #include #include #include void test_nop() { TEST_CHECK(true); } void test_config_bool() { ConfigBOOL v; TEST_CHECK(v.from_string("true") == 0); TEST_CHECK(v == true); TEST_CHECK(v.to_string() == "true"); TEST_CHECK(v.from_string("false") == 0); TEST_CHECK(v == false); TEST_CHECK(v.to_string() == "false"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_uint64() { ConfigU64 v; TEST_CHECK(v.from_string("0") == 0); TEST_CHECK(v == (uint64_t)0); TEST_CHECK(v.to_string() == "0"); TEST_CHECK(v.from_string("123") == 0); TEST_CHECK(v == (uint64_t)123); TEST_CHECK(v.to_string() == "123"); TEST_CHECK(v.from_string("1234567890") == 0); TEST_CHECK(v == (uint64_t)1234567890); TEST_CHECK(v.to_string() == "1234567890"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_int() { ConfigINT v; TEST_CHECK(v.from_string("0") == 0); TEST_CHECK(v == (int)0); TEST_CHECK(v.to_string() == "0"); TEST_CHECK(v.from_string("123") == 0); TEST_CHECK(v == (int)123); TEST_CHECK(v.to_string() == "123"); TEST_CHECK(v.from_string("1234567890") == 0); TEST_CHECK(v == (int)1234567890); TEST_CHECK(v.to_string() == "1234567890"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_str() { ConfigSTR v; TEST_CHECK(v.from_string("foo") == 0); TEST_CHECK(v == "foo"); TEST_CHECK(v.to_string() == "foo"); } void test_str_stuff() { std::vector v; std::set s; std::pair kv; // split() - vector v = str::split("",':'); TEST_CHECK(v.size() == 0); v = str::split("a:b:c",':'); TEST_CHECK(v.size() == 3); TEST_CHECK(v[0] == "a"); TEST_CHECK(v[1] == "b"); TEST_CHECK(v[2] == "c"); v = str::split("a::b:c",':'); TEST_CHECK(v.size() == 4); TEST_CHECK(v[0] == "a"); TEST_CHECK(v[1] == ""); TEST_CHECK(v[2] == "b"); TEST_CHECK(v[3] == "c"); v = str::split("single",':'); TEST_CHECK(v.size() == 1); TEST_CHECK(v[0] == "single"); // split_to_set() s = str::split_to_set("",':'); TEST_CHECK(s.size() == 0); s = str::split_to_set("a:b:c",':'); TEST_CHECK(s.size() == 3); TEST_CHECK(s.count("a") == 1); TEST_CHECK(s.count("b") == 1); TEST_CHECK(s.count("c") == 1); s = str::split_to_set("a:a:b",':'); TEST_CHECK(s.size() == 2); TEST_CHECK(s.count("a") == 1); TEST_CHECK(s.count("b") == 1); // split_on_null() std::string nullstr = "hello"; nullstr += '\0'; nullstr += "world"; nullstr += '\0'; nullstr += "test"; v = str::split_on_null(std::string_view(nullstr.data(), nullstr.size())); TEST_CHECK(v.size() == 3); TEST_CHECK(v[0] == "hello"); TEST_CHECK(v[1] == "world"); TEST_CHECK(v[2] == "test"); v = str::split_on_null(""); TEST_CHECK(v.size() == 0); // lsplit1() v = str::lsplit1("foo=bar=baz",'='); TEST_CHECK(v.size() == 2); TEST_CHECK(v[0] == "foo"); TEST_CHECK(v[1] == "bar=baz"); v = str::lsplit1("",'='); TEST_CHECK(v.size() == 0); v = str::lsplit1("no_delimiter",'='); TEST_CHECK(v.size() == 1); TEST_CHECK(v[0] == "no_delimiter"); // rsplit1() v = str::rsplit1("foo=bar=baz",'='); TEST_CHECK(v.size() == 2); TEST_CHECK(v[0] == "foo=bar"); TEST_CHECK(v[1] == "baz"); v = str::rsplit1("",'='); TEST_CHECK(v.size() == 0); v = str::rsplit1("no_delimiter",'='); TEST_CHECK(v.size() == 1); TEST_CHECK(v[0] == "no_delimiter"); // splitkv() kv = str::splitkv("key=value", '='); TEST_CHECK(kv.first == "key"); TEST_CHECK(kv.second == "value"); kv = str::splitkv("key=", '='); TEST_CHECK(kv.first == "key"); TEST_CHECK(kv.second == ""); kv = str::splitkv("key", '='); TEST_CHECK(kv.first == "key"); TEST_CHECK(kv.second == ""); kv = str::splitkv("", '='); TEST_CHECK(kv.first == ""); TEST_CHECK(kv.second == ""); // join() - vector with substr v = {"abc", "def", "ghi"}; std::string joined = str::join(v, 1, ':'); TEST_CHECK(joined == "bc:ef:hi"); // join() - vector joined = str::join(v, ':'); TEST_CHECK(joined == "abc:def:ghi"); v = {"single"}; joined = str::join(v, ':'); TEST_CHECK(joined == "single"); v = {}; joined = str::join(v, ':'); TEST_CHECK(joined == ""); // join() - set s = {"a", "b", "c"}; joined = str::join(s, ':'); TEST_CHECK(joined == "a:b:c"); s = {}; joined = str::join(s, ':'); TEST_CHECK(joined == ""); // longest_common_prefix_index() v = {"/foo/bar", "/foo/baz", "/foo/qux"}; TEST_CHECK(str::longest_common_prefix_index(v) == 5); v = {"/foo/bar"}; TEST_CHECK(str::longest_common_prefix_index(v) == std::string::npos); v = {}; TEST_CHECK(str::longest_common_prefix_index(v) == std::string::npos); v = {"abc", "xyz"}; TEST_CHECK(str::longest_common_prefix_index(v) == 0); // longest_common_prefix() v = {"/foo/bar", "/foo/baz"}; TEST_CHECK(str::longest_common_prefix(v) == "/foo/ba"); v = {"/foo/bar"}; // Single element - returns empty string (implementation detail) TEST_CHECK(str::longest_common_prefix(v) == ""); v = {}; TEST_CHECK(str::longest_common_prefix(v) == ""); // remove_common_prefix_and_join() v = {"/foo/bar", "/foo/baz", "/foo/qux"}; joined = str::remove_common_prefix_and_join(v, ':'); TEST_CHECK(joined == "bar:baz:qux"); v = {}; joined = str::remove_common_prefix_and_join(v, ':'); TEST_CHECK(joined == ""); // startswith() - string_view TEST_CHECK(str::startswith("hello world", "hello") == true); TEST_CHECK(str::startswith("hello world", "world") == false); TEST_CHECK(str::startswith("hello", "hello world") == false); TEST_CHECK(str::startswith("", "") == true); TEST_CHECK(str::startswith("hello", "") == true); // startswith() - char* TEST_CHECK(str::startswith("hello world", "hello") == true); TEST_CHECK(str::startswith("hello world", "world") == false); // endswith() TEST_CHECK(str::endswith("hello world", "world") == true); TEST_CHECK(str::endswith("hello world", "hello") == false); TEST_CHECK(str::endswith("hello", "hello world") == false); TEST_CHECK(str::endswith("", "") == true); TEST_CHECK(str::endswith("hello", "") == true); // trim() TEST_CHECK(str::trim(" hello ") == "hello"); TEST_CHECK(str::trim("hello") == "hello"); TEST_CHECK(str::trim(" ") == ""); TEST_CHECK(str::trim("") == ""); TEST_CHECK(str::trim("\t\nhello\r\n") == "hello"); TEST_CHECK(str::trim(" hello world ") == "hello world"); // eq() TEST_CHECK(str::eq("hello", "hello") == true); TEST_CHECK(str::eq("hello", "world") == false); TEST_CHECK(str::eq("", "") == true); // replace() TEST_CHECK(str::replace("hello world", ' ', '_') == "hello_world"); TEST_CHECK(str::replace("abc", 'x', 'y') == "abc"); TEST_CHECK(str::replace("", 'x', 'y') == ""); TEST_CHECK(str::replace("aaa", 'a', 'b') == "bbb"); // erase_fnmatches() v = {"foo.txt", "bar.cpp", "baz.h", "test.txt"}; std::vector patterns = {"*.txt"}; str::erase_fnmatches(patterns, v); TEST_CHECK(v.size() == 2); // Order may vary, just check elements exist bool has_bar_cpp = false; bool has_baz_h = false; for(const auto& s : v) { if(s == "bar.cpp") has_bar_cpp = true; if(s == "baz.h") has_baz_h = true; } TEST_CHECK(has_bar_cpp); TEST_CHECK(has_baz_h); v = {"foo.txt", "bar.cpp"}; patterns = {"*.txt", "*.cpp"}; str::erase_fnmatches(patterns, v); TEST_CHECK(v.size() == 0); v = {"foo.txt", "bar.cpp"}; patterns = {}; str::erase_fnmatches(patterns, v); TEST_CHECK(v.size() == 2); } void test_config_branches() { uint64_t minfreespace = 1234; Branches b; b.minfreespace = minfreespace; Branches::Ptr bcp0; Branches::Ptr bcp1; TEST_CHECK(b->minfreespace() == 1234); TEST_CHECK(b.to_string() == ""); // Parse initial value for branch TEST_CHECK(b.from_string(b.to_string()) == 0); bcp0 = b; TEST_CHECK(b.from_string("/foo/bar") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RW"); bcp1 = b; TEST_CHECK(bcp0.get() != bcp1.get()); TEST_CHECK(b.from_string("/foo/bar=RW,1234") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RW,1234"); TEST_CHECK(b.from_string("/foo/bar=RO,1234:/foo/baz=NC,4321") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RO,1234:/foo/baz=NC,4321"); bcp0 = b; TEST_CHECK((*bcp0).size() == 2); TEST_CHECK((*bcp0)[0].path == "/foo/bar"); TEST_CHECK((*bcp0)[0].minfreespace() == 1234); TEST_MSG("minfreespace: expected = %lu; produced = %lu", 1234UL, (*bcp0)[0].minfreespace()); TEST_CHECK((*bcp0)[1].path == "/foo/baz"); TEST_CHECK((*bcp0)[1].minfreespace() == 4321); TEST_MSG("minfreespace: expected = %lu; produced = %lu", 4321UL, (*bcp0)[1].minfreespace()); TEST_CHECK(b.from_string("foo/bar") == 0); TEST_CHECK(b.from_string("./foo/bar") == 0); TEST_CHECK(b.from_string("./foo/bar:/bar/baz:blah/asdf") == 0); } void test_config_cachefiles() { CacheFiles cf; TEST_CHECK(cf.from_string("off") == 0); TEST_CHECK(cf.to_string() == "off"); TEST_CHECK(cf.from_string("partial") == 0); TEST_CHECK(cf.to_string() == "partial"); TEST_CHECK(cf.from_string("full") == 0); TEST_CHECK(cf.to_string() == "full"); TEST_CHECK(cf.from_string("auto-full") == 0); TEST_CHECK(cf.to_string() == "auto-full"); TEST_CHECK(cf.from_string("foobar") == -EINVAL); } void test_config_inodecalc() { InodeCalc ic("passthrough"); TEST_CHECK(ic.from_string("passthrough") == 0); TEST_CHECK(ic.to_string() == "passthrough"); TEST_CHECK(ic.from_string("path-hash") == 0); TEST_CHECK(ic.to_string() == "path-hash"); TEST_CHECK(ic.from_string("devino-hash") == 0); TEST_CHECK(ic.to_string() == "devino-hash"); TEST_CHECK(ic.from_string("hybrid-hash") == 0); TEST_CHECK(ic.to_string() == "hybrid-hash"); TEST_CHECK(ic.from_string("path-hash32") == 0); TEST_CHECK(ic.to_string() == "path-hash32"); TEST_CHECK(ic.from_string("devino-hash32") == 0); TEST_CHECK(ic.to_string() == "devino-hash32"); TEST_CHECK(ic.from_string("hybrid-hash32") == 0); TEST_CHECK(ic.to_string() == "hybrid-hash32"); TEST_CHECK(ic.from_string("asdf") == -EINVAL); } void test_config_moveonenospc() { MoveOnENOSPC m(false); TEST_CHECK(m.to_string() == "false"); TEST_CHECK(m.from_string("mfs") == 0); TEST_CHECK(m.to_string() == "mfs"); TEST_CHECK(m.from_string("mspmfs") == 0); TEST_CHECK(m.to_string() == "mspmfs"); TEST_CHECK(m.from_string("true") == 0); TEST_CHECK(m.to_string() == "pfrd"); TEST_CHECK(m.from_string("blah") == -EINVAL); } void test_config_nfsopenhack() { NFSOpenHack n; TEST_CHECK(n.from_string("off") == 0); TEST_CHECK(n.to_string() == "off"); TEST_CHECK(n == NFSOpenHack::ENUM::OFF); TEST_CHECK(n.from_string("git") == 0); TEST_CHECK(n.to_string() == "git"); TEST_CHECK(n == NFSOpenHack::ENUM::GIT); TEST_CHECK(n.from_string("all") == 0); TEST_CHECK(n.to_string() == "all"); TEST_CHECK(n == NFSOpenHack::ENUM::ALL); } void test_config_readdir() { } void test_config_statfs() { StatFS s; TEST_CHECK(s.from_string("base") == 0); TEST_CHECK(s.to_string() == "base"); TEST_CHECK(s == StatFS::ENUM::BASE); TEST_CHECK(s.from_string("full") == 0); TEST_CHECK(s.to_string() == "full"); TEST_CHECK(s == StatFS::ENUM::FULL); TEST_CHECK(s.from_string("asdf") == -EINVAL); } void test_config_statfs_ignore() { StatFSIgnore s; TEST_CHECK(s.from_string("none") == 0); TEST_CHECK(s.to_string() == "none"); TEST_CHECK(s == StatFSIgnore::ENUM::NONE); TEST_CHECK(s.from_string("ro") == 0); TEST_CHECK(s.to_string() == "ro"); TEST_CHECK(s == StatFSIgnore::ENUM::RO); TEST_CHECK(s.from_string("nc") == 0); TEST_CHECK(s.to_string() == "nc"); TEST_CHECK(s == StatFSIgnore::ENUM::NC); TEST_CHECK(s.from_string("asdf") == -EINVAL); } void test_config_xattr() { XAttr x; TEST_CHECK(x.from_string("passthrough") == 0); TEST_CHECK(x.to_string() == "passthrough"); TEST_CHECK(x == XAttr::ENUM::PASSTHROUGH); TEST_CHECK(x.from_string("noattr") == 0); TEST_CHECK(x.to_string() == "noattr"); TEST_CHECK(x == XAttr::ENUM::NOATTR); TEST_CHECK(x.from_string("nosys") == 0); TEST_CHECK(x.to_string() == "nosys"); TEST_CHECK(x == XAttr::ENUM::NOSYS); TEST_CHECK(x.from_string("asdf") == -EINVAL); } void test_config() { Config cfg; TEST_CHECK(cfg.set("async-read","true") == 0); } // ===================================================================== // ThreadPool tests // ===================================================================== void test_tp_construct_default() { ThreadPool tp(2, 4, "test.default"); auto threads = tp.threads(); TEST_CHECK(threads.size() == 2); } void test_tp_construct_named() { ThreadPool tp(1, 2, "test.named"); auto threads = tp.threads(); TEST_CHECK(threads.size() == 1); } void test_tp_construct_zero_threads_throws() { bool threw = false; try { ThreadPool tp(0, 4, "test.zero"); } catch(const std::runtime_error &) { threw = true; } TEST_CHECK(threw); } void test_tp_enqueue_work() { ThreadPool tp(2, 4, "test.ew"); std::atomic counter{0}; for(int i = 0; i < 10; ++i) tp.enqueue_work([&counter](){ counter.fetch_add(1); }); // wait for completion for(int i = 0; i < 1000 && counter.load() < 10; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 10); } void test_tp_enqueue_work_with_ptoken() { ThreadPool tp(2, 4, "test.ewp"); std::atomic counter{0}; auto ptok = tp.ptoken(); for(int i = 0; i < 10; ++i) tp.enqueue_work(ptok, [&counter](){ counter.fetch_add(1); }); for(int i = 0; i < 1000 && counter.load() < 10; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 10); } void test_tp_try_enqueue_work() { // Use 1 thread and a small queue depth of 2 ThreadPool tp(1, 2, "test.tew"); std::atomic counter{0}; // These should succeed (queue has room) bool ok = tp.try_enqueue_work([&counter](){ counter.fetch_add(1); }); TEST_CHECK(ok); for(int i = 0; i < 1000 && counter.load() < 1; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 1); } void test_tp_try_enqueue_work_with_ptoken() { ThreadPool tp(1, 2, "test.tewp"); std::atomic counter{0}; auto ptok = tp.ptoken(); bool ok = tp.try_enqueue_work(ptok, [&counter](){ counter.fetch_add(1); }); TEST_CHECK(ok); for(int i = 0; i < 1000 && counter.load() < 1; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 1); } void test_tp_try_enqueue_work_for() { ThreadPool tp(1, 2, "test.tewf"); std::atomic counter{0}; bool ok = tp.try_enqueue_work_for(100000, // 100ms [&counter](){ counter.fetch_add(1); }); TEST_CHECK(ok); for(int i = 0; i < 1000 && counter.load() < 1; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 1); } void test_tp_try_enqueue_work_for_with_ptoken() { ThreadPool tp(1, 2, "test.tewfp"); std::atomic counter{0}; auto ptok = tp.ptoken(); bool ok = tp.try_enqueue_work_for(ptok, 100000, // 100ms [&counter](){ counter.fetch_add(1); }); TEST_CHECK(ok); for(int i = 0; i < 1000 && counter.load() < 1; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 1); } void test_tp_enqueue_task_int() { ThreadPool tp(2, 4, "test.eti"); auto future = tp.enqueue_task([]() -> int { return 42; }); TEST_CHECK(future.get() == 42); } void test_tp_enqueue_task_void() { ThreadPool tp(2, 4, "test.etv"); std::atomic ran{false}; auto future = tp.enqueue_task([&ran]() { ran.store(true); }); future.get(); // should not throw TEST_CHECK(ran.load()); } void test_tp_enqueue_task_string() { ThreadPool tp(2, 4, "test.ets"); auto future = tp.enqueue_task([]() -> std::string { return "hello"; }); TEST_CHECK(future.get() == "hello"); } void test_tp_enqueue_task_exception() { ThreadPool tp(2, 4, "test.ete"); auto future = tp.enqueue_task([]() -> int { throw std::runtime_error("task error"); return 0; }); bool caught = false; try { future.get(); } catch(const std::runtime_error &e) { caught = true; TEST_CHECK(std::string(e.what()) == "task error"); } TEST_CHECK(caught); } void test_tp_threads_returns_correct_count() { ThreadPool tp(4, 8, "test.thr"); auto threads = tp.threads(); TEST_CHECK(threads.size() == 4); // All thread IDs should be non-zero for(auto t : threads) TEST_CHECK(t != 0); } void test_tp_threads_unique_ids() { ThreadPool tp(4, 8, "test.uid"); auto threads = tp.threads(); // All thread IDs should be unique for(std::size_t i = 0; i < threads.size(); ++i) for(std::size_t j = i + 1; j < threads.size(); ++j) TEST_CHECK(threads[i] != threads[j]); } void test_tp_add_thread() { ThreadPool tp(2, 4, "test.add"); TEST_CHECK(tp.threads().size() == 2); int rv = tp.add_thread(); TEST_CHECK(rv == 0); TEST_CHECK(tp.threads().size() == 3); } void test_tp_remove_thread() { ThreadPool tp(3, 4, "test.rm"); TEST_CHECK(tp.threads().size() == 3); int rv = tp.remove_thread(); TEST_CHECK(rv == 0); TEST_CHECK(tp.threads().size() == 2); } void test_tp_remove_thread_refuses_last() { ThreadPool tp(1, 4, "test.rmlast"); TEST_CHECK(tp.threads().size() == 1); int rv = tp.remove_thread(); TEST_CHECK(rv == -EINVAL); TEST_CHECK(tp.threads().size() == 1); } void test_tp_set_threads_grow() { ThreadPool tp(2, 8, "test.grow"); TEST_CHECK(tp.threads().size() == 2); int rv = tp.set_threads(4); TEST_CHECK(rv == 0); TEST_CHECK(tp.threads().size() == 4); } void test_tp_set_threads_shrink() { ThreadPool tp(4, 8, "test.shrink"); TEST_CHECK(tp.threads().size() == 4); int rv = tp.set_threads(2); TEST_CHECK(rv == 0); TEST_CHECK(tp.threads().size() == 2); } void test_tp_set_threads_zero() { ThreadPool tp(2, 4, "test.sz"); int rv = tp.set_threads(0); TEST_CHECK(rv == -EINVAL); TEST_CHECK(tp.threads().size() == 2); } void test_tp_set_threads_same() { ThreadPool tp(3, 4, "test.same"); int rv = tp.set_threads(3); TEST_CHECK(rv == 0); TEST_CHECK(tp.threads().size() == 3); } void test_tp_work_after_add_thread() { ThreadPool tp(1, 4, "test.waat"); tp.add_thread(); tp.add_thread(); std::atomic counter{0}; const int N = 20; for(int i = 0; i < N; ++i) tp.enqueue_work([&counter](){ counter.fetch_add(1); }); for(int i = 0; i < 2000 && counter.load() < N; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == N); } void test_tp_work_after_remove_thread() { ThreadPool tp(3, 4, "test.wart"); tp.remove_thread(); std::atomic counter{0}; const int N = 10; for(int i = 0; i < N; ++i) tp.enqueue_work([&counter](){ counter.fetch_add(1); }); for(int i = 0; i < 2000 && counter.load() < N; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == N); } void test_tp_worker_exception_no_crash() { ThreadPool tp(2, 4, "test.exc"); // Enqueue work that throws -- pool should not crash tp.enqueue_work([](){ throw std::runtime_error("deliberate error"); }); // Enqueue normal work after the exception std::atomic ran{false}; tp.enqueue_work([&ran](){ ran.store(true); }); for(int i = 0; i < 1000 && !ran.load(); ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(ran.load()); } void test_tp_concurrent_enqueue() { ThreadPool tp(4, 64, "test.conc"); std::atomic counter{0}; const int PRODUCERS = 4; const int ITEMS_PER = 50; const int TOTAL = PRODUCERS * ITEMS_PER; std::vector producers; for(int p = 0; p < PRODUCERS; ++p) { producers.emplace_back([&tp, &counter]() { auto ptok = tp.ptoken(); for(int i = 0; i < ITEMS_PER; ++i) tp.enqueue_work(ptok, [&counter](){ counter.fetch_add(1); }); }); } for(auto &t : producers) t.join(); for(int i = 0; i < 5000 && counter.load() < TOTAL; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == TOTAL); } void test_tp_enqueue_task_multiple() { ThreadPool tp(4, 16, "test.etm"); std::vector> futures; const int N = 20; for(int i = 0; i < N; ++i) futures.push_back(tp.enqueue_task([i]() -> int { return i * i; })); for(int i = 0; i < N; ++i) TEST_CHECK(futures[i].get() == i * i); } void test_tp_ptoken_creation() { ThreadPool tp(2, 4, "test.ptok"); // Multiple ptokens should be independently usable auto ptok1 = tp.ptoken(); auto ptok2 = tp.ptoken(); std::atomic counter{0}; tp.enqueue_work(ptok1, [&counter](){ counter.fetch_add(1); }); tp.enqueue_work(ptok2, [&counter](){ counter.fetch_add(1); }); for(int i = 0; i < 1000 && counter.load() < 2; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); TEST_CHECK(counter.load() == 2); } void test_tp_destruction_drains_queue() { std::atomic counter{0}; { ThreadPool tp(2, 64, "test.drain"); for(int i = 0; i < 50; ++i) tp.enqueue_work([&counter](){ counter.fetch_add(1); }); // destructor runs here -- should drain all queued work } TEST_CHECK(counter.load() == 50); } void test_tp_move_only_callable() { ThreadPool tp(2, 4, "test.moc"); auto ptr = std::make_unique(99); auto future = tp.enqueue_task([p = std::move(ptr)]() -> int { return *p; }); TEST_CHECK(future.get() == 99); } void test_tp_work_ordering_single_thread() { // With a single thread, work should execute in FIFO order ThreadPool tp(1, 16, "test.order"); std::vector results; std::mutex mtx; const int N = 10; for(int i = 0; i < N; ++i) tp.enqueue_work([i, &results, &mtx](){ std::lock_guard lk(mtx); results.push_back(i); }); // Wait for all work to complete for(int i = 0; i < 2000; ++i) { { std::lock_guard lk(mtx); if((int)results.size() == N) break; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } std::lock_guard lk(mtx); TEST_CHECK((int)results.size() == N); for(int i = 0; i < N; ++i) TEST_CHECK(results[i] == i); } TEST_LIST = { {"nop",test_nop}, {"config_bool",test_config_bool}, {"config_uint64",test_config_uint64}, {"config_int",test_config_int}, {"config_str",test_config_str}, {"config_branches",test_config_branches}, {"config_cachefiles",test_config_cachefiles}, {"config_inodecalc",test_config_inodecalc}, {"config_moveonenospc",test_config_moveonenospc}, {"config_nfsopenhack",test_config_nfsopenhack}, {"config_readdir",test_config_readdir}, {"config_statfs",test_config_statfs}, {"config_statfsignore",test_config_statfs_ignore}, {"config_xattr",test_config_xattr}, {"config",test_config}, {"str",test_str_stuff}, {"tp_construct_default",test_tp_construct_default}, {"tp_construct_named",test_tp_construct_named}, {"tp_construct_zero_threads_throws",test_tp_construct_zero_threads_throws}, {"tp_enqueue_work",test_tp_enqueue_work}, {"tp_enqueue_work_with_ptoken",test_tp_enqueue_work_with_ptoken}, {"tp_try_enqueue_work",test_tp_try_enqueue_work}, {"tp_try_enqueue_work_with_ptoken",test_tp_try_enqueue_work_with_ptoken}, {"tp_try_enqueue_work_for",test_tp_try_enqueue_work_for}, {"tp_try_enqueue_work_for_with_ptoken",test_tp_try_enqueue_work_for_with_ptoken}, {"tp_enqueue_task_int",test_tp_enqueue_task_int}, {"tp_enqueue_task_void",test_tp_enqueue_task_void}, {"tp_enqueue_task_string",test_tp_enqueue_task_string}, {"tp_enqueue_task_exception",test_tp_enqueue_task_exception}, {"tp_enqueue_task_multiple",test_tp_enqueue_task_multiple}, {"tp_threads_returns_correct_count",test_tp_threads_returns_correct_count}, {"tp_threads_unique_ids",test_tp_threads_unique_ids}, {"tp_add_thread",test_tp_add_thread}, {"tp_remove_thread",test_tp_remove_thread}, {"tp_remove_thread_refuses_last",test_tp_remove_thread_refuses_last}, {"tp_set_threads_grow",test_tp_set_threads_grow}, {"tp_set_threads_shrink",test_tp_set_threads_shrink}, {"tp_set_threads_zero",test_tp_set_threads_zero}, {"tp_set_threads_same",test_tp_set_threads_same}, {"tp_work_after_add_thread",test_tp_work_after_add_thread}, {"tp_work_after_remove_thread",test_tp_work_after_remove_thread}, {"tp_worker_exception_no_crash",test_tp_worker_exception_no_crash}, {"tp_concurrent_enqueue",test_tp_concurrent_enqueue}, {"tp_ptoken_creation",test_tp_ptoken_creation}, {"tp_destruction_drains_queue",test_tp_destruction_drains_queue}, {"tp_move_only_callable",test_tp_move_only_callable}, {"tp_work_ordering_single_thread",test_tp_work_ordering_single_thread}, {NULL,NULL} };