| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/lazy_init/lazy_init.h> |
| #include <zircon/compiler.h> |
| |
| #include <atomic> |
| #include <cstring> |
| |
| #include <zxtest/zxtest.h> |
| |
| using lazy_init::CheckType; |
| using lazy_init::Destructor; |
| using lazy_init::LazyInit; |
| |
| namespace { |
| |
| template <auto...> |
| struct TestType { |
| TestType() { constructions_++; } |
| ~TestType() { destructions_++; } |
| |
| TestType(const TestType&) = delete; |
| TestType& operator=(const TestType&) = delete; |
| |
| void Method() {} |
| |
| static size_t constructions() { return constructions_.load(); } |
| static size_t destructions() { return destructions_.load(); } |
| |
| inline static std::atomic<size_t> constructions_; |
| inline static std::atomic<size_t> destructions_; |
| }; |
| |
| template <typename T> |
| union Storage { |
| constexpr Storage() : value{} {} |
| ~Storage() {} |
| |
| int dummy; |
| T value; |
| }; |
| |
| template <typename Type> |
| inline void DoNotOptimize(const Type& value) { |
| // The "memory" constraint tells the compiler that the inline assembly |
| // must be assumed to access memory that |value| points to. |
| asm volatile("" : : "g"(value) : "memory"); |
| } |
| |
| template <CheckType Check, Destructor Enabled> |
| void lazy_init_test() { |
| // Define a unique test type for this test instantiation. |
| using Type = TestType<Check, Enabled>; |
| |
| // Define a lazy-initialized variable for this test. Normally this |
| // would be a static or global, but for this test we need to control |
| // when the dtor is executed and avoid asserting at the end of the test |
| // process when global dtors run. |
| using LazyInitType = LazyInit<Type, Check, Enabled>; |
| |
| static Storage<LazyInitType> test_value_storage{}; |
| LazyInitType& test_value = test_value_storage.value; |
| |
| size_t expected_constructions = 0; |
| size_t expected_destructions = 0; |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| // Work around a bug in the LazyInit implementation which causes the |
| // compiler to optimize away an assertion. |
| // TODO(fxbug.dev/55268): Fix the bug and remove this workaround. |
| DoNotOptimize(&test_value); |
| |
| const auto dereference_test = [] { test_value->Method(); }; |
| |
| if (Check != CheckType::None) { |
| ASSERT_DEATH(dereference_test, "Testing assert before initialization.\n"); |
| } else { |
| ASSERT_NO_DEATH(dereference_test, "Testing assert before initialization.\n"); |
| } |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| const auto initialization_test = [] { test_value.Initialize(); }; |
| |
| ASSERT_NO_DEATH(initialization_test, "Testing intialization.\n"); |
| ++expected_constructions; |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| if (Check == CheckType::None) { |
| ASSERT_NO_DEATH(initialization_test, "Testing re-intialization.\n"); |
| ++expected_constructions; |
| } else { |
| ASSERT_DEATH(initialization_test, "Testing re-intialization.\n"); |
| } |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| ASSERT_NO_DEATH(dereference_test, "Testing assert after initialization.\n"); |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| const auto destruction_test = [] { test_value.LazyInitType::~LazyInitType(); }; |
| |
| ASSERT_NO_DEATH(destruction_test, "Testing destruction.\n"); |
| |
| if (Enabled == Destructor::Enabled) { |
| ++expected_destructions; |
| } |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| if (Check == CheckType::None || Enabled == Destructor::Disabled) { |
| ASSERT_NO_DEATH(dereference_test, "Testing assert after destruction.\n"); |
| } else { |
| ASSERT_DEATH(dereference_test, "Testing assert after destruction.\n"); |
| } |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| |
| if (Check == CheckType::None || Enabled == Destructor::Disabled) { |
| ASSERT_NO_DEATH(destruction_test, "Testing re-destruction.\n"); |
| if (Enabled == Destructor::Enabled) { |
| ++expected_destructions; |
| } |
| } else { |
| ASSERT_DEATH(destruction_test, "Testing re-destruction.\n"); |
| } |
| |
| EXPECT_EQ(expected_constructions, Type::constructions()); |
| EXPECT_EQ(expected_destructions, Type::destructions()); |
| } |
| |
| // TODO(eieio): Does it make sense to try to create races to test the atomic |
| // check specialization more thoroughly? |
| |
| TEST(LazyInitTest, NoCheckNoDtor) { lazy_init_test<CheckType::None, Destructor::Disabled>(); } |
| |
| TEST(LazyInitTest, BasicChecksNoDtor) { lazy_init_test<CheckType::Basic, Destructor::Disabled>(); } |
| |
| TEST(LazyInitTest, AtomicChecksNoDtor) { |
| lazy_init_test<CheckType::Atomic, Destructor::Disabled>(); |
| } |
| |
| TEST(LazyInitTest, NoChecksWithDtor) { lazy_init_test<CheckType::None, Destructor::Enabled>(); } |
| |
| TEST(LazyInitTest, BasicChecksWithDtor) { lazy_init_test<CheckType::Basic, Destructor::Enabled>(); } |
| |
| TEST(LazyInitTest, AtomicChecksWithDtor) { |
| lazy_init_test<CheckType::Atomic, Destructor::Enabled>(); |
| } |
| |
| } // anonymous namespace |