| Index: test/plugin/DetachedInitializationTest.cpp |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/test/plugin/DetachedInitializationTest.cpp |
| @@ -0,0 +1,198 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-2016 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 <exception> |
| +#include <gtest/gtest.h> |
| +#include "../../src/plugin/DetachedInitialization.h" |
| + |
| +// Initializer that does nothing |
| +void NullFunction() |
| +{} |
| + |
| +// Initializer that always throws |
| +const std::string failMessage = "initializer failed on purpose"; |
| +void FailFunction() |
| +{ |
| + throw std::runtime_error(failMessage); |
| +} |
| + |
| +/* |
| + * The destructor has to be able to block to wait for the initializer |
| + * This test ensures that it doesn't block unnecessarily. |
| + */ |
| +TEST(DetachedInitializer, DestructorDoesNotBlockAfterNoActivity) |
| +{ |
| + DetachedInitializer<NullFunction> x; |
| +} |
| + |
| +TEST(DetachedInitializer, NullInstance) |
| +{ |
| + ASSERT_NO_THROW( |
| + { |
| + DetachedInitializer<NullFunction> x; |
| + x.SpawnInitializer(); |
| + x.EnsureInitialized(); |
| + } |
| + ); |
| +} |
| + |
| +/* |
| + * This test illustrates a defect in Google Test. |
| + * This test fails with a message that the test threw an exception. |
| + * The exception message is the one thrown by FailFunction. |
| + * It does not fail, importantly, because SpawnInitializer() throws. |
| + * This can be verified in the debugger. |
| + * |
| + * It's disabled in the present code base so it doesn't break the unit test. |
| + * Check this test again when we upgrade past GTest 1.60 and the VS 2012 toolset. |
| + */ |
| +TEST(DetachedInitializer, DISABLED_EnsureInitializedThrowingGtestDefectIllustration) |
| +{ |
| + DetachedInitializer<FailFunction> x; |
| + bool threw = false; |
| + try |
| + { |
| + x.SpawnInitializer(); |
| + } |
| + catch (...) |
| + { |
| + threw = true; |
| + FAIL() << "SpawnInitializer() threw"; |
| + } |
| + ASSERT_FALSE(threw); |
| +} |
| + |
| +/* |
| + * This test fails because of a defect in Google Test. |
| + * It's disabled at present, but should be enabled when the previous test is working. |
| + */ |
| +TEST(DetachedInitializer, DISABLED_EnsureInitializedThrowing) |
| +{ |
| + DetachedInitializer<FailFunction> x; |
| + try |
| + { |
| + x.SpawnInitializer(); |
| + } |
| + catch (...) |
| + { |
| + FAIL() << "SpawnInitializer() threw"; |
| + return; |
| + } |
| + try |
| + { |
| + x.EnsureInitialized(); |
| + FAIL() << "EnsureInitialized() did not throw"; |
| + } |
| + catch (std::runtime_error& e) |
| + { |
| + ASSERT_EQ(failMessage, e.what()); |
| + } |
| +} |
| + |
| +/* |
| + * Resetable version of DetachedInitializer |
| + */ |
| +template<void(&F)()> |
| +class DidReset |
| + : public DetachedInitializer<F> |
| +{ |
| +public: |
| + void Reset() |
| + { |
| + isInitializationComplete.store(false, std::memory_order_relaxed); |
| + hasInitializerStarted.store(false, std::memory_order_relaxed); |
| + initializerException = nullptr; |
| + // Lock and unlock the mutex to clear any previous state |
| + // It will also detect a locked mutex left over from a previous test. |
| + mutex.lock(); |
| + mutex.unlock(); |
| + } |
| +}; |
| + |
| +/* |
| + * Uses designated implementation pattern of DetachedInitializer |
| + */ |
| +struct DeferredIncrementingInitializer |
| +{ |
| + static int initializedCount; |
| + |
| + static void Incrementer() |
| + { |
| + ++initializedCount; |
| + } |
| + |
| + typedef DidReset<Incrementer> InitializerType; |
| + static InitializerType di; |
| + |
| +public: |
| + DeferredIncrementingInitializer() |
| + { |
| + di.SpawnInitializer(); |
| + } |
| + |
| + static void ResetClass() |
| + { |
| + initializedCount = 0; |
| + di.Reset(); |
| + } |
| + |
| + static int GetInitializerCount () |
| + { |
| + di.EnsureInitialized(); |
| + return initializedCount; |
| + } |
| +}; |
| +DeferredIncrementingInitializer::InitializerType DeferredIncrementingInitializer::di; |
| +int DeferredIncrementingInitializer::initializedCount = 0; |
| + |
| +TEST(DetachedInitializer, Single) |
| +{ |
| + DeferredIncrementingInitializer::ResetClass(); |
| + DeferredIncrementingInitializer x; |
| + ASSERT_EQ(1, DeferredIncrementingInitializer::GetInitializerCount()); |
| +} |
| + |
| +TEST(DetachedInitializer, Two) |
| +{ |
| + DeferredIncrementingInitializer::ResetClass(); |
| + DeferredIncrementingInitializer x; |
| + DeferredIncrementingInitializer y; |
| + ASSERT_EQ(1, DeferredIncrementingInitializer::GetInitializerCount()); |
| +} |
| + |
| +/* |
| + * This test implicitly calls EnsureInitialized before the second call to SpawnInitializer. |
| + */ |
| +TEST(DetachedInitializer, TwoInterleaved) |
| +{ |
| + DeferredIncrementingInitializer::ResetClass(); |
| + DeferredIncrementingInitializer x; |
| + EXPECT_EQ(1, DeferredIncrementingInitializer::GetInitializerCount()); |
| + DeferredIncrementingInitializer y; |
| + ASSERT_EQ(1, DeferredIncrementingInitializer::GetInitializerCount()); |
| +} |
| + |
| +TEST(DetachedInitializer, Many) |
| +{ |
| + DeferredIncrementingInitializer::ResetClass(); |
| + const int many = 1000000; |
| + for (int j = 0; j < many;++j) |
| + { |
| + DeferredIncrementingInitializer x; |
| + } |
| + ASSERT_EQ(1, DeferredIncrementingInitializer::GetInitializerCount()); |
| +} |