From ddf80b29f23ca0eeef54a893ab9c0a0b36abbc51 Mon Sep 17 00:00:00 2001
From: Michael Scire <SciresM@gmail.com>
Date: Sun, 6 Mar 2022 14:13:10 -0800
Subject: [PATCH] add basic tests for os::Event/SystemEvent functionality

---
 libraries/libstratosphere/libstratosphere.mk  |   4 +
 .../source/fs/fs_access_log.cpp               |   4 +-
 .../os_inter_process_event_impl.os.linux.cpp  |   7 +-
 .../os_inter_process_event_impl.os.macos.cpp  |   7 +-
 ...ternal_condition_variable_impl.pthread.cpp |   2 +-
 .../os_multiple_wait_target_impl.os.linux.cpp |   5 +
 .../os_multiple_wait_target_impl.os.macos.cpp |   5 +
 .../impl/os_tick_manager_impl.os.windows.hpp  |   4 +-
 tests/TestOsEvents/Makefile                   |  51 ++
 tests/TestOsEvents/source/test.cpp            | 461 ++++++++++++++++++
 tests/TestOsEvents/unit_test.mk               | 155 ++++++
 11 files changed, 696 insertions(+), 9 deletions(-)
 create mode 100644 tests/TestOsEvents/Makefile
 create mode 100644 tests/TestOsEvents/source/test.cpp
 create mode 100644 tests/TestOsEvents/unit_test.mk

diff --git a/libraries/libstratosphere/libstratosphere.mk b/libraries/libstratosphere/libstratosphere.mk
index e741ef1f6..e854123b6 100644
--- a/libraries/libstratosphere/libstratosphere.mk
+++ b/libraries/libstratosphere/libstratosphere.mk
@@ -146,6 +146,10 @@ hos_stratosphere_api.o: CXXFLAGS += -fno-lto
 init_operator_new.o: CXXFLAGS += -fno-lto
 init_libnx_shim.os.horizon.o: CXXFLAGS += -fno-lto
 
+ifeq ($(ATMOSPHERE_OS_NAME),windows)
+os_%.o: CXXFLAGS += -fno-lto
+endif
+
 #---------------------------------------------------------------------------------
 %_bin.h %.bin.o	:	%.bin
 #---------------------------------------------------------------------------------
diff --git a/libraries/libstratosphere/source/fs/fs_access_log.cpp b/libraries/libstratosphere/source/fs/fs_access_log.cpp
index c665aa497..7f5e90745 100644
--- a/libraries/libstratosphere/source/fs/fs_access_log.cpp
+++ b/libraries/libstratosphere/source/fs/fs_access_log.cpp
@@ -543,7 +543,7 @@ namespace ams::fs::impl {
 
     bool IsEnabledFileSystemAccessorAccessLog(const char *mount_name) {
         /* Get the accessor. */
-        impl::FileSystemAccessor *accessor;
+        impl::FileSystemAccessor *accessor = nullptr;
         if (R_FAILED(impl::Find(std::addressof(accessor), mount_name))) {
             return true;
         }
@@ -553,7 +553,7 @@ namespace ams::fs::impl {
 
     void EnableFileSystemAccessorAccessLog(const char *mount_name) {
         /* Get the accessor. */
-        impl::FileSystemAccessor *accessor;
+        impl::FileSystemAccessor *accessor = nullptr;
         AMS_FS_R_ABORT_UNLESS(impl::Find(std::addressof(accessor), mount_name));
         accessor->SetAccessLogEnabled(true);
     }
diff --git a/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp b/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp
index 2fd362396..ae9944996 100644
--- a/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp
+++ b/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp
@@ -41,8 +41,11 @@ namespace ams::os::impl {
                 res = ::ppoll(std::addressof(pfd), 1, ns >= 0 ? std::addressof(ts) : nullptr, nullptr);
             } while (res < 0 && errno == EINTR);
 
-            AMS_ASSERT(res == 0);
-            return pfd.revents & POLLIN;
+            AMS_ASSERT(res == 0 || res == 1);
+
+            const bool signaled = pfd.revents & POLLIN;
+            AMS_ASSERT(signaled == (res == 1));
+            return signaled;
         }
 
     }
diff --git a/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp b/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp
index 486a1b8f8..4270eec84 100644
--- a/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp
+++ b/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp
@@ -48,8 +48,11 @@ namespace ams::os::impl {
                 res = ::poll(std::addressof(pfd), 1, timeout);
             } while (res < 0 && errno == EINTR);
 
-            AMS_ASSERT(res == 0);
-            return pfd.revents & POLLIN;
+            AMS_ASSERT(res == 0 || res == 1);
+
+            const bool signaled = pfd.revents & POLLIN;
+            AMS_ASSERT(signaled == (res == 1));
+            return signaled;
         }
 
     }
