Algo/randomx/mingw-std-threads-master/tests/tests.cpp

451 lines
15 KiB
C++

#ifndef MINGW_STDTHREADS_GENERATED_STDHEADERS
#include <mingw.thread.h>
#include <mingw.mutex.h>
#include <mingw.condition_variable.h>
#include <mingw.shared_mutex.h>
#include <mingw.future.h>
#else
#include <thread>
#include <mutex>
#include <condition_variable>
#include <shared_mutex>
#include <future>
#endif
#include <atomic>
#include <cassert>
#include <string>
#include <iostream>
#include <typeinfo>
using namespace std;
int test_int = 42;
// Pre-declaration to suppress some warnings.
void test_call_once(int, char const *);
int cond = 0;
std::mutex m;
std::shared_mutex sm;
std::condition_variable cv;
std::condition_variable_any cv_any;
template<class ... Args>
void log (char const * fmtString, Args ...args) {
printf(fmtString, args...);
printf("\n");
fflush(stdout);
}
void test_call_once(int a, const char* str)
{
log("test_call_once called with a=%d, str=%s", a, str);
this_thread::sleep_for(std::chrono::milliseconds(500));
}
struct TestMove
{
std::string mStr;
TestMove(const std::string& aStr): mStr(aStr){}
TestMove(TestMove&& other): mStr(other.mStr+" moved")
{ printf("%s: Object moved\n", mStr.c_str()); }
TestMove(const TestMove&) : mStr()
{
assert(false && "TestMove: Object COPIED instead of moved");
}
};
template<class T>
void test_future_set_value (promise<T> & promise)
{
promise.set_value(T(test_int));
}
template<>
void test_future_set_value (promise<void> & promise)
{
promise.set_value();
}
template<class T>
bool test_future_get_value (future<T> & future)
{
return (future.get() == T(test_int));
}
template<>
bool test_future_get_value (future<void> & future)
{
future.get();
return true;
}
template<class T>
struct CustomAllocator
{
CustomAllocator (void) noexcept
{
}
template<class U>
CustomAllocator (CustomAllocator<U> const &) noexcept
{
}
template<class U>
CustomAllocator<T> & operator= (CustomAllocator<U> const &) noexcept
{
return *this;
}
typedef T value_type;
T * allocate (size_t n)
{
log("Used custom allocator to allocate %zu object(s).", n);
return static_cast<T*>(std::malloc(n * sizeof(T)));
}
void deallocate (T * ptr, size_t n)
{
log("Used custom allocator to deallocate %zu object(s).", n);
std::free(ptr);
}
};
template<class T>
void test_future ()
{
static_assert(is_move_constructible<promise<T> >::value,
"std::promise must be move-constructible.");
static_assert(is_move_assignable<promise<T> >::value,
"std::promise must be move-assignable.");
static_assert(!is_copy_constructible<promise<T> >::value,
"std::promise must not be copy-constructible.");
static_assert(!is_copy_assignable<promise<T> >::value,
"std::promise must not be copy-assignable.");
static_assert(is_move_constructible<future<T> >::value,
"std::future must be move-constructible.");
static_assert(is_move_assignable<future<T> >::value,
"std::future must be move-assignable.");
static_assert(!is_copy_constructible<future<T> >::value,
"std::future must not be copy-constructible.");
static_assert(!is_copy_assignable<future<T> >::value,
"std::future must not be copy-assignable.");
static_assert(is_move_constructible<shared_future<T> >::value,
"std::shared_future must be move-constructible.");
static_assert(is_move_assignable<shared_future<T> >::value,
"std::shared_future must be move-assignable.");
static_assert(is_copy_constructible<shared_future<T> >::value,
"std::shared_future must be copy-constructible.");
static_assert(is_copy_assignable<shared_future<T> >::value,
"std::shared_future must be copy-assignable.");
log("\tMaking a few promises, and getting their futures...");
promise<T> promise_value, promise_exception, promise_broken, promise_late;
future<T> future_value = promise_value.get_future();
future<T> future_exception = promise_exception.get_future();
future<T> future_broken = promise_broken.get_future();
future<T> future_late = promise_late.get_future();
try {
future<T> impossible_future = promise_value.get_future();
log("WARNING: Promise failed to detect that its future was already retrieved.");
} catch(...) {
log("\tPromise successfully prevented redundant future retrieval.");
}
log("\tPassing promises to a new thread...");
thread t ([](promise<T> p_value, promise<T> p_exception, promise<T>, promise<T> p_late)
{
this_thread::sleep_for(std::chrono::seconds(1));
try {
throw std::runtime_error("Thrown during the thread.");
} catch (...) {
p_late.set_exception_at_thread_exit(std::current_exception());
}
test_future_set_value(p_value);
try {
throw std::runtime_error("Things happened as expected.");
} catch (...) {
p_exception.set_exception(std::current_exception());
}
this_thread::sleep_for(std::chrono::seconds(2));
},
std::move(promise_value),
std::move(promise_exception),
std::move(promise_broken),
std::move(promise_late));
t.detach();
try {
bool was_expected = test_future_get_value(future_value);
log("\tReceived %sexpected value.", (was_expected ? "" : "un"));
} catch (...) {
log("WARNING: Exception where there should be none!");
throw;
}
try {
test_future_get_value(future_exception);
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
log("\tReceived an exception (\"%s\") as expected.", e.what());
}
log("\tWaiting for the thread to exit...");
try {
test_future_get_value(future_late);
log("WARNING: Got a value where there should be an exception!");
} catch (std::exception & e) {
log("\tReceived an exception (\"%s\") as expected.", e.what());
}
try {
test_future_get_value(future_broken);
log("WARNING: Got a value where there should be an exception!");
} catch (std::future_error & e) {
log("\tReceived a future_error (\"%s\") as expected.", e.what());
}
log("\tDeferring a function...");
auto async_deferred = async(launch::deferred, [] (void) -> T
{
std::hash<std::thread::id> hasher;
log("\t\tDeferred function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
log("\tCalling a function asynchronously...");
auto async_async = async(launch::async, [] (void) -> T
{
std::hash<std::thread::id> hasher;
log("\t\tAsynchronous function called on thread %zu", hasher(std::this_thread::get_id()));
if (!is_void<T>::value)
return T(test_int);
});
log("\tLetting the implementation decide...");
auto async_either = async([] (thread::id other_id) -> T
{
std::hash<thread::id> hasher;
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}, this_thread::get_id());
log("\tFetching asynchronous result.");
test_future_get_value(async_async);
log("\tFetching deferred result.");
test_future_get_value(async_deferred);
log("\tFetching implementation-defined result.");
test_future_get_value(async_either);
log("\tTesting async on pointer-to-member-function.");
struct Helper
{
thread::id other_id;
T call (void) const
{
std::hash<thread::id> hasher;
log("\t\tFunction called on thread %zu. Implementation chose %s execution.", hasher(this_thread::get_id()), (this_thread::get_id() == other_id) ? "deferred" : "asynchronous");
if (!is_void<T>::value)
return T(test_int);
}
} test_class { this_thread::get_id() };
auto async_member = async(Helper::call, test_class);
log("\tFetching result.");
test_future_get_value(async_member);
}
#define TEST_SL_MV_CPY(ClassName) \
static_assert(std::is_standard_layout<ClassName>::value, \
"ClassName does not satisfy concept StandardLayoutType."); \
static_assert(!std::is_move_constructible<ClassName>::value, \
"ClassName must not be move-constructible."); \
static_assert(!std::is_move_assignable<ClassName>::value, \
"ClassName must not be move-assignable."); \
static_assert(!std::is_copy_constructible<ClassName>::value, \
"ClassName must not be copy-constructible."); \
static_assert(!std::is_copy_assignable<ClassName>::value, \
"ClassName must not be copy-assignable.");
int main()
{
#ifdef MINGW_STDTHREADS_GENERATED_STDHEADERS
std::cout << "Using cmake-generated stdheaders, ";
#endif
static_assert(std::is_trivially_copyable<thread::id>::value,
"thread::id must be trivially copyable.");
TEST_SL_MV_CPY(mutex)
TEST_SL_MV_CPY(recursive_mutex)
TEST_SL_MV_CPY(timed_mutex)
TEST_SL_MV_CPY(recursive_timed_mutex)
TEST_SL_MV_CPY(shared_mutex)
TEST_SL_MV_CPY(shared_timed_mutex)
TEST_SL_MV_CPY(condition_variable)
TEST_SL_MV_CPY(condition_variable_any)
static_assert(!std::is_move_constructible<once_flag>::value,
"once_flag must not be move-constructible.");
static_assert(!std::is_move_assignable<once_flag>::value,
"once_flag must not be move-assignable.");
static_assert(!std::is_copy_constructible<once_flag>::value,
"once_flag must not be copy-constructible.");
static_assert(!std::is_copy_assignable<once_flag>::value,
"once_flag must not be copy-assignable.");
// With C++ feature level and target Windows version potentially affecting
// behavior, make this information visible.
{
switch (__cplusplus)
{
case 201103L: std::cout << "Compiled in C++11"; break;
case 201402L: std::cout << "Compiled in C++14"; break;
case 201703L: std::cout << "Compiled in C++17"; break;
default: std::cout << "Compiled in a non-conforming C++ compiler";
}
std::cout << ", targeting Windows ";
static_assert(WINVER > 0x0500, "Windows NT and earlier are not supported.");
switch (WINVER)
{
case 0x0501: std::cout << "XP"; break;
case 0x0502: std::cout << "Server 2003"; break;
case 0x0600: std::cout << "Vista"; break;
case 0x0601: std::cout << "7"; break;
case 0x0602: std::cout << "8"; break;
case 0x0603: std::cout << "8.1"; break;
case 0x0A00: std::cout << "10"; break;
default: std::cout << "10+";
}
std::cout << "\n";
}
{
log("Testing serialization and hashing for thread::id...");
std::cout << "Serialization:\t" << this_thread::get_id() << "\n";
std::hash<thread::id> hasher;
std::cout << "Hash:\t" << hasher(this_thread::get_id()) << "\n";
}
// Regression test: Thread must copy any argument that is passed by value.
{
std::vector<std::thread> loop_threads;
std::atomic<int> i_vals_touched [4];// { 0, 0, 0, 0 };
for (int i = 0; i < 4; ++i)
i_vals_touched[i].store(0, std::memory_order_relaxed);
for (int i = 0; i < 4; ++i)
{
loop_threads.push_back(std::thread([&](int c)
{
log("For-loop test thread got value: %i", c);
i_vals_touched[c].fetch_add(1, std::memory_order_relaxed);
}, i));
}
for (std::thread & thr : loop_threads)
thr.join();
for (int i = 0; i < 4; ++i)
{
if (i_vals_touched[i] != 1)
{
log("FATAL: Threads are not copying arguments!");
return 1;
}
}
}
std::thread t([](TestMove&& a, const char* b, int c) mutable
{
try
{
log("Worker thread started, sleeping for a while...");
// Thread might move the string more than once.
assert(a.mStr.substr(0, 15) == "move test moved");
assert(!strcmp(b, "test message"));
assert(c == -20);
auto move2nd = std::move(a); //test move to final destination
this_thread::sleep_for(std::chrono::milliseconds(1000));
{
lock_guard<mutex> lock(m);
cond = 1;
log("Notifying condvar");
cv.notify_all();
}
this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 2;
log("Notifying condvar");
cv_any.notify_all();
}
this_thread::sleep_for(std::chrono::milliseconds(500));
{
lock_guard<decltype(sm)> lock(sm);
cond = 3;
log("Notifying condvar");
cv_any.notify_all();
}
log("Worker thread finishing");
}
catch(std::exception& e)
{
printf("EXCEPTION in worker thread: %s\n", e.what());
}
},
TestMove("move test"), "test message", -20);
try
{
log("Main thread: Locking mutex, waiting on condvar...");
{
std::unique_lock<decltype(m)> lk(m);
cv.wait(lk, []{ return cond >= 1;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Locking shared_mutex, waiting on condvar...");
{
std::unique_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 2;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Locking shared_mutex in shared mode, waiting on condvar...");
{
std::shared_lock<decltype(sm)> lk(sm);
cv_any.wait(lk, []{ return cond >= 3;} );
log("condvar notified, cond = %d", cond);
assert(lk.owns_lock());
}
log("Main thread: Waiting on worker join...");
t.join();
log("Main thread: Worker thread joined");
fflush(stdout);
}
catch(std::exception& e)
{
log("EXCEPTION in main thread: %s", e.what());
}
once_flag of;
call_once(of, test_call_once, 1, "test");
call_once(of, test_call_once, 1, "ERROR! Should not be called second time");
log("Test complete");
{
log("Testing implementation of <future>...");
test_future<int>();
test_future<void>();
test_future<int &>();
test_future<int const &>();
test_future<int volatile &>();
test_future<int const volatile &>();
log("Testing <future>'s use of allocators. Should allocate, then deallocate.");
promise<int> allocated_promise (std::allocator_arg, CustomAllocator<unsigned>());
allocated_promise.set_value(7);
}
return 0;
}