TEST_F(WebsocketTest, CleanShutdown) { AutoRequired<WebsocketExceptionFilter>(); // Try starting and stopping server multiple times { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<AutoNetServer>(); ctxt->Initiate(); ctxt->Wait(std::chrono::milliseconds(200)); ctxt->SignalShutdown(true); } { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<AutoNetServer>(); ctxt->Initiate(); ctxt->Wait(std::chrono::milliseconds(200)); ctxt->SignalShutdown(true); } { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<AutoNetServer>(); ctxt->Initiate(); ctxt->Wait(std::chrono::milliseconds(200)); ctxt->SignalShutdown(true); } }
TEST_F(ContextMapTest, VerifyWithThreadsPathological) { ContextMap<size_t> mp; // Context collection and exit race threads: vector<std::shared_ptr<CoreContext>> contexts; // Exit race controller: AutoRequired<ExitRaceSignal> signal; // Create a number of dependent contexts: for(size_t i = 0; i < 100; i++) { AutoCreateContext context; contexts.push_back(context); // Store a shared pointer mp.Add(i, context); // Start the context context->Initiate(); } // Set the signal: signal->Signal(); // Verify that the map empties once our zero-count is hit: for(size_t i = 0; i < contexts.size(); i++) { contexts[i]->SignalShutdown(true); } // Clear the context collection: contexts.clear(); EXPECT_EQ(0UL, mp.size()) << "Context map did not empty as expected"; }
TEST_F(ContextMapTest, VerifyWithThreads) { ContextMap<string> mp; std::shared_ptr<SimpleThreaded> threaded; std::weak_ptr<CoreContext> weakContext; { AutoCreateContext context; // Obtain a weak pointer of our own, and add to the context: weakContext = context; mp.Add("context_withthreads", context); // Add a thread to hold the context open for awhile: threaded = context->Inject<SimpleThreaded>(); // Start the context context->Initiate(); } // Assert that the context still actually exists: ASSERT_TRUE(!weakContext.expired()) << "Simple thread exited before it was signalled to exit"; { // Verify that we can still find the context while the thread is alive: std::shared_ptr<CoreContext> context = mp.Find("context_withthreads"); ASSERT_TRUE(!!context.get()) << "Map evicted a context before expected"; // Relock the weak context, verify that we get back the same pointer: auto relocked = weakContext.lock(); EXPECT_EQ(relocked, context) << "Mapped context pointer was not identical to a previously stored context pointer"; // Terminate whole context context->SignalTerminate(); } // Release our threaded entity: threaded.reset(); { // Verify that the context is gone now that everything in it has stopped running auto ctxt = mp.Find("context_withthreads"); EXPECT_FALSE(ctxt) << "Context was not properly evicted from the map"; // Just return early if the context was empty as we expected, the next part of this test is for diagnostics if(!ctxt) return; // Release the pointer so we aren't guilty of holding a reference to the very thing whose // destruction we are trying to assure. ctxt.reset(); // Sleep for a little bit and run the verification again. If the prior expectation fails, // but this one succeeds, it could be due to race conditions in CoreThread std::this_thread::sleep_for(std::chrono::milliseconds(10)); ctxt = mp.Find("context_withthreads"); EXPECT_FALSE(ctxt) << "Context was not properly evicted even after waiting for a time to ensure eviction"; } }
TEST_F(ContextCreatorTest, ValidateMultipleEviction) { AutoCurrentContext()->Initiate(); // Number of dependent contexts to be created const size_t count = 100; // Teardown lock, counter, and condition: std::mutex lock; std::condition_variable cond; int counter = count; // Obtain creator pointer: AutoRequired<Creator> creator; // Set up a signal manager at global context scope: AutoRequired<GlobalSignal> signal; { // Array of objects to test destruction on, and corresponding collection of contexts: std::shared_ptr<WaitMember> members[count]; // Create a few contexts: for(int i = count; i--;) { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); // Trivial validation that the newly created context is an empty context: ASSERT_EQ(static_cast<size_t>(0), ctxt->GetMemberCount()) << "A created context was not empty"; // Add in an object to test asynchronous destruction: AutoRequired<WaitMember> obj; members[i] = obj; ASSERT_EQ(signal.get(), obj->m_signal.get()) << "Dependent context wiring did not correctly match to the enclosing scope"; // Add a notifier to signal a continue condition when we have everything we need: ctxt->AddTeardownListener([&lock, &cond, &counter] { (std::lock_guard<std::mutex>)lock, counter--, cond.notify_all(); }); // Kick off the context: ctxt->Initiate(); } // Signal all members and then release everything: signal->Signal(); } // Wait for all contexts to be destroyed std::unique_lock<std::mutex> lk(lock); bool wait_status = cond.wait_for(lk, std::chrono::seconds(1), [&counter] {return counter == 0;}); ASSERT_TRUE(wait_status) << "All teardown listeners didn't trigger, counter still at " << counter; // Validate that everything expires: ASSERT_EQ(static_cast<size_t>(0), creator->GetSize()) << "Not all contexts were evicted as expected"; }
TEST_F(CoreContextTest, InitiateAssertsSignals) { AutoCurrentContext outer; auto teardown = std::make_shared<bool>(false); { AutoCreateContext ctxt; auto initiated = std::make_shared<bool>(false); auto running = std::make_shared<bool>(false); auto shutdown = std::make_shared<bool>(false); ctxt->onInitiated += [initiated] { *initiated = true; }; ctxt->onRunning += [running] { *running = true; }; ctxt->onShutdown += [shutdown] { *shutdown = true; }; ctxt->onTeardown += [teardown] (const CoreContext&) { *teardown = true; }; ctxt->Initiate(); ASSERT_TRUE(*initiated) << "Initiation signal not asserted on context startup"; ASSERT_FALSE(*running) << "Running signal asserted before the outer context was started"; ASSERT_FALSE(*shutdown) << "Termination signal asserted prematurely"; *initiated = false; outer->Initiate(); ASSERT_FALSE(*initiated) << "Initiation signal was redundantly asserted"; ASSERT_TRUE(*running) << "Running signal not asserted when the outer context was started"; ASSERT_FALSE(*shutdown) << "Termination signal asserted prematurely"; *running = false; ctxt->Initiate(); ASSERT_FALSE(*initiated) << "Initiation signal redundantly asserted"; ASSERT_FALSE(*running) << "Running signal redundantly asserted"; ASSERT_FALSE(*shutdown) << "Termination signal asserted unexpectedly"; ctxt->SignalShutdown(); ASSERT_FALSE(*initiated) << "Initiation signal not asserted during teardown"; ASSERT_FALSE(*running) << "Running signal asserted improperly on teardown"; ASSERT_TRUE(*shutdown) << "Termination signal not asserted as expected"; ASSERT_FALSE(*teardown) << "Teardown handler notified prematurely"; } ASSERT_TRUE(*teardown) << "Teardown handler not correctly notified on context teardown"; }
TEST_F(AutowiringUtilitiesTest, ThreadSpecificPtr) { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<Thread1> thread1; AutoRequired<Thread2> thread2; s_thread_specific_int.reset(new int(5)); ctxt->Initiate(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); EXPECT_EQ(5, *s_thread_specific_int); AutoCurrentContext()->SignalShutdown(true); }
TEST_F(CoreContextTest, ChildContextSignalOrder) { AutoCurrentContext ctxt; AutoCreateContext childCtxt; AutoRequired<ChildListener> cl(childCtxt); childCtxt->Initiate(); // Now verify correct ordering: bool gotStartObservation = false; childCtxt->onRunning += [&] { gotStartObservation = cl->gotStart; }; ctxt->Initiate(); ASSERT_TRUE(gotStartObservation) << "Start observation obtained"; }
TEST_F(AutoPacketFactoryTest, AutoPacketFactoryCycle) { AutoCurrentContext()->Initiate(); std::weak_ptr<CoreContext> ctxtWeak; std::weak_ptr<HoldsAutoPacketFactoryReference> hapfrWeak; std::shared_ptr<AutoPacket> packet; { // Create a context, fill it up, kick it off: AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<HoldsAutoPacketFactoryReference> hapfr(ctxt); ctxt->Initiate(); // A weak pointer is used to detect object destruction ctxtWeak = ctxt; hapfrWeak = hapfr; // Trivial validation-of-reciept: AutoRequired<AutoPacketFactory> factory; { auto trivial = factory->NewPacket(); trivial->Decorate((int) 54); ASSERT_EQ(54, hapfr->m_value) << "A simple packet was not received as expected by an AutoFilter"; } // Create a packet which will force in a back-reference: packet = factory->NewPacket(); // Terminate the context: ctxt->SignalShutdown(); // Verify that we can still decorate the packet and also that the packet is delivered to the factory: packet->Decorate((int) 55); // Relock, verify the value was received by the hapfr: ASSERT_EQ(55, hapfr->m_value) << "AutoFilter did not receive a packet as expected"; } // The context cannot go out of socpe until all packets in the context are out of scope ASSERT_FALSE(ctxtWeak.expired()) << "Context went out of scope before all packets were finished being processed"; // Now we can release the packet and verify that everything gets cleaned up: packet.reset(); ASSERT_TRUE(ctxtWeak.expired()) << "AutoPacketFactory incorrectly held a cyclic reference even after the context was shut down"; ASSERT_TRUE(hapfrWeak.expired()) << "The last packet from a factory was released; this should have resulted in teardown, but it did not"; }
TEST_F(CoreContextTest, InitiateCausesDelayedHold) { std::weak_ptr<CoreContext> ctxtWeak; // Create and initiate a subcontext, but do not initiate the parent context { AutoCreateContext ctxt; ctxtWeak = ctxt; ctxt->Initiate(); } // Weak pointer should not be expired yet ASSERT_FALSE(ctxtWeak.expired()) << "Subcontext expired after initiation even though its parent context was not yet initiated"; // Starting up the outer context should cause the inner one to self destruct AutoCurrentContext()->Initiate(); ASSERT_TRUE(ctxtWeak.expired()) << "Subcontext containing no threads incorrectly persisted after termination"; }
TEST_F(AutowiringUtilitiesTest, ThreadSpecificPtr) { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<Thread1> thread1; AutoRequired<Thread2> thread2; s_thread_specific_int.reset(new int(5)); ctxt->Initiate(); auto limit = std::chrono::high_resolution_clock::now() + std::chrono::seconds(10); while(std::chrono::high_resolution_clock::now() < limit) if(5 == *s_thread_specific_int) return; FAIL() << "Thread specific pointer did not increment to the destination value in a timely fashion"; }
TEST_F(CoreContextTest, AppropriateShutdownInterleave) { // Need both an outer and an inner context AutoCurrentContext ctxtOuter; AutoCreateContext ctxtInner; // Need to inject types at both scopes AutoRequired<ExplicitlyHoldsOutstandingCount> outer(ctxtOuter); AutoRequired<ExplicitlyHoldsOutstandingCount> inner(ctxtInner); // Start both contexts up ctxtOuter->Initiate(); ctxtInner->Initiate(); // Now shut down the outer context. Hand off to an async, we want this to block. std::thread holder{ [ctxtOuter] { ctxtOuter->SignalShutdown(true); } }; auto holderClean = MakeAtExit([&holder] { holder.join(); }); // Need to ensure that both outstanding counters are reset at some point: { auto cleanup = MakeAtExit([&] { outer->Proceed(); inner->Proceed(); }); // Outer entry should have called "stop": auto future = outer->calledStop.get_future(); ASSERT_EQ( std::future_status::ready, future.wait_for(std::chrono::seconds(5)) ) << "Outer scope's OnStop method was incorrectly blocked by a child context member taking a long time to shut down"; } // Both contexts should be stopped now: ASSERT_TRUE(ctxtOuter->Wait(std::chrono::seconds(5))) << "Outer context did not tear down in a timely fashion"; ASSERT_TRUE(ctxtOuter->IsQuiescent()) << "Quiescence not achieved by outer context after shutdown"; ASSERT_TRUE(ctxtInner->Wait(std::chrono::seconds(5))) << "Inner context did not tear down in a timely fashion"; }