| //===-- Unittests for the internal Semaphore class ------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "hdr/errno_macros.h" |
| #include "hdr/fcntl_macros.h" |
| #include "hdr/time_macros.h" |
| #include "hdr/types/struct_timespec.h" |
| #include "src/__support/time/clock_gettime.h" |
| #include "src/semaphore/linux/semaphore.h" |
| #include "test/UnitTest/Test.h" |
| |
| using LIBC_NAMESPACE::Semaphore; |
| |
| TEST(LlvmLibcSemaphoreTest, InitAndGetValue) { |
| Semaphore sem(3, /*is_shared=*/false); |
| ASSERT_TRUE(sem.is_valid()); |
| ASSERT_EQ(sem.getvalue(), 3); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, Destroy) { |
| Semaphore sem(5, /*is_shared=*/false); |
| ASSERT_TRUE(sem.is_valid()); |
| sem.destroy(); |
| ASSERT_FALSE(sem.is_valid()); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TryWait) { |
| Semaphore sem(2, /*is_shared=*/false); |
| |
| // two successful non-blocking decrements. |
| ASSERT_EQ(sem.trywait(), 0); |
| ASSERT_EQ(sem.trywait(), 0); |
| ASSERT_EQ(sem.getvalue(), 0); |
| |
| // value is now zero, trywait must fail with EAGAIN. |
| ASSERT_EQ(sem.trywait(), EAGAIN); |
| ASSERT_EQ(sem.getvalue(), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, Post) { |
| Semaphore sem(0, /*is_shared=*/false); |
| ASSERT_EQ(sem.getvalue(), 0); |
| |
| ASSERT_EQ(sem.post(), 0); |
| ASSERT_EQ(sem.getvalue(), 1); |
| |
| // the posted value can be consumed by trywait. |
| ASSERT_EQ(sem.trywait(), 0); |
| ASSERT_EQ(sem.getvalue(), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, WaitNonBlocking) { |
| Semaphore sem(2, /*is_shared=*/false); |
| |
| // value is positive: wait() should decrement without blocking. |
| ASSERT_EQ(sem.wait(), 0); |
| ASSERT_EQ(sem.getvalue(), 1); |
| ASSERT_EQ(sem.wait(), 0); |
| ASSERT_EQ(sem.getvalue(), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TimedWaitNonBlocking) { |
| Semaphore sem(2, /*is_shared=*/false); |
| timespec ts{}; |
| LIBC_NAMESPACE::internal::clock_gettime(CLOCK_REALTIME, &ts); |
| ts.tv_sec += 60; |
| |
| // value is positive: timedwait should decrement without consulting the |
| // clock. |
| ASSERT_EQ(sem.timedwait(&ts), 0); |
| ASSERT_EQ(sem.getvalue(), 1); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TimedWaitNullTime) { |
| // When the semaphore can be locked immediately, abstime must not be |
| // dereferenced. |
| Semaphore sem(1, /*is_shared=*/false); |
| ASSERT_EQ(sem.timedwait(nullptr), 0); |
| ASSERT_EQ(sem.getvalue(), 0); |
| |
| Semaphore sem2(1, /*is_shared=*/false); |
| ASSERT_EQ(sem2.clockwait(CLOCK_MONOTONIC, nullptr), 0); |
| ASSERT_EQ(sem2.getvalue(), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, PostOverflow) { |
| // The value exceeds maximum, post() must fail with EOVERFLOW |
| Semaphore sem(LIBC_NAMESPACE::SEM_VALUE_MAX, /*is_shared=*/false); |
| ASSERT_EQ(sem.post(), EOVERFLOW); |
| ASSERT_EQ(sem.getvalue(), static_cast<int>(LIBC_NAMESPACE::SEM_VALUE_MAX)); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TimedWaitTimeout) { |
| Semaphore sem(0, /*is_shared=*/false); |
| timespec ts{}; |
| LIBC_NAMESPACE::internal::clock_gettime(CLOCK_REALTIME, &ts); |
| // a few milliseconds in the future. |
| ts.tv_nsec += 10'000'000; |
| if (ts.tv_nsec >= 1'000'000'000) { |
| ts.tv_sec += 1; |
| ts.tv_nsec -= 1'000'000'000; |
| } |
| |
| // value is zero and no post() arrives: must time out. |
| ASSERT_EQ(sem.timedwait(&ts), ETIMEDOUT); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TimedWaitBeforeEpoch) { |
| Semaphore sem(0, /*is_shared=*/false); |
| // tv_sec < 0 is treated as an already expired deadline. |
| timespec ts{}; |
| ts.tv_sec = -1; |
| ts.tv_nsec = 0; |
| ASSERT_EQ(sem.timedwait(&ts), ETIMEDOUT); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, TimedWaitInvalidTimespec) { |
| Semaphore sem(0, /*is_shared=*/false); |
| // tv_nsec out of [0, 1e9) is malformed. |
| timespec ts{}; |
| ts.tv_sec = 1; |
| ts.tv_nsec = 1'000'000'001; |
| ASSERT_EQ(sem.timedwait(&ts), EINVAL); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, ClockWaitMonotonicTimeout) { |
| Semaphore sem(0, /*is_shared=*/false); |
| timespec ts{}; |
| LIBC_NAMESPACE::internal::clock_gettime(CLOCK_MONOTONIC, &ts); |
| ts.tv_nsec += 10'000'000; |
| if (ts.tv_nsec >= 1'000'000'000) { |
| ts.tv_sec += 1; |
| ts.tv_nsec -= 1'000'000'000; |
| } |
| |
| ASSERT_EQ(sem.clockwait(CLOCK_MONOTONIC, &ts), ETIMEDOUT); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, ClockWaitUnsupportedClock) { |
| Semaphore sem(0, /*is_shared=*/false); |
| timespec ts{}; |
| ts.tv_sec = 1; |
| ts.tv_nsec = 0; |
| // any clock other than CLOCK_MONOTONIC / CLOCK_REALTIME is rejected. |
| ASSERT_EQ(sem.clockwait(static_cast<clockid_t>(99), &ts), EINVAL); |
| } |
| |
| // Named semaphore tests. |
| |
| TEST(LlvmLibcSemaphoreTest, NamedOpenCloseUnlink) { |
| const char *name = "/llvmlibc_test_sem"; |
| |
| // clean up any leftover from previous test runs. |
| Semaphore::unlink(name); |
| |
| // create a new named semaphore. |
| auto result = Semaphore::open(name, O_CREAT | O_EXCL, 0644, 7); |
| ASSERT_TRUE(result.has_value()); |
| |
| Semaphore *sem = result.value(); |
| ASSERT_TRUE(sem->is_valid()); |
| ASSERT_EQ(sem->getvalue(), 7); |
| |
| // close and unlink. |
| ASSERT_EQ(Semaphore::close(sem), 0); |
| ASSERT_EQ(Semaphore::unlink(name), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, NamedOpenExisting) { |
| const char *name = "/llvmlibc_test_sem_exist"; |
| |
| Semaphore::unlink(name); |
| |
| // create a named semaphore. |
| auto r1 = Semaphore::open(name, O_CREAT | O_EXCL, 0644, 10); |
| ASSERT_TRUE(r1.has_value()); |
| |
| Semaphore *sem1 = r1.value(); |
| ASSERT_EQ(sem1->getvalue(), 10); |
| |
| // open the same semaphore again without O_EXCL. |
| auto r2 = Semaphore::open(name, O_CREAT, 0644, 99); |
| ASSERT_TRUE(r2.has_value()); |
| |
| Semaphore *sem2 = r2.value(); |
| ASSERT_EQ(sem2->getvalue(), 10); |
| |
| ASSERT_EQ(Semaphore::close(sem2), 0); |
| ASSERT_EQ(Semaphore::close(sem1), 0); |
| ASSERT_EQ(Semaphore::unlink(name), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, NamedOpenExclFails) { |
| const char *name = "/llvmlibc_test_sem_excl"; |
| |
| Semaphore::unlink(name); |
| |
| // create a named semaphore. |
| auto r1 = Semaphore::open(name, O_CREAT | O_EXCL, 0644, 1); |
| ASSERT_TRUE(r1.has_value()); |
| |
| // trying O_CREAT | O_EXCL again should fail with EEXIST. |
| auto r2 = Semaphore::open(name, O_CREAT | O_EXCL, 0644, 1); |
| ASSERT_FALSE(r2.has_value()); |
| ASSERT_EQ(r2.error(), EEXIST); |
| |
| ASSERT_EQ(Semaphore::close(r1.value()), 0); |
| ASSERT_EQ(Semaphore::unlink(name), 0); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, NamedOpenNonExistent) { |
| // opening a non-existent semaphore without O_CREAT should fail. |
| auto result = Semaphore::open("/llvmlibc_nonexistent", 0, 0, 0); |
| ASSERT_FALSE(result.has_value()); |
| ASSERT_EQ(result.error(), ENOENT); |
| } |
| |
| TEST(LlvmLibcSemaphoreTest, NamedOpenInvalidName) { |
| // empty name. |
| auto r1 = Semaphore::open("", O_CREAT, 0644, 0); |
| ASSERT_FALSE(r1.has_value()); |
| ASSERT_EQ(r1.error(), EINVAL); |
| |
| // name with embedded slash. |
| auto r2 = Semaphore::open("/has/slash", O_CREAT, 0644, 0); |
| ASSERT_FALSE(r2.has_value()); |
| ASSERT_EQ(r2.error(), EINVAL); |
| |
| // just a slash. |
| auto r3 = Semaphore::open("/", O_CREAT, 0644, 0); |
| ASSERT_FALSE(r3.has_value()); |
| ASSERT_EQ(r3.error(), EINVAL); |
| } |