From 10d05d9c46aaa726f951601dc2f0f1f8da4ac083 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 11 Oct 2023 14:39:10 -0700 Subject: [PATCH 1/2] [libc++] Introduce make_test_jthread for jthread tests This patch introduces the support::make_test_jthread utility which is basically the same as support::make_test_thread but for std::jthread. It allows vendors to maintain a downstream way to create threads for use within the test suite, which is especially useful for embedded platforms. --- .../thread.jthread/assign.move.pass.cpp | 37 ++++++++++--------- .../thread/thread.jthread/cons.move.pass.cpp | 9 +++-- .../std/thread/thread.jthread/detach.pass.cpp | 7 ++-- .../std/thread/thread.jthread/dtor.pass.cpp | 10 +++-- .../std/thread/thread.jthread/get_id.pass.cpp | 3 +- .../thread.jthread/get_stop_source.pass.cpp | 3 +- .../thread.jthread/get_stop_token.pass.cpp | 3 +- .../thread.jthread/join.deadlock.pass.cpp | 5 ++- .../std/thread/thread.jthread/join.pass.cpp | 11 +++--- .../thread/thread.jthread/joinable.pass.cpp | 7 ++-- .../thread.jthread/request_stop.pass.cpp | 5 ++- .../thread/thread.jthread/swap.free.pass.cpp | 9 +++-- .../thread.jthread/swap.member.pass.cpp | 9 +++-- libcxx/test/support/make_test_thread.h | 21 +++++++++++ 14 files changed, 87 insertions(+), 52 deletions(-) diff --git a/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp index b932ac39d2f377..89521ad7660a12 100644 --- a/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/assign.move.pass.cpp @@ -23,6 +23,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(std::is_nothrow_move_assignable_v); @@ -30,10 +31,10 @@ static_assert(std::is_nothrow_move_assignable_v); int main(int, char**) { // If &x == this is true, there are no effects. { - std::jthread j([] {}); - auto id = j.get_id(); - auto ssource = j.get_stop_source(); - j = std::move(j); + std::jthread j = support::make_test_jthread([] {}); + auto id = j.get_id(); + auto ssource = j.get_stop_source(); + j = std::move(j); assert(j.get_id() == id); assert(j.get_stop_source() == ssource); } @@ -41,12 +42,12 @@ int main(int, char**) { // if joinable() is true, calls request_stop() and then join() // request_stop is called { - std::jthread j1([] {}); - bool called = false; + std::jthread j1 = support::make_test_jthread([] {}); + bool called = false; std::stop_callback cb(j1.get_stop_token(), [&called] { called = true; }); - std::jthread j2([] {}); - j1 = std::move(j2); + std::jthread j2 = support::make_test_jthread([] {}); + j1 = std::move(j2); assert(called); } @@ -58,10 +59,10 @@ int main(int, char**) { constexpr auto numberOfThreads = 10u; jts.reserve(numberOfThreads); for (auto i = 0u; i < numberOfThreads; ++i) { - jts.emplace_back([&] { + jts.emplace_back(support::make_test_jthread([&] { std::this_thread::sleep_for(std::chrono::milliseconds(2)); calledTimes.fetch_add(1, std::memory_order_relaxed); - }); + })); } for (auto i = 0u; i < numberOfThreads; ++i) { @@ -79,10 +80,10 @@ int main(int, char**) { // then assigns the state of x to *this { - std::jthread j1([] {}); - std::jthread j2([] {}); - auto id2 = j2.get_id(); - auto ssource2 = j2.get_stop_source(); + std::jthread j1 = support::make_test_jthread([] {}); + std::jthread j2 = support::make_test_jthread([] {}); + auto id2 = j2.get_id(); + auto ssource2 = j2.get_stop_source(); j1 = std::move(j2); @@ -92,9 +93,9 @@ int main(int, char**) { // sets x to a default constructed state { - std::jthread j1([] {}); - std::jthread j2([] {}); - j1 = std::move(j2); + std::jthread j1 = support::make_test_jthread([] {}); + std::jthread j2 = support::make_test_jthread([] {}); + j1 = std::move(j2); assert(j2.get_id() == std::jthread::id()); assert(!j2.get_stop_source().stop_possible()); @@ -103,7 +104,7 @@ int main(int, char**) { // joinable is false { std::jthread j1; - std::jthread j2([] {}); + std::jthread j2 = support::make_test_jthread([] {}); auto j2Id = j2.get_id(); diff --git a/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp index 9eacf8971c2a58..c3c04467703c96 100644 --- a/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/cons.move.pass.cpp @@ -19,6 +19,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(std::is_nothrow_move_constructible_v); @@ -27,8 +28,8 @@ int main(int, char**) { { // x.get_id() == id() and get_id() returns the value of x.get_id() prior // to the start of construction. - std::jthread j1{[] {}}; - auto id1 = j1.get_id(); + std::jthread j1 = support::make_test_jthread([] {}); + auto id1 = j1.get_id(); std::jthread j2(std::move(j1)); assert(j1.get_id() == std::jthread::id()); @@ -38,8 +39,8 @@ int main(int, char**) { { // ssource has the value of x.ssource prior to the start of construction // and x.ssource.stop_possible() is false. - std::jthread j1{[] {}}; - auto ss1 = j1.get_stop_source(); + std::jthread j1 = support::make_test_jthread([] {}); + auto ss1 = j1.get_stop_source(); std::jthread j2(std::move(j1)); assert(ss1 == j2.get_stop_source()); diff --git a/libcxx/test/std/thread/thread.jthread/detach.pass.cpp b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp index ee48d2691e6843..54fd5fd93bed6f 100644 --- a/libcxx/test/std/thread/thread.jthread/detach.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/detach.pass.cpp @@ -23,6 +23,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" int main(int, char**) { @@ -30,10 +31,10 @@ int main(int, char**) { { std::atomic_bool start{false}; std::atomic_bool done{false}; - std::optional jt{[&start, &done] { + std::optional jt = support::make_test_jthread([&start, &done] { start.wait(false); done = true; - }}; + }); // If it blocks, it will deadlock here jt->detach(); @@ -49,7 +50,7 @@ int main(int, char**) { // Postconditions: get_id() == id(). { - std::jthread jt{[] {}}; + std::jthread jt = support::make_test_jthread([] {}); assert(jt.get_id() != std::jthread::id()); jt.detach(); assert(jt.get_id() == std::jthread::id()); diff --git a/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp index 47ee62023f62d9..35be0f6c0dd82c 100644 --- a/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/dtor.pass.cpp @@ -20,6 +20,8 @@ #include #include #include + +#include "make_test_thread.h" #include "test_macros.h" int main(int, char**) { @@ -32,8 +34,8 @@ int main(int, char**) { // If joinable() is true, calls request_stop() and then join(). // request_stop is called { - std::optional jt([] {}); - bool called = false; + std::optional jt = support::make_test_jthread([] {}); + bool called = false; std::stop_callback cb(jt->get_stop_token(), [&called] { called = true; }); jt.reset(); assert(called); @@ -48,10 +50,10 @@ int main(int, char**) { constexpr auto numberOfThreads = 10u; jts.reserve(numberOfThreads); for (auto i = 0u; i < numberOfThreads; ++i) { - jts.emplace_back([&calledTimes] { + jts.emplace_back(support::make_test_jthread([&calledTimes] { std::this_thread::sleep_for(std::chrono::milliseconds{2}); calledTimes.fetch_add(1, std::memory_order_relaxed); - }); + })); } jts.clear(); diff --git a/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp index f92472d3d8dc64..b3a2beff9f416a 100644 --- a/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/get_id.pass.cpp @@ -18,6 +18,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(noexcept(std::declval().get_id())); @@ -32,7 +33,7 @@ int main(int, char**) { // Represents a thread { - const std::jthread jt{[] {}}; + const std::jthread jt = support::make_test_jthread([] {}); std::same_as decltype(auto) result = jt.get_id(); assert(result != std::jthread::id()); } diff --git a/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp index 41df2d894f45df..8f35db297b7496 100644 --- a/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/get_stop_source.pass.cpp @@ -19,6 +19,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(noexcept(std::declval().get_stop_source())); @@ -26,7 +27,7 @@ static_assert(noexcept(std::declval().get_stop_source())); int main(int, char**) { // Represents a thread { - std::jthread jt{[] {}}; + std::jthread jt = support::make_test_jthread([] {}); std::same_as decltype(auto) result = jt.get_stop_source(); assert(result.stop_possible()); } diff --git a/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp index c65d39b3cdf4a7..070761e0a3ab88 100644 --- a/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/get_stop_token.pass.cpp @@ -20,6 +20,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(noexcept(std::declval().get_stop_token())); @@ -27,7 +28,7 @@ static_assert(noexcept(std::declval().get_stop_token())); int main(int, char**) { // Represents a thread { - std::jthread jt{[] {}}; + std::jthread jt = support::make_test_jthread([] {}); auto ss = jt.get_stop_source(); std::same_as decltype(auto) st = std::as_const(jt).get_stop_token(); diff --git a/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp b/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp index aa5cdf2783dba0..8e2f1e5f5d9d47 100644 --- a/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/join.deadlock.pass.cpp @@ -31,6 +31,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" int main(int, char**) { @@ -40,12 +41,12 @@ int main(int, char**) { std::atomic_bool start = false; std::atomic_bool done = false; - std::jthread jt{[&] { + std::jthread jt = support::make_test_jthread([&] { start.wait(false); f(); done = true; done.notify_all(); - }}; + }); f = [&] { try { diff --git a/libcxx/test/std/thread/thread.jthread/join.pass.cpp b/libcxx/test/std/thread/thread.jthread/join.pass.cpp index 38986bdfed8d74..2bafd863382475 100644 --- a/libcxx/test/std/thread/thread.jthread/join.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/join.pass.cpp @@ -23,6 +23,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" int main(int, char**) { @@ -33,10 +34,10 @@ int main(int, char**) { constexpr auto numberOfThreads = 10u; jts.reserve(numberOfThreads); for (auto i = 0u; i < numberOfThreads; ++i) { - jts.emplace_back([&] { + jts.emplace_back(support::make_test_jthread([&] { std::this_thread::sleep_for(std::chrono::milliseconds(2)); calledTimes.fetch_add(1, std::memory_order_relaxed); - }); + })); } for (auto i = 0u; i < numberOfThreads; ++i) { @@ -55,15 +56,15 @@ int main(int, char**) { // Synchronization: The completion of the thread represented by *this synchronizes with // ([intro.multithread]) the corresponding successful join() return. { - bool flag = false; - std::jthread jt{[&] { flag = true; }}; + bool flag = false; + std::jthread jt = support::make_test_jthread([&] { flag = true; }); jt.join(); assert(flag); // non atomic write is visible to the current thread } // Postconditions: The thread represented by *this has completed. get_id() == id(). { - std::jthread jt{[] {}}; + std::jthread jt = support::make_test_jthread([] {}); assert(jt.get_id() != std::jthread::id()); jt.join(); assert(jt.get_id() == std::jthread::id()); diff --git a/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp index 5c0fbece4c21e4..3a88100d934dbf 100644 --- a/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/joinable.pass.cpp @@ -19,6 +19,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(noexcept(std::declval().joinable())); @@ -33,7 +34,7 @@ int main(int, char**) { // Non-default constructed { - const std::jthread jt{[] {}}; + const std::jthread jt = support::make_test_jthread([] {}); std::same_as decltype(auto) result = jt.joinable(); assert(result); } @@ -41,8 +42,8 @@ int main(int, char**) { // Non-default constructed // the thread of execution has not finished { - std::atomic_bool done = false; - const std::jthread jt{[&done] { done.wait(false); }}; + std::atomic_bool done = false; + const std::jthread jt = support::make_test_jthread([&done] { done.wait(false); }); std::same_as decltype(auto) result = jt.joinable(); done = true; done.notify_all(); diff --git a/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp index f1109561cf9f29..ccbea9f145e504 100644 --- a/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/request_stop.pass.cpp @@ -19,6 +19,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" static_assert(noexcept(std::declval().request_stop())); @@ -26,8 +27,8 @@ static_assert(noexcept(std::declval().request_stop())); int main(int, char**) { // Represents a thread { - std::jthread jt{[] {}}; - auto st = jt.get_stop_token(); + std::jthread jt = support::make_test_jthread([] {}); + auto st = jt.get_stop_token(); assert(!st.stop_requested()); std::same_as decltype(auto) result = jt.request_stop(); assert(result); diff --git a/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp index 776537cdff4835..01c8ccd659687a 100644 --- a/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/swap.free.pass.cpp @@ -17,6 +17,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" template @@ -30,7 +31,7 @@ int main(int, char**) { // x is default constructed { std::jthread t1; - std::jthread t2{[] {}}; + std::jthread t2 = support::make_test_jthread([] {}); const auto originalId2 = t2.get_id(); swap(t1, t2); @@ -40,7 +41,7 @@ int main(int, char**) { // y is default constructed { - std::jthread t1([] {}); + std::jthread t1 = support::make_test_jthread([] {}); std::jthread t2{}; const auto originalId1 = t1.get_id(); swap(t1, t2); @@ -51,8 +52,8 @@ int main(int, char**) { // both not default constructed { - std::jthread t1([] {}); - std::jthread t2{[] {}}; + std::jthread t1 = support::make_test_jthread([] {}); + std::jthread t2 = support::make_test_jthread([] {}); const auto originalId1 = t1.get_id(); const auto originalId2 = t2.get_id(); swap(t1, t2); diff --git a/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp index 614e3ac8312dab..8ae17f435aa31b 100644 --- a/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp +++ b/libcxx/test/std/thread/thread.jthread/swap.member.pass.cpp @@ -17,6 +17,7 @@ #include #include +#include "make_test_thread.h" #include "test_macros.h" template @@ -30,7 +31,7 @@ int main(int, char**) { // this is default constructed { std::jthread t1; - std::jthread t2{[] {}}; + std::jthread t2 = support::make_test_jthread([] {}); const auto originalId2 = t2.get_id(); t1.swap(t2); @@ -40,7 +41,7 @@ int main(int, char**) { // that is default constructed { - std::jthread t1([] {}); + std::jthread t1 = support::make_test_jthread([] {}); std::jthread t2{}; const auto originalId1 = t1.get_id(); t1.swap(t2); @@ -51,8 +52,8 @@ int main(int, char**) { // both not default constructed { - std::jthread t1([] {}); - std::jthread t2{[] {}}; + std::jthread t1 = support::make_test_jthread([] {}); + std::jthread t2 = support::make_test_jthread([] {}); const auto originalId1 = t1.get_id(); const auto originalId2 = t2.get_id(); t1.swap(t2); diff --git a/libcxx/test/support/make_test_thread.h b/libcxx/test/support/make_test_thread.h index eaf967e2180ede..d1f7778df687e0 100644 --- a/libcxx/test/support/make_test_thread.h +++ b/libcxx/test/support/make_test_thread.h @@ -12,13 +12,34 @@ #include #include +#include "test_macros.h" + namespace support { +// These functions are used to mock the creation of threads within the test suite. +// +// This provides a vendor-friendly way of making the test suite work even on platforms +// where the standard thread constructors don't work (e.g. embedded environments where +// creating a thread requires additional information like setting attributes). +// +// Vendors can keep a downstream diff in this file to create threads however they +// need on their platform, and the majority of the test suite will work out of the +// box. Of course, tests that exercise the standard thread constructors won't work, +// but any other test that only creates threads as a side effect of testing should +// work if they use the utilities in this file. + template std::thread make_test_thread(F&& f, Args&& ...args) { return std::thread(std::forward(f), std::forward(args)...); } +#if TEST_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) +template +std::jthread make_test_jthread(F&& f, Args&& ...args) { + return std::jthread(std::forward(f), std::forward(args)...); +} +#endif + } // end namespace support #endif // TEST_SUPPORT_MAKE_TEST_THREAD_H From f7771d852f9078f3b3cecfaae7085edac07e34ec Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 12 Oct 2023 20:53:22 -0700 Subject: [PATCH 2/2] Fix availability markup --- libcxx/test/support/make_test_thread.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libcxx/test/support/make_test_thread.h b/libcxx/test/support/make_test_thread.h index d1f7778df687e0..cd548fd909d71f 100644 --- a/libcxx/test/support/make_test_thread.h +++ b/libcxx/test/support/make_test_thread.h @@ -34,9 +34,15 @@ std::thread make_test_thread(F&& f, Args&& ...args) { } #if TEST_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) -template -std::jthread make_test_jthread(F&& f, Args&& ...args) { - return std::jthread(std::forward(f), std::forward(args)...); +# ifdef _LIBCPP_VERSION +# define TEST_AVAILABILITY_SYNC _LIBCPP_AVAILABILITY_SYNC +# else +# define TEST_AVAILABILITY_SYNC +# endif + +template +TEST_AVAILABILITY_SYNC std::jthread make_test_jthread(F&& f, Args&&... args) { + return std::jthread(std::forward(f), std::forward(args)...); } #endif