zero_copy_stream_unittest.cc revision fbaaef999ba563838ebd00874ed8a1c01fbf286d
1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.  All rights reserved.
3// http://code.google.com/p/protobuf/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9//     * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11//     * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15//     * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: kenton@google.com (Kenton Varda)
32//  Based on original Protocol Buffers design by
33//  Sanjay Ghemawat, Jeff Dean, and others.
34//
35// Testing strategy:  For each type of I/O (array, string, file, etc.) we
36// create an output stream and write some data to it, then create a
37// corresponding input stream to read the same data back and expect it to
38// match.  When the data is written, it is written in several small chunks
39// of varying sizes, with a BackUp() after each chunk.  It is read back
40// similarly, but with chunks separated at different points.  The whole
41// process is run with a variety of block sizes for both the input and
42// the output.
43//
44// TODO(kenton):  Rewrite this test to bring it up to the standards of all
45//   the other proto2 tests.  May want to wait for gTest to implement
46//   "parametized tests" so that one set of tests can be used on all the
47//   implementations.
48
49#include "config.h"
50
51#ifdef _MSC_VER
52#include <io.h>
53#else
54#include <unistd.h>
55#endif
56#include <stdlib.h>
57#include <sys/types.h>
58#include <sys/stat.h>
59#include <fcntl.h>
60#include <errno.h>
61#include <sstream>
62
63#include <google/protobuf/io/zero_copy_stream_impl.h>
64
65#if HAVE_ZLIB
66#include <google/protobuf/io/gzip_stream.h>
67#endif
68
69#include <google/protobuf/stubs/common.h>
70#include <google/protobuf/testing/googletest.h>
71#include <google/protobuf/testing/file.h>
72#include <gtest/gtest.h>
73
74namespace google {
75namespace protobuf {
76namespace io {
77namespace {
78
79#ifdef _WIN32
80#define pipe(fds) _pipe(fds, 4096, O_BINARY)
81#endif
82
83#ifndef O_BINARY
84#ifdef _O_BINARY
85#define O_BINARY _O_BINARY
86#else
87#define O_BINARY 0     // If this isn't defined, the platform doesn't need it.
88#endif
89#endif
90
91class IoTest : public testing::Test {
92 protected:
93  // Test helpers.
94
95  // Helper to write an array of data to an output stream.
96  bool WriteToOutput(ZeroCopyOutputStream* output, const void* data, int size);
97  // Helper to read a fixed-length array of data from an input stream.
98  int ReadFromInput(ZeroCopyInputStream* input, void* data, int size);
99  // Write a string to the output stream.
100  void WriteString(ZeroCopyOutputStream* output, const string& str);
101  // Read a number of bytes equal to the size of the given string and checks
102  // that it matches the string.
103  void ReadString(ZeroCopyInputStream* input, const string& str);
104  // Writes some text to the output stream in a particular order.  Returns
105  // the number of bytes written, incase the caller needs that to set up an
106  // input stream.
107  int WriteStuff(ZeroCopyOutputStream* output);
108  // Reads text from an input stream and expects it to match what
109  // WriteStuff() writes.
110  void ReadStuff(ZeroCopyInputStream* input);
111
112  // Similar to WriteStuff, but performs more sophisticated testing.
113  int WriteStuffLarge(ZeroCopyOutputStream* output);
114  // Reads and tests a stream that should have been written to
115  // via WriteStuffLarge().
116  void ReadStuffLarge(ZeroCopyInputStream* input);
117
118#if HAVE_ZLIB
119  string Compress(const string& data, const GzipOutputStream::Options& options);
120  string Uncompress(const string& data);
121#endif
122
123  static const int kBlockSizes[];
124  static const int kBlockSizeCount;
125};
126
127const int IoTest::kBlockSizes[] = {-1, 1, 2, 5, 7, 10, 23, 64};
128const int IoTest::kBlockSizeCount = GOOGLE_ARRAYSIZE(IoTest::kBlockSizes);
129
130bool IoTest::WriteToOutput(ZeroCopyOutputStream* output,
131                           const void* data, int size) {
132  const uint8* in = reinterpret_cast<const uint8*>(data);
133  int in_size = size;
134
135  void* out;
136  int out_size;
137
138  while (true) {
139    if (!output->Next(&out, &out_size)) {
140      return false;
141    }
142    EXPECT_GT(out_size, 0);
143
144    if (in_size <= out_size) {
145      memcpy(out, in, in_size);
146      output->BackUp(out_size - in_size);
147      return true;
148    }
149
150    memcpy(out, in, out_size);
151    in += out_size;
152    in_size -= out_size;
153  }
154}
155
156#define MAX_REPEATED_ZEROS 100
157
158int IoTest::ReadFromInput(ZeroCopyInputStream* input, void* data, int size) {
159  uint8* out = reinterpret_cast<uint8*>(data);
160  int out_size = size;
161
162  const void* in;
163  int in_size = 0;
164
165  int repeated_zeros = 0;
166
167  while (true) {
168    if (!input->Next(&in, &in_size)) {
169      return size - out_size;
170    }
171    EXPECT_GT(in_size, -1);
172    if (in_size == 0) {
173      repeated_zeros++;
174    } else {
175      repeated_zeros = 0;
176    }
177    EXPECT_LT(repeated_zeros, MAX_REPEATED_ZEROS);
178
179    if (out_size <= in_size) {
180      memcpy(out, in, out_size);
181      if (in_size > out_size) {
182        input->BackUp(in_size - out_size);
183      }
184      return size;  // Copied all of it.
185    }
186
187    memcpy(out, in, in_size);
188    out += in_size;
189    out_size -= in_size;
190  }
191}
192
193void IoTest::WriteString(ZeroCopyOutputStream* output, const string& str) {
194  EXPECT_TRUE(WriteToOutput(output, str.c_str(), str.size()));
195}
196
197void IoTest::ReadString(ZeroCopyInputStream* input, const string& str) {
198  scoped_array<char> buffer(new char[str.size() + 1]);
199  buffer[str.size()] = '\0';
200  EXPECT_EQ(ReadFromInput(input, buffer.get(), str.size()), str.size());
201  EXPECT_STREQ(str.c_str(), buffer.get());
202}
203
204int IoTest::WriteStuff(ZeroCopyOutputStream* output) {
205  WriteString(output, "Hello world!\n");
206  WriteString(output, "Some te");
207  WriteString(output, "xt.  Blah blah.");
208  WriteString(output, "abcdefg");
209  WriteString(output, "01234567890123456789");
210  WriteString(output, "foobar");
211
212  EXPECT_EQ(output->ByteCount(), 68);
213
214  int result = output->ByteCount();
215  return result;
216}
217
218// Reads text from an input stream and expects it to match what WriteStuff()
219// writes.
220void IoTest::ReadStuff(ZeroCopyInputStream* input) {
221  ReadString(input, "Hello world!\n");
222  ReadString(input, "Some text.  ");
223  ReadString(input, "Blah ");
224  ReadString(input, "blah.");
225  ReadString(input, "abcdefg");
226  EXPECT_TRUE(input->Skip(20));
227  ReadString(input, "foo");
228  ReadString(input, "bar");
229
230  EXPECT_EQ(input->ByteCount(), 68);
231
232  uint8 byte;
233  EXPECT_EQ(ReadFromInput(input, &byte, 1), 0);
234}
235
236int IoTest::WriteStuffLarge(ZeroCopyOutputStream* output) {
237  WriteString(output, "Hello world!\n");
238  WriteString(output, "Some te");
239  WriteString(output, "xt.  Blah blah.");
240  WriteString(output, string(100000, 'x'));  // A very long string
241  WriteString(output, string(100000, 'y'));  // A very long string
242  WriteString(output, "01234567890123456789");
243
244  EXPECT_EQ(output->ByteCount(), 200055);
245
246  int result = output->ByteCount();
247  return result;
248}
249
250// Reads text from an input stream and expects it to match what WriteStuff()
251// writes.
252void IoTest::ReadStuffLarge(ZeroCopyInputStream* input) {
253  ReadString(input, "Hello world!\nSome text.  ");
254  EXPECT_TRUE(input->Skip(5));
255  ReadString(input, "blah.");
256  EXPECT_TRUE(input->Skip(100000 - 10));
257  ReadString(input, string(10, 'x') + string(100000 - 20000, 'y'));
258  EXPECT_TRUE(input->Skip(20000 - 10));
259  ReadString(input, "yyyyyyyyyy01234567890123456789");
260
261  EXPECT_EQ(input->ByteCount(), 200055);
262
263  uint8 byte;
264  EXPECT_EQ(ReadFromInput(input, &byte, 1), 0);
265}
266
267// ===================================================================
268
269TEST_F(IoTest, ArrayIo) {
270  const int kBufferSize = 256;
271  uint8 buffer[kBufferSize];
272
273  for (int i = 0; i < kBlockSizeCount; i++) {
274    for (int j = 0; j < kBlockSizeCount; j++) {
275      int size;
276      {
277        ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]);
278        size = WriteStuff(&output);
279      }
280      {
281        ArrayInputStream input(buffer, size, kBlockSizes[j]);
282        ReadStuff(&input);
283      }
284    }
285  }
286}
287
288#if HAVE_ZLIB
289TEST_F(IoTest, GzipIo) {
290  const int kBufferSize = 2*1024;
291  uint8* buffer = new uint8[kBufferSize];
292  for (int i = 0; i < kBlockSizeCount; i++) {
293    for (int j = 0; j < kBlockSizeCount; j++) {
294      for (int z = 0; z < kBlockSizeCount; z++) {
295        int gzip_buffer_size = kBlockSizes[z];
296        int size;
297        {
298          ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]);
299          GzipOutputStream gzout(
300              &output, GzipOutputStream::GZIP, gzip_buffer_size);
301          WriteStuff(&gzout);
302          gzout.Close();
303          size = output.ByteCount();
304        }
305        {
306          ArrayInputStream input(buffer, size, kBlockSizes[j]);
307          GzipInputStream gzin(
308              &input, GzipInputStream::GZIP, gzip_buffer_size);
309          ReadStuff(&gzin);
310        }
311      }
312    }
313  }
314  delete [] buffer;
315}
316
317TEST_F(IoTest, ZlibIo) {
318  const int kBufferSize = 2*1024;
319  uint8* buffer = new uint8[kBufferSize];
320  for (int i = 0; i < kBlockSizeCount; i++) {
321    for (int j = 0; j < kBlockSizeCount; j++) {
322      for (int z = 0; z < kBlockSizeCount; z++) {
323        int gzip_buffer_size = kBlockSizes[z];
324        int size;
325        {
326          ArrayOutputStream output(buffer, kBufferSize, kBlockSizes[i]);
327          GzipOutputStream gzout(
328              &output, GzipOutputStream::ZLIB, gzip_buffer_size);
329          WriteStuff(&gzout);
330          gzout.Close();
331          size = output.ByteCount();
332        }
333        {
334          ArrayInputStream input(buffer, size, kBlockSizes[j]);
335          GzipInputStream gzin(
336              &input, GzipInputStream::ZLIB, gzip_buffer_size);
337          ReadStuff(&gzin);
338        }
339      }
340    }
341  }
342  delete [] buffer;
343}
344
345TEST_F(IoTest, ZlibIoInputAutodetect) {
346  const int kBufferSize = 2*1024;
347  uint8* buffer = new uint8[kBufferSize];
348  int size;
349  {
350    ArrayOutputStream output(buffer, kBufferSize);
351    GzipOutputStream gzout(&output, GzipOutputStream::ZLIB);
352    WriteStuff(&gzout);
353    gzout.Close();
354    size = output.ByteCount();
355  }
356  {
357    ArrayInputStream input(buffer, size);
358    GzipInputStream gzin(&input, GzipInputStream::AUTO);
359    ReadStuff(&gzin);
360  }
361  {
362    ArrayOutputStream output(buffer, kBufferSize);
363    GzipOutputStream gzout(&output, GzipOutputStream::GZIP);
364    WriteStuff(&gzout);
365    gzout.Close();
366    size = output.ByteCount();
367  }
368  {
369    ArrayInputStream input(buffer, size);
370    GzipInputStream gzin(&input, GzipInputStream::AUTO);
371    ReadStuff(&gzin);
372  }
373  delete [] buffer;
374}
375
376string IoTest::Compress(const string& data,
377                        const GzipOutputStream::Options& options) {
378  string result;
379  {
380    StringOutputStream output(&result);
381    GzipOutputStream gzout(&output, options);
382    WriteToOutput(&gzout, data.data(), data.size());
383  }
384  return result;
385}
386
387string IoTest::Uncompress(const string& data) {
388  string result;
389  {
390    ArrayInputStream input(data.data(), data.size());
391    GzipInputStream gzin(&input);
392    const void* buffer;
393    int size;
394    while (gzin.Next(&buffer, &size)) {
395      result.append(reinterpret_cast<const char*>(buffer), size);
396    }
397  }
398  return result;
399}
400
401TEST_F(IoTest, CompressionOptions) {
402  // Some ad-hoc testing of compression options.
403
404  string golden;
405  File::ReadFileToStringOrDie(
406      TestSourceDir() + "/google/protobuf/testdata/golden_message", &golden);
407
408  GzipOutputStream::Options options;
409  string gzip_compressed = Compress(golden, options);
410
411  options.compression_level = 0;
412  string not_compressed = Compress(golden, options);
413
414  // Try zlib compression for fun.
415  options = GzipOutputStream::Options();
416  options.format = GzipOutputStream::ZLIB;
417  string zlib_compressed = Compress(golden, options);
418
419  // Uncompressed should be bigger than the original since it should have some
420  // sort of header.
421  EXPECT_GT(not_compressed.size(), golden.size());
422
423  // Higher compression levels should result in smaller sizes.
424  EXPECT_LT(zlib_compressed.size(), not_compressed.size());
425
426  // ZLIB format should differ from GZIP format.
427  EXPECT_TRUE(zlib_compressed != gzip_compressed);
428
429  // Everything should decompress correctly.
430  EXPECT_TRUE(Uncompress(not_compressed) == golden);
431  EXPECT_TRUE(Uncompress(gzip_compressed) == golden);
432  EXPECT_TRUE(Uncompress(zlib_compressed) == golden);
433}
434#endif
435
436// There is no string input, only string output.  Also, it doesn't support
437// explicit block sizes.  So, we'll only run one test and we'll use
438// ArrayInput to read back the results.
439TEST_F(IoTest, StringIo) {
440  string str;
441  {
442    StringOutputStream output(&str);
443    WriteStuff(&output);
444  }
445  {
446    ArrayInputStream input(str.data(), str.size());
447    ReadStuff(&input);
448  }
449}
450
451
452// To test files, we create a temporary file, write, read, truncate, repeat.
453TEST_F(IoTest, FileIo) {
454  string filename = TestTempDir() + "/zero_copy_stream_test_file";
455
456  for (int i = 0; i < kBlockSizeCount; i++) {
457    for (int j = 0; j < kBlockSizeCount; j++) {
458      // Make a temporary file.
459      int file =
460        open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0777);
461      ASSERT_GE(file, 0);
462
463      {
464        FileOutputStream output(file, kBlockSizes[i]);
465        WriteStuff(&output);
466        EXPECT_EQ(0, output.GetErrno());
467      }
468
469      // Rewind.
470      ASSERT_NE(lseek(file, 0, SEEK_SET), (off_t)-1);
471
472      {
473        FileInputStream input(file, kBlockSizes[j]);
474        ReadStuff(&input);
475        EXPECT_EQ(0, input.GetErrno());
476      }
477
478      close(file);
479    }
480  }
481}
482
483#if HAVE_ZLIB
484TEST_F(IoTest, GzipFileIo) {
485  string filename = TestTempDir() + "/zero_copy_stream_test_file";
486
487  for (int i = 0; i < kBlockSizeCount; i++) {
488    for (int j = 0; j < kBlockSizeCount; j++) {
489      // Make a temporary file.
490      int file =
491        open(filename.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0777);
492      ASSERT_GE(file, 0);
493      {
494        FileOutputStream output(file, kBlockSizes[i]);
495        GzipOutputStream gzout(&output);
496        WriteStuffLarge(&gzout);
497        gzout.Close();
498        output.Flush();
499        EXPECT_EQ(0, output.GetErrno());
500      }
501
502      // Rewind.
503      ASSERT_NE(lseek(file, 0, SEEK_SET), (off_t)-1);
504
505      {
506        FileInputStream input(file, kBlockSizes[j]);
507        GzipInputStream gzin(&input);
508        ReadStuffLarge(&gzin);
509        EXPECT_EQ(0, input.GetErrno());
510      }
511
512      close(file);
513    }
514  }
515}
516#endif
517
518// MSVC raises various debugging exceptions if we try to use a file
519// descriptor of -1, defeating our tests below.  This class will disable
520// these debug assertions while in scope.
521class MsvcDebugDisabler {
522 public:
523#ifdef _MSC_VER
524  MsvcDebugDisabler() {
525    old_handler_ = _set_invalid_parameter_handler(MyHandler);
526    old_mode_ = _CrtSetReportMode(_CRT_ASSERT, 0);
527  }
528  ~MsvcDebugDisabler() {
529    old_handler_ = _set_invalid_parameter_handler(old_handler_);
530    old_mode_ = _CrtSetReportMode(_CRT_ASSERT, old_mode_);
531  }
532
533  static void MyHandler(const wchar_t *expr,
534                        const wchar_t *func,
535                        const wchar_t *file,
536                        unsigned int line,
537                        uintptr_t pReserved) {
538    // do nothing
539  }
540
541  _invalid_parameter_handler old_handler_;
542  int old_mode_;
543#else
544  // Dummy constructor and destructor to ensure that GCC doesn't complain
545  // that debug_disabler is an unused variable.
546  MsvcDebugDisabler() {}
547  ~MsvcDebugDisabler() {}
548#endif
549};
550
551// Test that FileInputStreams report errors correctly.
552TEST_F(IoTest, FileReadError) {
553  MsvcDebugDisabler debug_disabler;
554
555  // -1 = invalid file descriptor.
556  FileInputStream input(-1);
557
558  const void* buffer;
559  int size;
560  EXPECT_FALSE(input.Next(&buffer, &size));
561  EXPECT_EQ(EBADF, input.GetErrno());
562}
563
564// Test that FileOutputStreams report errors correctly.
565TEST_F(IoTest, FileWriteError) {
566  MsvcDebugDisabler debug_disabler;
567
568  // -1 = invalid file descriptor.
569  FileOutputStream input(-1);
570
571  void* buffer;
572  int size;
573
574  // The first call to Next() succeeds because it doesn't have anything to
575  // write yet.
576  EXPECT_TRUE(input.Next(&buffer, &size));
577
578  // Second call fails.
579  EXPECT_FALSE(input.Next(&buffer, &size));
580
581  EXPECT_EQ(EBADF, input.GetErrno());
582}
583
584// Pipes are not seekable, so File{Input,Output}Stream ends up doing some
585// different things to handle them.  We'll test by writing to a pipe and
586// reading back from it.
587TEST_F(IoTest, PipeIo) {
588  int files[2];
589
590  for (int i = 0; i < kBlockSizeCount; i++) {
591    for (int j = 0; j < kBlockSizeCount; j++) {
592      // Need to create a new pipe each time because ReadStuff() expects
593      // to see EOF at the end.
594      ASSERT_EQ(pipe(files), 0);
595
596      {
597        FileOutputStream output(files[1], kBlockSizes[i]);
598        WriteStuff(&output);
599        EXPECT_EQ(0, output.GetErrno());
600      }
601      close(files[1]);  // Send EOF.
602
603      {
604        FileInputStream input(files[0], kBlockSizes[j]);
605        ReadStuff(&input);
606        EXPECT_EQ(0, input.GetErrno());
607      }
608      close(files[0]);
609    }
610  }
611}
612
613// Test using C++ iostreams.
614TEST_F(IoTest, IostreamIo) {
615  for (int i = 0; i < kBlockSizeCount; i++) {
616    for (int j = 0; j < kBlockSizeCount; j++) {
617      {
618        stringstream stream;
619
620        {
621          OstreamOutputStream output(&stream, kBlockSizes[i]);
622          WriteStuff(&output);
623          EXPECT_FALSE(stream.fail());
624        }
625
626        {
627          IstreamInputStream input(&stream, kBlockSizes[j]);
628          ReadStuff(&input);
629          EXPECT_TRUE(stream.eof());
630        }
631      }
632
633      {
634        stringstream stream;
635
636        {
637          OstreamOutputStream output(&stream, kBlockSizes[i]);
638          WriteStuffLarge(&output);
639          EXPECT_FALSE(stream.fail());
640        }
641
642        {
643          IstreamInputStream input(&stream, kBlockSizes[j]);
644          ReadStuffLarge(&input);
645          EXPECT_TRUE(stream.eof());
646        }
647      }
648    }
649  }
650}
651
652// To test ConcatenatingInputStream, we create several ArrayInputStreams
653// covering a buffer and then concatenate them.
654TEST_F(IoTest, ConcatenatingInputStream) {
655  const int kBufferSize = 256;
656  uint8 buffer[kBufferSize];
657
658  // Fill the buffer.
659  ArrayOutputStream output(buffer, kBufferSize);
660  WriteStuff(&output);
661
662  // Now split it up into multiple streams of varying sizes.
663  ASSERT_EQ(68, output.ByteCount());  // Test depends on this.
664  ArrayInputStream input1(buffer     , 12);
665  ArrayInputStream input2(buffer + 12,  7);
666  ArrayInputStream input3(buffer + 19,  6);
667  ArrayInputStream input4(buffer + 25, 15);
668  ArrayInputStream input5(buffer + 40,  0);
669  // Note:  We want to make sure we have a stream boundary somewhere between
670  // bytes 42 and 62, which is the range that it Skip()ed by ReadStuff().  This
671  // tests that a bug that existed in the original code for Skip() is fixed.
672  ArrayInputStream input6(buffer + 40, 10);
673  ArrayInputStream input7(buffer + 50, 18);  // Total = 68 bytes.
674
675  ZeroCopyInputStream* streams[] =
676    {&input1, &input2, &input3, &input4, &input5, &input6, &input7};
677
678  // Create the concatenating stream and read.
679  ConcatenatingInputStream input(streams, GOOGLE_ARRAYSIZE(streams));
680  ReadStuff(&input);
681}
682
683// To test LimitingInputStream, we write our golden text to a buffer, then
684// create an ArrayInputStream that contains the whole buffer (not just the
685// bytes written), then use a LimitingInputStream to limit it just to the
686// bytes written.
687TEST_F(IoTest, LimitingInputStream) {
688  const int kBufferSize = 256;
689  uint8 buffer[kBufferSize];
690
691  // Fill the buffer.
692  ArrayOutputStream output(buffer, kBufferSize);
693  WriteStuff(&output);
694
695  // Set up input.
696  ArrayInputStream array_input(buffer, kBufferSize);
697  LimitingInputStream input(&array_input, output.ByteCount());
698
699  ReadStuff(&input);
700}
701
702// Check that a zero-size array doesn't confuse the code.
703TEST(ZeroSizeArray, Input) {
704  ArrayInputStream input(NULL, 0);
705  const void* data;
706  int size;
707  EXPECT_FALSE(input.Next(&data, &size));
708}
709
710TEST(ZeroSizeArray, Output) {
711  ArrayOutputStream output(NULL, 0);
712  void* data;
713  int size;
714  EXPECT_FALSE(output.Next(&data, &size));
715}
716
717}  // namespace
718}  // namespace io
719}  // namespace protobuf
720}  // namespace google
721