| //===-- Unittests for platform independent file class ---------------------===// | |
| // | |
| // 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 "hdr/types/size_t.h" | |
| #include "hdr/wchar_macros.h" | |
| #include "src/__support/CPP/new.h" | |
| #include "src/__support/File/file.h" | |
| #include "src/__support/File/file_mode.h" | |
| #include "src/__support/alloc-checker.h" | |
| #include "src/__support/error_or.h" | |
| #include "src/__support/macros/config.h" | |
| #include "test/UnitTest/MemoryMatcher.h" | |
| #include "test/UnitTest/Test.h" | |
| using MemoryView = LIBC_NAMESPACE::testing::MemoryView; | |
| using LIBC_NAMESPACE::ErrorOr; | |
| using LIBC_NAMESPACE::File; | |
| using LIBC_NAMESPACE::FileIOResult; | |
| using LIBC_NAMESPACE::FileMode; | |
| class StringFile : public File { | |
| static constexpr size_t SIZE = 512; | |
| size_t pos; | |
| char str[SIZE] = {0}; | |
| size_t eof_marker; | |
| bool write_append; | |
| static FileIOResult str_read(LIBC_NAMESPACE::File *f, void *data, size_t len); | |
| static FileIOResult str_write(LIBC_NAMESPACE::File *f, const void *data, | |
| size_t len); | |
| static ErrorOr<off_t> str_seek(LIBC_NAMESPACE::File *f, off_t offset, | |
| int whence); | |
| static int str_close(LIBC_NAMESPACE::File *f) { | |
| delete reinterpret_cast<StringFile *>(f); | |
| return 0; | |
| } | |
| public: | |
| explicit StringFile(char *buffer, size_t buflen, int bufmode, bool owned, | |
| FileMode mode) | |
| : LIBC_NAMESPACE::File(&str_write, &str_read, &str_seek, &str_close, | |
| reinterpret_cast<uint8_t *>(buffer), buflen, | |
| bufmode, owned, mode), | |
| pos(0), eof_marker(0), write_append(false) { | |
| if (mode.is_append()) | |
| write_append = true; | |
| } | |
| void reset() { pos = 0; } | |
| size_t get_pos() const { return pos; } | |
| char *get_str() { return str; } | |
| // Use this method to prefill the file. | |
| void reset_and_fill(const char *data, size_t len) { | |
| size_t i; | |
| for (i = 0; i < len && i < SIZE; ++i) { | |
| str[i] = data[i]; | |
| } | |
| pos = 0; | |
| eof_marker = i; | |
| } | |
| }; | |
| FileIOResult StringFile::str_read(LIBC_NAMESPACE::File *f, void *data, | |
| size_t len) { | |
| StringFile *sf = static_cast<StringFile *>(f); | |
| if (sf->pos >= sf->eof_marker) | |
| return 0; | |
| size_t i = 0; | |
| for (i = 0; i < len; ++i) | |
| reinterpret_cast<char *>(data)[i] = sf->str[sf->pos + i]; | |
| sf->pos += i; | |
| return i; | |
| } | |
| FileIOResult StringFile::str_write(LIBC_NAMESPACE::File *f, const void *data, | |
| size_t len) { | |
| StringFile *sf = static_cast<StringFile *>(f); | |
| if (sf->write_append) | |
| sf->pos = sf->eof_marker; | |
| if (sf->pos >= SIZE) | |
| return 0; | |
| size_t i = 0; | |
| for (i = 0; i < len && sf->pos < SIZE; ++i, ++sf->pos) | |
| sf->str[sf->pos] = reinterpret_cast<const char *>(data)[i]; | |
| // Move the eof marker if the data was written beyond the current eof marker. | |
| if (sf->pos > sf->eof_marker) | |
| sf->eof_marker = sf->pos; | |
| return i; | |
| } | |
| ErrorOr<off_t> StringFile::str_seek(LIBC_NAMESPACE::File *f, off_t offset, | |
| int whence) { | |
| StringFile *sf = static_cast<StringFile *>(f); | |
| if (whence == SEEK_SET) | |
| sf->pos = offset; | |
| if (whence == SEEK_CUR) | |
| sf->pos += offset; | |
| if (whence == SEEK_END) | |
| sf->pos = SIZE + offset; | |
| return sf->pos; | |
| } | |
| StringFile *new_string_file(char *buffer, size_t buflen, int bufmode, | |
| bool owned, const char *mode) { | |
| LIBC_NAMESPACE::AllocChecker ac; | |
| const FileMode file_mode(mode); | |
| // We will just assume the allocation succeeds. We cannot test anything | |
| // otherwise. | |
| return new (ac) StringFile(buffer, buflen, bufmode, owned, file_mode); | |
| } | |
| TEST(LlvmLibcFileTest, WriteOnly) { | |
| const char data[] = "hello, file"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w"); | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream | |
| ASSERT_EQ(f->flush(), 0); | |
| EXPECT_EQ(f->get_pos(), sizeof(data)); // Data should now be available | |
| EXPECT_STREQ(f->get_str(), data); | |
| f->reset(); | |
| ASSERT_EQ(f->get_pos(), size_t(0)); | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream | |
| // The second write should trigger a buffer flush. | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_GE(f->get_pos(), size_t(0)); | |
| ASSERT_EQ(f->flush(), 0); | |
| EXPECT_EQ(f->get_pos(), 2 * sizeof(data)); | |
| MemoryView src1("hello, file\0hello, file", sizeof(data) * 2), | |
| dst1(f->get_str(), sizeof(data) * 2); | |
| EXPECT_MEM_EQ(src1, dst1); | |
| char read_data[sizeof(data)]; | |
| { | |
| // This is not a readable file. | |
| auto result = f->read(read_data, sizeof(data)); | |
| EXPECT_EQ(result.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| EXPECT_TRUE(result.has_error()); | |
| } | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WriteLineBuffered) { | |
| const char data[] = "hello\n file"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2; | |
| char file_buffer_line[FILE_BUFFER_SIZE]; | |
| char file_buffer_full[FILE_BUFFER_SIZE]; | |
| StringFile *f_line = | |
| new_string_file(file_buffer_line, FILE_BUFFER_SIZE, _IOLBF, false, "w"); | |
| // We also initialize a fully buffered file we'll do the same writes to for | |
| // comparison. | |
| StringFile *f_full = | |
| new_string_file(file_buffer_full, FILE_BUFFER_SIZE, _IOFBF, false, "w"); | |
| ASSERT_EQ(sizeof(data), f_line->write(data, sizeof(data)).value); | |
| ASSERT_EQ(sizeof(data), f_full->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f_line->get_pos(), size_t(6)); // buffer after the newline | |
| EXPECT_EQ(f_full->get_pos(), size_t(0)); // buffer all of data | |
| MemoryView src1("hello\n", 6), dst1(f_line->get_str(), 6); | |
| EXPECT_MEM_EQ(src1, dst1); | |
| // We can't check the data in f_full, since no data has been written. | |
| const char data2[] = "longer for an \n overflow"; | |
| ASSERT_EQ(sizeof(data2), f_line->write(data2, sizeof(data2)).value); | |
| // The line buffer's initial contents should be " file\0" | |
| // Writing data2 should write up until the newline, even though that doesn't | |
| // all fit in the buffer. | |
| ASSERT_EQ(sizeof(data2), f_full->write(data2, sizeof(data2)).value); | |
| // The full buffer's initial contents should be "hello\n file\0" | |
| // Writing data2 should cause a flush of the buffer, as well as the remainder | |
| // to be written directly since it doesn't fit in the buffer. | |
| EXPECT_EQ(f_line->get_pos(), size_t(27)); | |
| EXPECT_EQ(f_full->get_pos(), sizeof(data) + sizeof(data2)); | |
| MemoryView src2("hello\n file\0longer for an \n", 27), | |
| dst2(f_line->get_str(), 27); | |
| EXPECT_MEM_EQ(src2, dst2); | |
| MemoryView src3("hello\n file\0longer for an \n overflow", 37), | |
| dst_full_final(f_full->get_str(), 37); | |
| EXPECT_MEM_EQ(src3, dst_full_final); | |
| ASSERT_EQ(f_line->flush(), 0); | |
| ASSERT_EQ(f_full->flush(), 0); | |
| EXPECT_EQ(f_line->get_pos(), sizeof(data) + sizeof(data2)); | |
| MemoryView dst_line_final(f_line->get_str(), 37); | |
| EXPECT_MEM_EQ(src3, dst_line_final); | |
| EXPECT_MEM_EQ(src3, dst_full_final); | |
| ASSERT_EQ(f_line->close(), 0); | |
| ASSERT_EQ(f_full->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WriteUnbuffered) { | |
| const char data[] = "written immediately"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(data) + 1; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IONBF, false, "w"); | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f->get_pos(), | |
| sizeof(data)); // no buffering means this is written immediately. | |
| EXPECT_STREQ(f->get_str(), data); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ReadOnly) { | |
| const char initial_content[] = "1234567890987654321"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(initial_content); | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r"); | |
| f->reset_and_fill(initial_content, sizeof(initial_content)); | |
| constexpr size_t READ_SIZE = sizeof(initial_content) / 2; | |
| char read_data[READ_SIZE]; | |
| ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| EXPECT_FALSE(f->iseof()); | |
| // Reading less than file buffer worth will still read one | |
| // full buffer worth of data. | |
| EXPECT_STREQ(file_buffer, initial_content); | |
| EXPECT_STREQ(file_buffer, f->get_str()); | |
| EXPECT_EQ(FILE_BUFFER_SIZE, f->get_pos()); | |
| // The read data should match what was supposed to be read anyway. | |
| MemoryView src1(initial_content, READ_SIZE), dst1(read_data, READ_SIZE); | |
| EXPECT_MEM_EQ(src1, dst1); | |
| // Reading another buffer worth should read out everything in | |
| // the file. | |
| ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| EXPECT_FALSE(f->iseof()); | |
| MemoryView src2(initial_content + READ_SIZE, READ_SIZE), | |
| dst2(read_data, READ_SIZE); | |
| EXPECT_MEM_EQ(src2, dst2); | |
| // Another read should trigger an EOF. | |
| ASSERT_GT(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| EXPECT_TRUE(f->iseof()); | |
| // Reset the pos to the beginning of the file which should allow | |
| // reading again. | |
| for (size_t i = 0; i < READ_SIZE; ++i) | |
| read_data[i] = 0; | |
| f->seek(0, SEEK_SET); | |
| ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| MemoryView src3(initial_content, READ_SIZE), dst3(read_data, READ_SIZE); | |
| EXPECT_MEM_EQ(src3, dst3); | |
| { | |
| // This is not a writable file. | |
| auto result = f->write(initial_content, sizeof(initial_content)); | |
| EXPECT_EQ(result.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| EXPECT_TRUE(result.has_error()); | |
| } | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ReadSeekCurAndRead) { | |
| const char initial_content[] = "1234567890987654321"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(initial_content); | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r"); | |
| f->reset_and_fill(initial_content, sizeof(initial_content)); | |
| constexpr size_t READ_SIZE = 5; | |
| char data[READ_SIZE]; | |
| data[READ_SIZE - 1] = '\0'; | |
| ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); | |
| ASSERT_STREQ(data, "1234"); | |
| ASSERT_EQ(f->seek(5, SEEK_CUR).value(), 0); | |
| ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); | |
| ASSERT_STREQ(data, "0987"); | |
| ASSERT_EQ(f->seek(-5, SEEK_CUR).value(), 0); | |
| ASSERT_EQ(f->read(data, READ_SIZE - 1).value, READ_SIZE - 1); | |
| ASSERT_STREQ(data, "9098"); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, AppendOnly) { | |
| const char initial_content[] = "1234567890987654321"; | |
| const char write_data[] = "append"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(write_data) * 3 / 2; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "a"); | |
| f->reset_and_fill(initial_content, sizeof(initial_content)); | |
| constexpr size_t READ_SIZE = 5; | |
| char read_data[READ_SIZE]; | |
| { | |
| // This is not a readable file. | |
| auto result = f->read(read_data, READ_SIZE); | |
| EXPECT_EQ(result.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| EXPECT_TRUE(result.has_error()); | |
| } | |
| // Write should succeed but will be buffered in the file stream. | |
| ASSERT_EQ(f->write(write_data, sizeof(write_data)).value, sizeof(write_data)); | |
| EXPECT_EQ(f->get_pos(), size_t(0)); | |
| // Flushing will write to the file. | |
| EXPECT_EQ(f->flush(), int(0)); | |
| EXPECT_EQ(f->get_pos(), sizeof(write_data) + sizeof(initial_content)); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WriteUpdate) { | |
| const char data[] = "hello, file"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| // Seek flushes the stream buffer so we can read the previously written data. | |
| char read_data[sizeof(data)]; | |
| ASSERT_EQ(f->read(read_data, sizeof(data)).value, sizeof(data)); | |
| EXPECT_STREQ(read_data, data); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ReadUpdate) { | |
| const char initial_content[] = "1234567890987654321"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(initial_content); | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r+"); | |
| f->reset_and_fill(initial_content, sizeof(initial_content)); | |
| constexpr size_t READ_SIZE = sizeof(initial_content) / 2; | |
| char read_data[READ_SIZE]; | |
| ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| EXPECT_FALSE(f->iseof()); | |
| // Reading less than file buffer worth will still read one | |
| // full buffer worth of data. | |
| EXPECT_STREQ(file_buffer, initial_content); | |
| EXPECT_STREQ(file_buffer, f->get_str()); | |
| EXPECT_EQ(FILE_BUFFER_SIZE, f->get_pos()); | |
| // The read data should match what was supposed to be read anyway. | |
| MemoryView src1(initial_content, READ_SIZE), dst1(read_data, READ_SIZE); | |
| EXPECT_MEM_EQ(src1, dst1); | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| const char write_data[] = "hello, file"; | |
| ASSERT_EQ(sizeof(write_data), f->write(write_data, sizeof(write_data)).value); | |
| EXPECT_STREQ(file_buffer, write_data); | |
| ASSERT_EQ(f->flush(), 0); | |
| MemoryView dst2(f->get_str(), sizeof(write_data)), | |
| src2(write_data, sizeof(write_data)); | |
| EXPECT_MEM_EQ(src2, dst2); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, AppendUpdate) { | |
| const char initial_content[] = "1234567890987654321"; | |
| const char data[] = "hello, file"; | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "a+"); | |
| f->reset_and_fill(initial_content, sizeof(initial_content)); | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream | |
| ASSERT_EQ(f->flush(), 0); | |
| // The flush should write |data| to the endof the file. | |
| EXPECT_EQ(f->get_pos(), sizeof(data) + sizeof(initial_content)); | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| // Seeking to the beginning of the file should not affect the place | |
| // where write happens. | |
| ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)).value); | |
| ASSERT_EQ(f->flush(), 0); | |
| EXPECT_EQ(f->get_pos(), sizeof(data) * 2 + sizeof(initial_content)); | |
| MemoryView src1(initial_content, sizeof(initial_content)), | |
| dst1(f->get_str(), sizeof(initial_content)); | |
| EXPECT_MEM_EQ(src1, dst1); | |
| MemoryView src2(data, sizeof(data)), | |
| dst2(f->get_str() + sizeof(initial_content), sizeof(data)); | |
| EXPECT_MEM_EQ(src2, dst2); | |
| MemoryView src3(data, sizeof(data)), | |
| dst3(f->get_str() + sizeof(initial_content) + sizeof(data), sizeof(data)); | |
| EXPECT_MEM_EQ(src3, dst3); | |
| // Reads can happen from any point. | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| constexpr size_t READ_SIZE = 10; | |
| char read_data[READ_SIZE]; | |
| ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE).value); | |
| MemoryView src4(initial_content, READ_SIZE), dst4(read_data, READ_SIZE); | |
| EXPECT_MEM_EQ(src4, dst4); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, SmallBuffer) { | |
| const char WRITE_DATA[] = "small buffer"; | |
| constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); | |
| constexpr size_t FILE_BUFFER_SIZE = sizeof(WRITE_DATA) / 2 - 1; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w"); | |
| ASSERT_EQ(WRITE_SIZE, f->write(WRITE_DATA, WRITE_SIZE).value); | |
| // Since data much larger than the buffer is being written, all of it should | |
| // be available in the file without a flush operation. | |
| EXPECT_EQ(f->get_pos(), sizeof(WRITE_DATA)); | |
| ASSERT_STREQ(f->get_str(), WRITE_DATA); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ZeroLengthBuffer) { | |
| const char WRITE_DATA[] = "small buffer"; | |
| constexpr size_t WRITE_SIZE = sizeof(WRITE_DATA); | |
| StringFile *f_fbf = new_string_file(nullptr, 0, _IOFBF, true, "w"); | |
| StringFile *f_lbf = new_string_file(nullptr, 0, _IOLBF, true, "w"); | |
| StringFile *f_nbf = new_string_file(nullptr, 0, _IONBF, true, "w"); | |
| ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| // Since there is no buffer space, all of the written data should | |
| // be available in the file without a flush operation. | |
| EXPECT_EQ(f_fbf->get_pos(), sizeof(WRITE_DATA)); | |
| EXPECT_EQ(f_lbf->get_pos(), sizeof(WRITE_DATA)); | |
| EXPECT_EQ(f_nbf->get_pos(), sizeof(WRITE_DATA)); | |
| ASSERT_STREQ(f_fbf->get_str(), WRITE_DATA); | |
| ASSERT_STREQ(f_lbf->get_str(), WRITE_DATA); | |
| ASSERT_STREQ(f_nbf->get_str(), WRITE_DATA); | |
| ASSERT_EQ(f_fbf->close(), 0); | |
| ASSERT_EQ(f_lbf->close(), 0); | |
| ASSERT_EQ(f_nbf->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WriteNothing) { | |
| const char WRITE_DATA[] = ""; | |
| constexpr size_t WRITE_SIZE = 0; | |
| constexpr size_t FILE_BUFFER_SIZE = 5; | |
| char file_buffer_fbf[FILE_BUFFER_SIZE]; | |
| char file_buffer_lbf[FILE_BUFFER_SIZE]; | |
| char file_buffer_nbf[FILE_BUFFER_SIZE]; | |
| StringFile *f_fbf = | |
| new_string_file(file_buffer_fbf, FILE_BUFFER_SIZE, _IOFBF, false, "w"); | |
| StringFile *f_lbf = | |
| new_string_file(file_buffer_lbf, FILE_BUFFER_SIZE, _IOLBF, false, "w"); | |
| StringFile *f_nbf = | |
| new_string_file(file_buffer_nbf, FILE_BUFFER_SIZE, _IONBF, false, "w"); | |
| ASSERT_EQ(WRITE_SIZE, f_fbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| ASSERT_EQ(WRITE_SIZE, f_lbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| ASSERT_EQ(WRITE_SIZE, f_nbf->write(WRITE_DATA, WRITE_SIZE).value); | |
| ASSERT_FALSE(f_fbf->error_unlocked()); | |
| ASSERT_FALSE(f_lbf->error_unlocked()); | |
| ASSERT_FALSE(f_nbf->error_unlocked()); | |
| ASSERT_EQ(f_fbf->close(), 0); | |
| ASSERT_EQ(f_lbf->close(), 0); | |
| ASSERT_EQ(f_nbf->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WriteSplit) { | |
| constexpr size_t FILE_BUFFER_SIZE = 8; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w"); | |
| static constexpr size_t AVAIL = 12; | |
| f->seek(-AVAIL, SEEK_END); | |
| const char data[] = "hello"; | |
| ASSERT_EQ(sizeof(data) - 1, f->write(data, sizeof(data) - 1).value); | |
| const char data2[] = " extra data"; | |
| static constexpr size_t WR_EXPECTED = AVAIL - (sizeof(data) - 1); | |
| ASSERT_EQ(WR_EXPECTED, f->write(data2, sizeof(data2) - 1).value); | |
| EXPECT_TRUE(f->error()); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WideCharOrientation) { | |
| constexpr size_t FILE_BUFFER_SIZE = 512; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| f->write(L"A", 1); | |
| auto write_res = f->write("B", 1); | |
| EXPECT_EQ(write_res.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ByteCharOrientation) { | |
| constexpr size_t FILE_BUFFER_SIZE = 512; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| f->write("A", 1); | |
| auto write_res = f->write(L"B", 1); | |
| EXPECT_EQ(write_res.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, Ungetwc) { | |
| constexpr size_t FILE_BUFFER_SIZE = 512; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| f->write(L"A", 1); | |
| f->flush(); | |
| f->seek(0, SEEK_SET); | |
| wchar_t ws_out[2]; | |
| auto read_res = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'A')); | |
| auto unget_res = f->ungetwc(L'B'); | |
| ASSERT_TRUE(unget_res.has_value()); | |
| EXPECT_EQ(static_cast<uint32_t>(unget_res.value()), | |
| static_cast<uint32_t>(L'B')); | |
| auto read_res2 = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res2.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'B')); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WideStringIO) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| ASSERT_FALSE(f == nullptr); | |
| const wchar_t *ws = L"Hello, World!"; | |
| size_t len = 13; | |
| auto write_res = f->write(ws, len); | |
| ASSERT_FALSE(write_res.has_error()); | |
| EXPECT_EQ(write_res.value, len); | |
| ASSERT_EQ(f->flush(), 0); // Ensure everything is written to StringFile | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| wchar_t read_buf[20]; | |
| auto read_res = f->read(read_buf, len); | |
| ASSERT_FALSE(read_res.has_error()); | |
| EXPECT_EQ(read_res.value, len); | |
| for (size_t i = 0; i < len; ++i) { | |
| EXPECT_EQ(static_cast<uint32_t>(read_buf[i]), static_cast<uint32_t>(ws[i])); | |
| } | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, TrySetOrientation) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r+"); | |
| ASSERT_FALSE(f == nullptr); | |
| EXPECT_EQ(static_cast<uint32_t>(f->get_orientation()), | |
| static_cast<uint32_t>(File::Orientation::UNORIENTED)); | |
| EXPECT_EQ( | |
| static_cast<uint32_t>(f->try_set_orientation(File::Orientation::WIDE)), | |
| static_cast<uint32_t>(File::Orientation::WIDE)); | |
| EXPECT_EQ(static_cast<uint32_t>(f->get_orientation()), | |
| static_cast<uint32_t>(File::Orientation::WIDE)); | |
| EXPECT_EQ( | |
| static_cast<uint32_t>(f->try_set_orientation(File::Orientation::BYTE)), | |
| static_cast<uint32_t>(File::Orientation::WIDE)); // Cannot change | |
| EXPECT_EQ(static_cast<uint32_t>(f->get_orientation()), | |
| static_cast<uint32_t>(File::Orientation::WIDE)); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, UngetwcMultiByte) { | |
| constexpr size_t FILE_BUFFER_SIZE = 512; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| f->write(L"€", 1); | |
| f->flush(); | |
| f->seek(0, SEEK_SET); | |
| wchar_t ws_out[2]; | |
| auto read_res = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'€')); | |
| auto unget_res = f->ungetwc(L'¢'); | |
| ASSERT_TRUE(unget_res.has_value()); | |
| EXPECT_EQ(static_cast<uint32_t>(unget_res.value()), | |
| static_cast<uint32_t>(L'¢')); | |
| auto read_res2 = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res2.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'¢')); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, UngetwcUnbufferedMultiByte) { | |
| StringFile *f = new_string_file(nullptr, 0, _IONBF, true, "w+"); | |
| ASSERT_FALSE(f == nullptr); | |
| f->write(L"€", 1); | |
| f->seek(0, SEEK_SET); | |
| wchar_t ws_out[2]; | |
| auto read_res = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'€')); | |
| auto unget_res = f->ungetwc(L'¢'); | |
| ASSERT_TRUE(unget_res.has_value()); | |
| EXPECT_EQ(static_cast<uint32_t>(unget_res.value()), | |
| static_cast<uint32_t>(L'¢')); | |
| auto read_res2 = f->read(ws_out, 1); | |
| ASSERT_EQ(read_res2.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'¢')); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, WideStringIO_Multibyte) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| ASSERT_FALSE(f == nullptr); | |
| const wchar_t *ws = L"Hello € World!"; | |
| size_t len = 14; | |
| auto write_res = f->write(ws, len); | |
| ASSERT_FALSE(write_res.has_error()); | |
| EXPECT_EQ(write_res.value, len); | |
| ASSERT_EQ(f->flush(), 0); | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| wchar_t read_buf[20]; | |
| auto read_res = f->read(read_buf, len); | |
| ASSERT_FALSE(read_res.has_error()); | |
| EXPECT_EQ(read_res.value, len); | |
| for (size_t i = 0; i < len; ++i) { | |
| EXPECT_EQ(static_cast<uint32_t>(read_buf[i]), static_cast<uint32_t>(ws[i])); | |
| } | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, SeekResetsMbstate) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r+"); | |
| ASSERT_FALSE(f == nullptr); | |
| f->reset_and_fill("\xE2\x82", 2); | |
| wchar_t ws_out[1]; | |
| auto read_res = f->read(ws_out, 1); | |
| EXPECT_EQ(read_res.value, size_t(0)); | |
| EXPECT_TRUE(f->error()); | |
| f->reset_and_fill("A", 1); | |
| f->seek(0, SEEK_SET); | |
| f->clearerr(); | |
| auto read_res2 = f->read(ws_out, 1); | |
| EXPECT_EQ(read_res2.value, size_t(1)); | |
| EXPECT_EQ(static_cast<uint32_t>(ws_out[0]), static_cast<uint32_t>(L'A')); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, ReadWideNotStopAtNewline) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "w+"); | |
| ASSERT_FALSE(f == nullptr); | |
| const wchar_t *ws = L"Hello\nWorld!"; | |
| size_t len = 12; | |
| auto write_res = f->write(ws, len); | |
| ASSERT_FALSE(write_res.has_error()); | |
| EXPECT_EQ(write_res.value, len); | |
| ASSERT_EQ(f->flush(), 0); | |
| ASSERT_EQ(f->seek(0, SEEK_SET).value(), 0); | |
| wchar_t read_buf[20]; | |
| auto read_res = f->read(read_buf, len); | |
| ASSERT_FALSE(read_res.has_error()); | |
| // Should NOT stop at newline, so should read all 12 characters. | |
| EXPECT_EQ(read_res.value, len); | |
| EXPECT_EQ(static_cast<uint32_t>(read_buf[5]), static_cast<uint32_t>(L'\n')); | |
| EXPECT_EQ(static_cast<uint32_t>(read_buf[11]), static_cast<uint32_t>(L'!')); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, UngetwcWEOF) { | |
| constexpr size_t FILE_BUFFER_SIZE = 100; | |
| char file_buffer[FILE_BUFFER_SIZE]; | |
| StringFile *f = | |
| new_string_file(file_buffer, FILE_BUFFER_SIZE, _IOFBF, false, "r+"); | |
| ASSERT_FALSE(f == nullptr); | |
| EXPECT_EQ(static_cast<uint32_t>(f->get_orientation()), | |
| static_cast<uint32_t>(File::Orientation::UNORIENTED)); | |
| auto unget_res = f->ungetwc(WEOF); | |
| ASSERT_TRUE(unget_res.has_value()); | |
| EXPECT_EQ(static_cast<uint32_t>(unget_res.value()), | |
| static_cast<uint32_t>(WEOF)); | |
| EXPECT_EQ(static_cast<uint32_t>(f->get_orientation()), | |
| static_cast<uint32_t>(File::Orientation::UNORIENTED)); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| // A File subclass with a platform_write that simulates short writes. | |
| // This models the behavior of write(2) on pipes, sockets, or FIFOs where | |
| // the kernel may write fewer bytes than requested. | |
| class ShortWriteFile : public File { | |
| static constexpr size_t SIZE = 512; | |
| size_t pos; | |
| char str[SIZE] = {0}; | |
| size_t max_write; | |
| static FileIOResult short_write(LIBC_NAMESPACE::File *f, const void *data, | |
| size_t len) { | |
| ShortWriteFile *sf = static_cast<ShortWriteFile *>(f); | |
| // Simulate a short write: write at most max_write bytes per call. | |
| size_t to_write = len < sf->max_write ? len : sf->max_write; | |
| for (size_t i = 0; i < to_write && sf->pos < SIZE; ++i, ++sf->pos) | |
| sf->str[sf->pos] = reinterpret_cast<const char *>(data)[i]; | |
| return to_write; | |
| } | |
| static FileIOResult short_read(LIBC_NAMESPACE::File *f, void *data, | |
| size_t len) { | |
| ShortWriteFile *sf = static_cast<ShortWriteFile *>(f); | |
| size_t i = 0; | |
| for (i = 0; i < len && sf->pos < SIZE; ++i) | |
| reinterpret_cast<char *>(data)[i] = sf->str[sf->pos + i]; | |
| sf->pos += i; | |
| return i; | |
| } | |
| static ErrorOr<off_t> short_seek(LIBC_NAMESPACE::File *f, off_t offset, | |
| int whence) { | |
| ShortWriteFile *sf = static_cast<ShortWriteFile *>(f); | |
| if (whence == SEEK_SET) | |
| sf->pos = offset; | |
| if (whence == SEEK_CUR) | |
| sf->pos += offset; | |
| if (whence == SEEK_END) | |
| sf->pos = SIZE + offset; | |
| return sf->pos; | |
| } | |
| static int short_close(LIBC_NAMESPACE::File *f) { | |
| delete reinterpret_cast<ShortWriteFile *>(f); | |
| return 0; | |
| } | |
| public: | |
| explicit ShortWriteFile(char *buffer, size_t buflen, int bufmode, bool owned, | |
| FileMode mode, size_t max_write_bytes) | |
| : LIBC_NAMESPACE::File(&short_write, &short_read, &short_seek, | |
| &short_close, reinterpret_cast<uint8_t *>(buffer), | |
| buflen, bufmode, owned, mode), | |
| pos(0), max_write(max_write_bytes) {} | |
| void reset() { pos = 0; } | |
| size_t get_pos() const { return pos; } | |
| char *get_str() { return str; } | |
| }; | |
| // Verify that a short platform_write of a multi-byte UTF-8 character is | |
| // detected and reported as a failure. POSIX write(2) may perform short | |
| // writes on pipes, sockets, and FIFOs, so a 3-byte character could have | |
| // only 2 bytes accepted by the kernel. | |
| TEST(LlvmLibcFileTest, PartialWideCharWriteDetected) { | |
| LIBC_NAMESPACE::AllocChecker ac; | |
| // Unbuffered so writes go directly to platform_write, limited to 2 bytes. | |
| ShortWriteFile *f = | |
| new (ac) ShortWriteFile(nullptr, 0, _IONBF, true, FileMode("w"), | |
| /*max_write_bytes=*/2); | |
| ASSERT_FALSE(f == nullptr); | |
| // € (U+20AC) encodes to 3 UTF-8 bytes: 0xE2 0x82 0xAC. | |
| // With max_write=2, only 2 of the 3 bytes will be accepted. | |
| const wchar_t euro = L'€'; | |
| auto result = f->write(&euro, 1); | |
| // The incomplete character must not be counted as written. | |
| EXPECT_TRUE(result.has_error()); | |
| EXPECT_EQ(result.value, size_t(0)); | |
| // The error indicator on the stream should be set. | |
| EXPECT_TRUE(f->error()); | |
| ASSERT_EQ(f->close(), 0); | |
| } | |
| TEST(LlvmLibcFileTest, FileLockRAII) { | |
| StringFile *f = new_string_file(nullptr, 0, _IONBF, false, "w+"); | |
| ASSERT_FALSE(f == nullptr); | |
| { | |
| File::FileLock lock(f); | |
| ASSERT_EQ(f->write_unlocked("abc", 3).value, size_t(3)); | |
| } | |
| // After FileLock is destroyed, operations under lock still acquire and | |
| // release cleanly. | |
| ASSERT_EQ(f->write("def", 3).value, size_t(3)); | |
| ASSERT_EQ(f->close(), 0); | |
| } |