OLD | NEW |
| (Empty) |
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/arraysize.h" | |
24 #include "webrtc/base/constructormagic.h" | |
25 #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 | |
32 namespace rtc { | |
33 | |
34 static int64_t GetCurrentTime() { | |
35 return TimeMillis() * 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 | |
47 class 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 | |
71 class StuckTask : public IdTimeoutTask { | |
72 public: | |
73 explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {} | |
74 virtual int ProcessStart() { | |
75 return STATE_BLOCKED; | |
76 } | |
77 }; | |
78 | |
79 class 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 | |
98 class MyTaskRunner : public TaskRunner { | |
99 public: | |
100 virtual void WakeTasks() { RunTasks(); } | |
101 virtual int64_t CurrentTime() { return GetCurrentTime(); } | |
102 | |
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 | |
128 class 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; | |
219 stuck_[i].task_ = NULL; | |
220 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; | |
234 happy_[i].task_ = NULL; | |
235 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) { | |
248 happy_[i].task_ = NULL; | |
249 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 " | |
271 << stuck_[i].task_->timeout_time() << std::endl; | |
272 } | |
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 | |
290 TEST(start_task_test, Timeout) { | |
291 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 | |
298 class 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: | |
309 RTC_DISALLOW_COPY_AND_ASSIGN(AbortTask); | |
310 }; | |
311 | |
312 class 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_; | |
334 RTC_DISALLOW_COPY_AND_ASSIGN(TaskAbortTest); | |
335 }; | |
336 | |
337 TEST(start_task_test, Abort) { | |
338 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 | |
345 class SetBoolOnDeleteTask : public Task { | |
346 public: | |
347 SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted) | |
348 : Task(parent), | |
349 set_when_deleted_(set_when_deleted) { | |
350 EXPECT_TRUE(NULL != set_when_deleted); | |
351 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_; | |
364 RTC_DISALLOW_COPY_AND_ASSIGN(SetBoolOnDeleteTask); | |
365 }; | |
366 | |
367 class 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_; | |
397 RTC_DISALLOW_COPY_AND_ASSIGN(AbortShouldWakeTest); | |
398 }; | |
399 | |
400 TEST(start_task_test, AbortShouldWake) { | |
401 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 | |
408 class TimeoutChangeTest : public sigslot::has_slots<> { | |
409 public: | |
410 TimeoutChangeTest() | |
411 : task_count_(arraysize(stuck_tasks_)) {} | |
412 | |
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) { | |
466 for (size_t i = 0; i < arraysize(stuck_tasks_); ++i) { | |
467 if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) { | |
468 task_count_--; | |
469 stuck_tasks_[i] = NULL; | |
470 break; | |
471 } | |
472 } | |
473 } | |
474 | |
475 MyTaskRunner task_runner_; | |
476 StuckTask* (stuck_tasks_[3]); | |
477 int task_count_; | |
478 RTC_DISALLOW_COPY_AND_ASSIGN(TimeoutChangeTest); | |
479 }; | |
480 | |
481 TEST(start_task_test, TimeoutChange) { | |
482 TimeoutChangeTest timeout_change_test; | |
483 timeout_change_test.Start(); | |
484 } | |
485 | |
486 class DeleteTestTaskRunner : public TaskRunner { | |
487 public: | |
488 DeleteTestTaskRunner() { | |
489 } | |
490 virtual void WakeTasks() { } | |
491 virtual int64_t CurrentTime() { return GetCurrentTime(); } | |
492 private: | |
493 RTC_DISALLOW_COPY_AND_ASSIGN(DeleteTestTaskRunner); | |
494 }; | |
495 | |
496 TEST(unstarted_task_test, DeleteTask) { | |
497 // 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 | |
511 TEST(unstarted_task_test, DoNotDeleteTask1) { | |
512 // 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 | |
525 TEST(unstarted_task_test, DoNotDeleteTask2) { | |
526 // 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 | |
OLD | NEW |