blob: b26d6706cf6de3b4897f6c30734c63b78dbd5595 [file] [log] [blame]
henrike@webrtc.orgf0488722014-05-13 18:00:26 +00001/*
2 * Copyright 2004 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#if defined(WEBRTC_POSIX)
12#include <sys/time.h>
13#endif // WEBRTC_POSIX
14
15// TODO: Remove this once the cause of sporadic failures in these
16// tests is tracked down.
17#include <iostream>
18
19#if defined(WEBRTC_WIN)
20#include "webrtc/base/win32.h"
henrikg3c089d72015-09-16 05:37:44 -070021#endif // WEBRTC_WIN
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000022
tfarina5237aaf2015-11-10 23:44:30 -080023#include "webrtc/base/arraysize.h"
kwiberg4485ffb2016-04-26 08:14:39 -070024#include "webrtc/base/constructormagic.h"
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000025#include "webrtc/base/gunit.h"
26#include "webrtc/base/logging.h"
27#include "webrtc/base/task.h"
28#include "webrtc/base/taskrunner.h"
29#include "webrtc/base/thread.h"
30#include "webrtc/base/timeutils.h"
31
32namespace rtc {
33
Peter Boström0c4e06b2015-10-07 12:23:21 +020034static int64_t GetCurrentTime() {
Honghai Zhang82d78622016-05-06 11:29:15 -070035 return TimeMillis() * 10000;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +000036}
37
38// feel free to change these numbers. Note that '0' won't work, though
39#define STUCK_TASK_COUNT 5
40#define HAPPY_TASK_COUNT 20
41
42// this is a generic timeout task which, when it signals timeout, will
43// include the unique ID of the task in the signal (we don't use this
44// in production code because we haven't yet had occasion to generate
45// an array of the same types of task)
46
47class IdTimeoutTask : public Task, public sigslot::has_slots<> {
48 public:
49 explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
50 SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
51 }
52
53 sigslot::signal1<const int> SignalTimeoutId;
54 sigslot::signal1<const int> SignalDoneId;
55
56 virtual int ProcessStart() {
57 return STATE_RESPONSE;
58 }
59
60 void OnLocalTimeout() {
61 SignalTimeoutId(unique_id());
62 }
63
64 protected:
65 virtual void Stop() {
66 SignalDoneId(unique_id());
67 Task::Stop();
68 }
69};
70
71class StuckTask : public IdTimeoutTask {
72 public:
73 explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
74 virtual int ProcessStart() {
75 return STATE_BLOCKED;
76 }
77};
78
79class HappyTask : public IdTimeoutTask {
80 public:
81 explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
82 time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
83 }
84 virtual int ProcessStart() {
85 if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
86 return STATE_RESPONSE;
87 else
88 return STATE_BLOCKED;
89 }
90
91 private:
92 int time_to_perform_;
93};
94
95// simple implementation of a task runner which uses Windows'
96// GetSystemTimeAsFileTime() to get the current clock ticks
97
98class MyTaskRunner : public TaskRunner {
99 public:
100 virtual void WakeTasks() { RunTasks(); }
Peter Boström0c4e06b2015-10-07 12:23:21 +0200101 virtual int64_t CurrentTime() { return GetCurrentTime(); }
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000102
103 bool timeout_change() const {
104 return timeout_change_;
105 }
106
107 void clear_timeout_change() {
108 timeout_change_ = false;
109 }
110 protected:
111 virtual void OnTimeoutChange() {
112 timeout_change_ = true;
113 }
114 bool timeout_change_;
115};
116
117//
118// this unit test is primarily concerned (for now) with the timeout
119// functionality in tasks. It works as follows:
120//
121// * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
122// and some "happy" (will immediately finish).
123// * Set the timeout on the "stuck" tasks to some number of seconds between
124// 1 and the number of stuck tasks
125// * Start all the stuck & happy tasks in random order
126// * Wait "number of stuck tasks" seconds and make sure everything timed out
127
128class TaskTest : public sigslot::has_slots<> {
129 public:
130 TaskTest() {}
131
132 // no need to delete any tasks; the task runner owns them
133 ~TaskTest() {}
134
135 void Start() {
136 // create and configure tasks
137 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
138 stuck_[i].task_ = new StuckTask(&task_runner_);
139 stuck_[i].task_->SignalTimeoutId.connect(this,
140 &TaskTest::OnTimeoutStuck);
141 stuck_[i].timed_out_ = false;
142 stuck_[i].xlat_ = stuck_[i].task_->unique_id();
143 stuck_[i].task_->set_timeout_seconds(i + 1);
144 LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
145 << stuck_[i].task_->timeout_seconds();
146 }
147
148 for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
149 happy_[i].task_ = new HappyTask(&task_runner_);
150 happy_[i].task_->SignalTimeoutId.connect(this,
151 &TaskTest::OnTimeoutHappy);
152 happy_[i].task_->SignalDoneId.connect(this,
153 &TaskTest::OnDoneHappy);
154 happy_[i].timed_out_ = false;
155 happy_[i].xlat_ = happy_[i].task_->unique_id();
156 }
157
158 // start all the tasks in random order
159 int stuck_index = 0;
160 int happy_index = 0;
161 for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
162 if ((stuck_index < STUCK_TASK_COUNT) &&
163 (happy_index < HAPPY_TASK_COUNT)) {
164 if (rand() % 2 == 1) {
165 stuck_[stuck_index++].task_->Start();
166 } else {
167 happy_[happy_index++].task_->Start();
168 }
169 } else if (stuck_index < STUCK_TASK_COUNT) {
170 stuck_[stuck_index++].task_->Start();
171 } else {
172 happy_[happy_index++].task_->Start();
173 }
174 }
175
176 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
177 std::cout << "Stuck task #" << i << " timeout is " <<
178 stuck_[i].task_->timeout_seconds() << " at " <<
179 stuck_[i].task_->timeout_time() << std::endl;
180 }
181
182 // just a little self-check to make sure we started all the tasks
183 ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
184 ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
185
186 // run the unblocked tasks
187 LOG(LS_INFO) << "Running tasks";
188 task_runner_.RunTasks();
189
190 std::cout << "Start time is " << GetCurrentTime() << std::endl;
191
192 // give all the stuck tasks time to timeout
193 for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
194 ++i) {
195 Thread::Current()->ProcessMessages(1000);
196 for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
197 if (happy_[j].task_) {
198 happy_[j].task_->Wake();
199 }
200 }
201 LOG(LS_INFO) << "Polling tasks";
202 task_runner_.PollTasks();
203 }
204
205 // We see occasional test failures here due to the stuck tasks not having
206 // timed-out yet, which seems like it should be impossible. To help track
207 // this down we have added logging of the timing information, which we send
208 // directly to stdout so that we get it in opt builds too.
209 std::cout << "End time is " << GetCurrentTime() << std::endl;
210 }
211
212 void OnTimeoutStuck(const int id) {
213 LOG(LS_INFO) << "Timed out task " << id;
214
215 int i;
216 for (i = 0; i < STUCK_TASK_COUNT; ++i) {
217 if (stuck_[i].xlat_ == id) {
218 stuck_[i].timed_out_ = true;
deadbeef37f5ecf2017-02-27 14:06:41 -0800219 stuck_[i].task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000220 break;
221 }
222 }
223
224 // getting a bad ID here is a failure, but let's continue
225 // running to see what else might go wrong
226 EXPECT_LT(i, STUCK_TASK_COUNT);
227 }
228
229 void OnTimeoutHappy(const int id) {
230 int i;
231 for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
232 if (happy_[i].xlat_ == id) {
233 happy_[i].timed_out_ = true;
deadbeef37f5ecf2017-02-27 14:06:41 -0800234 happy_[i].task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000235 break;
236 }
237 }
238
239 // getting a bad ID here is a failure, but let's continue
240 // running to see what else might go wrong
241 EXPECT_LT(i, HAPPY_TASK_COUNT);
242 }
243
244 void OnDoneHappy(const int id) {
245 int i;
246 for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
247 if (happy_[i].xlat_ == id) {
deadbeef37f5ecf2017-02-27 14:06:41 -0800248 happy_[i].task_ = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000249 break;
250 }
251 }
252
253 // getting a bad ID here is a failure, but let's continue
254 // running to see what else might go wrong
255 EXPECT_LT(i, HAPPY_TASK_COUNT);
256 }
257
258 void check_passed() {
259 EXPECT_TRUE(task_runner_.AllChildrenDone());
260
261 // make sure none of our happy tasks timed out
262 for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
263 EXPECT_FALSE(happy_[i].timed_out_);
264 }
265
266 // make sure all of our stuck tasks timed out
267 for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
268 EXPECT_TRUE(stuck_[i].timed_out_);
269 if (!stuck_[i].timed_out_) {
270 std::cout << "Stuck task #" << i << " timeout is at "
henrikg3c089d72015-09-16 05:37:44 -0700271 << stuck_[i].task_->timeout_time() << std::endl;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000272 }
273 }
274
275 std::cout.flush();
276 }
277
278 private:
279 struct TaskInfo {
280 IdTimeoutTask *task_;
281 bool timed_out_;
282 int xlat_;
283 };
284
285 MyTaskRunner task_runner_;
286 TaskInfo stuck_[STUCK_TASK_COUNT];
287 TaskInfo happy_[HAPPY_TASK_COUNT];
288};
289
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000290TEST(start_task_test, Timeout) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000291 TaskTest task_test;
292 task_test.Start();
293 task_test.check_passed();
294}
295
296// Test for aborting the task while it is running
297
298class AbortTask : public Task {
299 public:
300 explicit AbortTask(TaskParent *parent) : Task(parent) {
301 set_timeout_seconds(1);
302 }
303
304 virtual int ProcessStart() {
305 Abort();
306 return STATE_NEXT;
307 }
308 private:
henrikg3c089d72015-09-16 05:37:44 -0700309 RTC_DISALLOW_COPY_AND_ASSIGN(AbortTask);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000310};
311
312class TaskAbortTest : public sigslot::has_slots<> {
313 public:
314 TaskAbortTest() {}
315
316 // no need to delete any tasks; the task runner owns them
317 ~TaskAbortTest() {}
318
319 void Start() {
320 Task *abort_task = new AbortTask(&task_runner_);
321 abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
322 abort_task->Start();
323
324 // run the task
325 task_runner_.RunTasks();
326 }
327
328 private:
329 void OnTimeout() {
330 FAIL() << "Task timed out instead of aborting.";
331 }
332
333 MyTaskRunner task_runner_;
henrikg3c089d72015-09-16 05:37:44 -0700334 RTC_DISALLOW_COPY_AND_ASSIGN(TaskAbortTest);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000335};
336
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000337TEST(start_task_test, Abort) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000338 TaskAbortTest abort_test;
339 abort_test.Start();
340}
341
342// Test for aborting a task to verify that it does the Wake operation
343// which gets it deleted.
344
345class SetBoolOnDeleteTask : public Task {
346 public:
347 SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
348 : Task(parent),
349 set_when_deleted_(set_when_deleted) {
deadbeef37f5ecf2017-02-27 14:06:41 -0800350 EXPECT_TRUE(nullptr != set_when_deleted);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000351 EXPECT_FALSE(*set_when_deleted);
352 }
353
354 virtual ~SetBoolOnDeleteTask() {
355 *set_when_deleted_ = true;
356 }
357
358 virtual int ProcessStart() {
359 return STATE_BLOCKED;
360 }
361
362 private:
363 bool* set_when_deleted_;
henrikg3c089d72015-09-16 05:37:44 -0700364 RTC_DISALLOW_COPY_AND_ASSIGN(SetBoolOnDeleteTask);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000365};
366
367class AbortShouldWakeTest : public sigslot::has_slots<> {
368 public:
369 AbortShouldWakeTest() {}
370
371 // no need to delete any tasks; the task runner owns them
372 ~AbortShouldWakeTest() {}
373
374 void Start() {
375 bool task_deleted = false;
376 Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
377 task_to_abort->Start();
378
379 // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
380 // TaskRunner::RunTasks() immediately which should delete the task.
381 task_to_abort->Abort();
382 EXPECT_TRUE(task_deleted);
383
384 if (!task_deleted) {
385 // avoid a crash (due to referencing a local variable)
386 // if the test fails.
387 task_runner_.RunTasks();
388 }
389 }
390
391 private:
392 void OnTimeout() {
393 FAIL() << "Task timed out instead of aborting.";
394 }
395
396 MyTaskRunner task_runner_;
henrikg3c089d72015-09-16 05:37:44 -0700397 RTC_DISALLOW_COPY_AND_ASSIGN(AbortShouldWakeTest);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000398};
399
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000400TEST(start_task_test, AbortShouldWake) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000401 AbortShouldWakeTest abort_should_wake_test;
402 abort_should_wake_test.Start();
403}
404
405// Validate that TaskRunner's OnTimeoutChange gets called appropriately
406// * When a task calls UpdateTaskTimeout
407// * When the next timeout task time, times out
408class TimeoutChangeTest : public sigslot::has_slots<> {
409 public:
410 TimeoutChangeTest()
tfarina5237aaf2015-11-10 23:44:30 -0800411 : task_count_(arraysize(stuck_tasks_)) {}
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000412
413 // no need to delete any tasks; the task runner owns them
414 ~TimeoutChangeTest() {}
415
416 void Start() {
417 for (int i = 0; i < task_count_; ++i) {
418 stuck_tasks_[i] = new StuckTask(&task_runner_);
419 stuck_tasks_[i]->set_timeout_seconds(i + 2);
420 stuck_tasks_[i]->SignalTimeoutId.connect(this,
421 &TimeoutChangeTest::OnTimeoutId);
422 }
423
424 for (int i = task_count_ - 1; i >= 0; --i) {
425 stuck_tasks_[i]->Start();
426 }
427 task_runner_.clear_timeout_change();
428
429 // At this point, our timeouts are set as follows
430 // task[0] is 2 seconds, task[1] at 3 seconds, etc.
431
432 stuck_tasks_[0]->set_timeout_seconds(2);
433 // Now, task[0] is 2 seconds, task[1] at 3 seconds...
434 // so timeout change shouldn't be called.
435 EXPECT_FALSE(task_runner_.timeout_change());
436 task_runner_.clear_timeout_change();
437
438 stuck_tasks_[0]->set_timeout_seconds(1);
439 // task[0] is 1 seconds, task[1] at 3 seconds...
440 // The smallest timeout got smaller so timeout change be called.
441 EXPECT_TRUE(task_runner_.timeout_change());
442 task_runner_.clear_timeout_change();
443
444 stuck_tasks_[1]->set_timeout_seconds(2);
445 // task[0] is 1 seconds, task[1] at 2 seconds...
446 // The smallest timeout is still 1 second so no timeout change.
447 EXPECT_FALSE(task_runner_.timeout_change());
448 task_runner_.clear_timeout_change();
449
450 while (task_count_ > 0) {
451 int previous_count = task_count_;
452 task_runner_.PollTasks();
453 if (previous_count != task_count_) {
454 // We only get here when a task times out. When that
455 // happens, the timeout change should get called because
456 // the smallest timeout is now in the past.
457 EXPECT_TRUE(task_runner_.timeout_change());
458 task_runner_.clear_timeout_change();
459 }
460 Thread::Current()->socketserver()->Wait(500, false);
461 }
462 }
463
464 private:
465 void OnTimeoutId(const int id) {
tfarina5237aaf2015-11-10 23:44:30 -0800466 for (size_t i = 0; i < arraysize(stuck_tasks_); ++i) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000467 if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
468 task_count_--;
deadbeef37f5ecf2017-02-27 14:06:41 -0800469 stuck_tasks_[i] = nullptr;
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000470 break;
471 }
472 }
473 }
474
475 MyTaskRunner task_runner_;
476 StuckTask* (stuck_tasks_[3]);
477 int task_count_;
henrikg3c089d72015-09-16 05:37:44 -0700478 RTC_DISALLOW_COPY_AND_ASSIGN(TimeoutChangeTest);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000479};
480
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000481TEST(start_task_test, TimeoutChange) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000482 TimeoutChangeTest timeout_change_test;
483 timeout_change_test.Start();
484}
485
486class DeleteTestTaskRunner : public TaskRunner {
487 public:
488 DeleteTestTaskRunner() {
489 }
490 virtual void WakeTasks() { }
Peter Boström0c4e06b2015-10-07 12:23:21 +0200491 virtual int64_t CurrentTime() { return GetCurrentTime(); }
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000492 private:
henrikg3c089d72015-09-16 05:37:44 -0700493 RTC_DISALLOW_COPY_AND_ASSIGN(DeleteTestTaskRunner);
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000494};
495
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000496TEST(unstarted_task_test, DeleteTask) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000497 // This test ensures that we don't
498 // crash if a task is deleted without running it.
499 DeleteTestTaskRunner task_runner;
500 HappyTask* happy_task = new HappyTask(&task_runner);
501 happy_task->Start();
502
503 // try deleting the task directly
504 HappyTask* child_happy_task = new HappyTask(happy_task);
505 delete child_happy_task;
506
507 // run the unblocked tasks
508 task_runner.RunTasks();
509}
510
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000511TEST(unstarted_task_test, DoNotDeleteTask1) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000512 // This test ensures that we don't
513 // crash if a task runner is deleted without
514 // running a certain task.
515 DeleteTestTaskRunner task_runner;
516 HappyTask* happy_task = new HappyTask(&task_runner);
517 happy_task->Start();
518
519 HappyTask* child_happy_task = new HappyTask(happy_task);
520 child_happy_task->Start();
521
522 // Never run the tasks
523}
524
henrike@webrtc.orgc732a3e2014-10-09 22:08:15 +0000525TEST(unstarted_task_test, DoNotDeleteTask2) {
henrike@webrtc.orgf0488722014-05-13 18:00:26 +0000526 // This test ensures that we don't
527 // crash if a taskrunner is delete with a
528 // task that has never been started.
529 DeleteTestTaskRunner task_runner;
530 HappyTask* happy_task = new HappyTask(&task_runner);
531 happy_task->Start();
532
533 // Do not start the task.
534 // Note: this leaks memory, so don't do this.
535 // Instead, always run your tasks or delete them.
536 new HappyTask(happy_task);
537
538 // run the unblocked tasks
539 task_runner.RunTasks();
540}
541
542} // namespace rtc