blob: 7f95e3835e2c29de259a7c0eb542d58b8a1ff29e [file] [log] [blame]
David Reiss35dc7692010-10-06 17:10:19 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19#ifndef _GNU_SOURCE
20#define _GNU_SOURCE // needed for getopt_long
21#endif
22
23#include <stdlib.h>
24#include <time.h>
25#include <unistd.h>
26#include <getopt.h>
27#include <sstream>
28#include <tr1/functional>
29
30#include <boost/mpl/list.hpp>
31#include <boost/shared_array.hpp>
32#include <boost/random.hpp>
33#include <boost/type_traits.hpp>
34#include <boost/test/unit_test.hpp>
35
36#include <transport/TBufferTransports.h>
37#include <transport/TFDTransport.h>
38#include <transport/TFileTransport.h>
David Reisse94fa332010-10-06 17:10:26 +000039#include <transport/TZlibTransport.h>
David Reiss35dc7692010-10-06 17:10:19 +000040
41using namespace apache::thrift::transport;
42
43static boost::mt19937 rng;
44static const char* tmp_dir = "/tmp";
45
46void initrand(const int* seedptr) {
47 unsigned int seed;
48 if (seedptr) {
49 seed = *seedptr;
50 } else {
51 seed = static_cast<unsigned int>(time(NULL));
52 }
53
54 rng.seed(seed);
55}
56
57class SizeGenerator {
58 public:
59 virtual ~SizeGenerator() {}
60 virtual uint32_t nextSize() = 0;
61 virtual std::string describe() const = 0;
62};
63
64class ConstantSizeGenerator : public SizeGenerator {
65 public:
66 ConstantSizeGenerator(uint32_t value) : value_(value) {}
67 uint32_t nextSize() { return value_; }
68 std::string describe() const {
69 std::ostringstream desc;
70 desc << value_;
71 return desc.str();
72 }
73
74 private:
75 uint32_t value_;
76};
77
78class RandomSizeGenerator : public SizeGenerator {
79 public:
80 RandomSizeGenerator(uint32_t min, uint32_t max) :
81 generator_(rng, boost::uniform_int<int>(min, max)) {}
82
83 uint32_t nextSize() { return generator_(); }
84
85 std::string describe() const {
86 std::ostringstream desc;
87 desc << "rand(" << getMin() << ", " << getMax() << ")";
88 return desc.str();
89 }
90
91 uint32_t getMin() const { return generator_.distribution().min(); }
92 uint32_t getMax() const { return generator_.distribution().max(); }
93
94 private:
95 boost::variate_generator< boost::mt19937&, boost::uniform_int<int> >
96 generator_;
97};
98
99/**
100 * This class exists solely to make the TEST_RW() macro easier to use.
101 * - it can be constructed implicitly from an integer
102 * - it can contain either a ConstantSizeGenerator or a RandomSizeGenerator
103 * (TEST_RW can't take a SizeGenerator pointer or reference, since it needs
104 * to make a copy of the generator to bind it to the test function.)
105 */
106class GenericSizeGenerator : public SizeGenerator {
107 public:
108 GenericSizeGenerator(uint32_t value) :
109 generator_(new ConstantSizeGenerator(value)) {}
110 GenericSizeGenerator(uint32_t min, uint32_t max) :
111 generator_(new RandomSizeGenerator(min, max)) {}
112
113 uint32_t nextSize() { return generator_->nextSize(); }
114 std::string describe() const { return generator_->describe(); }
115
116 private:
117 boost::shared_ptr<SizeGenerator> generator_;
118};
119
120/**************************************************************************
121 * Classes to set up coupled transports
122 **************************************************************************/
123
124template <class Transport_>
125class CoupledTransports {
126 public:
127 typedef Transport_ TransportType;
128
129 CoupledTransports() : in(NULL), out(NULL) {}
130
131 Transport_* in;
132 Transport_* out;
133
134 private:
135 CoupledTransports(const CoupledTransports&);
136 CoupledTransports &operator=(const CoupledTransports&);
137};
138
139class CoupledMemoryBuffers : public CoupledTransports<TMemoryBuffer> {
140 public:
141 CoupledMemoryBuffers() {
142 in = &buf;
143 out = &buf;
144 }
145
146 TMemoryBuffer buf;
147};
148
149class CoupledBufferedTransports :
150 public CoupledTransports<TBufferedTransport> {
151 public:
152 CoupledBufferedTransports() :
153 buf(new TMemoryBuffer) {
154 in = new TBufferedTransport(buf);
155 out = new TBufferedTransport(buf);
156 }
157
158 ~CoupledBufferedTransports() {
159 delete in;
160 delete out;
161 }
162
163 boost::shared_ptr<TMemoryBuffer> buf;
164};
165
166class CoupledFramedTransports : public CoupledTransports<TFramedTransport> {
167 public:
168 CoupledFramedTransports() :
169 buf(new TMemoryBuffer) {
170 in = new TFramedTransport(buf);
171 out = new TFramedTransport(buf);
172 }
173
174 ~CoupledFramedTransports() {
175 delete in;
176 delete out;
177 }
178
179 boost::shared_ptr<TMemoryBuffer> buf;
180};
181
David Reisse94fa332010-10-06 17:10:26 +0000182class CoupledZlibTransports : public CoupledTransports<TZlibTransport> {
183 public:
184 CoupledZlibTransports() :
185 buf(new TMemoryBuffer) {
186 in = new TZlibTransport(buf, false);
187 out = new TZlibTransport(buf, false);
188 }
189
190 ~CoupledZlibTransports() {
191 delete in;
192 delete out;
193 }
194
195 boost::shared_ptr<TMemoryBuffer> buf;
196};
197
David Reiss35dc7692010-10-06 17:10:19 +0000198class CoupledFDTransports : public CoupledTransports<TFDTransport> {
199 public:
200 CoupledFDTransports() {
201 int pipes[2];
202
203 if (pipe(pipes) != 0) {
204 return;
205 }
206
207 in = new TFDTransport(pipes[0], TFDTransport::CLOSE_ON_DESTROY);
208 out = new TFDTransport(pipes[1], TFDTransport::CLOSE_ON_DESTROY);
209 }
210
211 ~CoupledFDTransports() {
212 delete in;
213 delete out;
214 }
215};
216
217class CoupledFileTransports : public CoupledTransports<TFileTransport> {
218 public:
219 CoupledFileTransports() {
220 // Create a temporary file to use
221 size_t filename_len = strlen(tmp_dir) + 32;
222 filename = new char[filename_len];
223 snprintf(filename, filename_len,
224 "%s/thrift.transport_test.XXXXXX", tmp_dir);
225 fd = mkstemp(filename);
226 if (fd < 0) {
227 return;
228 }
229
230 in = new TFileTransport(filename, true);
231 out = new TFileTransport(filename);
232 }
233
234 ~CoupledFileTransports() {
235 delete in;
236 delete out;
237
238 if (fd >= 0) {
239 close(fd);
240 unlink(filename);
241 }
242 delete[] filename;
243 }
244
245 char* filename;
246 int fd;
247};
248
249template <class CoupledTransports_>
250class CoupledTTransports : public CoupledTransports<TTransport> {
251 public:
252 CoupledTTransports() : transports() {
253 in = transports.in;
254 out = transports.out;
255 }
256
257 CoupledTransports_ transports;
258};
259
260template <class CoupledTransports_>
261class CoupledBufferBases : public CoupledTransports<TBufferBase> {
262 public:
263 CoupledBufferBases() : transports() {
264 in = transports.in;
265 out = transports.out;
266 }
267
268 CoupledTransports_ transports;
269};
270
271/*
272 * TODO: It would be nice to test TSocket, too.
273 * Unfortunately, TSocket/TServerSocket currently don't provide a low-level
274 * API that would allow us to create a connected socket pair.
275 *
276 * TODO: It would be nice to test TZlibTransport, too.
277 * However, TZlibTransport doesn't conform to quite the same semantics as other
278 * transports. No new data can be written to a TZlibTransport after flush() is
279 * called, since flush() terminates the zlib data stream. In the future maybe
280 * we should make TZlibTransport behave more like the other transports.
281 */
282
283/**************************************************************************
284 * Main testing function
285 **************************************************************************/
286
287/**
288 * Test interleaved write and read calls.
289 *
290 * Generates a buffer totalSize bytes long, then writes it to the transport,
291 * and verifies the written data can be read back correctly.
292 *
293 * Mode of operation:
294 * - call wChunkGenerator to figure out how large of a chunk to write
295 * - call wSizeGenerator to get the size for individual write() calls,
296 * and do this repeatedly until the entire chunk is written.
297 * - call rChunkGenerator to figure out how large of a chunk to read
298 * - call rSizeGenerator to get the size for individual read() calls,
299 * and do this repeatedly until the entire chunk is read.
300 * - repeat until the full buffer is written and read back,
301 * then compare the data read back against the original buffer
302 *
303 *
304 * - If any of the size generators return 0, this means to use the maximum
305 * possible size.
306 *
307 * - If maxOutstanding is non-zero, write chunk sizes will be chosen such that
308 * there are never more than maxOutstanding bytes waiting to be read back.
309 */
310template <class CoupledTransports>
311void test_rw(uint32_t totalSize,
312 SizeGenerator& wSizeGenerator,
313 SizeGenerator& rSizeGenerator,
314 SizeGenerator& wChunkGenerator,
315 SizeGenerator& rChunkGenerator,
316 uint32_t maxOutstanding) {
317 CoupledTransports transports;
318 BOOST_REQUIRE(transports.in != NULL);
319 BOOST_REQUIRE(transports.out != NULL);
320
321 boost::shared_array<uint8_t> wbuf =
322 boost::shared_array<uint8_t>(new uint8_t[totalSize]);
323 boost::shared_array<uint8_t> rbuf =
324 boost::shared_array<uint8_t>(new uint8_t[totalSize]);
325
326 // store some data in wbuf
327 for (uint32_t n = 0; n < totalSize; ++n) {
328 wbuf[n] = (n & 0xff);
329 }
330 // clear rbuf
331 memset(rbuf.get(), 0, totalSize);
332
333 uint32_t total_written = 0;
334 uint32_t total_read = 0;
335 while (total_read < totalSize) {
336 // Determine how large a chunk of data to write
337 uint32_t wchunk_size = wChunkGenerator.nextSize();
338 if (wchunk_size == 0 || wchunk_size > totalSize - total_written) {
339 wchunk_size = totalSize - total_written;
340 }
341
342 // Make sure (total_written - total_read) + wchunk_size
343 // is less than maxOutstanding
344 if (maxOutstanding > 0 &&
345 wchunk_size > maxOutstanding - (total_written - total_read)) {
346 wchunk_size = maxOutstanding - (total_written - total_read);
347 }
348
349 // Write the chunk
350 uint32_t chunk_written = 0;
351 while (chunk_written < wchunk_size) {
352 uint32_t write_size = wSizeGenerator.nextSize();
353 if (write_size == 0 || write_size > wchunk_size - chunk_written) {
354 write_size = wchunk_size - chunk_written;
355 }
356
357 transports.out->write(wbuf.get() + total_written, write_size);
358 chunk_written += write_size;
359 total_written += write_size;
360 }
361
362 // Flush the data, so it will be available in the read transport
363 // Don't flush if wchunk_size is 0. (This should only happen if
364 // total_written == totalSize already, and we're only reading now.)
365 if (wchunk_size > 0) {
366 transports.out->flush();
367 }
368
369 // Determine how large a chunk of data to read back
370 uint32_t rchunk_size = rChunkGenerator.nextSize();
371 if (rchunk_size == 0 || rchunk_size > total_written - total_read) {
372 rchunk_size = total_written - total_read;
373 }
374
375 // Read the chunk
376 uint32_t chunk_read = 0;
377 while (chunk_read < rchunk_size) {
378 uint32_t read_size = rSizeGenerator.nextSize();
379 if (read_size == 0 || read_size > rchunk_size - chunk_read) {
380 read_size = rchunk_size - chunk_read;
381 }
382
David Reisse94fa332010-10-06 17:10:26 +0000383 int bytes_read = -1;
384 try {
385 bytes_read = transports.in->read(rbuf.get() + total_read, read_size);
386 } catch (TTransportException& e) {
387 BOOST_FAIL("read(pos=" << total_read << ", size=" << read_size <<
388 ") threw exception \"" << e.what() <<
389 "\"; written so far: " << total_written << " / " <<
390 totalSize << " bytes");
391 }
392
David Reiss35dc7692010-10-06 17:10:19 +0000393 BOOST_REQUIRE_MESSAGE(bytes_read > 0,
394 "read(pos=" << total_read << ", size=" <<
395 read_size << ") returned " << bytes_read <<
396 "; written so far: " << total_written << " / " <<
397 totalSize << " bytes");
398 chunk_read += bytes_read;
399 total_read += bytes_read;
400 }
401 }
402
403 // make sure the data read back is identical to the data written
404 BOOST_CHECK_EQUAL(memcmp(rbuf.get(), wbuf.get(), totalSize), 0);
405}
406
407/**************************************************************************
408 * Test case generation
409 *
410 * Pretty ugly and annoying. This would be much easier if we the unit test
411 * framework didn't force each test to be a separate function.
412 * - Writing a completely separate function definition for each of these would
413 * result in a lot of repetitive boilerplate code.
414 * - Combining many tests into a single function makes it more difficult to
415 * tell precisely which tests failed. It also means you can't get a progress
416 * update after each test, and the tests are already fairly slow.
417 * - Similar registration could be acheived with BOOST_TEST_CASE_TEMPLATE,
418 * but it requires a lot of awkward MPL code, and results in useless test
419 * case names. (The names are generated from std::type_info::name(), which
420 * is compiler-dependent. gcc returns mangled names.)
421 **************************************************************************/
422
423#define TEST_RW(CoupledTransports, totalSize, ...) \
424 do { \
425 /* Add the test as specified, to test the non-virtual function calls */ \
426 addTest<CoupledTransports>(BOOST_STRINGIZE(CoupledTransports), \
427 totalSize, ## __VA_ARGS__); \
428 /* \
429 * Also test using the transport as a TTransport*, to test \
430 * the read_virt()/write_virt() calls \
431 */ \
432 addTest< CoupledTTransports<CoupledTransports> >( \
433 BOOST_STRINGIZE(CoupledTTransports<CoupledTransports>), \
434 totalSize, ## __VA_ARGS__); \
435 } while (0)
436
437#define TEST_RW_BUF(CoupledTransports, totalSize, ...) \
438 do { \
439 /* Add the standard tests */ \
440 TEST_RW(CoupledTransports, totalSize, ## __VA_ARGS__); \
441 /* Also test using the transport as a TBufferBase* */ \
442 addTest< CoupledBufferBases<CoupledTransports> >( \
443 BOOST_STRINGIZE(CoupledBufferBases<CoupledTransports>), \
444 totalSize, ## __VA_ARGS__); \
445 } while (0)
446
447// We use the same tests for all of the buffered transports
448// This is a helper macro so we don't have to copy-and-paste them.
449#define BUFFER_TESTS(CoupledTransports) \
450 TEST_RW_BUF(CoupledTransports, 1024*1024*30, 0, 0); \
451 TEST_RW_BUF(CoupledTransports, 1024*1024*10, rand4k, rand4k); \
452 TEST_RW_BUF(CoupledTransports, 1024*1024*10, 167, 163); \
453 TEST_RW_BUF(CoupledTransports, 1024*1024, 1, 1); \
454 \
455 TEST_RW_BUF(CoupledTransports, 1024*1024*10, 0, 0, rand4k, rand4k); \
456 TEST_RW_BUF(CoupledTransports, 1024*1024*10, \
457 rand4k, rand4k, rand4k, rand4k); \
458 TEST_RW_BUF(CoupledTransports, 1024*1024*10, 167, 163, rand4k, rand4k); \
459 TEST_RW_BUF(CoupledTransports, 1024*1024*2, 1, 1, rand4k, rand4k);
460
461class TransportTestGen {
462 public:
463 TransportTestGen(boost::unit_test::test_suite* suite) : suite_(suite) {}
464
465 void generate() {
466 GenericSizeGenerator rand4k(1, 4096);
467
468 /*
469 * We do the basically the same set of tests for each transport type,
470 * although we tweak the parameters in some places.
471 */
472
473 // Buffered transport tests
474 BUFFER_TESTS(CoupledMemoryBuffers)
475 BUFFER_TESTS(CoupledBufferedTransports)
476 BUFFER_TESTS(CoupledFramedTransports)
477
David Reisse94fa332010-10-06 17:10:26 +0000478 TEST_RW(CoupledZlibTransports, 1024*1024*10, 0, 0);
479 TEST_RW(CoupledZlibTransports, 1024*1024*10, rand4k, rand4k);
480 TEST_RW(CoupledZlibTransports, 1024*1024*5, 167, 163);
481 TEST_RW(CoupledZlibTransports, 1024*64, 1, 1);
482
483 TEST_RW(CoupledZlibTransports, 1024*1024*10, 0, 0, rand4k, rand4k);
484 TEST_RW(CoupledZlibTransports, 1024*1024*10,
485 rand4k, rand4k, rand4k, rand4k);
486 TEST_RW(CoupledZlibTransports, 1024*1024*5, 167, 163, rand4k, rand4k);
487 TEST_RW(CoupledZlibTransports, 1024*64, 1, 1, rand4k, rand4k);
488
David Reiss35dc7692010-10-06 17:10:19 +0000489 // TFDTransport tests
490 // Since CoupledFDTransports tests with a pipe, writes will block
491 // if there is too much outstanding unread data in the pipe.
492 uint32_t fd_max_outstanding = 4096;
493 TEST_RW(CoupledFDTransports, 1024*1024*30, 0, 0,
494 0, 0, fd_max_outstanding);
495 TEST_RW(CoupledFDTransports, 1024*1024*10, rand4k, rand4k,
496 0, 0, fd_max_outstanding);
497 TEST_RW(CoupledFDTransports, 1024*1024*10, 167, 163,
498 0, 0, fd_max_outstanding);
499 TEST_RW(CoupledFDTransports, 1024*512, 1, 1,
500 0, 0, fd_max_outstanding);
501
502 TEST_RW(CoupledFDTransports, 1024*1024*10, 0, 0,
503 rand4k, rand4k, fd_max_outstanding);
504 TEST_RW(CoupledFDTransports, 1024*1024*10, rand4k, rand4k,
505 rand4k, rand4k, fd_max_outstanding);
506 TEST_RW(CoupledFDTransports, 1024*1024*10, 167, 163,
507 rand4k, rand4k, fd_max_outstanding);
508 TEST_RW(CoupledFDTransports, 1024*512, 1, 1,
509 rand4k, rand4k, fd_max_outstanding);
510
511 // TFileTransport tests
512 // We use smaller buffer sizes here, since TFileTransport is fairly slow.
513 //
514 // TFileTransport can't write more than 16MB at once
515 uint32_t max_write_at_once = 1024*1024*16 - 4;
516 TEST_RW(CoupledFileTransports, 1024*1024*30, max_write_at_once, 0);
517 TEST_RW(CoupledFileTransports, 1024*1024*5, rand4k, rand4k);
518 TEST_RW(CoupledFileTransports, 1024*1024*5, 167, 163);
519 TEST_RW(CoupledFileTransports, 1024*64, 1, 1);
520
521 TEST_RW(CoupledFileTransports, 1024*1024*2, 0, 0, rand4k, rand4k);
522 TEST_RW(CoupledFileTransports, 1024*1024*2,
523 rand4k, rand4k, rand4k, rand4k);
524 TEST_RW(CoupledFileTransports, 1024*1024*2, 167, 163, rand4k, rand4k);
525 TEST_RW(CoupledFileTransports, 1024*64, 1, 1, rand4k, rand4k);
526 }
527
528 private:
529 template <class CoupledTransports>
530 void addTest(const char* transport_name, uint32_t totalSize,
531 GenericSizeGenerator wSizeGen, GenericSizeGenerator rSizeGen,
532 GenericSizeGenerator wChunkSizeGen = 0,
533 GenericSizeGenerator rChunkSizeGen = 0,
534 uint32_t maxOutstanding = 0,
535 uint32_t expectedFailures = 0) {
536 std::ostringstream name;
537 name << transport_name << "::test_rw(" << totalSize << ", " <<
538 wSizeGen.describe() << ", " << rSizeGen.describe() << ", " <<
539 wChunkSizeGen.describe() << ", " << rChunkSizeGen.describe() << ", " <<
540 maxOutstanding << ")";
541
542 boost::unit_test::callback0<> test_func =
543 std::tr1::bind(test_rw<CoupledTransports>, totalSize,
544 wSizeGen, rSizeGen, wChunkSizeGen, rChunkSizeGen,
545 maxOutstanding);
546 boost::unit_test::test_case* tc =
547 boost::unit_test::make_test_case(test_func, name.str());
548 suite_->add(tc, expectedFailures);
549 };
550
551 boost::unit_test::test_suite* suite_;
552};
553
554/**************************************************************************
555 * General Initialization
556 **************************************************************************/
557
558void print_usage(FILE* f, const char* argv0) {
559 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
560 fprintf(f, "Options:\n");
561 fprintf(f, " --seed=<N>, -s <N>\n");
562 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
563 fprintf(f, " --help\n");
564}
565
566void parse_args(int argc, char* argv[]) {
567 int seed;
568 int *seedptr = NULL;
569
570 struct option long_opts[] = {
571 { "help", false, NULL, 'h' },
572 { "seed", true, NULL, 's' },
573 { "tmp-dir", true, NULL, 't' },
574 { NULL, 0, NULL, 0 }
575 };
576
577 while (true) {
578 optopt = 1;
579 int optchar = getopt_long(argc, argv, "hs:t:", long_opts, NULL);
580 if (optchar == -1) {
581 break;
582 }
583
584 switch (optchar) {
585 case 't':
586 tmp_dir = optarg;
587 break;
588 case 's': {
589 char *endptr;
590 seed = strtol(optarg, &endptr, 0);
591 if (endptr == optarg || *endptr != '\0') {
592 fprintf(stderr, "invalid seed value \"%s\": must be an integer\n",
593 optarg);
594 exit(1);
595 }
596 seedptr = &seed;
597 break;
598 }
599 case 'h':
600 print_usage(stdout, argv[0]);
601 exit(0);
602 case '?':
603 exit(1);
604 default:
605 // Only happens if someone adds another option to the optarg string,
606 // but doesn't update the switch statement to handle it.
607 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
608 exit(1);
609 }
610 }
611
612 initrand(seedptr);
613}
614
615boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
616 // Parse arguments
617 parse_args(argc, argv);
618
619 boost::unit_test::test_suite* suite = BOOST_TEST_SUITE("TransportTests");
620 TransportTestGen transport_test_generator(suite);
621 transport_test_generator.generate();
622
623 return suite;
624}