HelloAurora/Tests/Public/2. Hello Threading/Main.cpp
2022-04-01 05:14:58 +01:00

315 lines
8.0 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: Main.cpp
Date: 2022-2-18
Author: Reece
***/
#include <AuroraRuntime.hpp>
#include <gtest/gtest.h>
/**
* @brief Single threaded mutex lock test (rentrant mutexes are called critical sections in this subsystem)
*/
TEST(Mutex, LockTest)
{
auto mutex = AuThreadPrimitives::MutexUnique();
mutex->Lock();
ASSERT_FALSE(mutex->TryLock());
mutex->Unlock();
ASSERT_TRUE(mutex->TryLock());
ASSERT_FALSE(mutex->Lock(10));
mutex->Unlock();
ASSERT_TRUE(mutex->TryLock());
mutex->Unlock();
}
/**
* @brief Spinlock lock test
*/
TEST(Spinlock, LockTest)
{
AuThreadPrimitives::SpinLock mutex;
mutex.Lock();
ASSERT_FALSE(mutex.TryLock());
mutex.Unlock();
ASSERT_TRUE(mutex.TryLock());
ASSERT_FALSE(mutex.Lock(10));
mutex.Unlock();
ASSERT_TRUE(mutex.TryLock());
mutex.Unlock();
}
/**
* @brief Single threaded semaphore lock test
*/
TEST(Semaphore, LockTest)
{
auto semaphore = AuThreadPrimitives::SemaphoreUnique(0);
ASSERT_FALSE(semaphore->TryLock());
semaphore->Unlock(1);
ASSERT_TRUE(semaphore->TryLock());
ASSERT_FALSE(semaphore->Lock(10));
semaphore->Unlock(2);
ASSERT_TRUE(semaphore->TryLock());
ASSERT_TRUE(semaphore->TryLock());
}
/**
* @brief More of a compile check for the RAII stack/object lock guards
* Also validates TRY_[...]_NAMED returns false on failure
*/
TEST(LockGuard, Test)
{
auto mutex = AuThreadPrimitives::MutexUnique();
AU_LOCK_GUARD(mutex);
AuThreadPrimitives::SpinLock spinlock;
AU_LOCK_GUARD(spinlock);
AuThreadPrimitives::SpinLock spinlock2;
AU_LOCK_GUARD(&spinlock2);
AU_TRY_LOCK_GUARD_NAMED(spinlock, testlock);
ASSERT_TRUE(!testlock.Locked());
}
/**
* @brief Test write-entrant read-routines
*/
TEST(RWLocks, GodMode)
{
auto rwlock = AuThreadPrimitives::RWLockUnique();
// Test exclusive ownership
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_FALSE(rwlock->AsWritable()->TryLock());
rwlock->AsReadable()->Unlock();
// Enter exclusive mode
ASSERT_TRUE(rwlock->AsWritable()->TryLock());
// Enter read routines
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsWritable()->Unlock();
// Confirm release
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
rwlock->AsReadable()->Unlock();
}
/**
* @brief Test write-entrant read-routines
*/
TEST(RWLocks, GodMode2)
{
auto rwlock = AuThreadPrimitives::RWLockUnique();
// Test exclusive ownership
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_FALSE(rwlock->AsWritable()->TryLock());
rwlock->AsReadable()->Unlock();
// Test write-entrancy
{
AU_LOCK_GUARD(rwlock->AsWritable())
// Enter read routines
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
}
// Confirm release
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
rwlock->AsReadable()->Unlock();
}
/**
* @brief Test write-entrant read-routines w/ write-mode escalation
*
* UpgradeReadToWrite:
* "Allows for read-to-write upgrades when the decision to esclate is made based upon a shared resource
* "which would be lost by unlocking and relocking in exclusive mode"
*
*/
TEST(RWLocks, SlashGod)
{
auto rwlock = AuThreadPrimitives::RWLockUnique();
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_FALSE(rwlock->AsWritable()->TryLock());
ASSERT_TRUE(rwlock->UpgradeReadToWrite(0));
{
// Enter read routines
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
ASSERT_TRUE(rwlock->AsReadable()->TryLock());
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
rwlock->AsReadable()->Unlock(); // NOP
}
ASSERT_TRUE(rwlock->DowngradeWriteToRead());
ASSERT_FALSE(rwlock->DowngradeWriteToRead());
rwlock->AsReadable()->Unlock();
}
/**
* @brief Allocates a new thread, dispatches the ep, destroys the thread, and waits a reasonable amount of time
* to rejoin until the the context is terminated by the watchdog. In this instance, the thread exists in time.
*/
TEST(Thread, Spawn)
{
auto helloThread = []
{
AuLogDbg("Hello from worker thread");
};
auto thread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(
AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(helloThread)),
AuThreads::IThreadVectorsFunctional::OnExit_t {}),
"SpawnTest"
));
// Fail on resource allocation failure
SysAssert(thread);
ASSERT_TRUE(thread->Run());
}
/**
* @brief Tests watchdog shutdown on thread object release
*/
TEST(Thread, GracefulExit)
{
auto helloThread = []
{
AuLogDbg("Start: worker thread");
while (AuIsThreadRunning())
{
// Do work...
// Wait for signal/CV
}
AuLogDbg("Thread exiting...");
};
auto thread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(
AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(helloThread)),
AuThreads::IThreadVectorsFunctional::OnExit_t {}),
"SpawnTest"
));
// Fail on resource allocation failure
SysAssert(thread);
// Start the thread and assert
ASSERT_TRUE(thread->Run());
}
/**
* @brief Tests watchdog thread termination on manual exit request
*/
TEST(Thread, GracefulExitNoFree)
{
auto helloThread = []
{
AuLogDbg("Start: worker thread");
while (AuIsThreadRunning())
{
// Do work...
// Wait for signal/CV
AuThreading::Sleep(5 /*ms*/);
}
AuLogDbg("Thread exiting...");
};
auto thread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(
AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(helloThread)),
AuThreads::IThreadVectorsFunctional::OnExit_t {}),
"SpawnTest"
));
// Fail on resource allocation failure
SysAssert(thread);
// Start [and assert]
ASSERT_TRUE(thread->Run());
// Set flag and sync/join under a watchdog
thread->Exit();
// Has exited?
ASSERT_TRUE(thread->AsWaitable()->TryLock());
}
/**
* @brief Tests watchdog thread termination on manual exit request
*/
TEST(Thread, ForcefulExitNoFree)
{
auto helloThread = []
{
while (true)
{
AuThreading::Sleep(50000 /*ms*/);
}
};
auto thread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(
AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(helloThread)),
AuThreads::IThreadVectorsFunctional::OnExit_t {}),
"SpawnTest"
));
// Fail on resource allocation failure
SysAssert(thread);
// Start [and assert]
ASSERT_TRUE(thread->Run());
// Set flag and sync/join under a watchdog
thread->Exit();
// Has exited?
ASSERT_TRUE(thread->AsWaitable()->TryLock());
}
// TODO: sample and test code for:
// * Events (does it run igors piss 7zip code was a good test lmao)
// * Condition Variables
// * Rentrant Mutexes
// * Sleep
// * TLS
// * Test approx timeout
// * Semaphore based RunInX utility for MT tests
void RunTests()
{
Aurora::RuntimeStartInfo info;
info.console.fio.enableLogging = false;
Aurora::RuntimeStart(info);
}