serialize-test.c++ 12.8 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
Kenton Varda's avatar
Kenton Varda committed
3
//
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
Kenton Varda's avatar
Kenton Varda committed
10
//
Kenton Varda's avatar
Kenton Varda committed
11 12
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
Kenton Varda's avatar
Kenton Varda committed
13
//
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18 19 20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
Kenton Varda's avatar
Kenton Varda committed
21 22

#include "serialize.h"
Kenton Varda's avatar
Kenton Varda committed
23
#include <kj/debug.h>
Kenton Varda's avatar
Kenton Varda committed
24 25 26
#include <gtest/gtest.h>
#include <string>
#include <stdlib.h>
27
#include <fcntl.h>
Kenton Varda's avatar
Kenton Varda committed
28 29
#include "test-util.h"

30
namespace capnp {
31
namespace _ {  // private
Kenton Varda's avatar
Kenton Varda committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
namespace {

class TestMessageBuilder: public MallocMessageBuilder {
  // A MessageBuilder that tries to allocate an exact number of total segments, by allocating
  // minimum-size segments until it reaches the number, then allocating one large segment to
  // finish.

public:
  explicit TestMessageBuilder(uint desiredSegmentCount)
      : MallocMessageBuilder(0, AllocationStrategy::FIXED_SIZE),
        desiredSegmentCount(desiredSegmentCount) {}
  ~TestMessageBuilder() {
    EXPECT_EQ(0u, desiredSegmentCount);
  }

47
  kj::ArrayPtr<word> allocateSegment(uint minimumSize) override {
Kenton Varda's avatar
Kenton Varda committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    if (desiredSegmentCount <= 1) {
      if (desiredSegmentCount < 1) {
        ADD_FAILURE() << "Allocated more segments than desired.";
      } else {
        --desiredSegmentCount;
      }
      return MallocMessageBuilder::allocateSegment(SUGGESTED_FIRST_SEGMENT_WORDS);
    } else {
      --desiredSegmentCount;
      return MallocMessageBuilder::allocateSegment(minimumSize);
    }
  }

private:
  uint desiredSegmentCount;
};

TEST(Serialize, FlatArray) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

69
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
70

71 72 73 74 75 76
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

77 78 79 80 81 82 83 84
  {
    MallocMessageBuilder builder2;
    auto remaining = initMessageBuilderFromFlatArrayCopy(serialized, builder2);
    checkTestMessage(builder2.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), remaining.begin());
    EXPECT_EQ(serialized.end(), remaining.end());
  }

85 86 87 88 89 90 91 92
  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
93 94 95 96 97 98 99 100

