// Exercise some basic codepaths ensuring registration order and // destruction order happen as expected, that instances are created // when expected, etc etc. TEST(Singleton, BasicUsage) { SingletonVault vault; EXPECT_EQ(vault.registeredSingletonCount(), 0); Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 1); Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); Watchdog* s1 = Singleton<Watchdog>::get(&vault); EXPECT_NE(s1, nullptr); Watchdog* s2 = Singleton<Watchdog>::get(&vault); EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); auto s3 = Singleton<ChildWatchdog>::get(&vault); EXPECT_NE(s3, nullptr); EXPECT_NE(s2, s3); EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.livingSingletonCount(), 2); vault.destroyInstances(); EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.livingSingletonCount(), 0); }
TEST(Singleton, SingletonConcurrency) { SingletonVault vault; Singleton<Slowpoke> slowpoke_singleton(nullptr, nullptr, &vault); vault.registrationComplete(); std::mutex gatekeeper; gatekeeper.lock(); auto func = [&vault, &gatekeeper]() { gatekeeper.lock(); gatekeeper.unlock(); auto unused = Singleton<Slowpoke>::get(&vault); }; EXPECT_EQ(vault.livingSingletonCount(), 0); std::vector<std::thread> threads; for (int i = 0; i < 100; ++i) { threads.emplace_back(func); } // If circular dependency checks fail, the unlock would trigger a // crash. Instead, it succeeds, and we have exactly one living // singleton. gatekeeper.unlock(); for (auto& t : threads) { t.join(); } EXPECT_EQ(vault.livingSingletonCount(), 1); }
TEST(Singleton, SingletonDependencies) { Singleton<NeededSingleton> needed_singleton(nullptr, nullptr, &needy_vault); Singleton<NeedySingleton> needy_singleton(nullptr, nullptr, &needy_vault); needy_vault.registrationComplete(); EXPECT_EQ(needy_vault.registeredSingletonCount(), 2); EXPECT_EQ(needy_vault.livingSingletonCount(), 0); auto needy = Singleton<NeedySingleton>::get(&needy_vault); EXPECT_EQ(needy_vault.livingSingletonCount(), 2); Singleton<SelfNeedySingleton> self_needy_singleton( nullptr, nullptr, &self_needy_vault); self_needy_vault.registrationComplete(); EXPECT_THROW([]() { Singleton<SelfNeedySingleton>::get(&self_needy_vault); }(), std::out_of_range); }
TEST(Singleton, SharedPtrUsage) { SingletonVault vault; EXPECT_EQ(vault.registeredSingletonCount(), 0); Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 1); Singleton<ChildWatchdog> child_watchdog_singleton(nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); Watchdog* s1 = Singleton<Watchdog>::get(&vault); EXPECT_NE(s1, nullptr); Watchdog* s2 = Singleton<Watchdog>::get(&vault); EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); auto weak_s1 = Singleton<Watchdog>::get_weak(&vault); auto shared_s1 = weak_s1.lock(); EXPECT_EQ(shared_s1.get(), s1); EXPECT_EQ(shared_s1.use_count(), 2); LOG(ERROR) << "The following log message regarding ref counts is expected"; vault.destroyInstances(); EXPECT_EQ(vault.registeredSingletonCount(), 2); EXPECT_EQ(vault.livingSingletonCount(), 0); EXPECT_EQ(shared_s1.use_count(), 1); EXPECT_EQ(shared_s1.get(), s1); auto locked_s1 = weak_s1.lock(); EXPECT_EQ(locked_s1.get(), s1); EXPECT_EQ(shared_s1.use_count(), 2); locked_s1.reset(); EXPECT_EQ(shared_s1.use_count(), 1); // Track serial number rather than pointer since the memory could be // re-used when we create new_s1. auto old_serial = shared_s1->serial_number; shared_s1.reset(); locked_s1 = weak_s1.lock(); EXPECT_TRUE(weak_s1.expired()); Watchdog* new_s1 = Singleton<Watchdog>::get(&vault); EXPECT_NE(new_s1->serial_number, old_serial); }