blob: 2ba61eb2b1fe1abe7af63f0367f861c44345be1e [file]
// Copyright 2026 The IREE Authors
//
// Licensed 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
// Tests for iree_async_primitive_t lifecycle operations (dup, close).
#include "iree/async/primitive.h"
#if !defined(IREE_PLATFORM_WINDOWS)
#include <fcntl.h>
#include <unistd.h>
#else
// clang-format off
#include <windows.h>
// clang-format on
#endif // !IREE_PLATFORM_WINDOWS
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"
namespace {
// Creates a platform-native primitive for testing. On POSIX, creates a pipe and
// returns the read end (caller must close the write end via close_write_end).
// On Windows, creates an auto-reset Event.
class PrimitiveTestFixture : public ::testing::Test {
protected:
#if !defined(IREE_PLATFORM_WINDOWS)
void SetUp() override {
int fds[2];
ASSERT_EQ(pipe(fds), 0) << "pipe: " << strerror(errno);
read_fd_ = fds[0];
write_fd_ = fds[1];
}
void TearDown() override {
if (write_fd_ >= 0) close(write_fd_);
if (read_fd_ >= 0) close(read_fd_);
}
iree_async_primitive_t MakePrimitive() {
return iree_async_primitive_from_fd(read_fd_);
}
// Verifies that a primitive refers to a valid, readable fd by writing a byte
// through the pipe and reading it back from the given fd.
void AssertPrimitiveValid(iree_async_primitive_t primitive) {
ASSERT_EQ(primitive.type, IREE_ASYNC_PRIMITIVE_TYPE_FD);
char byte = 'x';
ASSERT_EQ(write(write_fd_, &byte, 1), 1);
char result = 0;
ASSERT_EQ(read(primitive.value.fd, &result, 1), 1);
ASSERT_EQ(result, 'x');
}
int read_fd_ = -1;
int write_fd_ = -1;
#else
void SetUp() override {
event_ = CreateEventW(NULL, FALSE, FALSE, NULL);
ASSERT_NE(event_, (HANDLE)NULL) << "CreateEvent: " << GetLastError();
}
void TearDown() override {
if (event_) CloseHandle(event_);
}
iree_async_primitive_t MakePrimitive() {
return iree_async_primitive_from_win32_handle((uintptr_t)event_);
}
// Verifies that a primitive refers to a valid Event handle by signaling
// and waiting on it.
void AssertPrimitiveValid(iree_async_primitive_t primitive) {
ASSERT_EQ(primitive.type, IREE_ASYNC_PRIMITIVE_TYPE_WIN32_HANDLE);
HANDLE handle = (HANDLE)primitive.value.win32_handle;
ASSERT_TRUE(SetEvent(handle)) << "SetEvent: " << GetLastError();
DWORD wait_result = WaitForSingleObject(handle, 0);
ASSERT_EQ(wait_result, WAIT_OBJECT_0);
}
HANDLE event_ = NULL;
#endif // !IREE_PLATFORM_WINDOWS
};
//===----------------------------------------------------------------------===//
// iree_async_primitive_dup
//===----------------------------------------------------------------------===//
TEST_F(PrimitiveTestFixture, DupNoneFails) {
iree_async_primitive_t dup = {};
IREE_EXPECT_STATUS_IS(
IREE_STATUS_INVALID_ARGUMENT,
iree_async_primitive_dup(iree_async_primitive_none(), &dup));
EXPECT_TRUE(iree_async_primitive_is_none(dup));
}
TEST_F(PrimitiveTestFixture, DupProducesValidHandle) {
iree_async_primitive_t original = MakePrimitive();
iree_async_primitive_t dup = {};
IREE_ASSERT_OK(iree_async_primitive_dup(original, &dup));
// Dup should have the same type but a different underlying handle value.
EXPECT_EQ(dup.type, original.type);
#if !defined(IREE_PLATFORM_WINDOWS)
EXPECT_NE(dup.value.fd, original.value.fd);
#else
EXPECT_NE(dup.value.win32_handle, original.value.win32_handle);
#endif
// The dup should be independently usable.
AssertPrimitiveValid(dup);
iree_async_primitive_close(&dup);
}
TEST_F(PrimitiveTestFixture, DupIsIndependentOfOriginal) {
iree_async_primitive_t original = MakePrimitive();
iree_async_primitive_t dup = {};
IREE_ASSERT_OK(iree_async_primitive_dup(original, &dup));
// Close the dup — the original should still be valid.
iree_async_primitive_close(&dup);
EXPECT_TRUE(iree_async_primitive_is_none(dup));
AssertPrimitiveValid(original);
}
TEST_F(PrimitiveTestFixture, MultipleDupsAreIndependent) {
iree_async_primitive_t original = MakePrimitive();
iree_async_primitive_t dup1 = {};
iree_async_primitive_t dup2 = {};
IREE_ASSERT_OK(iree_async_primitive_dup(original, &dup1));
IREE_ASSERT_OK(iree_async_primitive_dup(original, &dup2));
// All three should be distinct handles.
#if !defined(IREE_PLATFORM_WINDOWS)
EXPECT_NE(dup1.value.fd, dup2.value.fd);
EXPECT_NE(dup1.value.fd, original.value.fd);
#else
EXPECT_NE(dup1.value.win32_handle, dup2.value.win32_handle);
EXPECT_NE(dup1.value.win32_handle, original.value.win32_handle);
#endif
iree_async_primitive_close(&dup1);
iree_async_primitive_close(&dup2);
AssertPrimitiveValid(original);
}
//===----------------------------------------------------------------------===//
// iree_async_primitive_close
//===----------------------------------------------------------------------===//
TEST_F(PrimitiveTestFixture, CloseNoneIsNoop) {
iree_async_primitive_t none = iree_async_primitive_none();
iree_async_primitive_close(&none); // Should not crash.
EXPECT_TRUE(iree_async_primitive_is_none(none));
}
TEST_F(PrimitiveTestFixture, CloseNullIsNoop) {
iree_async_primitive_close(NULL); // Should not crash.
}
TEST_F(PrimitiveTestFixture, CloseSetsToNone) {
// Dup so we have a handle we can close without affecting the fixture's
// cleanup.
iree_async_primitive_t original = MakePrimitive();
iree_async_primitive_t dup = {};
IREE_ASSERT_OK(iree_async_primitive_dup(original, &dup));
EXPECT_FALSE(iree_async_primitive_is_none(dup));
iree_async_primitive_close(&dup);
EXPECT_TRUE(iree_async_primitive_is_none(dup));
}
//===----------------------------------------------------------------------===//
// iree_async_primitive_t construction helpers
//===----------------------------------------------------------------------===//
TEST(PrimitiveTest, NoneIsDefault) {
iree_async_primitive_t none = iree_async_primitive_none();
EXPECT_EQ(none.type, IREE_ASYNC_PRIMITIVE_TYPE_NONE);
EXPECT_TRUE(iree_async_primitive_is_none(none));
}
TEST(PrimitiveTest, MakePreservesTypeAndValue) {
iree_async_primitive_value_t value;
memset(&value, 0, sizeof(value));
value.reserved = 0x42;
iree_async_primitive_t p =
iree_async_primitive_make(IREE_ASYNC_PRIMITIVE_TYPE_NONE, value);
EXPECT_EQ(p.type, IREE_ASYNC_PRIMITIVE_TYPE_NONE);
EXPECT_EQ(p.value.reserved, 0x42);
}
TEST(PrimitiveTest, IsNoneOnlyMatchesNoneType) {
// Non-NONE type with zero value should not be considered NONE.
iree_async_primitive_value_t value;
memset(&value, 0, sizeof(value));
iree_async_primitive_t p =
iree_async_primitive_make(IREE_ASYNC_PRIMITIVE_TYPE_FD, value);
EXPECT_FALSE(iree_async_primitive_is_none(p));
}
#if defined(IREE_ASYNC_HAVE_FD)
TEST(PrimitiveTest, FromFd) {
iree_async_primitive_t p = iree_async_primitive_from_fd(42);
EXPECT_EQ(p.type, IREE_ASYNC_PRIMITIVE_TYPE_FD);
EXPECT_EQ(p.value.fd, 42);
EXPECT_FALSE(iree_async_primitive_is_none(p));
}
#endif // IREE_ASYNC_HAVE_FD
#if defined(IREE_ASYNC_HAVE_WIN32_HANDLE)
TEST(PrimitiveTest, FromWin32Handle) {
iree_async_primitive_t p = iree_async_primitive_from_win32_handle(0xDEADBEEF);
EXPECT_EQ(p.type, IREE_ASYNC_PRIMITIVE_TYPE_WIN32_HANDLE);
EXPECT_EQ(p.value.win32_handle, (uintptr_t)0xDEADBEEF);
EXPECT_FALSE(iree_async_primitive_is_none(p));
}
#endif // IREE_ASYNC_HAVE_WIN32_HANDLE
#if defined(IREE_ASYNC_HAVE_MACH_PORT)
TEST(PrimitiveTest, FromMachPort) {
iree_async_primitive_t p = iree_async_primitive_from_mach_port(12345);
EXPECT_EQ(p.type, IREE_ASYNC_PRIMITIVE_TYPE_MACH_PORT);
EXPECT_EQ(p.value.mach_port, 12345u);
EXPECT_FALSE(iree_async_primitive_is_none(p));
}
#endif // IREE_ASYNC_HAVE_MACH_PORT
} // namespace