[Base] Remove timing dependency from test

- Use atomics and spin waits to synchronize threads for tests
- Improves test stability on CI
This commit is contained in:
Joel Linn 2022-02-20 18:51:13 +01:00 committed by Rick Gibbed
parent 49efbeaca8
commit ca6296089e
1 changed files with 74 additions and 47 deletions

View File

@ -92,8 +92,7 @@ TEST_CASE("Fence") {
std::thread(func), std::thread(func),
}); });
Sleep(100ms); REQUIRE(spin_wait_for(1s, [&] { return started == threads.size(); }));
REQUIRE(started.load() == threads.size());
REQUIRE(finished.load() == 0); REQUIRE(finished.load() == 0);
pFence->Signal(); pFence->Signal();
@ -320,6 +319,7 @@ TEST_CASE("Wait on Multiple Events", "[event]") {
Event::CreateManualResetEvent(false), Event::CreateManualResetEvent(false),
}; };
std::atomic_uint threads_started(0);
std::array<char, 8> order = {0}; std::array<char, 8> order = {0};
std::atomic_uint index(0); std::atomic_uint index(0);
auto sign_in = [&order, &index](uint32_t id) { auto sign_in = [&order, &index](uint32_t id) {
@ -328,40 +328,53 @@ TEST_CASE("Wait on Multiple Events", "[event]") {
}; };
auto threads = std::array<std::thread, 4>{ auto threads = std::array<std::thread, 4>{
std::thread([&events, &sign_in] { std::thread([&events, &sign_in, &threads_started] {
auto res = WaitAll({events[1].get(), events[3].get()}, false, 100ms); set_name("1");
threads_started++;
auto res = WaitAll({events[1].get(), events[3].get()}, false);
REQUIRE(res == WaitResult::kSuccess);
if (res == WaitResult::kSuccess) { if (res == WaitResult::kSuccess) {
sign_in(1); sign_in(1);
} }
}), }),
std::thread([&events, &sign_in] { std::thread([&events, &sign_in, &threads_started] {
auto res = WaitAny({events[0].get(), events[2].get()}, false, 100ms); set_name("2");
threads_started++;
auto res = WaitAny({events[0].get(), events[2].get()}, false);
REQUIRE(res.first == WaitResult::kSuccess);
if (res.first == WaitResult::kSuccess) { if (res.first == WaitResult::kSuccess) {
sign_in(2); sign_in(2);
} }
}), }),
std::thread([&events, &sign_in] { std::thread([&events, &sign_in, &threads_started] {
auto res = WaitAll({events[0].get(), events[2].get(), events[3].get()}, set_name("3");
false, 100ms); threads_started++;
auto res =
WaitAll({events[0].get(), events[2].get(), events[3].get()}, false);
REQUIRE(res == WaitResult::kSuccess);
if (res == WaitResult::kSuccess) { if (res == WaitResult::kSuccess) {
sign_in(3); sign_in(3);
} }
}), }),
std::thread([&events, &sign_in] { std::thread([&events, &sign_in, &threads_started] {
auto res = WaitAny({events[1].get(), events[3].get()}, false, 100ms); set_name("4");
threads_started++;
auto res = WaitAny({events[1].get(), events[3].get()}, false);
REQUIRE(res.first == WaitResult::kSuccess);
if (res.first == WaitResult::kSuccess) { if (res.first == WaitResult::kSuccess) {
sign_in(4); sign_in(4);
} }
}), }),
}; };
Sleep(10ms); // wait for all threads starting up
REQUIRE(spin_wait_for(1s, [&] { return threads_started == 4; }));
events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3 events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return index == 1; }));
events[1]->Set(); // Signals thread id=1 events[1]->Set(); // Signals thread id=1
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return index == 2; }));
events[0]->Set(); // Signals thread id=2 events[0]->Set(); // Signals thread id=2
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return index == 3; }));
events[2]->Set(); // Partial signals thread id=3 events[2]->Set(); // Partial signals thread id=3
events[0]->Set(); // Signals thread id=3 events[0]->Set(); // Signals thread id=3
@ -369,12 +382,13 @@ TEST_CASE("Wait on Multiple Events", "[event]") {
t.join(); t.join();
} }
REQUIRE(index == 4);
INFO(order.data()); INFO(order.data());
REQUIRE(order[0] == '4'); REQUIRE(order[0] == '4');
// TODO(bwrsandman): Order is not always maintained on linux REQUIRE(order[1] == '1');
// REQUIRE(order[1] == '1'); REQUIRE(order[2] == '2');
// REQUIRE(order[2] == '2'); REQUIRE(order[3] == '3');
// REQUIRE(order[3] == '3');
} }
TEST_CASE("Wait on Semaphore", "[semaphore]") { TEST_CASE("Wait on Semaphore", "[semaphore]") {
@ -433,26 +447,24 @@ TEST_CASE("Wait on Semaphore", "[semaphore]") {
// Semaphore between threads // Semaphore between threads
sem = Semaphore::Create(5, 5); sem = Semaphore::Create(5, 5);
Sleep(10ms);
// Occupy the semaphore with 5 threads // Occupy the semaphore with 5 threads
std::atomic<int> wait_count(0); std::atomic<int> wait_count(0);
volatile bool threads_terminate(false); std::atomic<bool> threads_terminate(false);
auto func = [&sem, &wait_count, &threads_terminate] { auto func = [&sem, &wait_count, &threads_terminate] {
auto res = Wait(sem.get(), false, 100ms); auto res = Wait(sem.get(), false, 100ms);
wait_count++; wait_count++;
while (!threads_terminate) {
} REQUIRE(spin_wait_for(2s, [&] { return threads_terminate.load(); }));
if (res == WaitResult::kSuccess) {
sem->Release(1, nullptr); REQUIRE(res == WaitResult::kSuccess);
} sem->Release(1, nullptr);
}; };
auto threads = std::array<std::thread, 5>{ auto threads = std::array<std::thread, 5>{
std::thread(func), std::thread(func), std::thread(func), std::thread(func), std::thread(func), std::thread(func),
std::thread(func), std::thread(func), std::thread(func), std::thread(func),
}; };
// Wait for threads to finish semaphore calls // Wait for threads to finish semaphore calls
while (wait_count != 5) { REQUIRE(spin_wait_for(1s, [&] { return wait_count == 5; }));
}
// Attempt to acquire full semaphore with current (6th) thread // Attempt to acquire full semaphore with current (6th) thread
result = Wait(sem.get(), false, 20ms); result = Wait(sem.get(), false, 20ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
@ -465,18 +477,23 @@ TEST_CASE("Wait on Semaphore", "[semaphore]") {
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
sem->Release(1, &previous_count); sem->Release(1, &previous_count);
REQUIRE(previous_count == 4); REQUIRE(previous_count == 4);
}
TEST_CASE("Invalid semaphore parameters", "[semaphore]") {
std::unique_ptr<Semaphore> sem;
// Test invalid construction parameters // Test invalid construction parameters
// These are invalid according to documentation // These are invalid according to documentation
// TODO(bwrsandman): Many of these invalid invocations succeed
sem = Semaphore::Create(-1, 5); sem = Semaphore::Create(-1, 5);
// REQUIRE(sem.get() == nullptr); REQUIRE(sem == nullptr);
sem = Semaphore::Create(10, 5); sem = Semaphore::Create(10, 5);
// REQUIRE(sem.get() == nullptr); REQUIRE(sem == nullptr);
sem = Semaphore::Create(0, 0); sem = Semaphore::Create(0, 0);
// REQUIRE(sem.get() == nullptr); REQUIRE(sem == nullptr);
sem = Semaphore::Create(0, -1); sem = Semaphore::Create(0, -1);
// REQUIRE(sem.get() == nullptr); REQUIRE(sem == nullptr);
sem = Semaphore::Create(-1, 0);
REQUIRE(sem == nullptr);
} }
TEST_CASE("Wait on Multiple Semaphores", "[semaphore]") { TEST_CASE("Wait on Multiple Semaphores", "[semaphore]") {
@ -576,17 +593,18 @@ TEST_CASE("Wait on Mutant", "[mutant]") {
REQUIRE_FALSE(mut->Release()); REQUIRE_FALSE(mut->Release());
// Test mutants on other threads // Test mutants on other threads
auto thread1 = std::thread([&mut] { std::atomic<unsigned int> step(0);
Sleep(5ms); auto thread1 = std::thread([&mut, &step] {
mut = Mutant::Create(true); mut = Mutant::Create(true);
Sleep(100ms); step++; // 1
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
mut->Release(); mut->Release();
}); });
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
REQUIRE_FALSE(mut->Release()); REQUIRE_FALSE(mut->Release());
Sleep(10ms);
result = Wait(mut.get(), false, 50ms); result = Wait(mut.get(), false, 50ms);
REQUIRE(result == WaitResult::kTimeout); REQUIRE(result == WaitResult::kTimeout);
step++; // 2
thread1.join(); thread1.join();
result = Wait(mut.get(), false, 1ms); result = Wait(mut.get(), false, 1ms);
REQUIRE(result == WaitResult::kSuccess); REQUIRE(result == WaitResult::kSuccess);
@ -597,16 +615,18 @@ TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
WaitResult all_result; WaitResult all_result;
std::pair<WaitResult, size_t> any_result; std::pair<WaitResult, size_t> any_result;
std::unique_ptr<Mutant> mut0, mut1; std::unique_ptr<Mutant> mut0, mut1;
std::atomic<unsigned int> step(0);
// Test which should fail for WaitAll and WaitAny // Test which should fail for WaitAll and WaitAny
auto thread0 = std::thread([&mut0, &mut1] { auto thread0 = std::thread([&mut0, &mut1, &step] {
mut0 = Mutant::Create(true); mut0 = Mutant::Create(true);
mut1 = Mutant::Create(true); mut1 = Mutant::Create(true);
Sleep(50ms); step++; // 1
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
mut0->Release(); mut0->Release();
mut1->Release(); mut1->Release();
}); });
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
REQUIRE(all_result == WaitResult::kTimeout); REQUIRE(all_result == WaitResult::kTimeout);
REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut0->Release());
@ -616,16 +636,19 @@ TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
REQUIRE(any_result.second == 0); REQUIRE(any_result.second == 0);
REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut0->Release());
REQUIRE_FALSE(mut1->Release()); REQUIRE_FALSE(mut1->Release());
step++; // 2
thread0.join(); thread0.join();
// Test which should fail for WaitAll but not WaitAny // Test which should fail for WaitAll but not WaitAny
auto thread1 = std::thread([&mut0, &mut1] { step = 0;
auto thread1 = std::thread([&mut0, &mut1, &step] {
mut0 = Mutant::Create(true); mut0 = Mutant::Create(true);
mut1 = Mutant::Create(false); mut1 = Mutant::Create(false);
Sleep(50ms); step++; // 1
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
mut0->Release(); mut0->Release();
}); });
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
REQUIRE(all_result == WaitResult::kTimeout); REQUIRE(all_result == WaitResult::kTimeout);
REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut0->Release());
@ -635,15 +658,18 @@ TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
REQUIRE(any_result.second == 1); REQUIRE(any_result.second == 1);
REQUIRE_FALSE(mut0->Release()); REQUIRE_FALSE(mut0->Release());
REQUIRE(mut1->Release()); REQUIRE(mut1->Release());
step++; // 2
thread1.join(); thread1.join();
// Test which should pass for WaitAll and WaitAny // Test which should pass for WaitAll and WaitAny
auto thread2 = std::thread([&mut0, &mut1] { step = 0;
auto thread2 = std::thread([&mut0, &mut1, &step] {
mut0 = Mutant::Create(false); mut0 = Mutant::Create(false);
mut1 = Mutant::Create(false); mut1 = Mutant::Create(false);
Sleep(50ms); step++; // 1
REQUIRE(spin_wait_for(2s, [&] { return step == 2; }));
}); });
Sleep(10ms); REQUIRE(spin_wait_for(1s, [&] { return step == 1; }));
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
REQUIRE(all_result == WaitResult::kSuccess); REQUIRE(all_result == WaitResult::kSuccess);
REQUIRE(mut0->Release()); REQUIRE(mut0->Release());
@ -653,6 +679,7 @@ TEST_CASE("Wait on Multiple Mutants", "[mutant]") {
REQUIRE(any_result.second == 0); REQUIRE(any_result.second == 0);
REQUIRE(mut0->Release()); REQUIRE(mut0->Release());
REQUIRE_FALSE(mut1->Release()); REQUIRE_FALSE(mut1->Release());
step++; // 2
thread2.join(); thread2.join();
} }