| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// Unit tests for fmemopen. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "hdr/stdio_macros.h" |
| #include "hdr/types/off_t.h" |
| #include "src/__support/CPP/limits.h" |
| #include "src/__support/CPP/scope.h" |
| #include "src/stdio/clearerr.h" |
| #include "src/stdio/fclose.h" |
| #include "src/stdio/feof.h" |
| #include "src/stdio/ferror.h" |
| #include "src/stdio/fflush.h" |
| #include "src/stdio/fgetc.h" |
| #include "src/stdio/fmemopen.h" |
| #include "src/stdio/fread.h" |
| #include "src/stdio/fseek.h" |
| #include "src/stdio/fseeko.h" |
| #include "src/stdio/ftell.h" |
| #include "src/stdio/fwrite.h" |
| #include "src/stdio/setvbuf.h" |
| #include "test/UnitTest/ErrnoCheckingTest.h" |
| #include "test/UnitTest/ErrnoSetterMatcher.h" |
| #include "test/UnitTest/MemoryMatcher.h" |
| #include "test/UnitTest/Test.h" |
| |
| using LlvmLibcFMemOpenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest; |
| using MemoryView = LIBC_NAMESPACE::testing::MemoryView; |
| using LIBC_NAMESPACE::cpp::scope_exit; |
| using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails; |
| using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; |
| |
| TEST_F(LlvmLibcFMemOpenTest, InitialPositionAndEnd) { |
| struct Mode { |
| const char *name; |
| long position; |
| long end; |
| }; |
| const Mode modes[] = { |
| {"r", 0, 8}, {"rb", 0, 8}, {"r+", 0, 8}, {"rb+", 0, 8}, {"r+b", 0, 8}, |
| {"w", 0, 0}, {"wb", 0, 0}, {"w+", 0, 0}, {"wb+", 0, 0}, {"w+b", 0, 0}, |
| {"a", 2, 2}, {"ab", 2, 2}, {"a+", 2, 2}, {"ab+", 2, 2}, {"a+b", 2, 2}, |
| }; |
| for (const auto &mode : modes) { |
| char storage[] = {'A', 'B', '\0', 'x', 'x', 'x', 'x', 'x'}; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), mode.name); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| EXPECT_EQ(mode.position, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(mode.name[0] == 'w' ? '\0' : 'A', storage[0]); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END)); |
| EXPECT_EQ(mode.end, LIBC_NAMESPACE::ftell(f)); |
| } |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, ReadIncludesNullAndStopsAtEnd) { |
| char storage[] = {'A', '\0', 'B', 'C'}; |
| char output[] = {'?', '?', '?', '?', '?', '?'}; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "r"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f)); |
| const char expected[] = {'A', '\0', 'B', 'C', '?', '?'}; |
| EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)), |
| MemoryView(output, sizeof(output))); |
| EXPECT_NE(0, LIBC_NAMESPACE::feof(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f)); |
| EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fread(output, 1, 1, f)); |
| EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::feof(f)); |
| EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f)); |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, WriteAndOverwrite) { |
| char storage[8]; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| EXPECT_EQ(size_t(5), LIBC_NAMESPACE::fwrite("ABCDE", 1, 5, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| EXPECT_STREQ("ABCDE", storage); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| EXPECT_STREQ("AXCDE", storage); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END)); |
| EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("F", 1, 1, f)); |
| // Seeking flushes output and allows an update stream to switch to reading. |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| char output[8]; |
| EXPECT_EQ(size_t(6), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f)); |
| EXPECT_MEM_EQ(MemoryView("AXCDEF", 6), MemoryView(output, 6)); |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, ExactCapacityAndClose) { |
| const int buffer_modes[] = {_IONBF, _IOFBF, _IOLBF}; |
| for (int buffering : buffer_modes) { |
| char guarded[] = {'L', '?', '?', '?', '?', 'R'}; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w"); |
| ASSERT_TRUE(f != nullptr); |
| EXPECT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16)); |
| EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCD", 1, 4, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); |
| const char expected[] = {'L', 'A', 'B', 'C', 'D', 'R'}; |
| EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)), |
| MemoryView(guarded, sizeof(guarded))); |
| } |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, CloseFlushesPendingOutput) { |
| char storage[8]; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w"); |
| ASSERT_TRUE(f != nullptr); |
| EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); |
| EXPECT_STREQ("ABC", storage); |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, AppendAfterSeekAndRead) { |
| const int buffer_modes[] = {_IONBF, _IOFBF}; |
| for (int buffering : buffer_modes) { |
| char storage[8] = "AB"; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_STREQ("ABX", storage); |
| // Even a position beyond the current end must not create an append hole. |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 7, SEEK_SET)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Y", 1, 1, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| EXPECT_STREQ("ABXY", storage); |
| } |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, AppendTracksEndAcrossNullBytes) { |
| char storage[8] = ""; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f)); |
| const char first[] = {'A', '\0', 'B'}; |
| EXPECT_EQ(sizeof(first), LIBC_NAMESPACE::fwrite(first, 1, sizeof(first), f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("C", 1, 1, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| const char expected[] = {'A', '\0', 'B', 'C', '\0'}; |
| EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)), |
| MemoryView(storage, sizeof(expected))); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END)); |
| EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f)); |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, SeekBoundsAndEnd) { |
| char storage[8]; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| |
| EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f)); |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 8, SEEK_SET), Succeeds()); |
| EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 9, SEEK_SET), Fails(EINVAL)); |
| EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_SET), Fails(EINVAL)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds()); |
| EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 2, SEEK_END), Succeeds()); |
| EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f)); |
| |
| // Reading past the data end must not advance the memory stream's position. |
| EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f)); |
| EXPECT_NE(0, LIBC_NAMESPACE::feof(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR), Succeeds()); |
| EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_END), Succeeds()); |
| EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f)); |
| |
| const off_t offsets[] = {LIBC_NAMESPACE::cpp::numeric_limits<off_t>::min(), |
| LIBC_NAMESPACE::cpp::numeric_limits<off_t>::max()}; |
| for (off_t offset : offsets) { |
| ASSERT_THAT(LIBC_NAMESPACE::fseeko(f, offset, SEEK_CUR), Fails(EINVAL)); |
| EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f)); |
| } |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 6, SEEK_SET), Succeeds()); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Z", 1, 1, f)); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds()); |
| EXPECT_EQ(7L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ('Z', storage[6]); |
| EXPECT_EQ('\0', storage[7]); |
| |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, -1), Fails(EINVAL)); |
| // The contents of the gap between the old end and position 6 are unspecified. |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, InternalBuffer) { |
| const char *update_modes[] = {"w+", "a+", "r+"}; |
| for (const char *mode : update_modes) { |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 16, mode); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END)); |
| EXPECT_EQ(mode[0] == 'r' ? 16L : 0L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| char output[3]; |
| EXPECT_EQ(sizeof(output), |
| LIBC_NAMESPACE::fread(output, 1, sizeof(output), f)); |
| EXPECT_MEM_EQ(MemoryView("ABC", 3), MemoryView(output, 3)); |
| } |
| // Non-update modes are accepted, just as with an externally supplied buffer. |
| const char *other_modes[] = {"r", "w", "a"}; |
| for (const char *mode : other_modes) { |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 4, mode); |
| ASSERT_TRUE(f != nullptr); |
| EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); |
| } |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, ZeroCapacity) { |
| char guard = 'X'; |
| void *buffers[] = {&guard, nullptr}; |
| for (void *buf : buffers) { |
| const char *modes[] = {"r+", "w+", "a+"}; |
| for (const char *mode : modes) { |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(buf, 0, mode); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1)); |
| EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f)); |
| EXPECT_NE(0, LIBC_NAMESPACE::feof(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f)); |
| ASSERT_ERRNO_EQ(ENOSPC); |
| EXPECT_NE(0, LIBC_NAMESPACE::ferror(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END)); |
| EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET)); |
| ASSERT_ERRNO_EQ(EINVAL); |
| EXPECT_EQ('X', guard); |
| } |
| } |
| } |
| |
| TEST_F(LlvmLibcFMemOpenTest, ShortWriteAndRecovery) { |
| char guarded[] = {'L', '?', '?', '?', '?', 'R'}; |
| ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w+"); |
| ASSERT_TRUE(f != nullptr); |
| scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); }); |
| ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1)); |
| EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCDEF", 1, 6, f)); |
| ASSERT_ERRNO_EQ(ENOSPC); |
| EXPECT_NE(0, LIBC_NAMESPACE::ferror(f)); |
| EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f)); |
| EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("XYZ", 1, 3, f)); |
| ASSERT_ERRNO_EQ(ENOSPC); |
| LIBC_NAMESPACE::clearerr(f); |
| EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET)); |
| EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f)); |
| EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f)); |
| const char expected[] = {'L', 'X', 'B', 'C', 'D', 'R'}; |
| EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)), |
| MemoryView(guarded, sizeof(guarded))); |
| } |