From 38824e721ebaa3f50e443e1ea4b47d34030e4703 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Mon, 7 May 2018 12:22:57 -0400 Subject: C++ migration: add AsyncQueue, the C++ version of FSTDispatchQueue (#1176) AsyncQueue is a queue that executes given operations asynchronously, enforcing that only a single operation is executing at any given time, and that in-progress operations don't spawn more operations. The actual execution is delegated to a platform-specific executor. Executor is an interface for a FIFO queue that executes given operations serially. Two implementations of Executor, one using libdispatch and the other using C++11 standard library, are provided. AsyncQueue is not used anywhere in the code base at this point. --- .../firebase/firestore/util/executor_std_test.cc | 240 +++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 Firestore/core/test/firebase/firestore/util/executor_std_test.cc (limited to 'Firestore/core/test/firebase/firestore/util/executor_std_test.cc') diff --git a/Firestore/core/test/firebase/firestore/util/executor_std_test.cc b/Firestore/core/test/firebase/firestore/util/executor_std_test.cc new file mode 100644 index 0000000..43cad60 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/util/executor_std_test.cc @@ -0,0 +1,240 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/test/firebase/firestore/util/executor_test.h" + +#include // NOLINT(build/c++11) +#include +#include // NOLINT(build/c++11) +#include +#include // NOLINT(build/c++11) + +#include "Firestore/core/src/firebase/firestore/util/executor_std.h" +#include "Firestore/core/test/firebase/firestore/util/async_tests_util.h" +#include "absl/memory/memory.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +namespace chr = std::chrono; +using async::Schedule; + +class ScheduleTest : public ::testing::Test { + public: + ScheduleTest() : start_time{now()} { + } + + using ScheduleT = Schedule; + + ScheduleT schedule; + ScheduleT::TimePoint start_time; +}; + +// Schedule tests + +TEST_F(ScheduleTest, PopIfDue_Immediate) { + EXPECT_FALSE(schedule.PopIfDue().has_value()); + + // Push values in a deliberately non-sorted order. + schedule.Push(3, start_time); + schedule.Push(1, start_time); + schedule.Push(2, start_time); + EXPECT_FALSE(schedule.empty()); + EXPECT_EQ(schedule.size(), 3u); + + EXPECT_EQ(schedule.PopIfDue().value(), 3); + EXPECT_EQ(schedule.PopIfDue().value(), 1); + EXPECT_EQ(schedule.PopIfDue().value(), 2); + EXPECT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_TRUE(schedule.empty()); + EXPECT_EQ(schedule.size(), 0u); +} + +TEST_F(ScheduleTest, PopIfDue_Delayed) { + schedule.Push(1, start_time + chr::milliseconds(5)); + schedule.Push(2, start_time + chr::milliseconds(3)); + schedule.Push(3, start_time + chr::milliseconds(1)); + + EXPECT_FALSE(schedule.PopIfDue().has_value()); + std::this_thread::sleep_for(chr::milliseconds(5)); + + EXPECT_EQ(schedule.PopIfDue().value(), 3); + EXPECT_EQ(schedule.PopIfDue().value(), 2); + EXPECT_EQ(schedule.PopIfDue().value(), 1); + EXPECT_TRUE(schedule.empty()); +} + +TEST_F(ScheduleTest, PopBlocking) { + schedule.Push(1, start_time + chr::milliseconds(3)); + EXPECT_FALSE(schedule.PopIfDue().has_value()); + + EXPECT_EQ(schedule.PopBlocking(), 1); + EXPECT_GE(now(), start_time + chr::milliseconds(3)); + EXPECT_TRUE(schedule.empty()); +} + +TEST_F(ScheduleTest, RemoveIf) { + schedule.Push(1, start_time); + schedule.Push(2, now() + chr::minutes(1)); + + auto maybe_removed = schedule.RemoveIf([](const int v) { return v == 1; }); + EXPECT_TRUE(maybe_removed.has_value()); + EXPECT_EQ(maybe_removed.value(), 1); + + // Non-existent value. + maybe_removed = schedule.RemoveIf([](const int v) { return v == 1; }); + EXPECT_FALSE(maybe_removed.has_value()); + + maybe_removed = schedule.RemoveIf([](const int v) { return v == 2; }); + EXPECT_TRUE(maybe_removed.has_value()); + EXPECT_EQ(maybe_removed.value(), 2); + EXPECT_TRUE(schedule.empty()); +} + +TEST_F(ScheduleTest, Ordering) { + schedule.Push(11, start_time + chr::milliseconds(5)); + schedule.Push(1, start_time); + schedule.Push(2, start_time); + schedule.Push(9, start_time + chr::milliseconds(2)); + schedule.Push(3, start_time); + schedule.Push(10, start_time + chr::milliseconds(3)); + schedule.Push(12, start_time + chr::milliseconds(5)); + schedule.Push(4, start_time); + schedule.Push(5, start_time); + schedule.Push(6, start_time); + schedule.Push(8, start_time + chr::milliseconds(1)); + schedule.Push(7, start_time); + + std::vector values; + while (!schedule.empty()) { + values.push_back(schedule.PopBlocking()); + } + const std::vector expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + EXPECT_EQ(values, expected); +} + +TEST_F(ScheduleTest, AddingEntryUnblocksEmptyQueue) { + const auto future = std::async(std::launch::async, [&] { + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 1); + }); + + std::this_thread::sleep_for(chr::milliseconds(5)); + schedule.Push(1, start_time); + ABORT_ON_TIMEOUT(future); +} + +TEST_F(ScheduleTest, PopBlockingUnblocksOnNewPastDueEntries) { + const auto far_away = start_time + chr::seconds(10); + schedule.Push(5, far_away); + + const auto future = std::async(std::launch::async, [&] { + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 3); + }); + + std::this_thread::sleep_for(chr::milliseconds(5)); + schedule.Push(3, start_time); + ABORT_ON_TIMEOUT(future); +} + +TEST_F(ScheduleTest, PopBlockingAdjustsWaitTimeOnNewSoonerEntries) { + const auto far_away = start_time + chr::seconds(10); + schedule.Push(5, far_away); + + const auto future = std::async(std::launch::async, [&] { + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 3); + // Make sure schedule hasn't been waiting longer than necessary. + EXPECT_LT(now(), far_away); + }); + + std::this_thread::sleep_for(chr::milliseconds(5)); + schedule.Push(3, start_time + chr::milliseconds(100)); + ABORT_ON_TIMEOUT(future); +} + +TEST_F(ScheduleTest, PopBlockingCanReadjustTimeIfSeveralElementsAreAdded) { + const auto far_away = start_time + chr::seconds(5); + const auto very_far_away = start_time + chr::seconds(10); + schedule.Push(3, very_far_away); + + const auto future = std::async(std::launch::async, [&] { + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 1); + EXPECT_LT(now(), far_away); + }); + + std::this_thread::sleep_for(chr::milliseconds(5)); + schedule.Push(2, far_away); + std::this_thread::sleep_for(chr::milliseconds(1)); + schedule.Push(1, start_time + chr::milliseconds(100)); + ABORT_ON_TIMEOUT(future); +} + +TEST_F(ScheduleTest, PopBlockingNoticesRemovals) { + const auto future = std::async(std::launch::async, [&] { + schedule.Push(1, start_time + chr::milliseconds(50)); + schedule.Push(2, start_time + chr::milliseconds(100)); + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 2); + }); + + while (schedule.empty()) { + std::this_thread::sleep_for(chr::milliseconds(1)); + } + const auto maybe_removed = + schedule.RemoveIf([](const int v) { return v == 1; }); + EXPECT_EQ(maybe_removed.value(), 1); + ABORT_ON_TIMEOUT(future); +} + +TEST_F(ScheduleTest, PopBlockingIsNotAffectedByIrrelevantRemovals) { + const auto future = std::async(std::launch::async, [&] { + schedule.Push(1, start_time + chr::milliseconds(50)); + schedule.Push(2, start_time + chr::seconds(10)); + ASSERT_FALSE(schedule.PopIfDue().has_value()); + EXPECT_EQ(schedule.PopBlocking(), 1); + }); + + while (schedule.empty()) { + std::this_thread::sleep_for(chr::milliseconds(1)); + } + const auto maybe_removed = + schedule.RemoveIf([](const int v) { return v == 2; }); + EXPECT_EQ(maybe_removed.value(), 2); + ABORT_ON_TIMEOUT(future); +} + +// ExecutorStd tests + +namespace { + +inline std::unique_ptr ExecutorFactory() { + return absl::make_unique(); +} + +} // namespace + +INSTANTIATE_TEST_CASE_P(ExecutorTestStd, + ExecutorTest, + ::testing::Values(ExecutorFactory)); + +} // namespace util +} // namespace firestore +} // namespace firebase -- cgit v1.2.3