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