X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FSingletonTest.cpp;h=be95ea4dfef9a6496dee19d62f3a1a9de5bd2a60;hb=8e110a76171f5204d63ce3dbd283589f8914fe44;hp=8505d0686ffcb8d8a99ddd7b3f44a1db5be8cc5b;hpb=ad7e7f72235d3803b0077c7acbd082c79eb99709;p=folly.git diff --git a/folly/test/SingletonTest.cpp b/folly/test/SingletonTest.cpp index 8505d068..be95ea4d 100644 --- a/folly/test/SingletonTest.cpp +++ b/folly/test/SingletonTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,69 +17,26 @@ #include #include +#include +#include +#include +#include +#include -#include +#ifndef _MSC_VER +#include +#endif #include -#include +#include -using namespace folly; - -// A simple class that tracks how often instances of the class and -// subclasses are created, and the ordering. Also tracks a global -// unique counter for each object. -std::atomic global_counter(19770326); -struct Watchdog { - static std::vector creation_order; - Watchdog() : serial_number(++global_counter) { - creation_order.push_back(this); - } - - ~Watchdog() { - if (creation_order.back() != this) { - throw std::out_of_range("Watchdog destruction order mismatch"); - } - creation_order.pop_back(); - } +FOLLY_GCC_DISABLE_WARNING("-Wdeprecated-declarations") - const size_t serial_number; - size_t livingWatchdogCount() const { return creation_order.size(); } - - Watchdog(const Watchdog&) = delete; - Watchdog& operator=(const Watchdog&) = delete; - Watchdog(Watchdog&&) noexcept = default; -}; - -std::vector Watchdog::creation_order; - -// Some basic types we use for tracking. -struct ChildWatchdog : public Watchdog {}; -struct GlobalWatchdog : public Watchdog {}; -struct UnregisteredWatchdog : public Watchdog {}; - -namespace { -Singleton global_watchdog; -} - -// Test basic global usage (the default way singletons will generally -// be used). -TEST(Singleton, BasicGlobalUsage) { - EXPECT_EQ(Watchdog::creation_order.size(), 0); - EXPECT_EQ(SingletonVault::singleton()->registeredSingletonCount(), 1); - EXPECT_EQ(SingletonVault::singleton()->livingSingletonCount(), 0); - auto wd1 = Singleton::get(); - EXPECT_NE(wd1, nullptr); - EXPECT_EQ(Watchdog::creation_order.size(), 1); - auto wd2 = Singleton::get(); - EXPECT_NE(wd2, nullptr); - EXPECT_EQ(wd1, wd2); - EXPECT_EQ(Watchdog::creation_order.size(), 1); - SingletonVault::singleton()->destroyInstances(); - EXPECT_EQ(Watchdog::creation_order.size(), 0); -} +using namespace folly; TEST(Singleton, MissingSingleton) { - EXPECT_DEATH([]() { auto u = Singleton::get(); }(), ""); + EXPECT_DEATH([]() { auto u = Singleton::try_get(); }(), + ""); } struct BasicUsageTag {}; @@ -101,20 +58,25 @@ TEST(Singleton, BasicUsage) { vault.registrationComplete(); - Watchdog* s1 = SingletonBasicUsage::get(); - EXPECT_NE(s1, nullptr); + // limit a scope to release references so we can destroy them later + { + std::shared_ptr s1 = SingletonBasicUsage::try_get(); + EXPECT_NE(s1, nullptr); - Watchdog* s2 = SingletonBasicUsage::get(); - EXPECT_NE(s2, nullptr); + std::shared_ptr s2 = SingletonBasicUsage::try_get(); + EXPECT_NE(s2, nullptr); - EXPECT_EQ(s1, s2); + EXPECT_EQ(s1, s2); + EXPECT_EQ(s1.get(), SingletonBasicUsage::try_get_fast().get()); - auto s3 = SingletonBasicUsage::get(); - EXPECT_NE(s3, nullptr); - EXPECT_NE(s2, s3); + std::shared_ptr s3 = + SingletonBasicUsage::try_get(); + EXPECT_NE(s3, nullptr); + EXPECT_NE(s2, s3); - EXPECT_EQ(vault.registeredSingletonCount(), 2); - EXPECT_EQ(vault.livingSingletonCount(), 2); + EXPECT_EQ(vault.registeredSingletonCount(), 2); + EXPECT_EQ(vault.livingSingletonCount(), 2); + } vault.destroyInstances(); EXPECT_EQ(vault.registeredSingletonCount(), 2); @@ -138,11 +100,10 @@ TEST(Singleton, DirectUsage) { EXPECT_EQ(vault.registeredSingletonCount(), 2); vault.registrationComplete(); - EXPECT_NE(watchdog.get(), nullptr); - EXPECT_EQ(watchdog.get(), SingletonDirectUsage::get()); - EXPECT_NE(watchdog.get(), named_watchdog.get()); - EXPECT_EQ(watchdog->livingWatchdogCount(), 2); - EXPECT_EQ((*watchdog).livingWatchdogCount(), 2); + EXPECT_NE(watchdog.try_get(), nullptr); + EXPECT_EQ(watchdog.try_get(), SingletonDirectUsage::try_get()); + EXPECT_NE(watchdog.try_get(), named_watchdog.try_get()); + EXPECT_EQ(watchdog.try_get()->livingWatchdogCount(), 2); vault.destroyInstances(); } @@ -168,22 +129,23 @@ TEST(Singleton, NamedUsage) { EXPECT_EQ(vault.registeredSingletonCount(), 3); vault.registrationComplete(); - - // Verify our three singletons are distinct and non-nullptr. - Watchdog* s1 = SingletonNamedUsage::get(); - EXPECT_EQ(s1, watchdog1_singleton.get()); - Watchdog* s2 = SingletonNamedUsage::get(); - EXPECT_EQ(s2, watchdog2_singleton.get()); - EXPECT_NE(s1, s2); - Watchdog* s3 = SingletonNamedUsage::get(); - EXPECT_EQ(s3, watchdog3_singleton.get()); - EXPECT_NE(s3, s1); - EXPECT_NE(s3, s2); - - // Verify the "default" singleton is the same as the DefaultTag-tagged - // singleton. - Watchdog* s4 = SingletonNamedUsage::get(); - EXPECT_EQ(s4, watchdog3_singleton.get()); + { + // Verify our three singletons are distinct and non-nullptr. + auto s1 = SingletonNamedUsage::try_get(); + EXPECT_EQ(s1, watchdog1_singleton.try_get()); + auto s2 = SingletonNamedUsage::try_get(); + EXPECT_EQ(s2, watchdog2_singleton.try_get()); + EXPECT_NE(s1, s2); + auto s3 = SingletonNamedUsage::try_get(); + EXPECT_EQ(s3, watchdog3_singleton.try_get()); + EXPECT_NE(s3, s1); + EXPECT_NE(s3, s2); + + // Verify the "default" singleton is the same as the DefaultTag-tagged + // singleton. + auto s4 = SingletonNamedUsage::try_get(); + EXPECT_EQ(s4, watchdog3_singleton.try_get()); + } vault.destroyInstances(); } @@ -203,30 +165,29 @@ TEST(Singleton, NaughtyUsage) { vault.registrationComplete(); // Unregistered. - EXPECT_DEATH(Singleton::get(), ""); - EXPECT_DEATH(SingletonNaughtyUsage::get(), ""); + EXPECT_DEATH(Singleton::try_get(), ""); + EXPECT_DEATH(SingletonNaughtyUsage::try_get(), ""); vault.destroyInstances(); auto& vault2 = *SingletonVault::singleton(); - EXPECT_DEATH(SingletonNaughtyUsage2::get(), ""); + EXPECT_DEATH(SingletonNaughtyUsage2::try_get(), ""); SingletonNaughtyUsage2 watchdog_singleton; // double registration - EXPECT_DEATH([]() { SingletonNaughtyUsage2 watchdog_singleton; }(), - ""); + EXPECT_DEATH([]() { SingletonNaughtyUsage2 w2; }(), ""); vault2.destroyInstances(); // double registration after destroy - EXPECT_DEATH([]() { SingletonNaughtyUsage2 watchdog_singleton; }(), - ""); + EXPECT_DEATH([]() { SingletonNaughtyUsage2 w3; }(), ""); } struct SharedPtrUsageTag {}; template using SingletonSharedPtrUsage = Singleton ; +// TODO (anob): revisit this test TEST(Singleton, SharedPtrUsage) { struct WatchdogHolder { ~WatchdogHolder() { @@ -241,7 +202,18 @@ TEST(Singleton, SharedPtrUsage) { auto& vault = *SingletonVault::singleton(); EXPECT_EQ(vault.registeredSingletonCount(), 0); - SingletonSharedPtrUsage watchdog_singleton; + std::vector> watchdog_instances; + SingletonSharedPtrUsage watchdog_singleton( + [&] { + watchdog_instances.push_back(std::make_unique()); + return watchdog_instances.back().get(); + }, + [&](Watchdog* ptr) { + // Make sure that only second instance is destroyed. First instance is + // expected to be leaked. + EXPECT_EQ(watchdog_instances[1].get(), ptr); + watchdog_instances[1].reset(); + }); EXPECT_EQ(vault.registeredSingletonCount(), 1); SingletonSharedPtrUsage child_watchdog_singleton; @@ -256,12 +228,12 @@ TEST(Singleton, SharedPtrUsage) { // Initilize holder singleton first, so that it's the last one to be // destroyed. - watchdog_holder_singleton.get(); + watchdog_holder_singleton.try_get(); - Watchdog* s1 = SingletonSharedPtrUsage::get(); + auto s1 = SingletonSharedPtrUsage::try_get().get(); EXPECT_NE(s1, nullptr); - Watchdog* s2 = SingletonSharedPtrUsage::get(); + auto s2 = SingletonSharedPtrUsage::try_get().get(); EXPECT_NE(s2, nullptr); EXPECT_EQ(s1, s2); @@ -283,7 +255,7 @@ TEST(Singleton, SharedPtrUsage) { // We should release externally locked shared_ptr, otherwise it will be // considered a leak - watchdog_holder_singleton->watchdog = std::move(shared_s1); + watchdog_holder_singleton.try_get()->watchdog = std::move(shared_s1); LOG(ERROR) << "The following log message regarding shared_ptr is expected"; { @@ -303,11 +275,13 @@ TEST(Singleton, SharedPtrUsage) { vault.reenableInstances(); - // Singleton should be re-created only after reenableInstances() was called. - Watchdog* new_s1 = SingletonSharedPtrUsage::get(); - // Track serial number rather than pointer since the memory could be - // re-used when we create new_s1. - EXPECT_NE(new_s1->serial_number, old_serial); + { + // Singleton should be re-created only after reenableInstances() was called. + auto new_s1 = SingletonSharedPtrUsage::try_get(); + // Track serial number rather than pointer since the memory could be + // re-used when we create new_s1. + EXPECT_NE(new_s1->serial_number, old_serial); + } auto new_s1_weak = SingletonSharedPtrUsage::get_weak(); auto new_s1_shared = new_s1_weak.lock(); @@ -337,7 +311,7 @@ using SingletonNeedy = Singleton ; struct NeededSingleton {}; struct NeedySingleton { NeedySingleton() { - auto unused = SingletonNeedy::get(); + auto unused = SingletonNeedy::try_get(); EXPECT_NE(unused, nullptr); } }; @@ -349,7 +323,7 @@ using SingletonSelfNeedy = Singleton ; struct SelfNeedySingleton { SelfNeedySingleton() { - auto unused = SingletonSelfNeedy::get(); + auto unused = SingletonSelfNeedy::try_get(); EXPECT_NE(unused, nullptr); } }; @@ -364,14 +338,15 @@ TEST(Singleton, SingletonDependencies) { EXPECT_EQ(needy_vault.registeredSingletonCount(), 2); EXPECT_EQ(needy_vault.livingSingletonCount(), 0); - auto needy = SingletonNeedy::get(); + auto needy = SingletonNeedy::try_get(); EXPECT_EQ(needy_vault.livingSingletonCount(), 2); SingletonSelfNeedy self_needy_singleton; auto& self_needy_vault = *SingletonVault::singleton(); self_needy_vault.registrationComplete(); - EXPECT_DEATH([]() { SingletonSelfNeedy::get(); }(), ""); + EXPECT_DEATH([]() { SingletonSelfNeedy::try_get(); }(), + ""); } // A test to ensure multiple threads contending on singleton creation @@ -396,7 +371,7 @@ TEST(Singleton, SingletonConcurrency) { auto func = [&gatekeeper]() { gatekeeper.lock(); gatekeeper.unlock(); - auto unused = SingletonConcurrency::get(); + auto unused = SingletonConcurrency::try_get(); }; EXPECT_EQ(vault.livingSingletonCount(), 0); @@ -429,14 +404,14 @@ template using SingletonCreationError = Singleton; TEST(Singleton, SingletonCreationError) { - auto& vault = *SingletonVault::singleton(); SingletonCreationError error_once_singleton; + SingletonVault::singleton()->registrationComplete(); // first time should error out - EXPECT_THROW(error_once_singleton.get_weak().lock(), std::runtime_error); + EXPECT_THROW(error_once_singleton.try_get(), std::runtime_error); // second time it'll work fine - error_once_singleton.get_weak().lock(); + error_once_singleton.try_get(); SUCCEED(); } @@ -447,11 +422,12 @@ using SingletonConcurrencyStress = Singleton ; TEST(Singleton, SingletonConcurrencyStress) { auto& vault = *SingletonVault::singleton(); SingletonConcurrencyStress slowpoke_singleton; + vault.registrationComplete(); std::vector ts; for (size_t i = 0; i < 100; ++i) { ts.emplace_back([&]() { - slowpoke_singleton.get_weak().lock(); + slowpoke_singleton.try_get(); }); } @@ -469,18 +445,131 @@ TEST(Singleton, SingletonConcurrencyStress) { } } -// Benchmarking a normal singleton vs a Meyers singleton vs a Folly -// singleton. Meyers are insanely fast, but (hopefully) Folly -// singletons are fast "enough." -int* getMeyersSingleton() { - static auto ret = new int(0); - return ret; +namespace { +struct EagerInitSyncTag {}; +} +template +using SingletonEagerInitSync = Singleton; +TEST(Singleton, SingletonEagerInitSync) { + auto& vault = *SingletonVault::singleton(); + bool didEagerInit = false; + auto sing = SingletonEagerInitSync( + [&] {didEagerInit = true; return new std::string("foo"); }) + .shouldEagerInit(); + vault.registrationComplete(); + EXPECT_FALSE(didEagerInit); + vault.doEagerInit(); + EXPECT_TRUE(didEagerInit); + sing.get_weak(); // (avoid compile error complaining about unused var 'sing') } -int normal_singleton_value = 0; -int* getNormalSingleton() { - doNotOptimizeAway(&normal_singleton_value); - return &normal_singleton_value; +namespace { +struct EagerInitAsyncTag {}; +} +template +using SingletonEagerInitAsync = Singleton; +TEST(Singleton, SingletonEagerInitAsync) { + auto& vault = *SingletonVault::singleton(); + bool didEagerInit = false; + auto sing = SingletonEagerInitAsync( + [&] {didEagerInit = true; return new std::string("foo"); }) + .shouldEagerInit(); + folly::EventBase eb; + folly::Baton<> done; + vault.registrationComplete(); + EXPECT_FALSE(didEagerInit); + vault.doEagerInitVia(eb, &done); + eb.loop(); + done.wait(); + EXPECT_TRUE(didEagerInit); + sing.get_weak(); // (avoid compile error complaining about unused var 'sing') +} + +namespace { +class TestEagerInitParallelExecutor : public folly::Executor { + public: + explicit TestEagerInitParallelExecutor(const size_t threadCount) { + eventBases_.reserve(threadCount); + threads_.reserve(threadCount); + for (size_t i = 0; i < threadCount; i++) { + eventBases_.push_back(std::make_shared()); + auto eb = eventBases_.back(); + threads_.emplace_back(std::make_shared( + [eb] { eb->loopForever(); })); + } + } + + ~TestEagerInitParallelExecutor() override { + for (auto eb : eventBases_) { + eb->runInEventBaseThread([eb] { eb->terminateLoopSoon(); }); + } + for (auto thread : threads_) { + thread->join(); + } + } + + void add(folly::Func func) override { + const auto index = (counter_ ++) % eventBases_.size(); + eventBases_[index]->add(std::move(func)); + } + + private: + std::vector> eventBases_; + std::vector> threads_; + std::atomic counter_ {0}; +}; +} // namespace + +namespace { +struct EagerInitParallelTag {}; +} +template +using SingletonEagerInitParallel = Singleton; +TEST(Singleton, SingletonEagerInitParallel) { + const static size_t kIters = 1000; + const static size_t kThreads = 20; + + std::atomic initCounter; + + auto& vault = *SingletonVault::singleton(); + + auto sing = SingletonEagerInitParallel( + [&] {++initCounter; return new std::string(""); }) + .shouldEagerInit(); + + for (size_t i = 0; i < kIters; i++) { + SCOPE_EXIT { + // clean up each time + vault.destroyInstances(); + vault.reenableInstances(); + }; + + initCounter.store(0); + + { + std::vector> threads; + boost::barrier barrier(kThreads); + TestEagerInitParallelExecutor exe(kThreads); + vault.registrationComplete(); + + EXPECT_EQ(0, initCounter.load()); + + for (size_t j = 0; j < kThreads; j++) { + threads.push_back(std::make_shared([&] { + barrier.wait(); + vault.doEagerInitVia(exe); + })); + } + + for (auto thread : threads) { + thread->join(); + } + } + + EXPECT_EQ(1, initCounter.load()); + + sing.get_weak(); // (avoid compile error complaining about unused var) + } } struct MockTag {}; @@ -498,66 +587,192 @@ TEST(Singleton, MockTest) { // Registring singletons after registrationComplete called works // with make_mock (but not with Singleton ctor). EXPECT_EQ(vault.registeredSingletonCount(), 1); - int serial_count_first = SingletonMock::get()->serial_number; + int serial_count_first = SingletonMock::try_get()->serial_number; // Override existing mock using make_mock. SingletonMock::make_mock(); EXPECT_EQ(vault.registeredSingletonCount(), 1); - int serial_count_mock = SingletonMock::get()->serial_number; + int serial_count_mock = SingletonMock::try_get()->serial_number; // If serial_count value is the same, then singleton was not replaced. EXPECT_NE(serial_count_first, serial_count_mock); -} -struct BenchmarkSingleton { - int val = 0; -}; + // Override existing mock using make_mock one more time + SingletonMock::make_mock(); -BENCHMARK(NormalSingleton, n) { - for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(getNormalSingleton()); - } + EXPECT_EQ(vault.registeredSingletonCount(), 1); + int serial_count_mock2 = SingletonMock::try_get()->serial_number; + + // If serial_count value is the same, then singleton was not replaced. + EXPECT_NE(serial_count_first, serial_count_mock2); + EXPECT_NE(serial_count_mock, serial_count_mock2); + + vault.destroyInstances(); +} + +#ifndef _MSC_VER +// Subprocess isn't currently supported under MSVC. +TEST(Singleton, DoubleRegistrationLogging) { + const auto basename = "singleton_double_registration"; + const auto sub = fs::executable_path().remove_filename() / basename; + auto p = Subprocess( + std::vector{sub.string()}, + Subprocess::Options() + .stdinFd(Subprocess::CLOSE) + .stdoutFd(Subprocess::CLOSE) + .pipeStderr() + .closeOtherFds()); + auto err = p.communicate("").second; + auto res = p.wait(); + EXPECT_EQ(ProcessReturnCode::KILLED, res.state()); + EXPECT_EQ(SIGABRT, res.killSignal()); + EXPECT_THAT(err, testing::StartsWith("Double registration of singletons")); } +#endif -BENCHMARK_RELATIVE(MeyersSingleton, n) { - for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(getMeyersSingleton()); +// Singleton using a non default constructor test/example: +struct X { + X() : X(-1, "unset") {} + X(int a1, std::string a2) : a1(a1), a2(a2) { + LOG(INFO) << "X(" << a1 << "," << a2 << ")"; } + const int a1; + const std::string a2; +}; + +folly::Singleton singleton_x([]() { return new X(42, "foo"); }); + +TEST(Singleton, CustomCreator) { + X x1; + std::shared_ptr x2p = singleton_x.try_get(); + EXPECT_NE(nullptr, x2p); + EXPECT_NE(x1.a1, x2p->a1); + EXPECT_NE(x1.a2, x2p->a2); + EXPECT_EQ(42, x2p->a1); + EXPECT_EQ(std::string("foo"), x2p->a2); } -struct BenchmarkTag {}; +struct ConcurrentCreationDestructionTag {}; template -using SingletonBenchmark = Singleton ; +using SingletonConcurrentCreationDestruction = + Singleton; + +folly::Baton<> slowpokeNeedySingletonBaton; + +struct SlowpokeNeedySingleton { + SlowpokeNeedySingleton() { + slowpokeNeedySingletonBaton.post(); + /* sleep override */ std::this_thread::sleep_for( + std::chrono::milliseconds(100)); + auto unused = + SingletonConcurrentCreationDestruction::try_get(); + EXPECT_NE(unused, nullptr); + } +}; -struct GetTag{}; -struct GetWeakTag{}; +TEST(Singleton, ConcurrentCreationDestruction) { + auto& vault = *SingletonVault::singleton(); + SingletonConcurrentCreationDestruction neededSingleton; + SingletonConcurrentCreationDestruction needySingleton; + vault.registrationComplete(); -SingletonBenchmark benchmark_singleton_get; -SingletonBenchmark benchmark_singleton_get_weak; + std::thread needyThread([&] { needySingleton.try_get(); }); -BENCHMARK_RELATIVE(FollySingleton, n) { - for (size_t i = 0; i < n; ++i) { - doNotOptimizeAway(SingletonBenchmark::get()); - } + slowpokeNeedySingletonBaton.wait(); + + vault.destroyInstances(); + + needyThread.join(); } -BENCHMARK_RELATIVE(FollySingletonWeak, n) { - for (size_t i = 0; i < n; ++i) { - SingletonBenchmark::get_weak(); +struct MainThreadDestructorTag {}; +template +using SingletonMainThreadDestructor = + Singleton; + +struct ThreadLoggingSingleton { + ThreadLoggingSingleton() { + initThread = std::this_thread::get_id(); + } + + ~ThreadLoggingSingleton() { + destroyThread = std::this_thread::get_id(); } + + static std::thread::id initThread; + static std::thread::id destroyThread; +}; +std::thread::id ThreadLoggingSingleton::initThread{}; +std::thread::id ThreadLoggingSingleton::destroyThread{}; + +TEST(Singleton, MainThreadDestructor) { + auto& vault = *SingletonVault::singleton(); + SingletonMainThreadDestructor singleton; + + vault.registrationComplete(); + EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::initThread); + + singleton.try_get(); + EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::initThread); + + std::thread t([instance = singleton.try_get()] { + /* sleep override */ std::this_thread::sleep_for( + std::chrono::milliseconds{100}); + }); + + EXPECT_EQ(std::thread::id(), ThreadLoggingSingleton::destroyThread); + + vault.destroyInstances(); + EXPECT_EQ(std::this_thread::get_id(), ThreadLoggingSingleton::destroyThread); + + t.join(); } -int main(int argc, char* argv[]) { - testing::InitGoogleTest(&argc, argv); - google::InitGoogleLogging(argv[0]); - gflags::ParseCommandLineFlags(&argc, &argv, true); +TEST(Singleton, DoubleMakeMockAfterTryGet) { + // to keep track of calls to ctor and dtor below + struct Counts { + size_t ctor = 0; + size_t dtor = 0; + }; + + // a test type which keeps track of its ctor and dtor calls + struct VaultTag {}; + struct PrivateTag {}; + struct Object { + explicit Object(Counts& counts) : counts_(counts) { + ++counts_.ctor; + } + ~Object() { + ++counts_.dtor; + } + Counts& counts_; + }; + using SingletonObject = Singleton; + + // register everything + Counts counts; + auto& vault = *SingletonVault::singleton(); + auto new_object = [&] { return new Object(counts); }; + SingletonObject object_(new_object); + vault.registrationComplete(); - SingletonVault::singleton()->registrationComplete(); + // no eager inits, nada (sanity) + EXPECT_EQ(0, counts.ctor); + EXPECT_EQ(0, counts.dtor); - auto ret = RUN_ALL_TESTS(); - if (!ret) { - folly::runBenchmarksOnFlag(); - } - return ret; + // explicit request, ctor + SingletonObject::try_get(); + EXPECT_EQ(1, counts.ctor); + EXPECT_EQ(0, counts.dtor); + + // first make_mock, dtor (ctor is lazy) + SingletonObject::make_mock(new_object); + EXPECT_EQ(1, counts.ctor); + EXPECT_EQ(1, counts.dtor); + + // second make_mock, nada (dtor already ran, ctor is lazy) + SingletonObject::make_mock(new_object); + EXPECT_EQ(1, counts.ctor); + EXPECT_EQ(1, counts.dtor); }