avs-device-sdk/shared/acsdkManufactory/test/ManufactoryTest.cpp

697 lines
22 KiB
C++

/*
* 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 <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "acsdkManufactory/Component.h"
#include "acsdkManufactory/ComponentAccumulator.h"
#include "acsdkManufactory/Manufactory.h"
namespace alexaClientSDK {
namespace acsdkManufactory {
namespace test {
using namespace ::testing;
using namespace acsdkManufactory;
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 std::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 std::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 std::string& input) override {
m_state = input;
};
std::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.
std::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 <int X>
class ABSubclass : public AB {};
// ----- test_manufactureUnique ----- //
/**
* Factory for creating a new instance of InterfaceA and returning it via std::unique_ptr<InterfaceA>.
*
* @return A new instance of InterfaceA.
*/
std::unique_ptr<InterfaceA> createUniqueA() {
return std::unique_ptr<InterfaceA>(new AB());
}
/**
* Test manufacture of an instances returned via std::unique_ptr. Verify that multiple instances of the same
* interface are indeed distinct instances.
*/
TEST_F(ManufactoryTest, test_manufactureUnique) {
Component<std::unique_ptr<InterfaceA>> component(ComponentAccumulator<>().addUniqueFactory(createUniqueA));
auto manufactory = Manufactory<std::unique_ptr<InterfaceA>>::create(component);
ASSERT_TRUE(manufactory);
auto a1 = manufactory->get<std::unique_ptr<InterfaceA>>();
ASSERT_TRUE(a1);
auto a2 = manufactory->get<std::unique_ptr<InterfaceA>>();
ASSERT_TRUE(a2);
ASSERT_NE(a1, a2);
}
// ----- test_manufactureShared ----- //
/**
* Factory for creating a new instance of InterfaceAB and returning it via std::shared_ptr<InterfaceAB>.
*
* @return A new instance of InterfaceAB.
*/
std::shared_ptr<InterfaceAB> createSharedAB() {
return std::make_shared<AB>();
}
/**
* Test manufacture of an 'unloadable' instance returned via std::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<std::shared_ptr<InterfaceAB>> component(ComponentAccumulator<>().addUnloadableFactory(createSharedAB));
auto manufactory = Manufactory<std::shared_ptr<InterfaceAB>>::create(component);
ASSERT_TRUE(manufactory);
// Verify created instances are shared...
auto ab1 = manufactory->get<std::shared_ptr<InterfaceAB>>();
ASSERT_TRUE(ab1);
ASSERT_TRUE(ab1->getString().empty());
auto ab2 = manufactory->get<std::shared_ptr<InterfaceAB>>();
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<std::shared_ptr<InterfaceAB>>();
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.
*/
std::shared_ptr<InterfaceA> createAFromAB(const std::shared_ptr<InterfaceAB>& 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.
*/
std::shared_ptr<InterfaceB> createBFromAB(const std::shared_ptr<InterfaceAB>& ab) {
return ab;
}
/**
* Factory used to create an instance of InterfaceAB.
*
* @return An instance of InterfaceAB.
*/
std::shared_ptr<InterfaceAB> createAB() {
return std::make_shared<AB>();
}
/**
* Definition of a component that exports implementations of InterfaceA and InterfaceB.
* @return
*/
Component<std::shared_ptr<InterfaceA>, std::shared_ptr<InterfaceB>> 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<std::shared_ptr<InterfaceA>, std::shared_ptr<InterfaceB>>::create(component);
ASSERT_TRUE(manufactory);
auto a = manufactory->get<std::shared_ptr<InterfaceA>>();
ASSERT_TRUE(a);
auto b = manufactory->get<std::shared_ptr<InterfaceB>>();
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<Annotation1, InterfaceAB> createAB1(const Annotated<Annotation3, InterfaceAB>& 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<Annotation2, InterfaceAB> 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<Annotation3, InterfaceAB> createAB3(const Annotated<Annotation2, InterfaceAB>& 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<Annotation1, InterfaceAB>,
Annotated<Annotation2, InterfaceAB>,
Import<Annotated<Annotation3, InterfaceAB>>>
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<Annotated<Annotation3, InterfaceAB>> 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<Annotated<Annotation3, InterfaceAB>>::create(component);
ASSERT_TRUE(manufactory);
auto ab3 = manufactory->get<Annotated<Annotation3, InterfaceAB>>();
ASSERT_TRUE(ab3);
ASSERT_EQ(ab3->getString(), "3");
auto anotherAb3 = manufactory->get<Annotated<Annotation3, InterfaceAB>>();
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<std::shared_ptr<InterfaceAB>> 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<std::shared_ptr<InterfaceAB>>::create(component);
ASSERT_TRUE(manufactory);
auto ab1 = manufactory->get<std::shared_ptr<InterfaceAB>>();
ASSERT_TRUE(ab1);
id1 = ab1->getId();
auto ab2 = manufactory->get<std::shared_ptr<InterfaceAB>>();
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<std::shared_ptr<InterfaceAB>>::create(component);
ASSERT_TRUE(manufactory);
auto ab3 = manufactory->get<std::shared_ptr<InterfaceAB>>();
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<Annotation1, InterfaceAB> 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<Annotation2, InterfaceAB> createRequiredAB2(const Annotated<Annotation1, InterfaceAB>& 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<Annotated<Annotation1, InterfaceAB>> 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<Annotated<Annotation1, InterfaceAB>>::create(component);
ASSERT_TRUE(manufactory);
auto ab1 = manufactory->get<Annotated<Annotation1, InterfaceAB>>();
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<X>.
*/
template <int X>
static std::shared_ptr<ABSubclass<X>> createABSubclass() {
return std::make_shared<ABSubclass<X>>();
}
/**
* 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<std::shared_ptr<ABSubclass<1>>, std::shared_ptr<ABSubclass<2>>, std::shared_ptr<ABSubclass<3>>>
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<std::shared_ptr<ABSubclass<1>>, std::shared_ptr<ABSubclass<2>>, std::shared_ptr<ABSubclass<3>>>::
create(component);
ASSERT_TRUE(manufactory);
auto v1 = manufactory->get<std::shared_ptr<ABSubclass<1>>>();
ASSERT_TRUE(v1);
auto v3 = manufactory->get<std::shared_ptr<ABSubclass<3>>>();
ASSERT_TRUE(v3);
auto v2 = manufactory->get<std::shared_ptr<ABSubclass<2>>>();
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<X> factory
* @return A function to create a std:functions that creates a distinct subclasses of AB.
*/
template <int X, typename... Dependencies>
std::function<std::shared_ptr<ABSubclass<X>>(Dependencies...)> getFunctionFactory() {
return [](Dependencies... dependencies) { return std::make_shared<ABSubclass<X>>(); };
}
Component<
std::shared_ptr<ABSubclass<1>>,
std::shared_ptr<ABSubclass<2>>,
std::shared_ptr<ABSubclass<3>>,
std::shared_ptr<ABSubclass<4>>>
getFunctionTestComponent() {
return ComponentAccumulator<>()
.addPrimaryFactory(getFunctionFactory<1>())
.addRequiredFactory(getFunctionFactory<2, const std::shared_ptr<ABSubclass<3>>&>())
.addRetainedFactory(getFunctionFactory<3>())
.addUnloadableFactory(getFunctionFactory<4>());
}
/**
* Verify that std::function factories work and are invoked in the correct order.
*/
TEST_F(ManufactoryTest, test_functionManufacture) {
auto component = getFunctionTestComponent();
auto manufactory = Manufactory<
std::shared_ptr<ABSubclass<1>>,
std::shared_ptr<ABSubclass<2>>,
std::shared_ptr<ABSubclass<3>>,
std::shared_ptr<ABSubclass<4>>>::create(component);
ASSERT_TRUE(manufactory);
auto v2 = manufactory->get<std::shared_ptr<ABSubclass<2>>>();
ASSERT_TRUE(v2);
auto v3 = manufactory->get<std::shared_ptr<ABSubclass<3>>>();
ASSERT_TRUE(v3);
auto v4 = manufactory->get<std::shared_ptr<ABSubclass<4>>>();
ASSERT_TRUE(v4);
auto v1 = manufactory->get<std::shared_ptr<ABSubclass<1>>>();
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<X> factory
* @return A function to create a std:functions that creates a distinct subclasses of AB.
*/
template <typename Annotation, typename... Dependencies>
std::function<Annotated<Annotation, AB>(Dependencies...)> getAnnotatedFunctionFactory() {
return [](Dependencies... dependencies) { return Annotated<Annotation, AB>(new AB); };
}
Component<
Annotated<Annotation1, AB>,
Annotated<Annotation2, AB>,
Annotated<Annotation3, AB>,
Annotated<Annotation4, AB>,
Annotated<Annotation2, AB>>
getAnnotatedFunctionTestComponent() {
return ComponentAccumulator<>()
.addPrimaryFactory(getAnnotatedFunctionFactory<Annotation1>())
.addRequiredFactory(getAnnotatedFunctionFactory<Annotation2>())
.addRetainedFactory(getAnnotatedFunctionFactory<Annotation3, const Annotated<Annotation4, AB>&>())
.addUnloadableFactory(getAnnotatedFunctionFactory<Annotation4>());
}
/**
* Verify that std::function factories work and are invoked in the correct order.
*/
TEST_F(ManufactoryTest, test_anotatedFunctionManufacture) {
auto component = getAnnotatedFunctionTestComponent();
auto manufactory = Manufactory<
Annotated<Annotation1, AB>,
Annotated<Annotation2, AB>,
Annotated<Annotation3, AB>,
Annotated<Annotation4, AB>,
Annotated<Annotation2, AB>>::create(component);
ASSERT_TRUE(manufactory);
auto v2 = manufactory->get<Annotated<Annotation2, AB>>();
ASSERT_TRUE(v2);
auto v3 = manufactory->get<Annotated<Annotation3, AB>>();
ASSERT_TRUE(v3);
auto v4 = manufactory->get<Annotated<Annotation4, AB>>();
ASSERT_TRUE(v4);
auto v1 = manufactory->get<Annotated<Annotation1, AB>>();
ASSERT_TRUE(v1);
// Annotated<Annotation1, AB> 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<Annotation3, AB> depends upon Annotated<Annotation4, AB> but doesn't keep the reference and
// Annotated<Annotation4, AB> 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.
*/
std::shared_ptr<InterfaceA> createCyclicA(const std::shared_ptr<InterfaceB>&) {
return createAB();
}
/**
* Factory for instances of InterfaceB that depends upon InterfaceA.
*
* @return An instance of InterfaceB.
*/
std::shared_ptr<InterfaceB> createCyclicB(const std::shared_ptr<InterfaceA>&) {
return createAB();
}
/**
* Definition of a component with a cyclic dependency graph (InterfaceA <-> InterfaceB).
*
* @return A component with a cyclic dependency graph.
*/
Component<std::shared_ptr<InterfaceA>, std::shared_ptr<InterfaceB>> 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<std::shared_ptr<InterfaceA>, std::shared_ptr<InterfaceB>>::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<std::shared_ptr<InterfaceA>, std::shared_ptr<InterfaceB>>::create(component);
ASSERT_TRUE(manufactory);
auto a = manufactory->get<std::shared_ptr<InterfaceA>>();
ASSERT_TRUE(a);
auto b = manufactory->get<std::shared_ptr<InterfaceB>>();
ASSERT_TRUE(b);
ASSERT_TRUE(b->getString().empty());
a->setString("something");
ASSERT_FALSE(b->getString().empty());
auto subsetManufactory = manufactory->createSubsetManufactory<std::shared_ptr<InterfaceB>>();
auto subB = subsetManufactory->get<std::shared_ptr<InterfaceB>>();
ASSERT_TRUE(subB);
ASSERT_EQ(b, subB);
}
} // namespace test
} // namespace acsdkManufactory
} // namespace alexaClientSDK