diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn index 11550e8324..69f21336fb 100644 --- a/api/task_queue/BUILD.gn +++ b/api/task_queue/BUILD.gn @@ -39,6 +39,25 @@ rtc_source_set("task_queue_factory") { ] } +rtc_source_set("task_queue_test") { + visibility = [ "*" ] + testonly = true + sources = [ + "task_queue_test.cc", + "task_queue_test.h", + ] + deps = [ + ":task_queue", + ":task_queue_factory", + "../../rtc_base:rtc_event", + "../../rtc_base:rtc_task_queue_api", + "../../rtc_base:timeutils", + "../../test:test_support", + "//third_party/abseil-cpp/absl/memory", + "//third_party/abseil-cpp/absl/strings", + ] +} + rtc_source_set("default_task_queue_factory") { # TODO(bugs.webrtc.org/10191): Make public when implemented for all # supported platforms. diff --git a/api/task_queue/DEPS b/api/task_queue/DEPS index e480aa9bc9..26f86a29ad 100644 --- a/api/task_queue/DEPS +++ b/api/task_queue/DEPS @@ -4,4 +4,7 @@ specific_include_rules = { "task_queue_impl\.h": [ "+rtc_base/task_queue.h", ], + "task_queue_test\.h": [ + "+test/gtest.h", + ], } diff --git a/api/task_queue/task_queue_test.cc b/api/task_queue/task_queue_test.cc new file mode 100644 index 0000000000..cf9c81408e --- /dev/null +++ b/api/task_queue/task_queue_test.cc @@ -0,0 +1,212 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "api/task_queue/task_queue_test.h" + +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "rtc_base/event.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/timeutils.h" + +namespace webrtc { +namespace { + +std::unique_ptr CreateTaskQueue( + TaskQueueFactory* factory, + absl::string_view task_queue_name, + TaskQueueFactory::Priority priority = TaskQueueFactory::Priority::NORMAL) { + return factory->CreateTaskQueue(task_queue_name, priority); +} + +TEST_P(TaskQueueTest, Construct) { + auto queue = CreateTaskQueue(GetParam(), "Construct"); + EXPECT_FALSE(queue->IsCurrent()); +} + +TEST_P(TaskQueueTest, PostAndCheckCurrent) { + rtc::Event event; + auto queue = CreateTaskQueue(GetParam(), "PostAndCheckCurrent"); + + // We're not running a task, so there shouldn't be a current queue. + EXPECT_FALSE(queue->IsCurrent()); + EXPECT_FALSE(TaskQueueBase::Current()); + + queue->PostTask(rtc::NewClosure([&event, &queue] { + EXPECT_TRUE(queue->IsCurrent()); + event.Set(); + })); + EXPECT_TRUE(event.Wait(1000)); +} + +TEST_P(TaskQueueTest, PostCustomTask) { + rtc::Event ran; + auto queue = CreateTaskQueue(GetParam(), "PostCustomImplementation"); + + class CustomTask : public QueuedTask { + public: + explicit CustomTask(rtc::Event* ran) : ran_(ran) {} + + private: + bool Run() override { + ran_->Set(); + return false; // Do not allow the task to be deleted by the queue. + } + + rtc::Event* const ran_; + } my_task(&ran); + + queue->PostTask(absl::WrapUnique(&my_task)); + EXPECT_TRUE(ran.Wait(1000)); +} + +TEST_P(TaskQueueTest, PostDelayedZero) { + rtc::Event event; + auto queue = CreateTaskQueue(GetParam(), "PostDelayedZero"); + + queue->PostDelayedTask(rtc::NewClosure([&event] { event.Set(); }), 0); + EXPECT_TRUE(event.Wait(1000)); +} + +TEST_P(TaskQueueTest, PostFromQueue) { + rtc::Event event; + auto queue = CreateTaskQueue(GetParam(), "PostFromQueue"); + + queue->PostTask(rtc::NewClosure([&event, &queue] { + queue->PostTask(rtc::NewClosure([&event] { event.Set(); })); + })); + EXPECT_TRUE(event.Wait(1000)); +} + +TEST_P(TaskQueueTest, PostDelayed) { + rtc::Event event; + auto queue = CreateTaskQueue(GetParam(), "PostDelayed", + TaskQueueFactory::Priority::HIGH); + + int64_t start = rtc::TimeMillis(); + queue->PostDelayedTask(rtc::NewClosure([&event, &queue] { + EXPECT_TRUE(queue->IsCurrent()); + event.Set(); + }), + 100); + EXPECT_TRUE(event.Wait(1000)); + int64_t end = rtc::TimeMillis(); + // These tests are a little relaxed due to how "powerful" our test bots can + // be. Most recently we've seen windows bots fire the callback after 94-99ms, + // which is why we have a little bit of leeway backwards as well. + EXPECT_GE(end - start, 90u); + EXPECT_NEAR(end - start, 190u, 100u); // Accept 90-290. +} + +TEST_P(TaskQueueTest, PostMultipleDelayed) { + auto queue = CreateTaskQueue(GetParam(), "PostMultipleDelayed"); + + std::vector events(100); + for (int i = 0; i < 100; ++i) { + rtc::Event* event = &events[i]; + queue->PostDelayedTask(rtc::NewClosure([event, &queue] { + EXPECT_TRUE(queue->IsCurrent()); + event->Set(); + }), + i); + } + + for (rtc::Event& e : events) + EXPECT_TRUE(e.Wait(1000)); +} + +TEST_P(TaskQueueTest, PostDelayedAfterDestruct) { + rtc::Event run; + rtc::Event deleted; + auto queue = CreateTaskQueue(GetParam(), "PostDelayedAfterDestruct"); + queue->PostDelayedTask( + rtc::NewClosure([&run] { run.Set(); }, [&deleted] { deleted.Set(); }), + 100); + // Destroy the queue. + queue = nullptr; + // Task might outlive the TaskQueue, but still should be deleted. + EXPECT_TRUE(deleted.Wait(200)); + EXPECT_FALSE(run.Wait(0)); // and should not run. +} + +TEST_P(TaskQueueTest, PostAndReuse) { + rtc::Event event; + auto post_queue = CreateTaskQueue(GetParam(), "PostQueue"); + auto reply_queue = CreateTaskQueue(GetParam(), "ReplyQueue"); + + int call_count = 0; + + class ReusedTask : public QueuedTask { + public: + ReusedTask(int* counter, TaskQueueBase* reply_queue, rtc::Event* event) + : counter_(*counter), reply_queue_(reply_queue), event_(*event) { + EXPECT_EQ(counter_, 0); + } + + private: + bool Run() override { + if (++counter_ == 1) { + reply_queue_->PostTask(absl::WrapUnique(this)); + // At this point, the object is owned by reply_queue_ and it's + // theoratically possible that the object has been deleted (e.g. if + // posting wasn't possible). So, don't touch any member variables here. + + // Indicate to the current queue that ownership has been transferred. + return false; + } else { + EXPECT_EQ(counter_, 2); + EXPECT_TRUE(reply_queue_->IsCurrent()); + event_.Set(); + return true; // Indicate that the object should be deleted. + } + } + + int& counter_; + TaskQueueBase* const reply_queue_; + rtc::Event& event_; + }; + + auto task = + absl::make_unique(&call_count, reply_queue.get(), &event); + post_queue->PostTask(std::move(task)); + EXPECT_TRUE(event.Wait(1000)); +} + +// Tests posting more messages than a queue can queue up. +// In situations like that, tasks will get dropped. +TEST_P(TaskQueueTest, PostALot) { + // To destruct the event after the queue has gone out of scope. + rtc::Event event; + + int tasks_executed = 0; + int tasks_cleaned_up = 0; + static const int kTaskCount = 0xffff; + + { + auto queue = CreateTaskQueue(GetParam(), "PostALot"); + + // On linux, the limit of pending bytes in the pipe buffer is 0xffff. + // So here we post a total of 0xffff+1 messages, which triggers a failure + // case inside of the libevent queue implementation. + + queue->PostTask( + rtc::NewClosure([&event] { event.Wait(rtc::Event::kForever); })); + for (int i = 0; i < kTaskCount; ++i) + queue->PostTask( + rtc::NewClosure([&tasks_executed] { ++tasks_executed; }, + [&tasks_cleaned_up] { ++tasks_cleaned_up; })); + event.Set(); // Unblock the first task. + } + + EXPECT_GE(tasks_cleaned_up, tasks_executed); + EXPECT_EQ(tasks_cleaned_up, kTaskCount); +} + +} // namespace +} // namespace webrtc diff --git a/api/task_queue/task_queue_test.h b/api/task_queue/task_queue_test.h new file mode 100644 index 0000000000..300d6f3b3e --- /dev/null +++ b/api/task_queue/task_queue_test.h @@ -0,0 +1,32 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef API_TASK_QUEUE_TASK_QUEUE_TEST_H_ +#define API_TASK_QUEUE_TASK_QUEUE_TEST_H_ + +#include "api/task_queue/task_queue_factory.h" +#include "test/gtest.h" + +namespace webrtc { + +// Suite of tests to verify TaskQueue implementation with. +// Example usage: +// +// namespace { +// +// using ::webrtc::TaskQueueTest; +// webrtc::TaskQueueFactory* MyFactory(); +// INSTANTIATE_TEST_SUITE_P(My, TaskQueueTest, ::testing::Values(MyFactory())); +// +// } // namespace +class TaskQueueTest : public ::testing::TestWithParam {}; + +} // namespace webrtc + +#endif // API_TASK_QUEUE_TASK_QUEUE_TEST_H_