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()); |
+} |