TEST_F(AutoRestarterTest, RestartsOnException) { AutoCurrentContext()->Initiate(); // Use a bolt facility to attach one of our text fixtures: AutoCurrentContext()->BoltTo<ThrowsAnExceptionFirstTime, RestartingSigil>(); // Create our bolt and the restarter AutoRequired<CreationDetectionBolt> bolt; AutoRestarterConfig cfg; cfg.restartOnException = true; cfg.restartOnShutdown = false; cfg.startWhenCreated = true; AutoConstruct<AutoRestarter<RestartingSigil>> restarter(cfg); // Verify the bolt got called: ASSERT_TRUE(bolt->called) << "Bolt was not called even though a context restarter was present in the current context"; // Verify subcontext properties: auto subCtxt = restarter->GetContext(); ASSERT_TRUE(subCtxt != nullptr) << "Restarter did not correctly create a subcontext"; ASSERT_TRUE(subCtxt->Is<RestartingSigil>()) << "Generated subcontext was not marked with the right sigil"; ASSERT_TRUE(subCtxt->IsInitiated()) << "Generated subcontext should have been prospectively started, but was not"; // Terminate the subcontext directly: subCtxt->SignalShutdown(); // Verify that this causes the restarter to release its hold on the subcontext: auto newSubCtxt = restarter->GetContext(); ASSERT_NE(subCtxt, newSubCtxt) << "Restarter did not release a terminated subcontext"; ASSERT_FALSE(newSubCtxt) << "Restarter should not have attempted to create any new contexts on teardown"; }
TEST_F(CoreContextTest, CorrectHitAllocatorNew) { HasOverriddenNewOperator::s_deleterHitCount = 0; { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); // Verify that the overload itsef gets called as expected: ASSERT_EQ( (void*) HasOverriddenNewOperator::s_space, new HasOverriddenNewOperator(false) ) << "Overloaded new operator on a test type did not get invoked as expected"; // Create an instance which won't throw: auto hono = AutoCurrentContext()->Construct<HasOverriddenNewOperator>(false); // Verify the correct new allocator was hit: ASSERT_EQ( (void*)HasOverriddenNewOperator::s_space, (void*)hono.get() ) << "Overridden new allocator was not invoked as anticipated"; } // Verify that the deleter got hit as anticipated: ASSERT_EQ(1UL, HasOverriddenNewOperator::s_deleterHitCount) << "The correct deleter was not hit under ordinary teardown"; }
TEST_F(ContextEnumeratorTest, VerifySimpleEnumeration) { // Create a pair of descendant contexts, verify we don't accidentally hit these when enumerating children AutoCreateContext outer; AutoCreateContext inner; CurrentContextPusher pshr(inner); // Add a few children to the current context: AutoCreateContext c1; AutoCreateContext c2; AutoCreateContext c3; AutoCreateContext c4; std::unordered_set<std::shared_ptr<CoreContext>> allCtxts; allCtxts.insert(AutoCurrentContext()); allCtxts.insert(c1); allCtxts.insert(c2); allCtxts.insert(c3); allCtxts.insert(c4); // Create the enumerator, verify we get the expected count: size_t nContexts = 0; for(const std::shared_ptr<CoreContext>& cur : CurrentContextEnumerator()) { ASSERT_TRUE(!!allCtxts.count(cur)) << "Context enumerator accidentally enumerated a context not rooted in the specified parent"; allCtxts.erase(cur); nContexts++; } ASSERT_EQ(5UL, nContexts) << "Context enumerator did not encounter the number of expected children"; ASSERT_TRUE(allCtxts.empty()) << "Context enumerator did not encounter all children as expected"; }
TEST_F(AutoInjectableTest, VerifySimpleThreadWait) { // Immediate kickoff: AutoCurrentContext()->Initiate(); // Make an injectable, run it, and stuff it right into a future: AutoFuture future; MakeInjectable<CoreThread>()(&future); // Make a thread and then start it going: Autowired<CoreThread> thread; ASSERT_TRUE(thread.IsAutowired()) << "Thread was not injected by an injector as expected"; AutoRequired<std::mutex> barr; { std::lock_guard<std::mutex> lk(*barr); // Add a lambda that we intentionally block: *thread += [] { Autowired<CoreThread> thread; Autowired<std::mutex> barr; ASSERT_TRUE(thread && barr) << "Failed to find a required type in the current context"; std::lock_guard<std::mutex> lk(*barr); thread->Stop(); }; // Instant wait: ASSERT_FALSE(future.WaitFor(std::chrono::nanoseconds(1))) << "Premature wait return on an injector-provided future"; } // Now that the thread is unblocked, verify that it quits: ASSERT_TRUE(future.WaitFor(std::chrono::seconds(5))) << "Wait failed to return on an injector-provided future"; }
TEST_F(ContextEnumeratorTest, TrivialEnumeration) { size_t ct = 0; for(const auto& cur : ContextEnumerator(AutoCurrentContext())) { (void) cur; ct++; } ASSERT_EQ(1UL, ct) << "Context enumerator failed to enumerate a context with no children"; }
TEST_F(CoreContextTest, CorrectHitExceptionalTeardown) { HasOverriddenNewOperator::s_deleterHitCount = 0; // Create our type--we expect this to throw: ASSERT_ANY_THROW(AutoCurrentContext()->Construct<HasOverriddenNewOperator>(true)) << "Construct operation did not propagate an exception to the caller"; // Now verify that the correct deleter was hit to release partially constructed memory: ASSERT_EQ(1UL, HasOverriddenNewOperator::s_deleterHitCount) << "Deleter was not correctly hit in an exceptional teardown"; }
TEST_F(AutoPacketFactoryTest, CurrentPacket) { AutoCurrentContext()->Initiate(); AutoRequired<AutoPacketFactory> factory; ASSERT_EQ(nullptr, factory->CurrentPacket()) << "Current packet returned before any packets were issued"; auto packet = factory->NewPacket(); ASSERT_EQ(packet, factory->CurrentPacket()) << "Current packet was not reported correctly as being issued to the known current packet"; packet.reset(); ASSERT_EQ(nullptr, factory->CurrentPacket()) << "A current packet was reported after the current packet has expired"; }
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(AutoPacketFactoryTest, WaitRunsDownAllPackets) { AutoCurrentContext()->Initiate(); // Create a factory in our context, factory had better be started: AutoRequired<AutoPacketFactory> factory; ASSERT_TRUE(factory->IsRunning()) << "Factory was not started even though it was a member of an initiated context"; // Make the thread create and hold a packet, and then return AutoRequired<IssuesPacketWaitsThenQuits> ipwtq; // Shutdown context AutoCurrentContext()->SignalShutdown(); // Now we're going to try to run down the factory: factory->Wait(); // Verify that the thread has quit: ASSERT_TRUE(ipwtq->m_hasQuit) << "AutoPacketFactory::Wait returned prematurely"; }
TEST_F(AutoPacketFactoryTest, CanRemoveAddedLambda) { AutoCurrentContext()->Initiate(); AutoRequired<AutoPacketFactory> factory; auto desc = *factory += [](int&){}; auto packet1 = factory->NewPacket(); *factory -= desc; auto packet2 = factory->NewPacket(); ASSERT_TRUE(packet1->Has<int>()) << "First packet did not posess expected decoration"; ASSERT_FALSE(packet2->Has<int>()) << "Decoration present even after all filters were removed from a factory"; }
TEST_F(ObjectPoolTest, MovableObjectPoolAysnc) { static const size_t sc_count = 10000; ObjectPool<int> from; { // Issue a zillion objects from the from pool: std::vector<std::shared_ptr<int>> objs; for(size_t i = sc_count; i--;) objs.push_back(from.Wait()); // Make a thread, let it hold these objects while we move its pool: *AutoRequired<CoreThread>() += [objs] {}; } // Kick off threads, then immediately and asynchronously move the pool: AutoCurrentContext()->Initiate(); ObjectPool<int> to = std::move(from); // Shutdown time: AutoCurrentContext()->SignalShutdown(true); // Verify that new pool got all of the objects: ASSERT_EQ(sc_count, to.GetCached()) << "Object pool move operation did not correctly relay checked out types"; }
TEST_F(ObjectPoolTest, OutstandingLimitIsOne) { ObjectPool<int> pool(1); AutoRequired<CoreThread> ct; AutoCurrentContext()->Initiate(); // Bounce 1000 shared pointers into the CoreThread for(size_t i = 0; i < 1000; i++) { // Try to wait on the pool: auto entity = pool.WaitFor(std::chrono::seconds(10)); ASSERT_NE(nullptr, entity) << "Failed to obtain an entity from a single-element pool"; // Pend a lambda that just holds a closure on the entity: *ct += [entity] {}; } }
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(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(AutoRestarterTest, RestartsOnShutdown) { AutoCurrentContext()->Initiate(); // Create the restarter AutoRestarterConfig cfg; cfg.restartOnShutdown = true; cfg.startWhenCreated = true; AutoConstruct<AutoRestarter<RestartingSigil>> restarter(cfg); // Terminate the restarter's subcontext: auto subCtxt = restarter->GetContext(); subCtxt->SignalShutdown(); // New context should be created immediately ASSERT_NE(subCtxt, restarter->GetContext()) << "Restarter incorrectly held original context beyond shutdown"; ASSERT_TRUE(restarter->GetContext() != nullptr) << "Restarter did not correctly generate a new context after termination"; }
TEST_F(ObjectPoolTest, VerifyAsynchronousUsage) { AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); AutoRequired<SimpleThreadedT<PooledObject>> obj; AutoFired<SharedPtrReceiver<PooledObject>> spr; ObjectPool<PooledObject> pool(3); { // Obtain the pool limit in objects: std::shared_ptr<PooledObject> obj1, obj2, obj3; pool(obj1); pool(obj2); pool(obj3); ASSERT_TRUE(nullptr != obj1.get()) << "Failed to obtain an entry from a new object pool"; // Block--verify that we _do not_ get any of those objects back while they are // still outstanding. { auto obj4 = pool.WaitFor(std::chrono::milliseconds(1)); EXPECT_TRUE(obj4 == nullptr) << "Pool issued another element even though it should have hit its outstanding limit"; } // Now we kick off threads: AutoCurrentContext()->Initiate(); // Fire off a few events: spr(&SharedPtrReceiver<PooledObject>::OnEvent)(obj1); spr(&SharedPtrReceiver<PooledObject>::OnEvent)(obj2); spr(&SharedPtrReceiver<PooledObject>::OnEvent)(obj3); } // This should return more or less right away as objects become available: { auto obj4 = pool.WaitFor(std::chrono::milliseconds(10)); EXPECT_TRUE(obj4 != nullptr) << "Object pool failed to be notified that it received a new element"; } // Cause the thread to quit: *obj += [&obj] { obj->Stop(); }; obj->Wait(); }
TEST_F(ContextEnumeratorTest, SimpleRemovalInterference) { static const size_t nChildren = 5; // Create a few contexts which we intend to destroy as we go along: std::unordered_set<std::shared_ptr<CoreContext>> contexts; for(size_t i = nChildren; i--;) contexts.insert(AutoCreateContext()); // Also add ourselves: contexts.insert(AutoCurrentContext()); // Enumerate contexts, and remove them from the set: size_t nRemoved = 0; for(const auto& cur : CurrentContextEnumerator()) { ASSERT_TRUE(!!contexts.count(cur)) << "Failed to find a context enumerated by the context enumerator"; contexts.erase(cur); nRemoved++; } // Verify we got the number removed we expected: ASSERT_EQ(nChildren + 1UL, nRemoved) << "Context enumerator did not remove the expected number of children"; }
int main() { // Initiate the current context AutoCurrentContext()->Initiate(); // Inject our AutoFilter enabled context members into the context AutoRequired<StringFilter>(); AutoRequired<StringIntFilter>(); // Each context automatically includes an AutoPacketFactory type. This // can be used to create new packets in the AutoFilter network Autowired<AutoPacketFactory> factory; // When decorating a packet, all AutoFilters that have that type as an argument // will be called. It is only called when all argument types have been decorated auto packet = factory->NewPacket(); packet->Decorate(std::string("Hello World")); std::cout << "StringIntFilter not called yet" << std::endl; // StringIntFilter will now be called since all AutoFilter arguments have ben decorated packet->Decorate(42); }
int main () { // Initialization of context, injection of context members, obtaining access to the factory. AutoCurrentContext()->Initiate(); AutoRequired<Hippo1> hippo1; AutoRequired<Hippo2> hippo2; AutoRequired<Hippo3> hippo3; AutoRequired<Hippo4> hippo4; AutoRequired<Hippo5> hippo5; AutoRequired<Hippo6> hippo6; Autowired<AutoPacketFactory> factory; // Declare a packet to use in the following code blocks. std::shared_ptr<AutoPacket> packet; /// At this point we will execute the filter network a number of times, each with a different type related to `std::string`, /// in order to observe which filters are executed. The first six executions demonstrate the types that are equivalent to /// `std::string` as input parameters (in which we expect only `Hippo#::AutoFilter` to be called). { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `std::string`:\n"; std::string s("std::string"); packet->Decorate(s); std::cout << '\n'; } { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `const std::string`:\n"; const std::string s("const std::string"); packet->Decorate(s); std::cout << '\n'; } { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `std::shared_ptr<std::string>`:\n"; std::shared_ptr<std::string> s = std::make_shared<std::string>("std::shared_ptr<std::string>"); packet->Decorate(s); std::cout << '\n'; } { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `const std::shared_ptr<std::string>`:\n"; const std::shared_ptr<std::string> s = std::make_shared<std::string>("const std::shared_ptr<std::string>"); packet->Decorate(s); std::cout << '\n'; } { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `std::shared_ptr<const std::string>`:\n"; std::shared_ptr<const std::string> s = std::make_shared<const std::string>("std::shared_ptr<const std::string>"); packet->Decorate(s); std::cout << '\n'; } { packet = factory->NewPacket(); std::cout << "Decorating packet with instance of `const std::shared_ptr<const std::string>`:\n"; const std::shared_ptr<const std::string> s = std::make_shared<const std::string>("const std::shared_ptr<const std::string>"); packet->Decorate(s); std::cout << '\n'; } /// Verify that the accumulated values in the `m_received_input_decoration_types` for each context member are what we expect. { std::unordered_set<std::string> expected_hippo_inputs({ "std::string", "const std::string", "std::shared_ptr<std::string>", "const std::shared_ptr<std::string>", "std::shared_ptr<const std::string>", "const std::shared_ptr<const std::string>" }); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo1 did not receive the expected inputs."); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo2 did not receive the expected inputs."); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo3 did not receive the expected inputs."); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo4 did not receive the expected inputs."); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo5 did not receive the expected inputs."); if (hippo1->m_received_input_decoration_types != expected_hippo_inputs) throw std::runtime_error("Hippo6 did not receive the expected inputs."); std::cout << "All Hippo# context members received the expected inputs.\n"; } std::cout << "All verifications passed.\n"; return 0; // Return with no error. }
TEST_F(AutoPacketFactoryTest, StopReallyStops) { AutoCurrentContext()->SignalShutdown(); AutoRequired<AutoPacketFactory> factory; ASSERT_TRUE(factory->ShouldStop()) << "Expected that an attempt to insert a packet factory to an already-stopped context would stop the packet factory"; }
TEST_F(AutoPacketFactoryTest, VerifyNoIssueWhileStopped) { AutoCurrentContext()->SignalShutdown(); AutoRequired<AutoPacketFactory> factory; ASSERT_THROW(factory->NewPacket(), autowiring_error) << "Issuing a packet in a context that has already been stopped should throw an exception"; }