/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /// @file ManufactoryTest.cpp #include #include #include #include "acsdkManufactory/Component.h" #include "acsdkManufactory/ComponentAccumulator.h" #include "acsdkManufactory/Manufactory.h" #include "acsdkManufactory/OptionalImport.h" namespace alexaClientSDK { namespace acsdkManufactory { namespace test { using namespace ::testing; using namespace acsdkManufactory; using namespace std; class ManufactoryTest : public ::testing::Test { public: }; /** * Interface for setting a string. */ class InterfaceA { public: /** * Destructor. */ virtual ~InterfaceA() = default; /** * Set the string value. * * @param input The new value for the string. */ virtual void setString(const string& input) = 0; }; /** * Interface for accessing an object's ID and string value. */ class InterfaceB { public: /** * Destructor */ virtual ~InterfaceB() = default; /** * Get the object's ID. * * @return The object's ID. */ virtual int getId() const = 0; /** * Get the string value of the object. * * @return The string value of the object. */ virtual string getString() const = 0; }; /** * Interface combining InterfaceA and InterfaceB. */ class InterfaceAB : public InterfaceA , public InterfaceB {}; /** * Class implementing InterfaceAB. */ class AB : public InterfaceAB { public: /** * Constructor. Provides each instance of AB with a unique ID. */ AB() : m_id(m_nextId++) { } int getId() const override { return m_id; } void setString(const string& input) override { m_state = input; }; string getString() const override { return m_state; }; private: /// The ID of the next instance of AB. static int m_nextId; /// The ID of this instance of AB. const int m_id; /// The string value of this instance of AB. string m_state; }; int AB::m_nextId = 1; /// Type used to annotate (i.e. distinguish instances of) another type. struct Annotation1 {}; /// Type used to annotate (i.e. distinguish instances of) another type. struct Annotation2 {}; /// Type used to annotate (i.e. distinguish instances of) another type. struct Annotation3 {}; /// Type used to annotate (i.e. distinguish instances of) another type. struct Annotation4 {}; /** * Template class for defining simple derivations of AB. * @tparam X Number used to differentiate derived types. */ template class ABSubclass : public AB {}; // ----- test_manufactureUnique ----- // /** * Factory for creating a new instance of InterfaceA and returning it via unique_ptr. * * @return A new instance of InterfaceA. */ unique_ptr createUniqueA() { return unique_ptr(new AB()); } /** * Test manufacture of an instances returned via unique_ptr. Verify that multiple instances of the same * interface are indeed distinct instances. */ TEST_F(ManufactoryTest, test_manufactureUnique) { Component> component(ComponentAccumulator<>().addUniqueFactory(createUniqueA)); auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); auto a1 = manufactory->get>(); ASSERT_TRUE(a1); auto a2 = manufactory->get>(); ASSERT_TRUE(a2); ASSERT_NE(a1, a2); } // ----- test_manufactureShared ----- // /** * Factory for creating a new instance of InterfaceAB and returning it via shared_ptr. * * @return A new instance of InterfaceAB. */ shared_ptr createSharedAB() { return make_shared(); } /** * Test manufacture of an 'unloadable' instance returned via shared_ptr. Verify that while an instance * is still referenced, manufacture of the same interface will provide the same instance. Also verify that * when all references have been deleted, the next manufacture will create a new instance. */ TEST_F(ManufactoryTest, test_manufactureShared) { Component> component(ComponentAccumulator<>().addUnloadableFactory(createSharedAB)); auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); // Verify created instances are shared... auto ab1 = manufactory->get>(); ASSERT_TRUE(ab1); ASSERT_TRUE(ab1->getString().empty()); auto ab2 = manufactory->get>(); ASSERT_TRUE(ab2); ASSERT_EQ(ab1, ab2); ab1->setString("something"); // ...and that shared instances are released when references outside the manufactory are reset. ab1.reset(); ab2.reset(); auto ab3 = manufactory->get>(); ASSERT_TRUE(ab3); ASSERT_TRUE(ab3->getString().empty()); } // ----- test_multipleInhertence ----- // /** * Factory for InterfaceA that depends upon an instance of InterfaceAB, and which returns an InterfaceA * pointer the the provided instance of InterfaceAB. * * @param ab The instance of InterfaceAB to use to create an InterfaceA. * @return The InterfaceA portion of the InterfaceAB that was provided. */ shared_ptr createAFromAB(const shared_ptr& ab) { return ab; } /** * Factory for InterfaceB that depends upon an instance of InterfaceAB, and which returns an InterfaceB * pointer the the provided instance of InterfaceAB. * * @param ab The instance of InterfaceAB to use to create an InterfaceB. * @return The InterfaceB portion of the InterfaceAB that was provided. */ shared_ptr createBFromAB(const shared_ptr& ab) { return ab; } /** * Factory used to create an instance of InterfaceAB. * * @return An instance of InterfaceAB. */ shared_ptr createAB() { return make_shared(); } /** * Definition of a component that exports implementations of InterfaceA and InterfaceB. * @return */ Component, shared_ptr> getABComponent() { ComponentAccumulator<> accumulator; return accumulator.addUnloadableFactory(createAFromAB) .addUnloadableFactory(createBFromAB) .addUnloadableFactory(createAB); } /** * Verify that a single object can be manufactured as multiple interfaces. */ TEST_F(ManufactoryTest, test_multipleInhertance) { auto component = getABComponent(); auto manufactory = Manufactory, shared_ptr>::create(component); ASSERT_TRUE(manufactory); auto a = manufactory->get>(); ASSERT_TRUE(a); auto b = manufactory->get>(); ASSERT_TRUE(b); // Make sure b's string is empty. ASSERT_TRUE(b->getString().empty()); // Set a's string value. a->setString("something"); // Verify that setting a's string sets b's string value. ASSERT_FALSE(b->getString().empty()); } // ------ test_annotatedManufacture ----- // /** * Factory used to create an annotated (with Annotation1) instance of InterfaceAB that depends upon * the instance of InterfaceAB annotated with Annotation3. * * @param ab3 A different instance of InterfaceAB (annotated with Annotation3) * @return The instance of InterfaceAB annotated with Annotation1. */ Annotated createAB1(const Annotated& ab3) { if (!ab3) { EXPECT_TRUE(ab3); return nullptr; } EXPECT_EQ(ab3->getString(), "3"); auto result = createAB(); result->setString("1"); return result; } /** * Factory used to create an annotated (with Annotation2) instance of InterfaceAB. * * @return The instance of InterfaceAB annotated with Annotation2. */ Annotated createAB2() { auto result = createAB(); result->setString("2"); return result; } /** * Factory used to create an annotated (with Annotation3) instance of InterfaceAB that depends upon * the instance of InterfaceAB annotated with Annotation2. * * @param ab2 A different instance of InterfaceAB (annotated with Annotation2) * @return The instance of InterfaceAB annotated with Annotation3. */ Annotated createAB3(const Annotated& ab2) { if (!ab2) { EXPECT_TRUE(ab2); return nullptr; } EXPECT_EQ(ab2->getString(), "2"); auto result = createAB(); result->setString("3"); return result; } /** * Definition of a component that exports instances of InterfaceAB annotated with Annotation1 and Annotation2, * and which imports an instance of InterfaceAB annotated with Annotation3. * * @return A component that exports instances of InterfaceAB annotated with Annotation1 and Annotation2... */ Component< Annotated, Annotated, Import>> getComponent12() { ComponentAccumulator<> accumulator; return accumulator.addUnloadableFactory(createAB1).addUnloadableFactory(createAB2); } /** * Definition of a component that exports the instance of InterfaceAB annotated with Annotation3. * * @return A component that exports the instance of InterfaceAB annotated with Annotation3 */ Component> getComponent3() { ComponentAccumulator<> accumulator; return accumulator.addComponent(getComponent12()).addUnloadableFactory(createAB3); } /** * Verify that annotations can be used to distinguish specific instances of objects. * Also verify that imports between components are properly resolved. */ TEST_F(ManufactoryTest, test_annotatedManufacture) { auto component = getComponent3(); auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); auto ab3 = manufactory->get>(); ASSERT_TRUE(ab3); ASSERT_EQ(ab3->getString(), "3"); auto anotherAb3 = manufactory->get>(); ASSERT_EQ(ab3, anotherAb3); } // ----- test_retainedManufacture ----- // /** * Definition of a Component that exports 'retained' (not unloadable) instances of InterfaceAB. * * @return A Component that exports 'retained' (not unloadable) instances of InterfaceAB. */ Component> getRetainedABComponent() { ComponentAccumulator<> accumulator; return accumulator.addRetainedFactory(createAB); } /** * Verify that manufacture of retained interfaces provides a single instance fore the life of the manufactory. */ TEST_F(ManufactoryTest, test_retainedManufacture) { auto component = getRetainedABComponent(); // Verify that retained instances are retained across manufactures. int id1 = 0; { auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); auto ab1 = manufactory->get>(); ASSERT_TRUE(ab1); id1 = ab1->getId(); auto ab2 = manufactory->get>(); ASSERT_TRUE(ab2); auto id2 = ab2->getId(); ASSERT_EQ(ab1, ab2); ASSERT_EQ(id1, id2); } // Verify that even retained instances are retained even when all references (outside of the manufactory) // are released. { auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); auto ab3 = manufactory->get>(); ASSERT_TRUE(ab3); auto id3 = ab3->getId(); ASSERT_NE(id1, id3); } }; // ----- test_requiredManufacture ----- // /** * Factory for an instance of InterfaceAB annotated with Anotation1. * * @return An instance of InterfaceAB annotated with Anotation1 */ Annotated createRetainedAB1() { return createAB(); } /** * Factory for an instance of InterfaceAB annotated with Anotation2, which is dependent upon an instance of * InterfaceAB annotated with Annotation1. * * @param ab1 An instance of InterfaceAB annotated with Annotation1. * @return An instance of InterfaceAB annotated with Annotation2. */ Annotated createRequiredAB2(const Annotated& ab1) { if (!ab1) { EXPECT_TRUE(ab1); return nullptr; } ab1->setString("ab2 was here!"); return createAB(); } /** * Definition of a Component that exports a 'required' instance of InterfaceAB annotated with Annotation1. * * @return A Component that exports an instance of InterfaceAB annotated with Annotation1. */ Component> getRequiredComponent() { ComponentAccumulator<> accumulator; return accumulator.addRetainedFactory(createRetainedAB1).addRequiredFactory(createRequiredAB2); }; /** * Verify that manufacture of a required interface results in instantiation of the interface at the time that * the @c Manufactory is created rather than being driven by other dependencies. */ TEST_F(ManufactoryTest, test_requiredManufacture) { auto component = getRequiredComponent(); auto manufactory = Manufactory>::create(component); ASSERT_TRUE(manufactory); auto ab1 = manufactory->get>(); ASSERT_TRUE(ab1); ASSERT_EQ(ab1->getString(), "ab2 was here!"); } // ----- test_primeManufacture ----- /** * Template function to create instances of a variety of subclasses of AB. * * @tparam X A number used to distinguish subclasses. * @return A new instance of ABSubclass. */ template static shared_ptr> createABSubclass() { return make_shared>(); } /** * Definition of a component that includes both primary and required types to verify proper ordering of their * instantiation. * * @return A component that includes both primary and required types. */ Component>, shared_ptr>, shared_ptr>> getPrimaryTestComponent() { return ComponentAccumulator<>() .addRequiredFactory(createABSubclass<1>) .addPrimaryFactory(createABSubclass<2>) .addRequiredFactory(createABSubclass<3>); }; /** * Verify that primary factories are invoked before required factories. */ TEST_F(ManufactoryTest, test_primeManufacture) { auto component = getPrimaryTestComponent(); auto manufactory = Manufactory>, shared_ptr>, shared_ptr>>::create(component); ASSERT_TRUE(manufactory); auto v1 = manufactory->get>>(); ASSERT_TRUE(v1); auto v3 = manufactory->get>>(); ASSERT_TRUE(v3); auto v2 = manufactory->get>>(); ASSERT_TRUE(v2); // Because ABSubclass<2> is 'primary', so it should be instantiated first. ASSERT_LT(v2->getId(), v1->getId()); ASSERT_LT(v2->getId(), v3->getId()); } // ----- test_functionManufacture ----- /** * Template function to create a std:functions that creates a distinct subclasses of AB. * * @tparam X A number used to distinguish subclasses. * @tparam Dependencies Types of arguments to the ABSubclass factory * @return A function to create a std:functions that creates a distinct subclasses of AB. */ template function>(Dependencies...)> getFunctionFactory() { return [](Dependencies... dependencies) { return make_shared>(); }; } Component>, shared_ptr>, shared_ptr>, shared_ptr>> getFunctionTestComponent() { return ComponentAccumulator<>() .addPrimaryFactory(getFunctionFactory<1>()) .addRequiredFactory(getFunctionFactory<2, const shared_ptr>&>()) .addRetainedFactory(getFunctionFactory<3>()) .addUnloadableFactory(getFunctionFactory<4>()); } /** * Verify that function factories work and are invoked in the correct order. */ TEST_F(ManufactoryTest, test_functionManufacture) { auto component = getFunctionTestComponent(); auto manufactory = Manufactory< shared_ptr>, shared_ptr>, shared_ptr>, shared_ptr>>::create(component); ASSERT_TRUE(manufactory); auto v2 = manufactory->get>>(); ASSERT_TRUE(v2); auto v3 = manufactory->get>>(); ASSERT_TRUE(v3); auto v4 = manufactory->get>>(); ASSERT_TRUE(v4); auto v1 = manufactory->get>>(); ASSERT_TRUE(v1); // ABSubclass<1> is primary, so it should be instantiated first. ASSERT_LT(v1->getId(), v2->getId()); // ABSubclass<2> depends upon ABSubclass<3>, so ABSubclass<3> should in instantiated first. ASSERT_LT(v3->getId(), v2->getId()); } // ----- test_annotatedFfunctionManufacture ----- /** * Template function to create a std:functions that creates an Annotated subclasses of AB. * * @tparam X A number used to distinguish subclasses. * @tparam Dependencies Types of arguments to the ABSubclass factory * @return A function to create a std:functions that creates a distinct subclasses of AB. */ template function(Dependencies...)> getAnnotatedFunctionFactory() { return [](Dependencies... dependencies) { return Annotated(new AB); }; } Component< Annotated, Annotated, Annotated, Annotated, Annotated> getAnnotatedFunctionTestComponent() { return ComponentAccumulator<>() .addPrimaryFactory(getAnnotatedFunctionFactory()) .addRequiredFactory(getAnnotatedFunctionFactory()) .addRetainedFactory(getAnnotatedFunctionFactory&>()) .addUnloadableFactory(getAnnotatedFunctionFactory()); } /** * Verify that function factories work and are invoked in the correct order. */ TEST_F(ManufactoryTest, test_anotatedFunctionManufacture) { auto component = getAnnotatedFunctionTestComponent(); auto manufactory = Manufactory< Annotated, Annotated, Annotated, Annotated, Annotated>::create(component); ASSERT_TRUE(manufactory); auto v2 = manufactory->get>(); ASSERT_TRUE(v2); auto v3 = manufactory->get>(); ASSERT_TRUE(v3); auto v4 = manufactory->get>(); ASSERT_TRUE(v4); auto v1 = manufactory->get>(); ASSERT_TRUE(v1); // Annotated is primary, so it should be instantiated first. ASSERT_LT(v1->getId(), v2->getId()); ASSERT_LT(v1->getId(), v3->getId()); ASSERT_LT(v1->getId(), v4->getId()); // Annotated depends upon Annotated but doesn't keep the reference and // Annotated is unloadable, so v4 gets a newer instance. ASSERT_LT(v3->getId(), v4->getId()); } // ----- test_checkCyclicDependencies ----- /** * Factory for instances of InterfaceA that depends upon InterfaceB. * * @return An instance of InterfaceA. */ shared_ptr createCyclicA(const shared_ptr&) { return createAB(); } /** * Factory for instances of InterfaceB that depends upon InterfaceA. * * @return An instance of InterfaceB. */ shared_ptr createCyclicB(const shared_ptr&) { return createAB(); } /** * Definition of a component with a cyclic dependency graph (InterfaceA <-> InterfaceB). * * @return A component with a cyclic dependency graph. */ Component, shared_ptr> getCyclicComponent() { ComponentAccumulator<> accumulator; return accumulator.addUnloadableFactory(createCyclicA).addUnloadableFactory(createCyclicB); }; /** * Verify that circular dependencies are detected, and the creation of an Manufactory from a component with * a circular dependency will fail. */ TEST_F(ManufactoryTest, test_checkCyclicDependencies) { auto component = getCyclicComponent(); auto manufactory = Manufactory, shared_ptr>::create(component); ASSERT_FALSE(manufactory); } // ----- test_subManufactory ----- /** * Verify that the creation of a subManufactory works and that cached instances are shared between the * primary and subManufactorys. */ TEST_F(ManufactoryTest, test_subManufactory) { auto component = getABComponent(); auto manufactory = Manufactory, shared_ptr>::create(component); ASSERT_TRUE(manufactory); auto a = manufactory->get>(); ASSERT_TRUE(a); auto b = manufactory->get>(); ASSERT_TRUE(b); ASSERT_TRUE(b->getString().empty()); a->setString("something"); ASSERT_FALSE(b->getString().empty()); auto subsetManufactory = manufactory->createSubsetManufactory>(); auto subB = subsetManufactory->get>(); ASSERT_TRUE(subB); ASSERT_EQ(b, subB); } // ------ test_optionalImport ----- class Dependency {}; struct Type1 { static shared_ptr create(shared_ptr dependency = nullptr) { return make_shared(dependency); } Type1(shared_ptr dependency = nullptr) : m_dependency{dependency} { } shared_ptr m_dependency; }; Component, OptionalImport>> getOptional() { return ComponentAccumulator<>().addRetainedFactory(Type1::create).makeImportOptional>(); } /** * Test that we are able to build manufactory from a component that has unsatisfied Optional dependency. Expect * manufactory to just wire nullptr to that dependency. */ TEST_F(ManufactoryTest, test_missingOptionalImportUsesEmptyObject) { auto manufactory = Manufactory>::create(getOptional()); ASSERT_NE(manufactory, nullptr); ASSERT_NE(manufactory->get>(), nullptr); EXPECT_EQ(manufactory->get>()->m_dependency, nullptr); } /** * Test that if the user provides a recipe for an optional dependency, the recipe is used. */ TEST_F(ManufactoryTest, test_withOptionalImportUsesProvidedImplementation) { auto myDependency = make_shared(); Component> component = ComponentAccumulator<>().addInstance(myDependency).addComponent(getOptional()); auto manufactory = Manufactory>::create(component); ASSERT_NE(manufactory, nullptr); ASSERT_NE(manufactory->get>(), nullptr); EXPECT_EQ(manufactory->get>()->m_dependency, myDependency); } } // namespace test } // namespace acsdkManufactory } // namespace alexaClientSDK