| Index: test/TimeoutTest.cpp |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/test/TimeoutTest.cpp |
| @@ -0,0 +1,220 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-2017 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 "../src/Timeout.h" |
| +#include "BaseJsTest.h" |
| +#include "PausePoint.h" |
| +#include <thread> |
| + |
| +TEST(Timeout, Instance) |
| +{ |
| + DelayedExecutor x([]() {}); |
| +} |
| + |
| +TEST(Timeout, ConstructorBodyMayNotBeNull) |
| +{ |
| + auto nullBody = std::function<void()>(); |
| + ASSERT_ANY_THROW({ DelayedExecutor x(nullBody); }); |
| +} |
| + |
| +TEST(Timeout, ImmediateRunSimple) |
| +{ |
| + int count = 0; |
| + DelayedExecutor x([&]() { ++count; }); |
| + x.RunImmediately(); |
| + EXPECT_EQ(1, count); |
| +} |
| + |
| +TEST(Timeout, DelayedRunSimple) |
| +{ |
| + int count = 0; |
| + DelayedExecutor x([&]() { ++count; }); |
| + x.RunWithDelay(std::chrono::milliseconds(1)); |
| + EXPECT_EQ(1, count); |
| +} |
| + |
| +/** |
| + * Test class exposes each aspect point in the constructor. |
| + */ |
| +class DelayedExecutorTestAspect |
| + : public AspectBase<DelayedExecutorTestAspect> |
| +{ |
| + std::function<void()> runnableToSleeping; |
| + std::function<void()> runFinish; |
| + |
| +public: |
| + template<class LockType> |
| + void RunnableToSleeping(LockType& lock) |
| + { |
| + AntiLock<LockType> x(lock); |
| + runnableToSleeping(); |
| + }; |
| + |
| + void RunFinish() |
| + { |
| + runFinish(); |
| + } |
| + |
| + DelayedExecutorTestAspect(std::function<void()> runnableToSleeping, |
| + std::function<void()> runFinish) |
| + : runnableToSleeping(runnableToSleeping), |
| + runFinish(runFinish) |
| + {} |
| +}; |
| + |
| +class DelayedExecutorForTesting |
| + : public DelayedExecutorAspected<DelayedExecutorTestAspect> |
| +{ |
| +public: |
| + typedef DelayedExecutorAspected<DelayedExecutorTestAspect> Base; |
| + DelayedExecutorForTesting(std::function<void()> body, |
| + std::function<void()> runnableToSleeping, |
| + std::function<void()> runFinish) |
| + : Base(DelayedExecutorTestAspect(runnableToSleeping, runFinish), body) |
| + {} |
| +}; |
| + |
| +/** |
| + * Exercises an ordinary sequence for RunWithDelay(). |
| + * This version tracks the sequence through test aspect points. |
| + */ |
| +TEST(Timeout, DelayedRunAspected) |
| +{ |
| + int sequence = 0; |
| + auto body = [&]() |
| + { |
| + ASSERT_EQ(1, sequence); |
| + sequence = 2; |
| + }; |
| + auto runnableToSleeping = [&]() |
| + { |
| + ASSERT_EQ(0, sequence); |
| + sequence = 1; |
| + }; |
| + auto runFinish = [&]() |
| + { |
| + ASSERT_EQ(2, sequence); |
| + sequence = 3; |
| + }; |
| + |
| + DelayedExecutorForTesting x(body, runnableToSleeping, runFinish); |
| + ASSERT_EQ(0, sequence); |
| + x.RunWithDelay(std::chrono::milliseconds(1)); |
| + ASSERT_EQ(3, sequence); |
| +} |
| + |
| +/** |
| + * Exercises an ordinary sequence for RunWithDelay(). |
| + * This version is in a separate thread. |
| + */ |
| +TEST(Timeout, DelayedRunAspectedThreaded) |
| +{ |
| + PausePoint pp; |
| + int sequence = 0; |
| + auto body = [&]() |
| + { |
| + sequence = 3; |
| + pp.Pause(); |
| + }; |
| + auto runnableToSleeping = [&]() |
| + { |
| + sequence = 2; |
| + pp.Pause(); |
| + }; |
| + auto runFinish = [&]() |
| + { |
| + sequence = 4; |
| + pp.Pause(); |
| + }; |
| + |
| + std::thread th([&]() |
| + { |
| + DelayedExecutorForTesting x(body, runnableToSleeping, runFinish); |
| + sequence = 1; |
| + pp.Pause(); |
| + x.RunWithDelay(std::chrono::milliseconds(1)); |
| + sequence = 5; |
| + pp.Pause(); |
| + sequence = 6; |
| + }); |
| + for (int j = 1; j <= 5; ++j) { |
| + pp.WaitUntilPaused(); |
| + ASSERT_EQ(j, sequence); |
| + pp.Resume(); |
| + } |
| + th.join(); |
| + ASSERT_EQ(6, sequence); |
| +} |
| + |
| +TEST(Timeout, DelayedRunInterrupted) |
| +{ |
| + PausePoint pp; |
| + int sequence = 0; |
| + auto body = [&]() |
| + { |
| + sequence = 3; |
| + pp.Pause(); |
| + }; |
| + auto runnableToSleeping = [&]() |
| + { |
| + sequence = 2; |
| + pp.Pause(); |
| + }; |
| + auto runFinish = [&]() |
| + { |
| + sequence = 4; |
| + pp.Pause(); |
| + }; |
| + |
| + DelayedExecutorForTesting x(body, runnableToSleeping, runFinish); |
| + ASSERT_EQ(0, sequence); |
| + std::thread th([&]() |
| + { |
| + sequence = 1; |
| + pp.Pause(); |
| + x.RunWithDelay(std::chrono::minutes(2)); // long delay to interrupt |
| + sequence = 5; |
| + pp.Pause(); |
| + sequence = 6; |
| + }); |
| + pp.WaitUntilPaused(); |
| + ASSERT_EQ(1, sequence); |
| + pp.Resume(); |
| + pp.WaitUntilPaused(); |
| + ASSERT_EQ(2, sequence); |
| + /* |
| + * We are at an aspect point where we're about to wait. |
| + * We don't have the capability of interrupting the wait itself, |
| + * so we rely on a heuristic. |
| + * We resume and sleep for a bit to give the scheduler a chance to wake up |
| + * the thread and for the condition variable to begin waiting. |
| + * It's possible to get occasional failures with this algorithm. |
| + */ |
| + pp.ResumeAndSleep(2); |
| + // Assume the condition variable is waiting. |
| + x.ForestallExecution(); |
| + // We skip sequence 3, where the body executes. |
| + // We skip sequence 4, where DelayedExecutor changes state after execution |
| + pp.WaitUntilPaused(); |
| + ASSERT_EQ(5, sequence); |
| + pp.Resume(); |
| + th.join(); |
| + ASSERT_EQ(6, sequence); |
| +} |
| + |
| + |
| + |