diff --git a/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp b/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp
index 225bb4901..c18a98bfe 100644
--- a/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp
+++ b/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp
@@ -62,7 +62,7 @@ namespace ams::os::impl {
         const auto res = pthread_cond_timedwait(std::addressof(m_pthread_cond), std::addressof(cs->Get()->m_pthread_mutex), std::addressof(ts));
 
         if (res != 0) {
-            AMS_ABORT_UNLESS(errno == ETIMEDOUT);
+            AMS_ABORT_UNLESS(res == ETIMEDOUT);
             return ConditionVariableStatus::TimedOut;
         }
 
diff --git a/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp b/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp
index 1045ba72c..83750fbea 100644
--- a/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp
+++ b/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp
@@ -93,4 +93,9 @@ namespace ams::os::impl {
         }
     }
 
+    Result MultiWaitLinuxImpl::ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) {
+        AMS_UNUSED(out_index, num, arr, array_size, ns, reply_target);
+        R_ABORT_UNLESS(os::ResultNotImplemented());
+    }
+
 }
diff --git a/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp b/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp
index 923bb46c4..31d92d5b7 100644
--- a/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp
+++ b/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp
@@ -97,4 +97,9 @@ namespace ams::os::impl {
         }
     }
 
+    Result MultiWaitMacosImpl::ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) {
+        AMS_UNUSED(out_index, num, arr, array_size, ns, reply_target);
+        R_ABORT_UNLESS(os::ResultNotImplemented());
+    }
+
 }
diff --git a/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp b/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp
index 50dc0a599..e09ca29fd 100644
--- a/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp
+++ b/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp
@@ -50,7 +50,7 @@ namespace ams::os::impl {
 
             ALWAYS_INLINE Tick GetTick() const {
                 LARGE_INTEGER freq;
-                ::QueryPerformanceFrequency(std::addressof(freq));
+                ::QueryPerformanceCounter(std::addressof(freq));
                 return Tick(static_cast<s64>(freq.QuadPart));
             }
 
@@ -58,7 +58,7 @@ namespace ams::os::impl {
                 LARGE_INTEGER freq;
 
                 PerformOrderingForGetSystemTickOrdered();
-                ::QueryPerformanceFrequency(std::addressof(freq));
+                ::QueryPerformanceCounter(std::addressof(freq));
                 PerformOrderingForGetSystemTickOrdered();
 
                 return Tick(static_cast<s64>(freq.QuadPart));
diff --git a/tests/TestOsEvents/Makefile b/tests/TestOsEvents/Makefile
new file mode 100644
index 000000000..4f16374da
--- /dev/null
+++ b/tests/TestOsEvents/Makefile
@@ -0,0 +1,51 @@
+ATMOSPHERE_BUILD_CONFIGS :=
+all: nx_release
+
+THIS_MAKEFILE     := $(abspath $(lastword $(MAKEFILE_LIST)))
+CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
+
+define ATMOSPHERE_ADD_TARGET
+
+ATMOSPHERE_BUILD_CONFIGS += $(strip $1)
+
+$(strip $1):
+	@echo "Building $(strip $1)"
+	@$$(MAKE) -f $(CURRENT_DIRECTORY)/unit_test.mk ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+clean-$(strip $1):
+	@echo "Cleaning $(strip $1)"
+	@$$(MAKE) -f $(CURRENT_DIRECTORY)/unit_test.mk clean ATMOSPHERE_MAKEFILE_TARGET="$(strip $1)" ATMOSPHERE_BUILD_NAME="$(strip $2)" ATMOSPHERE_BOARD="$(strip $3)" ATMOSPHERE_CPU="$(strip $4)" $(strip $5)
+
+endef
+
+define ATMOSPHERE_ADD_TARGETS
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_release, $(strip $2)release, $(strip $3), $(strip $4), \
+    ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \
+    ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \
+))
+
+$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \
+    ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \
+))
+
+endef
+
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, nx,                      , nx-hac-001, arm-cortex-a57,,))
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64,                 , generic_windows, generic_x64,,))
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64,               , generic_linux, generic_x64,,))
+$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang,   clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang"))
+$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang"))
+
+$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64,               , generic_macos, generic_x64,,))
+$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64,             , generic_macos, generic_arm64,,))
+
+clean: $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS),clean-$(config))
+
+.PHONY: all clean $(foreach config,$(ATMOSPHERE_BUILD_CONFIGS), $(config) clean-$(config))
diff --git a/tests/TestOsEvents/source/test.cpp b/tests/TestOsEvents/source/test.cpp
new file mode 100644
index 000000000..e3533efe9
--- /dev/null
+++ b/tests/TestOsEvents/source/test.cpp
@@ -0,0 +1,461 @@
+#include <stratosphere.hpp>
+
+namespace ams {
+
+    namespace {
+
+        struct InterThreadSync {
+            util::Atomic<int> reader_state;
+            util::Atomic<int> writer_state;
+            os::EventType writer_ready_event;
+            os::EventType reader_ready_event;
+            union {
+                struct {
+                    os::SystemEventType system_event_as_manual_clear_event;
+                    os::SystemEventType system_event_as_manual_clear_interprocess_event;
+                    os::SystemEventType system_event_as_auto_clear_event;
+                    os::SystemEventType system_event_as_auto_clear_interprocess_event;
+                };
+                os::SystemEventType system_events[4];
+            };
+        };
+
+        bool IsManualClearEventIndex(size_t i) {
+            return i == 0 || i == 1;
+        }
+
+        alignas(os::MemoryPageSize) constinit u8 g_writer_thread_stack[16_KB];
+        alignas(os::MemoryPageSize) constinit u8 g_reader_thread_stack[16_KB];
+
+        void TestWriterThread(void *arg) {
+            /* Get the synchronization arguments. */
+            auto &sync = *static_cast<InterThreadSync *>(arg);
+            AMS_UNUSED(sync);
+
+            /* Wait for reader to be ready. */
+            os::WaitEvent(std::addressof(sync.reader_ready_event));
+            AMS_ABORT_UNLESS(sync.reader_state == 1);
+
+            /* Verify that all events can be signaled. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 1;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 1);
+            }
+
+            /* Verify that all events can be signaled (for TimedWait 0). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 2;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 2);
+            }
+
+            /* Verify that all events can be signaled (for TimedWait 2). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 3;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 3);
+            }
+
+            /* Verify that all events can be signaled (for True Wait). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 4;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 4);
+            }
+
+            /* Verify that all events can be signaled (TryWaitAny). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 5;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 5);
+            }
+
+            /* Verify that all events can be signaled (TimedWaitAny 0). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 6;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 6);
+            }
+
+            /* Verify that all events can be signaled (TimedWaitAny 2). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 7;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 7);
+            }
+
+            /* Verify that all events can be signaled (TrueWaitAny). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 8;
+                os::SignalEvent(std::addressof(sync.writer_ready_event));
+
+                /* Wait for the reader to finish. */
+                os::WaitEvent(std::addressof(sync.reader_ready_event));
+                AMS_ABORT_UNLESS(sync.reader_state == 8);
+            }
+
+            /* Verify that reader can receive without explicit sync. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Set the event for this go. */
+                os::SignalSystemEvent(sync.system_events + i);
+
+                sync.writer_state = 9;
+
+            }
+
+            /* Wait for the reader to finish. */
+            os::WaitEvent(std::addressof(sync.reader_ready_event));
+            AMS_ABORT_UNLESS(sync.reader_state == 9);
+        }
+
+        void TestReaderThread(void *arg) {
+            /* Get the synchronization arguments. */
+            auto &sync = *static_cast<InterThreadSync *>(arg);
+            AMS_UNUSED(sync);
+
+            /* Set up multi-wait objects. */
+            os::MultiWaitType mw;
+            os::MultiWaitHolderType holders[util::size(sync.system_events)];
+            os::InitializeMultiWait(std::addressof(mw));
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                os::InitializeMultiWaitHolder(holders + i, sync.system_events + i);
+                os::LinkMultiWaitHolder(std::addressof(mw), holders + i);
+            }
+            ON_SCOPE_EXIT {
+                for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                    os::UnlinkMultiWaitHolder(holders + i);
+                    os::FinalizeMultiWaitHolder(holders + i);
+                }
+                os::FinalizeMultiWait(std::addressof(mw));
+            };
+
+            /* Sanity check: all events are non-signaled. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + i) == false);
+                AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + i, TimeSpan::FromNanoSeconds(0)) == false);
+                AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + i, TimeSpan::FromMilliSeconds(2)) == false);
+            }
+
+            /* Sanity check that wait any does the right thing when nothing is signaled. */
+            AMS_ABORT_UNLESS(os::TryWaitAny(std::addressof(mw)) == nullptr);
+            AMS_ABORT_UNLESS(os::TimedWaitAny(std::addressof(mw), TimeSpan::FromNanoSeconds(0)) == nullptr);
+            AMS_ABORT_UNLESS(os::TimedWaitAny(std::addressof(mw), TimeSpan::FromNanoSeconds(2)) == nullptr);
+
+            /* Let writer know that we're ready. */
+            sync.reader_state = 1;
+            os::SignalEvent(std::addressof(sync.reader_ready_event));
+
+            /* Verify that we can receive signal on each event. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 1);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    if (i == n) {
+                        AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true);
+                        if (IsManualClearEventIndex(n)) {
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true);
+                            os::ClearSystemEvent(sync.system_events + n);
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                        } else {
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                        }
+                    } else {
+                        AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                    }
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 1;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (Timed Wait 0). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 2);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    if (i == n) {
+                        AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == true);
+                        if (IsManualClearEventIndex(n)) {
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == true);
+                            os::ClearSystemEvent(sync.system_events + n);
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false);
+                        } else {
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false);
+                        }
+                    } else {
+                        AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false);
+                    }
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 2;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (Timed Wait 2). */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 3);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    if (i == n) {
+                        AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == true);
+                        if (IsManualClearEventIndex(n)) {
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == true);
+                            os::ClearSystemEvent(sync.system_events + n);
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false);
+                        } else {
+                            AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false);
+                        }
+                    } else {
+                        AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false);
+                    }
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 3;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 4);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    if (i == n) {
+                        os::WaitSystemEvent(sync.system_events + n);
+                        if (IsManualClearEventIndex(n)) {
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true);
+                            os::WaitSystemEvent(sync.system_events + n);
+                            os::ClearSystemEvent(sync.system_events + n);
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                        } else {
+                            AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                        }
+                    } else {
+                        AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                    }
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 4;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (TryWaitAny) */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 5);
+
+                /* Get the signaled holder. */
+                auto *signaled = os::TryWaitAny(std::addressof(mw));
+                AMS_ABORT_UNLESS(signaled == holders + i);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n));
+                    os::ClearSystemEvent(sync.system_events + n);
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 5;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (TimedWaitAny 0) */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 6);
+
+                /* Get the signaled holder. */
+                auto *signaled = os::TimedWaitAny(std::addressof(mw), TimeSpan::FromMilliSeconds(0));
+                AMS_ABORT_UNLESS(signaled == holders + i);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n));
+                    os::ClearSystemEvent(sync.system_events + n);
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 6;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (TimedWaitAny 2) */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 7);
+
+                /* Get the signaled holder. */
+                auto *signaled = os::TimedWaitAny(std::addressof(mw), TimeSpan::FromMilliSeconds(2));
+                AMS_ABORT_UNLESS(signaled == holders + i);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n));
+                    os::ClearSystemEvent(sync.system_events + n);
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 7;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive signal on each event (True WaitAny) */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                /* Wait for writer to do the relevant work */
+                os::WaitEvent(std::addressof(sync.writer_ready_event));
+                AMS_ABORT_UNLESS(sync.writer_state == 8);
+
+                /* Get the signaled holder. */
+                auto *signaled = os::WaitAny(std::addressof(mw));
+                AMS_ABORT_UNLESS(signaled == holders + i);
+
+                /* Test all events. */
+                for (size_t n = 0; n < util::size(sync.system_events); ++n) {
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n));
+                    os::ClearSystemEvent(sync.system_events + n);
+                    AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+                }
+
+                /* Let writer know we're done. */
+                sync.reader_state = 8;
+                os::SignalEvent(std::addressof(sync.reader_ready_event));
+            }
+
+            /* Verify that we can receive wait-any signals without sync. */
+            for (size_t i = 0; i < util::size(sync.system_events); ++i) {
+                auto *signaled = os::WaitAny(std::addressof(mw));
+                AMS_ABORT_UNLESS(signaled != nullptr);
+                const size_t n = signaled - holders;
+                AMS_ABORT_UNLESS(n < util::size(sync.system_events));
+
+                AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true);
+                os::ClearSystemEvent(sync.system_events + n);
+                AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false);
+            }
+
+            AMS_ABORT_UNLESS(os::TryWaitAny(std::addressof(mw)) == nullptr);
+
+            /* Let writer know we're done. */
+            sync.reader_state = 9;
+            os::SignalEvent(std::addressof(sync.reader_ready_event));
+        }
+
+    }
+
+
+    void Main() {
+        printf("Doing OS Event tests!\n");
+        {
+            /* Create the synchronization state. */
+            InterThreadSync sync_state;
+            sync_state.reader_state = 0;
+            sync_state.writer_state = 0;
+            os::InitializeEvent(std::addressof(sync_state.writer_ready_event), false, os::EventClearMode_AutoClear);
+            os::InitializeEvent(std::addressof(sync_state.reader_ready_event), false, os::EventClearMode_AutoClear);
+
+            R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_manual_clear_event), os::EventClearMode_ManualClear, false));
+            R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_manual_clear_interprocess_event), os::EventClearMode_ManualClear, true));
+            R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_auto_clear_event), os::EventClearMode_AutoClear, false));
+            R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_auto_clear_interprocess_event), os::EventClearMode_AutoClear, true));
+
+            /* Ensure we clean up the sync-state when done. */
+            ON_SCOPE_EXIT {
+                os::FinalizeEvent(std::addressof(sync_state.writer_ready_event));
+                os::FinalizeEvent(std::addressof(sync_state.reader_ready_event));
+
+                os::DestroySystemEvent(std::addressof(sync_state.system_event_as_manual_clear_event));
+                os::DestroySystemEvent(std::addressof(sync_state.system_event_as_manual_clear_interprocess_event));
+                os::DestroySystemEvent(std::addressof(sync_state.system_event_as_auto_clear_event));
+                os::DestroySystemEvent(std::addressof(sync_state.system_event_as_auto_clear_interprocess_event));
+            };
+
+            /* Create the threads. */
+            os::ThreadType reader_thread, writer_thread;
+            R_ABORT_UNLESS(os::CreateThread(std::addressof(reader_thread), TestReaderThread, std::addressof(sync_state), g_reader_thread_stack, sizeof(g_reader_thread_stack), os::DefaultThreadPriority));
+            R_ABORT_UNLESS(os::CreateThread(std::addressof(writer_thread), TestWriterThread, std::addressof(sync_state), g_writer_thread_stack, sizeof(g_writer_thread_stack), os::DefaultThreadPriority));
+            os::SetThreadNamePointer(std::addressof(reader_thread), "ReaderThread");
+            os::SetThreadNamePointer(std::addressof(writer_thread), "WriterThread");
+
+            /* Start the threads. */
+            os::StartThread(std::addressof(reader_thread));
+            os::StartThread(std::addressof(writer_thread));
+
+            /* Wait for the threads to complete. */
+            os::WaitThread(std::addressof(reader_thread));
+            os::WaitThread(std::addressof(writer_thread));
+
+            /* Destroy the threads. */
+            os::WaitThread(std::addressof(reader_thread));
+            os::WaitThread(std::addressof(writer_thread));
+        }
+        printf("All tests completed!\n");
+    }
+
+}
\ No newline at end of file
diff --git a/tests/TestOsEvents/unit_test.mk b/tests/TestOsEvents/unit_test.mk
new file mode 100644
index 000000000..508777b98
--- /dev/null
+++ b/tests/TestOsEvents/unit_test.mk
@@ -0,0 +1,155 @@
+#---------------------------------------------------------------------------------
+# pull in common stratosphere sysmodule configuration
+#---------------------------------------------------------------------------------
+THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
+include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
+
+ifeq ($(ATMOSPHERE_BOARD),nx-hac-001)
+export BOARD_TARGET_SUFFIX := .kip
+else ifeq ($(ATMOSPHERE_BOARD),generic_windows)
+export BOARD_TARGET_SUFFIX := .exe
+else ifeq ($(ATMOSPHERE_BOARD),generic_linux)
+export BOARD_TARGET_SUFFIX :=
+else ifeq ($(ATMOSPHERE_BOARD),generic_macos)
+export BOARD_TARGET_SUFFIX :=
+else
+export BOARD_TARGET_SUFFIX := $(TARGET)
+endif
+
+#---------------------------------------------------------------------------------
+# no real need to edit anything past this point unless you need to add additional
+# rules for different file extensions
+#---------------------------------------------------------------------------------
+ifneq ($(__RECURSIVE__),1)
+#---------------------------------------------------------------------------------
+
+export TOPDIR	:=	$(CURDIR)
+
+export VPATH	:=	$(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
+			$(foreach dir,$(DATA),$(CURDIR)/$(dir))
+
+CFILES      :=	$(call FIND_SOURCE_FILES,$(SOURCES),c)
+CPPFILES    :=	$(call FIND_SOURCE_FILES,$(SOURCES),cpp)
+SFILES      :=	$(call FIND_SOURCE_FILES,$(SOURCES),s)
+
+BINFILES	:=	$(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
+
+#---------------------------------------------------------------------------------
+# use CXX for linking C++ projects, CC for standard C
+#---------------------------------------------------------------------------------
+ifeq ($(strip $(CPPFILES)),)
+#---------------------------------------------------------------------------------
+	export LD	:=	$(CC)
+#---------------------------------------------------------------------------------
+else
+#---------------------------------------------------------------------------------
+	export LD	:=	$(CXX)
+#---------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------
+
+export OFILES	:=	$(addsuffix .o,$(BINFILES)) \
+			$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
+
+export INCLUDE	:=	$(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
+			$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
+			$(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \
+			-I$(CURDIR)/$(BUILD)
+
+export LIBPATHS	:=	$(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
+
+export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
+
+ifeq ($(strip $(CONFIG_JSON)),)
+	jsons := $(wildcard *.json)
+	ifneq (,$(findstring $(TARGET).json,$(jsons)))
+		export APP_JSON := $(TOPDIR)/$(TARGET).json
+	else
+		ifneq (,$(findstring config.json,$(jsons)))
+			export APP_JSON := $(TOPDIR)/config.json
+		endif
+	endif
+else
+	export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
+endif
+
+.PHONY: clean all check_lib
+
+#---------------------------------------------------------------------------------
+all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
+	@$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \
+	DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
+	--no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
+	-f $(THIS_MAKEFILE)
+
+$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib
+	@$(SILENTCMD)echo "Checked library."
+
+check_lib:
+	@$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk
+
+$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
+	@[ -d $@ ] || mkdir -p $@
+
+#---------------------------------------------------------------------------------
+clean:
+	@echo clean ...
+	@rm -fr $(BUILD) $(BOARD_TARGET) $(TARGET).elf
+	@for i in $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR); do [ -d $$i ] && rmdir --ignore-fail-on-non-empty $$i || true; done
+
+
+#---------------------------------------------------------------------------------
+else
+.PHONY:	all
+
+DEPENDS	:=	$(OFILES:.o=.d)
+
+#---------------------------------------------------------------------------------
+# main targets
+#---------------------------------------------------------------------------------
+all	:	$(OUTPUT)$(BOARD_TARGET_SUFFIX)
+
+%.kip : %.elf
+
+%.nsp : %.nso %.npdm
+
+%.nso: %.elf
+
+
+#---------------------------------------------------------------------------------
+$(OUTPUT).elf: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
+	@echo linking $(notdir $@)
+	$(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
+	$(SILENTCMD)$(NM) -CSn $@ > $(notdir $(OUTPUT).lst)
+
+$(OUTPUT).exe: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
+	@echo linking $(notdir $@)
+	$(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
+	$(SILENTCMD)$(NM) -CSn $@ > $(notdir $*.lst)
+
+
+ifeq ($(strip $(BOARD_TARGET_SUFFIX)),)
+$(OUTPUT): $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
+	@echo linking $(notdir $@)
+	$(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
+	$(SILENTCMD)$(NM) -CSn $@ > $(notdir $@.lst)
+endif
+
+%.npdm  :   %.npdm.json
+	@echo built ... $< $@
+	@npdmtool $< $@
+	@echo built ... $(notdir $@)
+
+#---------------------------------------------------------------------------------
+# you need a rule like this for each extension you use as binary data
+#---------------------------------------------------------------------------------
+%.bin.o	:	%.bin
+#---------------------------------------------------------------------------------
+	@echo $(notdir $<)
+	@$(bin2o)
+
+-include $(DEPENDS)
+
+#---------------------------------------------------------------------------------------
+endif
+#---------------------------------------------------------------------------------------