capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

serialize-test.c++ (15117B)


      1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
      2 // Licensed under the MIT License:
      3 //
      4 // Permission is hereby granted, free of charge, to any person obtaining a copy
      5 // of this software and associated documentation files (the "Software"), to deal
      6 // in the Software without restriction, including without limitation the rights
      7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      8 // copies of the Software, and to permit persons to whom the Software is
      9 // furnished to do so, subject to the following conditions:
     10 //
     11 // The above copyright notice and this permission notice shall be included in
     12 // all copies or substantial portions of the Software.
     13 //
     14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     20 // THE SOFTWARE.
     21 
     22 #ifndef _GNU_SOURCE
     23 #define _GNU_SOURCE
     24 #endif
     25 
     26 #include "serialize.h"
     27 #include <kj/debug.h>
     28 #include <kj/compat/gtest.h>
     29 #include <kj/miniposix.h>
     30 #include <string>
     31 #include <stdlib.h>
     32 #include <fcntl.h>
     33 #include "test-util.h"
     34 
     35 namespace capnp {
     36 namespace _ {  // private
     37 namespace {
     38 
     39 class TestMessageBuilder: public MallocMessageBuilder {
     40   // A MessageBuilder that tries to allocate an exact number of total segments, by allocating
     41   // minimum-size segments until it reaches the number, then allocating one large segment to
     42   // finish.
     43 
     44 public:
     45   explicit TestMessageBuilder(uint desiredSegmentCount)
     46       : MallocMessageBuilder(0, AllocationStrategy::FIXED_SIZE),
     47         desiredSegmentCount(desiredSegmentCount) {}
     48   ~TestMessageBuilder() {
     49     EXPECT_EQ(0u, desiredSegmentCount);
     50   }
     51 
     52   kj::ArrayPtr<word> allocateSegment(uint minimumSize) override {
     53     if (desiredSegmentCount <= 1) {
     54       if (desiredSegmentCount < 1) {
     55         ADD_FAILURE() << "Allocated more segments than desired.";
     56       } else {
     57         --desiredSegmentCount;
     58       }
     59       return MallocMessageBuilder::allocateSegment(SUGGESTED_FIRST_SEGMENT_WORDS);
     60     } else {
     61       --desiredSegmentCount;
     62       return MallocMessageBuilder::allocateSegment(minimumSize);
     63     }
     64   }
     65 
     66 private:
     67   uint desiredSegmentCount;
     68 };
     69 
     70 kj::Array<word> copyWords(kj::ArrayPtr<const word> input) {
     71   auto result = kj::heapArray<word>(input.size());
     72   memcpy(result.asBytes().begin(), input.asBytes().begin(), input.asBytes().size());
     73   return result;
     74 }
     75 
     76 TEST(Serialize, FlatArray) {
     77   TestMessageBuilder builder(1);
     78   initTestMessage(builder.initRoot<TestAllTypes>());
     79 
     80   kj::Array<word> serialized = messageToFlatArray(builder);
     81 
     82   {
     83     FlatArrayMessageReader reader(serialized.asPtr());
     84     checkTestMessage(reader.getRoot<TestAllTypes>());
     85     EXPECT_EQ(serialized.end(), reader.getEnd());
     86   }
     87 
     88   {
     89     MallocMessageBuilder builder2;
     90     auto remaining = initMessageBuilderFromFlatArrayCopy(serialized, builder2);
     91     checkTestMessage(builder2.getRoot<TestAllTypes>());
     92     EXPECT_EQ(serialized.end(), remaining.begin());
     93     EXPECT_EQ(serialized.end(), remaining.end());
     94   }
     95 
     96   kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
     97   memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));
     98 
     99   {
    100     FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    101     checkTestMessage(reader.getRoot<TestAllTypes>());
    102     EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
    103   }
    104 
    105   {
    106     MallocMessageBuilder builder2;
    107     auto remaining = initMessageBuilderFromFlatArrayCopy(serializedWithSuffix, builder2);
    108     checkTestMessage(builder2.getRoot<TestAllTypes>());
    109     EXPECT_EQ(serializedWithSuffix.end() - 5, remaining.begin());
    110     EXPECT_EQ(serializedWithSuffix.end(), remaining.end());
    111   }
    112 
    113   {
    114     // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    115     // detect out-of-bounds access.
    116     EXPECT_EQ(1, expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, 0))));
    117     for (uint i = 1; i <= serialized.size(); i++) {
    118       EXPECT_EQ(serialized.size(),
    119           expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    120     }
    121   }
    122 }
    123 
    124 TEST(Serialize, FlatArrayOddSegmentCount) {
    125   TestMessageBuilder builder(7);
    126   initTestMessage(builder.initRoot<TestAllTypes>());
    127 
    128   kj::Array<word> serialized = messageToFlatArray(builder);
    129 
    130   {
    131     FlatArrayMessageReader reader(serialized.asPtr());
    132     checkTestMessage(reader.getRoot<TestAllTypes>());
    133     EXPECT_EQ(serialized.end(), reader.getEnd());
    134   }
    135 
    136   kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
    137   memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));
    138 
    139   {
    140     FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    141     checkTestMessage(reader.getRoot<TestAllTypes>());
    142     EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
    143   }
    144 
    145   {
    146     // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    147     // detect out-of-bounds access.
    148 
    149     // Segment table is 4 words, so with fewer words we'll have incomplete information.
    150     for (uint i = 0; i < 4; i++) {
    151       size_t expectedSize = expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i)));
    152       EXPECT_LT(expectedSize, serialized.size());
    153       EXPECT_GT(expectedSize, i);
    154     }
    155     // After that, we get the exact length.
    156     for (uint i = 4; i <= serialized.size(); i++) {
    157       EXPECT_EQ(serialized.size(),
    158           expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    159     }
    160   }
    161 }
    162 
    163 TEST(Serialize, FlatArrayEvenSegmentCount) {
    164   TestMessageBuilder builder(10);
    165   initTestMessage(builder.initRoot<TestAllTypes>());
    166 
    167   kj::Array<word> serialized = messageToFlatArray(builder);
    168 
    169   {
    170     FlatArrayMessageReader reader(serialized.asPtr());
    171     checkTestMessage(reader.getRoot<TestAllTypes>());
    172     EXPECT_EQ(serialized.end(), reader.getEnd());
    173   }
    174 
    175   kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
    176   memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));
    177 
    178   {
    179     FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    180     checkTestMessage(reader.getRoot<TestAllTypes>());
    181     EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
    182   }
    183 
    184   {
    185     // Test expectedSizeInWordsFromPrefix(). We pass in a copy of the slice so that valgrind can
    186     // detect out-of-bounds access.
    187 
    188     // Segment table is 6 words, so with fewer words we'll have incomplete information.
    189     for (uint i = 0; i < 6; i++) {
    190       size_t expectedSize = expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i)));
    191       EXPECT_LT(expectedSize, serialized.size());
    192       EXPECT_GT(expectedSize, i);
    193     }
    194     // After that, we get the exact length.
    195     for (uint i = 6; i <= serialized.size(); i++) {
    196       EXPECT_EQ(serialized.size(),
    197           expectedSizeInWordsFromPrefix(copyWords(serialized.slice(0, i))));
    198     }
    199   }
    200 }
    201 
    202 class TestInputStream: public kj::InputStream {
    203 public:
    204   TestInputStream(kj::ArrayPtr<const word> data, bool lazy)
    205       : pos(data.asChars().begin()),
    206         end(data.asChars().end()),
    207         lazy(lazy) {}
    208   ~TestInputStream() {}
    209 
    210   size_t tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {
    211     KJ_ASSERT(maxBytes <= size_t(end - pos), "Overran end of stream.");
    212     size_t amount = lazy ? minBytes : maxBytes;
    213     memcpy(buffer, pos, amount);
    214     pos += amount;
    215     return amount;
    216   }
    217 
    218 private:
    219   const char* pos;
    220   const char* end;
    221   bool lazy;
    222 };
    223 
    224 TEST(Serialize, InputStream) {
    225   TestMessageBuilder builder(1);
    226   initTestMessage(builder.initRoot<TestAllTypes>());
    227 
    228   kj::Array<word> serialized = messageToFlatArray(builder);
    229 
    230   TestInputStream stream(serialized.asPtr(), false);
    231   InputStreamMessageReader reader(stream, ReaderOptions());
    232 
    233   checkTestMessage(reader.getRoot<TestAllTypes>());
    234 }
    235 
    236 TEST(Serialize, InputStreamScratchSpace) {
    237   TestMessageBuilder builder(1);
    238   initTestMessage(builder.initRoot<TestAllTypes>());
    239 
    240   kj::Array<word> serialized = messageToFlatArray(builder);
    241 
    242   word scratch[4096];
    243   TestInputStream stream(serialized.asPtr(), false);
    244   InputStreamMessageReader reader(stream, ReaderOptions(), kj::ArrayPtr<word>(scratch, 4096));
    245 
    246   checkTestMessage(reader.getRoot<TestAllTypes>());
    247 }
    248 
    249 TEST(Serialize, InputStreamLazy) {
    250   TestMessageBuilder builder(1);
    251   initTestMessage(builder.initRoot<TestAllTypes>());
    252 
    253   kj::Array<word> serialized = messageToFlatArray(builder);
    254 
    255   TestInputStream stream(serialized.asPtr(), true);
    256   InputStreamMessageReader reader(stream, ReaderOptions());
    257 
    258   checkTestMessage(reader.getRoot<TestAllTypes>());
    259 }
    260 
    261 TEST(Serialize, InputStreamOddSegmentCount) {
    262   TestMessageBuilder builder(7);
    263   initTestMessage(builder.initRoot<TestAllTypes>());
    264 
    265   kj::Array<word> serialized = messageToFlatArray(builder);
    266 
    267   TestInputStream stream(serialized.asPtr(), false);
    268   InputStreamMessageReader reader(stream, ReaderOptions());
    269 
    270   checkTestMessage(reader.getRoot<TestAllTypes>());
    271 }
    272 
    273 TEST(Serialize, InputStreamOddSegmentCountLazy) {
    274   TestMessageBuilder builder(7);
    275   initTestMessage(builder.initRoot<TestAllTypes>());
    276 
    277   kj::Array<word> serialized = messageToFlatArray(builder);
    278 
    279   TestInputStream stream(serialized.asPtr(), true);
    280   InputStreamMessageReader reader(stream, ReaderOptions());
    281 
    282   checkTestMessage(reader.getRoot<TestAllTypes>());
    283 }
    284 
    285 TEST(Serialize, InputStreamEvenSegmentCount) {
    286   TestMessageBuilder builder(10);
    287   initTestMessage(builder.initRoot<TestAllTypes>());
    288 
    289   kj::Array<word> serialized = messageToFlatArray(builder);
    290 
    291   TestInputStream stream(serialized.asPtr(), false);
    292   InputStreamMessageReader reader(stream, ReaderOptions());
    293 
    294   checkTestMessage(reader.getRoot<TestAllTypes>());
    295 }
    296 
    297 TEST(Serialize, InputStreamEvenSegmentCountLazy) {
    298   TestMessageBuilder builder(10);
    299   initTestMessage(builder.initRoot<TestAllTypes>());
    300 
    301   kj::Array<word> serialized = messageToFlatArray(builder);
    302 
    303   TestInputStream stream(serialized.asPtr(), true);
    304   InputStreamMessageReader reader(stream, ReaderOptions());
    305 
    306   checkTestMessage(reader.getRoot<TestAllTypes>());
    307 }
    308 
    309 TEST(Serialize, InputStreamToBuilder) {
    310   TestMessageBuilder builder(1);
    311   initTestMessage(builder.initRoot<TestAllTypes>());
    312 
    313   kj::Array<word> serialized = messageToFlatArray(builder);
    314 
    315   TestInputStream stream(serialized.asPtr(), false);
    316 
    317   MallocMessageBuilder builder2;
    318   readMessageCopy(stream, builder2);
    319 
    320   checkTestMessage(builder2.getRoot<TestAllTypes>());
    321 }
    322 
    323 class TestOutputStream: public kj::OutputStream {
    324 public:
    325   TestOutputStream() {}
    326   ~TestOutputStream() {}
    327 
    328   void write(const void* buffer, size_t size) override {
    329     data.append(reinterpret_cast<const char*>(buffer), size);
    330   }
    331 
    332   bool dataEquals(kj::ArrayPtr<const word> other) {
    333     return data ==
    334         std::string(other.asChars().begin(), other.asChars().size());
    335   }
    336 
    337 private:
    338   std::string data;
    339 };
    340 
    341 TEST(Serialize, WriteMessage) {
    342   TestMessageBuilder builder(1);
    343   initTestMessage(builder.initRoot<TestAllTypes>());
    344 
    345   kj::Array<word> serialized = messageToFlatArray(builder);
    346 
    347   TestOutputStream output;
    348   writeMessage(output, builder);
    349 
    350   EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
    351 }
    352 
    353 TEST(Serialize, WriteMessageOddSegmentCount) {
    354   TestMessageBuilder builder(7);
    355   initTestMessage(builder.initRoot<TestAllTypes>());
    356 
    357   kj::Array<word> serialized = messageToFlatArray(builder);
    358 
    359   TestOutputStream output;
    360   writeMessage(output, builder);
    361 
    362   EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
    363 }
    364 
    365 TEST(Serialize, WriteMessageEvenSegmentCount) {
    366   TestMessageBuilder builder(10);
    367   initTestMessage(builder.initRoot<TestAllTypes>());
    368 
    369   kj::Array<word> serialized = messageToFlatArray(builder);
    370 
    371   TestOutputStream output;
    372   writeMessage(output, builder);
    373 
    374   EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
    375 }
    376 
    377 #if _WIN32
    378 int mkstemp(char *tpl) {
    379   char* end = tpl + strlen(tpl);
    380   while (end > tpl && *(end-1) == 'X') --end;
    381 
    382   for (;;) {
    383     KJ_ASSERT(_mktemp(tpl) == tpl);
    384 
    385     int fd = open(tpl, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY | O_BINARY, 0700);
    386     if (fd >= 0) {
    387       return fd;
    388     }
    389 
    390     int error = errno;
    391     if (error != EEXIST && error != EINTR) {
    392       KJ_FAIL_SYSCALL("open(mktemp())", error, tpl);
    393     }
    394 
    395     memset(end, 'X', strlen(end));
    396   }
    397 }
    398 #endif
    399 
    400 TEST(Serialize, FileDescriptors) {
    401 #if _WIN32 || __ANDROID__
    402   // TODO(cleanup): Find the Windows temp directory? Seems overly difficult.
    403   char filename[] = "capnproto-serialize-test-XXXXXX";
    404 #else
    405   char filename[] = "/tmp/capnproto-serialize-test-XXXXXX";
    406 #endif
    407   kj::AutoCloseFd tmpfile(mkstemp(filename));
    408   ASSERT_GE(tmpfile.get(), 0);
    409 
    410 #if !_WIN32
    411   // Unlink the file so that it will be deleted on close.
    412   // (For win32, we already handled this is mkstemp().)
    413   EXPECT_EQ(0, unlink(filename));
    414 #endif
    415 
    416   {
    417     TestMessageBuilder builder(7);
    418     initTestMessage(builder.initRoot<TestAllTypes>());
    419     writeMessageToFd(tmpfile.get(), builder);
    420   }
    421 
    422   {
    423     TestMessageBuilder builder(1);
    424     builder.initRoot<TestAllTypes>().setTextField("second message in file");
    425     writeMessageToFd(tmpfile.get(), builder);
    426   }
    427 
    428   lseek(tmpfile, 0, SEEK_SET);
    429 
    430   {
    431     StreamFdMessageReader reader(tmpfile.get());
    432     checkTestMessage(reader.getRoot<TestAllTypes>());
    433   }
    434 
    435   {
    436     StreamFdMessageReader reader(tmpfile.get());
    437     EXPECT_EQ("second message in file", reader.getRoot<TestAllTypes>().getTextField());
    438   }
    439 }
    440 
    441 TEST(Serialize, RejectTooManySegments) {
    442   kj::Array<word> data = kj::heapArray<word>(8192);
    443   WireValue<uint32_t>* table = reinterpret_cast<WireValue<uint32_t>*>(data.begin());
    444   table[0].set(1024);
    445   for (uint i = 0; i < 1024; i++) {
    446     table[i+1].set(1);
    447   }
    448   TestInputStream input(data.asPtr(), false);
    449 
    450   kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
    451     InputStreamMessageReader reader(input);
    452 #if !KJ_NO_EXCEPTIONS
    453     ADD_FAILURE() << "Should have thrown an exception.";
    454 #endif
    455   });
    456 
    457   KJ_EXPECT(e != nullptr, "Should have thrown an exception.");
    458 }
    459 
    460 #if !__MINGW32__  // Inexplicably crashes when exception is thrown from constructor.
    461 TEST(Serialize, RejectHugeMessage) {
    462   // A message whose root struct contains two words of data!
    463   AlignedData<4> data = {{0,0,0,0,3,0,0,0, 0,0,0,0,2,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}};
    464 
    465   TestInputStream input(kj::arrayPtr(data.words, 4), false);
    466 
    467   // We'll set the traversal limit to 2 words so our 3-word message is too big.
    468   ReaderOptions options;
    469   options.traversalLimitInWords = 2;
    470 
    471   kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
    472     InputStreamMessageReader reader(input, options);
    473 #if !KJ_NO_EXCEPTIONS
    474     ADD_FAILURE() << "Should have thrown an exception.";
    475 #endif
    476   });
    477 
    478   KJ_EXPECT(e != nullptr, "Should have thrown an exception.");
    479 }
    480 #endif  // !__MINGW32__
    481 
    482 // TODO(test):  Test error cases.
    483 
    484 }  // namespace
    485 }  // namespace _ (private)
    486 }  // namespace capnp