315 lines
8.0 KiB
C++
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);
|
|
} |