| // 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 |