TEST_F(AutoPacketFactoryTest, AutoPacketStatistics) { // Create a context, fill it up, kick it off: AutoCurrentContext ctxt; AutoRequired<DelaysAutoPacketsOneMS> dapoms; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); int numPackets = 20; // Send 20 packets which should all be delayed 1ms for (int i = 0; i < numPackets; ++i) { auto packet = factory->NewPacket(); packet->Decorate(i); } // Shutdown our context, and rundown our factory ctxt->SignalShutdown(); factory->Wait(); // Ensure that the statistics are not too wrong // We delayed each packet by one ms, and our statistics are given in nanoseconds double packetDelay = (double) std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(1)).count(); ASSERT_EQ(numPackets, factory->GetTotalPacketCount()) << "The factory did not get enough packets"; ASSERT_LE(packetDelay, factory->GetMeanPacketLifetime()) << "The mean packet lifetime was less than the delay on each packet"; }
TEST_F(DecoratorTest, VerifyDecoratorAwareness) { auto filter = [](const Decoration<0>& zero, const Decoration<1>& one) {}; // Create a packet while the factory has no subscribers: AutoRequired<AutoPacketFactory> factory; auto packet1 = factory->NewPacket(); // Verify subscription-free status: ASSERT_FALSE(packet1->HasSubscribers<Decoration<0>>()) << "Subscription exists where one should not have existed"; // Create another packet where a subscriber exists: *factory += filter; auto packet2 = factory->NewPacket(); // Verify the first packet still does not have subscriptions: ASSERT_THROW(packet1->GetSatisfaction<decltype(filter)>(), autowiring_error) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_FALSE(packet1->HasSubscribers<Decoration<0>>()) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_EQ(0UL, packet1->GetDecorationTypeCount()) << "Subscription was incorrectly, retroactively added to a packet"; ASSERT_FALSE(packet1->HasSubscribers<Decoration<0>>()) << "Subscription was incorrectly, retroactively added to a packet"; // Verify the second one does: ASSERT_NO_THROW(packet2->GetSatisfaction<decltype(filter)>()) << "Packet lacked an expected subscription"; ASSERT_EQ(2UL, packet2->GetDecorationTypeCount()) << "Incorrect count of expected decorations"; ASSERT_TRUE(packet2->HasSubscribers<Decoration<0>>()) << "Packet lacked an expected subscription"; }
int main(){ // The 2 main thread classes in Autowiring are the BasicThread and CoreThread. // Classes that inherit from these types will have thread capabilities // Both start when their enclosing context is 'initiated'. Threads injected // after the context is initiated will start immediatly AutoRequired<MyBasicThread> myBasic; AutoCurrentContext ctxt; ctxt->Initiate(); // myBasic->Run() starts now in its own thread std::this_thread::sleep_for(std::chrono::milliseconds(250)); std::cout << "injecting a CoreThread" << std::endl; // Types inheriting from CoreThread implement a dispatch queue in their 'run()' // function. Lambdas can be appended with operator+= AutoRequired<MyCoreThread> myCore; myCore->AddToQueue(42); myCore->AddToQueue(1337); *myCore += []{ std::cout << "This gets run after '1337'" << std::endl; }; // This should be run before 'myCore' is finished std::cout << "This thread is faster\n"; // This will wait for all outstanding threads to finish before terminating the context ctxt->SignalShutdown(true); }
TEST_F(ContextCreatorTest, ClearAndTeardown) { // Create a context and verify it gets evicted from the context creator: std::weak_ptr<CoreContext> ctxtWeak; { AutoCreateContext mainContext; CurrentContextPusher pusher(mainContext); AutoRequired<Creator> creator; std::shared_ptr<CoreContext> ctxt; // Make a sub-context ctxt = creator->CreateContext(0).first; // Obtain a weak pointer, in order to ensure proper teardown: ctxtWeak = ctxt; //Call clear on the creator. creator->Clear(true); //Make another one! ctxt = creator->CreateContext(1).first; //Let the creator go out of scope } // Context must be destroyed as a precondition of the subsequent assertion ASSERT_TRUE(ctxtWeak.expired()) << "Expected the context to be destroyed"; // Verify that our creator is now empty: // ASSERT_EQ(0UL, creator->GetSize()) << "Context creator is non-empty after all created contexts were destroyed"; }
IssuesPacketWaitsThenQuits(void) { // Note: Don't do this in practice. This only works because we only inject this type // into a context that's already running; normally, creating a packet from our ctor can // cause an exception if we are being injected before Initiate is called. AutoRequired<AutoPacketFactory> factory; m_packet = factory->NewPacket(); }
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(DecoratorTest, VerifySimplePacketDecoration) { AutoRequired<AutoPacketFactory> factory; // Create the packet we will be persisting: auto packet = factory->NewPacket(); // Add a few decorations on this packet: auto& knownDec0 = packet->Decorate(Decoration<0>()); auto& knownDec1 = packet->Decorate(Decoration<1>()); auto& knownDec2 = packet->Decorate(Decoration<2>()); // Verify we can get these packets back--might throw exceptions here! auto& dec0 = packet->Get<Decoration<0>>(); auto& dec1 = packet->Get<Decoration<1>>(); auto& dec2 = packet->Get<Decoration<2>>(); // Verify identities: EXPECT_EQ(&knownDec0, &dec0) << "Decoration 0 returned at an incorrect location"; EXPECT_EQ(&knownDec1, &dec1) << "Decoration 1 returned at an incorrect location"; EXPECT_EQ(&knownDec2, &dec2) << "Decoration 2 returned at an incorrect location"; // Verify content correctness: EXPECT_EQ(0, dec0.i) << "Decoration 0 incorrectly persisted"; EXPECT_EQ(1, dec1.i) << "Decoration 1 incorrectly persisted"; EXPECT_EQ(2, dec2.i) << "Decoration 2 incorrectly persisted"; }
TEST_F(AutoPacketFactoryTest, AddSubscriberTest) { AutoCurrentContext ctxt; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); bool first_called = false; bool second_called = false; factory->AddSubscriber(AutoFilterDescriptor([&first_called](int) {first_called = true; })); { std::vector<AutoFilterDescriptor> descs; factory->AppendAutoFiltersTo(descs); ASSERT_EQ(1UL, descs.size()) << "Expected exactly one AutoFilters after call to AddSubscriber"; } *factory += [&second_called] (int v) { second_called = true; ASSERT_EQ(101, v) << "Decoration value mismatch"; }; { std::vector<AutoFilterDescriptor> descs; factory->AppendAutoFiltersTo(descs); ASSERT_EQ(2UL, descs.size()) << "Expected exactly two AutoFilters on this packet"; } auto packet = factory->NewPacket(); ASSERT_FALSE(first_called) << "Normal subscriber called too early"; ASSERT_FALSE(second_called) << "Subscriber added with operator+= called too early"; packet->DecorateImmediate(int(101)); ASSERT_TRUE(first_called) << "Normal subscriber never called"; ASSERT_TRUE(second_called) << "Subscriber added with operator+= never called"; }
TEST_F(AutoPacketFactoryTest, IsRunningWhilePacketIssued) { AutoCurrentContext ctxt; ctxt->Initiate(); AutoRequired<AutoPacketFactory> factory; auto packet = factory->NewPacket(); ctxt->SignalShutdown(); ASSERT_TRUE(factory->IsRunning()) << "Factory should be considered to be running as long as packets are outstanding"; }
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(DecoratorTest, ForwardAllTest) { AutoRequired<AutoPacketFactory> factory; auto packet1 = factory->NewPacket(); auto packet2 = factory->NewPacket(); packet1->Decorate(Decoration<0>()); packet1->ForwardAll(packet2); ASSERT_TRUE(packet2->Has<Decoration<0>>()) << "Forwarded packet did not have a decoration present on the original packet as expected"; }
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"; }
int main(){ AutoCurrentContext ctxt; // A context must be initiated before events can be received. Similar to CoreThread ctxt->Initiate(); // This creates an proxy object that can fire MyEvent::* events AutoFired<MyEvent> eventFirer; // Inject receiver types into current context AutoRequired<FooEvent> foo; AutoRequired<BarEvent> bar; std::cout << "Foo should be 0: " << foo->getSecret() << std::endl; std::cout << "Bar Should be 0: " << bar->getSecret() << std::endl; // Fire event, this should set m_secret on both foo and bar eventFirer(&MyEvent::myFunction)(42); std::cout << "Foo should be 42: " << foo->getSecret() << std::endl; std::cout << "Bar should be 42: " << bar->getSecret() << std::endl; // You can also manually fire events on a context with `Invoke` // Since the function pointer is to `BarEvent`, `FooEvent` won't receive the event ctxt->Invoke(&BarEvent::myFunction)(77); std::cout << "Foo should be 42: " << foo->getSecret() << std::endl; std::cout << "Bar should be 77: " << bar->getSecret() << std::endl; }
TEST_F(ContextCreatorTest, TeardownListenerTest) { // Create a context and verify teardown happens as expected AutoCreateContext mainContext; CurrentContextPusher pusher(mainContext); AutoRequired<ContextCreator<mySigil, int>> creator; { auto subctxt = creator->CreateContext(0).first; auto brc = subctxt->Inject<Runnable>(); subctxt->Initiate(); } creator->Clear(true); ASSERT_TRUE(true) << "Really all this test has to do is not crash by this point."; }
TEST_F(AutoPacketFactoryTest, MultipleInstanceAddition) { AutoCurrentContext ctxt; AutoRequired<AutoPacketFactory> factory; ctxt->Initiate(); bool ary[2] = {}; for (size_t i = 0; i < 2; i++) *factory += [i, &ary] (int) { ary[i] = true; }; auto packet = factory->NewPacket(); packet->Decorate(101); ASSERT_TRUE(ary[0]) << "First of two identically typed AutoFilter lambdas was not called"; ASSERT_TRUE(ary[1]) << "Second of two identically typed AutoFilter lambdas was not called"; }
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(DecoratorTest, VerifyDecoratorAwareness) { // Create a packet while the factory has no subscribers: AutoRequired<AutoPacketFactory> factory; auto packet1 = factory->NewPacket(); // Verify subscription-free status: EXPECT_FALSE(packet1->HasSubscribers<Decoration<0>>()) << "Subscription exists where one should not have existed"; // Create another packet where a subscriber exists: AutoRequired<FilterA> filterA; auto packet2 = factory->NewPacket(); // Verify the first packet still does not have subscriptions: EXPECT_FALSE(packet1->HasSubscribers<Decoration<0>>()) << "Subscription was incorrectly, retroactively added to a packet"; // Verify the second one does: EXPECT_TRUE(packet2->HasSubscribers<Decoration<0>>()) << "Packet lacked an expected subscription"; }
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(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(ContextCreatorTest, ValidateSimpleEviction) { // Create a context and verify it gets evicted from the context creator: AutoRequired<Creator> creator; std::weak_ptr<CoreContext> ctxtWeak; { std::shared_ptr<CoreContext> ctxt; // Make a context: ctxt = creator->CreateContext(1).first; // Obtain a weak pointer, in order to ensure proper teardown: ctxtWeak = ctxt; } // Context must be destroyed as a precondition of the subsequent assertion ASSERT_TRUE(ctxtWeak.expired()) << "Expected the context to be destroyed"; // Verify that our creator is now empty: ASSERT_EQ(0UL, creator->GetSize()) << "Context creator is non-empty after all created contexts were destroyed"; }
TEST_F(CoreContextTest, UnlinkOnTeardown) { std::weak_ptr<ClassThatPoints1> weakA; std::shared_ptr<ClassThatPoints2> strongB; AutoRequired<SimpleObject> so; // Set up a subcontext with some cycles and external links: { // Main context that gets reset second AutoCreateContext ctxt; // Sibling context that we're also going to reset AutoCreateContext otherContext; otherContext->Inject<std::vector<int>>(); AutoRequired<ClassThatPoints1> a(ctxt); AutoRequired<ClassThatPoints2> b(ctxt, otherContext); weakA = a; strongB = b; ASSERT_TRUE(a->so.IsAutowired()) << "Root object pointer not correctly obtained"; ASSERT_TRUE(b->so.IsAutowired()) << "Root object pointer not correctly obtained"; ctxt->onTeardown += [weakA, strongB] (const CoreContext&) { // Verify that nothing got screwed up at this point: auto a = weakA.lock(); ASSERT_FALSE(weakA.expired()) << "Weak pointer expired prematurely"; ASSERT_EQ(strongB, a->b) << "Unlink occurred prematurely"; ASSERT_EQ(a, strongB->a) << "Unlink occured prematurely"; }; // Set the flag at the last possible and to ensure things still get torn down ctxt->SetUnlinkOnTeardown(true); } ASSERT_TRUE(weakA.expired()) << "A reference was leaked even though unlinking was turned on"; ASSERT_TRUE(strongB->v.IsAutowired()) << "An Autowired field pointing to a foreign context was incorrectly unlinked"; ASSERT_EQ(so.get(), strongB->so.get()) << "An Autowired field was unlinked on teardown even though it pointed outside of a context"; }
TEST_F(ContextCreatorTest, VoidKeyType) { AutoRequired<VoidCreator> vc; { std::shared_ptr<CoreContext> ctxt = vc->CreateContext(); ASSERT_EQ(1UL, vc->GetSize()) << "Requested that a context be created, but the void creator did not have any members"; ASSERT_EQ(1UL, vc->GetSize()) << "A created context was apparently destroyed after firing bolts"; ASSERT_EQ(0UL, vc->m_totalDestroyed) << "The void creator received a NotifyContextDestroyed call unexpectedly early"; vc->Clear(true); //Make another one to check about collisions std::shared_ptr<CoreContext> ctxt2 = vc->CreateContext(); ASSERT_EQ(1UL, vc->GetSize()) << "Second void context creation failed!"; } ASSERT_EQ(0UL, vc->GetSize()) << "A void context creator was not correctly updated when its dependent context went out of scope"; ASSERT_EQ(2UL, vc->m_totalDestroyed) << "The void creator did not receive the expected number of NotifyContextDestroyed calls"; }
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"; }
TEST_F(AutoPacketFactoryTest, VerifyNoIssueWhileNotStarted) { AutoRequired<AutoPacketFactory> factory; ASSERT_THROW(factory->NewPacket(), autowiring_error) << "Issuing a packet in a context that has not yet been started should throw an exception"; }