// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. // // Testing strategy: For each type of I/O (array, string, file, etc.) we // create an output stream and write some data to it, then create a // corresponding input stream to read the same data back and expect it to // match. When the data is written, it is written in several small chunks // of varying sizes, with a BackUp() after each chunk. It is read back // similarly, but with chunks separated at different points. The whole // process is run with a variety of block sizes for both the input and // the output. // // TODO(kenton): Rewrite this test to bring it up to the standards of all // the other proto2 tests. May want to wait for gTest to implement // "parametized tests" so that one set of tests can be used on all the // implementations. #ifndef _WIN32 #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "google/protobuf/stubs/common.h" #include "google/protobuf/testing/file.h" #include "google/protobuf/testing/file.h" #include "google/protobuf/testing/googletest.h" #include #include "absl/log/absl_check.h" #include "absl/log/absl_log.h" #include "absl/status/status.h" #include "absl/strings/cord.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "google/protobuf/io/coded_stream.h" #include "google/protobuf/io/io_win32.h" #include "google/protobuf/io/zero_copy_stream_impl.h" #include "google/protobuf/test_util2.h" #if HAVE_ZLIB #include "google/protobuf/io/gzip_stream.h" #endif // Must be included last. #include "google/protobuf/port_def.inc" namespace google { namespace protobuf { namespace io { namespace { #ifdef _WIN32 #define pipe(fds) _pipe(fds, 4096, O_BINARY) // DO NOT include , instead create functions in io_win32.{h,cc} and import // them like we do below. using google::protobuf::io::win32::access; using google::protobuf::io::win32::close; using google::protobuf::io::win32::mkdir; using google::protobuf::io::win32::open; #endif #ifndef O_BINARY #ifdef _O_BINARY #define O_BINARY _O_BINARY #else #define O_BINARY 0 // If this isn't defined, the platform doesn't need it. #endif #endif class IoTest : public testing::Test { protected: // Test helpers. // Helper to write an array of data to an output stream. bool WriteToOutput(ZeroCopyOutputStream* output, const void* data, int size); // Helper to read a fixed-length array of data from an input stream. int ReadFromInput(ZeroCopyInputStream* input, void* data, int size); // Write a string to the output stream. void WriteString(ZeroCopyOutputStream* output, const std::string& str); // Read a number of bytes equal to the size of the given string and checks // that it matches the string. void ReadString(ZeroCopyInputStream* input, const std::string& str); // Writes some text to the output stream in a particular order. Returns // the number of bytes written, in case the caller needs that to set up an // input stream. int WriteStuff(ZeroCopyOutputStream* output); // Reads text from an input stream and expects it to match what // WriteStuff() writes. void ReadStuff(ZeroCopyInputStream* input, bool read_eof = true); // Similar to WriteStuff, but performs more sophisticated testing. int WriteStuffLarge(ZeroCopyOutputStream* output); // Reads and tests a stream that should have been written to // via WriteStuffLarge(). void ReadStuffLarge(ZeroCopyInputStream* input); #if HAVE_ZLIB std::string Compress(const std::string& data, const GzipOutputStream::Options& options); std::string Uncompress(const std::string& data); #endif static const int kBlockSizes[]; static const int kBlockSizeCount; }; const int IoTest::kBlockSizes[] = {-1, 1, 2, 5, 7, 10, 23, 64}; const int IoTest::kBlockSizeCount = ABSL_ARRAYSIZE(IoTest::kBlockSizes); bool IoTest::WriteToOutput(ZeroCopyOutputStream* output, const void* data, int size) { const uint8_t* in = reinterpret_cast(data); int in_size = size; void* out; int out_size; while (true) { if (!output->Next(&out, &out_size)) { return false; } EXPECT_GT(out_size, 0); if (in_size <= out_size) { memcpy(out, in, in_size); output->BackUp(out_size - in_size); return true; } memcpy(out, in, out_size); in += out_size; in_size -= out_size; } } #define MAX_REPEATED_ZEROS 100 int IoTest::ReadFromInput(ZeroCopyInputStream* input, void* data, int size) { uint8_t* out = reinterpret_cast(data); int out_size = size; const void* in; int in_size = 0; int repeated_zeros = 0; while (true) { if (!input->Next(&in, &in_size)) { return size - out_size; } EXPECT_GT(in_size, -1); if (in_size == 0) { repeated_zeros++; } else { repeated_zeros = 0; } EXPECT_LT(repeated_zeros, MAX_REPEATED_ZEROS); if (out_size <= in_size) { memcpy(out, in, out_size); if (in_size > out_size) { input->BackUp(in_size - out_size); } return size; // Copied all of it. } memcpy(out, in, in_size); out += in_size; out_size -= in_size; } } void IoTest::WriteString(ZeroCopyOutputStream* output, const std::string& str) { EXPECT_TRUE(WriteToOutput(output, str.c_str(), str.size())); } void IoTest::ReadString(ZeroCopyInputStream* input, const std::string& str) { std::unique_ptr buffer(new char[str.size() + 1]); buffer[str.size()] = '\0'; EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size()); EXPECT_STREQ(str.c_str(), buffer.get()); } int IoTest::WriteStuff(ZeroCopyOutputStream* output) { WriteString(output, "Hello world!\n"); WriteString(output, "Some te"); WriteString(output, "xt. Blah blah."); WriteString(output, "abcdefg"); WriteString(output, "01234567890123456789"); WriteString(output, "foobar"); EXPECT_EQ(output->ByteCount(), 68); int result = output->ByteCount(); return result; } // Reads text from an input stream and expects it to match what WriteStuff() // writes. void IoTest::ReadStuff(ZeroCopyInputStream* input, bool read_eof) { ReadString(input, "Hello world!\n"); ReadString(input, "Some text. "); ReadString(input, "Blah "); ReadString(input, "blah."); ReadString(input, "abcdefg"); EXPECT_TRUE(input->Skip(20)); ReadString(input, "foo"); ReadString(input, "bar"); EXPECT_EQ(input->ByteCount(), 68); if (read_eof) { uint8_t byte; EXPECT_EQ(ReadFromInput(input, &byte, 1), 0); } } int IoTest::WriteStuffLarge(ZeroCopyOutputStream* output) { WriteString(output, "Hello world!\n"); WriteString(output, "Some te"); WriteString(output, "xt. Blah blah."); WriteString(output, std::string(100000, 'x')); // A very long string WriteString(output, std::string(100000, 'y')); // A very long string WriteString(output, "01234567890123456789"); EXPECT_EQ(output->ByteCount(), 200055); int result = output->ByteCount(); return result; } // Reads text from an input stream and expects it to match what WriteStuff() // writes. void IoTest::ReadStuffLarge(ZeroCopyInputStream* input) { ReadString(input, "Hello world!\nSome text. "); EXPECT_TRUE(input->Skip(5)); ReadString(input, "blah."); EXPECT_TRUE(input->Skip(100000 - 10)); ReadString(input, std::string(10, 'x') + std::string(100000 - 20000, 'y')); EXPECT_TRUE(input->Skip(20000 - 10)); ReadString(input, "yyyyyyyyyy01234567890123456789"); EXPECT_EQ(input->ByteCount(), 200055); uint8_t byte; EXPECT_EQ(ReadFromInput(input, &byte, 1), 0); } // =================================================================== TEST_F(IoTest, ArrayIo) { const int kBufferSize = 256; uint8_t buffer[kBufferSize]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { int size; { ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]); size = WriteStuff(&output); } { ArrayInputStream input(buffer, size, kBlockSizes[j]); ReadStuff(&input); } } } } TEST_F(IoTest, TwoSessionWrite) { // Test that two concatenated write sessions read correctly static const char* strA = "0123456789"; static const char* strB = "WhirledPeas"; const int kBufferSize = 2 * 1024; uint8_t* buffer = new uint8_t[kBufferSize]; char* temp_buffer = new char[40]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { ArrayOutputStream* output = new ArrayOutputStream(buffer, kBufferSize, kBlockSizes[i]); CodedOutputStream* coded_output = new CodedOutputStream(output); coded_output->WriteVarint32(strlen(strA)); coded_output->WriteRaw(strA, strlen(strA)); delete coded_output; // flush int64_t pos = output->ByteCount(); delete output; output = new ArrayOutputStream(buffer + pos, kBufferSize - pos, kBlockSizes[i]); coded_output = new CodedOutputStream(output); coded_output->WriteVarint32(strlen(strB)); coded_output->WriteRaw(strB, strlen(strB)); delete coded_output; // flush int64_t size = pos + output->ByteCount(); delete output; ArrayInputStream* input = new ArrayInputStream(buffer, size, kBlockSizes[j]); CodedInputStream* coded_input = new CodedInputStream(input); uint32_t insize; EXPECT_TRUE(coded_input->ReadVarint32(&insize)); EXPECT_EQ(strlen(strA), insize); EXPECT_TRUE(coded_input->ReadRaw(temp_buffer, insize)); EXPECT_EQ(0, memcmp(temp_buffer, strA, insize)); EXPECT_TRUE(coded_input->ReadVarint32(&insize)); EXPECT_EQ(strlen(strB), insize); EXPECT_TRUE(coded_input->ReadRaw(temp_buffer, insize)); EXPECT_EQ(0, memcmp(temp_buffer, strB, insize)); delete coded_input; delete input; } } delete[] temp_buffer; delete[] buffer; } #if HAVE_ZLIB TEST_F(IoTest, GzipIo) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { for (int z = 0; z < kBlockSizeCount; z++) { int gzip_buffer_size = kBlockSizes[z]; int size; { ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]); GzipOutputStream::Options options; options.format = GzipOutputStream::GZIP; if (gzip_buffer_size != -1) { options.buffer_size = gzip_buffer_size; } GzipOutputStream gzout(&output, options); WriteStuff(&gzout); gzout.Close(); size = output.ByteCount(); } { ArrayInputStream input(buffer, size, kBlockSizes[j]); GzipInputStream gzin(&input, GzipInputStream::GZIP, gzip_buffer_size); ReadStuff(&gzin); } } } } delete[] buffer; } TEST_F(IoTest, GzipIoWithFlush) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; // We start with i = 4 as we want a block size > 6. With block size <= 6 // Flush() fills up the entire 2K buffer with flush markers and the test // fails. See documentation for Flush() for more detail. for (int i = 4; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { for (int z = 0; z < kBlockSizeCount; z++) { int gzip_buffer_size = kBlockSizes[z]; int size; { ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]); GzipOutputStream::Options options; options.format = GzipOutputStream::GZIP; if (gzip_buffer_size != -1) { options.buffer_size = gzip_buffer_size; } GzipOutputStream gzout(&output, options); WriteStuff(&gzout); EXPECT_TRUE(gzout.Flush()); gzout.Close(); size = output.ByteCount(); } { ArrayInputStream input(buffer, size, kBlockSizes[j]); GzipInputStream gzin(&input, GzipInputStream::GZIP, gzip_buffer_size); ReadStuff(&gzin); } } } } delete[] buffer; } TEST_F(IoTest, GzipIoContiguousFlushes) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; int block_size = kBlockSizes[4]; int gzip_buffer_size = block_size; int size; ArrayOutputStream output(buffer, kBufferSize, block_size); GzipOutputStream::Options options; options.format = GzipOutputStream::GZIP; if (gzip_buffer_size != -1) { options.buffer_size = gzip_buffer_size; } GzipOutputStream gzout(&output, options); WriteStuff(&gzout); EXPECT_TRUE(gzout.Flush()); EXPECT_TRUE(gzout.Flush()); gzout.Close(); size = output.ByteCount(); ArrayInputStream input(buffer, size, block_size); GzipInputStream gzin(&input, GzipInputStream::GZIP, gzip_buffer_size); ReadStuff(&gzin); delete[] buffer; } TEST_F(IoTest, GzipIoReadAfterFlush) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; int block_size = kBlockSizes[4]; int gzip_buffer_size = block_size; int size; ArrayOutputStream output(buffer, kBufferSize, block_size); GzipOutputStream::Options options; options.format = GzipOutputStream::GZIP; if (gzip_buffer_size != -1) { options.buffer_size = gzip_buffer_size; } GzipOutputStream gzout(&output, options); WriteStuff(&gzout); EXPECT_TRUE(gzout.Flush()); size = output.ByteCount(); ArrayInputStream input(buffer, size, block_size); GzipInputStream gzin(&input, GzipInputStream::GZIP, gzip_buffer_size); ReadStuff(&gzin); gzout.Close(); delete[] buffer; } TEST_F(IoTest, ZlibIo) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { for (int z = 0; z < kBlockSizeCount; z++) { int gzip_buffer_size = kBlockSizes[z]; int size; { ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]); GzipOutputStream::Options options; options.format = GzipOutputStream::ZLIB; if (gzip_buffer_size != -1) { options.buffer_size = gzip_buffer_size; } GzipOutputStream gzout(&output, options); WriteStuff(&gzout); gzout.Close(); size = output.ByteCount(); } { ArrayInputStream input(buffer, size, kBlockSizes[j]); GzipInputStream gzin(&input, GzipInputStream::ZLIB, gzip_buffer_size); ReadStuff(&gzin); } } } } delete[] buffer; } TEST_F(IoTest, ZlibIoInputAutodetect) { const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; int size; { ArrayOutputStream output(buffer, kBufferSize); GzipOutputStream::Options options; options.format = GzipOutputStream::ZLIB; GzipOutputStream gzout(&output, options); WriteStuff(&gzout); gzout.Close(); size = output.ByteCount(); } { ArrayInputStream input(buffer, size); GzipInputStream gzin(&input, GzipInputStream::AUTO); ReadStuff(&gzin); } { ArrayOutputStream output(buffer, kBufferSize); GzipOutputStream::Options options; options.format = GzipOutputStream::GZIP; GzipOutputStream gzout(&output, options); WriteStuff(&gzout); gzout.Close(); size = output.ByteCount(); } { ArrayInputStream input(buffer, size); GzipInputStream gzin(&input, GzipInputStream::AUTO); ReadStuff(&gzin); } delete[] buffer; } std::string IoTest::Compress(const std::string& data, const GzipOutputStream::Options& options) { std::string result; { StringOutputStream output(&result); GzipOutputStream gzout(&output, options); WriteToOutput(&gzout, data.data(), data.size()); } return result; } std::string IoTest::Uncompress(const std::string& data) { std::string result; { ArrayInputStream input(data.data(), data.size()); GzipInputStream gzin(&input); const void* buffer; int size; while (gzin.Next(&buffer, &size)) { result.append(reinterpret_cast(buffer), size); } } return result; } TEST_F(IoTest, CompressionOptions) { // Some ad-hoc testing of compression options. std::string golden_filename = TestUtil::GetTestDataPath("google/protobuf/testdata/golden_message"); std::string golden; ABSL_CHECK_OK(File::GetContents(golden_filename, &golden, true)); GzipOutputStream::Options options; std::string gzip_compressed = Compress(golden, options); options.compression_level = 0; std::string not_compressed = Compress(golden, options); // Try zlib compression for fun. options = GzipOutputStream::Options(); options.format = GzipOutputStream::ZLIB; std::string zlib_compressed = Compress(golden, options); // Uncompressed should be bigger than the original since it should have some // sort of header. EXPECT_GT(not_compressed.size(), golden.size()); // Higher compression levels should result in smaller sizes. EXPECT_LT(zlib_compressed.size(), not_compressed.size()); // ZLIB format should differ from GZIP format. EXPECT_TRUE(zlib_compressed != gzip_compressed); // Everything should decompress correctly. EXPECT_TRUE(Uncompress(not_compressed) == golden); EXPECT_TRUE(Uncompress(gzip_compressed) == golden); EXPECT_TRUE(Uncompress(zlib_compressed) == golden); } TEST_F(IoTest, TwoSessionWriteGzip) { // Test that two concatenated gzip streams can be read correctly static const char* strA = "0123456789"; static const char* strB = "QuickBrownFox"; const int kBufferSize = 2 * 1024; uint8* buffer = new uint8[kBufferSize]; char* temp_buffer = new char[40]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { ArrayOutputStream* output = new ArrayOutputStream(buffer, kBufferSize, kBlockSizes[i]); GzipOutputStream* gzout = new GzipOutputStream(output); CodedOutputStream* coded_output = new CodedOutputStream(gzout); int32 outlen = strlen(strA) + 1; coded_output->WriteVarint32(outlen); coded_output->WriteRaw(strA, outlen); delete coded_output; // flush delete gzout; // flush int64 pos = output->ByteCount(); delete output; output = new ArrayOutputStream(buffer + pos, kBufferSize - pos, kBlockSizes[i]); gzout = new GzipOutputStream(output); coded_output = new CodedOutputStream(gzout); outlen = strlen(strB) + 1; coded_output->WriteVarint32(outlen); coded_output->WriteRaw(strB, outlen); delete coded_output; // flush delete gzout; // flush int64 size = pos + output->ByteCount(); delete output; ArrayInputStream* input = new ArrayInputStream(buffer, size, kBlockSizes[j]); GzipInputStream* gzin = new GzipInputStream(input); CodedInputStream* coded_input = new CodedInputStream(gzin); uint32 insize; EXPECT_TRUE(coded_input->ReadVarint32(&insize)); EXPECT_EQ(strlen(strA) + 1, insize); EXPECT_TRUE(coded_input->ReadRaw(temp_buffer, insize)); EXPECT_EQ(0, memcmp(temp_buffer, strA, insize)) << "strA=" << strA << " in=" << temp_buffer; EXPECT_TRUE(coded_input->ReadVarint32(&insize)); EXPECT_EQ(strlen(strB) + 1, insize); EXPECT_TRUE(coded_input->ReadRaw(temp_buffer, insize)); EXPECT_EQ(0, memcmp(temp_buffer, strB, insize)) << " out_block_size=" << kBlockSizes[i] << " in_block_size=" << kBlockSizes[j] << " pos=" << pos << " size=" << size << " strB=" << strB << " in=" << temp_buffer; delete coded_input; delete gzin; delete input; } } delete[] temp_buffer; delete[] buffer; } TEST_F(IoTest, GzipInputByteCountAfterClosed) { std::string golden = "abcdefghijklmnopqrstuvwxyz"; std::string compressed = Compress(golden, GzipOutputStream::Options()); for (int i = 0; i < kBlockSizeCount; i++) { ArrayInputStream arr_input(compressed.data(), compressed.size(), kBlockSizes[i]); GzipInputStream gz_input(&arr_input); const void* buffer; int size; while (gz_input.Next(&buffer, &size)) { EXPECT_LE(gz_input.ByteCount(), golden.size()); } EXPECT_EQ(golden.size(), gz_input.ByteCount()); } } TEST_F(IoTest, GzipInputByteCountAfterClosedConcatenatedStreams) { std::string golden1 = "abcdefghijklmnopqrstuvwxyz"; std::string golden2 = "the quick brown fox jumps over the lazy dog"; const size_t total_size = golden1.size() + golden2.size(); std::string compressed = Compress(golden1, GzipOutputStream::Options()) + Compress(golden2, GzipOutputStream::Options()); for (int i = 0; i < kBlockSizeCount; i++) { ArrayInputStream arr_input(compressed.data(), compressed.size(), kBlockSizes[i]); GzipInputStream gz_input(&arr_input); const void* buffer; int size; while (gz_input.Next(&buffer, &size)) { EXPECT_LE(gz_input.ByteCount(), total_size); } EXPECT_EQ(total_size, gz_input.ByteCount()); } } #endif // There is no string input, only string output. Also, it doesn't support // explicit block sizes. So, we'll only run one test and we'll use // ArrayInput to read back the results. TEST_F(IoTest, StringIo) { std::string str; { StringOutputStream output(&str); WriteStuff(&output); } { ArrayInputStream input(str.data(), str.size()); ReadStuff(&input); } } TEST(DefaultReadCordTest, ReadSmallCord) { std::string source = "abcdefghijk"; ArrayInputStream input(source.data(), source.size()); absl::Cord dest; EXPECT_TRUE(input.Skip(1)); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); EXPECT_EQ(dest, "bcdefghij"); } TEST(DefaultReadCordTest, ReadSmallCordAfterBackUp) { std::string source = "abcdefghijk"; ArrayInputStream input(source.data(), source.size()); absl::Cord dest; const void* buffer; int size; EXPECT_TRUE(input.Next(&buffer, &size)); input.BackUp(size - 1); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); EXPECT_EQ(dest, "bcdefghij"); } TEST(DefaultReadCordTest, ReadLargeCord) { std::string source = "abcdefghijk"; for (int i = 0; i < 1024; i++) { source.append("abcdefghijk"); } absl::Cord dest; ArrayInputStream input(source.data(), source.size()); EXPECT_TRUE(input.Skip(1)); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); absl::Cord expected(source); expected.RemovePrefix(1); expected.RemoveSuffix(1); EXPECT_EQ(expected, dest); } TEST(DefaultReadCordTest, ReadLargeCordAfterBackup) { std::string source = "abcdefghijk"; for (int i = 0; i < 1024; i++) { source.append("abcdefghijk"); } absl::Cord dest; ArrayInputStream input(source.data(), source.size()); const void* buffer; int size; EXPECT_TRUE(input.Next(&buffer, &size)); input.BackUp(size - 1); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); absl::Cord expected(source); expected.RemovePrefix(1); expected.RemoveSuffix(1); EXPECT_EQ(expected, dest); EXPECT_TRUE(input.Next(&buffer, &size)); EXPECT_EQ("k", std::string(reinterpret_cast(buffer), size)); } TEST(DefaultReadCordTest, ReadCordEof) { std::string source = "abcdefghijk"; absl::Cord dest; ArrayInputStream input(source.data(), source.size()); input.Skip(1); EXPECT_FALSE(input.ReadCord(&dest, source.size())); absl::Cord expected(source); expected.RemovePrefix(1); EXPECT_EQ(expected, dest); } TEST(DefaultWriteCordTest, WriteEmptyCordToArray) { absl::Cord source; std::string buffer = "abc"; ArrayOutputStream output(&buffer[0], static_cast(buffer.length())); EXPECT_TRUE(output.WriteCord(source)); EXPECT_EQ(output.ByteCount(), source.size()); EXPECT_EQ(buffer, "abc"); } TEST(DefaultWriteCordTest, WriteSmallCord) { absl::Cord source("foo bar"); std::string buffer(source.size(), 'z'); ArrayOutputStream output(&buffer[0], static_cast(buffer.length())); EXPECT_TRUE(output.WriteCord(source)); EXPECT_EQ(output.ByteCount(), source.size()); EXPECT_EQ(buffer, source); } TEST(DefaultWriteCordTest, WriteLargeCord) { absl::Cord source; for (int i = 0; i < 1024; i++) { source.Append("foo bar"); } // Verify that we created a fragmented cord. ASSERT_GT(std::distance(source.chunk_begin(), source.chunk_end()), 1); std::string buffer(source.size(), 'z'); ArrayOutputStream output(&buffer[0], static_cast(buffer.length())); EXPECT_TRUE(output.WriteCord(source)); EXPECT_EQ(output.ByteCount(), source.size()); EXPECT_EQ(buffer, source); } TEST(DefaultWriteCordTest, WriteTooLargeCord) { absl::Cord source; for (int i = 0; i < 1024; i++) { source.Append("foo bar"); } std::string buffer(source.size() - 1, 'z'); ArrayOutputStream output(&buffer[0], static_cast(buffer.length())); EXPECT_FALSE(output.WriteCord(source)); EXPECT_EQ(output.ByteCount(), buffer.size()); EXPECT_EQ(buffer, source.Subcord(0, output.ByteCount())); } TEST(CordInputStreamTest, SkipToEnd) { absl::Cord source(std::string(10000, 'z')); CordInputStream stream(&source); EXPECT_TRUE(stream.Skip(10000)); EXPECT_EQ(stream.ByteCount(), 10000); } TEST_F(IoTest, CordIo) { CordOutputStream output; int size = WriteStuff(&output); absl::Cord cord = output.Consume(); EXPECT_EQ(size, cord.size()); { CordInputStream input(&cord); ReadStuff(&input); } } template absl::Cord MakeFragmentedCord(const Container& c) { absl::Cord result; for (const auto& s : c) { absl::string_view sv(s); auto buffer = absl::CordBuffer::CreateWithDefaultLimit(sv.size()); absl::Span out = buffer.available_up_to(sv.size()); memcpy(out.data(), sv.data(), out.size()); buffer.SetLength(out.size()); result.Append(std::move(buffer)); } return result; } // Test that we can read correctly from a fragmented Cord. TEST_F(IoTest, FragmentedCordInput) { std::string str; { StringOutputStream output(&str); WriteStuff(&output); } for (int i = 0; i < kBlockSizeCount; i++) { int block_size = kBlockSizes[i]; if (block_size < 0) { // Skip the -1 case. continue; } absl::string_view str_piece = str; // Create a fragmented cord by splitting the input into many cord // functions. std::vector fragments; while (!str_piece.empty()) { size_t n = std::min(str_piece.size(), block_size); fragments.push_back(str_piece.substr(0, n)); str_piece.remove_prefix(n); } absl::Cord fragmented_cord = MakeFragmentedCord(fragments); CordInputStream input(&fragmented_cord); ReadStuff(&input); } } TEST_F(IoTest, ReadSmallCord) { absl::Cord source; source.Append("foo bar"); absl::Cord dest; CordInputStream input(&source); EXPECT_TRUE(input.Skip(1)); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); EXPECT_EQ(absl::Cord("oo ba"), dest); } TEST_F(IoTest, ReadSmallCordAfterBackUp) { absl::Cord source; source.Append("foo bar"); absl::Cord dest; CordInputStream input(&source); const void* buffer; int size; EXPECT_TRUE(input.Next(&buffer, &size)); input.BackUp(size - 1); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); EXPECT_EQ(absl::Cord("oo ba"), dest); } TEST_F(IoTest, ReadLargeCord) { absl::Cord source; for (int i = 0; i < 1024; i++) { source.Append("foo bar"); } absl::Cord dest; CordInputStream input(&source); EXPECT_TRUE(input.Skip(1)); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); absl::Cord expected = source; expected.RemovePrefix(1); expected.RemoveSuffix(1); EXPECT_EQ(expected, dest); } TEST_F(IoTest, ReadLargeCordAfterBackUp) { absl::Cord source; for (int i = 0; i < 1024; i++) { source.Append("foo bar"); } absl::Cord dest; CordInputStream input(&source); const void* buffer; int size; EXPECT_TRUE(input.Next(&buffer, &size)); input.BackUp(size - 1); EXPECT_TRUE(input.ReadCord(&dest, source.size() - 2)); absl::Cord expected = source; expected.RemovePrefix(1); expected.RemoveSuffix(1); EXPECT_EQ(expected, dest); EXPECT_TRUE(input.Next(&buffer, &size)); EXPECT_EQ("r", std::string(reinterpret_cast(buffer), size)); } TEST_F(IoTest, ReadCordEof) { absl::Cord source; source.Append("foo bar"); absl::Cord dest; CordInputStream input(&source); input.Skip(1); EXPECT_FALSE(input.ReadCord(&dest, source.size())); absl::Cord expected = source; expected.RemovePrefix(1); EXPECT_EQ(expected, dest); } TEST(CordOutputStreamTest, Empty) { CordOutputStream output; EXPECT_TRUE(output.Consume().empty()); } TEST(CordOutputStreamTest, ConsumesCordClearingState) { CordOutputStream output(absl::Cord("abcdef")); EXPECT_EQ(output.Consume(), "abcdef"); EXPECT_TRUE(output.Consume().empty()); } TEST(CordOutputStreamTest, DonateEmptyCordBuffer) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); void* available_data = available.data(); CordOutputStream output(std::move(buffer)); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); EXPECT_EQ(data, available_data); EXPECT_EQ(size, static_cast(available.size())); memset(data, 'a', static_cast(size)); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, std::string(static_cast(size), 'a')); EXPECT_EQ(flat.data(), available_data); } TEST(CordOutputStreamTest, DonatePartialCordBuffer) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'a', 100); buffer.IncreaseLengthBy(100); void* available_data = available.data(); CordOutputStream output(std::move(buffer)); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, std::string(100, 'a')); EXPECT_EQ(flat.data(), available_data); } TEST(CordOutputStreamTest, DonatePartialCordBufferAndUseExtraCapacity) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'a', 100); buffer.IncreaseLengthBy(100); void* available_data = available.data(); void* next_available_data = available.data() + 100; CordOutputStream output(std::move(buffer)); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); EXPECT_EQ(data, next_available_data); EXPECT_EQ(size, static_cast(available.size() - 100)); memset(data, 'b', static_cast(size)); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, std::string(100, 'a') + std::string(static_cast(size), 'b')); EXPECT_EQ(flat.data(), available_data); } TEST(CordOutputStreamTest, DonateCordAndPartialCordBufferAndUseExtraCapacity) { absl::Cord cord(std::string(400, 'a')); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'b', 100); buffer.IncreaseLengthBy(100); void* next_available_data = available.data() + 100; CordOutputStream output(std::move(cord), std::move(buffer)); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); EXPECT_EQ(data, next_available_data); EXPECT_EQ(size, static_cast(available.size() - 100)); memset(data, 'c', static_cast(size)); cord = output.Consume(); EXPECT_FALSE(cord.TryFlat()); EXPECT_EQ(cord, std::string(400, 'a') + std::string(100, 'b') + std::string(static_cast(size), 'c')); } TEST(CordOutputStreamTest, DonateFullCordBuffer) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'a', available.size()); buffer.IncreaseLengthBy(available.size()); CordOutputStream output(std::move(buffer)); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); memset(data, 'b', static_cast(size)); absl::Cord cord = output.Consume(); EXPECT_FALSE(cord.TryFlat()); EXPECT_EQ(cord, std::string(available.size(), 'a') + std::string(static_cast(size), 'b')); } TEST(CordOutputStreamTest, DonateFullCordBufferAndCord) { absl::Cord cord(std::string(400, 'a')); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'b', available.size()); buffer.IncreaseLengthBy(available.size()); CordOutputStream output(std::move(cord), std::move(buffer)); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); memset(data, 'c', static_cast(size)); cord = output.Consume(); EXPECT_FALSE(cord.TryFlat()); EXPECT_EQ(cord, std::string(400, 'a') + std::string(available.size(), 'b') + std::string(static_cast(size), 'c')); } TEST(CordOutputStreamTest, DonateFullCordBufferAndBackup) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'a', available.size()); buffer.IncreaseLengthBy(available.size()); // We back up by 100 before calling Next() void* available_data = available.data(); void* next_available_data = available.data() + available.size() - 100; CordOutputStream output(std::move(buffer)); output.BackUp(100); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); EXPECT_EQ(data, next_available_data); EXPECT_EQ(size, 100); memset(data, 'b', 100); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, std::string(available.size() - 100, 'a') + std::string(100, 'b')); EXPECT_EQ(flat.data(), available_data); } TEST(CordOutputStreamTest, DonateCordAndFullCordBufferAndBackup) { absl::Cord cord(std::string(400, 'a')); absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); absl::Span available = buffer.available(); memset(available.data(), 'b', available.size()); buffer.IncreaseLengthBy(available.size()); // We back up by 100 before calling Next() void* next_available_data = available.data() + available.size() - 100; CordOutputStream output(std::move(cord), std::move(buffer)); output.BackUp(100); void* data; int size; EXPECT_TRUE(output.Next(&data, &size)); EXPECT_EQ(data, next_available_data); EXPECT_EQ(size, 100); memset(data, 'c', 100); cord = output.Consume(); EXPECT_FALSE(cord.TryFlat()); EXPECT_EQ(cord, std::string(400, 'a') + std::string(available.size() - 100, 'b') + std::string(100, 'c')); } TEST(CordOutputStreamTest, ProperHintCreatesSingleFlatCord) { CordOutputStream output(2000); void* data; int size; ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 2000); memset(data, 'a', 2000); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, std::string(2000, 'a')); } TEST(CordOutputStreamTest, SizeHintDicatesTotalSize) { absl::Cord cord(std::string(500, 'a')); CordOutputStream output(std::move(cord), 2000); void* data; int size; int remaining = 1500; while (remaining > 0) { ASSERT_TRUE(output.Next(&data, &size)); ASSERT_LE(size, remaining); memset(data, 'b', static_cast(size)); remaining -= size; } ASSERT_EQ(remaining, 0); cord = output.Consume(); EXPECT_EQ(cord, absl::StrCat(std::string(500, 'a'), std::string(1500, 'b'))); } TEST(CordOutputStreamTest, BackUpReusesPartialBuffer) { CordOutputStream output(2000); void* data; int size; ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 2000); memset(data, '1', 100); output.BackUp(1900); ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 1900); memset(data, '2', 200); output.BackUp(1700); ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 1700); memset(data, '3', 400); output.BackUp(1300); ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 1300); memset(data, '4', 1300); absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, absl::StrCat(std::string(100, '1'), std::string(200, '2'), std::string(400, '3'), std::string(1300, '4'))); } TEST(CordOutputStreamTest, UsesPrivateCapacityInDonatedCord) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(2000); memset(buffer.data(), 'a', 500); buffer.SetLength(500); cord.Append(std::move(buffer)); CordOutputStream output(std::move(cord), 2000); void* data; int size; ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 1500); memset(data, 'b', 1500); cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, absl::StrCat(std::string(500, 'a'), std::string(1500, 'b'))); } TEST(CordOutputStreamTest, UsesPrivateCapacityInAppendedCord) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(2000); memset(buffer.data(), 'a', 500); buffer.SetLength(500); cord.Append(std::move(buffer)); CordOutputStream output(2000); void* data; int size; // Add cord. Clearing it makes it privately owned by 'output' as it's non // trivial size guarantees it is ref counted, not deep copied. output.WriteCord(cord); cord.Clear(); ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, 1500); memset(data, 'b', 1500); cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, absl::StrCat(std::string(500, 'a'), std::string(1500, 'b'))); } TEST(CordOutputStreamTest, CapsSizeAtHintButUsesCapacityBeyondHint) { // This tests verifies that when we provide a hint of 'x' bytes, that the // returned size from Next() will be capped at 'size_hint', but that if we // exceed size_hint, it will use the capacity in any internal buffer beyond // the size hint. We test this by providing a hint that is too large to be // inlined, but so small that we have a guarantee it's smaller than the // minimum flat size so we will have a 'capped' larger buffer as state. size_t size_hint = sizeof(absl::Cord) + 1; CordOutputStream output(size_hint); void* data; int size; ASSERT_TRUE(output.Next(&data, &size)); ASSERT_EQ(size, size_hint); memset(data, 'a', static_cast(size)); ASSERT_TRUE(output.Next(&data, &size)); memset(data, 'b', static_cast(size)); // We should have received the same buffer on each Next() call absl::Cord cord = output.Consume(); ASSERT_TRUE(cord.TryFlat()); absl::string_view flat = *cord.TryFlat(); EXPECT_EQ(flat, absl::StrCat(std::string(size_hint, 'a'), std::string(static_cast(size), 'b'))); } TEST(CordOutputStreamTest, SizeDoublesWithoutHint) { CordOutputStream output; void* data; int size; // Whitebox: we are guaranteed at least 128 bytes initially. We also assume // that the maximum size is roughly 4KiB - overhead without being precise. int min_size = 128; const int max_size = 4000; ASSERT_TRUE(output.Next(&data, &size)); memset(data, 0, static_cast(size)); ASSERT_GE(size, min_size); for (int i = 0; i < 6; ++i) { ASSERT_TRUE(output.Next(&data, &size)); memset(data, 0, static_cast(size)); ASSERT_GE(size, min_size); min_size = (std::min)(min_size * 2, max_size); } } TEST_F(IoTest, WriteSmallCord) { absl::Cord source; source.Append("foo bar"); CordOutputStream output(absl::Cord("existing:")); EXPECT_TRUE(output.WriteCord(source)); absl::Cord cord = output.Consume(); EXPECT_EQ(absl::Cord("existing:foo bar"), cord); } TEST_F(IoTest, WriteLargeCord) { absl::Cord source; for (int i = 0; i < 1024; i++) { source.Append("foo bar"); } CordOutputStream output(absl::Cord("existing:")); EXPECT_TRUE(output.WriteCord(source)); absl::Cord cord = output.Consume(); absl::Cord expected = source; expected.Prepend("existing:"); EXPECT_EQ(expected, cord); } // Test that large size hints lead to large block sizes. TEST_F(IoTest, CordOutputSizeHint) { CordOutputStream output1; CordOutputStream output2(12345); void* data1; void* data2; int size1, size2; ASSERT_TRUE(output1.Next(&data1, &size1)); ASSERT_TRUE(output2.Next(&data2, &size2)); // Prevent 'unflushed output' debug checks and warnings output1.BackUp(size1); output2.BackUp(size2); EXPECT_GT(size2, size1); // Prevent any warnings on unused or unflushed data output1.Consume(); output2.Consume(); } // Test that when we use a size hint, we get a buffer boundary exactly on that // byte. TEST_F(IoTest, CordOutputBufferEndsAtSizeHint) { static const int kSizeHint = 12345; CordOutputStream output(kSizeHint); void* data; int size; int total_read = 0; while (total_read < kSizeHint) { ASSERT_TRUE(output.Next(&data, &size)); memset(data, 0, static_cast(size)); // Avoid uninitialized data UB total_read += size; } EXPECT_EQ(kSizeHint, total_read); // We should be able to keep going past the size hint. ASSERT_TRUE(output.Next(&data, &size)); EXPECT_GT(size, 0); // Prevent any warnings on unused or unflushed data output.Consume(); } // To test files, we create a temporary file, write, read, truncate, repeat. TEST_F(IoTest, FileIo) { std::string filename = absl::StrCat(TestTempDir(), "/zero_copy_stream_test_file"); for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { // Make a temporary file. int file = open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0777); ASSERT_GE(file, 0); { FileOutputStream output(file, kBlockSizes[i]); WriteStuff(&output); EXPECT_EQ(0, output.GetErrno()); } // Rewind. ASSERT_NE(lseek(file, 0, SEEK_SET), (off_t)-1); { FileInputStream input(file, kBlockSizes[j]); ReadStuff(&input); EXPECT_EQ(0, input.GetErrno()); } close(file); } } } #ifndef _WIN32 // This tests the FileInputStream with a non blocking file. It opens a pipe in // non blocking mode, then starts reading it. The writing thread starts writing // 100ms after that. TEST_F(IoTest, NonBlockingFileIo) { for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { int fd[2]; // On Linux we could use pipe2 to make the pipe non-blocking in one step, // but we instead use pipe and fcntl because pipe2 is not available on // Mac OS. ASSERT_EQ(pipe(fd), 0); ASSERT_EQ(fcntl(fd[0], F_SETFL, O_NONBLOCK), 0); ASSERT_EQ(fcntl(fd[1], F_SETFL, O_NONBLOCK), 0); std::mutex go_write; go_write.lock(); bool done_reading = false; std::thread write_thread([this, fd, &go_write, i]() { go_write.lock(); go_write.unlock(); FileOutputStream output(fd[1], kBlockSizes[i]); WriteStuff(&output); EXPECT_EQ(0, output.GetErrno()); }); std::thread read_thread([this, fd, &done_reading, j]() { FileInputStream input(fd[0], kBlockSizes[j]); ReadStuff(&input, false /* read_eof */); done_reading = true; close(fd[0]); close(fd[1]); EXPECT_EQ(0, input.GetErrno()); }); // Sleeping is not necessary but makes the next expectation relevant: the // reading thread waits for the data to be available before returning. std::this_thread::sleep_for(std::chrono::milliseconds(100)); EXPECT_FALSE(done_reading); go_write.unlock(); write_thread.join(); read_thread.join(); EXPECT_TRUE(done_reading); } } } TEST_F(IoTest, BlockingFileIoWithTimeout) { int fd[2]; for (int i = 0; i < kBlockSizeCount; i++) { ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), 0); struct timeval tv { .tv_sec = 0, .tv_usec = 5000 }; ASSERT_EQ(setsockopt(fd[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), 0); FileInputStream input(fd[0], kBlockSizes[i]); uint8_t byte; EXPECT_EQ(ReadFromInput(&input, &byte, 1), 0); EXPECT_EQ(EAGAIN, input.GetErrno()); } } #endif #if HAVE_ZLIB TEST_F(IoTest, GzipFileIo) { std::string filename = absl::StrCat(TestTempDir(), "/zero_copy_stream_test_file"); for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { // Make a temporary file. int file = open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0777); ASSERT_GE(file, 0); { FileOutputStream output(file, kBlockSizes[i]); GzipOutputStream gzout(&output); WriteStuffLarge(&gzout); gzout.Close(); output.Flush(); EXPECT_EQ(0, output.GetErrno()); } // Rewind. ASSERT_NE(lseek(file, 0, SEEK_SET), (off_t)-1); { FileInputStream input(file, kBlockSizes[j]); GzipInputStream gzin(&input); ReadStuffLarge(&gzin); EXPECT_EQ(0, input.GetErrno()); } close(file); } } } #endif // MSVC raises various debugging exceptions if we try to use a file // descriptor of -1, defeating our tests below. This class will disable // these debug assertions while in scope. class MsvcDebugDisabler { public: #if defined(_MSC_VER) && _MSC_VER >= 1400 MsvcDebugDisabler() { old_handler_ = _set_invalid_parameter_handler(MyHandler); old_mode_ = _CrtSetReportMode(_CRT_ASSERT, 0); } ~MsvcDebugDisabler() { old_handler_ = _set_invalid_parameter_handler(old_handler_); old_mode_ = _CrtSetReportMode(_CRT_ASSERT, old_mode_); } static void MyHandler(const wchar_t* expr, const wchar_t* func, const wchar_t* file, unsigned int line, uintptr_t pReserved) { // do nothing } _invalid_parameter_handler old_handler_; int old_mode_; #else // Dummy constructor and destructor to ensure that GCC doesn't complain // that debug_disabler is an unused variable. MsvcDebugDisabler() {} ~MsvcDebugDisabler() {} #endif }; // Test that FileInputStreams report errors correctly. TEST_F(IoTest, FileReadError) { MsvcDebugDisabler debug_disabler; // -1 = invalid file descriptor. FileInputStream input(-1); const void* buffer; int size; EXPECT_FALSE(input.Next(&buffer, &size)); EXPECT_EQ(EBADF, input.GetErrno()); } // Test that FileOutputStreams report errors correctly. TEST_F(IoTest, FileWriteError) { MsvcDebugDisabler debug_disabler; // -1 = invalid file descriptor. FileOutputStream input(-1); void* buffer; int size; // The first call to Next() succeeds because it doesn't have anything to // write yet. EXPECT_TRUE(input.Next(&buffer, &size)); // Second call fails. EXPECT_FALSE(input.Next(&buffer, &size)); EXPECT_EQ(EBADF, input.GetErrno()); } // Pipes are not seekable, so File{Input,Output}Stream ends up doing some // different things to handle them. We'll test by writing to a pipe and // reading back from it. TEST_F(IoTest, PipeIo) { int files[2]; for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { // Need to create a new pipe each time because ReadStuff() expects // to see EOF at the end. ASSERT_EQ(pipe(files), 0); { FileOutputStream output(files[1], kBlockSizes[i]); WriteStuff(&output); EXPECT_EQ(0, output.GetErrno()); } close(files[1]); // Send EOF. { FileInputStream input(files[0], kBlockSizes[j]); ReadStuff(&input); EXPECT_EQ(0, input.GetErrno()); } close(files[0]); } } } // Test using C++ iostreams. TEST_F(IoTest, IostreamIo) { for (int i = 0; i < kBlockSizeCount; i++) { for (int j = 0; j < kBlockSizeCount; j++) { { std::stringstream stream; { OstreamOutputStream output(&stream, kBlockSizes[i]); WriteStuff(&output); EXPECT_FALSE(stream.fail()); } { IstreamInputStream input(&stream, kBlockSizes[j]); ReadStuff(&input); EXPECT_TRUE(stream.eof()); } } { std::stringstream stream; { OstreamOutputStream output(&stream, kBlockSizes[i]); WriteStuffLarge(&output); EXPECT_FALSE(stream.fail()); } { IstreamInputStream input(&stream, kBlockSizes[j]); ReadStuffLarge(&input); EXPECT_TRUE(stream.eof()); } } } } } // To test ConcatenatingInputStream, we create several ArrayInputStreams // covering a buffer and then concatenate them. TEST_F(IoTest, ConcatenatingInputStream) { const int kBufferSize = 256; uint8_t buffer[kBufferSize]; // Fill the buffer. ArrayOutputStream output(buffer, kBufferSize); WriteStuff(&output); // Now split it up into multiple streams of varying sizes. ASSERT_EQ(68, output.ByteCount()); // Test depends on this. ArrayInputStream input1(buffer, 12); ArrayInputStream input2(buffer + 12, 7); ArrayInputStream input3(buffer + 19, 6); ArrayInputStream input4(buffer + 25, 15); ArrayInputStream input5(buffer + 40, 0); // Note: We want to make sure we have a stream boundary somewhere between // bytes 42 and 62, which is the range that it Skip()ed by ReadStuff(). This // tests that a bug that existed in the original code for Skip() is fixed. ArrayInputStream input6(buffer + 40, 10); ArrayInputStream input7(buffer + 50, 18); // Total = 68 bytes. ZeroCopyInputStream* streams[] = {&input1, &input2, &input3, &input4, &input5, &input6, &input7}; // Create the concatenating stream and read. ConcatenatingInputStream input(streams, ABSL_ARRAYSIZE(streams)); ReadStuff(&input); } // To test LimitingInputStream, we write our golden text to a buffer, then // create an ArrayInputStream that contains the whole buffer (not just the // bytes written), then use a LimitingInputStream to limit it just to the // bytes written. TEST_F(IoTest, LimitingInputStream) { const int kBufferSize = 256; uint8_t buffer[kBufferSize]; // Fill the buffer. ArrayOutputStream output(buffer, kBufferSize); WriteStuff(&output); // Set up input. ArrayInputStream array_input(buffer, kBufferSize); LimitingInputStream input(&array_input, output.ByteCount()); ReadStuff(&input); } // Checks that ByteCount works correctly for LimitingInputStreams where the // underlying stream has already been read. TEST_F(IoTest, LimitingInputStreamByteCount) { const int kHalfBufferSize = 128; const int kBufferSize = kHalfBufferSize * 2; uint8_t buffer[kBufferSize] = {}; // Set up input. Only allow half to be read at once. ArrayInputStream array_input(buffer, kBufferSize, kHalfBufferSize); const void* data; int size; EXPECT_TRUE(array_input.Next(&data, &size)); EXPECT_EQ(kHalfBufferSize, array_input.ByteCount()); // kHalfBufferSize - 1 to test limiting logic as well. LimitingInputStream input(&array_input, kHalfBufferSize - 1); EXPECT_EQ(0, input.ByteCount()); EXPECT_TRUE(input.Next(&data, &size)); EXPECT_EQ(kHalfBufferSize - 1, input.ByteCount()); } // Check that a zero-size array doesn't confuse the code. TEST(ZeroSizeArray, Input) { ArrayInputStream input(NULL, 0); const void* data; int size; EXPECT_FALSE(input.Next(&data, &size)); } TEST(ZeroSizeArray, Output) { ArrayOutputStream output(NULL, 0); void* data; int size; EXPECT_FALSE(output.Next(&data, &size)); } } // namespace } // namespace io } // namespace protobuf } // namespace google