blob: 0c4a7a20e84662c35aa10885a9a5f5106d4310d6 [file] [log] [blame]
henrike@webrtc.org0e118e72013-07-10 00:45:36 +00001/*
2 * libjingle
3 * Copyright 2004--2011, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#ifdef POSIX
29#include <sys/time.h>
30#endif // POSIX
31
32// TODO: Remove this once the cause of sporadic failures in these
33// tests is tracked down.
34#include <iostream>
35
36#ifdef WIN32
37#include "talk/base/win32.h"
38#endif // WIN32
39
40#include "talk/base/common.h"
41#include "talk/base/gunit.h"
42#include "talk/base/logging.h"
43#include "talk/base/task.h"
44#include "talk/base/taskrunner.h"
45#include "talk/base/thread.h"
46#include "talk/base/timeutils.h"
47
48namespace talk_base {
49
50static int64 GetCurrentTime() {
51 return static_cast<int64>(Time()) * 10000;
52}
53
54// feel free to change these numbers. Note that '0' won't work, though
55#define STUCK_TASK_COUNT 5
56#define HAPPY_TASK_COUNT 20
57
58// this is a generic timeout task which, when it signals timeout, will
59// include the unique ID of the task in the signal (we don't use this
60// in production code because we haven't yet had occasion to generate
61// an array of the same types of task)
62
63class IdTimeoutTask : public Task, public sigslot::has_slots<> {
64 public:
65 explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
66 SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
67 }
68
69 sigslot::signal1<const int> SignalTimeoutId;
70 sigslot::signal1<const int> SignalDoneId;
71
72 virtual int ProcessStart() {
73 return STATE_RESPONSE;
74 }
75
76 void OnLocalTimeout() {
77 SignalTimeoutId(unique_id());
78 }
79
80 protected:
81 virtual void Stop() {
82 SignalDoneId(unique_id());
83 Task::Stop();
84 }
85};
86
87class StuckTask : public IdTimeoutTask {
88 public:
89 explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
90 virtual int ProcessStart() {
91 return STATE_BLOCKED;
92 }
93};
94
95class HappyTask : public IdTimeoutTask {
96 public:
97 explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
98 time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
99 }
100 virtual int ProcessStart() {
101 if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
102 return STATE_RESPONSE;
103 else
104 return STATE_BLOCKED;
105 }
106
107 private:
108 int time_to_perform_;
109};
110
111// simple implementation of a task runner which uses Windows'
112// GetSystemTimeAsFileTime() to get the current clock ticks
113
114class MyTaskRunner : public TaskRunner {
115 public:
116 virtual void WakeTasks() { RunTasks(); }
117 virtual int64 CurrentTime() {
118 return GetCurrentTime();
119 }
120
121 bool timeout_change() const {
122 return timeout_change_;
123 }
124
125 void clear_timeout_change() {
126 timeout_change_ = false;
127 }
128 protected:
129 virtual void OnTimeoutChange() {
130 timeout_change_ = true;
131 }
132 bool timeout_change_;
133};
134
135//
136// this unit test is primarily concerned (for now) with the timeout
137// functionality in tasks. It works as follows:
138//
139// * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
140// and some "happy" (will immediately finish).
141// * Set the timeout on the "stuck" tasks to some number of seconds between
142// 1 and the number of stuck tasks
143// * Start all the stuck & happy tasks in random order
144// * Wait "number of stuck tasks" seconds and make sure everything timed out
145
146class TaskTest : public sigslot::has_slots<> {
147 public:
148 TaskTest() {}
149
150 // no need to delete any tasks; the task runner owns them
151 ~TaskTest() {}
152
153 void Start() {
154 // create and configure tasks
155 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
156 stuck_[i].task_ = new StuckTask(&task_runner_);
157 stuck_[i].task_->SignalTimeoutId.connect(this,
158 &TaskTest::OnTimeoutStuck);
159 stuck_[i].timed_out_ = false;
160 stuck_[i].xlat_ = stuck_[i].task_->unique_id();
161 stuck_[i].task_->set_timeout_seconds(i + 1);
162 LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
163 << stuck_[i].task_->timeout_seconds();
164 }
165
166 for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
167 happy_[i].task_ = new HappyTask(&task_runner_);
168 happy_[i].task_->SignalTimeoutId.connect(this,
169 &TaskTest::OnTimeoutHappy);
170 happy_[i].task_->SignalDoneId.connect(this,
171 &TaskTest::OnDoneHappy);
172 happy_[i].timed_out_ = false;
173 happy_[i].xlat_ = happy_[i].task_->unique_id();
174 }
175
176 // start all the tasks in random order
177 int stuck_index = 0;
178 int happy_index = 0;
179 for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
180 if ((stuck_index < STUCK_TASK_COUNT) &&
181 (happy_index < HAPPY_TASK_COUNT)) {
182 if (rand() % 2 == 1) {
183 stuck_[stuck_index++].task_->Start();
184 } else {
185 happy_[happy_index++].task_->Start();
186 }
187 } else if (stuck_index < STUCK_TASK_COUNT) {
188 stuck_[stuck_index++].task_->Start();
189 } else {
190 happy_[happy_index++].task_->Start();
191 }
192 }
193
194 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
195 std::cout << "Stuck task #" << i << " timeout is " <<
196 stuck_[i].task_->timeout_seconds() << " at " <<
197 stuck_[i].task_->timeout_time() << std::endl;
198 }
199
200 // just a little self-check to make sure we started all the tasks
201 ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
202 ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
203
204 // run the unblocked tasks
205 LOG(LS_INFO) << "Running tasks";
206 task_runner_.RunTasks();
207
208 std::cout << "Start time is " << GetCurrentTime() << std::endl;
209
210 // give all the stuck tasks time to timeout
211 for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
212 ++i) {
213 Thread::Current()->ProcessMessages(1000);
214 for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
215 if (happy_[j].task_) {
216 happy_[j].task_->Wake();
217 }
218 }
219 LOG(LS_INFO) << "Polling tasks";
220 task_runner_.PollTasks();
221 }
222
223 // We see occasional test failures here due to the stuck tasks not having
224 // timed-out yet, which seems like it should be impossible. To help track
225 // this down we have added logging of the timing information, which we send
226 // directly to stdout so that we get it in opt builds too.
227 std::cout << "End time is " << GetCurrentTime() << std::endl;
228 }
229
230 void OnTimeoutStuck(const int id) {
231 LOG(LS_INFO) << "Timed out task " << id;
232
233 int i;
234 for (i = 0; i < STUCK_TASK_COUNT; ++i) {
235 if (stuck_[i].xlat_ == id) {
236 stuck_[i].timed_out_ = true;
237 stuck_[i].task_ = NULL;
238 break;
239 }
240 }
241
242 // getting a bad ID here is a failure, but let's continue
243 // running to see what else might go wrong
244 EXPECT_LT(i, STUCK_TASK_COUNT);
245 }
246
247 void OnTimeoutHappy(const int id) {
248 int i;
249 for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
250 if (happy_[i].xlat_ == id) {
251 happy_[i].timed_out_ = true;
252 happy_[i].task_ = NULL;
253 break;
254 }
255 }
256
257 // getting a bad ID here is a failure, but let's continue
258 // running to see what else might go wrong
259 EXPECT_LT(i, HAPPY_TASK_COUNT);
260 }
261
262 void OnDoneHappy(const int id) {
263 int i;
264 for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
265 if (happy_[i].xlat_ == id) {
266 happy_[i].task_ = NULL;
267 break;
268 }
269 }
270
271 // getting a bad ID here is a failure, but let's continue
272 // running to see what else might go wrong
273 EXPECT_LT(i, HAPPY_TASK_COUNT);
274 }
275
276 void check_passed() {
277 EXPECT_TRUE(task_runner_.AllChildrenDone());
278
279 // make sure none of our happy tasks timed out
280 for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
281 EXPECT_FALSE(happy_[i].timed_out_);
282 }
283
284 // make sure all of our stuck tasks timed out
285 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
286 EXPECT_TRUE(stuck_[i].timed_out_);
287 if (!stuck_[i].timed_out_) {
288 std::cout << "Stuck task #" << i << " timeout is at "
289 << stuck_[i].task_->timeout_time() << std::endl;
290 }
291 }
292
293 std::cout.flush();
294 }
295
296 private:
297 struct TaskInfo {
298 IdTimeoutTask *task_;
299 bool timed_out_;
300 int xlat_;
301 };
302
303 MyTaskRunner task_runner_;
304 TaskInfo stuck_[STUCK_TASK_COUNT];
305 TaskInfo happy_[HAPPY_TASK_COUNT];
306};
307
308TEST(start_task_test, Timeout) {
309 TaskTest task_test;
310 task_test.Start();
311 task_test.check_passed();
312}
313
314// Test for aborting the task while it is running
315
316class AbortTask : public Task {
317 public:
318 explicit AbortTask(TaskParent *parent) : Task(parent) {
319 set_timeout_seconds(1);
320 }
321
322 virtual int ProcessStart() {
323 Abort();
324 return STATE_NEXT;
325 }
326 private:
327 DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
328};
329
330class TaskAbortTest : public sigslot::has_slots<> {
331 public:
332 TaskAbortTest() {}
333
334 // no need to delete any tasks; the task runner owns them
335 ~TaskAbortTest() {}
336
337 void Start() {
338 Task *abort_task = new AbortTask(&task_runner_);
339 abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
340 abort_task->Start();
341
342 // run the task
343 task_runner_.RunTasks();
344 }
345
346 private:
347 void OnTimeout() {
348 FAIL() << "Task timed out instead of aborting.";
349 }
350
351 MyTaskRunner task_runner_;
352 DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
353};
354
355TEST(start_task_test, Abort) {
356 TaskAbortTest abort_test;
357 abort_test.Start();
358}
359
360// Test for aborting a task to verify that it does the Wake operation
361// which gets it deleted.
362
363class SetBoolOnDeleteTask : public Task {
364 public:
365 SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
366 : Task(parent),
367 set_when_deleted_(set_when_deleted) {
368 EXPECT_TRUE(NULL != set_when_deleted);
369 EXPECT_FALSE(*set_when_deleted);
370 }
371
372 virtual ~SetBoolOnDeleteTask() {
373 *set_when_deleted_ = true;
374 }
375
376 virtual int ProcessStart() {
377 return STATE_BLOCKED;
378 }
379
380 private:
381 bool* set_when_deleted_;
382 DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
383};
384
385class AbortShouldWakeTest : public sigslot::has_slots<> {
386 public:
387 AbortShouldWakeTest() {}
388
389 // no need to delete any tasks; the task runner owns them
390 ~AbortShouldWakeTest() {}
391
392 void Start() {
393 bool task_deleted = false;
394 Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
395 task_to_abort->Start();
396
397 // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
398 // TaskRunner::RunTasks() immediately which should delete the task.
399 task_to_abort->Abort();
400 EXPECT_TRUE(task_deleted);
401
402 if (!task_deleted) {
403 // avoid a crash (due to referencing a local variable)
404 // if the test fails.
405 task_runner_.RunTasks();
406 }
407 }
408
409 private:
410 void OnTimeout() {
411 FAIL() << "Task timed out instead of aborting.";
412 }
413
414 MyTaskRunner task_runner_;
415 DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
416};
417
418TEST(start_task_test, AbortShouldWake) {
419 AbortShouldWakeTest abort_should_wake_test;
420 abort_should_wake_test.Start();
421}
422
423// Validate that TaskRunner's OnTimeoutChange gets called appropriately
424// * When a task calls UpdateTaskTimeout
425// * When the next timeout task time, times out
426class TimeoutChangeTest : public sigslot::has_slots<> {
427 public:
428 TimeoutChangeTest()
429 : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
430
431 // no need to delete any tasks; the task runner owns them
432 ~TimeoutChangeTest() {}
433
434 void Start() {
435 for (int i = 0; i < task_count_; ++i) {
436 stuck_tasks_[i] = new StuckTask(&task_runner_);
437 stuck_tasks_[i]->set_timeout_seconds(i + 2);
438 stuck_tasks_[i]->SignalTimeoutId.connect(this,
439 &TimeoutChangeTest::OnTimeoutId);
440 }
441
442 for (int i = task_count_ - 1; i >= 0; --i) {
443 stuck_tasks_[i]->Start();
444 }
445 task_runner_.clear_timeout_change();
446
447 // At this point, our timeouts are set as follows
448 // task[0] is 2 seconds, task[1] at 3 seconds, etc.
449
450 stuck_tasks_[0]->set_timeout_seconds(2);
451 // Now, task[0] is 2 seconds, task[1] at 3 seconds...
452 // so timeout change shouldn't be called.
453 EXPECT_FALSE(task_runner_.timeout_change());
454 task_runner_.clear_timeout_change();
455
456 stuck_tasks_[0]->set_timeout_seconds(1);
457 // task[0] is 1 seconds, task[1] at 3 seconds...
458 // The smallest timeout got smaller so timeout change be called.
459 EXPECT_TRUE(task_runner_.timeout_change());
460 task_runner_.clear_timeout_change();
461
462 stuck_tasks_[1]->set_timeout_seconds(2);
463 // task[0] is 1 seconds, task[1] at 2 seconds...
464 // The smallest timeout is still 1 second so no timeout change.
465 EXPECT_FALSE(task_runner_.timeout_change());
466 task_runner_.clear_timeout_change();
467
468 while (task_count_ > 0) {
469 int previous_count = task_count_;
470 task_runner_.PollTasks();
471 if (previous_count != task_count_) {
472 // We only get here when a task times out. When that
473 // happens, the timeout change should get called because
474 // the smallest timeout is now in the past.
475 EXPECT_TRUE(task_runner_.timeout_change());
476 task_runner_.clear_timeout_change();
477 }
478 Thread::Current()->socketserver()->Wait(500, false);
479 }
480 }
481
482 private:
483 void OnTimeoutId(const int id) {
484 for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
485 if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
486 task_count_--;
487 stuck_tasks_[i] = NULL;
488 break;
489 }
490 }
491 }
492
493 MyTaskRunner task_runner_;
494 StuckTask* (stuck_tasks_[3]);
495 int task_count_;
496 DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
497};
498
499TEST(start_task_test, TimeoutChange) {
500 TimeoutChangeTest timeout_change_test;
501 timeout_change_test.Start();
502}
503
504class DeleteTestTaskRunner : public TaskRunner {
505 public:
506 DeleteTestTaskRunner() {
507 }
508 virtual void WakeTasks() { }
509 virtual int64 CurrentTime() {
510 return GetCurrentTime();
511 }
512 private:
513 DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
514};
515
516TEST(unstarted_task_test, DeleteTask) {
517 // This test ensures that we don't
518 // crash if a task is deleted without running it.
519 DeleteTestTaskRunner task_runner;
520 HappyTask* happy_task = new HappyTask(&task_runner);
521 happy_task->Start();
522
523 // try deleting the task directly
524 HappyTask* child_happy_task = new HappyTask(happy_task);
525 delete child_happy_task;
526
527 // run the unblocked tasks
528 task_runner.RunTasks();
529}
530
531TEST(unstarted_task_test, DoNotDeleteTask1) {
532 // This test ensures that we don't
533 // crash if a task runner is deleted without
534 // running a certain task.
535 DeleteTestTaskRunner task_runner;
536 HappyTask* happy_task = new HappyTask(&task_runner);
537 happy_task->Start();
538
539 HappyTask* child_happy_task = new HappyTask(happy_task);
540 child_happy_task->Start();
541
542 // Never run the tasks
543}
544
545TEST(unstarted_task_test, DoNotDeleteTask2) {
546 // This test ensures that we don't
547 // crash if a taskrunner is delete with a
548 // task that has never been started.
549 DeleteTestTaskRunner task_runner;
550 HappyTask* happy_task = new HappyTask(&task_runner);
551 happy_task->Start();
552
553 // Do not start the task.
554 // Note: this leaks memory, so don't do this.
555 // Instead, always run your tasks or delete them.
556 new HappyTask(happy_task);
557
558 // run the unblocked tasks
559 task_runner.RunTasks();
560}
561
562} // namespace talk_base