| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 | |
| /// Unittests for fgetpos | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "hdr/stdio_macros.h" | |
| #include "hdr/types/FILE.h" | |
| #include "hdr/types/fpos_t.h" | |
| #include "hdr/types/off_t.h" | |
| #include "hdr/types/size_t.h" | |
| #include "hdr/types/wint_t.h" | |
| #include "src/__support/CPP/scope.h" | |
| #include "src/__support/libc_errno.h" | |
| #include "src/stdio/fclose.h" | |
| #include "src/stdio/feof.h" | |
| #include "src/stdio/fgetpos.h" | |
| #include "src/stdio/fopen.h" | |
| #include "src/stdio/fread.h" | |
| #include "src/stdio/fseek.h" | |
| #include "src/stdio/fwrite.h" | |
| #include "src/stdio/remove.h" | |
| #include "src/stdio/setvbuf.h" | |
| #include "src/stdio/ungetc.h" | |
| #include "src/wchar/fgetwc.h" | |
| #include "src/wchar/fputwc.h" | |
| #include "src/wchar/fwide.h" | |
| #include "src/wchar/mbsinit.h" | |
| #include "src/wchar/ungetwc.h" | |
| #include "test/UnitTest/ErrnoCheckingTest.h" | |
| #include "test/UnitTest/ErrnoSetterMatcher.h" | |
| #include "test/UnitTest/Test.h" | |
| using LIBC_NAMESPACE::cpp::scope_exit; | |
| using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; | |
| class LlvmLibcFgetposTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest { | |
| protected: | |
| void test_with_bufmode(int bufmode) { | |
| constexpr char FILENAME[] = "fgetpos_bufmode.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| constexpr size_t BUFFER_SIZE = 1024; | |
| char buffer[BUFFER_SIZE]; | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(file, buffer, bufmode, BUFFER_SIZE)); | |
| constexpr char CONTENT[] = "12\n345\n6789"; | |
| constexpr size_t WRITE_SIZE = sizeof(CONTENT) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(CONTENT, 1, WRITE_SIZE, file), | |
| Succeeds(WRITE_SIZE)); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(WRITE_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 0, SEEK_SET), Succeeds(0)); | |
| constexpr size_t READ_SIZE = WRITE_SIZE / 2; | |
| char data[READ_SIZE]; | |
| ASSERT_THAT(LIBC_NAMESPACE::fread(data, 1, READ_SIZE, file), | |
| Succeeds(READ_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(READ_SIZE)); | |
| } | |
| }; | |
| TEST_F(LlvmLibcFgetposTest, NewlyOpenedFile) { | |
| constexpr char FILENAME[] = "fgetpos_newly_opened.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(0)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, WriteAndRead) { | |
| constexpr char FILENAME[] = "fgetpos_write_read.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| constexpr char FIRST_DATA[] = "1234567890"; // 10 bytes | |
| constexpr size_t FIRST_SIZE = sizeof(FIRST_DATA) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(FIRST_DATA, 1, FIRST_SIZE, file), | |
| Succeeds(FIRST_SIZE)); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(FIRST_SIZE)); | |
| constexpr char SECOND_DATA[] = "abcde"; // 5 bytes | |
| constexpr size_t SECOND_SIZE = sizeof(SECOND_DATA) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(SECOND_DATA, 1, SECOND_SIZE, file), | |
| Succeeds(SECOND_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(FIRST_SIZE + SECOND_SIZE)); | |
| // Seek to offset 4 | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 4, SEEK_SET), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(4)); | |
| // Read 3 bytes | |
| char read_buf[4]; | |
| ASSERT_THAT(LIBC_NAMESPACE::fread(read_buf, 1, 3, file), Succeeds(size_t(3))); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(7)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, UngetcEffectBinaryStream) { | |
| constexpr char FILENAME[] = "fgetpos_ungetc_bin.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| // ISO C §7.21.7.10 guarantees decrement by 1 specifically for binary streams. | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "wb+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| constexpr char DATA[] = "abcdef"; | |
| constexpr size_t DATA_SIZE = sizeof(DATA) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(DATA, 1, DATA_SIZE, file), | |
| Succeeds(DATA_SIZE)); | |
| // Rewind to beginning | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 0, SEEK_SET), Succeeds(0)); | |
| char read_buf[4]; | |
| ASSERT_THAT(LIBC_NAMESPACE::fread(read_buf, 1, 3, file), Succeeds(size_t(3))); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(3)); | |
| // Pushing back a character decrements the file position indicator by 1 | |
| ASSERT_EQ(LIBC_NAMESPACE::ungetc(read_buf[2], file), int(read_buf[2])); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(2)); | |
| // Reading back the character restores position | |
| char c; | |
| ASSERT_THAT(LIBC_NAMESPACE::fread(&c, 1, 1, file), Succeeds(size_t(1))); | |
| ASSERT_EQ(c, read_buf[2]); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(3)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, WideStream) { | |
| constexpr char FILENAME[] = "fgetpos_wide.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| // Orient stream to wide | |
| ASSERT_GT(LIBC_NAMESPACE::fwide(file, 1), 0); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(0)); | |
| // Verify initial parse state is recorded | |
| ASSERT_NE(LIBC_NAMESPACE::mbsinit(&pos.__state), 0); | |
| // Write ASCII wide char (1 byte in UTF-8) | |
| ASSERT_EQ(LIBC_NAMESPACE::fputwc(L'A', file), static_cast<wint_t>(L'A')); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(1)); | |
| // Write multi-byte wide char: L'¢' (2 bytes in UTF-8: 0xC2, 0xA2) | |
| ASSERT_EQ(LIBC_NAMESPACE::fputwc(L'¢', file), static_cast<wint_t>(L'¢')); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(3)); | |
| // Seek to start | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 0, SEEK_SET), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(0)); | |
| // Read back first wide char | |
| ASSERT_EQ(LIBC_NAMESPACE::fgetwc(file), static_cast<wint_t>(L'A')); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(1)); | |
| // Read back second wide char (multi-byte) | |
| ASSERT_EQ(LIBC_NAMESPACE::fgetwc(file), static_cast<wint_t>(L'¢')); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(3)); | |
| // Push back wide character and then read back to verify restoration | |
| ASSERT_EQ(LIBC_NAMESPACE::ungetwc(L'¢', file), static_cast<wint_t>(L'¢')); | |
| ASSERT_EQ(LIBC_NAMESPACE::fgetwc(file), static_cast<wint_t>(L'¢')); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(3)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, AtEOF) { | |
| constexpr char FILENAME[] = "fgetpos_eof.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| constexpr char DATA[] = "hello"; | |
| constexpr size_t DATA_SIZE = sizeof(DATA) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(DATA, 1, DATA_SIZE, file), | |
| Succeeds(DATA_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 0, SEEK_SET), Succeeds(0)); | |
| // Reading past end in one call sets EOF indicator with non-zero read_limit | |
| char buf[DATA_SIZE + 1]; | |
| ASSERT_EQ(LIBC_NAMESPACE::fread(buf, 1, DATA_SIZE + 1, file), DATA_SIZE); | |
| ASSERT_NE(LIBC_NAMESPACE::feof(file), 0); | |
| // fgetpos at EOF must succeed, report file size, and preserve EOF indicator | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(DATA_SIZE)); | |
| ASSERT_NE(LIBC_NAMESPACE::feof(file), 0); | |
| // ISO C allows switching from input to output without an intervening seek | |
| // if the input operation encountered end-of-file. | |
| constexpr char EXTRA[] = "!"; | |
| constexpr size_t EXTRA_SIZE = sizeof(EXTRA) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(EXTRA, 1, EXTRA_SIZE, file), | |
| Succeeds(EXTRA_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(DATA_SIZE + EXTRA_SIZE)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, AppendMode) { | |
| constexpr char FILENAME[] = "fgetpos_append.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "ab+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| constexpr char FIRST[] = "12345"; | |
| constexpr size_t FIRST_SIZE = sizeof(FIRST) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(FIRST, 1, FIRST_SIZE, file), | |
| Succeeds(FIRST_SIZE)); | |
| fpos_t pos; | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(FIRST_SIZE)); | |
| // Seek to beginning | |
| ASSERT_THAT(LIBC_NAMESPACE::fseek(file, 0, SEEK_SET), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(0)); | |
| // In append mode, write is always positioned at EOF | |
| constexpr char SECOND[] = "67890"; | |
| constexpr size_t SECOND_SIZE = sizeof(SECOND) - 1; | |
| ASSERT_THAT(LIBC_NAMESPACE::fwrite(SECOND, 1, SECOND_SIZE, file), | |
| Succeeds(SECOND_SIZE)); | |
| ASSERT_THAT(LIBC_NAMESPACE::fgetpos(file, &pos), Succeeds(0)); | |
| ASSERT_EQ(pos.__pos, off_t(FIRST_SIZE + SECOND_SIZE)); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, ErrnoPreservedOnSuccess) { | |
| constexpr char FILENAME[] = "fgetpos_errno.test"; | |
| auto FILEPATH = libc_make_test_file_path(FILENAME); | |
| ::FILE *file = LIBC_NAMESPACE::fopen(FILEPATH, "w+"); | |
| ASSERT_NE(file, nullptr); | |
| scope_exit close_file([&] { | |
| ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); | |
| ASSERT_THAT(LIBC_NAMESPACE::remove(FILEPATH), Succeeds(0)); | |
| }); | |
| // Pre-set errno to non-zero value | |
| libc_errno = 42; | |
| fpos_t pos; | |
| ASSERT_EQ(LIBC_NAMESPACE::fgetpos(file, &pos), 0); | |
| ASSERT_EQ(pos.__pos, off_t(0)); | |
| // ISO C / POSIX: errno must remain unaltered on success | |
| ASSERT_ERRNO_EQ(42); | |
| } | |
| TEST_F(LlvmLibcFgetposTest, BufferingModes) { | |
| test_with_bufmode(_IOFBF); | |
| test_with_bufmode(_IOLBF); | |
| test_with_bufmode(_IONBF); | |
| } |