| //===-- 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/limits_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(SEM_VALUE_MAX, /*is_shared=*/false); | |
| ASSERT_EQ(sem.post(), EOVERFLOW); | |
| ASSERT_EQ(sem.getvalue(), 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); | |
| } |