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 |