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