  {
    MallocMessageBuilder builder2;
    auto remaining = initMessageBuilderFromFlatArrayCopy(serializedWithSuffix, builder2);
    checkTestMessage(builder2.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, remaining.begin());
    EXPECT_EQ(serializedWithSuffix.end(), remaining.end());
  }
Kenton Varda's avatar
Kenton Varda committed
101 102 103 104 105 106
}

TEST(Serialize, FlatArrayOddSegmentCount) {
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

107
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
108

109 110 111 112 113 114 115 116 117 118 119 120 121 122
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
Kenton Varda's avatar
Kenton Varda committed
123 124
}

125
TEST(Serialize, FlatArrayEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
126 127 128
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

129
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
130

131 132 133 134 135 136 137 138 139 140 141 142 143 144
  {
    FlatArrayMessageReader reader(serialized.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serialized.end(), reader.getEnd());
  }

  kj::Array<word> serializedWithSuffix = kj::heapArray<word>(serialized.size() + 5);
  memcpy(serializedWithSuffix.begin(), serialized.begin(), serialized.size() * sizeof(word));

  {
    FlatArrayMessageReader reader(serializedWithSuffix.asPtr());
    checkTestMessage(reader.getRoot<TestAllTypes>());
    EXPECT_EQ(serializedWithSuffix.end() - 5, reader.getEnd());
  }
Kenton Varda's avatar
Kenton Varda committed
145 146
}

147
class TestInputStream: public kj::InputStream {
Kenton Varda's avatar
Kenton Varda committed
148
public:
149
  TestInputStream(kj::ArrayPtr<const word> data, bool lazy)
150 151
      : pos(data.asChars().begin()),
        end(data.asChars().end()),
152
        lazy(lazy) {}
Kenton Varda's avatar
Kenton Varda committed
153 154
  ~TestInputStream() {}

155
  size_t tryRead(void* buffer, size_t minBytes, size_t maxBytes) override {
156
    KJ_ASSERT(maxBytes <= size_t(end - pos), "Overran end of stream.");
157 158 159 160
    size_t amount = lazy ? minBytes : maxBytes;
    memcpy(buffer, pos, amount);
    pos += amount;
    return amount;
Kenton Varda's avatar
Kenton Varda committed
161 162 163 164 165
  }

private:
  const char* pos;
  const char* end;
166
  bool lazy;
Kenton Varda's avatar
Kenton Varda committed
167 168 169 170 171 172
};

TEST(Serialize, InputStream) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

173
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
174

175 176
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
177 178 179 180

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

181
TEST(Serialize, InputStreamScratchSpace) {
Kenton Varda's avatar
Kenton Varda committed
182 183 184
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

185
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
186

187 188
  word scratch[4096];
  TestInputStream stream(serialized.asPtr(), false);
189
  InputStreamMessageReader reader(stream, ReaderOptions(), kj::ArrayPtr<word>(scratch, 4096));
Kenton Varda's avatar
Kenton Varda committed
190 191 192 193

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

194
TEST(Serialize, InputStreamLazy) {
Kenton Varda's avatar
Kenton Varda committed
195 196 197
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

198
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
199

200 201
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
202 203 204 205

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

206
TEST(Serialize, InputStreamOddSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
207 208 209
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

210
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
211

212 213
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
214 215 216 217

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

218
TEST(Serialize, InputStreamOddSegmentCountLazy) {
Kenton Varda's avatar
Kenton Varda committed
219 220 221
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

222
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
223

224 225
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
226 227 228 229

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

230
TEST(Serialize, InputStreamEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
231 232 233
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

234
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
235

236 237
  TestInputStream stream(serialized.asPtr(), false);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
238 239 240 241

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

242
TEST(Serialize, InputStreamEvenSegmentCountLazy) {
Kenton Varda's avatar
Kenton Varda committed
243 244 245
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

246
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
247

248 249
  TestInputStream stream(serialized.asPtr(), true);
  InputStreamMessageReader reader(stream, ReaderOptions());
Kenton Varda's avatar
Kenton Varda committed
250 251 252 253

  checkTestMessage(reader.getRoot<TestAllTypes>());
}

254 255 256 257 258 259 260 261 262 263 264 265 266 267
TEST(Serialize, InputStreamToBuilder) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

  kj::Array<word> serialized = messageToFlatArray(builder);

  TestInputStream stream(serialized.asPtr(), false);

  MallocMessageBuilder builder2;
  readMessageCopy(stream, builder2);

  checkTestMessage(builder2.getRoot<TestAllTypes>());
}

268
class TestOutputStream: public kj::OutputStream {
Kenton Varda's avatar
Kenton Varda committed
269 270 271 272 273 274 275 276
public:
  TestOutputStream() {}
  ~TestOutputStream() {}

  void write(const void* buffer, size_t size) override {
    data.append(reinterpret_cast<const char*>(buffer), size);
  }

277
  const bool dataEquals(kj::ArrayPtr<const word> other) {
Kenton Varda's avatar
Kenton Varda committed
278
    return data ==
279
        std::string(other.asChars().begin(), other.asChars().size());
Kenton Varda's avatar
Kenton Varda committed
280 281 282 283 284 285 286 287 288 289
  }

private:
  std::string data;
};

TEST(Serialize, WriteMessage) {
  TestMessageBuilder builder(1);
  initTestMessage(builder.initRoot<TestAllTypes>());

290
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
291 292 293 294 295 296 297 298 299 300 301

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

TEST(Serialize, WriteMessageOddSegmentCount) {
  TestMessageBuilder builder(7);
  initTestMessage(builder.initRoot<TestAllTypes>());

302
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
303 304 305 306 307 308 309

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

310
TEST(Serialize, WriteMessageEvenSegmentCount) {
Kenton Varda's avatar
Kenton Varda committed
311 312 313
  TestMessageBuilder builder(10);
  initTestMessage(builder.initRoot<TestAllTypes>());

314
  kj::Array<word> serialized = messageToFlatArray(builder);
Kenton Varda's avatar
Kenton Varda committed
315 316 317 318 319 320 321

  TestOutputStream output;
  writeMessage(output, builder);

  EXPECT_TRUE(output.dataEquals(serialized.asPtr()));
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
#if _WIN32
int mkstemp(char *tpl) {
  char* end = tpl + strlen(tpl);
  while (end > tpl && *(end-1) == 'X') --end;

  for (;;) {
    KJ_ASSERT(_mktemp(tpl) == tpl);

    int fd = open(tpl, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY | O_BINARY, 0700);
    if (fd >= 0) {
      return fd;
    }

    int error = errno;
    if (error != EEXIST && error != EINTR) {
337
      KJ_FAIL_SYSCALL("open(mktemp())", error, tpl);
338 339 340 341 342 343 344
    }

    memset(end, 'X', strlen(end));
  }
}
#endif

Kenton Varda's avatar
Kenton Varda committed
345
TEST(Serialize, FileDescriptors) {
Kenton Varda's avatar
Kenton Varda committed
346
#if _WIN32 || __ANDROID__
347 348 349
  // TODO(cleanup): Find the Windows temp directory? Seems overly difficult.
  char filename[] = "capnproto-serialize-test-XXXXXX";
#else
Kenton Varda's avatar
Kenton Varda committed
350
  char filename[] = "/tmp/capnproto-serialize-test-XXXXXX";
351
#endif
352
  kj::AutoCloseFd tmpfile(mkstemp(filename));
Kenton Varda's avatar
Kenton Varda committed
353 354
  ASSERT_GE(tmpfile.get(), 0);

355
#if !_WIN32
Kenton Varda's avatar
Kenton Varda committed
356
  // Unlink the file so that it will be deleted on close.
357
  // (For win32, we already handled this is mkstemp().)
Kenton Varda's avatar
Kenton Varda committed
358
  EXPECT_EQ(0, unlink(filename));
359
#endif
Kenton Varda's avatar
Kenton Varda committed
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

  {
    TestMessageBuilder builder(7);
    initTestMessage(builder.initRoot<TestAllTypes>());
    writeMessageToFd(tmpfile.get(), builder);
  }

  {
    TestMessageBuilder builder(1);
    builder.initRoot<TestAllTypes>().setTextField("second message in file");
    writeMessageToFd(tmpfile.get(), builder);
  }

  lseek(tmpfile, 0, SEEK_SET);

  {
    StreamFdMessageReader reader(tmpfile.get());
    checkTestMessage(reader.getRoot<TestAllTypes>());
  }

  {
    StreamFdMessageReader reader(tmpfile.get());
    EXPECT_EQ("second message in file", reader.getRoot<TestAllTypes>().getTextField());
  }
}

386
TEST(Serialize, RejectTooManySegments) {
Kenton Varda's avatar
Kenton Varda committed
387
  kj::Array<word> data = kj::heapArray<word>(8192);
388 389 390 391 392 393 394
  WireValue<uint32_t>* table = reinterpret_cast<WireValue<uint32_t>*>(data.begin());
  table[0].set(1024);
  for (uint i = 0; i < 1024; i++) {
    table[i+1].set(1);
  }
  TestInputStream input(data.asPtr(), false);

395
  kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
396
    InputStreamMessageReader reader(input);
397
#if !KJ_NO_EXCEPTIONS
398
    ADD_FAILURE() << "Should have thrown an exception.";
399 400 401 402
#endif
  });

  EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
403 404
}

405
#if !__MINGW32__  // Inexplicably crashes when exception is thrown from constructor.
406 407 408 409
TEST(Serialize, RejectHugeMessage) {
  // A message whose root struct contains two words of data!
  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}};

410
  TestInputStream input(kj::arrayPtr(data.words, 4), false);
411 412 413 414 415

  // We'll set the traversal limit to 2 words so our 3-word message is too big.
  ReaderOptions options;
  options.traversalLimitInWords = 2;

416
  kj::Maybe<kj::Exception> e = kj::runCatchingExceptions([&]() {
417
    InputStreamMessageReader reader(input, options);
418
#if !KJ_NO_EXCEPTIONS
419
    ADD_FAILURE() << "Should have thrown an exception.";
420 421 422 423
#endif
  });

  EXPECT_TRUE(e != nullptr) << "Should have thrown an exception.";
424
}
425
#endif  // !__MINGW32__
426

427
// TODO(test):  Test error cases.
Kenton Varda's avatar
Kenton Varda committed
428 429

}  // namespace
430
}  // namespace _ (private)
431
}  // namespace capnp