//===-- PipeTest.cpp ------------------------------------------------------===// // // 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 "lldb/Host/Pipe.h" #include "TestingSupport/SubsystemRAII.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/HostInfo.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" #include #include #include #include #include #include using namespace lldb_private; class PipeTest : public testing::Test { public: SubsystemRAII subsystems; }; TEST_F(PipeTest, CreateWithUniqueName) { Pipe pipe; llvm::SmallString<0> name; ASSERT_THAT_ERROR( pipe.CreateWithUniqueName("PipeTest-CreateWithUniqueName", name) .ToError(), llvm::Succeeded()); } // Test broken #ifndef _WIN32 TEST_F(PipeTest, OpenAsReader) { Pipe pipe; llvm::SmallString<0> name; ASSERT_THAT_ERROR( pipe.CreateWithUniqueName("PipeTest-OpenAsReader", name).ToError(), llvm::Succeeded()); // Ensure name is not null-terminated size_t name_len = name.size(); name += "foobar"; llvm::StringRef name_ref(name.data(), name_len); ASSERT_THAT_ERROR(pipe.OpenAsReader(name_ref).ToError(), llvm::Succeeded()); ASSERT_TRUE(pipe.CanRead()); } #endif // Tests flaky on Windows #ifndef _WIN32 TEST_F(PipeTest, WriteWithTimeout) { Pipe pipe; ASSERT_THAT_ERROR(pipe.CreateNew().ToError(), llvm::Succeeded()); // The pipe buffer is 1024 for PipeWindows and at least 512 on Darwin. // In Linux versions before 2.6.11, the capacity of a pipe was the same as the // system page size (e.g., 4096 bytes on i386). // Since Linux 2.6.11, the pipe capacity is 16 pages (i.e., 65,536 bytes in a // system with a page size of 4096 bytes). // Since Linux 2.6.35, the default pipe capacity is 16 pages, but the capacity // can be queried and set using the fcntl(2) F_GETPIPE_SZ and F_SETPIPE_SZ // operations: #if !defined(_WIN32) && defined(F_SETPIPE_SZ) ::fcntl(pipe.GetWriteFileDescriptor(), F_SETPIPE_SZ, 4096); #endif const size_t buf_size = 66000; // Note write_chunk_size must be less than the pipe buffer. const size_t write_chunk_size = 234; std::vector write_buf(buf_size / sizeof(int32_t)); std::iota(write_buf.begin(), write_buf.end(), 0); std::vector read_buf(write_buf.size() + 100, -1); char *write_ptr = reinterpret_cast(write_buf.data()); char *read_ptr = reinterpret_cast(read_buf.data()); size_t write_bytes = 0; size_t read_bytes = 0; // Write to the pipe until it is full. while (write_bytes + write_chunk_size <= buf_size) { llvm::Expected num_bytes = pipe.Write(write_ptr + write_bytes, write_chunk_size, std::chrono::milliseconds(10)); if (num_bytes) { write_bytes += *num_bytes; } else { ASSERT_THAT_ERROR(num_bytes.takeError(), llvm::Failed()); break; // The write buffer is full. } } ASSERT_LE(write_bytes + write_chunk_size, buf_size) << "Pipe buffer larger than expected"; // Attempt a write with a long timeout. auto start_time = std::chrono::steady_clock::now(); // TODO: Assert a specific error (EAGAIN?) here. ASSERT_THAT_EXPECTED(pipe.Write(write_ptr + write_bytes, write_chunk_size, std::chrono::seconds(2)), llvm::Failed()); auto dur = std::chrono::steady_clock::now() - start_time; ASSERT_GE(dur, std::chrono::seconds(2)); // Attempt a write with a short timeout. start_time = std::chrono::steady_clock::now(); ASSERT_THAT_EXPECTED(pipe.Write(write_ptr + write_bytes, write_chunk_size, std::chrono::milliseconds(200)), llvm::Failed()); dur = std::chrono::steady_clock::now() - start_time; ASSERT_GE(dur, std::chrono::milliseconds(200)); ASSERT_LT(dur, std::chrono::seconds(2)); // Drain the pipe. while (read_bytes < write_bytes) { llvm::Expected num_bytes = pipe.Read(read_ptr + read_bytes, write_bytes - read_bytes, std::chrono::milliseconds(10)); ASSERT_THAT_EXPECTED(num_bytes, llvm::Succeeded()); read_bytes += *num_bytes; } // Be sure the pipe is empty. ASSERT_THAT_EXPECTED( pipe.Read(read_ptr + read_bytes, 100, std::chrono::milliseconds(10)), llvm::Failed()); // Check that we got what we wrote. ASSERT_EQ(write_bytes, read_bytes); ASSERT_TRUE(std::equal(write_buf.begin(), write_buf.begin() + write_bytes / sizeof(uint32_t), read_buf.begin())); // Write to the pipe again and check that it succeeds. ASSERT_THAT_EXPECTED( pipe.Write(write_ptr, write_chunk_size, std::chrono::milliseconds(10)), llvm::Succeeded()); } TEST_F(PipeTest, ReadWithTimeout) { Pipe pipe; ASSERT_THAT_ERROR(pipe.CreateNew().ToError(), llvm::Succeeded()); char buf[100]; // The pipe is initially empty. A polling read returns immediately. ASSERT_THAT_EXPECTED(pipe.Read(buf, sizeof(buf), std::chrono::seconds(0)), llvm::Failed()); // With a timeout, we should wait for at least this amount of time (but not // too much). auto start = std::chrono::steady_clock::now(); ASSERT_THAT_EXPECTED( pipe.Read(buf, sizeof(buf), std::chrono::milliseconds(200)), llvm::Failed()); auto dur = std::chrono::steady_clock::now() - start; EXPECT_GT(dur, std::chrono::milliseconds(200)); EXPECT_LT(dur, std::chrono::seconds(2)); // Write something into the pipe, and read it back. The blocking read call // should return even though it hasn't filled the buffer. llvm::StringRef hello_world("Hello world!"); ASSERT_THAT_EXPECTED(pipe.Write(hello_world.data(), hello_world.size()), llvm::HasValue(hello_world.size())); ASSERT_THAT_EXPECTED(pipe.Read(buf, sizeof(buf)), llvm::HasValue(hello_world.size())); EXPECT_EQ(llvm::StringRef(buf, hello_world.size()), hello_world); // Now write something and try to read it in chunks. memset(buf, 0, sizeof(buf)); ASSERT_THAT_EXPECTED(pipe.Write(hello_world.data(), hello_world.size()), llvm::HasValue(hello_world.size())); ASSERT_THAT_EXPECTED(pipe.Read(buf, 4), llvm::HasValue(4)); ASSERT_THAT_EXPECTED(pipe.Read(buf + 4, sizeof(buf) - 4), llvm::HasValue(hello_world.size() - 4)); EXPECT_EQ(llvm::StringRef(buf, hello_world.size()), hello_world); // A blocking read should wait until the data arrives. memset(buf, 0, sizeof(buf)); std::future> future_num_bytes = std::async( std::launch::async, [&] { return pipe.Read(buf, sizeof(buf)); }); std::this_thread::sleep_for(std::chrono::milliseconds(10)); ASSERT_THAT_EXPECTED(pipe.Write(hello_world.data(), hello_world.size()), llvm::HasValue(hello_world.size())); ASSERT_THAT_EXPECTED(future_num_bytes.get(), llvm::HasValue(hello_world.size())); EXPECT_EQ(llvm::StringRef(buf, hello_world.size()), hello_world); } #endif /*ifndef _WIN32*/