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"; }
void AutowiringEnclosure::OnTestEnd(const testing::TestInfo& info) { // Verify we can grab the test case back out and that the pointer is correct: Autowired<TestInfoProxy> ti; Autowired<AutowiringEnclosureExceptionFilter> ecef; auto ctxt = ecef ? ecef->GetContext() : nullptr; // Unconditionally reset the global context as the current context AutoGlobalContext()->SetCurrent(); // Global initialization tests are special, we don't bother checking closure principle on them: if(!strcmp("GlobalInitTest", info.test_case_name())) return; // Need to make sure we got back our exception filter before continuing: ASSERT_TRUE(ecef.IsAutowired()) << "Failed to find the enclosed context exception filter; unit test may have incorrectly reset the enclosing context before returning"; // Now try to tear down the test context enclosure: ctxt->SignalShutdown(); // Do not allow teardown to take more than 250 milliseconds or so if(!ctxt->Wait(std::chrono::milliseconds(250))) { // Critical error--took too long to tear down assert(false); } static const char s_autothrow[] = "AUTOTHROW_"; if(!strncmp(s_autothrow, info.name(), ARRAYCOUNT(s_autothrow) - 1)) // Throw expected, end here return; // If an exception occurred somewhere, report it: ASSERT_FALSE(ecef->m_excepted) << "An unhandled exception occurred in this context" << std::endl << "[" << (ecef->m_ti ? ecef->m_ti->name() : "unknown") << "] " << ecef->m_what; }
TEST_F(AutoInjectableTest, VerifySimpleInjection) { auto injector = MakeInjectable<SimpleObject>(); injector(); Autowired<SimpleObject> myobj; ASSERT_TRUE(myobj.IsAutowired()) << "Injectable failed to introduce a zero-arguments constructed item into the context"; }
TEST_F(CoreContextTest, CoreContextAdd) { auto myClass = std::make_shared<CoreContextAddTestClass>(); AutoCurrentContext ctxt; ctxt->Add(myClass); Autowired<CoreContextAddTestClass> mc; ASSERT_TRUE(mc.IsAutowired()) << "Manually registered interface was not detected as expected"; }
TEST_F(AutoInjectableTest, VerifyReduplicatedInjection) { auto injector = MakeInjectable<StealsConstructorArgument>("Hello") + MakeInjectable<StealsConstructorArgument>("World"); injector(); Autowired<StealsConstructorArgument> myobj; ASSERT_TRUE(myobj.IsAutowired()) << "Injectable failed to introduce a single-arguments constructed item into the context"; ASSERT_EQ("Hello", myobj->myVal) << "Injectable failed to copy an rvalue argument properly"; }
TEST_F(PostConstructTest, DelayedNotifyWhenAutowired) { // Autorequire, and add a registration: bool called = false; Autowired<SimpleObject> so; so.NotifyWhenAutowired([&called] { called = true; }); // Inject a type after: AutoRequired<SimpleObject>(); ASSERT_TRUE(called) << "An autowiring notification was not invoked on an already-satisfied field as expected"; }
TEST_F(PostConstructTest, InjectNotifyWhenAutowired) { // Autorequire, and add a registration: bool called = false; Autowired<Interface3> interface; interface.NotifyWhenAutowired([&called] { called = true; }); // Inject a type after: AutoCurrentContext()->Inject<Implementation3>(); ASSERT_TRUE(called) << "An autowiring notification was not invoked on an already-satisfied field as expected"; }
TEST_F(PostConstructTest, NotificationTeardownRace) { if (std::thread::hardware_concurrency() == 1) return; // Don't bother running on a single-core machine std::shared_ptr<CoreContext> pContext; auto quit = false; auto shouldQuit = MakeAtExit([&quit] { quit = true; }); std::atomic<size_t> counter{0}; // This thread sets up the race pathology: std::thread t([&] { while(!quit) { // Barrier until setup time: while(counter != 1) std::this_thread::yield(); if(!pContext) break; // Set the context current, then try to autowire: CurrentContextPusher pshr(pContext); Autowired<SimpleObject> sobj; sobj.NotifyWhenAutowired([] {}); // Now barrier, and then we will try to race against the context // for teardown. counter++; } }); for(size_t i = 0; i < 200; i++) { // Make a new context: AutoCreateContext ctxt; pContext = ctxt; // Wake up the other thread, let it set a notify-when-autowired: counter++; while(counter != 2) std::this_thread::yield(); // Counter goes back to zero before we make the next loop iteration: counter = 0; // Now we reset our pContext pointer, and then tell the thread // to race with us against context teardown: pContext.reset(); } // All done, wait for the thread to back out: quit = true; counter = 1; t.join(); }
TEST_F(AutoInjectableTest, VerifyInjectableAdditionPermutation2) { auto injector = MakeInjectable<StealsConstructorArgument>("Hello"); auto injector2 = MakeInjectable<SimpleObject>() + injector; injector2(); Autowired<StealsConstructorArgument> myStealObj; Autowired<SimpleObject> mySimpleObj; ASSERT_TRUE(myStealObj.IsAutowired()) << "Combined injectable failed to introduce a single-argument constructed item"; ASSERT_EQ("Hello", myStealObj->myVal) << "Combined injectable failed to copy an rvalue argument"; ASSERT_TRUE(mySimpleObj.IsAutowired()) << "Combined injectable failed to introduce a zero-arguments constructed"; }
TEST_F(PostConstructTest, MultiNotifyWhenAutowired) { // Add multiple notifications on the same space: int field = 0; Autowired<SimpleObject> so; for(size_t i = 10; i--;) so.NotifyWhenAutowired([&field] { field++; }); // Inject the type to trigger the autowiring AutoRequired<SimpleObject>(); // Verify that the notification got hit ten times: ASSERT_EQ(10, field) << "Autowiring lambdas did not run the expected number of times"; }
TEST_F(MultiInheritTest, VerifyCast) { // Create a dummy context and make it current: AutoCreateContext ctxt; CurrentContextPusher pshr(ctxt); // Insert a MultiInherit object: auto obj = ctxt->Inject<MultiInherit>(); // Autowire in the pObj: Autowired<MultiInherit> wiredPobj; ASSERT_TRUE(wiredPobj.IsAutowired()) << "Autowiring failed for a multi-inheritance object"; // Verify that we get a pObj back with correct casting: ASSERT_EQ(obj.get(), wiredPobj.get()) << "Autowiring failed on a multiple inheritance object"; }
TEST_F(PostConstructTest, VerifySmartBehavior) { AutoCurrentContext ctxt; // Add the smart class, which has a member that autowired type A ctxt->Inject<Smarter>(); // Check that we can get the item we just injected Autowired<Smarter> smarter; ASSERT_TRUE(smarter.IsAutowired()) << "Slot was not satisfied as expected"; ASSERT_EQ(1, smarter->value) << "Unexpected initial value of SmarterA instance"; // Now inject A, and see if delayed autowiring has taken place: ctxt->Inject<A>(); ASSERT_FALSE(!smarter->m_a.get()) << "Autowired member was not wired as expected"; // Verify the value was updated by the notification routine ASSERT_EQ(2, smarter->value) << "Post-construction notification routine wasn't invoked as expected"; }
TEST_F(ContextMemberTest, PathologicalResetCase) { Autowired<TypeThatIsNotInjected>* pv; volatile std::atomic<size_t> nBarr{ 0 }; volatile bool proceed = true; volatile bool go = false; auto resetsV = [&] { while(proceed) { ++nBarr; while (proceed && !go) std::this_thread::yield(); if (!proceed) break; pv->reset(); --nBarr; while (proceed && go) std::this_thread::yield(); } }; std::thread a{ resetsV }; std::thread b{ resetsV }; for (size_t i = 1000; i--;) { Autowired<TypeThatIsNotInjected> v; pv = &v; for (size_t j = 10; j--;) v.NotifyWhenAutowired([] {}); // Bump the threads to spin around: while (nBarr != 2) std::this_thread::yield(); go = true; while (nBarr) std::this_thread::yield(); go = false; } proceed = false; a.join(); b.join(); }
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); }
void Run(void) override { // Wait for our event to be signalled, then leave m_signal->Delay(); }
void Run(void) override { m_exitRace->Wait(); }
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(PostConstructTest, VerifyLoopingFailedAutowiring) { for(size_t i = 10; i--;) { Autowired<FailedAutowiringInstance> ignored; ASSERT_FALSE(ignored.IsAutowired()) << "Successfully autowired an instance that should not have been autowirable"; } }