// Some pathological cases such as getting unregistered singletons, // double registration, etc. TEST(Singleton, NaughtyUsage) { SingletonVault vault; vault.registrationComplete(); // Unregistered. EXPECT_THROW(Singleton<Watchdog>::get(), std::out_of_range); EXPECT_THROW(Singleton<Watchdog>::get(&vault), std::out_of_range); // Registring singletons after registrationComplete called. EXPECT_THROW([&vault]() { Singleton<Watchdog> watchdog_singleton( nullptr, nullptr, &vault); }(), std::logic_error); EXPECT_THROW([]() { Singleton<Watchdog> watchdog_singleton; }(), std::logic_error); SingletonVault vault_2; EXPECT_THROW(Singleton<Watchdog>::get(&vault_2), std::logic_error); Singleton<Watchdog> watchdog_singleton(nullptr, nullptr, &vault_2); // double registration EXPECT_THROW([&vault_2]() { Singleton<Watchdog> watchdog_singleton( nullptr, nullptr, &vault_2); }(), std::logic_error); vault_2.destroyInstances(); // double registration after destroy EXPECT_THROW([&vault_2]() { Singleton<Watchdog> watchdog_singleton( nullptr, nullptr, &vault_2); }(), std::logic_error); }
// 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, DirectUsage) { SingletonVault vault; EXPECT_EQ(vault.registeredSingletonCount(), 0); // Verify we can get to the underlying singletons via directly using // the singleton definition. Singleton<Watchdog> watchdog(nullptr, nullptr, &vault); Singleton<Watchdog> named_watchdog("named", nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); EXPECT_NE(watchdog.ptr(), nullptr); EXPECT_EQ(watchdog.ptr(), Singleton<Watchdog>::get(&vault)); EXPECT_NE(watchdog.ptr(), named_watchdog.ptr()); EXPECT_EQ(watchdog->livingWatchdogCount(), 2); EXPECT_EQ((*watchdog).livingWatchdogCount(), 2); }
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, NamedUsage) { SingletonVault vault; EXPECT_EQ(vault.registeredSingletonCount(), 0); // Define two named Watchdog singletons and one unnamed singleton. Singleton<Watchdog> watchdog1_singleton( "watchdog1", nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 1); Singleton<Watchdog> watchdog2_singleton( "watchdog2", nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 2); Singleton<Watchdog> watchdog3_singleton(nullptr, nullptr, &vault); EXPECT_EQ(vault.registeredSingletonCount(), 3); vault.registrationComplete(); // Verify our three singletons are distinct and non-nullptr. Watchdog* s1 = Singleton<Watchdog>::get("watchdog1", &vault); EXPECT_EQ(s1, watchdog1_singleton.ptr()); Watchdog* s2 = Singleton<Watchdog>::get("watchdog2", &vault); EXPECT_EQ(s2, watchdog2_singleton.ptr()); EXPECT_NE(s1, s2); Watchdog* s3 = Singleton<Watchdog>::get(&vault); EXPECT_EQ(s3, watchdog3_singleton.ptr()); EXPECT_NE(s3, s1); EXPECT_NE(s3, s2); // Verify the "default" singleton is the same as the empty string // singleton. Watchdog* s4 = Singleton<Watchdog>::get("", &vault); EXPECT_EQ(s4, watchdog3_singleton.ptr()); }
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); }