354 lines
13 KiB
C++
354 lines
13 KiB
C++
|
/*
|
||
|
* Copyright 2018 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.
|
||
|
*/
|
||
|
|
||
|
#include <iterator>
|
||
|
#include <memory>
|
||
|
#include <sstream>
|
||
|
|
||
|
#include <gmock/gmock.h>
|
||
|
#include <gtest/gtest.h>
|
||
|
|
||
|
#include <AVSCommon/SDKInterfaces/MockSpeakerManager.h>
|
||
|
#include <AVSCommon/SDKInterfaces/MockUserInactivityMonitor.h>
|
||
|
#include <AVSCommon/SDKInterfaces/SpeakerInterface.h>
|
||
|
|
||
|
#include "MRM/MRMCapabilityAgent.h"
|
||
|
|
||
|
namespace alexaClientSDK {
|
||
|
namespace capabilityAgents {
|
||
|
namespace mrm {
|
||
|
namespace test {
|
||
|
|
||
|
using namespace avsCommon::avs;
|
||
|
using namespace avsCommon::sdkInterfaces;
|
||
|
using namespace avsCommon::sdkInterfaces::test;
|
||
|
using namespace ::testing;
|
||
|
|
||
|
/// Test version string which our dummy MRMHandler will return.
|
||
|
static const std::string TEST_MRM_HANDLER_VERSION_STRING = "test_version_string";
|
||
|
|
||
|
/// A timeout for things which we expect to occur (so it's long enough for reasonable latency)
|
||
|
static const auto WAIT_FOR_INVOCATION_LONG_TIMEOUT = std::chrono::milliseconds{100};
|
||
|
|
||
|
/// A timeout for things which we do not expect to occur (so, we will expect this duration to elapse).
|
||
|
static const auto WAIT_FOR_INVOCATION_SHORT_TIMEOUT = std::chrono::milliseconds{5};
|
||
|
|
||
|
/// A sample Directive JSON string for the purposes of creating an AVSDirective object.
|
||
|
static const std::string TEST_DIRECTIVE_JSON_STRING = R"delim(
|
||
|
{
|
||
|
"directive": {
|
||
|
"header": {
|
||
|
"namespace": "MRM",
|
||
|
"name": "TestDirective",
|
||
|
"messageId": "12345"
|
||
|
},
|
||
|
"payload": {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
})delim";
|
||
|
|
||
|
/**
|
||
|
* A utility class to simplify capturing that the invocation of an event has occurred. This class expects that
|
||
|
* two threads are involved - thread A waits for the invocation, while thread B performs the invocation.
|
||
|
*/
|
||
|
class SynchronizedInvocation {
|
||
|
public:
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*/
|
||
|
SynchronizedInvocation() : m_hasBeenInvoked{false} {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record that the invocation has occurred. Calling this function will wake any waiting threads.
|
||
|
*/
|
||
|
void invoke() {
|
||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||
|
m_hasBeenInvoked = true;
|
||
|
m_cv.notify_all();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Wait for an invocation to occur. If it has already occurred, this function will immediately return true.
|
||
|
* If the invocation did occur, then this function resets its tracking data, allowing this object to be used
|
||
|
* repeatedly.
|
||
|
*
|
||
|
* @param timeout How long the caller wishes to wait for the invocation to occur.
|
||
|
* @return Whether the invocation occurred within the timeout, or if it has already occurred before the call.
|
||
|
*/
|
||
|
bool wait(const std::chrono::milliseconds timeout) {
|
||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||
|
if (m_cv.wait_for(lock, timeout, [this]() { return m_hasBeenInvoked; })) {
|
||
|
m_hasBeenInvoked = false;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
/// The mutex controlling access to the boolean.
|
||
|
std::mutex m_mutex;
|
||
|
/// The condition variable which may be waited upon.
|
||
|
std::condition_variable m_cv;
|
||
|
/// A boolean variable to capture an event occurring.
|
||
|
bool m_hasBeenInvoked;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* WaitableExceptionEncounteredSender is a mock of the @c ExceptionEncounteredSenderInterface and allows tests
|
||
|
* to wait for invocations upon those interfaces and inspect the parameters of those invocations.
|
||
|
*/
|
||
|
class WaitableExceptionEncounteredSender : public ExceptionEncounteredSenderInterface {
|
||
|
public:
|
||
|
void sendExceptionEncountered(
|
||
|
const std::string& unparsedDirective,
|
||
|
avsCommon::avs::ExceptionErrorType error,
|
||
|
const std::string& message) override {
|
||
|
m_invocation.invoke();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Allows the caller to wait until an exception has been sent, up to a maximum timeout.
|
||
|
* Also returns true if an exception has been previously sent which was not waited upon.
|
||
|
*
|
||
|
* @return Whether the exception was sent within the specified timeout, or if an exception has been previously sent,
|
||
|
* which was not waited upon.
|
||
|
*/
|
||
|
bool wait(const std::chrono::milliseconds timeout) {
|
||
|
return m_invocation.wait(timeout);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
/// Our invocation object to track when an exception has been sent.
|
||
|
SynchronizedInvocation m_invocation;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Class with which to mock a connection to AVS.
|
||
|
*/
|
||
|
class MockMRMHandler : public MRMHandlerInterface {
|
||
|
public:
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*/
|
||
|
MockMRMHandler() : MRMHandlerInterface{"MockMRMHandler"} {
|
||
|
}
|
||
|
|
||
|
MOCK_CONST_METHOD0(getVersionString, std::string());
|
||
|
MOCK_METHOD1(handleDirective, bool(std::shared_ptr<AVSDirective> directive));
|
||
|
MOCK_METHOD0(doShutdown, void());
|
||
|
|
||
|
/**
|
||
|
* overridden function, minus the explicit override, since gtest does not use override.
|
||
|
*/
|
||
|
void onSpeakerSettingsChanged(const SpeakerInterface::Type& type) {
|
||
|
m_lastSpeakerType = type;
|
||
|
m_speakerSettingUpdatedInvocation.invoke();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* overridden function, minus the explicit override, since gtest does not use override.
|
||
|
*/
|
||
|
void onUserInactivityReportSent() {
|
||
|
m_userInactivityInvocation.invoke();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to wait for a speaker setting of a particular type to change. Will return true if a speaker of the
|
||
|
* desired type did change within the timeout, or if a non-waited-upon change occurred before this call was made.
|
||
|
*
|
||
|
* @param expectedType The type of the speaker we are waiting for.
|
||
|
* @param timeout How long we wish to wait for the change to occur.
|
||
|
* @return Whether the change occurred within the timeout, or if a non-waited-upon change occurred before this call.
|
||
|
*/
|
||
|
bool waitForSpeakerSettingChanged(
|
||
|
const SpeakerInterface::Type& expectedType,
|
||
|
const std::chrono::milliseconds timeout) {
|
||
|
if (m_speakerSettingUpdatedInvocation.wait(timeout)) {
|
||
|
return expectedType == m_lastSpeakerType;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function to wait for a System.UserInactivityReport to be sent. Will return true if an exception was sent
|
||
|
* within the timeout, or if a non-waited-upon report occurred before this call was made.
|
||
|
*
|
||
|
* @param timeout How long we wish to wait for the report to occur.
|
||
|
* @return Whether the report occurred within the timeout, or if a non-waited-upon report occurred before this call.
|
||
|
*/
|
||
|
bool waitForUserInactivityReport(const std::chrono::milliseconds timeout) {
|
||
|
return m_userInactivityInvocation.wait(timeout);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
/// Our invocation object to track the sending of a System.UserInactivityReport.
|
||
|
SynchronizedInvocation m_userInactivityInvocation;
|
||
|
/// Our invocation object to track the changing of a speaker type.
|
||
|
SynchronizedInvocation m_speakerSettingUpdatedInvocation;
|
||
|
/// A tracking variable for the most recently changed speaker type.
|
||
|
SpeakerInterface::Type m_lastSpeakerType;
|
||
|
};
|
||
|
|
||
|
/// Test harness for @c MRMCapabilityAgent class.
|
||
|
class MRMCapabilityAgentTest : public ::testing::Test {
|
||
|
public:
|
||
|
void SetUp() override;
|
||
|
void TearDown() override;
|
||
|
|
||
|
protected:
|
||
|
/// Our MRM CA.
|
||
|
std::shared_ptr<MRMCapabilityAgent> m_mrmCA;
|
||
|
/// A pointer to a dummy MRMHandler. Since the CA takes the Handler by unique_ptr, we will keep the raw pointer.
|
||
|
MockMRMHandler* m_mockMRMHandlerPtr;
|
||
|
/// A mock speaker manager.
|
||
|
std::shared_ptr<MockSpeakerManager> m_mockSpeakerManager;
|
||
|
/// A mock user inactivity monitor.
|
||
|
std::shared_ptr<MockUserInactivityMonitor> m_mockUserInactivityMonitor;
|
||
|
/// A waitable exception sender.
|
||
|
std::shared_ptr<WaitableExceptionEncounteredSender> m_exceptionSender;
|
||
|
};
|
||
|
|
||
|
void MRMCapabilityAgentTest::SetUp() {
|
||
|
// Create our main objects.
|
||
|
auto mrmHandler = std::unique_ptr<MRMHandlerInterface>(new MockMRMHandler());
|
||
|
m_mockMRMHandlerPtr = static_cast<MockMRMHandler*>(mrmHandler.get());
|
||
|
|
||
|
m_mockSpeakerManager = std::make_shared<MockSpeakerManager>();
|
||
|
m_mockUserInactivityMonitor = std::make_shared<MockUserInactivityMonitor>();
|
||
|
m_exceptionSender = std::make_shared<WaitableExceptionEncounteredSender>();
|
||
|
|
||
|
// Instantiate the capability agent.
|
||
|
EXPECT_CALL(*m_mockSpeakerManager, addSpeakerManagerObserver(_)).Times(1);
|
||
|
EXPECT_CALL(*m_mockUserInactivityMonitor, addObserver(_)).Times(1);
|
||
|
|
||
|
m_mrmCA = MRMCapabilityAgent::create(
|
||
|
std::move(mrmHandler), m_mockSpeakerManager, m_mockUserInactivityMonitor, m_exceptionSender);
|
||
|
|
||
|
ASSERT_NE(m_mrmCA, nullptr);
|
||
|
}
|
||
|
|
||
|
void MRMCapabilityAgentTest::TearDown() {
|
||
|
EXPECT_CALL(*m_mockMRMHandlerPtr, doShutdown()).Times(1);
|
||
|
EXPECT_CALL(*m_mockSpeakerManager, removeSpeakerManagerObserver(_)).Times(1);
|
||
|
EXPECT_CALL(*m_mockUserInactivityMonitor, removeObserver(_)).Times(1);
|
||
|
|
||
|
m_mrmCA->shutdown();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c create function of @c MRMCapabilityAgent class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, createTest) {
|
||
|
/// A dummy MRMHandler.
|
||
|
auto mrmHandler = std::unique_ptr<MRMHandlerInterface>(new MockMRMHandler());
|
||
|
|
||
|
/// Test all the bad cases. The good case is already tested in SetUp.
|
||
|
ASSERT_EQ(
|
||
|
nullptr,
|
||
|
MRMCapabilityAgent::create(nullptr, m_mockSpeakerManager, m_mockUserInactivityMonitor, m_exceptionSender));
|
||
|
ASSERT_EQ(
|
||
|
nullptr,
|
||
|
MRMCapabilityAgent::create(std::move(mrmHandler), nullptr, m_mockUserInactivityMonitor, m_exceptionSender));
|
||
|
ASSERT_EQ(
|
||
|
nullptr, MRMCapabilityAgent::create(std::move(mrmHandler), m_mockSpeakerManager, nullptr, m_exceptionSender));
|
||
|
ASSERT_EQ(
|
||
|
nullptr,
|
||
|
MRMCapabilityAgent::create(std::move(mrmHandler), m_mockSpeakerManager, m_mockUserInactivityMonitor, nullptr));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c getConfiguration function of @c MRMCapabilityAgent class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, getConfigurationTest) {
|
||
|
auto config = m_mrmCA->getConfiguration();
|
||
|
ASSERT_NE(true, config.empty());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c getVersionString function of @c MRMCapabilityAgent class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, getVersionStringTest) {
|
||
|
EXPECT_CALL(*m_mockMRMHandlerPtr, getVersionString()).WillOnce(Return(TEST_MRM_HANDLER_VERSION_STRING));
|
||
|
std::string versionString = m_mrmCA->getVersionString();
|
||
|
ASSERT_NE(true, versionString.empty());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c handleDirective function of @c MRMHandler class, invoked by the @c MRMCapabilityAgent class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, handleMRMDirectiveTest) {
|
||
|
// Create a dummy AVSDirective.
|
||
|
auto directivePair = AVSDirective::create(TEST_DIRECTIVE_JSON_STRING, nullptr, "");
|
||
|
std::shared_ptr<AVSDirective> directive = std::move(directivePair.first);
|
||
|
|
||
|
// Test that the MRMHandler will receive the Directive and fail to handle it.
|
||
|
EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_)).WillOnce(Return(false));
|
||
|
m_mrmCA->handleDirectiveImmediately(directive);
|
||
|
ASSERT_TRUE(m_exceptionSender->wait(WAIT_FOR_INVOCATION_LONG_TIMEOUT));
|
||
|
|
||
|
// Test that the MRMHandler will receive the Directive and successfully handle it.
|
||
|
EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_)).WillOnce(Return(true));
|
||
|
m_mrmCA->handleDirectiveImmediately(directive);
|
||
|
ASSERT_FALSE(m_exceptionSender->wait(WAIT_FOR_INVOCATION_SHORT_TIMEOUT));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c onSpeakerSettingsChanged function of @c MRMHandler class, invoked by the @c MRMCapabilityAgent
|
||
|
* class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, onSpeakerSettingsChangedTest) {
|
||
|
SpeakerInterface::SpeakerSettings dummySpeakerSettings;
|
||
|
|
||
|
// Test that the LOCAL option works.
|
||
|
m_mrmCA->onSpeakerSettingsChanged(
|
||
|
SpeakerManagerObserverInterface::Source::DIRECTIVE, SpeakerInterface::Type::LOCAL, dummySpeakerSettings);
|
||
|
ASSERT_TRUE(m_mockMRMHandlerPtr->waitForSpeakerSettingChanged(
|
||
|
SpeakerInterface::Type::LOCAL, WAIT_FOR_INVOCATION_LONG_TIMEOUT));
|
||
|
|
||
|
// Test that the AVS_SYNCED option works.
|
||
|
m_mrmCA->onSpeakerSettingsChanged(
|
||
|
SpeakerManagerObserverInterface::Source::DIRECTIVE, SpeakerInterface::Type::AVS_SYNCED, dummySpeakerSettings);
|
||
|
ASSERT_TRUE(m_mockMRMHandlerPtr->waitForSpeakerSettingChanged(
|
||
|
SpeakerInterface::Type::AVS_SYNCED, WAIT_FOR_INVOCATION_LONG_TIMEOUT));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Test to verify the @c waitForUserInactivityReport function of @c MRMHandler class, invoked by the
|
||
|
* @c MRMCapabilityAgent class.
|
||
|
*/
|
||
|
TEST_F(MRMCapabilityAgentTest, onUserInactivityReportTest) {
|
||
|
// Verify that our Inactivity Report is sent by MRMHandler when invoked by the MRM CA.
|
||
|
m_mrmCA->onUserInactivityReportSent();
|
||
|
ASSERT_TRUE(m_mockMRMHandlerPtr->waitForUserInactivityReport(WAIT_FOR_INVOCATION_LONG_TIMEOUT));
|
||
|
|
||
|
// Verify that the Inactivity Report is only sent once.
|
||
|
ASSERT_FALSE(m_mockMRMHandlerPtr->waitForUserInactivityReport(WAIT_FOR_INVOCATION_SHORT_TIMEOUT));
|
||
|
}
|
||
|
|
||
|
} // namespace test
|
||
|
} // namespace mrm
|
||
|
} // namespace capabilityAgents
|
||
|
} // namespace alexaClientSDK
|
||
|
|
||
|
int main(int argc, char** argv) {
|
||
|
::testing::InitGoogleTest(&argc, argv);
|
||
|
if (argc < 1) {
|
||
|
std::cerr << "USAGE: " << std::string(argv[0]) << std::endl;
|
||
|
return 1;
|
||
|
}
|
||
|
return RUN_ALL_TESTS();
|
||
|
}
|