blob: 06747d0c3c84265bac4f27bdf22f2b5cc546b99f [file]
//===-- 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);
}