1/*
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"
21#endif  // WEBRTC_WIN
22
23#include "webrtc/base/common.h"
24#include "webrtc/base/gunit.h"
25#include "webrtc/base/logging.h"
26#include "webrtc/base/task.h"
27#include "webrtc/base/taskrunner.h"
28#include "webrtc/base/thread.h"
29#include "webrtc/base/timeutils.h"
30#include "webrtc/test/testsupport/gtest_disable.h"
31
32namespace rtc {
33
34static int64 GetCurrentTime() {
35  return static_cast<int64>(Time()) * 10000;
36}
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(); }
101  virtual int64 CurrentTime() {
102    return GetCurrentTime();
103  }
104
105  bool timeout_change() const {
106    return timeout_change_;
107  }
108
109  void clear_timeout_change() {
110    timeout_change_ = false;
111  }
112 protected:
113  virtual void OnTimeoutChange() {
114    timeout_change_ = true;
115  }
116  bool timeout_change_;
117};
118
119//
120// this unit test is primarily concerned (for now) with the timeout
121// functionality in tasks.  It works as follows:
122//
123//   * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
124//     and some "happy" (will immediately finish).
125//   * Set the timeout on the "stuck" tasks to some number of seconds between
126//     1 and the number of stuck tasks
127//   * Start all the stuck & happy tasks in random order
128//   * Wait "number of stuck tasks" seconds and make sure everything timed out
129
130class TaskTest : public sigslot::has_slots<> {
131 public:
132  TaskTest() {}
133
134  // no need to delete any tasks; the task runner owns them
135  ~TaskTest() {}
136
137  void Start() {
138    // create and configure tasks
139    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
140      stuck_[i].task_ = new StuckTask(&task_runner_);
141      stuck_[i].task_->SignalTimeoutId.connect(this,
142                                               &TaskTest::OnTimeoutStuck);
143      stuck_[i].timed_out_ = false;
144      stuck_[i].xlat_ = stuck_[i].task_->unique_id();
145      stuck_[i].task_->set_timeout_seconds(i + 1);
146      LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
147                   << stuck_[i].task_->timeout_seconds();
148    }
149
150    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
151      happy_[i].task_ = new HappyTask(&task_runner_);
152      happy_[i].task_->SignalTimeoutId.connect(this,
153                                               &TaskTest::OnTimeoutHappy);
154      happy_[i].task_->SignalDoneId.connect(this,
155                                            &TaskTest::OnDoneHappy);
156      happy_[i].timed_out_ = false;
157      happy_[i].xlat_ = happy_[i].task_->unique_id();
158    }
159
160    // start all the tasks in random order
161    int stuck_index = 0;
162    int happy_index = 0;
163    for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
164      if ((stuck_index < STUCK_TASK_COUNT) &&
165          (happy_index < HAPPY_TASK_COUNT)) {
166        if (rand() % 2 == 1) {
167          stuck_[stuck_index++].task_->Start();
168        } else {
169          happy_[happy_index++].task_->Start();
170        }
171      } else if (stuck_index < STUCK_TASK_COUNT) {
172        stuck_[stuck_index++].task_->Start();
173      } else {
174        happy_[happy_index++].task_->Start();
175      }
176    }
177
178    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
179      std::cout << "Stuck task #" << i << " timeout is " <<
180          stuck_[i].task_->timeout_seconds() << " at " <<
181          stuck_[i].task_->timeout_time() << std::endl;
182    }
183
184    // just a little self-check to make sure we started all the tasks
185    ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
186    ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
187
188    // run the unblocked tasks
189    LOG(LS_INFO) << "Running tasks";
190    task_runner_.RunTasks();
191
192    std::cout << "Start time is " << GetCurrentTime() << std::endl;
193
194    // give all the stuck tasks time to timeout
195    for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
196         ++i) {
197      Thread::Current()->ProcessMessages(1000);
198      for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
199        if (happy_[j].task_) {
200          happy_[j].task_->Wake();
201        }
202      }
203      LOG(LS_INFO) << "Polling tasks";
204      task_runner_.PollTasks();
205    }
206
207    // We see occasional test failures here due to the stuck tasks not having
208    // timed-out yet, which seems like it should be impossible. To help track
209    // this down we have added logging of the timing information, which we send
210    // directly to stdout so that we get it in opt builds too.
211    std::cout << "End time is " << GetCurrentTime() << std::endl;
212  }
213
214  void OnTimeoutStuck(const int id) {
215    LOG(LS_INFO) << "Timed out task " << id;
216
217    int i;
218    for (i = 0; i < STUCK_TASK_COUNT; ++i) {
219      if (stuck_[i].xlat_ == id) {
220        stuck_[i].timed_out_ = true;
221        stuck_[i].task_ = NULL;
222        break;
223      }
224    }
225
226    // getting a bad ID here is a failure, but let's continue
227    // running to see what else might go wrong
228    EXPECT_LT(i, STUCK_TASK_COUNT);
229  }
230
231  void OnTimeoutHappy(const int id) {
232    int i;
233    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
234      if (happy_[i].xlat_ == id) {
235        happy_[i].timed_out_ = true;
236        happy_[i].task_ = NULL;
237        break;
238      }
239    }
240
241    // getting a bad ID here is a failure, but let's continue
242    // running to see what else might go wrong
243    EXPECT_LT(i, HAPPY_TASK_COUNT);
244  }
245
246  void OnDoneHappy(const int id) {
247    int i;
248    for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
249      if (happy_[i].xlat_ == id) {
250        happy_[i].task_ = NULL;
251        break;
252      }
253    }
254
255    // getting a bad ID here is a failure, but let's continue
256    // running to see what else might go wrong
257    EXPECT_LT(i, HAPPY_TASK_COUNT);
258  }
259
260  void check_passed() {
261    EXPECT_TRUE(task_runner_.AllChildrenDone());
262
263    // make sure none of our happy tasks timed out
264    for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
265      EXPECT_FALSE(happy_[i].timed_out_);
266    }
267
268    // make sure all of our stuck tasks timed out
269    for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
270      EXPECT_TRUE(stuck_[i].timed_out_);
271      if (!stuck_[i].timed_out_) {
272        std::cout << "Stuck task #" << i << " timeout is at "
273            << stuck_[i].task_->timeout_time() << std::endl;
274      }
275    }
276
277    std::cout.flush();
278  }
279
280 private:
281  struct TaskInfo {
282    IdTimeoutTask *task_;
283    bool timed_out_;
284    int xlat_;
285  };
286
287  MyTaskRunner task_runner_;
288  TaskInfo stuck_[STUCK_TASK_COUNT];
289  TaskInfo happy_[HAPPY_TASK_COUNT];
290};
291
292TEST(start_task_test, DISABLED_ON_MAC(Timeout)) {
293  TaskTest task_test;
294  task_test.Start();
295  task_test.check_passed();
296}
297
298// Test for aborting the task while it is running
299
300class AbortTask : public Task {
301 public:
302  explicit AbortTask(TaskParent *parent) : Task(parent) {
303    set_timeout_seconds(1);
304  }
305
306  virtual int ProcessStart() {
307    Abort();
308    return STATE_NEXT;
309  }
310 private:
311  DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
312};
313
314class TaskAbortTest : public sigslot::has_slots<> {
315 public:
316  TaskAbortTest() {}
317
318  // no need to delete any tasks; the task runner owns them
319  ~TaskAbortTest() {}
320
321  void Start() {
322    Task *abort_task = new AbortTask(&task_runner_);
323    abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
324    abort_task->Start();
325
326    // run the task
327    task_runner_.RunTasks();
328  }
329
330 private:
331  void OnTimeout() {
332    FAIL() << "Task timed out instead of aborting.";
333  }
334
335  MyTaskRunner task_runner_;
336  DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
337};
338
339TEST(start_task_test, DISABLED_ON_MAC(Abort)) {
340  TaskAbortTest abort_test;
341  abort_test.Start();
342}
343
344// Test for aborting a task to verify that it does the Wake operation
345// which gets it deleted.
346
347class SetBoolOnDeleteTask : public Task {
348 public:
349  SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
350    : Task(parent),
351      set_when_deleted_(set_when_deleted) {
352    EXPECT_TRUE(NULL != set_when_deleted);
353    EXPECT_FALSE(*set_when_deleted);
354  }
355
356  virtual ~SetBoolOnDeleteTask() {
357    *set_when_deleted_ = true;
358  }
359
360  virtual int ProcessStart() {
361    return STATE_BLOCKED;
362  }
363
364 private:
365  bool* set_when_deleted_;
366  DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
367};
368
369class AbortShouldWakeTest : public sigslot::has_slots<> {
370 public:
371  AbortShouldWakeTest() {}
372
373  // no need to delete any tasks; the task runner owns them
374  ~AbortShouldWakeTest() {}
375
376  void Start() {
377    bool task_deleted = false;
378    Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
379    task_to_abort->Start();
380
381    // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
382    // TaskRunner::RunTasks() immediately which should delete the task.
383    task_to_abort->Abort();
384    EXPECT_TRUE(task_deleted);
385
386    if (!task_deleted) {
387      // avoid a crash (due to referencing a local variable)
388      // if the test fails.
389      task_runner_.RunTasks();
390    }
391  }
392
393 private:
394  void OnTimeout() {
395    FAIL() << "Task timed out instead of aborting.";
396  }
397
398  MyTaskRunner task_runner_;
399  DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
400};
401
402TEST(start_task_test, DISABLED_ON_MAC(AbortShouldWake)) {
403  AbortShouldWakeTest abort_should_wake_test;
404  abort_should_wake_test.Start();
405}
406
407// Validate that TaskRunner's OnTimeoutChange gets called appropriately
408//  * When a task calls UpdateTaskTimeout
409//  * When the next timeout task time, times out
410class TimeoutChangeTest : public sigslot::has_slots<> {
411 public:
412  TimeoutChangeTest()
413    : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
414
415  // no need to delete any tasks; the task runner owns them
416  ~TimeoutChangeTest() {}
417
418  void Start() {
419    for (int i = 0; i < task_count_; ++i) {
420      stuck_tasks_[i] = new StuckTask(&task_runner_);
421      stuck_tasks_[i]->set_timeout_seconds(i + 2);
422      stuck_tasks_[i]->SignalTimeoutId.connect(this,
423                                               &TimeoutChangeTest::OnTimeoutId);
424    }
425
426    for (int i = task_count_ - 1; i >= 0; --i) {
427      stuck_tasks_[i]->Start();
428    }
429    task_runner_.clear_timeout_change();
430
431    // At this point, our timeouts are set as follows
432    // task[0] is 2 seconds, task[1] at 3 seconds, etc.
433
434    stuck_tasks_[0]->set_timeout_seconds(2);
435    // Now, task[0] is 2 seconds, task[1] at 3 seconds...
436    // so timeout change shouldn't be called.
437    EXPECT_FALSE(task_runner_.timeout_change());
438    task_runner_.clear_timeout_change();
439
440    stuck_tasks_[0]->set_timeout_seconds(1);
441    // task[0] is 1 seconds, task[1] at 3 seconds...
442    // The smallest timeout got smaller so timeout change be called.
443    EXPECT_TRUE(task_runner_.timeout_change());
444    task_runner_.clear_timeout_change();
445
446    stuck_tasks_[1]->set_timeout_seconds(2);
447    // task[0] is 1 seconds, task[1] at 2 seconds...
448    // The smallest timeout is still 1 second so no timeout change.
449    EXPECT_FALSE(task_runner_.timeout_change());
450    task_runner_.clear_timeout_change();
451
452    while (task_count_ > 0) {
453      int previous_count = task_count_;
454      task_runner_.PollTasks();
455      if (previous_count != task_count_) {
456        // We only get here when a task times out.  When that
457        // happens, the timeout change should get called because
458        // the smallest timeout is now in the past.
459        EXPECT_TRUE(task_runner_.timeout_change());
460        task_runner_.clear_timeout_change();
461      }
462      Thread::Current()->socketserver()->Wait(500, false);
463    }
464  }
465
466 private:
467  void OnTimeoutId(const int id) {
468    for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
469      if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
470        task_count_--;
471        stuck_tasks_[i] = NULL;
472        break;
473      }
474    }
475  }
476
477  MyTaskRunner task_runner_;
478  StuckTask* (stuck_tasks_[3]);
479  int task_count_;
480  DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
481};
482
483TEST(start_task_test, DISABLED_ON_MAC(TimeoutChange)) {
484  TimeoutChangeTest timeout_change_test;
485  timeout_change_test.Start();
486}
487
488class DeleteTestTaskRunner : public TaskRunner {
489 public:
490  DeleteTestTaskRunner() {
491  }
492  virtual void WakeTasks() { }
493  virtual int64 CurrentTime() {
494    return GetCurrentTime();
495  }
496 private:
497  DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
498};
499
500TEST(unstarted_task_test, DISABLED_ON_MAC(DeleteTask)) {
501  // This test ensures that we don't
502  // crash if a task is deleted without running it.
503  DeleteTestTaskRunner task_runner;
504  HappyTask* happy_task = new HappyTask(&task_runner);
505  happy_task->Start();
506
507  // try deleting the task directly
508  HappyTask* child_happy_task = new HappyTask(happy_task);
509  delete child_happy_task;
510
511  // run the unblocked tasks
512  task_runner.RunTasks();
513}
514
515TEST(unstarted_task_test, DISABLED_ON_MAC(DoNotDeleteTask1)) {
516  // This test ensures that we don't
517  // crash if a task runner is deleted without
518  // running a certain task.
519  DeleteTestTaskRunner task_runner;
520  HappyTask* happy_task = new HappyTask(&task_runner);
521  happy_task->Start();
522
523  HappyTask* child_happy_task = new HappyTask(happy_task);
524  child_happy_task->Start();
525
526  // Never run the tasks
527}
528
529TEST(unstarted_task_test, DISABLED_ON_MAC(DoNotDeleteTask2)) {
530  // This test ensures that we don't
531  // crash if a taskrunner is delete with a
532  // task that has never been started.
533  DeleteTestTaskRunner task_runner;
534  HappyTask* happy_task = new HappyTask(&task_runner);
535  happy_task->Start();
536
537  // Do not start the task.
538  // Note: this leaks memory, so don't do this.
539  // Instead, always run your tasks or delete them.
540  new HappyTask(happy_task);
541
542  // run the unblocked tasks
543  task_runner.RunTasks();
544}
545
546}  // namespace rtc
547