blob: fade0959c559d84ba69dcf56f0666f83e0e402c7 [file] [log] [blame]
David Reiss709b69f2010-10-06 17:10:30 +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 <sys/time.h>
24#include <getopt.h>
25#include <boost/test/unit_test.hpp>
26
27#include <transport/TFileTransport.h>
28
29using namespace apache::thrift::transport;
30
31/**************************************************************************
32 * Global state
33 **************************************************************************/
34
35static const char* tmp_dir = "/tmp";
36
37class FsyncLog;
38FsyncLog* fsync_log;
39
40
41/**************************************************************************
42 * Helper code
43 **************************************************************************/
44
45// Provide BOOST_CHECK_LT() and BOOST_CHECK_GT(), in case we're compiled
46// with an older version of boost
47#ifndef BOOST_CHECK_LT
David Reiss4f9efdb2010-10-06 17:10:33 +000048#define BOOST_CHECK_CMP(a, b, op, check_fn) \
49 check_fn((a) op (b), \
50 "check " BOOST_STRINGIZE(a) " " BOOST_STRINGIZE(op) " " \
51 BOOST_STRINGIZE(b) " failed: " BOOST_STRINGIZE(a) "=" << (a) << \
52 " " BOOST_STRINGIZE(b) "=" << (b))
David Reiss709b69f2010-10-06 17:10:30 +000053
David Reiss4f9efdb2010-10-06 17:10:33 +000054#define BOOST_CHECK_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_CHECK_MESSAGE)
55#define BOOST_CHECK_GT(a, b) BOOST_CHECK_CMP(a, b, >, BOOST_CHECK_MESSAGE)
56#define BOOST_REQUIRE_LT(a, b) BOOST_CHECK_CMP(a, b, <, BOOST_REQUIRE_MESSAGE)
David Reiss709b69f2010-10-06 17:10:30 +000057#endif // BOOST_CHECK_LT
58
59/**
60 * Class to record calls to fsync
61 */
62class FsyncLog {
63 public:
64 struct FsyncCall {
65 struct timeval time;
66 int fd;
67 };
68 typedef std::list<FsyncCall> CallList;
69
70 FsyncLog() {}
71
72 void fsync(int fd) {
73 FsyncCall call;
74 gettimeofday(&call.time, NULL);
75 calls_.push_back(call);
76 }
77
78 const CallList* getCalls() const {
79 return &calls_;
80 }
81
82 private:
83 CallList calls_;
84};
85
86/**
87 * Helper class to clean up temporary files
88 */
89class TempFile {
90 public:
91 TempFile(const char* directory, const char* prefix) {
92 size_t path_len = strlen(directory) + strlen(prefix) + 8;
93 path_ = new char[path_len];
94 snprintf(path_, path_len, "%s/%sXXXXXX", directory, prefix);
95
96 fd_ = mkstemp(path_);
97 if (fd_ < 0) {
98 throw apache::thrift::TException("mkstemp() failed");
99 }
100 }
101
102 ~TempFile() {
103 unlink();
104 close();
105 }
106
107 const char* getPath() const {
108 return path_;
109 }
110
111 int getFD() const {
112 return fd_;
113 }
114
115 void unlink() {
116 if (path_) {
117 ::unlink(path_);
118 delete[] path_;
119 path_ = NULL;
120 }
121 }
122
123 void close() {
124 if (fd_ < 0) {
125 return;
126 }
127
128 ::close(fd_);
129 fd_ = -1;
130 }
131
132 private:
133 char* path_;
134 int fd_;
135};
136
137// Use our own version of fsync() for testing.
138// This returns immediately, so timing in test_destructor() isn't affected by
139// waiting on the actual filesystem.
140extern "C"
141int fsync(int fd) {
142 if (fsync_log) {
143 fsync_log->fsync(fd);
144 }
145 return 0;
146}
147
David Reiss709b69f2010-10-06 17:10:30 +0000148int time_diff(const struct timeval* t1, const struct timeval* t2) {
149 return (t2->tv_usec - t1->tv_usec) + (t2->tv_sec - t1->tv_sec) * 1000000;
150}
151
152
153/**************************************************************************
154 * Test cases
155 **************************************************************************/
156
157/**
158 * Make sure the TFileTransport destructor exits "quickly".
159 *
160 * Previous versions had a bug causing the writer thread not to exit
161 * right away.
162 *
163 * It's kind of lame that we just check to see how long the destructor takes in
164 * wall-clock time. This could result in false failures on slower systems, or
165 * on heavily loaded machines.
166 */
David Reiss109693c2010-10-06 17:10:42 +0000167BOOST_AUTO_TEST_CASE(test_destructor) {
David Reiss709b69f2010-10-06 17:10:30 +0000168 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
169
170 unsigned int const NUM_ITERATIONS = 1000;
David Reiss709b69f2010-10-06 17:10:30 +0000171
Roger Meier085a3e72010-10-08 21:23:35 +0000172 unsigned int num_over = 0;
David Reiss709b69f2010-10-06 17:10:30 +0000173 for (unsigned int n = 0; n < NUM_ITERATIONS; ++n) {
174 ftruncate(f.getFD(), 0);
175
176 TFileTransport* transport = new TFileTransport(f.getPath());
177
178 // write something so that the writer thread gets started
179 transport->write(reinterpret_cast<const uint8_t*>("foo"), 3);
180
181 // Every other iteration, also call flush(), just in case that potentially
182 // has any effect on how the writer thread wakes up.
183 if (n & 0x1) {
184 transport->flush();
185 }
186
187 /*
188 * Time the call to the destructor
189 */
190 struct timeval start;
191 struct timeval end;
192
193 gettimeofday(&start, NULL);
194 delete transport;
195 gettimeofday(&end, NULL);
196
197 int delta = time_diff(&start, &end);
David Reiss41993772010-10-06 17:10:31 +0000198
199 // If any attempt takes more than 100ms, treat that as a failure.
200 // Treat this as a fatal failure, so we'll return now instead of
201 // looping over a very slow operation.
202 BOOST_REQUIRE_LT(delta, 100000);
203
Roger Meier085a3e72010-10-08 21:23:35 +0000204 // Normally, it takes less than 1000us on my dev box.
David Reiss41993772010-10-06 17:10:31 +0000205 // However, if the box is heavily loaded, some of the test runs
206 // take longer, since we're just waiting for our turn on the CPU.
Roger Meier085a3e72010-10-08 21:23:35 +0000207 if (delta > 1000) {
208 ++num_over;
David Reiss41993772010-10-06 17:10:31 +0000209 }
David Reiss709b69f2010-10-06 17:10:30 +0000210 }
David Reiss41993772010-10-06 17:10:31 +0000211
Roger Meier085a3e72010-10-08 21:23:35 +0000212 // Make sure fewer than 10% of the runs took longer than 1000us
213 BOOST_CHECK(num_over < (NUM_ITERATIONS / 10));
David Reiss709b69f2010-10-06 17:10:30 +0000214}
215
216/**
217 * Make sure setFlushMaxUs() is honored.
218 */
219void test_flush_max_us_impl(uint32_t flush_us, uint32_t write_us,
220 uint32_t test_us) {
221 // TFileTransport only calls fsync() if data has been written,
222 // so make sure the write interval is smaller than the flush interval.
223 BOOST_REQUIRE(write_us < flush_us);
224
225 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
226
227 // Record calls to fsync()
228 FsyncLog log;
229 fsync_log = &log;
230
231 TFileTransport* transport = new TFileTransport(f.getPath());
232 // Don't flush because of # of bytes written
233 transport->setFlushMaxBytes(0xffffffff);
234 uint8_t buf[] = "a";
235 uint32_t buflen = sizeof(buf);
236
David Reiss41993772010-10-06 17:10:31 +0000237 // Set the flush interval
David Reiss709b69f2010-10-06 17:10:30 +0000238 transport->setFlushMaxUs(flush_us);
239
David Reiss41993772010-10-06 17:10:31 +0000240 // Make one call to write, to start the writer thread now.
241 // (If we just let the thread get created during our test loop,
242 // the thread creation sometimes takes long enough to make the first
243 // fsync interval fail the check.)
244 transport->write(buf, buflen);
245
David Reiss709b69f2010-10-06 17:10:30 +0000246 // Add one entry to the fsync log, just to mark the start time
247 log.fsync(-1);
248
249 // Loop doing write(), sleep(), ...
250 uint32_t total_time = 0;
251 while (true) {
252 transport->write(buf, buflen);
253 if (total_time > test_us) {
254 break;
255 }
256 usleep(write_us);
257 total_time += write_us;
258 }
259
260 delete transport;
261
262 // Stop logging new fsync() calls
263 fsync_log = NULL;
264
265 // Examine the fsync() log
266 //
267 // TFileTransport uses pthread_cond_timedwait(), which only has millisecond
268 // resolution. In my testing, it normally wakes up about 1 millisecond late.
269 // However, sometimes it takes a bit longer. Allow 5ms leeway.
270 int max_allowed_delta = flush_us + 5000;
271
272 const FsyncLog::CallList* calls = log.getCalls();
273 // We added 1 fsync call above.
274 // Make sure TFileTransport called fsync at least once
275 BOOST_CHECK_GT(calls->size(), 1);
276
277 const struct timeval* prev_time = NULL;
278 for (FsyncLog::CallList::const_iterator it = calls->begin();
279 it != calls->end();
280 ++it) {
281 if (prev_time) {
282 int delta = time_diff(prev_time, &it->time);
283 BOOST_CHECK_LT(delta, max_allowed_delta);
284 }
285 prev_time = &it->time;
286 }
287}
288
David Reiss109693c2010-10-06 17:10:42 +0000289BOOST_AUTO_TEST_CASE(test_flush_max_us1) {
David Reiss709b69f2010-10-06 17:10:30 +0000290 // fsync every 10ms, write every 5ms, for 500ms
291 test_flush_max_us_impl(10000, 5000, 500000);
292}
293
David Reiss109693c2010-10-06 17:10:42 +0000294BOOST_AUTO_TEST_CASE(test_flush_max_us2) {
David Reiss709b69f2010-10-06 17:10:30 +0000295 // fsync every 50ms, write every 20ms, for 500ms
296 test_flush_max_us_impl(50000, 20000, 500000);
297}
298
David Reiss109693c2010-10-06 17:10:42 +0000299BOOST_AUTO_TEST_CASE(test_flush_max_us3) {
David Reiss709b69f2010-10-06 17:10:30 +0000300 // fsync every 400ms, write every 300ms, for 1s
301 test_flush_max_us_impl(400000, 300000, 1000000);
302}
303
David Reiss4f9efdb2010-10-06 17:10:33 +0000304/**
305 * Make sure flush() is fast when there is nothing to do.
306 *
307 * TFileTransport used to have a bug where flush() would wait for the fsync
308 * timeout to expire.
309 */
David Reiss109693c2010-10-06 17:10:42 +0000310BOOST_AUTO_TEST_CASE(test_noop_flush) {
David Reiss4f9efdb2010-10-06 17:10:33 +0000311 TempFile f(tmp_dir, "thrift.TFileTransportTest.");
312 TFileTransport transport(f.getPath());
313
314 // Write something to start the writer thread.
315 uint8_t buf[] = "a";
316 transport.write(buf, 1);
317
318 struct timeval start;
319 gettimeofday(&start, NULL);
320
321 for (unsigned int n = 0; n < 10; ++n) {
322 transport.flush();
323
324 struct timeval now;
325 gettimeofday(&now, NULL);
326
327 // Fail if at any point we've been running for longer than half a second.
328 // (With the buggy code, TFileTransport used to take 3 seconds per flush())
329 //
330 // Use a fatal fail so we break out early, rather than continuing to make
331 // many more slow flush() calls.
332 int delta = time_diff(&start, &now);
333 BOOST_REQUIRE_LT(delta, 500000);
334 }
335}
336
David Reiss709b69f2010-10-06 17:10:30 +0000337/**************************************************************************
338 * General Initialization
339 **************************************************************************/
340
341void print_usage(FILE* f, const char* argv0) {
342 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
343 fprintf(f, "Options:\n");
344 fprintf(f, " --tmp-dir=DIR, -t DIR\n");
345 fprintf(f, " --help\n");
346}
347
348void parse_args(int argc, char* argv[]) {
349 int seed;
350 int *seedptr = NULL;
351
352 struct option long_opts[] = {
353 { "help", false, NULL, 'h' },
354 { "tmp-dir", true, NULL, 't' },
355 { NULL, 0, NULL, 0 }
356 };
357
358 while (true) {
359 optopt = 1;
360 int optchar = getopt_long(argc, argv, "ht:", long_opts, NULL);
361 if (optchar == -1) {
362 break;
363 }
364
365 switch (optchar) {
366 case 't':
367 tmp_dir = optarg;
368 break;
369 case 'h':
370 print_usage(stdout, argv[0]);
371 exit(0);
372 case '?':
373 exit(1);
374 default:
375 // Only happens if someone adds another option to the optarg string,
376 // but doesn't update the switch statement to handle it.
377 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
378 exit(1);
379 }
380 }
381}
382
383boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
David Reiss109693c2010-10-06 17:10:42 +0000384 boost::unit_test::framework::master_test_suite().p_name.value =
385 "TFileTransportTest";
386
David Reiss709b69f2010-10-06 17:10:30 +0000387 // Parse arguments
388 parse_args(argc, argv);
David Reiss109693c2010-10-06 17:10:42 +0000389 return NULL;
David Reiss709b69f2010-10-06 17:10:30 +0000390}