Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: test/AsyncExecutor.cpp

Issue 29706560: Issue 5179 - Implement asynchronous executor with a controllable lifetime (Closed) Base URL: https://github.com/adblockplus/libadblockplus.git
Patch Set: Created Feb. 23, 2018, 10:29 a.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« src/Platform.cpp ('K') | « src/Platform.cpp ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: test/AsyncExecutor.cpp
diff --git a/test/AsyncExecutor.cpp b/test/AsyncExecutor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..70ad057f6f7847ad99ed3fa69b33d2ca9ad88542
--- /dev/null
+++ b/test/AsyncExecutor.cpp
@@ -0,0 +1,236 @@
+/*
+ * This file is part of Adblock Plus <https://adblockplus.org/>,
+ * Copyright (C) 2006-present eyeo GmbH
+ *
+ * Adblock Plus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Adblock Plus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <future>
+#include <gtest/gtest.h>
+#include <AdblockPlus/AsyncExecutor.h>
+
+using namespace AdblockPlus;
+
+
+// For present gtest does not provide public API to have value- and type-
+// parameterized tests, so the approach is to accumalte the common code in
+// BaseAsyncExecutorTest, then define it with different types and then for each
+// type instantiate value-parameterized tests and call the common test body.
+
+template<typename Executor>
+struct BaseAsyncExecutorTestTraits;
+
+typedef ::testing::tuple<uint16_t, // number of procucers
+ uint16_t // number of tasks issued by each producer
+ > BaseAsyncExecutorTestParams;
+
+template<typename TExecutor>
+class BaseAsyncExecutorTest : public ::testing::TestWithParam<BaseAsyncExecutorTestParams>
+{
+public:
+ typedef typename ::testing::tuple_element<0, ParamType>::type ProducersNumber;
+ typedef typename ::testing::tuple_element<1, ParamType>::type ProducerTasksNumber;
+
+ typedef BaseAsyncExecutorTestTraits<TExecutor> Traits;
+ typedef typename Traits::Executor Executor;
+
+protected:
+ void SetUp() override
+ {
+ testThreadID = std::this_thread::get_id();
+ }
+
+ void MultithreadedCallsTest();
+
+ std::thread::id testThreadID;
+};
+
+template<>
+struct BaseAsyncExecutorTestTraits<ActiveObject>
+{
+ typedef ActiveObject Executor;
+ typedef std::vector<uint32_t> PayloadResults;
+
+ static void Dispatch(typename BaseAsyncExecutorTest<Executor>::Executor& executor, std::function<void()> call)
+ {
+ executor.Post(std::move(call));
+ }
+};
+
+template<>
+struct BaseAsyncExecutorTestTraits<AsyncExecutor>
+{
+ typedef AsyncExecutor Executor;
+
+ // The API of this class is not thread-safe! It's merely a helper to
+ // generalize particular tests, don't use it anywhere else.
+ class PayloadResults : public SynchronizedCollection<std::vector<uint32_t>>
+ {
+ public:
+ typename Container::size_type size() const
+ {
+ return collection.size();
+ }
+ typename Container::iterator begin()
+ {
+ return collection.begin();
+ }
+ typename Container::iterator end()
+ {
+ return collection.end();
+ }
+ typename Container::const_reference operator[](typename Container::size_type pos) const
+ {
+ return collection[pos];
+ }
+ };
+
+ static void Dispatch(typename BaseAsyncExecutorTest<Executor>::Executor& executor, std::function<void()> call)
+ {
+ executor.Dispatch(std::move(call));
+ }
+};
+
+template<typename Executor>
+void BaseAsyncExecutorTest<Executor>::MultithreadedCallsTest()
+{
+ typename Traits::PayloadResults results;
+
+ std::promise<void> prepare;
+ std::shared_future<void> asyncPrepared = prepare.get_future();
+ const ProducersNumber producersNumber = ::testing::get<0>(GetParam());
+ const ProducerTasksNumber producerTasksNumber = ::testing::get<1>(GetParam());
+ const uint32_t totalTasksNumber = producersNumber * producerTasksNumber;
+ {
+ Executor executor;
+ {
+ // Defining AsyncExecutor (async) after tested Executor ensures that all
+ // producers finish before destroying the Executor. Otherwise there could
+ // be cases when the Executor is already destroyed but payload task is not
+ // dispatched yet, what would result in a data race.
+ // It seems at least strange to use AsyncExecutor in the test of
+ // AsyncExecutor, but perhaps it can be considered as addition testing.
+ AsyncExecutor async;
+ for (ProducersNumber producer = 0; producer < producersNumber; ++producer)
+ {
+ auto producerTask = [producerTasksNumber, this, &executor, producer, &results](std::thread::id producerThreadID)
+ {
+ for (ProducerTasksNumber task = 0; task < producerTasksNumber; ++task)
+ {
+ auto id = producer * producerTasksNumber + task;
+ Traits::Dispatch(executor, /*payload task*/[this, id, &results, producerThreadID]
+ {
+ results.push_back(id);
+ EXPECT_NE(producerThreadID, std::this_thread::get_id());
+ EXPECT_NE(testThreadID, std::this_thread::get_id());
+ });
+ }
+ };
+ async.Dispatch([this, producerTask, asyncPrepared]
+ {
+ const auto producerThreadID = std::this_thread::get_id();
+ EXPECT_NE(testThreadID, producerThreadID);
+ asyncPrepared.wait();
+ producerTask(producerThreadID);
+ });
+ std::this_thread::yield();
+ }
+ // it's not ideal because some threads can still have not reached
+ // asyncPrepared.wait() but it's good enough, in particular yield should
+ // be helpful there.
+ prepare.set_value();
+ }
+ }
+
+ std::sort(results.begin(), results.end());
+ // ensure that all tasks are executed by finding their IDs
+ ASSERT_EQ(totalTasksNumber, results.size());
+ for (uint16_t id = 0; id < totalTasksNumber; ++id) {
+ EXPECT_EQ(id, results[id]);
+ }
+}
+
+namespace
+{
+
+ std::string humanReadbleParams(const ::testing::TestParamInfo<BaseAsyncExecutorTestParams> paramInfo)
+ {
+ std::stringstream ss;
+ ss << "producers_" << ::testing::get<0>(paramInfo.param) << "_tasks_" << ::testing::get<1>(paramInfo.param);
+ return ss.str();
+ }
+
+ // The reason for such splitting for different implementations and different
+ // platforms is the limited number of threads and time to run tests on
+ // travis-ci.
+ // E.g. 100 x 1000 is already too much, so the generators are split.
+ // 0 - 10 1 - 100, 1000
+ const auto MultithreadedCallsGenerator1 = ::testing::Combine(::testing::Values(0, 1, 2, 3, 5, 10),
+ ::testing::Values(1, 2, 3, 5, 10, 100, 1000));
+ // 100, 1 - 100
+ const auto MultithreadedCallsGenerator2 = ::testing::Combine(::testing::Values(100),
+ ::testing::Values(1, 2, 3, 5, 10, 100));
+ // 0 - 10 2000|4000
+ const auto MultithreadedCallsGenerator3 = ::testing::Combine(::testing::Values(0, 1, 2, 3, 5, 10),
+#ifdef WIN32
+ ::testing::Values(4000)
+#else
+ ::testing::Values(2000)
+#endif
+ );
+
+ typedef BaseAsyncExecutorTest<ActiveObject> ActiveObjectTest;
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber1,
+ ActiveObjectTest,
+ MultithreadedCallsGenerator1,
+ humanReadbleParams);
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber2,
+ ActiveObjectTest,
+ MultithreadedCallsGenerator2,
+ humanReadbleParams);
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber3,
+ ActiveObjectTest,
+ MultithreadedCallsGenerator3,
+ humanReadbleParams);
+
+ TEST_P(ActiveObjectTest, MultithreadedCalls)
+ {
+ MultithreadedCallsTest();
+ }
+
+
+ typedef BaseAsyncExecutorTest<AsyncExecutor> AsyncExecutorTest;
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber1,
+ AsyncExecutorTest,
+ MultithreadedCallsGenerator1,
+ humanReadbleParams);
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber2,
+ AsyncExecutorTest,
+ MultithreadedCallsGenerator2,
+ humanReadbleParams);
+
+ INSTANTIATE_TEST_CASE_P(DifferentProducersNumber3,
+ AsyncExecutorTest,
+ MultithreadedCallsGenerator3,
+ humanReadbleParams);
+
+ TEST_P(AsyncExecutorTest, MultithreadedCalls)
+ {
+ MultithreadedCallsTest();
+ }
+}
« src/Platform.cpp ('K') | « src/Platform.cpp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld