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