blob: e2403d7ec77dc5177517c05e387a4e0dcfe3a349 [file] [log] [blame]
David Reissfaebedd2007-09-17 23:20:38 +00001/*
David Reissea2cba82009-03-30 21:35:00 +00002 * 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
David Reiss3cc9dab2010-10-06 17:10:21 +000020#define __STDC_LIMIT_MACROS
21#define __STDC_FORMAT_MACROS
22
David Reiss9a961e72010-10-06 17:10:23 +000023#ifndef _GNU_SOURCE
24#define _GNU_SOURCE // needed for getopt_long
25#endif
26
David Reiss3cc9dab2010-10-06 17:10:21 +000027#include <stdint.h>
28#include <inttypes.h>
David Reiss9a961e72010-10-06 17:10:23 +000029#include <getopt.h>
David Reissfaebedd2007-09-17 23:20:38 +000030#include <cstddef>
David Reissfaebedd2007-09-17 23:20:38 +000031#include <fstream>
32#include <iostream>
David Reiss9a961e72010-10-06 17:10:23 +000033#include <tr1/functional>
David Reiss3cc9dab2010-10-06 17:10:21 +000034
35#include <boost/random.hpp>
David Reiss9a961e72010-10-06 17:10:23 +000036#include <boost/shared_array.hpp>
37#include <boost/test/unit_test.hpp>
David Reiss3cc9dab2010-10-06 17:10:21 +000038
David Reiss28f298d2008-05-01 06:17:36 +000039#include <transport/TBufferTransports.h>
David Reissfaebedd2007-09-17 23:20:38 +000040#include <transport/TZlibTransport.h>
41
David Reiss9a961e72010-10-06 17:10:23 +000042using namespace std;
43using namespace boost;
44using namespace apache::thrift::transport;
45
David Reissf2abcf92010-10-06 17:10:24 +000046boost::mt19937 rng;
David Reissfaebedd2007-09-17 23:20:38 +000047
David Reissf2abcf92010-10-06 17:10:24 +000048/*
49 * Utility code
50 */
David Reissfaebedd2007-09-17 23:20:38 +000051
David Reissf2abcf92010-10-06 17:10:24 +000052class SizeGenerator {
53 public:
54 virtual ~SizeGenerator() {}
55 virtual unsigned int getSize() = 0;
David Reissfaebedd2007-09-17 23:20:38 +000056};
57
David Reissf2abcf92010-10-06 17:10:24 +000058class ConstantSizeGenerator : public SizeGenerator {
59 public:
60 ConstantSizeGenerator(unsigned int value) : value_(value) {}
61 virtual unsigned int getSize() {
62 return value_;
63 }
64
65 private:
66 unsigned int value_;
67};
68
69class LogNormalSizeGenerator : public SizeGenerator {
70 public:
71 LogNormalSizeGenerator(double mean, double std_dev) :
72 gen_(rng, lognormal_distribution<double>(mean, std_dev)) {}
73
74 virtual unsigned int getSize() {
75 // Loop until we get a size of 1 or more
76 while (true) {
77 unsigned int value = static_cast<unsigned int>(gen_());
78 if (value >= 1) {
79 return value;
80 }
81 }
82 }
83
84 private:
85 variate_generator< mt19937, lognormal_distribution<double> > gen_;
86};
David Reiss3cc9dab2010-10-06 17:10:21 +000087
88uint8_t* gen_uniform_buffer(uint32_t buf_len, uint8_t c) {
89 uint8_t* buf = new uint8_t[buf_len];
90 memset(buf, c, buf_len);
91 return buf;
92}
93
94uint8_t* gen_compressible_buffer(uint32_t buf_len) {
95 uint8_t* buf = new uint8_t[buf_len];
96
97 // Generate small runs of alternately increasing and decreasing bytes
98 boost::uniform_smallint<uint32_t> run_length_distribution(1, 64);
99 boost::uniform_smallint<uint8_t> byte_distribution(0, UINT8_MAX);
100 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint8_t> >
101 byte_generator(rng, byte_distribution);
102 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint32_t> >
103 run_len_generator(rng, run_length_distribution);
104
105 uint32_t idx = 0;
106 int8_t step = 1;
107 while (idx < buf_len) {
108 uint32_t run_length = run_len_generator();
109 if (idx + run_length > buf_len) {
110 run_length = buf_len - idx;
111 }
112
113 uint8_t byte = byte_generator();
114 for (uint32_t n = 0; n < run_length; ++n) {
115 buf[idx] = byte;
116 ++idx;
117 byte += step;
118 }
119
120 step *= -1;
121 }
122
123 return buf;
124}
125
126uint8_t* gen_random_buffer(uint32_t buf_len) {
127 uint8_t* buf = new uint8_t[buf_len];
128
129 boost::uniform_smallint<uint8_t> distribution(0, UINT8_MAX);
130 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint8_t> >
131 generator(rng, distribution);
132
133 for (uint32_t n = 0; n < buf_len; ++n) {
134 buf[n] = generator();
135 }
136
137 return buf;
138}
David Reissfaebedd2007-09-17 23:20:38 +0000139
David Reissf2abcf92010-10-06 17:10:24 +0000140/*
141 * Test functions
142 */
143
David Reiss9a961e72010-10-06 17:10:23 +0000144void test_write_then_read(const uint8_t* buf, uint32_t buf_len) {
145 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
146 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
147 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000148 zlib_trans->finish();
David Reissfaebedd2007-09-17 23:20:38 +0000149
David Reiss9a961e72010-10-06 17:10:23 +0000150 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
151 uint32_t got = zlib_trans->read(mirror.get(), buf_len);
152 BOOST_REQUIRE_EQUAL(got, buf_len);
153 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
154 zlib_trans->verifyChecksum();
155}
David Reissfaebedd2007-09-17 23:20:38 +0000156
David Reiss9a961e72010-10-06 17:10:23 +0000157void test_separate_checksum(const uint8_t* buf, uint32_t buf_len) {
158 // This one is tricky. I separate the last byte of the stream out
159 // into a separate crbuf_. The last byte is part of the checksum,
160 // so the entire read goes fine, but when I go to verify the checksum
161 // it isn't there. The original implementation complained that
162 // the stream was not complete. I'm about to go fix that.
163 // It worked. Awesome.
164 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
165 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
166 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000167 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000168 string tmp_buf;
169 membuf->appendBufferToString(tmp_buf);
170 zlib_trans.reset(new TZlibTransport(membuf, false,
171 TZlibTransport::DEFAULT_URBUF_SIZE,
172 tmp_buf.length()-1));
David Reissfaebedd2007-09-17 23:20:38 +0000173
David Reiss9a961e72010-10-06 17:10:23 +0000174 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
175 uint32_t got = zlib_trans->read(mirror.get(), buf_len);
176 BOOST_REQUIRE_EQUAL(got, buf_len);
177 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
178 zlib_trans->verifyChecksum();
179}
David Reissfaebedd2007-09-17 23:20:38 +0000180
David Reiss9a961e72010-10-06 17:10:23 +0000181void test_incomplete_checksum(const uint8_t* buf, uint32_t buf_len) {
182 // Make sure we still get that "not complete" error if
183 // it really isn't complete.
184 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
185 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
186 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000187 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000188 string tmp_buf;
189 membuf->appendBufferToString(tmp_buf);
190 tmp_buf.erase(tmp_buf.length() - 1);
191 membuf->resetBuffer(const_cast<uint8_t*>(
192 reinterpret_cast<const uint8_t*>(tmp_buf.data())),
193 tmp_buf.length());
194
195 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
196 uint32_t got = zlib_trans->read(mirror.get(), buf_len);
197 BOOST_REQUIRE_EQUAL(got, buf_len);
198 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
199 try {
200 zlib_trans->verifyChecksum();
201 BOOST_ERROR("verifyChecksum() did not report an error");
202 } catch (TTransportException& ex) {
203 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::CORRUPTED_DATA);
204 }
205}
206
207void test_read_write_mix(const uint8_t* buf, uint32_t buf_len,
David Reissf2abcf92010-10-06 17:10:24 +0000208 const shared_ptr<SizeGenerator>& write_gen,
209 const shared_ptr<SizeGenerator>& read_gen) {
David Reiss9a961e72010-10-06 17:10:23 +0000210 // Try it with a mix of read/write sizes.
211 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
212 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
David Reiss9a961e72010-10-06 17:10:23 +0000213 unsigned int tot;
214
David Reiss9a961e72010-10-06 17:10:23 +0000215 tot = 0;
216 while (tot < buf_len) {
David Reissf2abcf92010-10-06 17:10:24 +0000217 uint32_t write_len = write_gen->getSize();
David Reiss9a961e72010-10-06 17:10:23 +0000218 if (tot + write_len > buf_len) {
219 write_len = buf_len - tot;
David Reissfaebedd2007-09-17 23:20:38 +0000220 }
David Reiss9a961e72010-10-06 17:10:23 +0000221 zlib_trans->write(buf + tot, write_len);
222 tot += write_len;
David Reiss9a961e72010-10-06 17:10:23 +0000223 }
David Reissfaebedd2007-09-17 23:20:38 +0000224
David Reisse94fa332010-10-06 17:10:26 +0000225 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000226
David Reiss9a961e72010-10-06 17:10:23 +0000227 tot = 0;
228 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
229 while (tot < buf_len) {
David Reissf2abcf92010-10-06 17:10:24 +0000230 uint32_t read_len = read_gen->getSize();
David Reiss9a961e72010-10-06 17:10:23 +0000231 uint32_t expected_read_len = read_len;
232 if (tot + read_len > buf_len) {
233 expected_read_len = buf_len - tot;
David Reissfaebedd2007-09-17 23:20:38 +0000234 }
David Reiss9a961e72010-10-06 17:10:23 +0000235 uint32_t got = zlib_trans->read(mirror.get() + tot, read_len);
236 BOOST_REQUIRE_EQUAL(got, expected_read_len);
237 tot += got;
David Reiss9a961e72010-10-06 17:10:23 +0000238 }
David Reissfaebedd2007-09-17 23:20:38 +0000239
David Reiss9a961e72010-10-06 17:10:23 +0000240 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
241 zlib_trans->verifyChecksum();
242}
David Reissfaebedd2007-09-17 23:20:38 +0000243
David Reiss9a961e72010-10-06 17:10:23 +0000244void test_invalid_checksum(const uint8_t* buf, uint32_t buf_len) {
245 // Verify checksum checking.
246 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
247 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
248 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000249 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000250 string tmp_buf;
251 membuf->appendBufferToString(tmp_buf);
252 // Modify a byte at the end of the buffer (part of the checksum).
253 // On rare occasions, modifying a byte in the middle of the buffer
254 // isn't caught by the checksum.
255 //
256 // (This happens especially often for the uniform buffer. The
257 // re-inflated data is correct, however. I suspect in this case that
258 // we're more likely to modify bytes that are part of zlib metadata
259 // instead of the actual compressed data.)
260 //
261 // I've also seen some failure scenarios where a checksum failure isn't
262 // reported, but zlib keeps trying to decode past the end of the data.
263 // (When this occurs, verifyChecksum() throws an exception indicating
264 // that the end of the data hasn't been reached.) I haven't seen this
265 // error when only modifying checksum bytes.
266 int index = tmp_buf.size() - 1;
267 tmp_buf[index]++;
268 membuf->resetBuffer(const_cast<uint8_t*>(
269 reinterpret_cast<const uint8_t*>(tmp_buf.data())),
270 tmp_buf.length());
David Reissfaebedd2007-09-17 23:20:38 +0000271
David Reiss9a961e72010-10-06 17:10:23 +0000272 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
273 try {
274 zlib_trans->read(mirror.get(), buf_len);
275 zlib_trans->verifyChecksum();
276 BOOST_ERROR("verifyChecksum() did not report an error");
277 } catch (TZlibTransportException& ex) {
278 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::INTERNAL_ERROR);
279 }
280}
David Reissfaebedd2007-09-17 23:20:38 +0000281
David Reisse94fa332010-10-06 17:10:26 +0000282void test_write_after_flush(const uint8_t* buf, uint32_t buf_len) {
283 // write some data
284 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
285 shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf, false));
286 zlib_trans->write(buf, buf_len);
287
288 // call finish()
289 zlib_trans->finish();
290
291 // make sure write() throws an error
292 try {
293 uint8_t write_buf[] = "a";
294 zlib_trans->write(write_buf, 1);
295 BOOST_ERROR("write() after finish() did not raise an exception");
296 } catch (TTransportException& ex) {
297 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
298 }
299
300 // make sure flush() throws an error
301 try {
302 zlib_trans->flush();
303 BOOST_ERROR("flush() after finish() did not raise an exception");
304 } catch (TTransportException& ex) {
305 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
306 }
307
308 // make sure finish() throws an error
309 try {
310 zlib_trans->finish();
311 BOOST_ERROR("finish() after finish() did not raise an exception");
312 } catch (TTransportException& ex) {
313 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
314 }
315}
316
317void test_no_write() {
318 // Verify that no data is written to the underlying transport if we
319 // never write data to the TZlibTransport.
320 shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
321 {
322 // Create a TZlibTransport object, and immediately destroy it
323 // when it goes out of scope.
324 TZlibTransport w_zlib_trans(membuf, false);
325 }
326
327 BOOST_CHECK_EQUAL(membuf->available_read(), 0);
328}
329
David Reissf2abcf92010-10-06 17:10:24 +0000330/*
331 * Initialization
332 */
333
David Reiss9a961e72010-10-06 17:10:23 +0000334#define ADD_TEST_CASE(suite, name, function, ...) \
335 do { \
336 ::std::ostringstream name_ss; \
337 name_ss << name << "-" << BOOST_STRINGIZE(function); \
338 ::boost::unit_test::test_case* tc = ::boost::unit_test::make_test_case( \
339 ::std::tr1::bind(function, ## __VA_ARGS__), \
340 name_ss.str()); \
341 (suite)->add(tc); \
342 } while (0)
David Reissfaebedd2007-09-17 23:20:38 +0000343
David Reiss9a961e72010-10-06 17:10:23 +0000344void add_tests(unit_test::test_suite* suite,
345 const uint8_t* buf,
346 uint32_t buf_len,
347 const char* name) {
348 ADD_TEST_CASE(suite, name, test_write_then_read, buf, buf_len);
349 ADD_TEST_CASE(suite, name, test_separate_checksum, buf, buf_len);
350 ADD_TEST_CASE(suite, name, test_incomplete_checksum, buf, buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000351 ADD_TEST_CASE(suite, name, test_invalid_checksum, buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000352 ADD_TEST_CASE(suite, name, test_write_after_flush, buf, buf_len);
David Reissf2abcf92010-10-06 17:10:24 +0000353
354 shared_ptr<SizeGenerator> size_32k(new ConstantSizeGenerator(1<<15));
355 shared_ptr<SizeGenerator> size_lognormal(new LogNormalSizeGenerator(20, 30));
356 ADD_TEST_CASE(suite, name << "-constant",
357 test_read_write_mix, buf, buf_len,
358 size_32k, size_32k);
359 ADD_TEST_CASE(suite, name << "-lognormal-write",
360 test_read_write_mix, buf, buf_len,
361 size_lognormal, size_32k);
362 ADD_TEST_CASE(suite, name << "-lognormal-read",
363 test_read_write_mix, buf, buf_len,
364 size_32k, size_lognormal);
365 ADD_TEST_CASE(suite, name << "-lognormal-both",
366 test_read_write_mix, buf, buf_len,
367 size_lognormal, size_lognormal);
368
369 // Test with a random size distribution,
370 // but use the exact same distribution for reading as for writing.
371 //
372 // Because the SizeGenerator makes a copy of the random number generator,
373 // both SizeGenerators should return the exact same set of values, since they
374 // both start with random number generators in the same state.
375 shared_ptr<SizeGenerator> write_size_gen(new LogNormalSizeGenerator(20, 30));
376 shared_ptr<SizeGenerator> read_size_gen(new LogNormalSizeGenerator(20, 30));
377 ADD_TEST_CASE(suite, name << "-lognormal-same-distribution",
378 test_read_write_mix, buf, buf_len,
379 write_size_gen, read_size_gen);
David Reiss9a961e72010-10-06 17:10:23 +0000380}
381
382void print_usage(FILE* f, const char* argv0) {
383 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
384 fprintf(f, "Options:\n");
385 fprintf(f, " --seed=<N>, -s <N>\n");
386 fprintf(f, " --help\n");
387}
388
389void parse_args(int argc, char* argv[]) {
390 uint32_t seed = 0;
391 bool has_seed = false;
392
393 struct option long_opts[] = {
394 { "help", false, NULL, 'h' },
395 { "seed", true, NULL, 's' },
396 { NULL, 0, NULL, 0 }
397 };
398
399 while (true) {
400 optopt = 1;
401 int optchar = getopt_long(argc, argv, "hs:", long_opts, NULL);
402 if (optchar == -1) {
403 break;
404 }
405
406 switch (optchar) {
407 case 's': {
408 char *endptr;
409 seed = strtol(optarg, &endptr, 0);
410 if (endptr == optarg || *endptr != '\0') {
411 fprintf(stderr, "invalid seed value \"%s\": must be a positive "
412 "integer\n", optarg);
413 exit(1);
414 }
415 has_seed = true;
416 break;
417 }
418 case 'h':
419 print_usage(stdout, argv[0]);
420 exit(0);
421 case '?':
422 exit(1);
423 default:
424 // Only happens if someone adds another option to the optarg string,
425 // but doesn't update the switch statement to handle it.
426 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
427 exit(1);
428 }
429 }
430
431 if (!has_seed) {
432 seed = time(NULL);
433 }
434
435 printf("seed: %" PRIu32 "\n", seed);
436 rng.seed(seed);
437}
438
439unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
440 parse_args(argc, argv);
441
442 unit_test::test_suite* suite = BOOST_TEST_SUITE("ZlibTests");
443
444 uint32_t buf_len = 1024*32;
445 add_tests(suite, gen_uniform_buffer(buf_len, 'a'), buf_len, "uniform");
446 add_tests(suite, gen_compressible_buffer(buf_len), buf_len, "compressible");
447 add_tests(suite, gen_random_buffer(buf_len), buf_len, "random");
448
David Reisse94fa332010-10-06 17:10:26 +0000449 suite->add(BOOST_TEST_CASE(test_no_write));
450
David Reiss9a961e72010-10-06 17:10:23 +0000451 return suite;
David Reissfaebedd2007-09-17 23:20:38 +0000452}