Version 0.2.1 of alexa-client-sdk
This update: - Tightens up the use of libcural by ACL and AuthDelegate. - Improves the interface of ADSL.
This commit is contained in:
parent
d849f83194
commit
6217dae47a
ACL
ADSL
include/ADSL
BlockingPolicy.hDirectiveHandlerConfiguration.hDirectiveHandlerInterface.hDirectiveProcessor.hDirectiveRouter.hDirectiveSequencer.hDirectiveSequencerInterface.hHandlerAndPolicy.hMessageInterpreter.hNamespaceAndName.h
src
CMakeLists.txtDirectiveProcessor.cppDirectiveRouter.cppDirectiveSequencer.cppHandlerAndPolicy.cppMessageInterpreter.cppNamespaceAndName.cpp
test
AFML
AVSCommon
include/AVSCommon
AVSDirective.hAVSMessage.hAVSMessageHeader.hAttachmentManager.hAttachmentManagerInterface.hExceptionEncounteredSenderInterface.h
functional
src
test
AVSUtils
include/AVSUtils
Configuration
Initialization
LibcurlUtils
Logger
Logging
src
test
AuthDelegate
examples/ExampleAuthDelegateClient/src
include/AuthDelegate
src
test
Integration
include/Integration
inputs
src
test
|
@ -21,8 +21,7 @@
|
|||
#include <istream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include <AVSCommon/AttachmentManager.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
|
@ -49,7 +48,7 @@ public:
|
|||
* @param json the JSON content of message.
|
||||
* @param attachmentManager attachment manager.
|
||||
*/
|
||||
Message(const std::string& json, std::shared_ptr<AttachmentManagerInterface> attachmentManager);
|
||||
Message(const std::string& json, std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/**
|
||||
* Retrieve the JSON content.
|
||||
|
@ -75,7 +74,7 @@ public:
|
|||
*
|
||||
* @return instance that implements the attachment manager interface.
|
||||
*/
|
||||
std::shared_ptr<AttachmentManagerInterface> getAttachmentManager() const;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> getAttachmentManager() const;
|
||||
|
||||
private:
|
||||
/// The JSON content.
|
||||
|
@ -85,7 +84,7 @@ private:
|
|||
std::shared_ptr<std::istream> m_binaryContent;
|
||||
|
||||
/// The attachment manager that creates attachment reader and attachment writer.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> m_attachmentManager;
|
||||
};
|
||||
|
||||
} // namespace acl
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
*
|
||||
* @param exceptionMessage The exception message.
|
||||
*/
|
||||
virtual void onExceptionReceived(std::shared_ptr<acl::Message> exceptionMessage);
|
||||
virtual void onExceptionReceived(std::shared_ptr<Message> exceptionMessage);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -20,10 +20,11 @@
|
|||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
|
||||
#include "ACL/Transport/MessageRouter.h"
|
||||
#include "ACL/Transport/MessageConsumerInterface.h"
|
||||
#include "ACL/AttachmentManagerInterface.h"
|
||||
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
|
@ -56,7 +57,7 @@ private:
|
|||
TransportObserverInterface* transportObserverInterface) override;
|
||||
|
||||
/// The attachment manager.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> m_attachmentManager;
|
||||
};
|
||||
|
||||
} // acl
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include "ACL/Transport/CurlEasyHandleWrapper.h"
|
||||
#include "ACL/Transport/MimeParser.h"
|
||||
#include "ACL/Transport/MessageConsumerInterface.h"
|
||||
|
@ -56,7 +56,7 @@ public:
|
|||
* @param attachmentManager The attachment manager.
|
||||
*/
|
||||
HTTP2Stream(MessageConsumerInterface *messageConsumer,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager);
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/**
|
||||
* Initializes streams that are supposed to POST the given request.
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include "ACL/Transport/HTTP2Stream.h"
|
||||
#include "ACL/Transport/MessageConsumerInterface.h"
|
||||
|
||||
|
@ -38,7 +38,7 @@ public:
|
|||
* @params maxStreams The maximum number of streams that can be active
|
||||
* @params attachmentManager The attachment manager.
|
||||
*/
|
||||
HTTP2StreamPool(const int maxStreams, std::shared_ptr<AttachmentManagerInterface> attachmentManager);
|
||||
HTTP2StreamPool(const int maxStreams, std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/**
|
||||
* Grabs an HTTP2Stream from the pool and configures it to be an HTTP GET.
|
||||
|
@ -86,7 +86,7 @@ private:
|
|||
/// The maximum number of streams that can be active in the pool.
|
||||
const int m_maxStreams;
|
||||
/// The attachment manager.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> m_attachmentManager;
|
||||
};
|
||||
|
||||
} // acl
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
|
||||
#include "ACL/AuthDelegateInterface.h"
|
||||
#include "ACL/Transport/TransportInterface.h"
|
||||
|
@ -34,7 +35,7 @@
|
|||
#include "ACL/Transport/HTTP2Stream.h"
|
||||
#include "ACL/Transport/HTTP2StreamPool.h"
|
||||
#include "ACL/Transport/MessageConsumerInterface.h"
|
||||
#include "ACL/AttachmentManagerInterface.h"
|
||||
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
|
@ -55,7 +56,7 @@ public:
|
|||
*/
|
||||
HTTP2Transport(std::shared_ptr<AuthDelegateInterface> authDelegate, const std::string& avsEndpoint,
|
||||
MessageConsumerInterface* messageConsumerInterface,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager,
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager,
|
||||
TransportObserverInterface* observer = nullptr);
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
#include <MultipartParser/MultipartReader.h>
|
||||
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include "ACL/Transport/MessageConsumerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
|
@ -39,7 +39,7 @@ public:
|
|||
* @param attachmentManager The attachment manager that manages the attachment.
|
||||
*/
|
||||
MimeParser(MessageConsumerInterface *messageConsumer,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager);
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/**
|
||||
* Resets class for use in another transfer.
|
||||
|
@ -113,7 +113,7 @@ private:
|
|||
/// The object to report back to when JSON MIME parts are received.
|
||||
MessageConsumerInterface *m_messageConsumer;
|
||||
/// The attachment manager.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> m_attachmentManager;
|
||||
};
|
||||
|
||||
} // acl
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* MimeParserObserverInterface.h
|
||||
*
|
||||
* Copyright 2016-2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIME_PARSER_OBSERVER_INTERFACE_H_
|
||||
#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIME_PARSER_OBSERVER_INTERFACE_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ACL/Message.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
|
||||
/**
|
||||
* Interface to the MimeParser to receive MIME parts when they arrive.
|
||||
*/
|
||||
class MimeParserObserverInterface {
|
||||
public:
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~MimeParserObserverInterface() = default;
|
||||
|
||||
/**
|
||||
* Called when Mime parser has a part available
|
||||
*
|
||||
* @param message The message from the backend
|
||||
*/
|
||||
virtual void onMessage(std::shared_ptr<Message> message) = 0;
|
||||
};
|
||||
|
||||
} // acl
|
||||
} // alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_TRANSPORT_MIME_PARSER_OBSERVER_INTERFACE_H_
|
|
@ -3,9 +3,9 @@ find_package(Threads ${THREADS_PACKAGE_CONFIG})
|
|||
|
||||
file(GLOB_RECURSE ACL_SRC "${ACL_SOURCE_DIR}/src/*.cpp")
|
||||
add_library(ACL SHARED ${ACL_SRC})
|
||||
target_include_directories(ACL PUBLIC "${CMAKE_SOURCE_DIR}/ThirdParty/MultipartParser")
|
||||
target_include_directories(ACL PUBLIC "${MultipartParser_SOURCE_DIR}")
|
||||
target_include_directories(ACL PUBLIC ${CURL_INCLUDE_DIRS})
|
||||
target_include_directories(ACL PUBLIC "${ACL_SOURCE_DIR}/include")
|
||||
target_include_directories(ACL PUBLIC "${AuthDelegate_SOURCE_DIR}/include")
|
||||
target_include_directories(ACL PUBLIC "${AVSUtils_SOURCE_DIR}/include")
|
||||
target_link_libraries(ACL ${CURL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} AVSUtils)
|
||||
target_include_directories(ACL PUBLIC "${AVSCommon_SOURCE_DIR}/include")
|
||||
target_link_libraries(ACL ${CURL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} AVSUtils AVSCommon)
|
||||
|
|
|
@ -24,7 +24,7 @@ Message::Message(const std::string& json, std::shared_ptr<std::istream> binaryCo
|
|||
: m_jsonContent{json}, m_binaryContent{binaryContent} {
|
||||
}
|
||||
|
||||
Message::Message(const std::string& json, std::shared_ptr<AttachmentManagerInterface> attachmentManager)
|
||||
Message::Message(const std::string& json, std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager)
|
||||
: m_jsonContent{json}, m_attachmentManager{attachmentManager} {
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ std::shared_ptr<std::istream> Message::getAttachment() {
|
|||
return m_binaryContent;
|
||||
}
|
||||
|
||||
std::shared_ptr<AttachmentManagerInterface> Message::getAttachmentManager() const {
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> Message::getAttachmentManager() const {
|
||||
return m_attachmentManager;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,10 +14,13 @@
|
|||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
#include "AVSUtils/Logging/Logger.h"
|
||||
#include "ACL/Transport/CurlEasyHandleWrapper.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <AVSUtils/LibcurlUtils/LibcurlUtils.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "ACL/Transport/CurlEasyHandleWrapper.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
|
@ -236,16 +239,7 @@ void CurlEasyHandleWrapper::cleanupResources() {
|
|||
}
|
||||
|
||||
bool CurlEasyHandleWrapper::setDefaultOptions() {
|
||||
if (curl_easy_setopt(m_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) != CURLE_OK) {
|
||||
Logger::log("Cannot set SSL version to TLS v1.2");
|
||||
return false;
|
||||
}
|
||||
if (curl_easy_setopt(m_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0) != CURLE_OK) {
|
||||
Logger::log("Cannot set HTTP version to HTTP/2");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return libcurlUtils::prepareForTLS(m_handle);
|
||||
}
|
||||
|
||||
} // acl
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AVSUtils/Logging/Logger.h"
|
||||
#include "AVSUtils/Threading/Executor.h"
|
||||
#include <AVSCommon/AttachmentManager.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
#include <AVSUtils/Threading/Executor.h>
|
||||
|
||||
#include "ACL/Transport/HTTP2MessageRouter.h"
|
||||
#include "ACL/Transport/HTTP2Transport.h"
|
||||
|
||||
|
@ -31,7 +33,7 @@ HTTP2MessageRouter::HTTP2MessageRouter(std::shared_ptr<AuthDelegateInterface> au
|
|||
avsEndpoint,
|
||||
std::make_shared<threading::Executor>(),
|
||||
std::make_shared<threading::Executor>()) {
|
||||
m_attachmentManager = std::make_shared<AttachmentManager>();
|
||||
m_attachmentManager = std::make_shared<avsCommon::AttachmentManager>();
|
||||
}
|
||||
|
||||
HTTP2MessageRouter::~HTTP2MessageRouter() {
|
||||
|
|
|
@ -40,7 +40,7 @@ static const std::string ATTACHMENT_FIELD_NAME = "audio";
|
|||
static const std::string METADATA_FIELD_NAME = "metadata";
|
||||
|
||||
HTTP2Stream::HTTP2Stream(MessageConsumerInterface* messageConsumer,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager)
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager)
|
||||
: m_parser{messageConsumer, attachmentManager} {
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@ namespace acl {
|
|||
|
||||
using namespace avsUtils;
|
||||
|
||||
HTTP2StreamPool::HTTP2StreamPool(const int maxStreams, std::shared_ptr<AttachmentManagerInterface> attachmentManager)
|
||||
HTTP2StreamPool::HTTP2StreamPool(
|
||||
const int maxStreams,
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager)
|
||||
: m_numRemovedStreams{0},
|
||||
m_maxStreams{maxStreams},
|
||||
m_attachmentManager{attachmentManager} {
|
||||
|
|
|
@ -107,7 +107,7 @@ static void printCurlDiagnostics() {
|
|||
|
||||
HTTP2Transport::HTTP2Transport(std::shared_ptr<AuthDelegateInterface> authDelegate, const std::string& avsEndpoint,
|
||||
MessageConsumerInterface* messageConsumerInterface,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager,
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager,
|
||||
TransportObserverInterface* observer)
|
||||
: m_observer{observer},
|
||||
m_messageConsumer{messageConsumerInterface},
|
||||
|
|
|
@ -32,8 +32,31 @@ static const std::string MIME_JSON_CONTENT_TYPE = "application/json";
|
|||
/// MIME type for binary streams
|
||||
static const std::string MIME_OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
|
||||
|
||||
/**
|
||||
* Sanitize the Content-ID field in MIME header.
|
||||
*
|
||||
* This function is necessary per RFC-2392: A "cid" URL is converted to the corresponding Content-ID message header
|
||||
* MIME by removing the "cid:" prefix, and enclosing the remaining parts with an angle bracket pair, "<" and ">".
|
||||
* For example, "cid:foo4%25foo1@bar.net" corresponds to Content-ID: <foo4%25foo1@bar.net>
|
||||
*
|
||||
* @param mimeContentId The raw content ID value in MIME header.
|
||||
* @return The sanitized content ID.
|
||||
*/
|
||||
std::string sanitizeContentId(const std::string& mimeContentId) {
|
||||
std::string sanitizedContentId;
|
||||
if (mimeContentId.empty()) {
|
||||
avsUtils::Logger::log("The mimeContentId is empty, can't be sanitized");
|
||||
} else if ( ('<' == mimeContentId.front()) && ('>' == mimeContentId.back()) ) {
|
||||
// Getting attachment ID within angle bracket <>.
|
||||
sanitizedContentId = mimeContentId.substr(1, mimeContentId.size() - 2);
|
||||
} else {
|
||||
sanitizedContentId = mimeContentId;
|
||||
}
|
||||
return sanitizedContentId;
|
||||
}
|
||||
|
||||
MimeParser::MimeParser(MessageConsumerInterface *messageConsumer,
|
||||
std::shared_ptr<AttachmentManagerInterface> attachmentManager)
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager)
|
||||
: m_receivedFirstChunk{false},
|
||||
m_currDataType{ContentType::NONE},
|
||||
m_messageConsumer{messageConsumer},
|
||||
|
@ -51,7 +74,7 @@ void MimeParser::partBeginCallback(const MultipartHeaders &headers, void *userDa
|
|||
parser->m_currDataType = MimeParser::ContentType::JSON;
|
||||
} else if (contentType.find(MIME_OCTET_STREAM_CONTENT_TYPE) != std::string::npos) {
|
||||
if (headers.count(MIME_CONTENT_ID_FIELD_NAME) == 1) {
|
||||
parser->m_message.append(headers[MIME_CONTENT_ID_FIELD_NAME]);
|
||||
parser->m_message = sanitizeContentId(headers[MIME_CONTENT_ID_FIELD_NAME]);
|
||||
}
|
||||
parser->m_currDataType = MimeParser::ContentType::ATTACHMENT;
|
||||
parser->m_attachment = std::make_shared<std::stringstream>();
|
||||
|
@ -74,21 +97,25 @@ void MimeParser::partDataCallback(const char *buffer, size_t size, void *userDat
|
|||
|
||||
void MimeParser::partEndCallback(void *userData) {
|
||||
MimeParser *parser = static_cast<MimeParser*>(userData);
|
||||
std::shared_ptr<Message> message;
|
||||
switch (parser->m_currDataType) {
|
||||
case MimeParser::ContentType::JSON:
|
||||
message = std::make_shared<Message>(parser->m_message, parser->m_attachmentManager);
|
||||
{
|
||||
auto message = std::make_shared<Message>(parser->m_message,
|
||||
parser->m_attachmentManager);
|
||||
if(!parser->m_messageConsumer) {
|
||||
Logger::log("Message Consumer has not been set. Message from ACL cannot be processed.");
|
||||
break;
|
||||
}
|
||||
parser->m_messageConsumer->consumeMessage(message);
|
||||
break;
|
||||
}
|
||||
case MimeParser::ContentType::ATTACHMENT:
|
||||
{
|
||||
parser->m_attachmentManager->createAttachment(parser->m_message, parser->m_attachment);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Logger::log("Ended part for usupported part type");
|
||||
Logger::log("Ended part for unsupported part type");
|
||||
}
|
||||
|
||||
parser->m_message = "";
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_BLOCKING_POLICY_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_BLOCKING_POLICY_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
|
|
|
@ -18,25 +18,21 @@
|
|||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_HANDLER_CONFIGURATION_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_HANDLER_CONFIGURATION_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <AVSCommon/AVSMessage.h>
|
||||
#include "ADSL/BlockingPolicy.h"
|
||||
#include "ADSL/DirectiveHandlerInterface.h"
|
||||
#include "ADSL/HandlerAndPolicy.h"
|
||||
#include "ADSL/NamespaceAndName.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/// @c AVSMessage namespace and name pair for associating types of directives with a handler and blocking policy.
|
||||
using NamespaceAndNamePair = std::pair<std::string, std::string>;
|
||||
|
||||
/// Pair combining A DirectiveHandler and a BlockingPolicy in that order.
|
||||
using DirectiveHandlerAndBlockingPolicyPair = std::pair<std::shared_ptr<DirectiveHandlerInterface>, BlockingPolicy>;
|
||||
|
||||
/// Mapping from (namespace,name) pairs to (handler,policy) pairs.
|
||||
using DirectiveHandlerConfiguration = std::map<NamespaceAndNamePair, DirectiveHandlerAndBlockingPolicyPair>;
|
||||
/**
|
||||
* Map of @c NamespaceAndName values to @c HandlerAndPolicy values with which to register @c DirectiveHandlers with a
|
||||
* @c DirectiveSequencer.
|
||||
*/
|
||||
using DirectiveHandlerConfiguration = std::unordered_map<NamespaceAndName, HandlerAndPolicy>;
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
|
|
@ -21,24 +21,27 @@
|
|||
#include <memory>
|
||||
|
||||
#include <AVSCommon/AVSDirective.h>
|
||||
|
||||
#include "ADSL/DirectiveHandlerResultInterface.h"
|
||||
#include "ADSL/NamespaceAndName.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Interface for handling @c AVSDirectives. Implementations of this interface should expect either a single
|
||||
* handleDirectiveImmediately() all or a call to preHandleDirective() followed by a call to handleDirective()
|
||||
* unless cancelDirective() is called first. cancelDirective() may also be called after handleDirective().
|
||||
* Interface for handling @c AVSDirectives. For each @c AVSDirective received, implementations of this interface
|
||||
* should expect either a single call to @c handleDirectiveImmediately() or a call to @c preHandleDirective()
|
||||
* followed by a call to @c handleDirective() unless @c cancelDirective() is called first. @c cancelDirective()
|
||||
* may also be called after handleDirective().
|
||||
*
|
||||
* NOTE: The implementation of the methods of this interface MUST be thread-safe.
|
||||
* NOTE: The implementation of the methods of this interface MUST return quickly. Failure to do so blocks
|
||||
* @note The implementation of the methods of this interface MUST be thread-safe.
|
||||
* @note The implementation of the methods of this interface MUST return quickly. Failure to do so blocks
|
||||
* the processing of subsequent @c AVSDirectives.
|
||||
*/
|
||||
class DirectiveHandlerInterface {
|
||||
public:
|
||||
/**
|
||||
* Virtual destructor to ensure proper cleanup by derived types.
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~DirectiveHandlerInterface() = default;
|
||||
|
||||
|
@ -46,9 +49,10 @@ public:
|
|||
* Handle the action specified @c AVSDirective. Once this has been called the @c DirectiveHandler should not
|
||||
* expect to receive further calls regarding this directive.
|
||||
*
|
||||
* NOTE: The implementation of this method MUST be thread-safe.
|
||||
* NOTE: The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* @note The implementation of this method MUST be thread-safe.
|
||||
* @note The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* of subsequent @c AVSDirectives.
|
||||
* @note If this operation fails, an @c ExceptionEncountered message should be sent to AVS.
|
||||
*
|
||||
* @param directive The directive to handle.
|
||||
*/
|
||||
|
@ -63,8 +67,9 @@ public:
|
|||
* should cancel the handling of subsequent @c AVSDirectives with the same @c DialogRequestId, the
|
||||
* @c DirectiveHandler should call the setFailed() method on the @c result instance passed in to this call.
|
||||
*
|
||||
* NOTE: The implementation of this method MUST be thread-safe.
|
||||
* NOTE: The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* @note If this operation fails, an @c ExceptionEncountered message should be sent to AVS.
|
||||
* @note The implementation of this method MUST be thread-safe.
|
||||
* @note The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* of subsequent @c AVSDirectives.
|
||||
*
|
||||
* @param directive The directive to pre-handle.
|
||||
|
@ -72,30 +77,34 @@ public:
|
|||
*/
|
||||
virtual void preHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result) = 0;
|
||||
std::unique_ptr<DirectiveHandlerResultInterface> result) = 0;
|
||||
|
||||
/**
|
||||
* Handle the action specified by the directive identified by @c messageId. The handling of subsequent directives
|
||||
* with the same @c DialogRequestId may be blocked until the @c DirectiveHandler calls the @c setSucceeded()
|
||||
* method of the @c DirectiveHandlingResult instance passed in to the preHandleDirective() call for the directive
|
||||
* specified by @c messageId. If handling of this directive fails such that subsequent directives with the same
|
||||
* @c DialogRequestId should be cancelled, this @c DirectiveHandler should instead call setFailed() to indicate
|
||||
* a failure.
|
||||
* method of the @c DirectiveHandlingResult instance passed in to the @c preHandleDirective() call for the
|
||||
* directive specified by @c messageId. If handling of this directive fails such that subsequent directives with
|
||||
* the same @c DialogRequestId should be cancelled, this @c DirectiveHandler should instead call setFailed() to
|
||||
* indicate a failure.
|
||||
*
|
||||
* NOTE: The implementation of this method MUST be thread-safe.
|
||||
* NOTE: The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* @note If this operation fails, an @c ExceptionEncountered message should be sent to AVS.
|
||||
* @note The implementation of this method MUST be thread-safe.
|
||||
* @note The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* of subsequent @c AVSDirectives.
|
||||
*
|
||||
* @param messageId The message ID of a directive previously passed to preHandleDirective().
|
||||
* @param messageId The message ID of a directive previously passed to @c preHandleDirective().
|
||||
* @return @c false when @c messageId is not recognized, else @c true. Any errors related to handling of a valid
|
||||
* messageId should be reported using @c DirectiveHandlerResultInterface::setFailed().
|
||||
*/
|
||||
virtual void handleDirective(const std::string& messageId) = 0;
|
||||
virtual bool handleDirective(const std::string& messageId) = 0;
|
||||
|
||||
/**
|
||||
* Cancel an ongoing preHandleDirective() or handleDirective() operation for the specified @c AVSDirective. Once
|
||||
* this has been called the @c DirectiveHandler should not expect to receive further calls regarding this directive.
|
||||
* Cancel an ongoing @c preHandleDirective() or @c handleDirective() operation for the specified @c AVSDirective.
|
||||
* Once this has been called the @c DirectiveHandler should not expect to receive further calls regarding this
|
||||
* directive.
|
||||
*
|
||||
* NOTE: The implementation of this method MUST be thread-safe.
|
||||
* NOTE: The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* @note The implementation of this method MUST be thread-safe.
|
||||
* @note The implementation of this method MUST return quickly. Failure to do so blocks the processing
|
||||
* of subsequent @c AVSDirectives.
|
||||
*
|
||||
* @param messageId The message ID of a directive previously passed to preHandleDirective().
|
||||
|
@ -103,10 +112,9 @@ public:
|
|||
virtual void cancelDirective(const std::string& messageId) = 0;
|
||||
|
||||
/**
|
||||
* Shut down this @c DirectiveHandler. This handler will not receive any more calls. All references from this
|
||||
* handler to @c DirectiveHandlerResultInterface instances MUST be released before this method returns.
|
||||
* Notification that this handler has been de-registered and will not receive any more calls.
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
virtual void onDeregistered() = 0;
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* DirectiveProcessor.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_PROCESSOR_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_PROCESSOR_H_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <AVSCommon/AVSDirective.h>
|
||||
|
||||
#include "ADSL/DirectiveHandlerInterface.h"
|
||||
#include "ADSL/DirectiveRouter.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Object to process @c AVSDirectives that have a non-empty @c dialogRequestId.
|
||||
* @par
|
||||
* @c DirectiveProcessor receives directives via its @c onDirective() method. The @c dialogRequestId property of
|
||||
* incoming directives is checked against the current @c dialogRequestId (which is set by @c setDialogRequestId()).
|
||||
* If the values do not match, the @c AVSDirective is dropped, and @c onDirective() returns @c true to indicate that
|
||||
* the @c AVSDirective has been consumed (in this case, because it is not longer relevant).
|
||||
* @par
|
||||
* After passing this hurdle, the @c AVSDirective is forwarded to the @c preHandleDirective() method of whichever
|
||||
* @c DirectiveHandler is registered to handle the @c AVSDirective. If no @c DirectiveHandler is registered, the
|
||||
* incoming directive is rejected and any directives already queued for handling by the @c DirectiveProcessor are
|
||||
* canceled (because an entire dialog is canceled when the handling of any of its directives fails), and
|
||||
* @c onDirective() returns @c false to indicate that the unhandled @c AVDirective was rejected.
|
||||
* @par
|
||||
* Once an @c AVSDirective has been successfully forwarded for preHandling, it is enqueued awaiting its turn to be
|
||||
* handled. Handling is accomplished by forwarding the @c AVSDirective to the @c handleDirective() method of
|
||||
* whichever @c DirectiveHandler is registered to handle the @c AVSDirective. The handling of an @c AVSDirective can
|
||||
* be configured as @c BLOCKING or @c NON_BLOCKING. If the directive at the head of the handling queue is configured
|
||||
* for @c BLOCKING, the handling of subsequent @c AVSDirectives is held up until the @c DirectiveHandler for the
|
||||
* @c BLOCKING @c AVSDirective indicates that handling has completed or failed. Otherwise handleDirective() is
|
||||
* invoked, the @c AVSDirective is popped from the front of the queue, and processing of queued @c AVSDirective's
|
||||
* continues.
|
||||
*/
|
||||
class DirectiveProcessor {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param directiveRouter An object used to route directives to their registered handler.
|
||||
*/
|
||||
DirectiveProcessor(DirectiveRouter* directiveRouter);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~DirectiveProcessor();
|
||||
|
||||
/**
|
||||
* Set the current @c dialogRequestId. If a new value is specified any @c AVSDirective's whose pre-handling
|
||||
* or handling is already in progress the directive will be cancelled.
|
||||
*
|
||||
* @param dialogRequestId The new value for the current @c dialogRequestId.
|
||||
*/
|
||||
void setDialogRequestId(const std::string& dialogRequestId);
|
||||
|
||||
/**
|
||||
* Queue an @c AVSDirective for handling by whatever @c DirectiveHandler was registered to handle it.
|
||||
*
|
||||
* @param directive The @c AVADirective to process.
|
||||
* @return Whether the directive was consumed.
|
||||
*/
|
||||
bool onDirective(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* Shut down the DirectiveProcessor. This queues all outstanding @c AVSDirectives for cancellation and
|
||||
* blocks until the processing of all @c AVSDirectives has completed.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handle used to identify @c DirectiveProcessor instances referenced by @c DirectiveHandlerResult.
|
||||
*
|
||||
* Handles are used instead of a pointers to decouple the lifecycle of @c DirectiveProcessors from the lifecycle
|
||||
* of @c DirectiveHandlerInterface instances. In the case that a DirectiveHandler outlives the
|
||||
* @c DirectiveProcessor it may complete (or fail) the handling of a directive after (or during) the destruction
|
||||
* of the @c DirectiveProcessor. Using a handle instead of a pointer allows delivery of the completion / failure
|
||||
* notification to be dropped gracefully if the @c DirectiveProcessor is no longer there to receive it.
|
||||
*
|
||||
* @c ProcessorHandle values are mapped to @c DirectiveProcessor instances by the static @c m_handleMap.
|
||||
* The delivery of completion notifications by @c DirectiveHandlerResult and changes to @c m_handleMap
|
||||
* are serialized with the static @c m_handleMapMutex.
|
||||
*/
|
||||
using ProcessorHandle = unsigned int;
|
||||
|
||||
/**
|
||||
* Implementation of @c DirectiveHandlerResultInterface that forwards the completion / failure status
|
||||
* to the @c DirectiveProcessor from which it originated.
|
||||
*/
|
||||
class DirectiveHandlerResult : public DirectiveHandlerResultInterface {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param processorHandle handle of the @c DirectiveProcessor to forward to the result to.
|
||||
* @param directive The @c AVSDirective whose handling result will be specified by this instance.
|
||||
*/
|
||||
DirectiveHandlerResult(ProcessorHandle processorHandle, std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
void setCompleted() override;
|
||||
|
||||
void setFailed(const std::string& description) override;
|
||||
|
||||
private:
|
||||
/// Handle of the @c DirectiveProcessor to forward notifications to.
|
||||
ProcessorHandle m_processorHandle;
|
||||
|
||||
/// The @c messageId of the @c AVSDirective whose handling result will be specified by this instance.
|
||||
std::string m_messageId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive notification that the handling of an @c AVSDirective has completed.
|
||||
*
|
||||
* @param messageId The @c messageId of the @c AVSDirective whose handling has completed.
|
||||
*/
|
||||
void onHandlingCompleted(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Receive notification that the handling of an @c AVSDirective has failed.
|
||||
*
|
||||
* @param messageId The @c messageId of the @c AVSDirective whose handling has failed.
|
||||
* @param description A description (suitable for logging diagnostics) that indicates the nature of the failure.
|
||||
*/
|
||||
void onHandlingFailed(const std::string& messageId, const std::string& description);
|
||||
|
||||
/**
|
||||
* Remove an @c AVSDirective from @c m_handlingQueue.
|
||||
* @note This method must only be called by threads that have acquired @c m_contextMutex.
|
||||
*
|
||||
* @param messagId The @c messageId of the @c AVSDirective to remove.
|
||||
* @return Whether the @c AVSDirective was actually removed.
|
||||
*/
|
||||
bool removeFromHandlingQueueLocked(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Remove an @c AVSDirective from @c m_cancelingQueue.
|
||||
* @note This method must only be called by threads that have acquired @c m_contextMutex.
|
||||
*
|
||||
* @param messageId The @c messageId of the @c AVSDirective to remove.
|
||||
* @return Whether the @c AVSDirective was actually removed.
|
||||
*/
|
||||
bool removeFromCancelingQueueLocked(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Find the specified @c AVSDirective in the specified queue.
|
||||
*
|
||||
* @param messageId The message ID of the @c AVSDirective to find.
|
||||
* @param queue The queue to search for the @c AVSDirective.
|
||||
* @return An iterator positioned at the matching directive (or @c queue::end(), if not found).
|
||||
*/
|
||||
static std::deque<std::shared_ptr<avsCommon::AVSDirective>>::iterator findDirectiveInQueueLocked(
|
||||
const std::string& messageId,
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>& queue);
|
||||
|
||||
/**
|
||||
* Remove the specified @c AVSDirective from the specified queue. If the queue is not empty after the
|
||||
* removal, wake @c m_processingLoop.
|
||||
*
|
||||
* @param it An iterator positioned at the @c AVSDirective to remove from the queue.
|
||||
* @param queue The queue to remove the @c AVSDirective from.
|
||||
*/
|
||||
void removeDirectiveFromQueueLocked(
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>::iterator it,
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>& queue);
|
||||
|
||||
/**
|
||||
* Thread method for m_processingThread.
|
||||
*/
|
||||
void processingLoop();
|
||||
|
||||
/**
|
||||
* Process (cancel) all the items in @c m_cancelingQueue.
|
||||
* @note This method must only be called by threads that have acquired @c m_contextMutex.
|
||||
*
|
||||
* @param lock A @c unique_lock on m_contextMutex from the callers context, allowing this method to release
|
||||
* (and re-acquire) the lock around callbacks that need to be invoked.
|
||||
* @return Whether the @c AVSDirectives in @c m_cancelingQueue were processed.
|
||||
*/
|
||||
bool processCancelingQueueLocked(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
/**
|
||||
* Process (handle) the next @c AVSDirective in @c m_handlingQueue.
|
||||
* @note This method must only be called by threads that have acquired @c m_contextMutex.
|
||||
*
|
||||
* @param lock A @c unique_lock on m_contextMutex from the callers context, allowing this method to release
|
||||
* (and re-acquire) the lock around callbacks that need to be invoked.
|
||||
* @return Whether an @c AVSDirective from m_handlingQueue was processed.
|
||||
*/
|
||||
bool handleDirectiveLocked(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
/**
|
||||
* Move all the directives being handled or queued for handling to @c m_cancelingQueue. Also reset the
|
||||
* current @c dialogRequestId.
|
||||
*/
|
||||
void queueAllDirectivesForCancellationLocked();
|
||||
|
||||
/// Handle value identifying this instance.
|
||||
int m_handle;
|
||||
|
||||
/// A mutex used to serialize @c DirectiveProcessor operations with operations that occur in the creating context.
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// Object used to route directives to their assigned handler.
|
||||
DirectiveRouter* m_directiveRouter;
|
||||
|
||||
/// Whether or not the @c DirectiveProcessor is shutting down.
|
||||
bool m_isShuttingDown;
|
||||
|
||||
/// The current @c dialogRequestId
|
||||
std::string m_dialogRequestId;
|
||||
|
||||
/// Queue of @c AVSDirectives waiting to be canceled.
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> m_cancelingQueue;
|
||||
|
||||
/// The directive (if any) for which a preHandleDirective() call is in progress.
|
||||
std::shared_ptr<avsCommon::AVSDirective> m_directiveBeingPreHandled;
|
||||
|
||||
/// Queue of @c AVSDirectives waiting to be handled.
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> m_handlingQueue;
|
||||
|
||||
/// Whether @c handleDirective() has been called for the directive at the @c front() of @c m_handlingQueue.
|
||||
bool m_isHandlingDirective;
|
||||
|
||||
/// Condition variable used to wake @c processingLoop() when it is waiting.
|
||||
std::condition_variable m_wakeProcessingLoop;
|
||||
|
||||
/// Thread processing elements on @c m_handlingQueue and @c m_cancelingQueue.
|
||||
std::thread m_processingThread;
|
||||
|
||||
/// Mutex serializing the body of @ onDirective() to make the method thread-safe.
|
||||
std::mutex m_onDirectiveMutex;
|
||||
|
||||
/// Mutex to serialize access to @c m_handleMap;
|
||||
static std::mutex m_handleMapMutex;
|
||||
|
||||
/**
|
||||
* Map from @c ProcessorHandle value to @c DirectiveProcessor instance to allow for gracefully dropping a
|
||||
* completion (or failure) notification forwarded to the @c DirectiveProcessor during or after its destruction.
|
||||
*/
|
||||
static std::unordered_map<ProcessorHandle, DirectiveProcessor*> m_handleMap;
|
||||
|
||||
/// Next available @c ProcessorHandle value.
|
||||
static ProcessorHandle m_nextProcessorHandle;
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_PROCESSOR_H_
|
|
@ -19,51 +19,166 @@
|
|||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_ROUTER_H_
|
||||
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ADSL/DirectiveHandlerConfiguration.h"
|
||||
#include "ADSL/HandlerAndPolicy.h"
|
||||
#include "ADSL/NamespaceAndName.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Mapping from (@c namespace, @c name) pairs to (@c BlockingPolicy, @c DirectiveHandlerInterface) pairs.
|
||||
* Class to maintain a mapping from @c NamespaceAndName to @c HandlerAndPolicy, and to invoke
|
||||
* @c DirectiveHandlerInterface methods on the @c DirectiveHandler registered for a given @c AVSDirective.
|
||||
*/
|
||||
class DirectiveRouter {
|
||||
public:
|
||||
/**
|
||||
* Add mappings from from (@c namespace, @c name) pairs to (@c DirectiveHandler, @c BlockingPolicy) pairs.
|
||||
* The addition of a mapping for a (@c namespace, @c name) pair that already has a mapping replaces the
|
||||
* existing mapping. The addition of mapping to (@c DirectiveHandler, @c BlockingPolicy) pairs with either
|
||||
* a nullptr @c DirectiveHandler or a @c BlockingPolicy of NONE removes the mapping for that
|
||||
* (@c namespace, @c name) pair.
|
||||
*
|
||||
* @param configuration The configuration to add to the current mapping.
|
||||
* @return The set of @c DirectiveHandlers that were removed from the configuration.
|
||||
* Destructor.
|
||||
*/
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> setDirectiveHandlers(
|
||||
const DirectiveHandlerConfiguration& configuration);
|
||||
~DirectiveRouter();
|
||||
|
||||
/**
|
||||
* Get the (@c DirectiveHandlerInterface, @c BlockingPolicy) pair for a given @c AVSDirective.
|
||||
* Add mappings from from @c NamespaceAndName values to @c HandlerAndPolicy values. If a mapping for any of
|
||||
* the specified @c NamespaceAndName values already exists the entire call is refused.
|
||||
*
|
||||
* @param directive The @c AVSDirective to get the pair for.
|
||||
* @return The (@cDirectiveHandler, @c BlockingPolicy) pair specified by calls to @c setDirectiveHandlers().
|
||||
* If there is no match, the returned @c DirectiveHandlerInterface will be @c nullptr and the BlockingPolicy
|
||||
* will be @c NONE.
|
||||
* @param configuration The mappings to add.
|
||||
* @return Whether the configuration was added.
|
||||
*/
|
||||
DirectiveHandlerAndBlockingPolicyPair getDirectiveHandlerAndBlockingPolicy(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
bool addDirectiveHandlers(const DirectiveHandlerConfiguration& configuration);
|
||||
|
||||
/**
|
||||
* Remove all configured handlers from the @c DirectiveRouter.
|
||||
* Remove the specified mappings from @c NamespaceAndName values to @c HandlerAndPolicy values. If any of
|
||||
* the specified mappings do not match an existing mapping, the entire operation is refused.
|
||||
*
|
||||
* @return The set of @c DirectiveHandlers that were removed from the configuration.
|
||||
* @param configuration The mappings to remove.
|
||||
* @return Whether the configuration was removed.
|
||||
*/
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> clear();
|
||||
bool removeDirectiveHandlers(const DirectiveHandlerConfiguration& configuration);
|
||||
|
||||
/**
|
||||
* Invoke @c handleDirectiveImmediately() on the handler registered for the given @c AVSDirective.
|
||||
*
|
||||
* @param directive The directive to be handled immediately.
|
||||
* @return Whether or not the handler was invoked.
|
||||
*/
|
||||
bool handleDirectiveImmediately(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* Invoke @c preHandleDirective() on the handler registered for the given @c AVSDirective.
|
||||
*
|
||||
* @param directive The directive to be preHandled.
|
||||
* @param result A result object to receive notification about the completion (or failure) of handling
|
||||
* the @c AVSDirective.
|
||||
* @return Whether or not the handler was invoked.
|
||||
*/
|
||||
bool preHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
std::unique_ptr<DirectiveHandlerResultInterface> result);
|
||||
|
||||
/**
|
||||
* Invoke @c handleDirective() on the handler registered for the given @c AVSDirective.
|
||||
*
|
||||
* @param directive The directive to be handled.
|
||||
* @param[out] policyOut If this method returns @c true, @c policyOut is set to the @c BlockingPolicy value that
|
||||
* was configured when @c handleDirective() was called.
|
||||
* @return @c true if the the registered handler returned @c true. @c false if there was no registered handler
|
||||
* or the registered handler returned @c false (indicating that the directive was not recognized.
|
||||
*/
|
||||
bool handleDirective(std::shared_ptr<avsCommon::AVSDirective> directive, BlockingPolicy* policyOut);
|
||||
|
||||
/**
|
||||
* Invoke cancelDirective() on the handler registered for the given @c AVSDirective.
|
||||
*
|
||||
* @param directive The directive to be handled.
|
||||
* @return Whether or not the handler was invoked.
|
||||
*/
|
||||
bool cancelDirective(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
private:
|
||||
/// Mapping from (namespace, name) pairs to (@c BlockingPolicy, @c DirectiveHandlerInterface) pairs.
|
||||
DirectiveHandlerConfiguration m_configuration;
|
||||
/**
|
||||
* The lifecycle of instances of this class are used to set-up and tear-down around a call to a
|
||||
* @c DirectiveHandlerInterface method. In particular, while instantiated it increments the reference count of
|
||||
* uses of the handler and releases m_mutex via a @c std::unique_lock so the mutex is not held during the call.
|
||||
* When destroyed m_mutex is re-acquired and the reference count for the handler is decremented. When the
|
||||
* reference count goes to zero, the handler's @c onDeregistered() method in invoked.
|
||||
*/
|
||||
class HandlerCallScope {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* @note This constructor must only be called by threads that have acquired @c m_mutex. When this constructor
|
||||
* exits @c m_mutex will be unlocked.
|
||||
*
|
||||
* @param lock The @c std::unique_lock to use to release and re-acquire @c m_mutex.
|
||||
* @param router The @c DirectiveRouter instance the will make the call.
|
||||
* @param handler The @c DirectiveHandlerInterface instance to call.
|
||||
*/
|
||||
HandlerCallScope(
|
||||
std::unique_lock<std::mutex>& lock,
|
||||
DirectiveRouter* router,
|
||||
std::shared_ptr<DirectiveHandlerInterface> handler);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
* @note This destructor must be called with @c m_mutex released. When this destructor exits @c m_mutex will
|
||||
* have been re-acquired.
|
||||
*/
|
||||
~HandlerCallScope();
|
||||
|
||||
private:
|
||||
/// The lock used to release and re-acquire @c m_mutex.
|
||||
std::unique_lock<std::mutex>& m_lock;
|
||||
|
||||
/// The @c DirectiveRouter instance the will make the call.
|
||||
DirectiveRouter* m_router;
|
||||
|
||||
/// The @c DirectiveHandlerInterface instance to call.
|
||||
std::shared_ptr<DirectiveHandlerInterface> m_handler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the @c HandlerAndPolicy value for the specified @c AVSDirective.
|
||||
* @note The calling thread must have already acquired @c m_mutex.
|
||||
*
|
||||
* @param directive The directive to look up a value for.
|
||||
* @return The corresponding @c HandlerAndPolicy value for the specified directive.
|
||||
*/
|
||||
HandlerAndPolicy getHandlerAndPolicyLocked(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* Increment the reference count for the specified handler.
|
||||
*
|
||||
* @param handler The handler for which the reference count should be incremented.
|
||||
*/
|
||||
void incrementHandlerReferenceCountLocked(std::shared_ptr<DirectiveHandlerInterface> handler);
|
||||
|
||||
/**
|
||||
* Decrement the reference count for the specified handler. If the reference count goes to zero, call
|
||||
* the handlers onDeregistered() method.
|
||||
*
|
||||
* @param lock The @c std::unique_lock used to release and re-acquire @c m_mutex if @c onDeregistered() is called.
|
||||
* @param handler The @c DirectiveHandlerInterface instance whose reference count is to be decremented.
|
||||
*/
|
||||
void decrementHandlerReferenceCountLocked(
|
||||
std::unique_lock<std::mutex>& lock, std::shared_ptr<DirectiveHandlerInterface> handler);
|
||||
|
||||
/// A mutex used to serialize access to @c m_configuration and @c m_handlerReferenceCounts.
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// Mapping from @c NamespaceAndName to @c PolicyAndHandler.
|
||||
std::unordered_map<NamespaceAndName, HandlerAndPolicy> m_configuration;
|
||||
|
||||
/**
|
||||
* Instances of DirectiveHandlerInterface may receive calls after @c removeDirectiveHandlers() because
|
||||
* @ removeDirectiveHandlers() does not wait for any outstanding calls to complete. To provide notification
|
||||
* that no more calls will be received, a reference count is maintained for each directive handler. These
|
||||
* counts are incremented when a handler is added to @c m_configuration or when a call to a handler is in
|
||||
* progress. These counts are decremented when the handler is removed from @c m_configuration or a call to
|
||||
* a handler returns. When these count goes to zero the handler's @c onDeRegistered() method is invoked,
|
||||
* indicating that the handler will no longer be called (unless, of course, it is re-registered).
|
||||
*/
|
||||
std::unordered_map<std::shared_ptr<DirectiveHandlerInterface>, int> m_handlerReferenceCounts;
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
|
|
|
@ -20,375 +20,92 @@
|
|||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <AVSCommon/ExceptionEncounteredSenderInterface.h>
|
||||
#include "ADSL/DirectiveHandlerConfiguration.h"
|
||||
#include "ADSL/BlockingPolicy.h"
|
||||
|
||||
#include "ADSL/DirectiveProcessor.h"
|
||||
#include "ADSL/DirectiveRouter.h"
|
||||
#include "ADSL/DirectiveSequencerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Class for controlling the sequencing and handling of a stream of @c AVSDirective instances.
|
||||
*
|
||||
* Customers of this class specify a mapping of @c AVSDirectives specified by (namespace, name) pairs to
|
||||
* instances of the @c AVSDirectiveHandlerInterface via calls to @c setDirectiveHandlers(). Changes to this
|
||||
* mapping can be made at any time by specifying a new mapping. Customers pass @c AVSDirectives in to this
|
||||
* interface for processing via calls to @c onDirective(). @c AVSDirectives are processed in the order that
|
||||
* they are received. @c AVSDirectives with non-empty @c DialogRequestId values are filtered by the
|
||||
* @c DirectiveSequencer's current @c DialogRequestId value (specified by calls to @c setDialogRequestId().
|
||||
* Only @c AVSDirectives with a @c DialogRequestId that is empty or which matches the last setting of the
|
||||
* @c DialogRequestId are handled. All others are ignored. Specifying a new @c DialogRequestId value while
|
||||
* @c AVSDirectives are already being handled will cancel the handling of @c AVSDirectives that have the
|
||||
* previous @c DialogRequestId.
|
||||
* Class for sequencing and handling a stream of @c AVSDirective instances.
|
||||
*/
|
||||
class DirectiveSequencer {
|
||||
class DirectiveSequencer : public DirectiveSequencerInterface {
|
||||
public:
|
||||
/**
|
||||
* Create a new @c DirectiveSequencer instance.
|
||||
* Create a DirectiveSequencer.
|
||||
*
|
||||
* @param sender The instance of the @c ExceptionEncounteredSenderInterface to use to notify AVS
|
||||
* when we could not process a directive.
|
||||
* @param exceptionSender An instance of the @c ExceptionEncounteredSenderInterface used to send
|
||||
* ExceptionEncountered messages to AVS for directives that are not handled.
|
||||
* @return Returns a new DirectiveSequencer, or nullptr if the operation failed.
|
||||
*/
|
||||
static std::shared_ptr<DirectiveSequencer> create(
|
||||
static std::unique_ptr<DirectiveSequencerInterface> create(
|
||||
std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> exceptionSender);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~DirectiveSequencer();
|
||||
~DirectiveSequencer() override;
|
||||
|
||||
/**
|
||||
* Shut down the DirectiveSequencer. All calls to @c onDirective() subsequent to a call to shutdown() will fail
|
||||
* and a suitable notification will be delivered such that ExceptionEncountered can be sent back to AVS. This
|
||||
* method blocks until all @c AVSDirectives that have been passed to the @c DirectiveSequencer have been
|
||||
* canceled and all @c DirectiveHandlers have been shut down.
|
||||
*/
|
||||
void shutdown();
|
||||
bool addDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) override;
|
||||
|
||||
/**
|
||||
* Add mappings from from (@c namespace, @c name) pairs to (@c DirectiveHandler, @c BlockingPolicy) pairs.
|
||||
* The addition of a mapping for a (@c namespace, @c name) pair that already has a mapping replaces the
|
||||
* existing mapping. The addition of mapping to (@c DirectiveHandler, @c BlockingPolicy) pairs with either
|
||||
* a nullptr @c DirectiveHandler or a @c BlockingPolicy of NONE removes the mapping for that
|
||||
* (@c namespace, @c name) pair. Existing mappings not matching entries of @c configuration are unchanged.
|
||||
*
|
||||
* NOTE: Calling this method cancels any unhandled @c AVSDirectives already passed into calls to @c onDirective().
|
||||
* All subsequent @c AVSDirectives will be ignored until the value of the @c std::future returned from this
|
||||
* method has become available.
|
||||
*
|
||||
* @param configuration The set of mappings to add to the current mappings.
|
||||
* @return A future whose value becomes available once the new mapping(s) are active. The value indicates
|
||||
* whether or not the operation was successful.
|
||||
*/
|
||||
std::future<bool> setDirectiveHandlers(const DirectiveHandlerConfiguration& configuration);
|
||||
bool removeDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) override;
|
||||
|
||||
/**
|
||||
* Set the current @c DialogRequestId. This value can be set at any time. Setting this value causes a
|
||||
* @c DirectiveSequencerInterface to ignore or cancel any unhandled @c AVSDirectives with different
|
||||
* (and non-empty) DialogRequestId values.
|
||||
*
|
||||
* @param dialogRequestId The new value for the current @c DialogRequestId.
|
||||
*/
|
||||
void setDialogRequestId(const std::string& dialogRequestId);
|
||||
void setDialogRequestId(const std::string& dialogRequestId) override;
|
||||
|
||||
/**
|
||||
* Handle an @c AVSDirective. Handling is typically deferred to whatever @c DirectiveHandler is associated
|
||||
* with the @c AVSDirective's (namespace, name) pair. If no handler is registered for the @c AVSDirective
|
||||
* this method returns false.
|
||||
*
|
||||
* @param directive The @c AVSDirective to handle.
|
||||
* @return Whether or not the directive was accepted.
|
||||
*/
|
||||
bool onDirective(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
bool onDirective(std::shared_ptr<avsCommon::AVSDirective> directive) override;
|
||||
|
||||
void shutdown() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Implementation of @c DirectiveHandlerResultInterface that forwards the completion / failure status
|
||||
* to the @c DirectiveSequencer from which it originated.
|
||||
*/
|
||||
class DirectiveHandlerResult : public DirectiveHandlerResultInterface {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param directiveSequencer The @c DirectiveSequencer to forward to the result to. This is passed
|
||||
* and retained as a @c std::weak_ptr to avoid a cyclic std::shared_ptr relationship between
|
||||
* @c DirectiveHandlers and @c DirectiveSequencers.
|
||||
* @param directive The @c AVSDirective whose handling result will be specified by this instance.
|
||||
* @param blockingPolicy The @c BlockingPolicy configured for handling this directive.
|
||||
*/
|
||||
DirectiveHandlerResult(
|
||||
std::weak_ptr<DirectiveSequencer> directiveSequencer,
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
BlockingPolicy blockingPolicy);
|
||||
|
||||
void setCompleted() override;
|
||||
|
||||
void setFailed(const std::string& description) override;
|
||||
|
||||
private:
|
||||
/// The @c DirectiveSequencer to forward to the result to.
|
||||
std::weak_ptr<DirectiveSequencer> m_directiveSequencer;
|
||||
|
||||
/// The @c AVSDirective whose handling result will be specified by this instance.
|
||||
std::shared_ptr<avsCommon::AVSDirective> m_directive;
|
||||
|
||||
/// The @c BlockingPolicy for this directive.
|
||||
BlockingPolicy m_blockingPolicy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param exceptionSender An instance of the @c ExceptionEncounteredSenderInterface to send exceptions to.
|
||||
* will not be handled.
|
||||
*
|
||||
* @param exceptionSender An instance of the @c ExceptionEncounteredSenderInterface used to send
|
||||
* ExceptionEncountered messages to AVS for directives that are not handled.
|
||||
*/
|
||||
DirectiveSequencer(std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> exceptionSender);
|
||||
|
||||
/**
|
||||
* Deleted copy constructor to prevent copying instances of DirectiveSequencer.
|
||||
* Thread method for m_receivingThread.
|
||||
*/
|
||||
void receivingLoop();
|
||||
|
||||
/**
|
||||
* Process the next item in @c m_receivingQueue.
|
||||
* @note This method must only be called by threads that have acquired @c m_mutex.
|
||||
*
|
||||
* @param The instance to copy.
|
||||
* @param lock A @c unique_lock on m_mutex, allowing this method to release the lock around callbacks
|
||||
* that need to be invoked.
|
||||
*/
|
||||
DirectiveSequencer(const DirectiveSequencer& rhs) = delete;
|
||||
void receiveDirectiveLocked(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
/**
|
||||
* Deleted assignment operator to prevent copying instances of DirectiveSequencer.
|
||||
*
|
||||
* @param rhs The instance to copy.
|
||||
* @return The instance to copy to.
|
||||
*/
|
||||
DirectiveSequencer& operator=(const DirectiveSequencer& rhs) = delete;
|
||||
|
||||
/**
|
||||
* Initialize a new instance of @c DirectiveSequencer.
|
||||
*
|
||||
* @param thisDirectiveSequencer A @c weak_ptr to @c this @c DirectiveSequencer.
|
||||
*/
|
||||
void init(std::weak_ptr<DirectiveSequencer> thisDirectiveSequencer);
|
||||
|
||||
/**
|
||||
* Set @c m_isStopping to @c true.
|
||||
*/
|
||||
void setIsStopping();
|
||||
|
||||
/**
|
||||
* Clear all handlers from @c m_directiveRouter() and tell all the removed handlers to shut down.
|
||||
*/
|
||||
void clearDirectiveRouter();
|
||||
|
||||
/**
|
||||
* Receive notification that the handling of an @c AVSDirective has completed.
|
||||
* @param directive The @c AVSDirective whose handling has completed.
|
||||
*/
|
||||
void onHandlingCompleted(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* Receive notification that the handling of an @c AVSDirective has failed.
|
||||
* @param directive The @c AVSDirective whose handling has failed.
|
||||
* @param description A description (suitable for logging diagnostics) that indicates the nature of the failure.
|
||||
*/
|
||||
void onHandlingFailed(std::shared_ptr<avsCommon::AVSDirective> directive, const std::string& description);
|
||||
|
||||
/**
|
||||
* The thread method for the @c m_receivingThread. Loops, popping directives from @c m_receivingQueue,
|
||||
* performing initial processing and forwarding some directives to @c m_processingThread for handling
|
||||
* or canceling.
|
||||
*/
|
||||
void receiveDirectivesLoop();
|
||||
|
||||
/**
|
||||
* Get the next directive from the m_receivingQueue.
|
||||
* @return The next @c AVSDirective from @c m_receivingQueue to process, or nullptr of ths @c DirectiveSequencer
|
||||
* is stopping and @c m_receivingQueue is empty.
|
||||
*/
|
||||
std::shared_ptr<avsCommon::AVSDirective> receiveDirective();
|
||||
|
||||
/**
|
||||
* Invoke @c handleDirective(AVSDirective, DirectiveHandlingResult) or @c preHandleDirective() with the provided
|
||||
* @c AVSDirective, if possible. Return @c true if preHandleDirective() was invoked and the provided directive
|
||||
* should be pushed to @c m_handlingQueue for further processing. If @c true is returned,
|
||||
* @c m_directiveBeingPreHandled will be set to the provided directive.
|
||||
*
|
||||
* @param directive The @c AVSDirective to start processing.
|
||||
* @return Whether or not the specified @c AVSDirective requires further processing.
|
||||
*/
|
||||
bool shouldProcessDirective(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* If the @c AVSDirective specified by @c m_directiveBeingPreHandled is not nullptr, push it on to
|
||||
* @c m_handlingQueue or @c m_cancelingQueue, and reset @c m_directiveBeingPreHandled.
|
||||
*/
|
||||
void queueDirectiveForProcessing();
|
||||
|
||||
/**
|
||||
* The thread method for the @c m_processingThread. Loops, popping @c AVSDirectives from @c m_handlingQueue and
|
||||
* @c m_cancelingQueue and processing them.
|
||||
*/
|
||||
void processDirectivesLoop();
|
||||
|
||||
/**
|
||||
* Shut down the @c setDirectiveHandlers() in the set @c m_removedHandlers, and empty the set when done.
|
||||
* NOTE: m_processingMutex must be locked by the thread that calls this method.
|
||||
*
|
||||
* @param lock A lock on m_processingMutex.
|
||||
*/
|
||||
void shutdownRemovedHandlersLocked(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
/**
|
||||
* Handles processing of directives when @c m_isStopping is @c true. If @c AVSDirectives are in m_handlingQueue,
|
||||
* moves those directives to @c m_cancelingQueue. If m_cancelingQueue is not empty, returns false to indicate
|
||||
* that there are still @c AVSDirectives that need processing (i.e. canceling).
|
||||
*
|
||||
* @return Whether or not @c processDirectivesLoop() should exit.
|
||||
*/
|
||||
bool shouldProcessingStop();
|
||||
|
||||
/**
|
||||
* Call @c cancelDirective() on each @c AVSDirective in @c m_cancelingQueue.
|
||||
*/
|
||||
void cancelDirectives();
|
||||
|
||||
/**
|
||||
* Call @c handleDirective(MessageId, bool isBlocking) on the next @c AVSDirective in m_handlingQueue, unless an
|
||||
* @c AVSDirective is already being handled (i.e. @c handleDirective() wad called with @c isBlocking = @c true
|
||||
* and the result of the operation has yet to be indicated by the handler).
|
||||
*/
|
||||
void handleDirective();
|
||||
|
||||
/**
|
||||
* Remove the specified directive from @c m_handlingQueue or @c m_cancelingQueue. If @c cancelAllIfFound
|
||||
* is @c true and the specified @c AVSDirective was found in either queue, move all @c AVSDirectives from
|
||||
* m_handlingQueue to @c m_cancelingQueue (to cancel the entire group of @c AVSDirectives with the same
|
||||
* @c DialogrequestId.
|
||||
*
|
||||
* @param caller Text used to specify the caller of this function when creating log messages.
|
||||
* @param directive The directive ot remove from processing.
|
||||
* @param cancelAllIfFound Whether the @c AVSDirectives in m_handlingQueue should be cancelled if the
|
||||
* specified @c AVSDirective is still in @c m_handlingQueue or @c m_cancelingQueue.
|
||||
*/
|
||||
void removeDirectiveFromProcessing(
|
||||
const std::string& caller, std::shared_ptr<avsCommon::AVSDirective> directive, bool cancelAllIfFound);
|
||||
|
||||
/**
|
||||
* Cancel handling of directives in m_handlingQueue. Moves the contents of m_handlingQueue to the
|
||||
* end of m_cancelingQueue.
|
||||
* NOTE: MUST be called with m_processingMutex acquired.
|
||||
*/
|
||||
void cancelPreHandledDirectivesLocked();
|
||||
|
||||
/**
|
||||
* Check if the DirectiveSequencer has drained of @c AVSDirectives. If so, and there is an outstanding promise
|
||||
* to complete a @c setDirectiveHandlers() operation, complete the operation and fulfill that promise.
|
||||
*
|
||||
* NOTE: This is the only method that acquires both @c m_receivingMutex and @c m_handlingMutex. It does so
|
||||
* in that order. So, any future methods that need to acquire both mutexes must do so in the same order to
|
||||
* avoid a deadlock.
|
||||
*/
|
||||
void maybeSetDirectiveHandlers();
|
||||
|
||||
/**
|
||||
* Send ExceptionEncountered message top AVS for @c AVSDirectives that were not processed.
|
||||
*
|
||||
* @param directive The @c AVSDirective that was not processed.
|
||||
* @param error The type of error that prevented the @c AVSDirective from being processed.
|
||||
* @param message Text used to describe why the @c AVSDirective was not processed.
|
||||
*/
|
||||
void sendExceptionEncountered(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
avsCommon::ExceptionErrorType error,
|
||||
const std::string& message);
|
||||
/// Serializes access to data members (besides m_directiveRouter and m_directiveProcessor).
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// The @c ExceptionEncounteredSenderInterface instance to send exceptions to.
|
||||
std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> m_exceptionSender;
|
||||
|
||||
/// Whether the DirectiveSequencer is shutting down.
|
||||
std::atomic<bool> m_isStopping;
|
||||
/// Whether or not the @c DirectiveReceiver is shutting down.
|
||||
bool m_isShuttingDown;
|
||||
|
||||
/// Weak pointer to @c this, used to create links back to @c this without creating a @c shared_ptr cycle.
|
||||
std::weak_ptr<DirectiveSequencer> m_thisDirectiveSequencer;
|
||||
/// Object used to route directives to their assigned handler.
|
||||
DirectiveRouter m_directiveRouter;
|
||||
|
||||
/// Mutex to serialize access to @c m_receivingQueue.
|
||||
std::mutex m_receivingMutex;
|
||||
|
||||
/// Condition variable used to wake m_receivingThread.
|
||||
std::condition_variable m_receivingNotifier;
|
||||
|
||||
/// Thread to process @c AVSDirectives in @c m_receivingQueue.
|
||||
std::thread m_receivingThread;
|
||||
/// Object used to drive handling of @c AVSDirectives.
|
||||
std::shared_ptr<DirectiveProcessor> m_directiveProcessor;
|
||||
|
||||
/// Queue of @c AVSDirectives waiting to be received.
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> m_receivingQueue;
|
||||
|
||||
/**
|
||||
* Mutex to serialize access to @c m_dialogRequestId, @c m_directiveBeingPreHandled, @c m_isHandlingDirective,
|
||||
* @c m_handlingQueue, @c m_cancellingQueue.
|
||||
*/
|
||||
std::mutex m_processingMutex;
|
||||
/// Condition variable used to wake m_receivingLoop when waiting.
|
||||
std::condition_variable m_wakeReceivingLoop;
|
||||
|
||||
/// Condition variable used to wake m_processingThread.
|
||||
std::condition_variable m_processingNotifier;
|
||||
|
||||
/// Thread to process @c AVSDirectives in @c m_handlingQueue and @c m_cancelingQueue.
|
||||
std::thread m_processingThread;
|
||||
|
||||
/// The DialogRequestID used to filter out @c AVSDirectives (unless their DialogRequestId is empty).
|
||||
std::string m_dialogRequestId;
|
||||
|
||||
/**
|
||||
* Whether or not a call to @c handleDirectiveImmediately() is in progress. This is used to help with the
|
||||
* case where @c setDirectiveHandlers() is called and cannot complete while the @c DirectiveSequencer is
|
||||
* actively processing @c AVSDirectives.
|
||||
*/
|
||||
bool m_handlingReceivedDirective;
|
||||
|
||||
/**
|
||||
* The directive (if any) that has been passed to an active call to preHandleDirective(). This is used to
|
||||
* handle the case where @ onDirectiveError() is called before the @c AVSDirective has been added to
|
||||
* m_handlingQueue. @c m_receivingThread sets this value before calling preHandleDirective(), makes the
|
||||
* call and then checks the value once it has regained m_processingMutex. If a call to onDirectiveError()
|
||||
* has occurred for this @c AVSDirective in the mean time, it will clear @c m_directiveBeingPreHandled,
|
||||
* indicating to @c m_receivingThread that it should discard the @c AVSDirective, instead of push it onto
|
||||
* @c m_handlingQueue or @c m_cancelingQueue.
|
||||
*/
|
||||
std::shared_ptr<avsCommon::AVSDirective> m_directiveBeingPreHandled;
|
||||
|
||||
/// Whether the m_processingThread is currently handling the @c AVSDirective at the front of @c m_handlingQueue.
|
||||
bool m_isHandlingDirective;
|
||||
|
||||
/// Queue of @c AVSDirectives waiting to be handled.
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> m_handlingQueue;
|
||||
|
||||
/// Queue of @c AVSDirectives waiting to be cancelled.
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> m_cancellingQueue;
|
||||
|
||||
/// Whether the DirectiveSequencer is processing a call to setDirectiveHandlers().
|
||||
std::atomic<bool> m_isSettingDirectiveHandlers;
|
||||
|
||||
/**
|
||||
* @c promise for @c future returned from @c setDirectiveHandlers(). When set, the value indicates whether
|
||||
* or not the operation was successful.
|
||||
*/
|
||||
std::shared_ptr<std::promise<bool>> m_setDirectiveHandlersPromise;
|
||||
|
||||
/**
|
||||
* The @c DirectiveHandlerConfiguration to add to the current mapping once the @c DireciveSequencer has no
|
||||
* @c AVSDirectives to process.
|
||||
*/
|
||||
DirectiveHandlerConfiguration m_newDirectiveHandlerConfiguration;
|
||||
|
||||
/// Set of removed @c DirectiveHandlers that need to be shut down.
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> m_removedHandlers;
|
||||
|
||||
/// Object used to map (@c namespace, @c name) pairs to (@c DirectiveHandlerInterface, @c BlockingPolicy) pairs.
|
||||
DirectiveRouter m_directiveRouter;
|
||||
/// Thread to receive directives.
|
||||
std::thread m_receivingThread;
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* DirectiveSequencerInterface.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_SEQUENCER_INTERFACE_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_SEQUENCER_INTERFACE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ADSL/DirectiveHandlerConfiguration.h"
|
||||
#include "ADSL/DirectiveRouter.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Interface for sequencing and handling a stream of @c AVSDirective instances.
|
||||
*
|
||||
* Customers of this interface specify a mapping of @c AVSDirectives specified by (namespace, name) pairs to
|
||||
* instances of the @c AVSDirectiveHandlerInterface via calls to @c setDirectiveHandlers(). Changes to this
|
||||
* mapping can be made at any time by specifying a new mapping. Customers pass @c AVSDirectives in to this
|
||||
* interface for processing via calls to @c onDirective(). @c AVSDirectives are processed in the order that
|
||||
* they are received. @c AVSDirectives with a non-empty @c dialogRequestId value are filtered by the
|
||||
* @c DirectiveSequencer's current @c dialogRequestId value (specified by calls to @c setDialogRequestId()).
|
||||
* Only @c AVSDirectives with a @c dialogRequestId that is empty or which matches the last setting of the
|
||||
* @c dialogRequestId are handled. All others are ignored. Specifying a new @c DialogRequestId value while
|
||||
* @c AVSDirectives are already being handled will cancel the handling of @c AVSDirectives that have the
|
||||
* previous @c DialogRequestId and whose handling has not completed.
|
||||
*
|
||||
* This interface was factored out of DirectiveSequencer to facilitate mocking for unit tests.
|
||||
*/
|
||||
class DirectiveSequencerInterface {
|
||||
public:
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~DirectiveSequencerInterface() = default;
|
||||
|
||||
/**
|
||||
* Add mappings from from @c NamespaceAndName values to @c HandlerAndPolicy values. If a mapping for any of
|
||||
* the specified @c NamespaceAndName values already exists the entire call is refused.
|
||||
*
|
||||
* @param configuration The mappings to add.
|
||||
* @return Whether the mappings were added.
|
||||
*/
|
||||
virtual bool addDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) = 0;
|
||||
|
||||
/**
|
||||
* Remove the specified mappings from @c NamespaceAndName values to @c HandlerAndPolicy values. If any of
|
||||
* the specified mappings do not match an existing mapping, the entire operation is refused.
|
||||
*
|
||||
* @param configuration the mappings to remove.
|
||||
* @return Whether the mappings were removed.
|
||||
*/
|
||||
virtual bool removeDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) = 0;
|
||||
|
||||
/**
|
||||
* Set the current @c DialogRequestId. This value can be set at any time. Setting this value causes a
|
||||
* @c DirectiveSequencer to drop unhandled @c AVSDirectives with different (and non-empty) DialogRequestId
|
||||
* values. @c AVSDirectives with a differing @c dialogRequestId value and whose pre-handling or handling
|
||||
* is already in progress will be cancelled.
|
||||
*
|
||||
* @param dialogRequestId The new value for the current @c DialogRequestId.
|
||||
*/
|
||||
virtual void setDialogRequestId(const std::string& dialogRequestId) = 0;
|
||||
|
||||
/**
|
||||
* Sequence the handling of an @c AVSDirective. The actual handling is done by whichever @c DirectiveHandler
|
||||
* is associated with the @c AVSDirective's (namespace, name) pair.
|
||||
*
|
||||
* @param directive The @c AVSDirective to handle.
|
||||
* @return Whether or not the directive was accepted.
|
||||
*/
|
||||
virtual bool onDirective(std::shared_ptr<avsCommon::AVSDirective> directive) = 0;
|
||||
|
||||
/**
|
||||
* Shut down the DirectiveSequencer. This method blocks until all processing of directives has stopped.
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_DIRECTIVE_SEQUENCER_INTERFACE_H_
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* HandlerAndPolicy.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_HANDLER_AND_POLICY_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_HANDLER_AND_POLICY_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ADSL/BlockingPolicy.h"
|
||||
#include "ADSL/DirectiveHandlerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Conjoined @c DirectiveHandler and @c BlockingPolicy values.
|
||||
*/
|
||||
class HandlerAndPolicy {
|
||||
public:
|
||||
/**
|
||||
* Constructor to initialize with default property values.
|
||||
*/
|
||||
HandlerAndPolicy();
|
||||
|
||||
/**
|
||||
* Constructor to initialize with specific property values.
|
||||
*
|
||||
* @param handlerIn The @c AVSDirectiveHandlerInterface value for this instance.
|
||||
* @param policyIn The @c BlockingPolicy value for this instance.
|
||||
*/
|
||||
HandlerAndPolicy(std::shared_ptr<DirectiveHandlerInterface> handlerIn, BlockingPolicy policyIn);
|
||||
|
||||
/**
|
||||
* Return whether this instance specifies a non-null directive handler and a non-NONE BlockingPolicy.
|
||||
*
|
||||
* @return Whether this instance specifies a non-null directive handler and a non-NONE BlockingPolicy.
|
||||
*/
|
||||
operator bool () const;
|
||||
|
||||
/// The @c DirectiveHandlerInterface value for this instance.
|
||||
std::shared_ptr<DirectiveHandlerInterface> handler;
|
||||
|
||||
/// The @c BlockingPolicy value for this instance.
|
||||
BlockingPolicy policy;
|
||||
};
|
||||
|
||||
/**
|
||||
* == operator.
|
||||
*
|
||||
* @param lhs The HandlerAndPolicy instance on the left hand side of the == operation.
|
||||
* @param rhs The HandlerAndPolicy instance on the right hand side of the == operation.
|
||||
* @return Whether the @c lhs instance is equal to the @c rhs instance.
|
||||
*/
|
||||
bool operator == (const HandlerAndPolicy& lhs, const HandlerAndPolicy& rhs);
|
||||
|
||||
/**
|
||||
* != operator.
|
||||
*
|
||||
* @param lhs The HandlerAndPolicy instance on the left hand side of the == operation.
|
||||
* @param rhs The HandlerAndPolicy instance on the right hand side of the != operation.
|
||||
* @return Whether the @c lhs instance is NOT equal to the @c rhs instance.
|
||||
*/
|
||||
bool operator != (const HandlerAndPolicy& lhs, const HandlerAndPolicy& rhs);
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_HANDLER_AND_POLICY_H_
|
|
@ -20,19 +20,19 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <AVSCommon/ExceptionEncounteredSenderInterface.h>
|
||||
#include <ACL/AVSConnectionManager.h>
|
||||
#include <ACL/MessageObserverInterface.h>
|
||||
#include <ACL/Message.h>
|
||||
#include <ACL/MessageObserverInterface.h>
|
||||
#include <AVSCommon/ExceptionEncounteredSenderInterface.h>
|
||||
|
||||
#include "ADSL/DirectiveSequencer.h"
|
||||
#include "ADSL/DirectiveSequencerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Class that converts @c acl::Message to @c AVSDirective, and passes those directives to a @c DirectiveSequencer.
|
||||
* Class that converts @c acl::Message to @c AVSDirective, and passes those directives to a
|
||||
* @c DirectiveSequencerInterface.
|
||||
*/
|
||||
class MessageInterpreter : public acl::MessageObserverInterface {
|
||||
public:
|
||||
|
@ -41,10 +41,11 @@ public:
|
|||
*
|
||||
* @param exceptionEncounteredSender The exceptions encountered messages sender, which will allow us to send
|
||||
* exception encountered back to AVS.
|
||||
* @param directiveSequencer The DirectiveSequencerInterface implementation, which will receive @c AVSDirectives.
|
||||
* @param directiveSequencerInterface The DirectiveSequencerInterface implementation, which will receive
|
||||
* @c AVSDirectives.
|
||||
*/
|
||||
MessageInterpreter(std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> exceptionEncounteredSender,
|
||||
std::shared_ptr<DirectiveSequencer> directiveSequencer);
|
||||
std::shared_ptr<DirectiveSequencerInterface> directiveSequencer);
|
||||
|
||||
void receive(std::shared_ptr<acl::Message> message) override;
|
||||
|
||||
|
@ -69,7 +70,7 @@ private:
|
|||
/// Object that manages sending exceptions encountered messages to AVS.
|
||||
std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> m_exceptionEncounteredSender;
|
||||
/// Object to which we will send @c AVSDirectives once translated from @c acl::Messages.
|
||||
std::shared_ptr<DirectiveSequencer> m_directiveSequencer;
|
||||
std::shared_ptr<DirectiveSequencerInterface> m_directiveSequencer;
|
||||
};
|
||||
|
||||
} // namespace directiveSequencer
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* NamespaceAndName.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_NAMESPACE_AND_NAME_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_NAMESPACE_AND_NAME_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* Conjoined @c namespace and @c name values (intended for identifying sub-types of @c AVSDirective).
|
||||
*/
|
||||
class NamespaceAndName {
|
||||
public:
|
||||
/**
|
||||
* Constructor to initialize with default values.
|
||||
*/
|
||||
NamespaceAndName() = default;
|
||||
|
||||
/**
|
||||
* Constructor to initialize wih specific values.
|
||||
*
|
||||
* @param nameSpaceIn The @c namespace value for this instance.
|
||||
* @param nameIn The @c name value for this instance.
|
||||
*/
|
||||
NamespaceAndName(const std::string& nameSpaceIn, const std::string& nameIn);
|
||||
|
||||
/// The @c namespace value of this instance.
|
||||
const std::string nameSpace;
|
||||
|
||||
/// The @c name value of this instance.
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Operator == to allow @c namespaceAndName ot be used as a key in @cstd::unordered_map.
|
||||
*
|
||||
* @param rhs The left hand side of the == operation.
|
||||
* @param rhs The right hand side of the == operation.
|
||||
* @return Whether or not this instance and @c rhs are equivalent.
|
||||
*/
|
||||
bool operator == (const NamespaceAndName& lhs, const NamespaceAndName& rhs);
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
namespace std {
|
||||
|
||||
/**
|
||||
* @ std::hash() specialization defined to allow @c NamespaceAndName to be used as a key in @c std::unordered_map.
|
||||
*/
|
||||
template <> struct hash<alexaClientSDK::adsl::NamespaceAndName> {
|
||||
size_t operator()(const alexaClientSDK::adsl::NamespaceAndName& in) const;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_INCLUDE_ADSL_NAMESPACE_AND_NAME_H_
|
|
@ -2,14 +2,18 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
|||
|
||||
find_package(Threads ${THREADS_PACKAGE_CONFIG})
|
||||
add_library(ADSL SHARED
|
||||
DirectiveProcessor.cpp
|
||||
DirectiveRouter.cpp
|
||||
DirectiveSequencer.cpp
|
||||
MessageInterpreter.cpp)
|
||||
HandlerAndPolicy.cpp
|
||||
MessageInterpreter.cpp
|
||||
NamespaceAndName.cpp)
|
||||
target_include_directories(ADSL PUBLIC
|
||||
"${ACL_INCLUDE_DIRS}"
|
||||
"${ACL_SOURCE_DIR}/include"
|
||||
"${ADSL_SOURCE_DIR}/include"
|
||||
"${AVSCommon_INCLUDE_DIRS}")
|
||||
target_link_libraries(ADSL
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
AVSCommon
|
||||
ACL
|
||||
AVSUtils)
|
||||
|
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* DirectiveProcessor.cpp
|
||||
*
|
||||
* Copyright 2017 Amazon.com, Inc. or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License 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 <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <AVSCommon/ExceptionEncountered.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
#include <AVSUtils/Memory/Memory.h>
|
||||
|
||||
#include "ADSL/DirectiveProcessor.h"
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("DirectiveProcessor");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
using namespace avsCommon;
|
||||
|
||||
std::mutex DirectiveProcessor::m_handleMapMutex;
|
||||
DirectiveProcessor::ProcessorHandle DirectiveProcessor::m_nextProcessorHandle = 0;
|
||||
std::unordered_map<DirectiveProcessor::ProcessorHandle, DirectiveProcessor*> DirectiveProcessor::m_handleMap;
|
||||
|
||||
DirectiveProcessor::DirectiveProcessor(DirectiveRouter* directiveRouter) :
|
||||
m_directiveRouter{directiveRouter},
|
||||
m_isShuttingDown{false},
|
||||
m_isHandlingDirective{false} {
|
||||
std::lock_guard<std::mutex> lock(m_handleMapMutex);
|
||||
m_handle = ++m_nextProcessorHandle;
|
||||
m_handleMap[m_handle] = this;
|
||||
m_processingThread = std::thread(&DirectiveProcessor::processingLoop, this);
|
||||
}
|
||||
|
||||
DirectiveProcessor::~DirectiveProcessor() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void DirectiveProcessor::setDialogRequestId(const std::string& dialogRequestId) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (dialogRequestId == m_dialogRequestId) {
|
||||
ACSDK_WARN(LX("setDialogRequestIdIgnored").d("reason", "unchanged").d("dialogRequestId", dialogRequestId));
|
||||
return;
|
||||
}
|
||||
ACSDK_INFO(LX("setDialogRequestId").d("dialogRequestId", dialogRequestId));
|
||||
queueAllDirectivesForCancellationLocked();
|
||||
m_dialogRequestId = dialogRequestId;
|
||||
}
|
||||
|
||||
bool DirectiveProcessor::onDirective(std::shared_ptr<AVSDirective> directive) {
|
||||
if (!directive) {
|
||||
ACSDK_ERROR(LX("onDirectiveFailed").d("action", "ignored").d("reason", "nullptrDirective"));
|
||||
return false;
|
||||
}
|
||||
std::lock_guard<std::mutex> onDirectiveLock(m_onDirectiveMutex);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (m_isShuttingDown) {
|
||||
ACSDK_WARN(LX("onDirectiveFailed")
|
||||
.d("messageId", directive->getMessageId()).d("action", "ignored").d("reason", "shuttingDown"));
|
||||
return false;
|
||||
}
|
||||
if (directive->getDialogRequestId() != m_dialogRequestId) {
|
||||
ACSDK_INFO(LX("onDirective")
|
||||
.d("messageId", directive->getMessageId())
|
||||
.d("action", "dropped")
|
||||
.d("reason", "dialogRequestIdDoesNotMatch")
|
||||
.d("directivesDialogRequestId", directive->getDialogRequestId())
|
||||
.d("dialogRequestId", m_dialogRequestId));
|
||||
return true;
|
||||
}
|
||||
m_directiveBeingPreHandled = directive;
|
||||
lock.unlock();
|
||||
auto handled = m_directiveRouter->preHandleDirective(
|
||||
directive, alexaClientSDK::avsUtils::memory::make_unique<DirectiveHandlerResult>(m_handle, directive));
|
||||
lock.lock();
|
||||
if (m_directiveBeingPreHandled) {
|
||||
m_directiveBeingPreHandled.reset();
|
||||
if (handled) {
|
||||
m_handlingQueue.push_back(directive);
|
||||
m_wakeProcessingLoop.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void DirectiveProcessor::shutdown() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_handleMapMutex);
|
||||
m_handleMap.erase(m_handle);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
queueAllDirectivesForCancellationLocked();
|
||||
m_isShuttingDown = true;
|
||||
m_wakeProcessingLoop.notify_one();
|
||||
}
|
||||
if (m_processingThread.joinable()) {
|
||||
m_processingThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
DirectiveProcessor::DirectiveHandlerResult::DirectiveHandlerResult(
|
||||
DirectiveProcessor::ProcessorHandle processorHandle, std::shared_ptr<AVSDirective> directive) :
|
||||
m_processorHandle{processorHandle}, m_messageId{directive->getMessageId()} {
|
||||
}
|
||||
|
||||
void DirectiveProcessor::DirectiveHandlerResult::setCompleted() {
|
||||
std::lock_guard<std::mutex> lock(m_handleMapMutex);
|
||||
auto it = m_handleMap.find(m_processorHandle);
|
||||
if (it == m_handleMap.end()) {
|
||||
ACSDK_DEBUG(LX("setCompletedIgnored").d("reason", "directiveSequencerAlreadyShutDown"));
|
||||
return;
|
||||
}
|
||||
it->second->onHandlingCompleted(m_messageId);
|
||||
}
|
||||
|
||||
void DirectiveProcessor::DirectiveHandlerResult::setFailed(const std::string& description) {
|
||||
std::lock_guard<std::mutex> lock(m_handleMapMutex);
|
||||
auto it = m_handleMap.find(m_processorHandle);
|
||||
if (it == m_handleMap.end()) {
|
||||
ACSDK_DEBUG(LX("setFailedIgnored").d("reason", "directiveSequencerAlreadyShutDown"));
|
||||
return;
|
||||
}
|
||||
it->second->onHandlingFailed(m_messageId, description);
|
||||
}
|
||||
|
||||
void DirectiveProcessor::onHandlingCompleted(const std::string& messageId) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
ACSDK_DEBUG(LX("onHandlingCompeted").d("messageId", messageId).d("directiveBeingPreHandled",
|
||||
m_directiveBeingPreHandled ? m_directiveBeingPreHandled->getMessageId() : "(nullptr)"));
|
||||
|
||||
if (m_directiveBeingPreHandled && m_directiveBeingPreHandled->getMessageId() == messageId) {
|
||||
m_directiveBeingPreHandled.reset();
|
||||
} else if (!removeFromHandlingQueueLocked(messageId)) {
|
||||
removeFromCancelingQueueLocked(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveProcessor::onHandlingFailed(const std::string& messageId, const std::string& description) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
ACSDK_DEBUG(LX("onHandlingFailed")
|
||||
.d("messageId", messageId)
|
||||
.d("directiveBeingPreHandled",
|
||||
m_directiveBeingPreHandled ? m_directiveBeingPreHandled->getMessageId() : "(nullptr)")
|
||||
.d("description", description));
|
||||
|
||||
if (m_directiveBeingPreHandled && m_directiveBeingPreHandled->getMessageId() == messageId) {
|
||||
m_directiveBeingPreHandled.reset();
|
||||
queueAllDirectivesForCancellationLocked();
|
||||
} else if (removeFromHandlingQueueLocked(messageId)) {
|
||||
queueAllDirectivesForCancellationLocked();
|
||||
} else {
|
||||
removeFromCancelingQueueLocked(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectiveProcessor::removeFromHandlingQueueLocked(const std::string& messageId) {
|
||||
auto it = findDirectiveInQueueLocked(messageId, m_handlingQueue);
|
||||
if (m_handlingQueue.end() == it) {
|
||||
return false;
|
||||
}
|
||||
if (m_isHandlingDirective && m_handlingQueue.begin() == it) {
|
||||
m_isHandlingDirective = false;
|
||||
}
|
||||
removeDirectiveFromQueueLocked(it, m_handlingQueue);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveProcessor::removeFromCancelingQueueLocked(const std::string& messageId) {
|
||||
auto it = findDirectiveInQueueLocked(messageId, m_cancelingQueue);
|
||||
if (m_cancelingQueue.end() == it) {
|
||||
return false;
|
||||
}
|
||||
removeDirectiveFromQueueLocked(it, m_cancelingQueue);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>::iterator DirectiveProcessor::findDirectiveInQueueLocked(
|
||||
const std::string& messageId,
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>& queue) {
|
||||
auto matches = [messageId](std::shared_ptr<AVSDirective> element) {
|
||||
return element->getMessageId() == messageId;
|
||||
};
|
||||
return std::find_if(queue.begin(), queue.end(), matches);
|
||||
}
|
||||
|
||||
void DirectiveProcessor::removeDirectiveFromQueueLocked(
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>::iterator it,
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>>& queue) {
|
||||
queue.erase(it);
|
||||
if (!queue.empty()) {
|
||||
m_wakeProcessingLoop.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveProcessor::processingLoop() {
|
||||
auto wake = [this]() {
|
||||
return !m_cancelingQueue.empty() || (!m_handlingQueue.empty() && !m_isHandlingDirective) || m_isShuttingDown;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_wakeProcessingLoop.wait(lock, wake);
|
||||
if (!processCancelingQueueLocked(lock) && !handleDirectiveLocked(lock) && m_isShuttingDown) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectiveProcessor::processCancelingQueueLocked(std::unique_lock<std::mutex> &lock) {
|
||||
if (m_cancelingQueue.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::deque<std::shared_ptr<avsCommon::AVSDirective>> temp(std::move(m_cancelingQueue));
|
||||
lock.unlock();
|
||||
for (auto directive : temp) {
|
||||
m_directiveRouter->cancelDirective(directive);
|
||||
}
|
||||
lock.lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveProcessor::handleDirectiveLocked(std::unique_lock<std::mutex> &lock) {
|
||||
if (m_handlingQueue.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (m_isHandlingDirective) {
|
||||
return true;
|
||||
}
|
||||
auto directive = m_handlingQueue.front();
|
||||
m_isHandlingDirective = true;
|
||||
lock.unlock();
|
||||
auto policy = BlockingPolicy::NONE;
|
||||
auto handled = m_directiveRouter->handleDirective(directive, &policy);
|
||||
lock.lock();
|
||||
if (!handled || BlockingPolicy::BLOCKING != policy) {
|
||||
m_isHandlingDirective = false;
|
||||
if (!m_handlingQueue.empty() && m_handlingQueue.front() == directive) {
|
||||
m_handlingQueue.pop_front();
|
||||
} else if (!handled) {
|
||||
ACSDK_ERROR(LX("handlingDirectiveLockedFailed")
|
||||
.d("expected", directive->getMessageId())
|
||||
.d("front", m_handlingQueue.empty() ? "(empty)" : m_handlingQueue.front()->getMessageId())
|
||||
.d("reason", "handlingQueueFrontChangedWithoutBeingHandled"));
|
||||
}
|
||||
}
|
||||
if (!handled) {
|
||||
queueAllDirectivesForCancellationLocked();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DirectiveProcessor::queueAllDirectivesForCancellationLocked() {
|
||||
m_dialogRequestId.clear();
|
||||
if (m_directiveBeingPreHandled) {
|
||||
m_handlingQueue.push_back(m_directiveBeingPreHandled);
|
||||
m_directiveBeingPreHandled.reset();
|
||||
}
|
||||
if (!m_handlingQueue.empty()) {
|
||||
m_cancelingQueue.insert(m_cancelingQueue.end(), m_handlingQueue.begin(), m_handlingQueue.end());
|
||||
m_handlingQueue.clear();
|
||||
m_wakeProcessingLoop.notify_one();
|
||||
}
|
||||
m_isHandlingDirective = false;
|
||||
}
|
||||
|
||||
} // namespace directiveSequencer
|
||||
} // namespace alexaClientSDK
|
|
@ -18,93 +18,237 @@
|
|||
#include <iostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "ADSL/DirectiveRouter.h"
|
||||
|
||||
/// String used to identify log entries that originated from this file.
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("DirectiveRouter");
|
||||
|
||||
/// Macro to create a LogEntry in-line using the TAG for this file.
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) ::alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
// TODO: ACSDK-179 Remove this (and migrate invocations of this to ACSDK_<LEVEL> invocations.
|
||||
#define ACSDK_LOG(expression) \
|
||||
do { \
|
||||
::alexaClientSDK::avsUtils::Logger::log(expression.c_str()); \
|
||||
} while (false)
|
||||
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> DirectiveRouter::setDirectiveHandlers(
|
||||
const DirectiveHandlerConfiguration& configuration) {
|
||||
DirectiveRouter::~DirectiveRouter() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> removedHandlers;
|
||||
|
||||
for (auto entry : configuration) {
|
||||
auto it = m_configuration.find(entry.first);
|
||||
if (!entry.second.first || entry.second.second == BlockingPolicy::NONE) {
|
||||
if (m_configuration.end() == it) {
|
||||
ACSDK_LOG(LX("setDirectiveHandlers").d("action", "ignoringRemovalOfDirectiveHandler")
|
||||
.d("namespace", entry.first.first).d("name", entry.first.second).d("reason", "notFound"));
|
||||
} else {
|
||||
ACSDK_LOG(LX("setDirectiveHandlers").d("action", "removingDirectiveHandler")
|
||||
.d("namespace", entry.first.first).d("name", entry.first.second));
|
||||
removedHandlers.insert(it->second.first);
|
||||
m_configuration.erase(it);
|
||||
}
|
||||
} else {
|
||||
if (m_configuration.end() == it) {
|
||||
ACSDK_LOG(LX("setDirectiveHandlers").d("action", "addingDirectiveHandler")
|
||||
.d("namespace", entry.first.first).d("name", entry.first.second));
|
||||
m_configuration[entry.first] = entry.second;
|
||||
} else if (it->second != entry.second) {
|
||||
if (it->second.first != entry.second.first) {
|
||||
removedHandlers.insert(it->second.first);
|
||||
}
|
||||
ACSDK_LOG(LX("setDirectiveHandlers").d("action","changingDirectiveHandler")
|
||||
.d("namespace", entry.first.first).d("name", entry.first.second));
|
||||
it->second = entry.second;
|
||||
}
|
||||
}
|
||||
for (auto item : m_handlerReferenceCounts) {
|
||||
item.first->onDeregistered();
|
||||
}
|
||||
|
||||
// Reduce removedHandlers to just those that have been completely removed. That is the set returned.
|
||||
for (auto entry : m_configuration) {
|
||||
auto it = removedHandlers.find(entry.second.first);
|
||||
if (removedHandlers.end() != it) {
|
||||
removedHandlers.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
ACSDK_LOG(LX("setDirectiveHandlers").d("action","removedHandlers").d("count", removedHandlers.size()));
|
||||
return removedHandlers;
|
||||
}
|
||||
|
||||
DirectiveHandlerAndBlockingPolicyPair DirectiveRouter::getDirectiveHandlerAndBlockingPolicy(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive) {
|
||||
if (!directive) {
|
||||
ACSDK_LOG(LX("getDirectiveHandlerAndBlockingPolicy() failed.").d("directive", directive));
|
||||
return DirectiveHandlerAndBlockingPolicyPair{nullptr, BlockingPolicy::NONE};
|
||||
bool DirectiveRouter::addDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
for (auto item : configuration) {
|
||||
if (!item.second) {
|
||||
ACSDK_ERROR(LX("addDirectiveHandlersFailed")
|
||||
.d("reason", "emptyHandlerAndPolicy")
|
||||
.d("namespace", item.first.nameSpace)
|
||||
.d("name", item.first.name));
|
||||
return false;
|
||||
}
|
||||
auto it = m_configuration.find(item.first);
|
||||
if (m_configuration.end() != it) {
|
||||
ACSDK_ERROR(LX("addDirectiveHandlersFailed")
|
||||
.d("reason", "alreadySet")
|
||||
.d("namespace", item.first.nameSpace)
|
||||
.d("name", item.first.name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
auto it = m_configuration.find(NamespaceAndNamePair(directive->getNamespace(), directive->getName()));
|
||||
|
||||
for (auto item : configuration) {
|
||||
m_configuration[item.first] = item.second;
|
||||
incrementHandlerReferenceCountLocked(item.second.handler);
|
||||
ACSDK_INFO(LX("addDirectiveHandlers")
|
||||
.d("action", "added")
|
||||
.d("namespace", item.first.nameSpace)
|
||||
.d("name", item.first.name)
|
||||
.d("handler", item.second.handler.get())
|
||||
.d("policy", item.second.policy));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveRouter::removeDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
|
||||
for (auto item : configuration) {
|
||||
auto it = m_configuration.find(item.first);
|
||||
if (m_configuration.end() == it || it->second != item.second) {
|
||||
ACSDK_ERROR(LX("removeDirectiveHandlersFailed")
|
||||
.d("reason", "notFound")
|
||||
.d("namespace", item.first.nameSpace)
|
||||
.d("name", item.first.name)
|
||||
.d("handler", item.second.handler.get())
|
||||
.d("policy", item.second.policy));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement reference counts. Unfortunately, a simple loop calling @c decrementHandlerReferenceCountLocked()
|
||||
* would create a race condition because that function temporarily releases @c m_mutex when a count goes to zero.
|
||||
* Instead, the operation is expanded here with the lock released once we know which handlers to notify.
|
||||
*/
|
||||
std::vector<std::shared_ptr<DirectiveHandlerInterface>> releasedHandlers;
|
||||
for (auto item : configuration) {
|
||||
m_configuration.erase(item.first);
|
||||
ACSDK_INFO(LX("removeDirectiveHandlers")
|
||||
.d("action", "removed")
|
||||
.d("namespace", item.first.nameSpace)
|
||||
.d("name", item.first.name)
|
||||
.d("handler", item.second.handler.get())
|
||||
.d("policy", item.second.policy));
|
||||
auto it = m_handlerReferenceCounts.find(item.second.handler);
|
||||
if (0 == --(it->second)) {
|
||||
releasedHandlers.push_back(item.second.handler);
|
||||
m_handlerReferenceCounts.erase(it);
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
for (auto handler : releasedHandlers) {
|
||||
ACSDK_INFO(LX("onDeregisteredCalled").d("handler", handler.get()));
|
||||
handler->onDeregistered();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveRouter::handleDirectiveImmediately(std::shared_ptr<avsCommon::AVSDirective> directive) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
auto handlerAndPolicy = getHandlerAndPolicyLocked(directive);
|
||||
if (!handlerAndPolicy) {
|
||||
ACSDK_WARN(LX("handleDirectiveImmediatelyFailed")
|
||||
.d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered"));
|
||||
return false;
|
||||
}
|
||||
ACSDK_INFO(LX("handleDirectiveImmediately").d("messageId", directive->getMessageId()).d("action", "calling"));
|
||||
HandlerCallScope scope(lock, this, handlerAndPolicy.handler);
|
||||
handlerAndPolicy.handler->handleDirectiveImmediately(directive);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveRouter::preHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
std::unique_ptr<DirectiveHandlerResultInterface> result) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
auto handlerAndPolicy = getHandlerAndPolicyLocked(directive);
|
||||
if (!handlerAndPolicy) {
|
||||
ACSDK_WARN(LX("preHandleDirectiveFailed")
|
||||
.d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered"));
|
||||
return false;
|
||||
}
|
||||
ACSDK_INFO(LX("preHandleDirective").d("messageId", directive->getMessageId()).d("action", "calling"));
|
||||
HandlerCallScope scope(lock, this, handlerAndPolicy.handler);
|
||||
handlerAndPolicy.handler->preHandleDirective(directive, std::move(result));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectiveRouter::handleDirective(std::shared_ptr<avsCommon::AVSDirective> directive, BlockingPolicy* policyOut) {
|
||||
if (!policyOut) {
|
||||
ACSDK_ERROR(LX("handleDirectiveFailed")
|
||||
.d("messageId", directive->getMessageId()).d("reason", "nullptrPolicyOut"));
|
||||
return false;
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
auto handlerAndPolicy = getHandlerAndPolicyLocked(directive);
|
||||
if (!handlerAndPolicy) {
|
||||
ACSDK_WARN(LX("handleDirectiveFailed")
|
||||
.d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered"));
|
||||
return false;
|
||||
}
|
||||
ACSDK_INFO(LX("handleDirective").d("messageId", directive->getMessageId()).d("action", "calling"));
|
||||
HandlerCallScope scope(lock, this, handlerAndPolicy.handler);
|
||||
auto result = handlerAndPolicy.handler->handleDirective(directive->getMessageId());
|
||||
if (result) {
|
||||
*policyOut = handlerAndPolicy.policy;
|
||||
} else {
|
||||
ACSDK_WARN(LX("messageIdNotRecognized")
|
||||
.d("handler", handlerAndPolicy.handler.get())
|
||||
.d("messageId", directive->getMessageId())
|
||||
.d("reason", "handleDirectiveReturnedFalse"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool DirectiveRouter::cancelDirective(std::shared_ptr<avsCommon::AVSDirective> directive) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
auto handlerAndPolicy = getHandlerAndPolicyLocked(directive);
|
||||
if (!handlerAndPolicy) {
|
||||
ACSDK_WARN(LX("cancelDirectiveFailed")
|
||||
.d("messageId", directive->getMessageId()).d("reason", "noHandlerRegistered"));
|
||||
return false;
|
||||
}
|
||||
ACSDK_INFO(LX("cancelDirective").d("messageId", directive->getMessageId()).d("action", "calling"));
|
||||
HandlerCallScope scope(lock, this, handlerAndPolicy.handler);
|
||||
handlerAndPolicy.handler->cancelDirective(directive->getMessageId());
|
||||
return true;
|
||||
}
|
||||
|
||||
DirectiveRouter::HandlerCallScope::HandlerCallScope(
|
||||
std::unique_lock<std::mutex>& lock,
|
||||
DirectiveRouter* router,
|
||||
std::shared_ptr<DirectiveHandlerInterface> handler) :
|
||||
// Parenthesis are used for initializing @c m_lock to work-around a bug in the C++ specification. see:
|
||||
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1288
|
||||
m_lock(lock), m_router{router}, m_handler{handler} {
|
||||
m_router->incrementHandlerReferenceCountLocked(m_handler);
|
||||
m_lock.unlock();
|
||||
}
|
||||
|
||||
DirectiveRouter::HandlerCallScope::~HandlerCallScope() {
|
||||
m_lock.lock();
|
||||
m_router->decrementHandlerReferenceCountLocked(m_lock, m_handler);
|
||||
}
|
||||
|
||||
HandlerAndPolicy DirectiveRouter::getHandlerAndPolicyLocked(std::shared_ptr<avsCommon::AVSDirective> directive) {
|
||||
if (!directive) {
|
||||
ACSDK_WARN(LX("getHandlerAndPolicyLockedFailed").d("reason", "nullptrDirective"));
|
||||
return HandlerAndPolicy();
|
||||
}
|
||||
auto it = m_configuration.find(NamespaceAndName(directive->getNamespace(), directive->getName()));
|
||||
if (m_configuration.end() == it) {
|
||||
return DirectiveHandlerAndBlockingPolicyPair{nullptr, BlockingPolicy::NONE};
|
||||
return HandlerAndPolicy();
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> DirectiveRouter::clear() {
|
||||
ACSDK_LOG(LX("clear"));
|
||||
std::set<std::shared_ptr<DirectiveHandlerInterface>> removedHandlers;
|
||||
for (auto entry : m_configuration) {
|
||||
removedHandlers.insert(entry.second.first);
|
||||
void DirectiveRouter::incrementHandlerReferenceCountLocked(std::shared_ptr<DirectiveHandlerInterface> handler){
|
||||
const auto it = m_handlerReferenceCounts.find(handler);
|
||||
if (it != m_handlerReferenceCounts.end()) {
|
||||
it->second++;
|
||||
} else {
|
||||
m_handlerReferenceCounts[handler] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveRouter::decrementHandlerReferenceCountLocked(
|
||||
std::unique_lock<std::mutex>& lock, std::shared_ptr<DirectiveHandlerInterface> handler) {
|
||||
const auto it = m_handlerReferenceCounts.find(handler);
|
||||
if (it != m_handlerReferenceCounts.end()) {
|
||||
if (0 == --(it->second)) {
|
||||
m_handlerReferenceCounts.erase(it);
|
||||
ACSDK_INFO(LX("onDeregisteredCalled").d("handler", handler.get()));
|
||||
lock.unlock();
|
||||
handler->onDeregistered();
|
||||
lock.lock();
|
||||
}
|
||||
} else {
|
||||
ACSDK_ERROR(LX("removeHandlerReferenceLockedFailed").d("reason", "handlerNotFound"));
|
||||
}
|
||||
m_configuration.clear();
|
||||
return removedHandlers;
|
||||
}
|
||||
|
||||
} // namespace adsl
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include <AVSCommon/ExceptionEncountered.h>
|
||||
|
@ -27,474 +26,123 @@
|
|||
|
||||
#include "ADSL/DirectiveSequencer.h"
|
||||
|
||||
/// String used to identify log entries that originated from this file.
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("DirectiveSequencer");
|
||||
|
||||
/// Macro to create a LogEntry in-line using the TAG for this file.
|
||||
#define LX(event) ::alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
// TODO: ACSDK-179 Remove this (and migrate invocations of this to ACSDK_<LEVEL> invocations.
|
||||
#define ACSDK_LOG(expression) \
|
||||
do { \
|
||||
::alexaClientSDK::avsUtils::Logger::log(expression.c_str()); \
|
||||
} while (false)
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
using namespace avsCommon;
|
||||
|
||||
namespace adsl {
|
||||
|
||||
std::shared_ptr<DirectiveSequencer> DirectiveSequencer::create(
|
||||
std::unique_ptr<DirectiveSequencerInterface> DirectiveSequencer::create(
|
||||
std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> exceptionSender) {
|
||||
std::shared_ptr<DirectiveSequencer> sequencer(new DirectiveSequencer(exceptionSender));
|
||||
sequencer->init(sequencer);
|
||||
return sequencer;
|
||||
if (!exceptionSender) {
|
||||
ACSDK_INFO(LX("createFailed").d("reason", "nullptrExceptionSender"));
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<DirectiveSequencerInterface>(new DirectiveSequencer(exceptionSender));
|
||||
}
|
||||
|
||||
DirectiveSequencer::~DirectiveSequencer() {
|
||||
ACSDK_LOG(LX("~DirectiveSequencer()"));
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void DirectiveSequencer::shutdown() {
|
||||
ACSDK_LOG(LX("shutdown()"));
|
||||
setIsStopping();
|
||||
ACSDK_INFO(LX("shutdown"));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isShuttingDown = true;
|
||||
m_wakeReceivingLoop.notify_one();
|
||||
}
|
||||
if (m_receivingThread.joinable()) {
|
||||
m_receivingThread.join();
|
||||
}
|
||||
if (m_processingThread.joinable()) {
|
||||
m_processingThread.join();
|
||||
}
|
||||
clearDirectiveRouter();
|
||||
m_directiveProcessor->shutdown();
|
||||
}
|
||||
|
||||
std::future<bool> DirectiveSequencer::setDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) {
|
||||
ACSDK_LOG(LX("setDirectiveHandlers()"));
|
||||
auto newPromise = std::make_shared<std::promise<bool>>();
|
||||
std::lock_guard<std::mutex> lock(m_processingMutex);
|
||||
if (m_isStopping) {
|
||||
ACSDK_LOG(LX("setDirectiveHandlers()").d("m_isStopping", true).d("result", "ignored"));
|
||||
newPromise->set_value(false);
|
||||
return newPromise->get_future();
|
||||
}
|
||||
if (m_isSettingDirectiveHandlers) {
|
||||
newPromise->set_value(false);
|
||||
return newPromise->get_future();
|
||||
}
|
||||
|
||||
m_setDirectiveHandlersPromise = newPromise;
|
||||
m_newDirectiveHandlerConfiguration = configuration;
|
||||
m_isSettingDirectiveHandlers = true;
|
||||
m_receivingNotifier.notify_one();
|
||||
m_dialogRequestId.clear();
|
||||
cancelPreHandledDirectivesLocked();
|
||||
return m_setDirectiveHandlersPromise->get_future();
|
||||
bool DirectiveSequencer::addDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) {
|
||||
return m_directiveRouter.addDirectiveHandlers(configuration);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::setDialogRequestId(const std::string& newDialogRequestId) {
|
||||
ACSDK_LOG(LX("setDialogRequestId()").d("newDialogRequestId", newDialogRequestId));
|
||||
if (m_isStopping) {
|
||||
ACSDK_LOG(LX("setDialogRequestId()").d("m_isStopping", true).d("result", "ignored"));
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_processingMutex);
|
||||
if (newDialogRequestId != m_dialogRequestId) {
|
||||
m_dialogRequestId = newDialogRequestId;
|
||||
cancelPreHandledDirectivesLocked();
|
||||
} else {
|
||||
ACSDK_LOG(LX("setDialogRequestId() same as old value").d("result", "ignored"));
|
||||
}
|
||||
bool DirectiveSequencer::removeDirectiveHandlers(const DirectiveHandlerConfiguration& configuration) {
|
||||
return m_directiveRouter.removeDirectiveHandlers(configuration);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::setDialogRequestId(const std::string& dialogRequestId) {
|
||||
m_directiveProcessor->setDialogRequestId(dialogRequestId);
|
||||
}
|
||||
|
||||
bool DirectiveSequencer::onDirective(std::shared_ptr<AVSDirective> directive) {
|
||||
if (!directive) {
|
||||
ACSDK_LOG(LX("onDirective():").d("directive", "(nullptr)").d("result", "ignored"));
|
||||
ACSDK_ERROR(LX("onDirectiveFailed").d("action", "ignored").d("reason", "nullptrDirective"));
|
||||
return false;
|
||||
}
|
||||
if (m_isStopping) {
|
||||
ACSDK_LOG(LX("onDirective()").d("directive", directive).d("m_isStopping", true).d("result", "ignored"));
|
||||
sendExceptionEncountered(directive, ExceptionErrorType::INTERNAL_ERROR, "DirectiveSequencer stopping.");
|
||||
return true;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(m_receivingMutex);
|
||||
auto handlerAndPolcy = m_directiveRouter.getDirectiveHandlerAndBlockingPolicy(directive);
|
||||
if (!handlerAndPolcy.first || BlockingPolicy::NONE == handlerAndPolcy.second) {
|
||||
ACSDK_LOG(LX("onDirective() no handler or policy for this directive.")
|
||||
.d("directive", directive).d("result", "ignored"));
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_isShuttingDown) {
|
||||
ACSDK_WARN(LX("onDirectiveFailed")
|
||||
.d("directive", directive->getHeaderAsString())
|
||||
.d("action", "ignored")
|
||||
.d("reason", "isShuttingDown"));
|
||||
return false;
|
||||
}
|
||||
ACSDK_LOG(LX("onDirective()").d("directive", directive));
|
||||
ACSDK_INFO(LX("onDirective").d("directive", directive->getHeaderAsString()));
|
||||
m_receivingQueue.push_back(directive);
|
||||
m_receivingNotifier.notify_one();
|
||||
m_wakeReceivingLoop.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
DirectiveSequencer::DirectiveHandlerResult::DirectiveHandlerResult(
|
||||
std::weak_ptr<DirectiveSequencer> directiveSequencer,
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
BlockingPolicy blockingPolicy) :
|
||||
m_directiveSequencer(directiveSequencer), m_directive(directive), m_blockingPolicy(blockingPolicy) {
|
||||
}
|
||||
|
||||
void DirectiveSequencer::DirectiveHandlerResult::setCompleted() {
|
||||
if (m_blockingPolicy != BlockingPolicy::BLOCKING) {
|
||||
ACSDK_LOG(LX("DirectiveHandlerResult::setCompleted() for non-BLOCKING directive.")
|
||||
.d("messageId", m_directive->getMessageId()));
|
||||
}
|
||||
auto sequencer = m_directiveSequencer.lock();
|
||||
if (sequencer) {
|
||||
sequencer->onHandlingCompleted(m_directive);
|
||||
} else {
|
||||
ACSDK_LOG(LX("DirectiveHandlerResult::setCompleted() lock() failed")
|
||||
.d("messageId", m_directive->getMessageId()));
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::DirectiveHandlerResult::setFailed(const std::string& description) {
|
||||
auto sequencer = m_directiveSequencer.lock();
|
||||
if (sequencer) {
|
||||
sequencer->onHandlingFailed(m_directive, description);
|
||||
} else {
|
||||
ACSDK_LOG(LX("DirectiveHandlerResult::setFailed() lock() failed").d("messageId", m_directive->getMessageId()));
|
||||
}
|
||||
}
|
||||
|
||||
DirectiveSequencer::DirectiveSequencer(
|
||||
std::shared_ptr<avsCommon::ExceptionEncounteredSenderInterface> exceptionSender) :
|
||||
m_mutex{},
|
||||
m_exceptionSender{exceptionSender},
|
||||
m_isStopping{false},
|
||||
m_handlingReceivedDirective{false},
|
||||
m_isSettingDirectiveHandlers{false} {
|
||||
m_isShuttingDown{false} {
|
||||
m_directiveProcessor = std::make_shared<DirectiveProcessor>(&m_directiveRouter);
|
||||
m_receivingThread = std::thread(&DirectiveSequencer::receivingLoop, this);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::init(std::weak_ptr<DirectiveSequencer> thisDirectiveSequencer) {
|
||||
m_thisDirectiveSequencer = thisDirectiveSequencer;
|
||||
m_receivingThread = std::thread(&DirectiveSequencer::receiveDirectivesLoop, this);
|
||||
m_processingThread = std::thread(&DirectiveSequencer::processDirectivesLoop, this);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::setIsStopping() {
|
||||
std::lock_guard<std::mutex> lock(m_receivingMutex);
|
||||
{
|
||||
// Although @c m_isStopping is atomic, that is not quite good enough to publish this modification to a
|
||||
// waiting thread. We need to hold the lock as well.
|
||||
// @see http://en.cppreference.com/w/cpp/thread/condition_variable
|
||||
std::lock_guard<std::mutex> lock(m_processingMutex);
|
||||
m_isStopping = true;
|
||||
}
|
||||
m_receivingNotifier.notify_one();
|
||||
m_processingNotifier.notify_one();
|
||||
}
|
||||
|
||||
void DirectiveSequencer::clearDirectiveRouter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_processingMutex);
|
||||
m_newDirectiveHandlerConfiguration.clear();
|
||||
m_isSettingDirectiveHandlers = false;
|
||||
}
|
||||
auto removedHandlers = m_directiveRouter.clear();
|
||||
ACSDK_LOG(LX("clearDirectiveRouter").d("action", "shutDownHandlers").d("count", removedHandlers.size()));
|
||||
for (auto handler : removedHandlers) {
|
||||
handler->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::onHandlingCompleted(std::shared_ptr<AVSDirective> directive) {
|
||||
removeDirectiveFromProcessing("onHandlingCompleted()", directive, false);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::onHandlingFailed(std::shared_ptr<AVSDirective> directive, const std::string& description) {
|
||||
avsUtils::logger::LogEntryStream text;
|
||||
text << "onHandlingFailed('" << description << "')";
|
||||
removeDirectiveFromProcessing(text.c_str(), directive, true);
|
||||
}
|
||||
|
||||
void DirectiveSequencer::receiveDirectivesLoop() {
|
||||
while (!m_isStopping) {
|
||||
auto directive = receiveDirective();
|
||||
if (directive && shouldProcessDirective(directive)) {
|
||||
queueDirectiveForProcessing();
|
||||
}
|
||||
}
|
||||
ACSDK_LOG(LX("receiveDirectivesLoop(): stopping."));
|
||||
}
|
||||
|
||||
std::shared_ptr<avsCommon::AVSDirective> DirectiveSequencer::receiveDirective() {
|
||||
auto wakeReceivingThread = [this]() {
|
||||
return m_isStopping || !m_receivingQueue.empty() || m_isSettingDirectiveHandlers;
|
||||
void DirectiveSequencer::receivingLoop() {
|
||||
auto wake = [this]() {
|
||||
return !m_receivingQueue.empty() || m_isShuttingDown;
|
||||
};
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(m_receivingMutex);
|
||||
m_receivingNotifier.wait(lock, wakeReceivingThread);
|
||||
if (m_receivingQueue.empty()) {
|
||||
lock.unlock();
|
||||
maybeSetDirectiveHandlers();
|
||||
lock.lock();
|
||||
if (m_isStopping && m_receivingQueue.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
auto directive = m_receivingQueue.front();
|
||||
m_receivingQueue.pop_front();
|
||||
if (m_isSettingDirectiveHandlers) {
|
||||
ACSDK_LOG(LX("receiveDirective() while setting directive handlers.")
|
||||
.d("messageId", directive->getMessageId()).d("result", "ignored"));
|
||||
sendExceptionEncountered(
|
||||
directive, ExceptionErrorType::INTERNAL_ERROR, "Setting directive handlers.");
|
||||
} else {
|
||||
return directive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectiveSequencer::shouldProcessDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive) {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
auto handlerAndPolicy = m_directiveRouter.getDirectiveHandlerAndBlockingPolicy(directive);
|
||||
if (handlerAndPolicy.first) {
|
||||
if (directive->getDialogRequestId().empty()) {
|
||||
m_handlingReceivedDirective = true;
|
||||
lock.unlock();
|
||||
handlerAndPolicy.first->handleDirectiveImmediately(directive);
|
||||
lock.lock();
|
||||
m_handlingReceivedDirective = false;
|
||||
return false;
|
||||
} else if (directive->getDialogRequestId() == m_dialogRequestId) {
|
||||
m_directiveBeingPreHandled = directive;
|
||||
lock.unlock();
|
||||
handlerAndPolicy.first->preHandleDirective(directive, std::make_shared<DirectiveHandlerResult>(
|
||||
m_thisDirectiveSequencer, directive, handlerAndPolicy.second));
|
||||
return true;
|
||||
} else {
|
||||
ACSDK_LOG(LX("shouldProcessDirective(): dialogRequestId does not match.")
|
||||
.d("messageId", directive->getMessageId())
|
||||
.d("m_dialogRequestId", m_dialogRequestId)
|
||||
.d("dialogRequestId", directive->getDialogRequestId()));
|
||||
sendExceptionEncountered(
|
||||
directive, ExceptionErrorType::INTERNAL_ERROR, "dialogRequestId does not match.");
|
||||
}
|
||||
} else {
|
||||
ACSDK_LOG(LX("shouldProcessDirective(): No handler for this directive (this should never happen).")
|
||||
.d("messageId", directive->getMessageId()));
|
||||
sendExceptionEncountered(
|
||||
directive, ExceptionErrorType::UNSUPPORTED_OPERATION, "No handler for this directive.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DirectiveSequencer::queueDirectiveForProcessing() {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
if (!m_directiveBeingPreHandled) {
|
||||
return;
|
||||
}
|
||||
// Check that @c setDialogRequestId() was not called while @c m_processingMutex was unlocked when
|
||||
// @c preHandleDirective() was called. If it was, queue the @c AVSDirective to be cancelled.
|
||||
if (m_directiveBeingPreHandled->getDialogRequestId() == m_dialogRequestId) {
|
||||
ACSDK_LOG(LX("queueDirectiveForProcessing(): push to m_handlingQueue")
|
||||
.d("messageId", m_directiveBeingPreHandled->getMessageId()));
|
||||
m_handlingQueue.push_back(m_directiveBeingPreHandled);
|
||||
} else {
|
||||
ACSDK_LOG(LX("queueDirectiveForProcessing(): push to m_cancelingQueue")
|
||||
.d("messageId", m_directiveBeingPreHandled->getMessageId()));
|
||||
m_cancellingQueue.push_back(m_directiveBeingPreHandled);
|
||||
}
|
||||
m_directiveBeingPreHandled.reset();
|
||||
m_processingNotifier.notify_one();
|
||||
}
|
||||
|
||||
void DirectiveSequencer::processDirectivesLoop() {
|
||||
auto wakeProcessingThread = [this]() {
|
||||
return
|
||||
m_isStopping ||
|
||||
!m_cancellingQueue.empty() ||
|
||||
(!m_handlingQueue.empty() && !m_isHandlingDirective) ||
|
||||
m_isSettingDirectiveHandlers ||
|
||||
!m_removedHandlers.empty();
|
||||
};
|
||||
|
||||
while (true) {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
m_processingNotifier.wait(lock, wakeProcessingThread);
|
||||
shutdownRemovedHandlersLocked(lock);
|
||||
lock.unlock();
|
||||
if (shouldProcessingStop()) {
|
||||
m_wakeReceivingLoop.wait(lock, wake);
|
||||
if (m_isShuttingDown) {
|
||||
break;
|
||||
}
|
||||
cancelDirectives();
|
||||
handleDirective();
|
||||
}
|
||||
ACSDK_LOG(LX("processDirectivesLoop(): stopping."));
|
||||
}
|
||||
|
||||
void DirectiveSequencer::shutdownRemovedHandlersLocked(std::unique_lock<std::mutex>& lock) {
|
||||
while (!m_removedHandlers.empty()) {
|
||||
auto it = m_removedHandlers.begin();
|
||||
auto handler = *it;
|
||||
m_removedHandlers.erase(it);
|
||||
lock.unlock();
|
||||
handler->shutdown();
|
||||
lock.lock();
|
||||
receiveDirectiveLocked(lock);
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectiveSequencer::shouldProcessingStop() {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
if (m_isStopping) {
|
||||
if (!m_handlingQueue.empty()) {
|
||||
cancelPreHandledDirectivesLocked();
|
||||
}
|
||||
if (m_directiveBeingPreHandled) {
|
||||
ACSDK_LOG(LX("shouldProcessingStop(): push m_directiveBeingPreHandled to m_cancelingQueue")
|
||||
.d("messageId", m_directiveBeingPreHandled->getMessageId()));
|
||||
m_cancellingQueue.push_back(m_directiveBeingPreHandled);
|
||||
m_directiveBeingPreHandled.reset();
|
||||
}
|
||||
if (m_cancellingQueue.empty()) {
|
||||
lock.unlock();
|
||||
maybeSetDirectiveHandlers();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DirectiveSequencer::cancelDirectives() {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
while (!m_cancellingQueue.empty()) {
|
||||
auto directive = m_cancellingQueue.front();
|
||||
m_cancellingQueue.pop_front();
|
||||
auto handler = m_directiveRouter.getDirectiveHandlerAndBlockingPolicy(directive).first;
|
||||
if (handler) {
|
||||
lock.unlock();
|
||||
handler->cancelDirective(directive->getMessageId());
|
||||
lock.lock();
|
||||
} else {
|
||||
ACSDK_LOG(LX("cancelDirective(): No handler for directive (this should never happen).")
|
||||
.d("messageId", directive->getMessageId()));
|
||||
sendExceptionEncountered(
|
||||
directive, ExceptionErrorType::UNSUPPORTED_OPERATION, "No handler for this directive.");
|
||||
}
|
||||
}
|
||||
if (m_handlingQueue.empty()) {
|
||||
lock.unlock();
|
||||
maybeSetDirectiveHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::handleDirective() {
|
||||
std::unique_lock<std::mutex> lock(m_processingMutex);
|
||||
if (m_isHandlingDirective || m_handlingQueue.empty()) {
|
||||
return;
|
||||
}
|
||||
auto directive = m_handlingQueue.front();
|
||||
auto handlerAndPolicy = m_directiveRouter.getDirectiveHandlerAndBlockingPolicy(directive);
|
||||
if (handlerAndPolicy.first) {
|
||||
switch (handlerAndPolicy.second) {
|
||||
case BlockingPolicy::NON_BLOCKING:
|
||||
m_handlingQueue.pop_front();
|
||||
lock.unlock();
|
||||
handlerAndPolicy.first->handleDirective(directive->getMessageId());
|
||||
break;
|
||||
case BlockingPolicy::BLOCKING:
|
||||
m_isHandlingDirective = true;
|
||||
lock.unlock();
|
||||
handlerAndPolicy.first->handleDirective(directive->getMessageId());
|
||||
break;
|
||||
default:
|
||||
ACSDK_LOG(LX("processDirectivesLoop(): Unhandled BlockingPolicy: ")
|
||||
.d("directive", directive).d("policy", handlerAndPolicy.second));
|
||||
m_handlingQueue.pop_front();
|
||||
lock.unlock();
|
||||
handlerAndPolicy.first->cancelDirective(directive->getMessageId());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ACSDK_LOG(LX("handleDirective(): No handler for directive (this should never happen).")
|
||||
.d("messageId", directive->getMessageId()));
|
||||
sendExceptionEncountered(directive, ExceptionErrorType::INTERNAL_ERROR, "No handler for this directive.");
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::removeDirectiveFromProcessing(
|
||||
const std::string& caller, std::shared_ptr<AVSDirective> directive, bool cancelAllIfFound) {
|
||||
if (!directive) {
|
||||
ACSDK_LOG(LX("removeDirectiveFromProcessing() failed.").d("directive", "(nullptr)"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto matchFunction = [this, directive](std::shared_ptr<AVSDirective> element) {
|
||||
return element == directive;
|
||||
};
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_processingMutex);
|
||||
ACSDK_LOG(LX("removeDirectiveFromProcessing()")
|
||||
.d("caller", caller).d("messageId", directive->getMessageId()).d("cancelAllIfFound", cancelAllIfFound));
|
||||
auto it = std::find_if(m_handlingQueue.begin(), m_handlingQueue.end(), matchFunction);
|
||||
if (m_isHandlingDirective && it == m_handlingQueue.begin()) {
|
||||
m_isHandlingDirective = false;
|
||||
m_handlingQueue.pop_front();
|
||||
m_processingNotifier.notify_one();
|
||||
} else if (it != m_handlingQueue.end()) {
|
||||
m_handlingQueue.erase(it);
|
||||
} else if (m_directiveBeingPreHandled == directive) {
|
||||
m_directiveBeingPreHandled.reset();
|
||||
} else {
|
||||
it = std::find_if(m_cancellingQueue.begin(), m_cancellingQueue.end(), matchFunction);
|
||||
if (it != m_cancellingQueue.end()) {
|
||||
m_cancellingQueue.erase(it);
|
||||
} else {
|
||||
ACSDK_LOG(LX("removeDirectiveFromProcessing() directive NOT found."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (cancelAllIfFound) {
|
||||
m_dialogRequestId.clear();
|
||||
cancelPreHandledDirectivesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::cancelPreHandledDirectivesLocked() {
|
||||
m_cancellingQueue.insert(m_cancellingQueue.end(), m_handlingQueue.begin(), m_handlingQueue.end());
|
||||
m_handlingQueue.clear();
|
||||
m_isHandlingDirective = false;
|
||||
m_processingNotifier.notify_one();
|
||||
}
|
||||
|
||||
void DirectiveSequencer::maybeSetDirectiveHandlers() {
|
||||
std::lock_guard<std::mutex> receivingLock(m_receivingMutex);
|
||||
void DirectiveSequencer::receiveDirectiveLocked(std::unique_lock<std::mutex> &lock) {
|
||||
if (m_receivingQueue.empty()) {
|
||||
std::lock_guard<std::mutex> processingLock(m_processingMutex);
|
||||
bool isDirectiveRouterInUse =
|
||||
m_handlingReceivedDirective ||
|
||||
m_directiveBeingPreHandled ||
|
||||
!m_cancellingQueue.empty() ||
|
||||
!m_handlingQueue.empty();
|
||||
|
||||
if (m_isSettingDirectiveHandlers && !isDirectiveRouterInUse) {
|
||||
ACSDK_LOG(LX("maybeSetDirectiveHandlers() completed."));
|
||||
auto removedHandlers = m_directiveRouter.setDirectiveHandlers(m_newDirectiveHandlerConfiguration);
|
||||
m_removedHandlers.insert(removedHandlers.begin(), removedHandlers.end());
|
||||
m_processingNotifier.notify_one();
|
||||
m_newDirectiveHandlerConfiguration.clear();
|
||||
m_isSettingDirectiveHandlers = false;
|
||||
m_setDirectiveHandlersPromise->set_value(true);
|
||||
m_setDirectiveHandlersPromise.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DirectiveSequencer::sendExceptionEncountered(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
avsCommon::ExceptionErrorType error,
|
||||
const std::string& message) {
|
||||
ACSDK_LOG(LX("sendExceptionEncountered()")
|
||||
.d("messageId", directive->getMessageId()).d("error", error).d("message", message));
|
||||
if (m_exceptionSender) {
|
||||
m_exceptionSender->sendExceptionEncountered(directive->getUnparsedDirective(), error, message);
|
||||
auto directive = m_receivingQueue.front();
|
||||
m_receivingQueue.pop_front();
|
||||
lock.unlock();
|
||||
bool handled = false;
|
||||
if (directive->getDialogRequestId().empty()) {
|
||||
handled = m_directiveRouter.handleDirectiveImmediately(directive);
|
||||
} else {
|
||||
handled = m_directiveProcessor->onDirective(directive);
|
||||
}
|
||||
if (!handled) {
|
||||
ACSDK_INFO(LX("sendingExceptionEncountered").d("messageId", directive->getMessageId()));
|
||||
m_exceptionSender->sendExceptionEncountered(
|
||||
directive->getUnparsedDirective(),
|
||||
avsCommon::ExceptionErrorType::UNSUPPORTED_OPERATION,
|
||||
"Unsupported operation");
|
||||
}
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
} // namespace directiveSequencer
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* HandlerAndPolicy.cpp
|
||||
*
|
||||
* Copyright 2017 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 <tuple>
|
||||
|
||||
#include "ADSL/HandlerAndPolicy.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
HandlerAndPolicy::HandlerAndPolicy() : policy{BlockingPolicy::NONE} {
|
||||
}
|
||||
|
||||
HandlerAndPolicy::HandlerAndPolicy(std::shared_ptr<DirectiveHandlerInterface> handlerIn, BlockingPolicy policyIn) :
|
||||
handler{handlerIn}, policy{policyIn} {
|
||||
}
|
||||
|
||||
HandlerAndPolicy::operator bool () const {
|
||||
return handler && (policy != BlockingPolicy::NONE);
|
||||
}
|
||||
|
||||
bool operator == (const HandlerAndPolicy& lhs, const HandlerAndPolicy& rhs) {
|
||||
return std::tie(lhs.handler, lhs.policy) == std::tie(rhs.handler, rhs.policy);
|
||||
}
|
||||
|
||||
bool operator != (const HandlerAndPolicy& lhs, const HandlerAndPolicy& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -62,7 +62,7 @@ static void sendExceptionEncounteredHelper(
|
|||
}
|
||||
|
||||
MessageInterpreter::MessageInterpreter(std::shared_ptr<ExceptionEncounteredSenderInterface> exceptionEncounteredSender,
|
||||
std::shared_ptr<DirectiveSequencer> directiveSequencer):
|
||||
std::shared_ptr<DirectiveSequencerInterface> directiveSequencer):
|
||||
m_exceptionEncounteredSender{exceptionEncounteredSender},
|
||||
m_directiveSequencer{directiveSequencer} {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* NamespaceAndName.cpp
|
||||
*
|
||||
* Copyright 2017 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 <tuple>
|
||||
|
||||
#include <AVSCommon/functional/hash.h>
|
||||
|
||||
#include "ADSL/NamespaceAndName.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
NamespaceAndName::NamespaceAndName(const std::string& nameSpaceIn, const std::string& nameIn) :
|
||||
nameSpace{nameSpaceIn}, name{nameIn} {
|
||||
}
|
||||
|
||||
bool operator == (const NamespaceAndName& lhs, const NamespaceAndName& rhs) {
|
||||
return std::tie(lhs.nameSpace, lhs.name) == std::tie(rhs.nameSpace, rhs.name);
|
||||
}
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
namespace std {
|
||||
|
||||
size_t hash<alexaClientSDK::adsl::NamespaceAndName>::operator()(
|
||||
const alexaClientSDK::adsl::NamespaceAndName& in) const {
|
||||
std::size_t seed = 0;
|
||||
alexaClientSDK::avsCommon::functional::hashCombine(seed, in.nameSpace);
|
||||
alexaClientSDK::avsCommon::functional::hashCombine(seed, in.name);
|
||||
return seed;
|
||||
};
|
||||
|
||||
} // namespace std
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* MockDirectiveSequencer.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_TEST_ADSL_MOCK_DIRECTIVE_SEQUENCER_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_TEST_ADSL_MOCK_DIRECTIVE_SEQUENCER_H_
|
||||
|
||||
#include <AVSCommon/AVSDirective.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ADSL/DirectiveSequencerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
/**
|
||||
* The mock class that implements the DirectiveSequencerInterface.
|
||||
*/
|
||||
class MockDirectiveSequencer : public DirectiveSequencerInterface {
|
||||
public:
|
||||
MOCK_METHOD0(shutdown,void());
|
||||
|
||||
MOCK_METHOD1(addDirectiveHandlers, bool(const DirectiveHandlerConfiguration& configuration));
|
||||
|
||||
MOCK_METHOD1(removeDirectiveHandlers, bool(const DirectiveHandlerConfiguration& configuration));
|
||||
|
||||
MOCK_METHOD1(setDialogRequestId, void(const std::string& dialogRequestId));
|
||||
|
||||
MOCK_METHOD1(onDirective, bool(std::shared_ptr<avsCommon::AVSDirective> directive));
|
||||
};
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_TEST_ADSL_MOCK_DIRECTIVE_SEQUENCER_H_
|
|
@ -1,3 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
||||
|
||||
discover_unit_tests("${ADSL_INCLUDE_DIRS}" ADSL)
|
||||
add_subdirectory("common")
|
||||
|
||||
set(ADSL_TEST_LIBS ADSL ADSLTestCommon)
|
||||
discover_unit_tests("${AVSCommon_SOURCE_DIR}/test" "${ADSL_TEST_LIBS}")
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* DirectiveProcessorTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 DirectiveProcessorTest.cpp
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/DirectiveProcessor.h"
|
||||
#include "MockAttachmentManager.h"
|
||||
#include "MockDirectiveHandler.h"
|
||||
#include "MockDirectiveHandlerResult.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
using namespace avsCommon;
|
||||
|
||||
/// Generic messageId used for tests.
|
||||
static const std::string MESSAGE_ID_0_0("Message_0_0");
|
||||
|
||||
/// Generic MessageId used for test.
|
||||
static const std::string MESSAGE_ID_0_1("Message_0_1");
|
||||
|
||||
/// Generic MessageId used for tests.
|
||||
static const std::string MESSAGE_ID_1_0("Message_1_0");
|
||||
|
||||
/// Generic DialogRequestId used for tests.
|
||||
static const std::string DIALOG_REQUEST_ID_0("DialogRequestId_0");
|
||||
|
||||
/// Generic DialogRequestId used for tests.
|
||||
static const std::string DIALOG_REQUEST_ID_1("DialogRequestId_1");
|
||||
|
||||
/// An unparsed directive for test.
|
||||
static const std::string UNPARSED_DIRECTIVE("unparsedDirectiveForTest");
|
||||
|
||||
/// A paylaod for test;
|
||||
static const std::string PAYLOAD_TEST("payloadForTest");
|
||||
|
||||
/// A generic namespace string for tests.
|
||||
static const std::string NAMESPACE_0("namespace_0");
|
||||
|
||||
/// A generic namespace string for tests.
|
||||
static const std::string NAMESPACE_1("namespace_1");
|
||||
|
||||
/// A generic name string for tests.
|
||||
static const std::string NAME_0("name_0");
|
||||
|
||||
/// A generic name string for tests.
|
||||
static const std::string NAME_1("name_1");
|
||||
|
||||
/// Namespace and name combination for tests.
|
||||
#define NAMESPACE_AND_NAME_0_0 NAMESPACE_0, NAME_0
|
||||
|
||||
/// Namespace and name combination (changing name this time) for tests.
|
||||
#define NAMESPACE_AND_NAME_0_1 NAMESPACE_0, NAME_1
|
||||
|
||||
/// Namespace and name combination (changing namespace this time) for tests.
|
||||
#define NAMESPACE_AND_NAME_1_0 NAMESPACE_1, NAME_0
|
||||
|
||||
/**
|
||||
* DirectiveRouterTest
|
||||
*/
|
||||
class DirectiveProcessorTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override;
|
||||
|
||||
/// A DirectiveRouter instance to test with.
|
||||
std::shared_ptr<DirectiveRouter> m_router;
|
||||
|
||||
/// A DirectiveProcessor instance to test with.
|
||||
std::shared_ptr<DirectiveProcessor> m_processor;
|
||||
|
||||
/// Mock AttachmentManager with which to create directives.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler0;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler1;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler2;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_0_0;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_0_1;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_1_0;
|
||||
};
|
||||
|
||||
void DirectiveProcessorTest::SetUp() {
|
||||
m_router = std::make_shared<DirectiveRouter>();
|
||||
m_processor = std::make_shared<DirectiveProcessor>(m_router.get());
|
||||
m_attachmentManager = std::make_shared<NiceMock<MockAttachmentManager>>();
|
||||
|
||||
m_handler0 = MockDirectiveHandler::create();
|
||||
m_handler1 = MockDirectiveHandler::create();
|
||||
m_handler2 = MockDirectiveHandler::create();
|
||||
|
||||
auto avsMessageHeader_0_0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_0_0, MESSAGE_ID_0_0, DIALOG_REQUEST_ID_0);
|
||||
m_directive_0_0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_0_0, PAYLOAD_TEST, m_attachmentManager);
|
||||
auto avsMessageHeader_0_1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_0_1, MESSAGE_ID_0_1, DIALOG_REQUEST_ID_0);
|
||||
m_directive_0_1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_0_1, PAYLOAD_TEST, m_attachmentManager);
|
||||
auto avsMessageHeader_1_0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_1_0, MESSAGE_ID_1_0, DIALOG_REQUEST_ID_1);
|
||||
m_directive_1_0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_1_0, PAYLOAD_TEST, m_attachmentManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a nullptr @c AVSDirective. Expect that it is ignored and a failure status (false) is returned.
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testNullptrDirective) {
|
||||
ASSERT_FALSE(m_processor->onDirective(nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a @c DirectiveHandler and set the @c dialogRequestId. Send a @c AVSDirective that the handler
|
||||
* registered for, but with a @c dialogRequestId that does not match. Expect that @c onDirective()
|
||||
* returns true (because the handler was registered) but that none of the handler methods are called
|
||||
* (because directives with the wrong @c dialogRequestID are dropped).
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testWrongDialogRequestId) {
|
||||
ASSERT_TRUE(m_router->addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(m_directive_0_0, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_0_0)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a @c NON_BLOCKING @c DirectiveHandler. Send an @c AVSDirective that matches the registered handler.
|
||||
* Expect that @c preHandleDirective() and @c handleDirective() are called.
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testSendNonBlocking) {
|
||||
ASSERT_TRUE(m_router->addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(m_directive_0_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_0_0)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_0));
|
||||
|
||||
ASSERT_TRUE(m_handler0->waitUntilCompleted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sending a blocking and then a non-blocking directive. Expect that @c preHandleDirective() and
|
||||
* @c handleDirective() is called for each.
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testSendBlockingThenNonBlocking) {
|
||||
ASSERT_TRUE(m_router->addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(m_directive_0_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_0_0)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(m_directive_0_1, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(MESSAGE_ID_0_1)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_0));
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_1));
|
||||
|
||||
ASSERT_TRUE(m_handler1->waitUntilCompleted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler for two @c AVSDirectives. Send an @c AVSDirective for which no handler is registered.
|
||||
* Then send an @c AVSDirective with the same @c dialogRequestId. Expect that the first @c AVSDirective will
|
||||
* be dropped (and @c onDirective() will return false) because there is no handler. Expect the second directive
|
||||
* to be handled, including @c preHandleDirective() and @c handleDirective() calls. After @c handleDirective()
|
||||
* has been called, set the @c dialogRequestId and send an @c AVSDirective for which the other handler was
|
||||
* registered. Expect that the last directive is handled as well.
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testOnUnregisteredDirective) {
|
||||
ASSERT_TRUE(m_router->addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_1_0}, {m_handler2, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(m_directive_0_1, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(MESSAGE_ID_0_1)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), preHandleDirective(m_directive_1_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirective(MESSAGE_ID_1_0)).Times(1);
|
||||
EXPECT_CALL(*(m_handler2.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_FALSE(m_processor->onDirective(m_directive_0_0));
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_1));
|
||||
ASSERT_TRUE(m_handler1->waitUntilCompleted());
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_1_0));
|
||||
ASSERT_TRUE(m_handler2->waitUntilCompleted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a long running and @c BLOCKING @c DirectiveHandler for one @c AVSDirective and a short
|
||||
* running @c DirectiveHandler for two more @c AVSDirectives. Send the long running @c AVSDirective
|
||||
* followed by a short running one. Wait until the long running @c AVSDirective has entered
|
||||
* handleDirective() and the short running @c AVSDirective has entered preHandleDirective().
|
||||
* then change the @c dialogRequestId() and send a final @c AVSDirective with the new
|
||||
* @c dialogRequestId(). Expect the first two @c AVSDirectives to be cancelled and expect the
|
||||
* final @c AVSDirective to be processed normally.
|
||||
*/
|
||||
TEST_F(DirectiveProcessorTest, testSetDialogRequestIdCancelsOutstandingDirectives) {
|
||||
|
||||
auto longRunningHandler = MockDirectiveHandler::create(MockDirectiveHandler::DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
ASSERT_TRUE(m_router->addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {longRunningHandler, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_1_0}, {m_handler2, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(longRunningHandler.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(longRunningHandler.get()), preHandleDirective(_, _)).Times(1);
|
||||
EXPECT_CALL(*(longRunningHandler.get()), handleDirective(_)).Times(1);
|
||||
EXPECT_CALL(*(longRunningHandler.get()), cancelDirective(_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(m_directive_0_1, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(MESSAGE_ID_0_1)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(1);
|
||||
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), preHandleDirective(m_directive_1_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirective(MESSAGE_ID_1_0)).Times(1);
|
||||
EXPECT_CALL(*(m_handler2.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_0));
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_0_1));
|
||||
ASSERT_TRUE(longRunningHandler->waitUntilHandling());
|
||||
ASSERT_TRUE(m_handler1->waitUntilPreHandling());
|
||||
m_processor->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(m_processor->onDirective(m_directive_1_0));
|
||||
ASSERT_TRUE(m_handler2->waitUntilCompleted());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,316 @@
|
|||
/*
|
||||
* DirectiveRouterTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 <chrono>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/DirectiveRouter.h"
|
||||
#include "MockAttachmentManager.h"
|
||||
#include "MockDirectiveHandler.h"
|
||||
#include "MockDirectiveHandlerResult.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
using namespace avsCommon;
|
||||
|
||||
/// Generic messageId used for tests.
|
||||
static const std::string MESSAGE_ID_0_0("Message_0_0");
|
||||
|
||||
/// Generic MessageId used for test.
|
||||
static const std::string MESSAGE_ID_0_1("Message_0_1");
|
||||
|
||||
/// Generic MessageId used for tests.
|
||||
static const std::string MESSAGE_ID_1_0("Message_1_0");
|
||||
|
||||
/// Generic DialogRequestId used for tests.
|
||||
static const std::string DIALOG_REQUEST_ID_0("DialogRequestId_0");
|
||||
|
||||
/// An unparsed directive for test.
|
||||
static const std::string UNPARSED_DIRECTIVE("unparsedDirectiveForTest");
|
||||
|
||||
/// A paylaod for test;
|
||||
static const std::string PAYLOAD_TEST("payloadForTest");
|
||||
|
||||
/// A generic namespace string for tests.
|
||||
static const std::string NAMESPACE_0("namespace_0");
|
||||
|
||||
/// A generic namespace string for tests.
|
||||
static const std::string NAMESPACE_1("namespace_1");
|
||||
|
||||
/// A generic name string for tests.
|
||||
static const std::string NAME_0("name_0");
|
||||
|
||||
/// A generic name string for tests.
|
||||
static const std::string NAME_1("name_1");
|
||||
|
||||
/// Namespace and name combination for tests.
|
||||
#define NAMESPACE_AND_NAME_0_0 NAMESPACE_0, NAME_0
|
||||
|
||||
/// Namespace and name combination (changing name this time) for tests.
|
||||
#define NAMESPACE_AND_NAME_0_1 NAMESPACE_0, NAME_1
|
||||
|
||||
/// Namespace and name combination (changing namespace this time) for tests.
|
||||
#define NAMESPACE_AND_NAME_1_0 NAMESPACE_1, NAME_0
|
||||
|
||||
/// Long timeout we only reach when a concurrency test fails.
|
||||
static const std::chrono::seconds LONG_TIMEOUT(15);
|
||||
|
||||
/**
|
||||
* DirectiveRouterTest
|
||||
*/
|
||||
class DirectiveRouterTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override;
|
||||
|
||||
/// A DirectiveRouter instance to test with.
|
||||
DirectiveRouter m_router;
|
||||
|
||||
/// Mock AttachmentManager with which to create directives.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler0;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler1;
|
||||
|
||||
/// Generic mock @c DirectiveHandler for tests.
|
||||
std::shared_ptr<MockDirectiveHandler> m_handler2;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_0_0;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_0_1;
|
||||
|
||||
/// Generic @c AVSDirective for tests.
|
||||
std::shared_ptr<AVSDirective> m_directive_1_0;
|
||||
};
|
||||
|
||||
void DirectiveRouterTest::SetUp() {
|
||||
m_attachmentManager = std::make_shared<NiceMock<MockAttachmentManager>>();
|
||||
|
||||
m_handler0 = MockDirectiveHandler::create();
|
||||
m_handler1 = MockDirectiveHandler::create();
|
||||
m_handler2 = MockDirectiveHandler::create();
|
||||
|
||||
auto avsMessageHeader_0_0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_0_0, MESSAGE_ID_0_0, DIALOG_REQUEST_ID_0);
|
||||
m_directive_0_0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_0_0, PAYLOAD_TEST, m_attachmentManager);
|
||||
auto avsMessageHeader_0_1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_0_1, MESSAGE_ID_0_1, DIALOG_REQUEST_ID_0);
|
||||
m_directive_0_1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_0_1, PAYLOAD_TEST, m_attachmentManager);
|
||||
auto avsMessageHeader_1_0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AND_NAME_1_0, MESSAGE_ID_1_0, DIALOG_REQUEST_ID_0);
|
||||
m_directive_1_0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader_1_0, PAYLOAD_TEST, m_attachmentManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that an un-registered @c AVDirective will not be routed.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testUnroutedDirective) {
|
||||
ASSERT_FALSE(m_router.handleDirectiveImmediately(m_directive_0_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an @c AVSDirective for routing. Exercise routing via @c handleDirectiveImmediately().
|
||||
* Expect that the @c AVSDirective is routed.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testSettingADirectiveHandler) {
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(m_directive_0_0)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), onDeregistered()).Times(1);
|
||||
|
||||
ASSERT_TRUE(m_router.handleDirectiveImmediately(m_directive_0_0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register @c AVSDirectives to be routed to different handlers. Exercise routing via @c preHandleDirective().
|
||||
* Expect that the @c AVSDirectives make it to their registered handler.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testRegisteringMultipeHandler) {
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_1_0}, {m_handler2, BlockingPolicy::NON_BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(m_directive_0_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), onDeregistered()).Times(1);
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(m_directive_0_1, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), onDeregistered()).Times(1);
|
||||
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), preHandleDirective(m_directive_1_0, _)).Times(1);
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), onDeregistered()).Times(1);
|
||||
|
||||
ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_0, nullptr));
|
||||
ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_1, nullptr));
|
||||
ASSERT_TRUE(m_router.preHandleDirective(m_directive_1_0, nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register @c AVSDirectives to be routed to different handlers. Then update the registration by clearing it and
|
||||
* replacing it with a new configuration where one of the handlers was removed, one is changed, and one is not changed.
|
||||
* Exercise routing via @c handleDirective(). Expect that the @c AVSDirectives are delivered to the last handler they
|
||||
* were last assigned to and that false and a @c BlockingPolicy of NONE is returned for the directive whose handler
|
||||
* was removed.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testRemovingChangingAndNotChangingHandlers) {
|
||||
DirectiveHandlerConfiguration config1 = {
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_1_0}, {m_handler2, BlockingPolicy::NON_BLOCKING}}
|
||||
};
|
||||
DirectiveHandlerConfiguration config2 = {
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_1_0}, {m_handler0, BlockingPolicy::BLOCKING}}
|
||||
};
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_1_0)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), onDeregistered()).Times(2);
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(MESSAGE_ID_0_1)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), onDeregistered()).Times(2);
|
||||
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler2.get()), onDeregistered()).Times(1);
|
||||
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers(config1));
|
||||
ASSERT_FALSE(m_router.addDirectiveHandlers(config2));
|
||||
ASSERT_TRUE(m_router.removeDirectiveHandlers(config1));
|
||||
ASSERT_FALSE(m_router.removeDirectiveHandlers(config2));
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers(config2));
|
||||
|
||||
auto policy = BlockingPolicy::NONE;
|
||||
ASSERT_FALSE(m_router.handleDirective(m_directive_0_0, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::NONE);
|
||||
ASSERT_TRUE(m_router.handleDirective(m_directive_0_1, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::NON_BLOCKING);
|
||||
ASSERT_TRUE(m_router.handleDirective(m_directive_1_0, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::BLOCKING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register two @c AVSDirectives to be routed to different handlers with different blocking policies. Configure the
|
||||
* mock handlers to return false from @c handleDirective(). Exercise routing via handleDirective(). Expect that
|
||||
* @c DirectiveRouter::handleDirective() returns @c false and BlockingPolicy::NONE to indicate failure.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testResultOfHandleDirectiveFailure) {
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AND_NAME_0_1}, {m_handler1, BlockingPolicy::BLOCKING}}
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_0_0)).WillOnce(Return(false));
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), onDeregistered()).Times(1);
|
||||
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), handleDirective(MESSAGE_ID_0_1)).WillOnce(Return(false));
|
||||
EXPECT_CALL(*(m_handler1.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler1.get()), onDeregistered()).Times(1);
|
||||
|
||||
auto policy = BlockingPolicy::NONE;
|
||||
ASSERT_FALSE(m_router.handleDirective(m_directive_0_0, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::NONE);
|
||||
ASSERT_FALSE(m_router.handleDirective(m_directive_0_1, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an @c AVSDirective for handling. Invoke preHandleDirective() on a new thread and block its return
|
||||
* until a subsequent invocation of @c handleDirective() has started. Expect the blocked call to preHandleDirective()
|
||||
* to complete quickly.
|
||||
*/
|
||||
TEST_F(DirectiveRouterTest, testHandlerMethodsCanRunConcurrently) {
|
||||
ASSERT_TRUE(m_router.addDirectiveHandlers({
|
||||
{{NAMESPACE_AND_NAME_0_0}, {m_handler0, BlockingPolicy::BLOCKING}}
|
||||
}));
|
||||
|
||||
std::promise<void> waker;
|
||||
auto sleeper = waker.get_future();
|
||||
|
||||
auto sleeperFunction = [&sleeper]() {
|
||||
ASSERT_EQ(sleeper.wait_for(LONG_TIMEOUT), std::future_status::ready)
|
||||
<< "ERROR: Timeout reached while waiting for concurrent handler.";
|
||||
};
|
||||
|
||||
auto wakerFunction = [&waker]() {
|
||||
waker.set_value();
|
||||
return true;
|
||||
};
|
||||
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), preHandleDirective(m_directive_0_0, _))
|
||||
.WillOnce(InvokeWithoutArgs(sleeperFunction));
|
||||
EXPECT_CALL(*(m_handler0.get()), handleDirective(MESSAGE_ID_0_0))
|
||||
.WillOnce(InvokeWithoutArgs(wakerFunction));
|
||||
EXPECT_CALL(*(m_handler0.get()), cancelDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(m_handler0.get()), onDeregistered()).Times(1);
|
||||
|
||||
std::thread sleeperThread([this]() {
|
||||
ASSERT_TRUE(m_router.preHandleDirective(m_directive_0_0, nullptr));
|
||||
});
|
||||
auto policy = BlockingPolicy::NONE;
|
||||
ASSERT_TRUE(m_router.handleDirective(m_directive_0_0, &policy));
|
||||
ASSERT_EQ(policy, BlockingPolicy::BLOCKING);
|
||||
sleeperThread.join();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -14,6 +14,7 @@
|
|||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
// @file DirectiveSequencerTest.cpp
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
@ -23,8 +24,9 @@
|
|||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/DirectiveSequencer.h"
|
||||
|
||||
#include "ACL/AttachmentManagerInterface.h"
|
||||
#include "MockAttachmentManager.h"
|
||||
#include "MockDirectiveHandler.h"
|
||||
#include "MockDirectiveHandlerResult.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
|
@ -33,20 +35,10 @@ namespace adsl {
|
|||
namespace test {
|
||||
|
||||
using namespace avsCommon;
|
||||
using namespace acl;
|
||||
|
||||
/// Macro to wait for a future using the default done timeout and assert that the timeout was not reached.
|
||||
#define ASSERT_WAIT_FOR_TIMEOUT(future) ASSERT_EQ(future.wait_for(DEFAULT_DONE_TIMEOUT_MS), std::future_status::ready)
|
||||
|
||||
/// Default amount of time taken to handle a directive.
|
||||
static const std::chrono::milliseconds DEFAULT_HANDLING_TIME_MS(0);
|
||||
|
||||
/// Long amount of time for handling a directive to allow other things to happen (we should not reach this).
|
||||
static const std::chrono::milliseconds LONG_HANDLING_TIME_MS(30000);
|
||||
|
||||
/// Timeout used when waiting for tests to complete (we should not reach this).
|
||||
static const std::chrono::milliseconds DEFAULT_DONE_TIMEOUT_MS(60000);
|
||||
|
||||
/// Namespace for Test only directives.
|
||||
static const std::string NAMESPACE_TEST("Test");
|
||||
|
||||
|
@ -68,7 +60,7 @@ static const std::string NAME_SET_VOLUME("SetVolume");
|
|||
/// Name for SpeechSynthesizer::Speak directives.
|
||||
static const std::string NAME_SPEAK("Speak");
|
||||
|
||||
/// Name for AutioPlayer::Play directives.
|
||||
/// Name for AudioPlayer::Play directives.
|
||||
static const std::string NAME_PLAY("Play");
|
||||
|
||||
/// Name for Test::Blocking directives
|
||||
|
@ -108,285 +100,16 @@ static const std::string PAYLOAD_TEST("payloadForTest");
|
|||
static const std::string DIALOG_REQUEST_ID_2("DialogRequestId_2");
|
||||
|
||||
/**
|
||||
* MockDirectiveHandler used to verify DirectiveSequencer behavior.
|
||||
* NOTE: Each instance contains directive specific state, so it must only be be used to process a single directive.
|
||||
* Mock ExceptionEncounteredSenderInterface implementation.
|
||||
*/
|
||||
class MockDirectiveHandler : public DirectiveHandlerInterface {
|
||||
class MockExceptionEncounteredSender : public avsCommon::ExceptionEncounteredSenderInterface {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Create a MockDirectiveHandler.
|
||||
* @param handlingTimeMs The amount of time (in milliseconds) this handler takes to handle directives.
|
||||
* @return A new MockDirectiveHandler.
|
||||
*/
|
||||
static std::shared_ptr<NiceMock<MockDirectiveHandler>> create(
|
||||
std::chrono::milliseconds handlingTimeMs = DEFAULT_HANDLING_TIME_MS);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param handlingTimeMs The amount of time (in milliseconds) this handler takes to handle directives.
|
||||
*/
|
||||
MockDirectiveHandler(std::chrono::milliseconds handlingTimeMs);
|
||||
|
||||
/// Destructor.
|
||||
~MockDirectiveHandler();
|
||||
|
||||
/**
|
||||
* The functional part of mocking handleDirectiveImmediately().
|
||||
* @param directive The directive to handle.
|
||||
*/
|
||||
void mockHandleDirectiveImmediately(std::shared_ptr<AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* The functional part of mocking preHandleDirective().
|
||||
* @param directive The directive to pre-handle.
|
||||
* @param result The result object to
|
||||
*/
|
||||
void mockPreHandleDirective(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result);
|
||||
|
||||
/**
|
||||
* The functional part of mocking handleDirective().
|
||||
* @param messageId The MessageId of the @c AVSDirective to handle.
|
||||
*/
|
||||
void mockHandleDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* The functional part of mocking cancelDirective().
|
||||
* @param directive The MessageId of the directive to cancel.
|
||||
*/
|
||||
void mockCancelDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* The functional part of mocking shutdown().
|
||||
*/
|
||||
void mockShutdown();
|
||||
|
||||
/**
|
||||
* Method for m_doHandleDirectiveThread. Waits a specified time before reporting completion.
|
||||
* If cancelDirective() is called in the mean time, wake up and exit.
|
||||
* @param messageId The messageId to pass to the observer when reporting completion.
|
||||
*/
|
||||
void doHandleDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Fail preHandleDirective() by calling onDirectiveError().
|
||||
* @param directive The directive to simulate the failure of.
|
||||
* @param result An object to receive the result of the handling operation.
|
||||
*/
|
||||
void doPreHandlingFailed(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result);
|
||||
|
||||
/**
|
||||
* Fail handleDirective() by calling onDirectiveError().
|
||||
* @param messageId The MessageId of the directive to simulate the failure of.
|
||||
*/
|
||||
void doHandlingFailed(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Common shutdown code.
|
||||
*/
|
||||
void shutdownCommon();
|
||||
|
||||
/**
|
||||
* Block until preHandleDirective() is called.
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if preHandeDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilPreHandling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Block until handleDirective() is called.
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if handeDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilHandling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Block until cancelDirective() is called.
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if cancelDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilCanceling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Return whether shutdown() has been called.
|
||||
* @return whether shutdown() has been called.
|
||||
*/
|
||||
bool isShuttingDown();
|
||||
|
||||
/// The amount of time (in milliseconds) handling a directive will take.
|
||||
std::chrono::milliseconds m_handlingTimeMs;
|
||||
|
||||
/// Object used to specify the result of handling a directive.
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> m_result;
|
||||
|
||||
/// Thread to perform handleDirective() asynchronously.
|
||||
std::thread m_doHandleDirectiveThread;
|
||||
|
||||
/// Mutex to protect m_isShuttingDown.
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// Whether shutdown() has been called.
|
||||
bool m_isShuttingDown;
|
||||
|
||||
/// condition_variable use to wake doHandlingDirectiveThread.
|
||||
std::condition_variable m_wakeNotifier;
|
||||
|
||||
/// Promise fulfilled when preHandleDirective() begins.
|
||||
std::promise<void> m_preHandlingPromise;
|
||||
|
||||
/// Future to notify when preHandleDirective() begins.
|
||||
std::future<void> m_preHandlingFuture;
|
||||
|
||||
/// Promise fulfilled when handleDirective() begins.
|
||||
std::promise<void> m_handlingPromise;
|
||||
|
||||
/// Future to notify when handleDirective() begins.
|
||||
std::future<void> m_handlingFuture;
|
||||
|
||||
/// Promise fulfilled when cancelDirective() begins.
|
||||
std::promise<void> m_cancelingPromise;
|
||||
|
||||
/// Future to notify when cancelDirective() begins.
|
||||
std::shared_future<void> m_cancelingFuture;
|
||||
|
||||
MOCK_METHOD1(handleDirectiveImmediately, void(std::shared_ptr<AVSDirective>));
|
||||
MOCK_METHOD2(
|
||||
preHandleDirective,
|
||||
void(std::shared_ptr<AVSDirective>, std::shared_ptr<DirectiveHandlerResultInterface>));
|
||||
MOCK_METHOD1(handleDirective, void(const std::string&));
|
||||
MOCK_METHOD1(cancelDirective, void(const std::string&));
|
||||
MOCK_METHOD0(shutdown, void());
|
||||
MOCK_METHOD3(sendExceptionEncountered, void(const std::string&, ExceptionErrorType, const std::string&));
|
||||
};
|
||||
|
||||
class MockAttachmentManager : public AttachmentManagerInterface {
|
||||
public:
|
||||
MOCK_METHOD1(createAttachmentReader, std::future<std::shared_ptr<std::iostream>> (const std::string& attachmentId));
|
||||
MOCK_METHOD2(createAttachment, void (const std::string& attachmentId, std::shared_ptr<std::iostream> attachment));
|
||||
MOCK_METHOD1(releaseAttachment, void (const std::string& attachmentId));
|
||||
};
|
||||
|
||||
|
||||
std::shared_ptr<NiceMock<MockDirectiveHandler>> MockDirectiveHandler::create(std::chrono::milliseconds handlingTimeMs) {
|
||||
auto result = std::make_shared<NiceMock<MockDirectiveHandler>>(handlingTimeMs);
|
||||
ON_CALL(*result.get(), handleDirectiveImmediately(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockHandleDirectiveImmediately));
|
||||
ON_CALL(*result.get(), preHandleDirective(_, _)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockPreHandleDirective));
|
||||
ON_CALL(*result.get(), handleDirective(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockHandleDirective));
|
||||
ON_CALL(*result.get(), cancelDirective(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockCancelDirective));
|
||||
ON_CALL(*result.get(), shutdown()).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockShutdown));
|
||||
return result;
|
||||
}
|
||||
|
||||
MockDirectiveHandler::MockDirectiveHandler(std::chrono::milliseconds handlingTimeMs) :
|
||||
m_handlingTimeMs{handlingTimeMs},
|
||||
m_isShuttingDown{false},
|
||||
m_preHandlingPromise{},
|
||||
m_preHandlingFuture{m_preHandlingPromise.get_future()},
|
||||
m_handlingPromise{},
|
||||
m_handlingFuture{m_handlingPromise.get_future()},
|
||||
m_cancelingPromise{},
|
||||
m_cancelingFuture{m_cancelingPromise.get_future()} {
|
||||
}
|
||||
|
||||
MockDirectiveHandler::~MockDirectiveHandler() {
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockHandleDirectiveImmediately(std::shared_ptr<AVSDirective> directive) {
|
||||
ASSERT_FALSE(isShuttingDown());
|
||||
m_handlingPromise.set_value();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockPreHandleDirective(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result) {
|
||||
ASSERT_FALSE(isShuttingDown());
|
||||
m_result = result;
|
||||
m_preHandlingPromise.set_value();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockHandleDirective(const std::string& messageId) {
|
||||
ASSERT_FALSE(isShuttingDown());
|
||||
m_doHandleDirectiveThread = std::thread(&MockDirectiveHandler::doHandleDirective, this, messageId);
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockCancelDirective(const std::string& messageId) {
|
||||
ASSERT_FALSE(isShuttingDown());
|
||||
m_cancelingPromise.set_value();
|
||||
shutdownCommon();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockShutdown() {
|
||||
ASSERT_TRUE(waitUntilCanceling(std::chrono::milliseconds(0)) || !isShuttingDown());
|
||||
shutdownCommon();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doHandleDirective(const std::string& messageId) {
|
||||
auto isShuttingDown = [this]() {
|
||||
return m_isShuttingDown;
|
||||
};
|
||||
m_handlingPromise.set_value();
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (!m_wakeNotifier.wait_for(lock, m_handlingTimeMs, isShuttingDown)) {
|
||||
m_result->setCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doPreHandlingFailed(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result) {
|
||||
m_result = result;
|
||||
m_result->setFailed("doPreHandlingFailed()");
|
||||
m_preHandlingPromise.set_value();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doHandlingFailed(const std::string& messageId) {
|
||||
m_result->setFailed("doHandlingFailed()");
|
||||
m_handlingPromise.set_value();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::shutdownCommon() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isShuttingDown = true;
|
||||
m_wakeNotifier.notify_all();
|
||||
}
|
||||
if (m_doHandleDirectiveThread.joinable()) {
|
||||
m_doHandleDirectiveThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilPreHandling(std::chrono::milliseconds timeout) {
|
||||
return m_preHandlingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilHandling(std::chrono::milliseconds timeout) {
|
||||
return m_handlingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilCanceling(std::chrono::milliseconds timeout) {
|
||||
return m_cancelingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::isShuttingDown() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_isShuttingDown;
|
||||
}
|
||||
|
||||
/// DirectiveSequencerTest
|
||||
class DirectiveSequencerTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
/// Constructor.
|
||||
DirectiveSequencerTest();
|
||||
|
||||
/**
|
||||
* Setup the DirectiveSequencerTest. Creates a DirectiveSequencer and initialized it with a DirectiveHandler
|
||||
* that allows waiting for a final directive to be processed.
|
||||
|
@ -406,64 +129,59 @@ public:
|
|||
*/
|
||||
void setDialogRequestId(std::string dialogRequestId);
|
||||
|
||||
/**
|
||||
* Method to invoke when handleDirective() is invoked for the Test::Done directive.
|
||||
* @param messageId The message ID of a directive previously passed to preHandleDirective().
|
||||
*/
|
||||
void notifyDone(std::string messageId);
|
||||
|
||||
/// The DialogRequestId to use for the Test::Done directive.
|
||||
std::string m_latestDialogRequestId;
|
||||
|
||||
/// Handler to invoke for the Test::Done directive.
|
||||
std::shared_ptr<MockDirectiveHandler> m_doneHandler;
|
||||
|
||||
/// The DirectiveSequencer to test.
|
||||
std::shared_ptr<DirectiveSequencer> m_sequencer;
|
||||
std::shared_ptr<MockExceptionEncounteredSender> m_exceptionEncounteredSender;
|
||||
|
||||
/// Promise fulfilled when the test is done.
|
||||
std::promise<void> m_donePromise;
|
||||
|
||||
/// Future used to notify when the test is done.
|
||||
std::future<void> m_doneFuture;
|
||||
/// The DirectiveSequencer to test.
|
||||
std::unique_ptr<DirectiveSequencerInterface> m_sequencer;
|
||||
|
||||
/// Mock object of AttachmentManagerInterface
|
||||
std::shared_ptr<acl::AttachmentManagerInterface> m_mockAttachmentManager;
|
||||
std::shared_ptr<AttachmentManagerInterface> m_mockAttachmentManager;
|
||||
};
|
||||
|
||||
DirectiveSequencerTest::DirectiveSequencerTest() : m_donePromise{}, m_doneFuture{m_donePromise.get_future()} {
|
||||
}
|
||||
|
||||
void DirectiveSequencerTest::SetUp() {
|
||||
m_doneHandler = MockDirectiveHandler::create();
|
||||
m_doneHandler = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
m_mockAttachmentManager = std::make_shared<MockAttachmentManager>();
|
||||
m_sequencer = DirectiveSequencer::create(nullptr);
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
m_exceptionEncounteredSender = std::make_shared<NiceMock<MockExceptionEncounteredSender>>();
|
||||
m_sequencer = DirectiveSequencer::create(m_exceptionEncounteredSender);
|
||||
ASSERT_TRUE(m_sequencer);
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_TEST, NAME_DONE}, {m_doneHandler, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
}
|
||||
|
||||
void DirectiveSequencerTest::TearDown() {
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_TEST, NAME_DONE, MESSAGE_ID_DONE,
|
||||
DIALOG_REQUEST_ID_DONE);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
EXPECT_CALL(*(m_doneHandler.get()), handleDirective(MESSAGE_ID_DONE)).WillOnce(
|
||||
Invoke(this, &DirectiveSequencerTest::notifyDone));
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_TEST, NAME_DONE, MESSAGE_ID_DONE, DIALOG_REQUEST_ID_DONE);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
EXPECT_CALL(*(m_doneHandler.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(m_doneHandler.get()), preHandleDirective(directive, _)).Times(1);
|
||||
EXPECT_CALL(*(m_doneHandler.get()), handleDirective(_)).Times(1);
|
||||
EXPECT_CALL(*(m_doneHandler.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_DONE);
|
||||
m_sequencer->onDirective(directive);
|
||||
m_doneFuture.wait_for(DEFAULT_DONE_TIMEOUT_MS);
|
||||
m_doneHandler->waitUntilHandling();
|
||||
ASSERT_TRUE(m_sequencer->removeDirectiveHandlers({
|
||||
{{NAMESPACE_TEST, NAME_DONE}, {m_doneHandler, BlockingPolicy::BLOCKING}}
|
||||
}));
|
||||
m_sequencer->shutdown();
|
||||
m_sequencer.reset();
|
||||
m_doneHandler->doHandlingCompleted();
|
||||
}
|
||||
|
||||
void DirectiveSequencerTest::setDialogRequestId(std::string dialogRequestId) {
|
||||
m_latestDialogRequestId = dialogRequestId;
|
||||
m_sequencer->setDialogRequestId(m_latestDialogRequestId);
|
||||
}
|
||||
|
||||
void DirectiveSequencerTest::notifyDone(std::string messageId) {
|
||||
m_donePromise.set_value();
|
||||
/**
|
||||
* Test DirectiveSequencer::create() with a nullptr @c ExceptionEncounteredSender. Expect create to fail.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testNullptrExceptionSender) {
|
||||
ASSERT_TRUE(m_sequencer);
|
||||
auto sequencer = DirectiveSequencer::create(nullptr);
|
||||
ASSERT_FALSE(sequencer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,40 +198,50 @@ TEST_F(DirectiveSequencerTest, testNullptrDirective) {
|
|||
ASSERT_FALSE(m_sequencer->onDirective(nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise sending a @c AVSDirective for which no handler has been registered. Expect that
|
||||
* m_exceptionEncounteredSender will receive a request to send the ExceptionEncountered message.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testUnhandledDirective) {
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
EXPECT_CALL(*(m_exceptionEncounteredSender.get()), sendExceptionEncountered(_, _, _)).Times(1);
|
||||
m_sequencer->onDirective(directive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a directive with an empty DialogRequestId.
|
||||
* Expect a call to handleDirectiveImmediately().
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testEmptyDialogRequestId) {
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto handler = MockDirectiveHandler::create();
|
||||
EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(directive)).Times(1);
|
||||
EXPECT_CALL(*(handler.get()), preHandleDirective(_, _)).Times(0);
|
||||
EXPECT_CALL(*(handler.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(handler.get()), cancelDirective(_)).Times(0);
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEAKER, NAME_SET_VOLUME}, {handler, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
m_sequencer->onDirective(directive);
|
||||
ASSERT_TRUE(handler->waitUntilHandling());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set handlers (NON_BLOCKING) for two namespace,name pairs. Then remove one and change the other. Send directives
|
||||
* for each of the namespace,name pairs. Expect that the directive with no mapping is not seen by a handler that
|
||||
* the one that still has a handler is handled.
|
||||
* for each of the NamespaceAndName values. Expect that the directive with no mapping is not seen by a handler and
|
||||
* that the one that still has a handler is handled.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEAKER, NAME_SET_VOLUME, MESSAGE_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_TEST, NAME_NON_BLOCKING, MESSAGE_ID_1);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create();
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
|
@ -528,16 +256,18 @@ TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) {
|
|||
EXPECT_CALL(*(handler1.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
DirectiveHandlerConfiguration configuration1 = {
|
||||
{{NAMESPACE_SPEAKER, NAME_SET_VOLUME}, {handler0, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_NON_BLOCKING}, {handler1, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
ready = m_sequencer->setDirectiveHandlers({
|
||||
{{NAMESPACE_SPEAKER, NAME_SET_VOLUME}, {nullptr, BlockingPolicy::NONE}},
|
||||
};
|
||||
DirectiveHandlerConfiguration configuration2 = {
|
||||
{{NAMESPACE_TEST, NAME_NON_BLOCKING}, {handler0, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
};
|
||||
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers(configuration1));
|
||||
ASSERT_TRUE(m_sequencer->removeDirectiveHandlers(configuration1));
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers(configuration2));
|
||||
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler0->waitUntilHandling());
|
||||
|
@ -549,30 +279,30 @@ TEST_F(DirectiveSequencerTest, testRemovingAndChangingHandlers) {
|
|||
* a call to cancelDirective() to close out the test.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testBlockingDirective) {
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
|
||||
EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(handler.get()), preHandleDirective(directive, _)).Times(1);
|
||||
EXPECT_CALL(*(handler.get()), handleDirective(MESSAGE_ID_0)).Times(1);
|
||||
EXPECT_CALL(*(handler.get()), cancelDirective(_)).Times(1);
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
}));
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive);
|
||||
ASSERT_TRUE(handler->waitUntilHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(handler->waitUntilCanceling());
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a long running @c AVSDirective with an non-empty @c DialogRequestId and a @c BLOCKING policy followed by
|
||||
* a @c AVSDirective without a @c dialogRequestId. Expect that the second directive gets handled immediately
|
||||
* and without waiting for the @c BLOCKING directive to complete.
|
||||
* Send a long running directive with an non-empty @c DialogRequestId and a BLOCKING policy.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
|
@ -597,18 +327,16 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) {
|
|||
EXPECT_CALL(*(handler1.get()), handleDirective(_)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_SPEAKER, NAME_SET_VOLUME}, {handler1, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
}));
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler1->waitUntilHandling());
|
||||
ASSERT_TRUE(handler0->waitUntilHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(handler0->waitUntilCanceling());
|
||||
}
|
||||
|
||||
|
@ -620,27 +348,25 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonDialogDirective) {
|
|||
* and a call to @c cancelDirective(@c MessageId).
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testBargeIn) {
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler = MockDirectiveHandler::create(std::chrono::milliseconds(LONG_HANDLING_TIME_MS));
|
||||
|
||||
|
||||
EXPECT_CALL(*(handler.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(handler.get()), preHandleDirective(directive, _)).Times(1);
|
||||
EXPECT_CALL(*(handler.get()), handleDirective(MESSAGE_ID_0)).Times(1);
|
||||
EXPECT_CALL(*(handler.get()), cancelDirective(MESSAGE_ID_0)).Times(1);
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
}));
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive);
|
||||
ASSERT_TRUE(handler->waitUntilHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
ASSERT_TRUE(handler->waitUntilCanceling());
|
||||
}
|
||||
|
||||
|
@ -652,33 +378,28 @@ TEST_F(DirectiveSequencerTest, testBargeIn) {
|
|||
* current value does not cancel queued directives.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(NAMESPACE_TEST, NAME_NON_BLOCKING, MESSAGE_ID_2,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_TEST, NAME_NON_BLOCKING, MESSAGE_ID_2, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create();
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
auto handler2 = MockDirectiveHandler::create();
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_NON_BLOCKING}, {handler2, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(_)).Times(0);
|
||||
EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1);
|
||||
|
@ -695,13 +416,14 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) {
|
|||
EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_2)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), cancelDirective(_)).Times(0);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive2);
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_TRUE(handler2->waitUntilHandling());
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
ASSERT_TRUE(handler1->waitUntilCompleted());
|
||||
ASSERT_TRUE(handler2->waitUntilCompleted());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -712,33 +434,28 @@ TEST_F(DirectiveSequencerTest, testBlockingThenNonBockingOnSameDialogId) {
|
|||
* end by setting the dialogRequestId to close out the test).
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(NAMESPACE_TEST, NAME_BLOCKING, MESSAGE_ID_2,
|
||||
DIALOG_REQUEST_ID_1);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_TEST, NAME_BLOCKING, MESSAGE_ID_2, DIALOG_REQUEST_ID_1);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
auto handler2 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_BLOCKING}, {handler2, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(directive0)).Times(0);
|
||||
EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1);
|
||||
|
@ -755,15 +472,15 @@ TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) {
|
|||
EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_2)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), cancelDirective(MESSAGE_ID_2)).Times(1);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler0->waitUntilHandling());
|
||||
ASSERT_TRUE(handler1->waitUntilPreHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->onDirective(directive2);
|
||||
ASSERT_TRUE(handler2->waitUntilHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_2);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_2);
|
||||
ASSERT_TRUE(handler2->waitUntilCanceling());
|
||||
}
|
||||
|
||||
|
@ -774,28 +491,22 @@ TEST_F(DirectiveSequencerTest, testThatBargeInDropsSubsequentDirectives) {
|
|||
* entirely.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler1, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(directive0)).Times(0);
|
||||
EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).WillOnce(
|
||||
|
@ -808,7 +519,7 @@ TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) {
|
|||
EXPECT_CALL(*(handler1.get()), handleDirective(MESSAGE_ID_1)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(MESSAGE_ID_1)).Times(0);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler0->waitUntilPreHandling());
|
||||
|
@ -821,27 +532,22 @@ TEST_F(DirectiveSequencerTest, testPreHandleDirectiveError) {
|
|||
* dropped before @c preHandleDirective() is called, and that if not, it will be cancelled.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testHandleDirectiveError) {
|
||||
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler1, BlockingPolicy::NON_BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(directive0)).Times(0);
|
||||
EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1);
|
||||
|
@ -854,86 +560,102 @@ TEST_F(DirectiveSequencerTest, testHandleDirectiveError) {
|
|||
EXPECT_CALL(*(handler1.get()), handleDirective(MESSAGE_ID_1)).Times(0);;
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(MESSAGE_ID_1)).Times(AtMost(1));
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler0->waitUntilHandling());
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a long-running @c BLOCKING @c AVSDirective followed by a @c NON_BLOCKING directive with the same
|
||||
* @c DialogRequestId. Once they have reached the handling and preHandling stages, respectively, call
|
||||
* setDirectiveHandlers(), swapping the handlers and policies for the two outstanding @c AVSDirectives.
|
||||
* Expect that both of the outstanding @c AVSDirectives will be cancelled. Then send an @c BLOCKING
|
||||
* @c AVSDirective whose handler mapping has not changed. Expect that that last @c AVSDirective will
|
||||
* be preHandled and handled. The last @c AVSDirectiveis long running and in the end is cancelled to
|
||||
* close out the test.
|
||||
|
||||
* Send a long-running @c BLOCKING @c AVSDirective followed by two @c NON_BLOCKING @c AVSDirectives with the same
|
||||
* @c DialogRequestId. Once they have reached the handling and preHandling stages respectively, call
|
||||
* @c addDirectiveHandlers(), changing the handlers for the first two @c AVSDirectives. After the handlers
|
||||
* have been changed, trigger completion of the first @c AVSDirective. Expect that the first directive will be
|
||||
* completed by its initial handler (the switch was after handleDirective() was called, and there is no trigger to
|
||||
* cancel it. Expect that the second directive's first handler will get the preHandleDirective() call and that its
|
||||
* second handler will receive the handleDirective(). Expect that the third directive's handler will get calls for
|
||||
* preHandleDirective() and cancelDirective(). The cancel is triggered because the second directive's second
|
||||
* handler did not recognize the messageId passed in to handleDirective(), and returned false, canceling any
|
||||
* subsequent directives with the same dialogRequestId. Along the way, call @c addDirectiveHandlers() while
|
||||
* inside cancelDirective() to verify that that operation is refused.
|
||||
*/
|
||||
TEST_F(DirectiveSequencerTest, testSetDirectiveHandlersWhileHandlingDirectives) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1,
|
||||
DIALOG_REQUEST_ID_0);
|
||||
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(NAMESPACE_TEST, NAME_BLOCKING, MESSAGE_ID_2,
|
||||
DIALOG_REQUEST_ID_1);
|
||||
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST,
|
||||
m_mockAttachmentManager);
|
||||
|
||||
TEST_F(DirectiveSequencerTest, testAddDirectiveHandlersWhileHandlingDirectives) {
|
||||
auto avsMessageHeader0 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_0, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive0 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader0, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader1 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_1, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive1 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader1, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
auto avsMessageHeader2 = std::make_shared<AVSMessageHeader>(
|
||||
NAMESPACE_TEST, NAME_NON_BLOCKING, MESSAGE_ID_2, DIALOG_REQUEST_ID_0);
|
||||
std::shared_ptr<AVSDirective> directive2 = AVSDirective::create(
|
||||
UNPARSED_DIRECTIVE, avsMessageHeader2, PAYLOAD_TEST, m_mockAttachmentManager);
|
||||
|
||||
auto handler0 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
auto handler2 = MockDirectiveHandler::create(LONG_HANDLING_TIME_MS);
|
||||
auto handler2 = MockDirectiveHandler::create();
|
||||
auto handler3 = MockDirectiveHandler::create();
|
||||
auto handler4 = MockDirectiveHandler::create();
|
||||
|
||||
auto ready = m_sequencer->setDirectiveHandlers({
|
||||
DirectiveHandlerConfiguration configuration1 = {
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_BLOCKING}, {handler2, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler2, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_NON_BLOCKING}, {handler4, BlockingPolicy::NON_BLOCKING}}
|
||||
};
|
||||
|
||||
DirectiveHandlerConfiguration configuration2 = {
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler1, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler3, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_NON_BLOCKING}, {handler4, BlockingPolicy::NON_BLOCKING}}
|
||||
};
|
||||
|
||||
auto cancelDirectiveFunction = [this, &handler4, &configuration2](const std::string& messageId) {
|
||||
ASSERT_TRUE(m_sequencer->removeDirectiveHandlers(configuration2));
|
||||
handler4->mockCancelDirective(messageId);
|
||||
};
|
||||
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers(configuration1));
|
||||
|
||||
EXPECT_CALL(*(handler0.get()), handleDirectiveImmediately(directive0)).Times(0);
|
||||
EXPECT_CALL(*(handler0.get()), preHandleDirective(directive0, _)).Times(1);
|
||||
EXPECT_CALL(*(handler0.get()), handleDirective(MESSAGE_ID_0)).Times(1);
|
||||
EXPECT_CALL(*(handler0.get()), cancelDirective(MESSAGE_ID_0)).Times(1);
|
||||
EXPECT_CALL(*(handler0.get()), cancelDirective(MESSAGE_ID_0)).Times(0);
|
||||
|
||||
EXPECT_CALL(*(handler1.get()), handleDirectiveImmediately(directive1)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), preHandleDirective(directive1, _)).Times(1);
|
||||
EXPECT_CALL(*(handler1.get()), handleDirective(MESSAGE_ID_1)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(MESSAGE_ID_1)).Times(1);
|
||||
EXPECT_CALL(*(handler1.get()), handleDirectiveImmediately(directive0)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), preHandleDirective(directive0, _)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), handleDirective(MESSAGE_ID_0)).Times(0);
|
||||
EXPECT_CALL(*(handler1.get()), cancelDirective(MESSAGE_ID_0)).Times(0);
|
||||
|
||||
EXPECT_CALL(*(handler2.get()), handleDirectiveImmediately(directive2)).Times(0);
|
||||
EXPECT_CALL(*(handler2.get()), preHandleDirective(directive2, _)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_2)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), cancelDirective(MESSAGE_ID_2)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), handleDirectiveImmediately(directive1)).Times(0);
|
||||
EXPECT_CALL(*(handler2.get()), preHandleDirective(directive1, _)).Times(1);
|
||||
EXPECT_CALL(*(handler2.get()), handleDirective(MESSAGE_ID_1)).Times(0);
|
||||
EXPECT_CALL(*(handler2.get()), cancelDirective(MESSAGE_ID_1)).Times(0);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
EXPECT_CALL(*(handler3.get()), handleDirectiveImmediately(directive1)).Times(0);
|
||||
EXPECT_CALL(*(handler3.get()), preHandleDirective(directive1, _)).Times(0);
|
||||
EXPECT_CALL(*(handler3.get()), handleDirective(MESSAGE_ID_1)).Times(1);
|
||||
EXPECT_CALL(*(handler3.get()), cancelDirective(MESSAGE_ID_1)).Times(0);
|
||||
|
||||
EXPECT_CALL(*(handler4.get()), handleDirectiveImmediately(directive2)).Times(0);
|
||||
EXPECT_CALL(*(handler4.get()), preHandleDirective(directive2, _)).Times(1);
|
||||
EXPECT_CALL(*(handler4.get()), handleDirective(MESSAGE_ID_2)).Times(0);
|
||||
EXPECT_CALL(*(handler4.get()), cancelDirective(MESSAGE_ID_2)).WillOnce(Invoke(cancelDirectiveFunction));
|
||||
|
||||
m_sequencer->setDialogRequestId(DIALOG_REQUEST_ID_0);
|
||||
m_sequencer->onDirective(directive0);
|
||||
m_sequencer->onDirective(directive1);
|
||||
ASSERT_TRUE(handler0->waitUntilHandling());
|
||||
ASSERT_TRUE(handler1->waitUntilPreHandling());
|
||||
|
||||
ready = m_sequencer->setDirectiveHandlers({
|
||||
{{NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK}, {handler1, BlockingPolicy::NON_BLOCKING}},
|
||||
{{NAMESPACE_AUDIO_PLAYER, NAME_PLAY}, {handler0, BlockingPolicy::BLOCKING}},
|
||||
{{NAMESPACE_TEST, NAME_BLOCKING}, {handler2, BlockingPolicy::BLOCKING}}
|
||||
});
|
||||
|
||||
ASSERT_TRUE(handler1->waitUntilCanceling());
|
||||
ASSERT_WAIT_FOR_TIMEOUT(ready);
|
||||
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_1);
|
||||
m_sequencer->onDirective(directive2);
|
||||
ASSERT_TRUE(handler2->waitUntilHandling());
|
||||
setDialogRequestId(DIALOG_REQUEST_ID_2);
|
||||
ASSERT_TRUE(handler2->waitUntilCanceling());
|
||||
|
||||
handler0->waitUntilHandling();
|
||||
handler4->waitUntilPreHandling();
|
||||
|
||||
ASSERT_TRUE(m_sequencer->removeDirectiveHandlers(configuration1));
|
||||
ASSERT_TRUE(m_sequencer->addDirectiveHandlers(configuration2));
|
||||
|
||||
handler0->doHandlingCompleted();
|
||||
ASSERT_TRUE(handler4->waitUntilCanceling());
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* HandlerAndPolicyTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/HandlerAndPolicy.h"
|
||||
#include "MockDirectiveHandler.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
using namespace alexaClientSDK::avsCommon;
|
||||
|
||||
/**
|
||||
* HandlerAndPolicyTest
|
||||
*/
|
||||
class HandlerAndPolicyTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke default constructor. Expect @c nameSpace and @c name properties are both empty strings.
|
||||
*/
|
||||
TEST_F(HandlerAndPolicyTest, testDefaultConstructor) {
|
||||
HandlerAndPolicy handlerAndPolicy;
|
||||
ASSERT_EQ(handlerAndPolicy.handler, nullptr);
|
||||
ASSERT_EQ(handlerAndPolicy.policy, BlockingPolicy::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke constructor with member values. Expect properties match those provided to the constructor.
|
||||
*/
|
||||
TEST_F(HandlerAndPolicyTest, testConstructorWithValues) {
|
||||
auto handler = MockDirectiveHandler::create();
|
||||
HandlerAndPolicy handlerAndPolicy(handler, BlockingPolicy::NON_BLOCKING);
|
||||
ASSERT_EQ(handlerAndPolicy.handler, handler);
|
||||
ASSERT_EQ(handlerAndPolicy.policy, BlockingPolicy::NON_BLOCKING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty and non-empty HandlerAndPolicy instances. Expect that empty instances are interpreted as false and
|
||||
* non-empty values are interpreted as true.
|
||||
*/
|
||||
TEST_F(HandlerAndPolicyTest, testOperatorBool) {
|
||||
auto handler = MockDirectiveHandler::create();
|
||||
HandlerAndPolicy defaultHandlerAndPolicy;
|
||||
HandlerAndPolicy firstHalfEmpty(nullptr, BlockingPolicy::BLOCKING);
|
||||
HandlerAndPolicy secondHalfEmpty(handler, BlockingPolicy::NONE);
|
||||
HandlerAndPolicy nonEmpty(handler, BlockingPolicy::BLOCKING);
|
||||
ASSERT_FALSE(defaultHandlerAndPolicy);
|
||||
ASSERT_FALSE(firstHalfEmpty);
|
||||
ASSERT_FALSE(secondHalfEmpty);
|
||||
ASSERT_TRUE(nonEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instances with different and identical values. Expect that instances with different values test as
|
||||
* not equal and that instances with identical values test as equal.
|
||||
*/
|
||||
TEST_F(HandlerAndPolicyTest, testOperatorEqualandNotEqual) {
|
||||
auto handler1 = MockDirectiveHandler::create();
|
||||
auto handler2 = MockDirectiveHandler::create();
|
||||
HandlerAndPolicy defaultHandlerAndPolicy;
|
||||
HandlerAndPolicy handlerAndPolicy1(handler1, BlockingPolicy::NON_BLOCKING);
|
||||
HandlerAndPolicy handlerAndPolicy1Clone(handler1, BlockingPolicy::NON_BLOCKING);
|
||||
HandlerAndPolicy handlerAndPolicy2(handler1, BlockingPolicy::BLOCKING);
|
||||
HandlerAndPolicy handlerAndPolicy3(handler2, BlockingPolicy::NON_BLOCKING);
|
||||
HandlerAndPolicy handlerAndPolicy4(nullptr, BlockingPolicy::NON_BLOCKING);
|
||||
ASSERT_EQ(defaultHandlerAndPolicy, defaultHandlerAndPolicy);
|
||||
ASSERT_NE(defaultHandlerAndPolicy, handlerAndPolicy1);
|
||||
ASSERT_EQ(handlerAndPolicy1, handlerAndPolicy1Clone);
|
||||
ASSERT_NE(handlerAndPolicy1, handlerAndPolicy2);
|
||||
ASSERT_NE(handlerAndPolicy1, handlerAndPolicy3);
|
||||
ASSERT_NE(handlerAndPolicy2, handlerAndPolicy3);
|
||||
ASSERT_NE(handlerAndPolicy3, handlerAndPolicy4);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* MessageInterpreterTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 MessageInterpreterTest.cpp
|
||||
|
||||
#include <AVSCommon/AttachmentManager.h>
|
||||
#include <AVSCommon/MockExceptionEncounteredSender.h>
|
||||
#include <ADSL/DirectiveSequencer.h>
|
||||
#include <ADSL/MessageInterpreter.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
|
||||
#include "ADSL/MockDirectiveSequencer.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
|
||||
using namespace ::testing;
|
||||
using namespace alexaClientSDK::acl;
|
||||
using namespace alexaClientSDK::avsCommon;
|
||||
|
||||
/// The namespace in AVS message.
|
||||
static const std::string NAMESPACE_TEST = "SpeechSynthesizer";
|
||||
/// The name field in AVS message.
|
||||
static const std::string NAME_TEST = "Speak";
|
||||
/// The messageId in AVS message.
|
||||
static const std::string MESSAGE_ID_TEST = "testMessageId";
|
||||
/// The dialogRequestId in AVS message.
|
||||
static const std::string DIALOG_REQUEST_ID_TEST = "dialogRequestIdTest";
|
||||
/// The payload in AVS message.
|
||||
static const std::string PAYLOAD_TEST = R"({"url":"cid:testCID","format":"testFormat","token":"testToken"})";
|
||||
/// An invalid JSON string for testing.
|
||||
static const std::string INVALID_JSON = "invalidTestJSON }}";
|
||||
|
||||
/// A sample AVS speak directive with all valid JSON keys.
|
||||
static const std::string SPEAK_DIRECTIVE = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":")" + NAMESPACE_TEST + R"(",
|
||||
"name": ")" + NAME_TEST + R"(",
|
||||
"messageId": ")" + MESSAGE_ID_TEST + R"(",
|
||||
"dialogRequestId": ")" + DIALOG_REQUEST_ID_TEST + R"("
|
||||
},
|
||||
"payload": )" + PAYLOAD_TEST + R"(
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid directive JSON keys.
|
||||
static const std::string DIRECTIVE_INVALID_DIRECTIVE_KEY = R"({
|
||||
"Foo_directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid header key.
|
||||
static const std::string DIRECTIVE_INVALID_HEADER_KEY = R"({
|
||||
"directive": {
|
||||
"Foo_header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid namespace key.
|
||||
static const std::string DIRECTIVE_INVALID_NAMESPACE_KEY = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"Foo_namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid name key.
|
||||
static const std::string DIRECTIVE_INVALID_NAME_KEY = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"Foo_name": "name_test",
|
||||
"messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid messageId key.
|
||||
static const std::string DIRECTIVE_INVALID_MESSAGEID_KEY = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"Foo_messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with no payload key.
|
||||
static const std::string DIRECTIVE_NO_PAYLOAD = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"messageId": "messageId_test"
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with invalid payload key.
|
||||
static const std::string DIRECTIVE_INVALID_PAYLOAD_KEY = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"Foo_messageId": "messageId_test",
|
||||
"dialogRequestId": "dialogRequestId_test"
|
||||
},
|
||||
"Foo_payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/// A sample AVS speak directive with no dialogRequestId key.
|
||||
static const std::string DIRECTIVE_NO_DIALOG_REQUEST_ID_KEY = R"({
|
||||
"directive": {
|
||||
"header": {
|
||||
"namespace":"namespace_test",
|
||||
"name": "name_test",
|
||||
"messageId": "messageId_test"
|
||||
},
|
||||
"payload":{}
|
||||
}
|
||||
})";
|
||||
|
||||
/**
|
||||
* Test fixture for testing MessageInterpreter class.
|
||||
*/
|
||||
class MessageIntepreterTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
m_mockExceptionEncounteredSender = std::make_shared<MockExceptionEncounteredSender>();
|
||||
m_mockDirectiveSequencer = std::make_shared<MockDirectiveSequencer>();
|
||||
m_messageInterpreter = std::make_shared<MessageInterpreter>(m_mockExceptionEncounteredSender,
|
||||
m_mockDirectiveSequencer);
|
||||
m_attachmentManager = std::make_shared<AttachmentManager>();
|
||||
}
|
||||
/// The mock ExceptionEncounteredSender.
|
||||
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionEncounteredSender;
|
||||
/// The attachment manager.
|
||||
std::shared_ptr<AttachmentManagerInterface> m_attachmentManager;
|
||||
/// The mock directive sequencer.
|
||||
std::shared_ptr<MockDirectiveSequencer> m_mockDirectiveSequencer;
|
||||
/// The message interpreter under test.
|
||||
std::shared_ptr<MessageInterpreter> m_messageInterpreter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test creating the AVSDirective with attachment manager set to @c nullptr. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageWithoutAttachmentManager) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(SPEAK_DIRECTIVE);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the content of message is invalid JSON format. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageIsInValidJSON) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(INVALID_JSON, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the directive key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidDirectiveKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_DIRECTIVE_KEY, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the header key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidHeaderKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_HEADER_KEY, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the namespace key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidNamespaceKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_NAMESPACE_KEY, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the name key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidNameKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_NAME_KEY, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the messageId key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidMessageIdKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_MESSAGEID_KEY, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the dialogRequestId key in JSON content. DialogRequestId is optional, so
|
||||
* AVSDirective should be created and passed to the directive sequencer.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasNoDialogRequestIdKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_NO_DIALOG_REQUEST_ID_KEY,
|
||||
m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message doesn't contain the payload key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasNoPayloadKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_NO_PAYLOAD,
|
||||
m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message contains an invalid payload key in JSON content. The AVSDirective shouldn't be created and
|
||||
* and passed to directive sequencer. ExceptionEncounteredEvent should be sent to AVS.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageHasInvalidPayloadKey) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(0);
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(DIRECTIVE_INVALID_PAYLOAD_KEY,
|
||||
m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the message is valid JSON content with all keys required in the header. An AVSDirective should be created
|
||||
* and passed to the directive sequencer.
|
||||
*/
|
||||
TEST_F(MessageIntepreterTest, messageIsValidDirective) {
|
||||
EXPECT_CALL(*m_mockExceptionEncounteredSender, sendExceptionEncountered(_, _, _))
|
||||
.Times(0);
|
||||
EXPECT_CALL(*m_mockDirectiveSequencer, onDirective(_))
|
||||
.Times(1)
|
||||
.WillOnce(Invoke([](std::shared_ptr<AVSDirective> avsDirective) -> bool {
|
||||
// This lambda function is necessary because the ASSERT_EQ is a macro that "returns void";
|
||||
auto verifyArguments = [](std::shared_ptr<AVSDirective> avsDirective) {
|
||||
ASSERT_EQ(avsDirective->getNamespace(), NAMESPACE_TEST);
|
||||
ASSERT_EQ(avsDirective->getName(), NAME_TEST);
|
||||
ASSERT_EQ(avsDirective->getMessageId(), MESSAGE_ID_TEST);
|
||||
ASSERT_EQ(avsDirective->getDialogRequestId(), DIALOG_REQUEST_ID_TEST);
|
||||
};
|
||||
verifyArguments(avsDirective);
|
||||
return true;
|
||||
}));
|
||||
std::shared_ptr<Message> message = std::make_shared<Message>(SPEAK_DIRECTIVE, m_attachmentManager);
|
||||
m_messageInterpreter->receive(message);
|
||||
}
|
||||
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* NamespaceAndNameTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/NamespaceAndName.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace alexaClientSDK {
|
||||
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
/// @c nameSpace value for testing.
|
||||
static const std::string NAMESPACE_SPEECH_RECOGNIZER("SpeechRecognizer");
|
||||
|
||||
/// @c name value for testing.
|
||||
static const std::string NAME_RECOGNIZE("Recognize");
|
||||
|
||||
/// @c nameSpace value for testing.
|
||||
static const std::string NAMESPACE_SPEAKER("Speaker");
|
||||
|
||||
/// @c name value for testing.
|
||||
static const std::string NAME_SET_VOLUME("SetVolume");
|
||||
|
||||
/// NamespaceAndNameTest
|
||||
class NamespaceAndNameTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke default constructor. Expect @c nameSpace and @c name properties are both empty strings.
|
||||
*/
|
||||
TEST_F(NamespaceAndNameTest, testDefaultConstructor) {
|
||||
NamespaceAndName instance;
|
||||
ASSERT_TRUE(instance.nameSpace.empty());
|
||||
ASSERT_TRUE(instance.name.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke constructor with member values. Expect properties match those provided to the constructor.
|
||||
*/
|
||||
TEST_F(NamespaceAndNameTest, testConstructorWithValues) {
|
||||
NamespaceAndName instance(NAMESPACE_SPEECH_RECOGNIZER, NAME_RECOGNIZE);
|
||||
ASSERT_EQ(instance.nameSpace, NAMESPACE_SPEECH_RECOGNIZER);
|
||||
ASSERT_EQ(instance.name, NAME_RECOGNIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an @c std::unordered_map using NamespaceAndName values as keys. Expect that the keys map to distinct values.
|
||||
*/
|
||||
TEST_F(NamespaceAndNameTest, testInUnorderedMap) {
|
||||
std::unordered_map<NamespaceAndName, int> testMap;
|
||||
NamespaceAndName key1(NAMESPACE_SPEECH_RECOGNIZER, NAME_RECOGNIZE);
|
||||
NamespaceAndName key2(NAMESPACE_SPEAKER, NAME_SET_VOLUME);
|
||||
testMap[key1] = 1;
|
||||
testMap[key2] = 2;
|
||||
ASSERT_EQ(testMap[key1], 1);
|
||||
ASSERT_EQ(testMap[key2], 2);
|
||||
ASSERT_NE(testMap[key1], testMap[key2]);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
|
||||
|
||||
add_library(ADSLTestCommon
|
||||
MockDirectiveHandler.cpp)
|
||||
target_include_directories(ADSLTestCommon PUBLIC
|
||||
"${ADSL_SOURCE_DIR}/test/common"
|
||||
"${ADSL_INCLUDE_DIRS}"
|
||||
"${AVSCommon_INCLUDE_DIRS}")
|
||||
target_link_libraries(ADSLTestCommon
|
||||
ADSL
|
||||
AVSCommon
|
||||
gtest_main
|
||||
gmock_main)
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* MockAttachmentManager.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_ATTACHMENT_MANAGER_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_ATTACHMENT_MANAGER_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <AVSCommon/AttachmentManagerInterface.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* MockAttachmentManager used when creating @c AVSDirectives.
|
||||
*/
|
||||
class MockAttachmentManager : public avsCommon::AttachmentManagerInterface {
|
||||
public:
|
||||
MOCK_METHOD1(createAttachmentReader, std::future<std::shared_ptr<std::iostream>> (const std::string& attachmentId));
|
||||
MOCK_METHOD2(createAttachment, void (const std::string& attachmentId, std::shared_ptr<std::iostream> attachment));
|
||||
MOCK_METHOD1(releaseAttachment, void (const std::string& attachmentId));
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_ATTACHMENT_MANAGER_H_
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* DirectiveSequencerTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 "MockDirectiveHandler.h"
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
using namespace avsCommon;
|
||||
|
||||
/// Default amount of time taken to handle a directive.
|
||||
const std::chrono::milliseconds MockDirectiveHandler::DEFAULT_HANDLING_TIME_MS(0);
|
||||
|
||||
/// Timeout used when waiting for tests to complete (we should not reach this).
|
||||
const std::chrono::milliseconds MockDirectiveHandler::DEFAULT_DONE_TIMEOUT_MS(15000);
|
||||
|
||||
void DirectiveHandlerMockAdapter::preHandleDirective(
|
||||
std::shared_ptr<AVSDirective> directive, std::unique_ptr<DirectiveHandlerResultInterface> result) {
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> temp(std::move(result));
|
||||
preHandleDirective(directive, temp);
|
||||
}
|
||||
|
||||
std::shared_ptr<NiceMock<MockDirectiveHandler>> MockDirectiveHandler::create(std::chrono::milliseconds handlingTimeMs) {
|
||||
auto result = std::make_shared<NiceMock<MockDirectiveHandler>>(handlingTimeMs);
|
||||
ON_CALL(*result.get(), handleDirectiveImmediately(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockHandleDirectiveImmediately));
|
||||
ON_CALL(*result.get(), preHandleDirective(_, _)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockPreHandleDirective));
|
||||
ON_CALL(*result.get(), handleDirective(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockHandleDirective));
|
||||
ON_CALL(*result.get(), cancelDirective(_)).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockCancelDirective));
|
||||
ON_CALL(*result.get(), onDeregistered()).WillByDefault(
|
||||
Invoke(result.get(), &MockDirectiveHandler::mockOnDeregistered));
|
||||
return result;
|
||||
}
|
||||
|
||||
MockDirectiveHandler::MockDirectiveHandler(std::chrono::milliseconds handlingTimeMs) :
|
||||
m_handlingTimeMs{handlingTimeMs},
|
||||
m_isCompleted{false},
|
||||
m_isShuttingDown{false},
|
||||
m_preHandlingPromise{},
|
||||
m_preHandlingFuture{m_preHandlingPromise.get_future()},
|
||||
m_handlingPromise{},
|
||||
m_handlingFuture{m_handlingPromise.get_future()},
|
||||
m_cancelingPromise{},
|
||||
m_cancelingFuture{m_cancelingPromise.get_future()},
|
||||
m_completedPromise{},
|
||||
m_completedFuture{m_completedPromise.get_future()} {
|
||||
}
|
||||
|
||||
MockDirectiveHandler::~MockDirectiveHandler() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockHandleDirectiveImmediately(std::shared_ptr<AVSDirective> directive) {
|
||||
m_handlingPromise.set_value();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockPreHandleDirective(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result) {
|
||||
m_directive = directive;
|
||||
m_result = result;
|
||||
m_preHandlingPromise.set_value();
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::mockHandleDirective(const std::string& messageId) {
|
||||
if (!m_directive || m_directive->getMessageId() != messageId) {
|
||||
return false;
|
||||
}
|
||||
m_doHandleDirectiveThread = std::thread(&MockDirectiveHandler::doHandleDirective, this, messageId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockCancelDirective(const std::string& messageId) {
|
||||
if (!m_directive || m_directive->getMessageId() != messageId) {
|
||||
return;
|
||||
}
|
||||
m_cancelingPromise.set_value();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doHandleDirective(const std::string& messageId) {
|
||||
auto wake = [this]() {
|
||||
return m_isCompleted || m_isShuttingDown;
|
||||
};
|
||||
m_handlingPromise.set_value();
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_wakeNotifier.wait_for(lock, m_handlingTimeMs, wake);
|
||||
if (!m_isShuttingDown) {
|
||||
m_isCompleted = true;
|
||||
}
|
||||
if (m_isCompleted) {
|
||||
m_result->setCompleted();
|
||||
m_completedPromise.set_value();
|
||||
}
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::mockOnDeregistered() {
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doHandlingCompleted() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isCompleted = true;
|
||||
m_wakeNotifier.notify_all();
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::doPreHandlingFailed(
|
||||
std::shared_ptr<AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result) {
|
||||
m_directive = directive;
|
||||
m_result = result;
|
||||
m_result->setFailed("doPreHandlingFailed()");
|
||||
m_preHandlingPromise.set_value();
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::doHandlingFailed(const std::string& messageId) {
|
||||
if (!m_directive || m_directive->getMessageId() != messageId) {
|
||||
return false;
|
||||
}
|
||||
shutdown();
|
||||
m_result->setFailed("doHandlingFailed()");
|
||||
m_handlingPromise.set_value();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MockDirectiveHandler::shutdown() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_isShuttingDown = true;
|
||||
m_wakeNotifier.notify_all();
|
||||
}
|
||||
if (m_doHandleDirectiveThread.joinable()) {
|
||||
m_doHandleDirectiveThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilPreHandling(std::chrono::milliseconds timeout) {
|
||||
return m_preHandlingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilHandling(std::chrono::milliseconds timeout) {
|
||||
return m_handlingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilCanceling(std::chrono::milliseconds timeout) {
|
||||
return m_cancelingFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
bool MockDirectiveHandler::waitUntilCompleted(std::chrono::milliseconds timeout) {
|
||||
return m_completedFuture.wait_for(timeout) == std::future_status::ready;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* MockDirectiveHandler.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/DirectiveHandlerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* gmock does not fully support C++11's move only semantics. Replaces the use of unique_ptr in
|
||||
* DirectiveHandlerInterface with shared_ptr so that methods using unique_ptr can be mocked.
|
||||
*/
|
||||
class DirectiveHandlerMockAdapter : public DirectiveHandlerInterface {
|
||||
public:
|
||||
void preHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive, std::unique_ptr<DirectiveHandlerResultInterface> result) override;
|
||||
|
||||
/**
|
||||
* Variant of preHandleDirective taking a shared_ptr instead of a unique_ptr.
|
||||
*
|
||||
* @param directive The @c AVSDirective to be (pre) handled.
|
||||
* @param result The object to receive completion/failure notifications.
|
||||
*/
|
||||
virtual void preHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive, std::shared_ptr<DirectiveHandlerResultInterface> result) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* MockDirectiveHandler. This mock sports the following functionality:
|
||||
* - Spins up a thread when handleDirective() is invoked.
|
||||
* - Triggers early termination of the handling thread when cancelDirective() is invoked.
|
||||
* - Makes appropriate callbacks to DirectiveHandlerResultInterface instances passed bin by preHandle().
|
||||
* - Provides functions to dynamically trigger completion and failure.
|
||||
* - Provides methods to wait for the DirectiveHandlerInterface methods to be called.
|
||||
*
|
||||
* @note Each instance contains directive specific state, so it must only be be used to process a single directive.
|
||||
*/
|
||||
class MockDirectiveHandler : public DirectiveHandlerMockAdapter {
|
||||
public:
|
||||
/**
|
||||
* Create a MockDirectiveHandler.
|
||||
*
|
||||
* @param handlingTimeMs The amount of time (in milliseconds) this handler takes to handle directives.
|
||||
* @return A new MockDirectiveHandler.
|
||||
*/
|
||||
static std::shared_ptr<testing::NiceMock<MockDirectiveHandler>> create(
|
||||
std::chrono::milliseconds handlingTimeMs = DEFAULT_HANDLING_TIME_MS);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param handlingTimeMs The amount of time (in milliseconds) this handler takes to handle directives.
|
||||
*/
|
||||
MockDirectiveHandler(std::chrono::milliseconds handlingTimeMs);
|
||||
|
||||
/// Destructor.
|
||||
~MockDirectiveHandler();
|
||||
|
||||
/**
|
||||
* The functional part of mocking handleDirectiveImmediately().
|
||||
*
|
||||
* @param directive The directive to handle.
|
||||
*/
|
||||
void mockHandleDirectiveImmediately(std::shared_ptr<avsCommon::AVSDirective> directive);
|
||||
|
||||
/**
|
||||
* The functional part of mocking preHandleDirective().
|
||||
*
|
||||
* @param directive The directive to pre-handle.
|
||||
* @param result The result object to
|
||||
*/
|
||||
void mockPreHandleDirective(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result);
|
||||
|
||||
/**
|
||||
* The functional part of mocking handleDirective().
|
||||
*
|
||||
* @param messageId The MessageId of the @c AVSDirective to handle.
|
||||
* @return Whether handling the directive has begun.
|
||||
*/
|
||||
bool mockHandleDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* The functional part of mocking cancelDirective().
|
||||
*
|
||||
* @param directive The MessageId of the directive to cancel.
|
||||
*/
|
||||
void mockCancelDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* The functional part of mocking onDeregistered().
|
||||
*/
|
||||
void mockOnDeregistered();
|
||||
|
||||
/**
|
||||
* Method for m_doHandleDirectiveThread. Waits a specified time before reporting completion.
|
||||
* If cancelDirective() is called in the mean time, wake up and exit.
|
||||
*
|
||||
* @param messageId The messageId to pass to the observer when reporting completion.
|
||||
*/
|
||||
void doHandleDirective(const std::string& messageId);
|
||||
|
||||
/**
|
||||
* Fail preHandleDirective() by calling onDirectiveError().
|
||||
*
|
||||
* @param directive The directive to simulate the failure of.
|
||||
* @param result An object to receive the result of the handling operation.
|
||||
*/
|
||||
void doPreHandlingFailed(
|
||||
std::shared_ptr<avsCommon::AVSDirective> directive,
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> result);
|
||||
|
||||
/**
|
||||
* Trigger completion of handling.
|
||||
*/
|
||||
void doHandlingCompleted();
|
||||
|
||||
/**
|
||||
* Fail handleDirective() by calling onDirectiveError().
|
||||
*
|
||||
* @param messageId The MessageId of the directive to simulate the failure of.
|
||||
*/
|
||||
bool doHandlingFailed(const std::string& messageId);
|
||||
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Block until preHandleDirective() is called.
|
||||
*
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if preHandleDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilPreHandling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Block until handleDirective() is called.
|
||||
*
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if handleDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilHandling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Block until cancelDirective() is called.
|
||||
*
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if cancelDirective() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilCanceling(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/**
|
||||
* Block until completed() result is specified.
|
||||
*
|
||||
* @param timeout The max amount of time to wait (in milliseconds).
|
||||
* @return @c true if setCompleted() was called before the timeout.
|
||||
*/
|
||||
bool waitUntilCompleted(std::chrono::milliseconds timeout = DEFAULT_DONE_TIMEOUT_MS);
|
||||
|
||||
/// The amount of time (in milliseconds) handling a directive will take.
|
||||
std::chrono::milliseconds m_handlingTimeMs;
|
||||
|
||||
/// Object used to specify the result of handling a directive.
|
||||
std::shared_ptr<DirectiveHandlerResultInterface> m_result;
|
||||
|
||||
/// The @c AVSDirective (if any) being handled.
|
||||
std::shared_ptr<avsCommon::AVSDirective> m_directive;
|
||||
|
||||
/// Thread to perform handleDirective() asynchronously.
|
||||
std::thread m_doHandleDirectiveThread;
|
||||
|
||||
/// Mutex to protect m_isShuttingDown.
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// condition_variable use to wake doHandlingDirectiveThread.
|
||||
std::condition_variable m_wakeNotifier;
|
||||
|
||||
/// Whether or not handling completed.
|
||||
bool m_isCompleted;
|
||||
|
||||
/// Whether or not this handler is shutting down.
|
||||
bool m_isShuttingDown;
|
||||
|
||||
/// Promise fulfilled when preHandleDirective() begins.
|
||||
std::promise<void> m_preHandlingPromise;
|
||||
|
||||
/// Future to notify when preHandleDirective() begins.
|
||||
std::future<void> m_preHandlingFuture;
|
||||
|
||||
/// Promise fulfilled when handleDirective() begins.
|
||||
std::promise<void> m_handlingPromise;
|
||||
|
||||
/// Future to notify when handleDirective() begins.
|
||||
std::future<void> m_handlingFuture;
|
||||
|
||||
/// Promise fulfilled when cancelDirective() begins.
|
||||
std::promise<void> m_cancelingPromise;
|
||||
|
||||
/// Future to notify when cancelDirective() begins.
|
||||
std::shared_future<void> m_cancelingFuture;
|
||||
|
||||
/// Promise fulfilled when a completed result is reported.
|
||||
std::promise<void> m_completedPromise;
|
||||
|
||||
/// Future to notify when a completed result is reported.
|
||||
std::future<void> m_completedFuture;
|
||||
|
||||
MOCK_METHOD1(handleDirectiveImmediately, void(std::shared_ptr<avsCommon::AVSDirective>));
|
||||
MOCK_METHOD2(
|
||||
preHandleDirective,
|
||||
void(std::shared_ptr<avsCommon::AVSDirective>, std::shared_ptr<DirectiveHandlerResultInterface>));
|
||||
MOCK_METHOD1(handleDirective, bool(const std::string&));
|
||||
MOCK_METHOD1(cancelDirective, void(const std::string&));
|
||||
MOCK_METHOD0(onDeregistered, void());
|
||||
|
||||
/// Default amount of time taken to handle a directive (0).
|
||||
static const std::chrono::milliseconds DEFAULT_HANDLING_TIME_MS;
|
||||
|
||||
/// Timeout used when waiting for tests to complete (we should not reach this).
|
||||
static const std::chrono::milliseconds DEFAULT_DONE_TIMEOUT_MS;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_H_
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* MockDirectiveHandlerResult.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_RESULT_H_
|
||||
#define ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_RESULT_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "ADSL/DirectiveHandlerResultInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace adsl {
|
||||
namespace test {
|
||||
|
||||
/**
|
||||
* MockDirectiveHandlerResult
|
||||
*/
|
||||
class MockDirectiveHandlerResult : public DirectiveHandlerResultInterface {
|
||||
MOCK_METHOD0(setCompleted, void());
|
||||
MOCK_METHOD1(setFailed, void(const std::string&));
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace adsl
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_ADSL_TEST_COMMON_MOCK_DIRECTIVE_HANDLER_RESULT_H_
|
|
@ -89,11 +89,18 @@ public:
|
|||
* Notifies the Channel's observer to stop if the @c activityId matches the Channel's activity id.
|
||||
*
|
||||
* @param activityId The activity id to compare.
|
||||
*
|
||||
* @return Returns @c true if the activity on the Channel was stopped and @c false otherwise.
|
||||
* @return @c true if the activity on the Channel was stopped and @c false otherwise.
|
||||
*/
|
||||
bool stopActivity(const std::string& activityId);
|
||||
|
||||
/**
|
||||
* Checks whether the observer passed in currently owns the Channel.
|
||||
*
|
||||
* @param observer The observer to check.
|
||||
* @return @c true if the observer currently owns the Channel and @c false otherwise.
|
||||
*/
|
||||
bool doesObserverOwnChannel(std::shared_ptr<ChannelObserverInterface> observer) const;
|
||||
|
||||
private:
|
||||
/// The priority of the Channel.
|
||||
const unsigned int m_priority;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#ifndef ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUS_MANAGER_H_
|
||||
#define ALEXA_CLIENT_SDK_AFML_INCLUDE_AFML_FOCUS_MANAGER_H_
|
||||
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
@ -41,8 +42,8 @@ namespace afml {
|
|||
* acquire Channel - clients should call the acquireChannel() method, passing in the name of the Channel they wish to
|
||||
* acquire, a pointer to the observer that they want to be notified once they get focus, and a unique activity id.
|
||||
*
|
||||
* release Channel - clients should call the releaseChannel() method, passing in the name of the Channel they wish to
|
||||
* release.
|
||||
* release Channel - clients should call the releaseChannel() method, passing in the name of the Channel and the
|
||||
* observer of the Channel they wish to release.
|
||||
*
|
||||
* stop foreground Channel - clients should call the stopForegroundActivitiy() method.
|
||||
*
|
||||
|
@ -112,16 +113,17 @@ public:
|
|||
const std::string& activityId);
|
||||
|
||||
/**
|
||||
* This method will release the Channel by notifying the observer of the Channel to stop via
|
||||
* ChannelObserverInterface##onFocusChanged(). If the Channel to release is the current foreground focused Channel,
|
||||
* it will also notify the next highest priority Channel via an ChannelObserverInterface##onFocusChanged() callback
|
||||
* that it has gained foreground focus.
|
||||
* This method will release the Channel and notify the observer of the Channel, if the observer is the same as the
|
||||
* observer passed in the acquireChannel call, to stop via ChannelObserverInterface##onFocusChanged(). If the
|
||||
* Channel to release is the current foreground focused Channel, it will also notify the next highest priority
|
||||
* Channel via an ChannelObserverInterface##onFocusChanged() callback that it has gained foreground focus.
|
||||
*
|
||||
* @param channelName The name of the Channel to release.
|
||||
*
|
||||
* @return Returns @c true if the Channel can be released and @c false otherwise.
|
||||
* @param channelObserver The observer to be released from the Channel.
|
||||
* @return @c std::future<bool> which will contain @c true if the Channel can be released and @c false otherwise.
|
||||
*/
|
||||
bool releaseChannel(const std::string& channelName);
|
||||
std::future<bool> releaseChannel(
|
||||
const std::string& channelName, std::shared_ptr<ChannelObserverInterface> channelObserver);
|
||||
|
||||
/**
|
||||
* This method will request that the currently foregrounded Channel activity be stopped, if there is one. This will
|
||||
|
@ -167,8 +169,15 @@ private:
|
|||
* implementation which the public method will call.
|
||||
*
|
||||
* @param channelToRelease The Channel to release.
|
||||
* @param channelObserver The observer of the Channel to release.
|
||||
* @param releaseChannelSuccess The promise to satisfy.
|
||||
* @param channelName The name of the Channel.
|
||||
*/
|
||||
void releaseChannelHelper(std::shared_ptr<Channel> channelToRelease);
|
||||
void releaseChannelHelper(
|
||||
std::shared_ptr<Channel> channelToRelease,
|
||||
std::shared_ptr<ChannelObserverInterface> channelObserver,
|
||||
std::shared_ptr<std::promise<bool>> releaseChannelSuccess,
|
||||
const std::string& channelName);
|
||||
|
||||
/**
|
||||
* Stops the Channel specified and updates other Channels as needed if the activity id passed in matches the
|
||||
|
|
|
@ -73,5 +73,9 @@ bool Channel::stopActivity(const std::string& activityId) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Channel::doesObserverOwnChannel(std::shared_ptr<ChannelObserverInterface> observer) const {
|
||||
return observer == m_observer;
|
||||
}
|
||||
|
||||
} // namespace afml
|
||||
} // namespace alexaClientSDK
|
|
@ -54,18 +54,25 @@ bool FocusManager::acquireChannel(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FocusManager::releaseChannel(const std::string& channelName) {
|
||||
std::future<bool> FocusManager::releaseChannel(
|
||||
const std::string& channelName, std::shared_ptr<ChannelObserverInterface> channelObserver) {
|
||||
// Using a shared_ptr here so that the promise stays in scope by the time the Executor picks up the task.
|
||||
auto releaseChannelSuccess = std::make_shared<std::promise<bool>>();
|
||||
std::future<bool> returnValue = releaseChannelSuccess->get_future();
|
||||
std::shared_ptr<Channel> channelToRelease = getChannel(channelName);
|
||||
if (!channelToRelease) {
|
||||
Logger::log("Unable to release Channel: '" + channelName + "'.");
|
||||
return false;
|
||||
releaseChannelSuccess->set_value(false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
m_executor.submit(
|
||||
[this, channelToRelease] () {
|
||||
releaseChannelHelper(channelToRelease);
|
||||
[this, channelToRelease, channelObserver, releaseChannelSuccess, channelName] () {
|
||||
releaseChannelHelper(channelToRelease, channelObserver, releaseChannelSuccess, channelName);
|
||||
}
|
||||
);
|
||||
return true;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void FocusManager::stopForegroundActivity() {
|
||||
|
@ -111,7 +118,18 @@ void FocusManager::acquireChannelHelper(
|
|||
}
|
||||
}
|
||||
|
||||
void FocusManager::releaseChannelHelper(std::shared_ptr<Channel> channelToRelease) {
|
||||
void FocusManager::releaseChannelHelper(
|
||||
std::shared_ptr<Channel> channelToRelease,
|
||||
std::shared_ptr<ChannelObserverInterface> channelObserver,
|
||||
std::shared_ptr<std::promise<bool>> releaseChannelSuccess,
|
||||
const std::string& name) {
|
||||
if (!channelToRelease->doesObserverOwnChannel(channelObserver)) {
|
||||
Logger::log("Unable to release Channel " + name + " because caller does not own Channel");
|
||||
releaseChannelSuccess->set_value(false);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseChannelSuccess->set_value(true);
|
||||
// Lock here to update internal state which stopForegroundActivity may concurrently access.
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
bool wasForegrounded = isChannelForegroundedLocked(channelToRelease);
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "AFML/FocusManager.h"
|
||||
|
@ -25,8 +23,11 @@
|
|||
namespace alexaClientSDK {
|
||||
namespace afml {
|
||||
|
||||
/// Time out for the Channel observer to wait for the focus change callback.
|
||||
static const auto TIME_OUT_IN_SECONDS = std::chrono::seconds(30);
|
||||
/// Short time out for when callbacks are expected not to occur.
|
||||
static const auto SHORT_TIMEOUT = std::chrono::milliseconds(50);
|
||||
|
||||
/// Long time out for the Channel observer to wait for the focus change callback (we should not reach this).
|
||||
static const auto DEFAULT_TIMEOUT = std::chrono::seconds(15);
|
||||
|
||||
/// The dialog Channel name used in intializing the FocusManager.
|
||||
static const std::string DIALOG_CHANNEL_NAME = "DialogChannel";
|
||||
|
@ -64,11 +65,92 @@ static const std::string DIFFERENT_DIALOG_ACTIVITY_ID = "different dialog";
|
|||
/// A test observer that mocks out the ChannelObserverInterface##onFocusChanged() call.
|
||||
class TestClient : public ChannelObserverInterface {
|
||||
public:
|
||||
MOCK_METHOD1(onFocusChanged, void(FocusState focusState));
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
TestClient() :
|
||||
m_focusState(FocusState::NONE), m_focusChangeOccurred(false) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the ChannelObserverInterface##onFocusChanged() callback.
|
||||
*
|
||||
* @param focusState The new focus state of the Channel observer.
|
||||
*/
|
||||
void onFocusChanged(FocusState focusState) override {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusState = focusState;
|
||||
m_focusChangeOccurred = true;
|
||||
m_focusChanged.notify_one();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the ChannelObserverInterface##onFocusChanged() callback.
|
||||
*
|
||||
* @param timeout The amount of time to wait for the callback.
|
||||
* @param focusChanged An output parameter that notifies the caller whether a callback occurred.
|
||||
* @return Returns @c true if the callback occured within the timeout period and @c false otherwise.
|
||||
*/
|
||||
FocusState waitForFocusChange(std::chrono::milliseconds timeout, bool* focusChanged) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
bool success = m_focusChanged.wait_for(lock, timeout, [this] () {
|
||||
return m_focusChangeOccurred;
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
*focusChanged = false;
|
||||
} else {
|
||||
m_focusChangeOccurred = false;
|
||||
*focusChanged = true;
|
||||
}
|
||||
return m_focusState;
|
||||
}
|
||||
|
||||
private:
|
||||
/// The focus state of the observer.
|
||||
FocusState m_focusState;
|
||||
|
||||
/// A lock to guard against focus state changes.
|
||||
std::mutex m_mutex;
|
||||
|
||||
/// A condition variable to wait for focus changes.
|
||||
std::condition_variable m_focusChanged;
|
||||
|
||||
/// A boolean flag so that we can re-use the observer even after a callback has occurred.
|
||||
bool m_focusChangeOccurred;
|
||||
};
|
||||
|
||||
/// Manages testing focus changes
|
||||
class FocusChangeManager {
|
||||
public:
|
||||
/**
|
||||
* Checks that a focus change occurred and that the focus state received is the same as the expected focus state.
|
||||
*
|
||||
* @param client The Channel observer.
|
||||
* @param expectedFocusState The expected focus state.
|
||||
*/
|
||||
void assertFocusChange(std::shared_ptr<TestClient> client, FocusState expectedFocusState) {
|
||||
bool focusChanged = false;
|
||||
FocusState focusStateReceived = client->waitForFocusChange(DEFAULT_TIMEOUT, &focusChanged);
|
||||
ASSERT_TRUE(focusChanged);
|
||||
ASSERT_EQ(expectedFocusState, focusStateReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a focus change does not occur by waiting for the timeout duration.
|
||||
*
|
||||
* @param client The Channel observer.
|
||||
*/
|
||||
void assertNoFocusChange(std::shared_ptr<TestClient> client) {
|
||||
bool focusChanged = false;
|
||||
// Will wait for the short timeout duration before succeeding
|
||||
client->waitForFocusChange(SHORT_TIMEOUT, &focusChanged);
|
||||
ASSERT_FALSE(focusChanged);
|
||||
}
|
||||
};
|
||||
|
||||
/// Test fixture for testing FocusManager.
|
||||
class FocusManagerTest : public ::testing::Test {
|
||||
class FocusManagerTest : public ::testing::Test, public FocusChangeManager {
|
||||
protected:
|
||||
/// The FocusManager.
|
||||
std::shared_ptr<FocusManager> m_focusManager;
|
||||
|
@ -85,12 +167,6 @@ protected:
|
|||
/// A client that acquires the content Channel.
|
||||
std::shared_ptr<TestClient> contentClient;
|
||||
|
||||
/// A condition variable used to wait for all the onFocusChanged() calls.
|
||||
std::condition_variable m_cv;
|
||||
|
||||
/// A lock used used to wait for all the onFocusChanged() calls.
|
||||
std::mutex m_mutex;
|
||||
|
||||
virtual void SetUp() {
|
||||
FocusManager::ChannelConfiguration dialogChannelConfig{DIALOG_CHANNEL_NAME, DIALOG_CHANNEL_PRIORITY};
|
||||
|
||||
|
@ -118,98 +194,32 @@ TEST_F(FocusManagerTest, acquireInvalidChannelName) {
|
|||
|
||||
/// Tests acquireChannel, expecting to get Foreground status since no other Channels are active.
|
||||
TEST_F(FocusManagerTest, acquireChannelWithNoOtherChannelsActive) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 1;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests acquireChannel with a two Channel. The lower priority Channel should get Background focus and the higher
|
||||
* Tests acquireChannel with two Channels. The lower priority Channel should get Background focus and the higher
|
||||
* priority Channel should get Foreground focus.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, acquireLowerPriorityChannelWithOneHigherPriorityChannelTaken) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 2;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*alertsClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
assertFocusChange(alertsClient, FocusState::BACKGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests acquireChannel with three Channels. The two lowest priority Channels should get Background focus while the
|
||||
* highest priority Channel should be Foreground focused.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, acquireLowerPriorityChannelWithTwoHigherPriorityChannelsTaken) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*alertsClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
TEST_F(FocusManagerTest, aquireLowerPriorityChannelWithTwoHigherPriorityChannelsTaken) {
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID));
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
assertFocusChange(alertsClient, FocusState::BACKGROUND);
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,41 +228,12 @@ TEST_F(FocusManagerTest, acquireLowerPriorityChannelWithTwoHigherPriorityChannel
|
|||
* should be Foreground focused.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, acquireHigherPriorityChannelWithOneLowerPriorityChannelTaken) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,73 +241,37 @@ TEST_F(FocusManagerTest, acquireHigherPriorityChannelWithOneLowerPriorityChannel
|
|||
* should obtain Foreground focus.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, kickOutActivityOnSameChannel) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*anotherDialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
assertFocusChange(anotherDialogClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests releaseChannel with a single Channel. The observer should be notified to stop.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, simpleReleaseChannel) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 2;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, dialogClient).get());
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests releaseChannel on a Channel with an incorrect observer. The client should not receive any callback.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, simpleReleaseChannelWithIncorrectObserver) {
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_FALSE(m_focusManager->releaseChannel(CONTENT_CHANNEL_NAME, dialogClient).get());
|
||||
ASSERT_FALSE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, contentClient).get());
|
||||
|
||||
assertNoFocusChange(dialogClient);
|
||||
assertNoFocusChange(contentClient);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -335,84 +280,26 @@ TEST_F(FocusManagerTest, simpleReleaseChannel) {
|
|||
* be notified to stop.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, releaseForegroundChannelWhileBackgroundChannelTaken) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 4;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->releaseChannel(DIALOG_CHANNEL_NAME, dialogClient).get());
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
assertFocusChange(contentClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests stopForegroundActivity with a single Channel. The observer should be notified to stop.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, simpleNonTargetedStop) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 2;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,93 +307,25 @@ TEST_F(FocusManagerTest, simpleNonTargetedStop) {
|
|||
* stop each time and the next highest priority background Channel should be brought to the foreground each time.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, threeNonTargetedStopsWithThreeActivitiesHappening) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 8;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*alertsClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*alertsClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*alertsClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(ALERTS_CHANNEL_NAME, alertsClient, ALERTS_ACTIVITY_ID));
|
||||
assertFocusChange(alertsClient, FocusState::BACKGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
assertFocusChange(alertsClient, FocusState::FOREGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(alertsClient, FocusState::NONE);
|
||||
assertFocusChange(contentClient, FocusState::FOREGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(contentClient, FocusState::NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -514,43 +333,14 @@ TEST_F(FocusManagerTest, threeNonTargetedStopsWithThreeActivitiesHappening) {
|
|||
* foreground focus.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireDifferentChannel) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -558,42 +348,14 @@ TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireDifferentChannel) {
|
|||
* foreground focus.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireSameChannel) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
lock.unlock();
|
||||
m_focusManager->stopForegroundActivity();
|
||||
}
|
||||
)
|
||||
)
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
m_focusManager->stopForegroundActivity();
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -601,42 +363,16 @@ TEST_F(FocusManagerTest, stopForegroundActivityAndAcquireSameChannel) {
|
|||
* should remain foregrounded while the background Channel's observer should be notified to stop.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, releaseBackgroundChannelWhileTwoChannelsTaken) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 3;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_focusManager->releaseChannel(CONTENT_CHANNEL_NAME);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->releaseChannel(CONTENT_CHANNEL_NAME, contentClient).get());
|
||||
assertFocusChange(contentClient, FocusState::NONE);
|
||||
|
||||
assertNoFocusChange(dialogClient);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -645,55 +381,21 @@ TEST_F(FocusManagerTest, releaseBackgroundChannelWhileTwoChannelsTaken) {
|
|||
* Foreground focus. The originally backgrounded Channel should not change focus.
|
||||
*/
|
||||
TEST_F(FocusManagerTest, kickOutActivityOnSameChannelWhileOtherChannelsActive) {
|
||||
std::atomic<int> numCalls(0);
|
||||
const int expectedNumCalls = 4;
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*contentClient, onFocusChanged(FocusState::BACKGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*dialogClient, onFocusChanged(FocusState::NONE))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
EXPECT_CALL(*anotherDialogClient, onFocusChanged(FocusState::FOREGROUND))
|
||||
.WillOnce(testing::InvokeWithoutArgs(
|
||||
[this, &numCalls] () {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
++numCalls;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
)
|
||||
);
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID);
|
||||
m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_ACTIVITY_ID);
|
||||
m_cv.wait_for(lock, TIME_OUT_IN_SECONDS, [&numCalls, expectedNumCalls]() {
|
||||
return numCalls.load() == expectedNumCalls;
|
||||
});
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, dialogClient, DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(CONTENT_CHANNEL_NAME, contentClient, CONTENT_ACTIVITY_ID));
|
||||
assertFocusChange(contentClient, FocusState::BACKGROUND);
|
||||
|
||||
ASSERT_TRUE(m_focusManager->acquireChannel(DIALOG_CHANNEL_NAME, anotherDialogClient, DIFFERENT_DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(dialogClient, FocusState::NONE);
|
||||
assertFocusChange(anotherDialogClient, FocusState::FOREGROUND);
|
||||
|
||||
assertNoFocusChange(contentClient);
|
||||
}
|
||||
|
||||
/// Test fixture for testing Channel.
|
||||
class ChannelTest : public ::testing::Test {
|
||||
class ChannelTest : public ::testing::Test, public FocusChangeManager {
|
||||
protected:
|
||||
/// A test client that used to observe Channels.
|
||||
std::shared_ptr<TestClient> clientA;
|
||||
|
@ -714,56 +416,57 @@ protected:
|
|||
|
||||
/// Tests the that the getPriority method of Channel works properly.
|
||||
TEST_F(ChannelTest, getPriority) {
|
||||
EXPECT_EQ(testChannel->getPriority(), DIALOG_CHANNEL_PRIORITY);
|
||||
ASSERT_EQ(testChannel->getPriority(), DIALOG_CHANNEL_PRIORITY);
|
||||
}
|
||||
|
||||
/// Tests that an old observer is kicked out on a Channel when a new observer is set.
|
||||
TEST_F(ChannelTest, kickoutOldObserver) {
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::FOREGROUND));
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::NONE));
|
||||
testChannel->setObserver(clientA);
|
||||
|
||||
testChannel->setFocus(FocusState::FOREGROUND);
|
||||
assertFocusChange(clientA, FocusState::FOREGROUND);
|
||||
|
||||
testChannel->setObserver(clientB);
|
||||
assertFocusChange(clientA, FocusState::NONE);
|
||||
}
|
||||
|
||||
/// Tests that the observer properly gets notified of focus changes.
|
||||
TEST_F(ChannelTest, setObserverThenSetFocus) {
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::FOREGROUND));
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::BACKGROUND));
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::NONE));
|
||||
testChannel->setObserver(clientA);
|
||||
|
||||
testChannel->setFocus(FocusState::FOREGROUND);
|
||||
assertFocusChange(clientA, FocusState::FOREGROUND);
|
||||
|
||||
testChannel->setFocus(FocusState::BACKGROUND);
|
||||
assertFocusChange(clientA, FocusState::BACKGROUND);
|
||||
|
||||
testChannel->setFocus(FocusState::NONE);
|
||||
assertFocusChange(clientA, FocusState::NONE);
|
||||
}
|
||||
|
||||
/// Tests that Channels are compared properly
|
||||
TEST_F(ChannelTest, priorityComparison) {
|
||||
std::shared_ptr<Channel> lowerPriorityChannel = std::make_shared<Channel>(CONTENT_CHANNEL_PRIORITY);
|
||||
EXPECT_TRUE(*testChannel > *lowerPriorityChannel);
|
||||
EXPECT_FALSE(*lowerPriorityChannel > *testChannel);
|
||||
ASSERT_TRUE(*testChannel > *lowerPriorityChannel);
|
||||
ASSERT_FALSE(*lowerPriorityChannel > *testChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
*Tests that the stopActivity method on Channel works properly and that observers are stopped if the activity id
|
||||
* Tests that the stopActivity method on Channel works properly and that observers are stopped if the activity id
|
||||
* matches the the Channel's activity and doesn't get stopped if the ids don't match.
|
||||
*/
|
||||
TEST_F(ChannelTest, testStopActivityWithSameId) {
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::FOREGROUND));
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::NONE));
|
||||
TEST_F(ChannelTest, testStopActivity) {
|
||||
testChannel->setActivityId(DIALOG_ACTIVITY_ID);
|
||||
testChannel->setObserver(clientA);
|
||||
testChannel->setFocus(FocusState::FOREGROUND);
|
||||
testChannel->stopActivity(DIALOG_ACTIVITY_ID);
|
||||
}
|
||||
|
||||
TEST_F(ChannelTest, testStopActivityWithDifferentId) {
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::FOREGROUND));
|
||||
EXPECT_CALL(*clientA, onFocusChanged(FocusState::NONE)).Times(0);
|
||||
testChannel->setActivityId(DIALOG_ACTIVITY_ID);
|
||||
testChannel->setObserver(clientA);
|
||||
testChannel->setFocus(FocusState::FOREGROUND);
|
||||
testChannel->stopActivity(CONTENT_ACTIVITY_ID);
|
||||
assertFocusChange(clientA, FocusState::FOREGROUND);
|
||||
|
||||
ASSERT_FALSE(testChannel->stopActivity(CONTENT_ACTIVITY_ID));
|
||||
assertNoFocusChange(clientA);
|
||||
|
||||
ASSERT_TRUE(testChannel->stopActivity(DIALOG_ACTIVITY_ID));
|
||||
assertFocusChange(clientA, FocusState::NONE);
|
||||
}
|
||||
|
||||
} // namespace afml
|
||||
|
|
|
@ -21,10 +21,8 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ACL/Message.h>
|
||||
#include <ACL/AttachmentManagerInterface.h>
|
||||
|
||||
#include "AVSCommon/AVSMessage.h"
|
||||
#include "AttachmentManagerInterface.h"
|
||||
#include "AVSMessage.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsCommon {
|
||||
|
@ -46,7 +44,7 @@ public:
|
|||
static std::unique_ptr<AVSDirective> create(const std::string& unparsedDirective,
|
||||
std::shared_ptr<AVSMessageHeader> avsMessageHeader,
|
||||
const std::string& payload,
|
||||
std::shared_ptr<acl::AttachmentManagerInterface> attachmentManager);
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/**
|
||||
* Returns a reader for the attachment associated with this directive.
|
||||
|
@ -73,12 +71,12 @@ private:
|
|||
AVSDirective(const std::string& unparsedDirective,
|
||||
std::shared_ptr<AVSMessageHeader> avsMessageHeader,
|
||||
const std::string& payload,
|
||||
std::shared_ptr<acl::AttachmentManagerInterface> attachmentManager);
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager);
|
||||
|
||||
/// The unparsed directive JSON string from AVS.
|
||||
const std::string m_unparsedDirective;
|
||||
/// Object knows how to find the attachment based on the attachmentId.
|
||||
std::shared_ptr<acl::AttachmentManagerInterface> m_attachmentManager;
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> m_attachmentManager;
|
||||
};
|
||||
|
||||
} // namespace avsCommon
|
||||
|
|
|
@ -81,6 +81,13 @@ public:
|
|||
*/
|
||||
std::string getPayload() const;
|
||||
|
||||
/**
|
||||
* Return a string representation of this @c AVSMessage's header.
|
||||
*
|
||||
* @return A string representation of this @c AVSMessage's header.
|
||||
*/
|
||||
std::string getHeaderAsString() const;
|
||||
|
||||
private:
|
||||
/// The fields that represent the common items in the header of an AVS message.
|
||||
std::shared_ptr<AVSMessageHeader> m_header;
|
||||
|
|
|
@ -74,6 +74,13 @@ public:
|
|||
*/
|
||||
std::string getDialogRequestId() const;
|
||||
|
||||
/**
|
||||
* Return a string representation of this @c AVSMessage's header.
|
||||
*
|
||||
* @return A string representation of this @c AVSMessage's header.
|
||||
*/
|
||||
std::string getAsString() const;
|
||||
|
||||
private:
|
||||
/// Namespace of the AVSMessage header.
|
||||
const std::string m_namespace;
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_H_
|
||||
#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_H_
|
||||
#ifndef ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_H_
|
||||
#define ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
|
@ -28,7 +28,7 @@
|
|||
#include "AttachmentManagerInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
namespace avsCommon {
|
||||
|
||||
/**
|
||||
* Class that manages how attachment is stored and retrieved.
|
||||
|
@ -84,7 +84,7 @@ private:
|
|||
std::chrono::minutes m_timeout;
|
||||
};
|
||||
|
||||
} // namespace acl
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif //ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_H_
|
||||
#endif //ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_H_
|
|
@ -14,8 +14,8 @@
|
|||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
#ifndef ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_INTERFACE_H_
|
||||
#define ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_INTERFACE_H_
|
||||
#ifndef ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_INTERFACE_H_
|
||||
#define ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_INTERFACE_H_
|
||||
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
|
@ -23,7 +23,7 @@
|
|||
#include <string>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
namespace avsCommon {
|
||||
|
||||
/// Interface class that manages how attachments are stored and retrieved.
|
||||
class AttachmentManagerInterface {
|
||||
|
@ -58,7 +58,7 @@ public:
|
|||
virtual void releaseAttachment(const std::string& attachmentId) = 0;
|
||||
};
|
||||
|
||||
} // namespace acl
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif //ALEXA_CLIENT_SDK_ACL_INCLUDE_ACL_ATTACHMENT_MANAGER_INTERFACE_H_
|
||||
#endif //ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_ATTACHMENT_MANAGER_INTERFACE_H_
|
|
@ -18,7 +18,7 @@
|
|||
#ifndef ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_EXCEPTION_ENCOUNTERED_SENDER_INTERFACE_H_
|
||||
#define ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_EXCEPTION_ENCOUNTERED_SENDER_INTERFACE_H_
|
||||
|
||||
#include "AVSCommon/ExceptionEncountered.h"
|
||||
#include "ExceptionEncountered.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsCommon {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* hash.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_FUNCTIONAL_HASH_H_
|
||||
#define ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_FUNCTIONAL_HASH_H_
|
||||
|
||||
#include <climits>
|
||||
#include <functional>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsCommon {
|
||||
namespace functional {
|
||||
|
||||
/**
|
||||
* Function to combine hash values in to a single hash value.
|
||||
* Combination by XOR. A circular rotate left is applied to the seed value before the XOR so that combining hashes
|
||||
* generated by the same hash function for the same value (a common use case) won't degenerate to zero.
|
||||
*
|
||||
* @param seed Accumulated value from multiple calls.
|
||||
* @param value The next value whose hash is to be combined.
|
||||
*/
|
||||
template<typename Type> void hashCombine(size_t& seed, Type const& value) {
|
||||
constexpr int bitsMinus1 = (CHAR_BIT * sizeof(size_t)) - 1;
|
||||
std::hash<Type> hasher;
|
||||
seed = hasher(value) ^ ((seed << 1) | ((seed >> bitsMinus1) & 1));
|
||||
}
|
||||
|
||||
} // namespace functional
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif //ALEXA_CLIENT_SDK_AVS_COMMON_INCLUDE_AVS_COMMON_FUNCTIONAL_HASH_H_
|
|
@ -28,7 +28,7 @@ std::unique_ptr<AVSDirective> AVSDirective::create(
|
|||
const std::string& unparsedDirective,
|
||||
std::shared_ptr<AVSMessageHeader> avsMessageHeader,
|
||||
const std::string& payload,
|
||||
std::shared_ptr<acl::AttachmentManagerInterface> attachmentManager) {
|
||||
std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager) {
|
||||
if (!avsMessageHeader) {
|
||||
Logger::log("AVSDirective::create - message header was nullptr.");
|
||||
return nullptr;
|
||||
|
@ -47,7 +47,7 @@ std::future<std::shared_ptr<std::iostream>> AVSDirective::getAttachmentReader(co
|
|||
}
|
||||
|
||||
AVSDirective::AVSDirective(const std::string& unparsedDirective, std::shared_ptr<AVSMessageHeader> avsMessageHeader,
|
||||
const std::string& payload, std::shared_ptr<acl::AttachmentManagerInterface> attachmentManager) :
|
||||
const std::string& payload, std::shared_ptr<avsCommon::AttachmentManagerInterface> attachmentManager) :
|
||||
AVSMessage{avsMessageHeader, payload},
|
||||
m_unparsedDirective{unparsedDirective},
|
||||
m_attachmentManager{attachmentManager} {
|
||||
|
@ -57,5 +57,5 @@ std::string AVSDirective::getUnparsedDirective() const {
|
|||
return m_unparsedDirective;
|
||||
}
|
||||
|
||||
} // namespace alexaClientSDK
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include <ACL/Message.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "AVSCommon/AVSMessage.h"
|
||||
|
@ -49,5 +48,9 @@ std::string AVSMessage::getPayload() const {
|
|||
return m_payload;
|
||||
}
|
||||
|
||||
std::string AVSMessage::getHeaderAsString() const {
|
||||
return m_header->getAsString();
|
||||
}
|
||||
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
|
|
@ -36,5 +36,14 @@ std::string AVSMessageHeader::getDialogRequestId() const {
|
|||
return m_dialogRequestId;
|
||||
}
|
||||
|
||||
std::string AVSMessageHeader::getAsString() const {
|
||||
return std::string() +
|
||||
"{\"namespace:\"" + m_namespace +
|
||||
"\",name:\"" + m_name +
|
||||
"\",messageId:\"" + m_messageId +
|
||||
"\",dialogRequestId:\"" + m_dialogRequestId +
|
||||
"\"}";
|
||||
}
|
||||
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
|
@ -15,12 +15,13 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AVSCommon/AttachmentManager.h"
|
||||
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include "AVSUtils/Memory/Memory.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
namespace avsCommon {
|
||||
|
||||
using namespace avsUtils;
|
||||
|
||||
|
@ -81,5 +82,5 @@ void AttachmentManager::createAttachmentPromiseHelperLocked(const std::string& a
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace acl
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
|
@ -5,6 +5,6 @@ file(GLOB_RECURSE AVSCommonSourceFiles "${AVSCommon_SOURCE_DIR}/src/*.cpp")
|
|||
add_library(AVSCommon SHARED "${AVSCommonSourceFiles}")
|
||||
|
||||
target_include_directories(AVSCommon PUBLIC "${AVSCommon_SOURCE_DIR}/include" "${AVSUtils_SOURCE_DIR}/include"
|
||||
"${ACL_SOURCE_DIR}/ACL/include" "${RAPIDJSON_INCLUDE_DIR}")
|
||||
target_link_libraries(AVSCommon ACL AVSUtils)
|
||||
"${RAPIDJSON_INCLUDE_DIR}")
|
||||
target_link_libraries(AVSCommon AVSUtils)
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* MockExceptionEncounteredSenderInterface.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AVS_COMMON_TEST_AVS_COMMON_MOCK_EXCEPTION_ENCOUNTERED_SENDER_H_
|
||||
#define ALEXA_CLIENT_SDK_AVS_COMMON_TEST_AVS_COMMON_MOCK_EXCEPTION_ENCOUNTERED_SENDER_H_
|
||||
|
||||
#include "AVSCommon/ExceptionEncounteredSenderInterface.h"
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsCommon {
|
||||
|
||||
/**
|
||||
* Mock class that implements the ExceptionEncounteredSenderInterface.
|
||||
*/
|
||||
class MockExceptionEncounteredSender : public ExceptionEncounteredSenderInterface {
|
||||
public:
|
||||
MOCK_METHOD3(sendExceptionEncountered, void(const std::string& unparsedDirective, ExceptionErrorType error,
|
||||
const std::string& errorDescription));
|
||||
};
|
||||
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_AVS_COMMON_TEST_AVS_COMMON_MOCK_EXCEPTION_ENCOUNTERED_SENDER_H_
|
|
@ -22,10 +22,10 @@
|
|||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "ACL/AttachmentManager.h"
|
||||
#include "AVSCommon/AttachmentManager.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace acl {
|
||||
namespace avsCommon {
|
||||
|
||||
/// Time out to wait for an attachment.
|
||||
static const std::chrono::milliseconds TIME_OUT_IN_MILLISECONDS(50);
|
||||
|
@ -165,5 +165,5 @@ TEST_F(AttachmentManagerTest, readAttachmentAfterReleasing) {
|
|||
ASSERT_EQ(status, std::future_status::timeout);
|
||||
}
|
||||
|
||||
} // namespace acl
|
||||
} // namespace alexaClientSDK
|
||||
} // namespace avsCommon
|
||||
} // namespace alexaClientSDK
|
|
@ -6,4 +6,5 @@ set(INCLUDES "${AVSCommon_SOURCE_DIR}/include" "${AVSUtils_SOURCE_DIR}/include"
|
|||
"${ACL_SOURCE_DIR}/test" "${RAPIDJSON_INCLUDE_DIR}")
|
||||
|
||||
set(LIBRARIES AVSCommon ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
discover_unit_tests("${INCLUDES}" "${LIBRARIES}")
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
#include "AVSCommon/AVSDirective.h"
|
||||
#include "AVSCommon/JSON/JSONUtils.h"
|
||||
#include "ACL/Message.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsCommon {
|
||||
|
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* ConfigurationNode.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_CONFIGURATION_CONFIGURATION_NODE_H_
|
||||
#define ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_CONFIGURATION_CONFIGURATION_NODE_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace configuration {
|
||||
|
||||
/**
|
||||
* Class providing access to a global read-only configuration object. This object is a tree structure comprised of
|
||||
* @c ConfigurationNode instances that contain key-value pairs (including @c ConfigurationNode values).
|
||||
*
|
||||
* Methods to fetch a named value from a @c ConfigurationNode are of the form:
|
||||
* @code
|
||||
* bool get<Type>(const char* key, <Type>* out = nullptr, <Type> defaultValue = simpleDefault)
|
||||
* @endcode
|
||||
* This signature allows for fetching a value without the risk of an assertion or exception if the value is not
|
||||
* present. It also allows for checking if a key is present, or combining the check and fetch operations
|
||||
* in a single call.
|
||||
*
|
||||
* Sub @c ConfigurationNodes are accessed via operator[], where the index is the name of the sub @c ConfigurationNode.
|
||||
* If a key for a @c ConfigurationNode does not exist an empty @c ConfigurationNode is returned instead. This allows
|
||||
* for simple and safe traversal of the configuration hierarchy. For example:
|
||||
* @code
|
||||
* std::string tempString;
|
||||
* ConfigurationNode::getRoot()["someComponent"]["someSubComponent"].getString("someKey", &tempString);
|
||||
* @endcode
|
||||
*
|
||||
* The configuration is specified via JSON documents with a root object that corresponds to the root
|
||||
* @c ConfigurationNode value returned by ConfigurationNode::getRoot(). ConfiguriatonNode Sub-nodes accessed by operator[<key>]
|
||||
* correspond to JSON objects values with the of the name <key>. So, the code example above would return
|
||||
* "someStringValue" if the configuration was initialized with the following JSON document:
|
||||
* @code
|
||||
* {
|
||||
* "someComponent" : {
|
||||
* "someSubComponent" : {
|
||||
* "someKey" : "someStringValue",
|
||||
* "keyToSomeBooleanValue" : true,
|
||||
* "keyToSomeIntegerValue" : 123
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class ConfigurationNode {
|
||||
public:
|
||||
/**
|
||||
* Initialize the global configuration.
|
||||
*
|
||||
* @note If @c initialize() has already been called since startup or the latest call to uninitialize(), this
|
||||
* function will reject the request and return @c false.
|
||||
*
|
||||
* @param jsonConfigurationStreams Vector of @c istreams containing JSON documents from which to parse
|
||||
* configuration parameters. Streams are processed in the order they appear in the vector. When a
|
||||
* value appears in more than one JSON stream the last processed stream's value overwrites the previous value
|
||||
* (and a debug log entry will be created). This allows for specifying default settings (by providing them
|
||||
* first) and specifying the configuration from multiple sources (e.g. a separate stream for each component).
|
||||
* The resulting global configuration may be accessed from @c getRoot().
|
||||
*
|
||||
* @return Whether the initialization was successful.
|
||||
*/
|
||||
static bool initialize(const std::vector<std::istream *> &jsonStreams);
|
||||
|
||||
/**
|
||||
* Uninitialize the global configuration.
|
||||
*
|
||||
* @note Once this method has been called, all existing ConfigurationNode instances will become invalid.
|
||||
*/
|
||||
static void uninitialize();
|
||||
|
||||
/**
|
||||
* Get the root @c ConfigurationNode of the global configuration.
|
||||
*
|
||||
* @return The root @c ConfigurationNode of the global configuration.
|
||||
*/
|
||||
static ConfigurationNode getRoot();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ConfigurationNode();
|
||||
|
||||
/**
|
||||
* Get @c bool value for @c key from this @c ConfigurationNode.
|
||||
*
|
||||
* @param key The key of the @c bool value to get.
|
||||
* @param[out] out Pointer to receive the returned value.
|
||||
* @param defaultValue Default value to use if this @c ConfigurationNode does not have a @c bool value for @c key.
|
||||
* @c false if not specified.
|
||||
* @return Whether this @c ConfigurationNode has a @c bool value for @c key.
|
||||
*/
|
||||
bool getBool(const std::string& key, bool *out = nullptr, bool defaultValue = false) const;
|
||||
|
||||
/**
|
||||
* Get @c int value for @c key from this @c ConfigurationNode.
|
||||
*
|
||||
* @param key The key of the @c int value to get.
|
||||
* @param[out] out Pointer to receive the returned value.
|
||||
* @param defaultValue Default value to use if this @c ConfigurationNode does not have an @c int value for @c key.
|
||||
* Zero if not specified.
|
||||
* @return Whether this @c ConfigurationNode has an @c int value for @c key.
|
||||
*/
|
||||
bool getInt(const std::string& key, int *out = nullptr, int defaultValue = 0) const;
|
||||
|
||||
/**
|
||||
* Get the @c string value for @c key from this @c ConfigurationNode.
|
||||
*
|
||||
* @param key The key of the @c string value to get.
|
||||
* @param[out] out Pointer to receive the returned value.
|
||||
* @param defaultValue Default value to use if this @c ConfigurationNode does not have a @c string value for @c key.
|
||||
* The empty string if not specified.
|
||||
* @return Whether this @c ConfigurationNode has a @c string value for @c key.
|
||||
*/
|
||||
bool getString(const std::string& key, std::string* out = nullptr, std::string defaultValue = "") const;
|
||||
|
||||
/**
|
||||
* Get a duration value derived from an integer value for @c key from this @c ConfigurationNode.
|
||||
*
|
||||
* @tparam InputType std::chrono::duration type whose unit specifies how the integer value in the
|
||||
* JSON input stream is to be interpreted.
|
||||
* @tparam OutputType std::chrono::duration type specifying the type of the @c out parameter to this method.
|
||||
* @tparam DefaultType std::chrono::duration type specifying the type of the @c defaultValue to this method.
|
||||
* @param key The key of of the integer value to fetch and convert to @c OutputType.
|
||||
* @param out Pointer to receive the returned value.
|
||||
* @param defaultValue Default value to use if this @c ConfigurationNode does not have an integer value
|
||||
* value for @c key. Zero if not specified.
|
||||
* @return Whether this @c ConfigurationNode has an integer value for @c key.
|
||||
*/
|
||||
template<typename InputType, typename OutputType, typename DefaultType>
|
||||
bool getDuration(
|
||||
const std::string& key,
|
||||
OutputType* out = static_cast<std::chrono::seconds>(0),
|
||||
DefaultType defaultValue = std::chrono::seconds(0)) const;
|
||||
|
||||
/**
|
||||
* operator[] to get @c ConfigurationNode value for @c key from this @c ConfigurationNode.
|
||||
*
|
||||
* @param key The key of the @c ConfigurationNode value to get.
|
||||
* @return The @c ConfigurationNode value, or an empty node if this @c ConfigurationNode does not have
|
||||
* a @c ConfigurationNode value for @c key.
|
||||
*/
|
||||
ConfigurationNode operator[](const std::string& key) const;
|
||||
|
||||
/**
|
||||
* operator bool(). Indicates of the @c ConfigurationNode references a valid object.
|
||||
*
|
||||
* @return Whether the @c ConfigurationNode references a valid object.
|
||||
*/
|
||||
operator bool() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param object @c rapidjson::Value of type @c rapidjson::Type::kObject within the global configuration that this
|
||||
* @c ConfigurationNode will represent.
|
||||
*/
|
||||
ConfigurationNode(const rapidjson::Value* object);
|
||||
|
||||
/**
|
||||
* Adapt between the public version of @c getString() (which fetches @c std::string) and the @c getValue()
|
||||
* template method (which fetches @c const @c char * from a @c rapidjson::Value).
|
||||
*
|
||||
* @param key The key of the @c string value to get.
|
||||
* @param[out] out Pointer to receive the returned value.
|
||||
* @param defaultValue Default value to use if this @c ConfigurationNode does not have a @c string value for @c key.
|
||||
* @return Whether this @c ConfigurationNode has a @c string value for @c key.
|
||||
*/
|
||||
bool getString(const std::string& key, const char** out, const char* defaultValue) const;
|
||||
|
||||
/**
|
||||
* Common logic for getting a value of a specific type.
|
||||
*
|
||||
* @tparam Type The type to be gotten.
|
||||
* @param key The key of the value to get.
|
||||
* @param out Pointer to receive the value. May be nullptr to just test for the presence of the value.
|
||||
* @param defaultValue A default output value if no value of the desired type for @c key is present.
|
||||
* @param isType rapidjson::Value member function to test for the desired type.
|
||||
* @param getType rapidjson::Value member function to get the desired type.
|
||||
* @return Whether a value of the specified @c Type is present for @c key.
|
||||
*/
|
||||
template<typename Type>
|
||||
bool getValue(
|
||||
const std::string& key,
|
||||
Type *out,
|
||||
Type defaultValue,
|
||||
bool (rapidjson::Value::*isType)() const,
|
||||
Type (rapidjson::Value::*getType)() const) const;
|
||||
|
||||
/// Object value within the global configuration that this @c ConfigurationNode represents.
|
||||
const rapidjson::Value* m_object;
|
||||
|
||||
/**
|
||||
* Static mutex to serialize access to static values @c m_document and @c m_root. This enables enforcing
|
||||
* that @c initialize() is only performed once after startup or the latest call to @c uninitialize().
|
||||
*/
|
||||
static std::mutex m_mutex;
|
||||
|
||||
/// static Document containing the global configuration.
|
||||
static rapidjson::Document m_document;
|
||||
|
||||
/// static instance of @c ConfigurationNode identifying the root object within the global configuration.
|
||||
static ConfigurationNode m_root;
|
||||
};
|
||||
|
||||
template<typename InputType, typename OutputType, typename DefaultType>
|
||||
bool ConfigurationNode::getDuration(const std::string& key, OutputType* out, DefaultType defaultValue) const {
|
||||
int temp;
|
||||
auto result = getInt(key, &temp);
|
||||
if (out) {
|
||||
*out = OutputType(result ? InputType(temp) : defaultValue);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace configuration
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif //ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_CONFIGURATION_CONFIGURATION_NODE_H_
|
|
@ -18,6 +18,8 @@
|
|||
#define ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_INITIALIZATION_ALEXA_CLIENT_SDK_INIT_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
|
@ -40,9 +42,17 @@ public:
|
|||
* calls functions of other libraries that have the same requirements and thread safety.
|
||||
* terminate() must be called for each initialize() called.
|
||||
*
|
||||
* @param jsonConfigurationStreams Vector of @c istreams containing JSON documents from which
|
||||
* to parse configuration parameters. Streams are processed in the order they appear in the vector. When a
|
||||
* value appears in more than one JSON stream the last processed stream's value overwrites the previous value
|
||||
* (and a debug log entry will be created). This allows for specifying default settings (by providing them
|
||||
* first) and specifying the configuration from multiple sources (e.g. a separate stream for each component).
|
||||
* Documentation of the JSON configuration format and methods to access the resulting global configuration
|
||||
* can be found here: avsUtils::configuration::ConfigurationNode.
|
||||
*
|
||||
* @return Whether the initialization was successful.
|
||||
*/
|
||||
static bool initialize();
|
||||
static bool initialize(const std::vector<std::istream *> &jsonStreams);
|
||||
|
||||
/**
|
||||
* Uninitialize the Alexa Client SDK.
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* LibcurlUtils.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_LIBCURLUTILS_LIBCURLUTILS_H_
|
||||
#define ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_LIBCURLUTILS_LIBCURLUTILS_H_
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace libcurlUtils {
|
||||
|
||||
/**
|
||||
* Prepare a CURL handle to require TLS based upon global configuration settings.
|
||||
*
|
||||
* The 'libCurlUtils' sub-component of the global configuration supports the following options:
|
||||
* - CURLOPT_CAPATH If present, specifies a value for the libcurl property CURLOPT_CAPATH.
|
||||
*
|
||||
* Here is an example configuration:
|
||||
* @code
|
||||
* {
|
||||
* "libcurlUtils" : {
|
||||
* "CURLOPT_CAPATH" : "/path/to/directory/with/ca/certificates"
|
||||
* }
|
||||
* // Other configuration nodes
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param handle The libcurl handle to prepare.
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
bool prepareForTLS(CURL* handle);
|
||||
|
||||
} // namespace libcurlUtils
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_AVSUTILS_INCLUDE_AVSUTILS_LIBCURLUTILS_LIBCURLUTILS_H_
|
|
@ -76,6 +76,17 @@ public:
|
|||
template<typename ValueType>
|
||||
inline LogEntry& d(const char* key, const ValueType& value);
|
||||
|
||||
/**
|
||||
* Add sensitive data in the form of a @c key, @c value pair to the metadata of this log entry.
|
||||
* Because the data is 'sensitive' it will only be emitted in DEBUG builds.
|
||||
*
|
||||
* @param key The key identifying the value to add to this LogEntry.
|
||||
* @param value The value to add to this LogEntry.
|
||||
* @return This instance to facilitate adding more information to this log entry.
|
||||
*/
|
||||
template<typename ValueType>
|
||||
inline LogEntry& sensitive(const char* key, const ValueType& value);
|
||||
|
||||
/**
|
||||
* Add an arbitrary message to the end of the text of this LogEntry. Once this has been called no other
|
||||
* additions should be made to this LogEntry.
|
||||
|
@ -132,6 +143,23 @@ LogEntry& LogEntry::d(const char *key, const ValueType& value) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
// TODO: ACSDK-246 - we should use a specific flag, not just DEBUG for this.
|
||||
#ifdef DEBUG
|
||||
|
||||
template<typename ValueType>
|
||||
LogEntry& LogEntry::sensitive(const char *key, const ValueType& value) {
|
||||
return d(key, value);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<typename ValueType>
|
||||
LogEntry& LogEntry::sensitive(const char *key, const ValueType& value) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace logger
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
||||
|
|
|
@ -21,6 +21,58 @@
|
|||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
// TODO: ACSDK-179 Remove this file and migrate to new Logger
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
/**
|
||||
* Emit a debug level log line.
|
||||
*
|
||||
* @param expression An expression that results in some object with a c_str() method that returns the string to log.
|
||||
*/
|
||||
#define ACSDK_DEBUG(expression) \
|
||||
do { \
|
||||
::alexaClientSDK::avsUtils::Logger::log(expression.c_str()); \
|
||||
} while (false)
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Stifle a debug level log line.
|
||||
*
|
||||
* @param expression An expression that results in some object with a c_str() method that returns the string to log.
|
||||
*/
|
||||
#define ACSDK_DEBUG(expression) \
|
||||
do { \
|
||||
} while (false)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Emit an info level log line.
|
||||
*
|
||||
* @param expression An expression that results in some object with a c_str() method that returns the string to log.
|
||||
*/
|
||||
#define ACSDK_INFO(expression) \
|
||||
do { \
|
||||
::alexaClientSDK::avsUtils::Logger::log(expression.c_str()); \
|
||||
} while (false)
|
||||
|
||||
/**
|
||||
* Emit an warning level log line.
|
||||
*
|
||||
* @param expression An expression that results in some object with a c_str() method that returns the string to log.
|
||||
*/
|
||||
#define ACSDK_WARN(expression) ACSDK_INFO(expression)
|
||||
|
||||
/**
|
||||
* Emit an error level log line.
|
||||
*
|
||||
* @param expression An expression that results in some object with a c_str() method that returns the string to log.
|
||||
*/
|
||||
#define ACSDK_ERROR(expression) ACSDK_INFO(expression)
|
||||
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
|
||||
|
|
|
@ -15,14 +15,27 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
#include "AVSUtils/Logging/Logger.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "AVSUtils/Configuration/ConfigurationNode.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
#include "AVSUtils/Logger/LogEntry.h"
|
||||
#include "AVSUtils/Logging/Logger.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace initialization {
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("AlexaClientSdkInit");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
/// Tracks whether we've initialized the Alexa Client SDK or not
|
||||
std::atomic_int AlexaClientSDKInit::g_isInitialized{0};
|
||||
|
||||
|
@ -30,9 +43,14 @@ bool AlexaClientSDKInit::isInitialized() {
|
|||
return g_isInitialized > 0;
|
||||
}
|
||||
|
||||
bool AlexaClientSDKInit::initialize() {
|
||||
bool AlexaClientSDKInit::initialize(const std::vector<std::istream *> &jsonStreams) {
|
||||
if (!configuration::ConfigurationNode::initialize(jsonStreams)) {
|
||||
ACSDK_ERROR(LX("initializeFailed").d("reason", "ConfigurationNode::initializeFailed"));
|
||||
return false;
|
||||
}
|
||||
if (CURLE_OK != curl_global_init(CURL_GLOBAL_ALL)) {
|
||||
Logger::log("Could not initialize libcurl");
|
||||
ACSDK_ERROR(LX("initializeFailed").d("reason", "curl_global_initFailed"));
|
||||
configuration::ConfigurationNode::uninitialize();
|
||||
return false;
|
||||
}
|
||||
g_isInitialized++;
|
||||
|
@ -41,11 +59,12 @@ bool AlexaClientSDKInit::initialize() {
|
|||
|
||||
void AlexaClientSDKInit::uninitialize() {
|
||||
if (0 == g_isInitialized) {
|
||||
Logger::log("AlexaClientSDKInit::terminate called without corresponding AlexaClientSDKInit::initialize");
|
||||
ACSDK_ERROR(LX("initializeError").d("reason", "notInitialized"));
|
||||
return;
|
||||
}
|
||||
g_isInitialized--;
|
||||
curl_global_cleanup();
|
||||
configuration::ConfigurationNode::uninitialize();
|
||||
}
|
||||
|
||||
} // namespace initialization
|
||||
|
|
|
@ -3,5 +3,10 @@ find_package(Threads ${THREADS_PACKAGE_CONFIG})
|
|||
|
||||
file(GLOB_RECURSE AVSUtils_SRC "${AVSUtils_SOURCE_DIR}/src/*.cpp")
|
||||
add_library(AVSUtils SHARED ${AVSUtils_SRC})
|
||||
target_include_directories(AVSUtils PUBLIC ${CURL_INCLUDE_DIRS} "${AVSUtils_SOURCE_DIR}/include")
|
||||
target_link_libraries(AVSUtils ${CURL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_include_directories(AVSUtils PUBLIC
|
||||
${CURL_INCLUDE_DIRS}
|
||||
"${AVSUtils_SOURCE_DIR}/include"
|
||||
"${RAPIDJSON_INCLUDE_DIR}")
|
||||
target_link_libraries(AVSUtils
|
||||
${CURL_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT})
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* ConfigurationNode.cpp
|
||||
*
|
||||
* Copyright 2017 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 <rapidjson/istreamwrapper.h>
|
||||
#include <rapidjson/writer.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
|
||||
#include "AVSUtils/Configuration/ConfigurationNode.h"
|
||||
#include "AVSUtils/Logger/LogEntry.h"
|
||||
#include "AVSUtils/Logging/Logger.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace configuration {
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("ConfigurationNode");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
std::mutex ConfigurationNode::m_mutex;
|
||||
Document ConfigurationNode::m_document;
|
||||
ConfigurationNode ConfigurationNode::m_root;
|
||||
|
||||
/**
|
||||
* Render @c rapidjson::Value as a string.
|
||||
*
|
||||
* @param value The value to render as a string.
|
||||
* @return The string rendered from the @c value.
|
||||
*/
|
||||
static std::string valueToString(const Value& value) {
|
||||
rapidjson::StringBuffer stringBuffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(stringBuffer);
|
||||
if (value.Accept(writer)) {
|
||||
return stringBuffer.GetString();
|
||||
}
|
||||
ACSDK_ERROR(LX("valueToStringFailed").d("reason", "AcceptReturnedFalse"));
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep (possibly recursive) merge of two @c rapidjson values of type @c rapidjson::Type::kObject. The contents
|
||||
* of @c in are merged in to @c out. Values that occur in both are replaced by the values in @c in.
|
||||
*
|
||||
* @param path String describing the accumulated path to the sub-objects being merged.
|
||||
* @param[in,out] out The object that @c in will be merged in to.
|
||||
* @param in The object to merge in to @c out.
|
||||
* @param allocator Allocator of the @c rapidjson::Document containing @c out.
|
||||
*/
|
||||
static void mergeDocument(const std::string& path, Value& out, Value& in, Document::AllocatorType& allocator) {
|
||||
for (auto inIt = in.MemberBegin(); inIt != in.MemberEnd(); inIt++) {
|
||||
auto outIt = out.FindMember(inIt->name);
|
||||
if (outIt != out.MemberEnd() && inIt->value != outIt->value) {
|
||||
auto memberPath = path + "." + outIt->name.GetString();
|
||||
if (outIt->value.IsObject()) {
|
||||
ACSDK_DEBUG(LX("mergeDocument").d("reason", "objectsMerged").d("path", memberPath));
|
||||
mergeDocument(memberPath, outIt->value, inIt->value, allocator);
|
||||
} else {
|
||||
ACSDK_DEBUG(LX("mergeDocument")
|
||||
.d("reason", "valueReplaced")
|
||||
.d("path", memberPath)
|
||||
.d("old", valueToString(outIt->value))
|
||||
.d("new", valueToString(inIt->value)));
|
||||
outIt->value = inIt->value;
|
||||
}
|
||||
} else {
|
||||
out.AddMember(inIt->name, inIt->value, allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigurationNode::initialize(const std::vector<std::istream *> &jsonStreams) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_root) {
|
||||
ACSDK_ERROR(LX("initializeFailed").d("reason", "alreadyInitialized"));
|
||||
return false;
|
||||
}
|
||||
m_document.SetObject();
|
||||
for (auto jsonStream : jsonStreams) {
|
||||
if (!jsonStream) {
|
||||
m_document.SetObject();
|
||||
return false;
|
||||
}
|
||||
IStreamWrapper wrapper(*jsonStream);
|
||||
Document overlay(&m_document.GetAllocator());
|
||||
overlay.ParseStream(wrapper);
|
||||
if (overlay.HasParseError()) {
|
||||
ACSDK_ERROR(LX("initializeFailed")
|
||||
.d("reason", "parseFailure")
|
||||
.d("offset", overlay.GetErrorOffset())
|
||||
.d("message", GetParseError_En(overlay.GetParseError())));
|
||||
m_document.SetObject();
|
||||
return false;
|
||||
}
|
||||
mergeDocument("root", m_document, overlay, m_document.GetAllocator());
|
||||
}
|
||||
m_root = ConfigurationNode(&m_document);
|
||||
ACSDK_INFO(LX("initializeSuccess").d("configuration", valueToString(m_document)));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigurationNode::uninitialize() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_document.SetObject();
|
||||
m_root = ConfigurationNode();
|
||||
}
|
||||
|
||||
ConfigurationNode ConfigurationNode::getRoot() {
|
||||
return m_root;
|
||||
}
|
||||
|
||||
ConfigurationNode::ConfigurationNode() : m_object{nullptr} {
|
||||
}
|
||||
|
||||
bool ConfigurationNode::getBool(const std::string& key, bool *out, bool defaultValue) const {
|
||||
return getValue(key, out, defaultValue, &Value::IsBool, &Value::GetBool);
|
||||
}
|
||||
|
||||
bool ConfigurationNode::getInt(const std::string& key, int *out, int defaultValue) const {
|
||||
return getValue(key, out, defaultValue, &Value::IsInt, &Value::GetInt);
|
||||
}
|
||||
|
||||
bool ConfigurationNode::getString(const std::string& key, std::string* out, std::string defaultValue) const {
|
||||
const char* temp;
|
||||
auto result = getString(key, &temp, defaultValue.c_str());
|
||||
if (out) {
|
||||
*out = temp;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ConfigurationNode::getString(const std::string& key, const char** out, const char* defaultValue) const {
|
||||
return getValue(key, out, defaultValue, &Value::IsString, &Value::GetString);
|
||||
}
|
||||
|
||||
template<typename Type>
|
||||
bool ConfigurationNode::getValue(
|
||||
const std::string& key,
|
||||
Type *out,
|
||||
Type defaultValue,
|
||||
bool (rapidjson::Value::*isType)() const,
|
||||
Type (rapidjson::Value::*getType)() const) const {
|
||||
if (key.empty() || !m_object) {
|
||||
if (out) {
|
||||
*out = defaultValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
auto it = m_object->FindMember(key.c_str());
|
||||
if (m_object->MemberEnd() == it || !(it->value.*isType)()) {
|
||||
if (out) {
|
||||
*out = defaultValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (out) {
|
||||
*out = (it->value.*getType)();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigurationNode ConfigurationNode::operator[](const std::string& key) const {
|
||||
if (!*this) {
|
||||
return ConfigurationNode();
|
||||
}
|
||||
auto it = m_object->FindMember(key.c_str());
|
||||
if (m_object->MemberEnd() == it || !it->value.IsObject()) {
|
||||
return ConfigurationNode();
|
||||
}
|
||||
return ConfigurationNode(&it->value);
|
||||
}
|
||||
|
||||
ConfigurationNode::operator bool() const {
|
||||
return m_object;
|
||||
}
|
||||
|
||||
ConfigurationNode::ConfigurationNode(const rapidjson::Value* object) : m_object{object} {
|
||||
}
|
||||
|
||||
} // namespace configuration
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* LibcurlUtils.cpp
|
||||
*
|
||||
* Copyright 2017 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 <cerrno>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <AVSUtils/Configuration/ConfigurationNode.h>
|
||||
#include <AVSUtils/LibcurlUtils/LibcurlUtils.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace libcurlUtils {
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("LibcurlUtils");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
/// Key for looking up the @c LibCurlUtil @c ConfigurationNode.
|
||||
static const std::string LIBCURLUTILS_CONFIG_KEY = "libcurlUtils";
|
||||
|
||||
/// Key for looking up a configuration value for @c CURLOPT_CAPATH
|
||||
static const std::string CAPATH_CONFIG_KEY = "CURLOPT_CAPATH";
|
||||
|
||||
/**
|
||||
* Set an @c option on a @c libcurl handle to @c value with stringification of @c option name and @c value for logging.
|
||||
*
|
||||
* @param handle The CURL handle to set the option on.
|
||||
* @param option The option to set.
|
||||
* @param value The new value for the specified option.
|
||||
*/
|
||||
#define SETOPT(handle, option, value) setopt(handle, option, value, #option, #value)
|
||||
|
||||
/**
|
||||
* Set an @c option on a @c libcurl handle to @c value.
|
||||
*
|
||||
* @tparam ValueType The type of value being set
|
||||
* @param handle The CURL handle to set the option on.
|
||||
* @param option The option to set.
|
||||
* @param value The new value for the specified option.
|
||||
* @param optionName The name of the option being set.
|
||||
* @param valueAsString String representation of the value being set.
|
||||
* @return Whether the option was set.
|
||||
*/
|
||||
template<typename ValueType>
|
||||
static bool setopt(
|
||||
CURL* handle, CURLoption option, ValueType value, const char* optionName, const char* valueAsString) {
|
||||
auto result = curl_easy_setopt(handle, option, value);
|
||||
if (result != CURLE_OK) {
|
||||
ACSDK_ERROR(LX("curl_easy_setoptFailed")
|
||||
.d("option", optionName)
|
||||
.sensitive("value", valueAsString)
|
||||
.d("result", result)
|
||||
.d("error", curl_easy_strerror(result)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool prepareForTLS(CURL* handle) {
|
||||
if (!handle) {
|
||||
ACSDK_ERROR(LX("prepareForTLSFailed").d("reason", "nullHandle"));
|
||||
return false;
|
||||
}
|
||||
if (!(SETOPT(handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) &&
|
||||
SETOPT(handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0) &&
|
||||
SETOPT(handle, CURLOPT_USE_SSL, CURLUSESSL_ALL) &&
|
||||
SETOPT(handle, CURLOPT_SSL_VERIFYPEER, 1L) &&
|
||||
SETOPT(handle, CURLOPT_SSL_VERIFYHOST, 2L))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string caPath;
|
||||
if (configuration::ConfigurationNode::getRoot()[LIBCURLUTILS_CONFIG_KEY].getString(CAPATH_CONFIG_KEY, &caPath)) {
|
||||
return setopt(handle, CURLOPT_CAPATH, caPath.c_str(), "CURLOPT_CAPATH", caPath.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace libcurlUtils
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* ConfigurationNodeTest.cpp
|
||||
*
|
||||
* Copyright 2017 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 ConfigurationNodeTest.cpp
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "AVSUtils/Configuration/ConfigurationNode.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace avsUtils {
|
||||
namespace configuration {
|
||||
namespace test {
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
/// Name of non-existent object for exercising failure to find a @c ConfigurationNode.
|
||||
static const std::string NON_OBJECT = "non-existent-object";
|
||||
|
||||
/// Name of first root level object.
|
||||
static const std::string OBJECT1 = "object1";
|
||||
|
||||
/// Name of first bool value in first root level object.
|
||||
static const std::string BOOL1_1 = "bool1.1";
|
||||
|
||||
/// Value of first bool value in first root level object.
|
||||
static const bool BOOL_VALUE1_1 = true;
|
||||
|
||||
/// Name of first object inside first root level object.
|
||||
static const std::string OBJECT1_1 = "object1.1";
|
||||
|
||||
/// Name of first string value in first object inside first root level object.
|
||||
static const std::string STRING1_1_1 = "string1.1.1";
|
||||
|
||||
/// Value of first string value in first object inside first root level object.
|
||||
static const std::string STRING_VALUE1_1_1 = "stringValue1.1.1";
|
||||
|
||||
/// Name of second root level object.
|
||||
static const std::string OBJECT2 = "object2";
|
||||
|
||||
/// Name of first string in second root level object.
|
||||
static const std::string STRING2_1 = "string2.1";
|
||||
|
||||
/// Replaced value of first string in second root level object.
|
||||
static const std::string NEW_STRING_VALUE2_1 = "new-stringValue2.1";
|
||||
|
||||
/// Name for non-existent int value in second root level object.
|
||||
static const std::string NON_EXISTENT_INT2_1 = "non-existent-int2.1";
|
||||
|
||||
/// Default value for non-existent int value in second root level object.
|
||||
static const int NON_EXISTENT_INT_VALUE2_1 = 123;
|
||||
|
||||
/// Name of first int value in second root level object.
|
||||
static const std::string INT2_1 = "int2.1";
|
||||
|
||||
/// Name of first object inside second root level object.
|
||||
static const std::string OBJECT2_1 = "object2.1";
|
||||
|
||||
/// Name of first string inside first object inside second root level object.
|
||||
static const std::string STRING2_1_1 = "string2.1.1";
|
||||
|
||||
/// Replaced value of first string inside first object inside second root level object.
|
||||
static const std::string NEW_STRING_VALUE2_1_1 = "new-stringValue2.1.1";
|
||||
|
||||
/// Bad JSON string to verify handling the failure to parse JSON
|
||||
static const std::string BAD_JSON = "{ bad json }";
|
||||
|
||||
/// First JSON string to parse, serving as default for configuration values.
|
||||
static const std::string FIRST_JSON = R"(
|
||||
{
|
||||
"object1" : {
|
||||
"bool1.1" : true
|
||||
},
|
||||
"object2" : {
|
||||
"int2.1" : 21,
|
||||
"string2.1" : "stringValue2.1",
|
||||
"object2.1" : {
|
||||
"string2.1.1" : "stringValue2.1.1"
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
/// Second JSON string to parse, overlaying configuration values from FIRST_JSON.
|
||||
static const std::string SECOND_JSON = R"(
|
||||
{
|
||||
"object1" : {
|
||||
"object1.1" : {
|
||||
"string1.1.1" : "stringValue1.1.1"
|
||||
},
|
||||
"int1.1" : 11
|
||||
}
|
||||
})";
|
||||
|
||||
/// Third JSON string to parse, overlaying configuration values from FIRST_JSON and SECOND_JSON.
|
||||
static const std::string THIRD_JSON = R"(
|
||||
{
|
||||
"object2" : {
|
||||
"int2.1" : 21,
|
||||
"string2.1" : "new-stringValue2.1",
|
||||
"object2.1" : {
|
||||
"string2.1.1" : "new-stringValue2.1.1"
|
||||
}
|
||||
}
|
||||
})";
|
||||
|
||||
/**
|
||||
* Class for testing the ConfigurationNode class
|
||||
*/
|
||||
class ConfigurationNodeTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify initialization a configuration. Verify both the implementation of accessor methods and the results
|
||||
* of merging JSON streams.
|
||||
*/
|
||||
TEST_F(ConfigurationNodeTest, testInitializationAndAccess) {
|
||||
// Verify a null configuration results in failure
|
||||
ASSERT_FALSE(ConfigurationNode::initialize({nullptr}));
|
||||
|
||||
// Verify invalid JSON results in failure
|
||||
std::stringstream badStream;
|
||||
badStream << BAD_JSON;
|
||||
ASSERT_FALSE(ConfigurationNode::initialize({&badStream}));
|
||||
|
||||
// Combine valid JSON streams with overlapping values. Verify reported success.
|
||||
std::stringstream firstStream;
|
||||
firstStream << FIRST_JSON;
|
||||
std::stringstream secondStream;
|
||||
secondStream << SECOND_JSON;
|
||||
std::stringstream thirdStream;
|
||||
thirdStream << THIRD_JSON;
|
||||
ASSERT_TRUE(ConfigurationNode::initialize({&firstStream, &secondStream, &thirdStream}));
|
||||
|
||||
// Verify failure reported for subsequent initializations.
|
||||
firstStream << FIRST_JSON;
|
||||
ASSERT_FALSE(ConfigurationNode::initialize({&firstStream}));
|
||||
|
||||
// Verify non-found name results in a ConfigurationNode that evaluates to false.
|
||||
ASSERT_FALSE(ConfigurationNode::getRoot()[NON_OBJECT]);
|
||||
|
||||
// Verify found name results in a ConfigurationNode that evaluates to true.
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1]);
|
||||
|
||||
// Verify extraction of bool value.
|
||||
bool bool11 = true;
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1].getBool(BOOL1_1, &bool11));
|
||||
ASSERT_EQ(bool11, BOOL_VALUE1_1);
|
||||
|
||||
// Verify traversal of multiple levels of ConfigurationNode and extraction of a string value.
|
||||
std::string string111;
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1][OBJECT1_1].getString(STRING1_1_1, &string111));
|
||||
ASSERT_EQ(string111, STRING_VALUE1_1_1);
|
||||
|
||||
// Verify retrieval of default value when name does not match any value.
|
||||
int nonExistentInt21 = 0;
|
||||
ASSERT_NE(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||||
ASSERT_FALSE(ConfigurationNode::getRoot()[OBJECT2].getInt(
|
||||
NON_EXISTENT_INT2_1, &nonExistentInt21, NON_EXISTENT_INT_VALUE2_1));
|
||||
ASSERT_EQ(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||||
|
||||
// Verify extraction if an integer value.
|
||||
int int21;
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2].getInt(INT2_1, &int21));
|
||||
ASSERT_EQ(int21, 21);
|
||||
|
||||
// Verify overwrite of string value by subsequent JSON.
|
||||
std::string newString21;
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2].getString(STRING2_1, &newString21));
|
||||
ASSERT_EQ(newString21, NEW_STRING_VALUE2_1);
|
||||
|
||||
// Verify retrieval of default value when type does not match an existing value.
|
||||
nonExistentInt21 = 0;
|
||||
ASSERT_NE(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||||
ASSERT_FALSE(ConfigurationNode::getRoot()[OBJECT2].getInt(
|
||||
STRING2_1, &nonExistentInt21, NON_EXISTENT_INT_VALUE2_1));
|
||||
ASSERT_EQ(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||||
|
||||
// Verify overwrite of string value in nested Configuration node.
|
||||
std::string string211;
|
||||
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2][OBJECT2_1].getString(STRING2_1_1, &string211));
|
||||
ASSERT_EQ(string211, NEW_STRING_VALUE2_1_1);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace configuration
|
||||
} // namespace avsUtils
|
||||
} // namespace alexaClientSDK
|
|
@ -39,7 +39,7 @@ protected:
|
|||
/// Test that a new LogEntryStream instance's c_str() returns an empty string.
|
||||
TEST_F(LogEntryStreamTest, emptyStream) {
|
||||
ASSERT_NE(m_stream.c_str(), nullptr);
|
||||
ASSERT_EQ(strlen(m_stream.c_str()), 0);
|
||||
ASSERT_EQ(strlen(m_stream.c_str()), 0u);
|
||||
}
|
||||
|
||||
/// Send a character to an empty LogEntryStream. Expect that c_str() returns a string with just that character.
|
||||
|
@ -47,7 +47,7 @@ TEST_F(LogEntryStreamTest, shortString) {
|
|||
const char SOME_CHAR = 'x';
|
||||
m_stream << SOME_CHAR;
|
||||
ASSERT_EQ(SOME_CHAR, m_stream.c_str()[0]);
|
||||
ASSERT_EQ(strlen(m_stream.c_str()), 1);
|
||||
ASSERT_EQ(strlen(m_stream.c_str()), 1u);
|
||||
}
|
||||
|
||||
/// Send a medium sized string test to an empty LogEntryStream. Expect that c_str() returns a matching string.
|
||||
|
|
|
@ -473,6 +473,21 @@ TEST_F(LoggerTest, verifyMessage) {
|
|||
ASSERT_NE(m_log->m_lastText.find(TEST_MESSAGE_STRING), std::string::npos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test passing sensitive data to the logging system. It should only be emitted in DEBUG builds.
|
||||
*/
|
||||
TEST_F(LoggerTest, testSensitiveDataSuppressed) {
|
||||
m_log = MockLogger::create(Level::INFO);
|
||||
EXPECT_CALL(*(m_log.get()), emit(Level::INFO, _, _, _)).Times(1);
|
||||
ACSDK_INFO(m_log, LX("testing metadata").sensitive(METADATA_KEY, UNESCAPED_METADATA_VALUE));
|
||||
auto result = m_log->m_lastText.find(METADATA_KEY KEY_VALUE_SEPARATOR ESCAPED_METADATA_VALUE) != std::string::npos;
|
||||
#ifdef DEBUG
|
||||
ASSERT_TRUE(result);
|
||||
#else
|
||||
ASSERT_FALSE(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace logger
|
||||
} // namespace avsUtils
|
||||
|
|
|
@ -16,14 +16,29 @@
|
|||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <AVSUtils/Initialization/AlexaClientSDKInit.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "AuthDelegate/AuthDelegate.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
|
||||
using namespace alexaClientSDK;
|
||||
using acl::AuthObserverInterface;
|
||||
using authDelegate::AuthDelegate;
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("AlexAuthDelegateClient");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
/// Simple implementation of the AuthDelegateObserverInterface.
|
||||
class Observer : public AuthObserverInterface {
|
||||
public:
|
||||
|
@ -73,8 +88,7 @@ private:
|
|||
|
||||
/// Instantiate an AuthDelegate and fetch an authToken every couple of seconds.
|
||||
int exerciseAuthDelegate() {
|
||||
auto config = std::make_shared<authDelegate::Config>();
|
||||
auto authDelegate = AuthDelegate::create(config);
|
||||
auto authDelegate = AuthDelegate::create();
|
||||
|
||||
if (!authDelegate) {
|
||||
std::cerr << "AuthDelegate::Create() failed." << std::endl;
|
||||
|
@ -102,8 +116,21 @@ int exerciseAuthDelegate() {
|
|||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
if (!avsUtils::initialization::AlexaClientSDKInit::initialize()){
|
||||
std::cerr << "AlexaClientSDKInit::initialize() failed!" << std::endl;
|
||||
if (argc < 2) {
|
||||
ACSDK_ERROR(LX("ExampleAuthDelegateClientFailed")
|
||||
.d("reason", "missingConfigurationFilePath")
|
||||
.d("usage", "ExampleAuthDelegateClient <path-to-SDK-config-file>"));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::ifstream infile(argv[1]);
|
||||
if (!infile.good()) {
|
||||
ACSDK_ERROR(LX("ExampleAuthDelegateClientFailed")
|
||||
.d("reason", "openConfigurationFileFailed")
|
||||
.d("path", argv[1]));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!avsUtils::initialization::AlexaClientSDKInit::initialize({&infile})) {
|
||||
ACSDK_ERROR(LX("ExampleAuthDelegateClientFailed").d("reason", "alexaClientSDKInitFailed"));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
auto result = exerciseAuthDelegate();
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
#include "ACL/AuthDelegateInterface.h"
|
||||
#include "ACL/AuthObserverInterface.h"
|
||||
#include "AuthDelegate/Config.h"
|
||||
#include <ACL/AuthDelegateInterface.h>
|
||||
#include <ACL/AuthObserverInterface.h>
|
||||
|
||||
#include "AuthDelegate/HttpPostInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
|
@ -49,10 +49,9 @@ public:
|
|||
* <li>After AlexaClientSDKInit::uninitialize has been called.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param config AuthDelegate configuration parameters. Must not be @c nullptr.
|
||||
* @return If successful, returns a new AuthDelegate, othewise @c nullptr.
|
||||
* @return If successful, returns a new AuthDelegate, otherwise @c nullptr.
|
||||
*/
|
||||
static std::unique_ptr<AuthDelegate> create(std::shared_ptr<Config> config);
|
||||
static std::unique_ptr<AuthDelegate> create();
|
||||
|
||||
/**
|
||||
* Create an AuthDelegate.
|
||||
|
@ -62,13 +61,11 @@ public:
|
|||
* <li>After AlexaClientSDKInit::uninitialize has been called.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param config AuthDelegate configuration parameters. Must not be @c nullptr.
|
||||
* @param httpPost Instance that implement HttpPostInterface. Must not be @c nullptr. The behavior for passing in
|
||||
* @c nullptr is undefined.
|
||||
* @return If successful, returns a new AuthDelegate, othewise @c nullptr.
|
||||
* @return If successful, returns a new AuthDelegate, otherwise @c nullptr.
|
||||
*/
|
||||
static std::unique_ptr<AuthDelegate> create(std::shared_ptr<Config> config,
|
||||
std::unique_ptr<HttpPostInterface> httpPost);
|
||||
static std::unique_ptr<AuthDelegate> create(std::unique_ptr<HttpPostInterface> httpPost);
|
||||
|
||||
/**
|
||||
* Deleted copy constructor
|
||||
|
@ -96,13 +93,12 @@ private:
|
|||
/**
|
||||
* AuthDelegate constructor.
|
||||
*
|
||||
* @param config AuthDelegate configuration parameters. Must not be @c nullptr, or the behavior is undefined.
|
||||
* @param httpPost Instance that implement HttpPostInterface. Must not be @c nullptr, or the behavior is undefined.
|
||||
*/
|
||||
AuthDelegate(std::shared_ptr<Config> config, std::unique_ptr<HttpPostInterface> httpPost);
|
||||
AuthDelegate(std::unique_ptr<HttpPostInterface> httpPost);
|
||||
|
||||
/**
|
||||
* init() is used by create() to perform initialization after constructio but before returning the
|
||||
* init() is used by create() to perform initialization after construction but before returning the
|
||||
* AuthDelegate instance so that clients only get access to fully formed instances.
|
||||
*
|
||||
* @return @c true if initialization is successful.
|
||||
|
@ -157,12 +153,6 @@ private:
|
|||
*/
|
||||
void setState(acl::AuthObserverInterface::State newState);
|
||||
|
||||
/**
|
||||
* AuthDelegate configuration parameters.
|
||||
* Access is not synchronized because it is only accessed by @c m_refreshAndNotifyThread.
|
||||
*/
|
||||
std::shared_ptr<Config> m_config;
|
||||
|
||||
/// Authorization state change observer. Access is synchronized with @c m_mutex.
|
||||
std::shared_ptr<acl::AuthObserverInterface> m_observer;
|
||||
|
||||
|
@ -172,6 +162,21 @@ private:
|
|||
/// Current authorization error. Access is synchronized with @c m_mutex.
|
||||
acl::AuthObserverInterface::Error m_authError;
|
||||
|
||||
/// Client ID to pass in @c LWA requests.
|
||||
std::string m_clientId;
|
||||
|
||||
/// Client secret to pass in @c LWA requests.
|
||||
std::string m_clientSecret;
|
||||
|
||||
/// URL with which to connect to @c LWA.
|
||||
std::string m_lwaUrl;
|
||||
|
||||
/// How long to wait for a response from @c LWA.
|
||||
std::chrono::seconds m_requestTimeout;
|
||||
|
||||
/// How far ahead of auth token expiration to start making requests to refresh the auth token.
|
||||
std::chrono::seconds m_authTokenRefreshHeadStart;
|
||||
|
||||
/**
|
||||
* LWA token is used to refresh the auth token.
|
||||
* Access is not synchronized because it is only accessed by @c m_refreshAndNotifyThread.
|
||||
|
@ -185,7 +190,7 @@ private:
|
|||
std::condition_variable m_wakeThreadCond;
|
||||
|
||||
/**
|
||||
* The most recently received LWA authoriation token.
|
||||
* The most recently received LWA authorization token.
|
||||
* Access is not synchronized because it is only accessed by @c m_refreshAndNotifyThread.
|
||||
*/
|
||||
std::string m_authToken;
|
||||
|
@ -221,7 +226,7 @@ private:
|
|||
int m_retryCount;
|
||||
|
||||
/**
|
||||
* HTTP/POST client with which to make LWA requests.
|
||||
* HTTP/POST client with which to make @c LWA requests.
|
||||
* Access is not synchronized because it is only accessed by @c m_refreshAndNotifyThread.
|
||||
*/
|
||||
std::unique_ptr<HttpPostInterface> m_HttpPost;
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Config.h
|
||||
*
|
||||
* Copyright 2016 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AUTHDELEGATE_INCLUDE_CONFIG_H_
|
||||
#define ALEXA_CLIENT_SDK_AUTHDELEGATE_INCLUDE_CONFIG_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "AuthDelegate/HttpPostInterface.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
/// Access to configuration parameters for AuthDelegate
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
|
||||
virtual ~Config() {};
|
||||
|
||||
/**
|
||||
* Get the base URL for contacting LWA
|
||||
*
|
||||
* @return The LWA endpoint to get tokens from.
|
||||
*/
|
||||
virtual std::string getLwaUrl() const;
|
||||
|
||||
/**
|
||||
* Get the client ID to pass to LWA.
|
||||
*
|
||||
* @return The LWA client ID of this client.
|
||||
*/
|
||||
virtual std::string getClientId() const;
|
||||
|
||||
/**
|
||||
* Get the refresh token to pass to LWA.
|
||||
*
|
||||
* @return The LWA token used to refresh the clients auth token.
|
||||
*/
|
||||
virtual std::string getRefreshToken() const;
|
||||
|
||||
/**
|
||||
* Get the client secret to pass to LWA.
|
||||
*
|
||||
* @return The client secret to pass to LWA.
|
||||
*/
|
||||
virtual std::string getClientSecret() const;
|
||||
|
||||
/**
|
||||
* Get how long before an LWA auth token expires to try to refresh it.
|
||||
*
|
||||
* @param Return head start interval for refreshing the auth token.
|
||||
*/
|
||||
virtual std::chrono::seconds getAuthTokenRefreshHeadStart() const;
|
||||
|
||||
/**
|
||||
* Get the max number of seconds to wait for an LWA request to complete.
|
||||
*
|
||||
* @return The maximum number of seconds to wait on an LWA request.
|
||||
*/
|
||||
virtual std::chrono::seconds getRequestTimeout() const;
|
||||
protected:
|
||||
std::string m_lwaUrl;
|
||||
std::string m_clientId;
|
||||
std::string m_refreshToken;
|
||||
std::string m_clientSecret;
|
||||
std::chrono::seconds m_authTokenRefreshHeadStart;
|
||||
std::chrono::seconds m_maxAuthTokenTtl;
|
||||
std::chrono::seconds m_requestTimeout;
|
||||
};
|
||||
|
||||
} // namespace authDelegate
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_AUTHDELEGATE_INCLUDE_CONFIG_H_
|
|
@ -15,24 +15,65 @@
|
|||
* permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
#include <unordered_map>
|
||||
#include "curl/curl.h"
|
||||
|
||||
#include <AVSUtils/Configuration/ConfigurationNode.h>
|
||||
#include <AVSUtils/Initialization/AlexaClientSDKInit.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "AuthDelegate/AuthDelegate.h"
|
||||
#include "AuthDelegate/HttpPost.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
using acl::AuthObserverInterface;
|
||||
|
||||
namespace authDelegate {
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("AuthDelegate");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
/// Name of @c ConfigurationNode for AuthDelegate
|
||||
const std::string CONFIG_KEY_AUTH_DELEGATE = "authDelegate";
|
||||
|
||||
/// Name of clientId value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_CLIENT_ID = "clientId";
|
||||
|
||||
/// Name of clientSecret value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_CLIENT_SECRET = "clientSecret";
|
||||
|
||||
/// Name of refreshToken value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_REFRESH_TOKEN = "refreshToken";
|
||||
|
||||
/// Name of lwaURL value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_LWA_URL = "lwaUrl";
|
||||
|
||||
/// Name of requestTimeout value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_REQUEST_TIMEOUT = "requestTimeout";
|
||||
|
||||
/// Name of authTokenRefreshHeadStart value in AuthDelegate's @c ConfigurationNode.
|
||||
const char* CONFIG_KEY_AUTH_TOKEN_REFRESH_HEAD_START = "authTokenRefreshHeadStart";
|
||||
|
||||
/// Default value for lwaURL.
|
||||
static const std::string DEFAULT_LWA_URL = "https://api.amazon.com/auth/o2/token";
|
||||
|
||||
/// Default value for requestTimeout.
|
||||
static const std::chrono::minutes DEFAULT_REQUEST_TIMEOUT = std::chrono::minutes(5);
|
||||
|
||||
/// Default value for authTokenRefreshHeadStart.
|
||||
static const std::chrono::minutes DEFAULT_AUTH_TOKEN_REFRESH_HEAD_START = std::chrono::minutes(10);
|
||||
|
||||
/// POST data before 'client_id' that is sent to LWA to refresh the auth token.
|
||||
static const std::string POST_DATA_UP_TO_CLIENT_ID = "grant_type=refresh_token&client_id=";
|
||||
|
@ -169,31 +210,28 @@ static std::chrono::steady_clock::time_point calculateTimeToRetry(int retryCount
|
|||
static_cast<int>(retryBackoffTimes[retryCount] * RETRY_DECREASE_FACTOR),
|
||||
static_cast<int>(retryBackoffTimes[retryCount] * RETRY_INCREASE_FACTOR));
|
||||
auto delayMs = std::chrono::milliseconds(distribution(generator));
|
||||
// TODO: convert to debug log: ACSDK-57
|
||||
std::cerr << "calculateTimeToRetry() delayMs=" << delayMs.count() << std::endl;
|
||||
ACSDK_DEBUG(LX("calculatedTimeToRetry").d("delayMs", delayMs.count()));
|
||||
|
||||
return std::chrono::steady_clock::now() + delayMs;
|
||||
}
|
||||
|
||||
std::unique_ptr<AuthDelegate> AuthDelegate::create(std::shared_ptr<Config> config) {
|
||||
return AuthDelegate::create(config, HttpPost::create());
|
||||
std::unique_ptr<AuthDelegate> AuthDelegate::create() {
|
||||
return AuthDelegate::create(HttpPost::create());
|
||||
}
|
||||
|
||||
std::unique_ptr<AuthDelegate> AuthDelegate::create(std::shared_ptr<Config> config,
|
||||
std::unique_ptr<HttpPostInterface> httpPost) {
|
||||
std::unique_ptr<AuthDelegate> AuthDelegate::create(std::unique_ptr<HttpPostInterface> httpPost) {
|
||||
if (!avsUtils::initialization::AlexaClientSDKInit::isInitialized()) {
|
||||
std::cerr<<"Alexa Client SDK not initialized, aborting";
|
||||
ACSDK_ERROR(LX("createFailed").d("reason", "sdkNotInitialized"));
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<AuthDelegate> instance(new AuthDelegate(config, std::move(httpPost)));
|
||||
std::unique_ptr<AuthDelegate> instance(new AuthDelegate(std::move(httpPost)));
|
||||
if (instance->init()) {
|
||||
return instance;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AuthDelegate::AuthDelegate(std::shared_ptr<Config> config, std::unique_ptr<HttpPostInterface> httpPost):
|
||||
m_config{config},
|
||||
AuthDelegate::AuthDelegate(std::unique_ptr<HttpPostInterface> httpPost):
|
||||
m_authState{AuthObserverInterface::State::UNINITIALIZED},
|
||||
m_authError{AuthObserverInterface::Error::NO_ERROR},
|
||||
m_isStopping{false},
|
||||
|
@ -230,34 +268,42 @@ std::string AuthDelegate::getAuthToken() {
|
|||
}
|
||||
|
||||
bool AuthDelegate::init() {
|
||||
if (!m_config) {
|
||||
std::cerr << "ERROR: AuthDelegate::create() provided nullptr config." << std::endl;
|
||||
auto configuration = avsUtils::configuration::ConfigurationNode::getRoot()[CONFIG_KEY_AUTH_DELEGATE];
|
||||
if (!configuration) {
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "missingConfigurationValue").d("key", CONFIG_KEY_AUTH_DELEGATE));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_config->getClientId().empty()) {
|
||||
std::cerr << "ERROR: Config with empty clientID." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (m_config->getClientSecret().empty()) {
|
||||
std::cerr << "ERROR: Config with empty clientSecret." << std::endl;
|
||||
if (!configuration.getString(CONFIG_KEY_CLIENT_ID, &m_clientId) || m_clientId.empty()) {
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "missingConfigurationValue").d("key", CONFIG_KEY_CLIENT_ID));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_config->getRefreshToken().empty()) {
|
||||
std::cerr << "ERROR: Config with empty refreshToken." << std::endl;
|
||||
if (!configuration.getString(CONFIG_KEY_CLIENT_SECRET, &m_clientSecret) || m_clientSecret.empty()) {
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "missingConfigurationValue").d("key", CONFIG_KEY_CLIENT_SECRET));
|
||||
return false;
|
||||
}
|
||||
m_refreshToken = m_config->getRefreshToken();
|
||||
|
||||
if (m_config->getLwaUrl().empty()) {
|
||||
std::cerr << "ERROR: Config with empty lwaUrl." << std::endl;
|
||||
if (!configuration.getString(CONFIG_KEY_REFRESH_TOKEN, &m_refreshToken) || m_refreshToken.empty()) {
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "missingConfigurationValue").d("key", CONFIG_KEY_REFRESH_TOKEN));
|
||||
return false;
|
||||
}
|
||||
|
||||
configuration.getString(CONFIG_KEY_LWA_URL, &m_lwaUrl, DEFAULT_LWA_URL);
|
||||
|
||||
configuration.getDuration<std::chrono::seconds>(
|
||||
CONFIG_KEY_REQUEST_TIMEOUT, &m_requestTimeout, DEFAULT_REQUEST_TIMEOUT);
|
||||
|
||||
configuration.getDuration<std::chrono::seconds>(
|
||||
CONFIG_KEY_AUTH_TOKEN_REFRESH_HEAD_START,
|
||||
&m_authTokenRefreshHeadStart,
|
||||
DEFAULT_AUTH_TOKEN_REFRESH_HEAD_START);
|
||||
|
||||
if (!m_HttpPost) {
|
||||
std::cerr << "ERROR: m_HttpPost can't be nullptr." << std::endl;
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "nullptrHttPost"));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_refreshAndNotifyThread = std::thread(&AuthDelegate::refreshAndNotifyThreadFunction, this);
|
||||
return true;
|
||||
}
|
||||
|
@ -295,7 +341,7 @@ void AuthDelegate::refreshAndNotifyThreadFunction() {
|
|||
AuthObserverInterface::Error AuthDelegate::refreshAuthToken() {
|
||||
// Don't wait for this request so long that we would be late to notify our observer if the token expires.
|
||||
m_requestTime = std::chrono::steady_clock::now();
|
||||
auto timeout = m_config->getRequestTimeout();
|
||||
auto timeout = m_requestTimeout;
|
||||
if (AuthObserverInterface::State::REFRESHED == m_authState) {
|
||||
auto timeUntilExpired = std::chrono::duration_cast<std::chrono::seconds>(m_expirationTime - m_requestTime);
|
||||
if (timeout > timeUntilExpired) {
|
||||
|
@ -305,12 +351,12 @@ AuthObserverInterface::Error AuthDelegate::refreshAuthToken() {
|
|||
|
||||
std::ostringstream postData;
|
||||
postData
|
||||
<< POST_DATA_UP_TO_CLIENT_ID << m_config->getClientId()
|
||||
<< POST_DATA_UP_TO_CLIENT_ID << m_clientId
|
||||
<< POST_DATA_BETWEEN_CLIENT_ID_AND_REFRESH_TOKEN << m_refreshToken
|
||||
<< POST_DATA_BETWEEN_REFRESH_TOKEN_AND_CLIENT_SECRET << m_config->getClientSecret();
|
||||
<< POST_DATA_BETWEEN_REFRESH_TOKEN_AND_CLIENT_SECRET << m_clientSecret;
|
||||
|
||||
std::string body;
|
||||
auto code = m_HttpPost->doPost(m_config->getLwaUrl(), postData.str(), timeout, body);
|
||||
auto code = m_HttpPost->doPost(m_lwaUrl, postData.str(), timeout, body);
|
||||
auto newError = handleLwaResponse(code, body);
|
||||
|
||||
if (AuthObserverInterface::Error::NO_ERROR == newError) {
|
||||
|
@ -328,17 +374,17 @@ AuthObserverInterface::Error AuthDelegate::refreshAuthToken() {
|
|||
AuthObserverInterface::Error AuthDelegate::handleLwaResponse(long code, const std::string& body) {
|
||||
rapidjson::Document document;
|
||||
if (document.Parse(body.c_str()).HasParseError()) {
|
||||
std::cerr << "Error in JSON at position "
|
||||
<< document.GetErrorOffset()
|
||||
<< ": "
|
||||
<< GetParseError_En(document.GetParseError()) << std::endl;
|
||||
ACSDK_ERROR(LX("handleLwaResponseFailed")
|
||||
.d("reason", "parseJsonFailed")
|
||||
.d("position", document.GetErrorOffset())
|
||||
.d("error", GetParseError_En(document.GetParseError())));
|
||||
return AuthObserverInterface::Error::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (HttpPostInterface::HTTP_RESPONSE_CODE_SUCCESS_OK == code) {
|
||||
std::string authToken;
|
||||
std::string refreshToken;
|
||||
uint64_t expiresIn = 0;
|
||||
uint64_t expiresInSeconds = 0;
|
||||
|
||||
auto authTokenIterator = document.FindMember(JSON_KEY_ACCESS_TOKEN.c_str());
|
||||
if (authTokenIterator != document.MemberEnd() && authTokenIterator->value.IsString()) {
|
||||
|
@ -352,37 +398,24 @@ AuthObserverInterface::Error AuthDelegate::handleLwaResponse(long code, const st
|
|||
|
||||
auto expiresInIterator = document.FindMember(JSON_KEY_EXPIRES_IN.c_str());
|
||||
if (expiresInIterator != document.MemberEnd() && expiresInIterator->value.IsUint64()) {
|
||||
expiresIn = expiresInIterator->value.GetUint64();
|
||||
expiresInSeconds = expiresInIterator->value.GetUint64();
|
||||
}
|
||||
|
||||
if (authToken.empty() || refreshToken.empty() || expiresIn == 0) {
|
||||
std::cerr
|
||||
<< "handleResponse() failed: authToken.empty()='"
|
||||
<< authToken.empty()
|
||||
<< "' refreshToken.empty()='"
|
||||
<< refreshToken.empty()
|
||||
<< "' (expiresIn<=0)="
|
||||
<< (expiresIn <= 0)
|
||||
<< std::endl;
|
||||
|
||||
#ifdef DEBUG
|
||||
// TODO: Private data and must be logged at or below DEBUG level when we integrate the logging lib ACSDK-57.
|
||||
std::cerr << "response='" << body << "'" << std::endl;
|
||||
#endif
|
||||
|
||||
if (authToken.empty() || refreshToken.empty() || expiresInSeconds == 0) {
|
||||
ACSDK_ERROR(LX("handleLwaResponseFailed")
|
||||
.d("authTokenEmpty", authToken.empty())
|
||||
.d("refreshTokenEmpty", refreshToken.empty())
|
||||
.d("expiresInSeconds", expiresInSeconds));
|
||||
ACSDK_DEBUG(LX("handleLwaresponseFailed").d("body", body));
|
||||
return AuthObserverInterface::Error::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// TODO: Private data and must be logged at or below DEBUG level when we integrate the logging lib ACSDK-57.
|
||||
if (m_refreshToken != refreshToken) {
|
||||
std::cout << "RefreshToken updated to: '" << refreshToken << "'" << std::endl;
|
||||
}
|
||||
#endif
|
||||
ACSDK_DEBUG(LX("handleLwaResponseSucceeded")
|
||||
.d("refreshToken", refreshToken).d("authToken", authToken).d("expiresInSeconds", expiresInSeconds));
|
||||
|
||||
m_refreshToken = refreshToken;
|
||||
m_expirationTime = m_requestTime + std::chrono::seconds(expiresIn);
|
||||
m_timeToRefresh = m_expirationTime - m_config->getAuthTokenRefreshHeadStart();
|
||||
m_expirationTime = m_requestTime + std::chrono::seconds(expiresInSeconds);
|
||||
m_timeToRefresh = m_expirationTime - m_authTokenRefreshHeadStart;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_authToken = authToken;
|
||||
|
@ -397,18 +430,10 @@ AuthObserverInterface::Error AuthDelegate::handleLwaResponse(long code, const st
|
|||
}
|
||||
if (!error.empty()) {
|
||||
// If `error` field is non-empty in the body, it means that we encountered an error.
|
||||
if (isUnrecoverable(error)) {
|
||||
std::cerr << "The LWA response body indicated an unrecoverable error: " << error << std::endl;
|
||||
} else {
|
||||
std::cerr << "The LWA response body indicated a recoverable or unknown error: " << error << std::endl;
|
||||
}
|
||||
ACSDK_ERROR(LX("handleLwaResponseFailed").d("error", error).d("isUnrecoverable", isUnrecoverable(error)));
|
||||
return getErrorCode(error);
|
||||
} else {
|
||||
std::cerr << "The LWA response encountered an unknown response body." << std::endl;
|
||||
#ifdef DEBUG
|
||||
// TODO: Private data and must be logged at or below DEBUG level when we integrate the logging lib ACSDK-57.
|
||||
std::cerr << "Message body: " << body << std::endl;
|
||||
#endif
|
||||
ACSDK_ERROR(LX("handleLwaResponseFailed").d("error", "errorNotFoundInResponseBody"));
|
||||
return AuthObserverInterface::Error::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
@ -422,7 +447,7 @@ void AuthDelegate::setState(AuthObserverInterface::State newState) {
|
|||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (isUnrecoverable(m_authError)) {
|
||||
std::cerr << "The thread is stopping due to the unrecoverable error." << std::endl;
|
||||
ACSDK_ERROR(LX("threadStopping").d("reason", "encounteredUnrecoverableError"));
|
||||
newState = AuthObserverInterface::State::UNRECOVERABLE_ERROR;
|
||||
m_isStopping = true;
|
||||
}
|
||||
|
@ -430,6 +455,7 @@ void AuthDelegate::setState(AuthObserverInterface::State newState) {
|
|||
if (m_authState != newState) {
|
||||
m_authState = newState;
|
||||
if (m_observer) {
|
||||
ACSDK_DEBUG(LX("onAuthStateChangeCalled").d("state", (int)m_authState).d("error", (int)m_authError));
|
||||
m_observer->onAuthStateChange(m_authState, m_authError);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ find_package(Threads ${THREADS_PACKAGE_CONFIG})
|
|||
|
||||
add_library(AuthDelegate SHARED
|
||||
AuthDelegate.cpp
|
||||
Config.cpp
|
||||
HttpPost.cpp)
|
||||
target_include_directories(AuthDelegate PUBLIC
|
||||
"${AuthDelegate_SOURCE_DIR}/include")
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* Config.cpp
|
||||
*
|
||||
* Copyright 2016-2017 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 "AuthDelegate/Config.h"
|
||||
#include "AuthDelegate/HttpPost.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
static const std::string DEFAULT_LWA_URL = "https://api.amazon.com/auth/o2/token";
|
||||
static const std::string DEFAULT_CLIENT_ID = "";
|
||||
static const std::string DEFAULT_REFRESH_TOKEN = "";
|
||||
static const std::string DEFAULT_CLIENT_SECRET = "";
|
||||
static const std::chrono::seconds DEFAULT_AUTH_TOKEN_REFRESH_HEAD_START = std::chrono::seconds(10 * 60);
|
||||
static const std::chrono::seconds DEFAULT_MAX_AUTH_TOKEN_TTL = std::chrono::seconds::max();
|
||||
static const std::chrono::seconds DEFAULT_REQUEST_TIMEOUT = std::chrono::seconds(5 * 60);
|
||||
|
||||
Config::Config() :
|
||||
m_lwaUrl(DEFAULT_LWA_URL),
|
||||
m_clientId(DEFAULT_CLIENT_ID),
|
||||
m_refreshToken(DEFAULT_REFRESH_TOKEN),
|
||||
m_clientSecret(DEFAULT_CLIENT_SECRET),
|
||||
m_authTokenRefreshHeadStart(DEFAULT_AUTH_TOKEN_REFRESH_HEAD_START),
|
||||
m_maxAuthTokenTtl(DEFAULT_MAX_AUTH_TOKEN_TTL),
|
||||
m_requestTimeout(DEFAULT_REQUEST_TIMEOUT) {
|
||||
};
|
||||
|
||||
std::string Config::getLwaUrl() const {
|
||||
return m_lwaUrl;
|
||||
}
|
||||
|
||||
std::string Config::getClientId() const {
|
||||
return m_clientId;
|
||||
}
|
||||
|
||||
std::string Config::getRefreshToken() const {
|
||||
return m_refreshToken;
|
||||
}
|
||||
|
||||
std::string Config::getClientSecret() const {
|
||||
return m_clientSecret;
|
||||
}
|
||||
|
||||
std::chrono::seconds Config::getAuthTokenRefreshHeadStart() const {
|
||||
return m_authTokenRefreshHeadStart;
|
||||
}
|
||||
|
||||
std::chrono::seconds Config::getRequestTimeout() const {
|
||||
return m_requestTimeout;
|
||||
}
|
||||
|
||||
} // namespace authDelegate
|
||||
} // namespace alexaClientSDK
|
|
@ -14,13 +14,28 @@
|
|||
* express or implied. See the License for the specific language governing
|
||||
* permissions and limitations under the License.
|
||||
*/
|
||||
#include <iostream>
|
||||
#include "AuthDelegate/Config.h"
|
||||
|
||||
#include <AVSUtils/LibcurlUtils/LibcurlUtils.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "AuthDelegate/HttpPost.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
using namespace alexaClientSDK::avsUtils;
|
||||
|
||||
/// String to identify log entries originating from this file.
|
||||
static const std::string TAG("HttpPost");
|
||||
|
||||
/**
|
||||
* Create a LogEntry using this file's TAG and the specified event string.
|
||||
*
|
||||
* @param The event string for this @c LogEntry.
|
||||
*/
|
||||
#define LX(event) alexaClientSDK::avsUtils::logger::LogEntry(TAG, event)
|
||||
|
||||
std::unique_ptr<HttpPost> HttpPost::create() {
|
||||
std::unique_ptr<HttpPost> httpPost(new HttpPost());
|
||||
if (httpPost->init()){
|
||||
|
@ -35,7 +50,10 @@ HttpPost::HttpPost() : m_curl{nullptr} {
|
|||
bool HttpPost::init() {
|
||||
m_curl = curl_easy_init();
|
||||
if (!m_curl) {
|
||||
std::cerr << "curl_easy_init() failed." << std::endl;
|
||||
ACSDK_ERROR(LX("initFailed").d("reason", "curl_easy_initFailed"));
|
||||
return false;
|
||||
}
|
||||
if (!libcurlUtils::prepareForTLS(m_curl)) {
|
||||
return false;
|
||||
}
|
||||
if (!setopt(CURLOPT_WRITEFUNCTION, staticWriteCallbackLocked)) {
|
||||
|
@ -65,15 +83,13 @@ long HttpPost::doPost(const std::string& url,
|
|||
return HTTP_RESPONSE_CODE_UNDEFINED;
|
||||
}
|
||||
|
||||
if (!m_curl) {
|
||||
std::cerr << "HttpPost::perform() m_curl == nullptr" << std::endl;
|
||||
return HTTP_RESPONSE_CODE_UNDEFINED;
|
||||
}
|
||||
|
||||
auto result = curl_easy_perform(m_curl);
|
||||
|
||||
if (result != CURLE_OK) {
|
||||
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(result) << std::endl;
|
||||
ACSDK_ERROR(LX("doPostFailed")
|
||||
.d("reason", "curl_easy_performFailed")
|
||||
.d("result", result)
|
||||
.d("error", curl_easy_strerror(result)));
|
||||
body.clear();
|
||||
return HTTP_RESPONSE_CODE_UNDEFINED;
|
||||
}
|
||||
|
@ -81,31 +97,29 @@ long HttpPost::doPost(const std::string& url,
|
|||
long responseCode = 0;
|
||||
result = curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &responseCode);
|
||||
if (result != CURLE_OK) {
|
||||
std::cerr << "curl_easy_getinfo(CURLINFO_RESPONSE_CODE) failed: " << curl_easy_strerror(result) << std::endl;
|
||||
ACSDK_ERROR(LX("doPostFailed")
|
||||
.d("reason", "curl_easy_getinfoFailed")
|
||||
.d("property", "CURLINFO_RESPONSE_CODE")
|
||||
.d("result", result)
|
||||
.d("error", curl_easy_strerror(result)));
|
||||
body.clear();
|
||||
return HTTP_RESPONSE_CODE_UNDEFINED;
|
||||
} else {
|
||||
std::cout << "curl_easy_getinfo(CURLINFO_RESPONSE_CODE) successfully returned the HTTP code: "
|
||||
<< responseCode << std::endl;
|
||||
ACSDK_DEBUG(LX("doPostSucceeded").d("code", responseCode));
|
||||
return responseCode;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ParamType>
|
||||
bool HttpPost::setopt(CURLoption option, ParamType param) {
|
||||
if (!m_curl) {
|
||||
|
||||
#ifdef DEBUG
|
||||
// TODO Integrate with Logging library: ACSDK-57
|
||||
// This may be private information so any logging of values must be at debug level
|
||||
std::cerr << "!m_curl in HttpPost::setopt(option=" << option << ")" << std::endl;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
auto result = curl_easy_setopt(m_curl, option, param);
|
||||
bool HttpPost::setopt(CURLoption option, ParamType value) {
|
||||
auto result = curl_easy_setopt(m_curl, option, value);
|
||||
if (result != CURLE_OK) {
|
||||
std::cerr << "curl_easy_setopt() failed: " << curl_easy_strerror(result) << std::endl;
|
||||
ACSDK_ERROR(LX("setoptFailed")
|
||||
.d("reason", "nullCurlHandle")
|
||||
.d("option", option)
|
||||
.sensitive("value", value)
|
||||
.d("result", result)
|
||||
.d("error", curl_easy_strerror(result)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -113,7 +127,7 @@ bool HttpPost::setopt(CURLoption option, ParamType param) {
|
|||
|
||||
size_t HttpPost::staticWriteCallbackLocked(char* ptr, size_t size, size_t nmemb, void* userdata) {
|
||||
if (!userdata) {
|
||||
std::cerr << "staticWriteCallback() userdata == nullptr." << std::endl;
|
||||
ACSDK_ERROR(LX("staticWriteCallbackFailed").d("reason", "nullUserData"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* MockConfig.h
|
||||
*
|
||||
* Copyright 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_AUTHDELEGATE_MOCK_CONFIG_H_
|
||||
#define ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_AUTHDELEGATE_MOCK_CONFIG_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <gmock/gmock.h>
|
||||
#include <string>
|
||||
|
||||
#include "AuthDelegate/Config.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
/// Mock Config class
|
||||
class MockConfig : public Config {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(getLwaUrl, std::string());
|
||||
MOCK_CONST_METHOD0(getClientId, std::string());
|
||||
MOCK_CONST_METHOD0(getRefreshToken, std::string());
|
||||
MOCK_CONST_METHOD0(getClientSecret, std::string());
|
||||
MOCK_CONST_METHOD0(getAuthTokenRefreshHeadStart, std::chrono::seconds());
|
||||
MOCK_CONST_METHOD0(getMaxAuthTokenTtl, std::chrono::seconds());
|
||||
MOCK_CONST_METHOD0(getRequestTimeout, std::chrono::seconds());
|
||||
};
|
||||
|
||||
} // namespace authDelegate
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_AUTHDELEGATE_TEST_AUTHDELEGATE_MOCK_CONFIG_H_
|
|
@ -26,7 +26,6 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "AuthDelegate/AuthDelegate.h"
|
||||
#include "AuthDelegate/MockConfig.h"
|
||||
#include "AuthDelegate/MockHttpPost.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
#include "MockAuthObserver.h"
|
||||
|
@ -35,6 +34,7 @@ using namespace alexaClientSDK::authDelegate;
|
|||
using namespace alexaClientSDK::acl;
|
||||
|
||||
using namespace ::testing;
|
||||
using namespace alexaClientSDK::avsUtils::initialization;
|
||||
|
||||
/// URL to which the refresh token and access token request should be sent.
|
||||
static const std::string DEFAULT_LWA_URL = "https://api.amazon.com/auth/o2/token";
|
||||
|
@ -54,6 +54,16 @@ static const std::string ERROR_CODE_INVALID_REQUEST = "invalid_request";
|
|||
/// The HTTP response code for a bad request.
|
||||
static const long HTTP_RESPONSE_CODE_BAD_REQUEST = 400;
|
||||
|
||||
/// Default SDK configuration.
|
||||
static const std::string DEFAULT_SDK_CONFIGURATION = R"({
|
||||
"authDelegate" : {
|
||||
"clientId" : "invalid clientId",
|
||||
"refreshToken" : "invalid refreshToken",
|
||||
"clientSecret" : "invalid clientSecret",
|
||||
"authTokenRefreshHeadStart" : 1
|
||||
}
|
||||
})";
|
||||
|
||||
/// Define test fixture for testing AuthDelegate.
|
||||
class AuthDelegateTest : public ::testing::Test {
|
||||
protected:
|
||||
|
@ -61,25 +71,20 @@ protected:
|
|||
/// Initialize the objects for testing
|
||||
AuthDelegateTest() {
|
||||
m_mockHttpPost = std::unique_ptr<MockHttpPost>(new MockHttpPost());
|
||||
m_mockConfig = std::make_shared<NiceMock<MockConfig>>();
|
||||
m_mockAuthObserver = std::make_shared<NiceMock<MockAuthObserver>>();
|
||||
}
|
||||
|
||||
/// Stub certain mock objects with default actions
|
||||
virtual void SetUp() override {
|
||||
|
||||
ASSERT_TRUE(alexaClientSDK::avsUtils::initialization::AlexaClientSDKInit::initialize());
|
||||
|
||||
ON_CALL(*m_mockHttpPost, doPost(_, _, _, _))
|
||||
.WillByDefault(Return(HttpPostInterface::HTTP_RESPONSE_CODE_UNDEFINED));
|
||||
ON_CALL(*m_mockConfig, getClientId()).WillByDefault(Return("testClientId (invalid)"));
|
||||
ON_CALL(*m_mockConfig, getClientSecret()).WillByDefault(Return("testClientSecret (invalid)"));
|
||||
ON_CALL(*m_mockConfig, getRefreshToken()).WillByDefault(Return("testRefreshToken (invalid)"));
|
||||
ON_CALL(*m_mockConfig, getLwaUrl()).WillByDefault(Return(DEFAULT_LWA_URL));
|
||||
std::stringstream configuration;
|
||||
configuration << DEFAULT_SDK_CONFIGURATION;
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&configuration}));
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
alexaClientSDK::avsUtils::initialization::AlexaClientSDKInit::uninitialize();
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,8 +111,8 @@ protected:
|
|||
"expires_in":)";
|
||||
response += std::to_string(seconds.count());
|
||||
response += R"(,
|
||||
"refresh_token":"Atzr|IQEBLzAtAhUAibmh-1N0EVztZJofMx",
|
||||
"token_type":"bearer"
|
||||
"refresh_token":"Atzr|IQEBLzAtAhUAibmh-1N0EVztZJofMx",
|
||||
"token_type":"bearer"
|
||||
})";
|
||||
return response;
|
||||
}
|
||||
|
@ -131,9 +136,6 @@ protected:
|
|||
/// Mock object of @c HttpPostInterface through which refresh token request is sent in AuthDelegate.
|
||||
std::unique_ptr<MockHttpPost> m_mockHttpPost;
|
||||
|
||||
/// Mock object of @c Config which provides the AuthDelegate required information to get access tokens from LWA.
|
||||
std::shared_ptr<NiceMock<MockConfig>> m_mockConfig;
|
||||
|
||||
/// Mock object of @c AuthObserverInterface which will be notified on current AuthDelegate status.
|
||||
std::shared_ptr<NiceMock<MockAuthObserver>> m_mockAuthObserver;
|
||||
|
||||
|
@ -148,51 +150,74 @@ protected:
|
|||
* Test create() with a @c nullptr Config, expecting @c nullptr to be returned.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, createNullConfig) {
|
||||
ASSERT_FALSE(AuthDelegate::create(nullptr, std::move(m_mockHttpPost)));
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({}));
|
||||
ASSERT_FALSE(AuthDelegate::create(std::move(m_mockHttpPost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create() without a clientId set, expecting a @c nullptr to be returned.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, createMissingClientId) {
|
||||
|
||||
EXPECT_CALL(*m_mockConfig, getClientId()).WillRepeatedly(Return(""));
|
||||
|
||||
ASSERT_FALSE(AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost)));
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
std::stringstream configuration;
|
||||
configuration << DEFAULT_SDK_CONFIGURATION;
|
||||
std::stringstream overlay;
|
||||
overlay << R"X({
|
||||
"authDelegate" : {
|
||||
"clientId" : ""
|
||||
}
|
||||
})X";
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&configuration, &overlay}));
|
||||
ASSERT_FALSE(AuthDelegate::create(std::move(m_mockHttpPost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create() without a clientSecret set, expecting a @c nullptr to be returned.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, createMissingClientSecret) {
|
||||
|
||||
EXPECT_CALL(*m_mockConfig, getClientSecret()).WillRepeatedly(Return(""));
|
||||
|
||||
ASSERT_FALSE(AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost)));
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
std::stringstream configuration;
|
||||
configuration << DEFAULT_SDK_CONFIGURATION;
|
||||
std::stringstream overlay;
|
||||
overlay << R"X({
|
||||
"authDelegate" : {
|
||||
"clientSecret" : ""
|
||||
}
|
||||
})X";
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&configuration, &overlay}));
|
||||
ASSERT_FALSE(AuthDelegate::create(std::move(m_mockHttpPost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create() without a refresh token set, expecting a @c nullptr to be returned.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, createMissingRefreshToken) {
|
||||
|
||||
EXPECT_CALL(*m_mockConfig, getRefreshToken()).WillRepeatedly(Return(""));
|
||||
|
||||
ASSERT_FALSE(AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost)));
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
std::stringstream configuration;
|
||||
configuration << DEFAULT_SDK_CONFIGURATION;
|
||||
std::stringstream overlay;
|
||||
overlay << R"X({
|
||||
"authDelegate" : {
|
||||
"refreshToken" : ""
|
||||
}
|
||||
})X";
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&configuration, &overlay}));
|
||||
ASSERT_FALSE(AuthDelegate::create(std::move(m_mockHttpPost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test create() with a valid Config, expecting a valid AuthDelegate to be returned.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, create) {
|
||||
ASSERT_TRUE(AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost)));
|
||||
ASSERT_TRUE(AuthDelegate::create(std::move(m_mockHttpPost)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setAuthObserver() with a @c nullptr, expecting no exceptions or crashes.
|
||||
*/
|
||||
TEST_F(AuthDelegateTest, setAuthObserverNull) {
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
ASSERT_TRUE(authDelegate);
|
||||
|
||||
authDelegate->setAuthObserver(nullptr);
|
||||
|
@ -206,19 +231,18 @@ TEST_F(AuthDelegateTest, setAuthObserverNull) {
|
|||
*/
|
||||
TEST_F(AuthDelegateTest, setAuthObserver) {
|
||||
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
ASSERT_TRUE(authDelegate);
|
||||
|
||||
EXPECT_CALL(
|
||||
*m_mockAuthObserver,
|
||||
onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, AuthObserverInterface::Error::NO_ERROR));
|
||||
onAuthStateChange(AuthObserverInterface::State::UNINITIALIZED, _))
|
||||
.Times(AtMost(1));
|
||||
|
||||
EXPECT_CALL(
|
||||
*m_mockAuthObserver,
|
||||
onAuthStateChange(
|
||||
AuthObserverInterface::State::EXPIRED,
|
||||
AuthObserverInterface::Error::AUTHORIZATION_FAILED))
|
||||
.Times(AtMost(1));
|
||||
onAuthStateChange(AuthObserverInterface::State::EXPIRED, _))
|
||||
.Times(AtMost(1));
|
||||
|
||||
authDelegate->setAuthObserver(m_mockAuthObserver);
|
||||
}
|
||||
|
@ -250,7 +274,7 @@ TEST_F(AuthDelegateTest, retry) {
|
|||
m_cv.notify_all();
|
||||
}));
|
||||
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
authDelegate->setAuthObserver(m_mockAuthObserver);
|
||||
waitFor(TIME_OUT_IN_SECONDS, [&tokenRefreshed]() { return tokenRefreshed;});
|
||||
}
|
||||
|
@ -288,7 +312,7 @@ TEST_F(AuthDelegateTest, expirationNotification) {
|
|||
m_cv.notify_all();
|
||||
}));
|
||||
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
authDelegate->setAuthObserver(m_mockAuthObserver);
|
||||
waitFor(TIME_OUT_IN_SECONDS, [&tokenExpired]() {return tokenExpired;});
|
||||
}
|
||||
|
@ -301,12 +325,13 @@ TEST_F(AuthDelegateTest, expirationNotification) {
|
|||
*/
|
||||
TEST_F(AuthDelegateTest, recoverAfterExpiration) {
|
||||
bool tokenRefreshed = false;
|
||||
const auto& validResponse = generateValidLwaResponseWithExpiration(std::chrono::seconds(1));
|
||||
const auto& validResponse = generateValidLwaResponseWithExpiration(std::chrono::seconds(3));
|
||||
|
||||
EXPECT_CALL(*m_mockHttpPost, doPost(_, _, _, _))
|
||||
.WillOnce(DoAll(SetArgReferee<3>(validResponse), Return(HttpPostInterface::HTTP_RESPONSE_CODE_SUCCESS_OK)))
|
||||
.WillOnce(Return(HttpPostInterface::HTTP_RESPONSE_CODE_UNDEFINED))
|
||||
.WillOnce(Return(HttpPostInterface::HTTP_RESPONSE_CODE_UNDEFINED))
|
||||
.WillOnce(Return(HttpPostInterface::HTTP_RESPONSE_CODE_UNDEFINED))
|
||||
.WillOnce(DoAll(SetArgReferee<3>(validResponse), Return(HttpPostInterface::HTTP_RESPONSE_CODE_SUCCESS_OK)));
|
||||
|
||||
::testing::InSequence s;
|
||||
|
@ -333,7 +358,7 @@ TEST_F(AuthDelegateTest, recoverAfterExpiration) {
|
|||
m_cv.notify_all();
|
||||
}));
|
||||
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
authDelegate->setAuthObserver(m_mockAuthObserver);
|
||||
waitFor(TIME_OUT_IN_SECONDS, [&tokenRefreshed]() {return tokenRefreshed;});
|
||||
}
|
||||
|
@ -367,7 +392,7 @@ TEST_F(AuthDelegateTest, unrecoverableErrorNotification) {
|
|||
m_cv.notify_all();
|
||||
}));
|
||||
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig, std::move(m_mockHttpPost));
|
||||
auto authDelegate = AuthDelegate::create(std::move(m_mockHttpPost));
|
||||
authDelegate->setAuthObserver(m_mockAuthObserver);
|
||||
waitFor(TIME_OUT_IN_SECONDS, [&errorReceived]() {return errorReceived;});
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
include_directories("${CMAKE_SOURCE_DIR}/ACL/test/")
|
||||
include_directories("${ACL_SOURCE_DIR}/test/")
|
||||
discover_unit_tests("${AuthDelegate_SOURCE_DIR}/include" AuthDelegate)
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* ConfigTest.cpp
|
||||
*
|
||||
* Copyright 2016 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 "AuthDelegate/Config.h"
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace authDelegate {
|
||||
|
||||
class ConfigTest : public ::testing::Test {
|
||||
protected:
|
||||
Config configuration;
|
||||
};
|
||||
|
||||
TEST_F(ConfigTest, lwa_url_is_secure) {
|
||||
std::string url = configuration.getLwaUrl();
|
||||
|
||||
EXPECT_THAT(url, testing::StartsWith("https"));
|
||||
}
|
||||
|
||||
} // namespace authDelegate
|
||||
} // namespace alexaClientSDK
|
|
@ -9,11 +9,11 @@ include(tools/Testing.cmake)
|
|||
|
||||
# Alexa Client SDK targets.
|
||||
add_subdirectory("ThirdParty")
|
||||
add_subdirectory("AuthDelegate")
|
||||
add_subdirectory("ACL")
|
||||
add_subdirectory("ADSL")
|
||||
add_subdirectory("Integration")
|
||||
add_subdirectory("AVSUtils")
|
||||
add_subdirectory("AVSCommon")
|
||||
add_subdirectory("ACL")
|
||||
add_subdirectory("AuthDelegate")
|
||||
add_subdirectory("ADSL")
|
||||
add_subdirectory("Integration")
|
||||
add_subdirectory("AFML")
|
||||
add_subdirectory("doc")
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* FileConfig.h
|
||||
*
|
||||
* Copyright 2016-2017 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.
|
||||
*/
|
||||
|
||||
#ifndef ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_FILE_CONFIG_H_
|
||||
#define ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_FILE_CONFIG_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "AuthDelegate/Config.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace integration {
|
||||
|
||||
using namespace alexaClientSDK::authDelegate;
|
||||
|
||||
class FileConfig : public Config {
|
||||
public:
|
||||
FileConfig(const std::string&);
|
||||
};
|
||||
|
||||
} // namespace integration
|
||||
} // namespace alexaClientSDK
|
||||
|
||||
#endif // ALEXA_CLIENT_SDK_INTEGRATION_INCLUDE_FILE_CONFIG_H_
|
Binary file not shown.
Binary file not shown.
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* FileConfig.cpp
|
||||
*
|
||||
* Copyright 2016-2017 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 <fstream>
|
||||
#include "Integration/FileConfig.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace integration {
|
||||
|
||||
FileConfig::FileConfig(const std::string& path) {
|
||||
std::ifstream infile(path);
|
||||
if (!infile.good()) {
|
||||
std::cerr << "Integration tests require credentials placed in " << path << std::endl;
|
||||
}
|
||||
for (std::string line; infile.good(); std::getline(infile, line)) {
|
||||
size_t index = line.find("=");
|
||||
if (std::string::npos != index) {
|
||||
std::string key = line.substr(0, index);
|
||||
std::string value = line.substr(index + 1);
|
||||
if ("clientId" == key) {
|
||||
m_clientId = value;
|
||||
} else if ("refreshToken" == key) {
|
||||
m_refreshToken = value;
|
||||
} else if ("clientSecret" == key) {
|
||||
m_clientSecret = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace integration
|
||||
} // namespace alexaClientSDK
|
|
@ -16,15 +16,16 @@
|
|||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "AuthDelegate/AuthDelegate.h"
|
||||
#include "AuthDelegate/MockConfig.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
#include "Integration/FileConfig.h"
|
||||
#include "Integration/AuthObserver.h"
|
||||
#include <AuthDelegate/AuthDelegate.h>
|
||||
#include <AVSUtils/Initialization/AlexaClientSDKInit.h>
|
||||
|
||||
#include "Integration/AuthObserver.h"
|
||||
|
||||
using namespace alexaClientSDK::avsUtils::initialization;
|
||||
using namespace alexaClientSDK::authDelegate;
|
||||
|
@ -32,7 +33,7 @@ using namespace ::testing;
|
|||
|
||||
namespace {
|
||||
|
||||
/// Path to the AuthDelegate.config file
|
||||
/// Path to the AlexaClientSDKConfig.json file
|
||||
std::string g_configPath;
|
||||
|
||||
/// Timeout in seconds for AuthDelegate to wait for LWA response.
|
||||
|
@ -50,18 +51,13 @@ protected:
|
|||
/// Initialize test dependencies
|
||||
AlexaAuthorizationDelegateTest() {
|
||||
m_authObserver = std::make_shared<AuthObserver>();
|
||||
m_config = std::make_shared<FileConfig>(g_configPath);
|
||||
m_mockConfig = std::make_shared<NiceMock<MockConfig>>();
|
||||
}
|
||||
|
||||
/// Stub certain mock objects with default actions
|
||||
virtual void SetUp() {
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize());
|
||||
|
||||
ON_CALL(*m_mockConfig, getClientId()).WillByDefault(Return(m_config->getClientId()));
|
||||
ON_CALL(*m_mockConfig, getClientSecret()).WillByDefault(Return(m_config->getClientSecret()));
|
||||
ON_CALL(*m_mockConfig, getRefreshToken()).WillByDefault(Return(m_config->getRefreshToken()));
|
||||
ON_CALL(*m_mockConfig, getLwaUrl()).WillByDefault(Return(m_config->getLwaUrl()));
|
||||
std::ifstream infile(g_configPath);
|
||||
ASSERT_TRUE(infile.good());
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&infile}));
|
||||
}
|
||||
|
||||
/// Release resources
|
||||
|
@ -69,12 +65,6 @@ protected:
|
|||
AlexaClientSDKInit::uninitialize();
|
||||
}
|
||||
|
||||
/// Config object that contains valid information needed by LWA for authorization
|
||||
std::shared_ptr<Config> m_config;
|
||||
|
||||
/// Mock object of @c Config which could be stubbed with desired behaviors
|
||||
std::shared_ptr<NiceMock<MockConfig>> m_mockConfig;
|
||||
|
||||
/// AuthObserver that implements AuthObserverInterface
|
||||
std::shared_ptr<AuthObserver> m_authObserver;
|
||||
};
|
||||
|
@ -86,7 +76,7 @@ protected:
|
|||
* be able to retrieve the valid refresh token (get authorized).
|
||||
*/
|
||||
TEST_F(AlexaAuthorizationDelegateTest, refreshAuthToken) {
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig);
|
||||
auto authDelegate = AuthDelegate::create();
|
||||
authDelegate->setAuthObserver(m_authObserver);
|
||||
bool tokenRefreshed = m_authObserver->waitFor(AuthObserver::State::REFRESHED,
|
||||
std::chrono::seconds(TIME_OUT_IN_SECONDS));
|
||||
|
@ -100,9 +90,17 @@ TEST_F(AlexaAuthorizationDelegateTest, refreshAuthToken) {
|
|||
* notify the observer of the error.
|
||||
*/
|
||||
TEST_F(AlexaAuthorizationDelegateTest, invalidRefreshTokenWithUnrecoverableError) {
|
||||
ON_CALL(*m_mockConfig, getRefreshToken())
|
||||
.WillByDefault(Return("InvalidRefreshToken"));
|
||||
auto authDelegate = AuthDelegate::create(m_mockConfig);
|
||||
AlexaClientSDKInit::uninitialize();
|
||||
std::ifstream infile(g_configPath);
|
||||
ASSERT_TRUE(infile.good());
|
||||
std::stringstream override;
|
||||
override << R"({
|
||||
"authDelegate" : {
|
||||
"refreshToken" : "InvalidRefreshToken"
|
||||
}
|
||||
})";
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&infile, &override}));
|
||||
auto authDelegate = AuthDelegate::create();
|
||||
authDelegate->setAuthObserver(m_authObserver);
|
||||
bool gotUnrecoverableError = m_authObserver->waitFor(AuthObserver::State::UNRECOVERABLE_ERROR,
|
||||
std::chrono::seconds(TIME_OUT_IN_SECONDS));
|
||||
|
@ -115,7 +113,7 @@ TEST_F(AlexaAuthorizationDelegateTest, invalidRefreshTokenWithUnrecoverableError
|
|||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
if (argc < 2) {
|
||||
std::cerr << "USAGE: AlexaAuthorizationDelegateTest <path_to_auth_delgate_config>" << std::endl;
|
||||
std::cerr << "USAGE: AlexaAuthorizationDelegateTest <path to AlexaClientSDKConfig.json>" << std::endl;
|
||||
return 1;
|
||||
} else {
|
||||
g_configPath = std::string(argv[1]);
|
||||
|
|
|
@ -17,24 +17,26 @@
|
|||
|
||||
/// @file AlexaCommunicationsLibraryTest.cpp
|
||||
|
||||
#include "ACL/AVSConnectionManager.h"
|
||||
#include "ACL/Message.h"
|
||||
#include "ACL/Transport/HTTP2MessageRouter.h"
|
||||
#include "ACL/Values.h"
|
||||
#include "AuthDelegate/AuthDelegate.h"
|
||||
#include "AuthDelegate/Config.h"
|
||||
#include "AVSUtils/Initialization/AlexaClientSDKInit.h"
|
||||
#include "Integration/AuthObserver.h"
|
||||
#include "Integration/ClientMessageHandler.h"
|
||||
#include "Integration/ConnectionStatusObserver.h"
|
||||
#include "Integration/FileConfig.h"
|
||||
#include "Integration/ObservableMessageRequest.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <future>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ACL/AVSConnectionManager.h>
|
||||
#include <ACL/Message.h>
|
||||
#include <ACL/Transport/HTTP2MessageRouter.h>
|
||||
#include <ACL/Values.h>
|
||||
#include <AuthDelegate/AuthDelegate.h>
|
||||
#include <AVSUtils/Initialization/AlexaClientSDKInit.h>
|
||||
#include <AVSUtils/Logger/LogEntry.h>
|
||||
#include <AVSUtils/Logging/Logger.h>
|
||||
|
||||
#include "Integration/AuthObserver.h"
|
||||
#include "Integration/ClientMessageHandler.h"
|
||||
#include "Integration/ConnectionStatusObserver.h"
|
||||
#include "Integration/ObservableMessageRequest.h"
|
||||
|
||||
namespace alexaClientSDK {
|
||||
namespace integration {
|
||||
|
||||
|
@ -171,10 +173,11 @@ std::string inputPath;
|
|||
class AlexaCommunicationsLibraryTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize());
|
||||
m_config = std::make_shared<FileConfig>(configPath);
|
||||
std::ifstream infile(configPath);
|
||||
ASSERT_TRUE(infile.good());
|
||||
ASSERT_TRUE(AlexaClientSDKInit::initialize({&infile}));
|
||||
m_authObserver = std::make_shared<AuthObserver>();
|
||||
m_authDelegate = AuthDelegate::create(m_config);
|
||||
ASSERT_TRUE(m_authDelegate = AuthDelegate::create());
|
||||
m_authDelegate->setAuthObserver(m_authObserver);
|
||||
m_connectionStatusObserver = std::make_shared<ConnectionStatusObserver>();
|
||||
m_clientMessageHandler = std::make_shared<ClientMessageHandler>();
|
||||
|
@ -202,9 +205,10 @@ protected:
|
|||
}
|
||||
|
||||
virtual void disconnect() {
|
||||
m_avsConnectionManager->disable();
|
||||
ASSERT_TRUE(m_connectionStatusObserver->waitFor(ConnectionStatus::DISCONNECTED))
|
||||
<< "Connecting timed out.";
|
||||
if (m_avsConnectionManager) {
|
||||
m_avsConnectionManager->disable();
|
||||
ASSERT_TRUE(m_connectionStatusObserver->waitFor(ConnectionStatus::DISCONNECTED)) << "Connecting timed out.";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -255,7 +259,6 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Config> m_config;
|
||||
std::shared_ptr<AuthObserver> m_authObserver;
|
||||
std::shared_ptr<AuthDelegate> m_authDelegate;
|
||||
std::shared_ptr<ConnectionStatusObserver> m_connectionStatusObserver;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -20,10 +20,11 @@ if(BUILD_TESTING)
|
|||
separate_arguments(CTEST_CUSTOM_POST_TEST UNIX_COMMAND "${CTEST_CUSTOM_POST_TEST}")
|
||||
add_custom_target(integration
|
||||
COMMAND ${CTEST_CUSTOM_PRE_TEST}
|
||||
COMMAND AlexaAuthorizationDelegateTest "${Integration_SOURCE_DIR}/AuthDelegate.config"
|
||||
COMMAND AlexaCommunicationsLibraryTest "${Integration_SOURCE_DIR}/AuthDelegate.config"
|
||||
"${Integration_SOURCE_DIR}/inputs"
|
||||
COMMAND AlexaDirectiveSequencerLibraryTest "${Integration_SOURCE_DIR}/AuthDelegate.config"
|
||||
"${Integration_SOURCE_DIR}/inputs"
|
||||
COMMAND AlexaAuthorizationDelegateTest
|
||||
"${Integration_SOURCE_DIR}/AlexaClientSDKConfig.json"
|
||||
COMMAND AlexaCommunicationsLibraryTest
|
||||
"${Integration_SOURCE_DIR}/AlexaClientSDKConfig.json" "${Integration_SOURCE_DIR}/inputs"
|
||||
COMMAND AlexaDirectiveSequencerLibraryTest
|
||||
"${Integration_SOURCE_DIR}/AlexaClientSDKConfig.json" "${Integration_SOURCE_DIR}/inputs"
|
||||
COMMAND ${CTEST_CUSTOM_POST_TEST})
|
||||
endif()
|
||||
|
|
111
README.md
111
README.md
|
@ -1,4 +1,4 @@
|
|||
## Alexa Client SDK v0.2
|
||||
## Alexa Client SDK v0.2.1
|
||||
|
||||
This release of the Alexa Client SDK for C++ provides components for authentication and communications with the Alexa Voice Service (AVS), specifically AuthDelegate, Alexa Communications Library (ACL), Alexa Directive Sequencer Library (ADSL), Activity Focus Manager Library (AFML), and associated APIs.
|
||||
|
||||
|
@ -58,7 +58,7 @@ Focus management is not specific to Capability Agents or Directive Handlers, and
|
|||
## Minimum Requirements and Dependencies
|
||||
|
||||
* C++ 11 or later
|
||||
* [GNU Compiler Collection (GCC) 4.8x](https://gcc.gnu.org/) or later **OR** [Clang 3.3](http://clang.llvm.org/get_started.html) or later
|
||||
* [GNU Compiler Collection (GCC) 4.8.5](https://gcc.gnu.org/) or later **OR** [Clang 3.3](http://clang.llvm.org/get_started.html) or later
|
||||
* [CMake 3.0](https://cmake.org/download/) or later
|
||||
* [libcurl 7.50.2](https://curl.haxx.se/download.html) or later
|
||||
* [nghttp2 1.0](https://github.com/nghttp2/nghttp2) or later
|
||||
|
@ -66,7 +66,7 @@ Focus management is not specific to Capability Agents or Directive Handlers, and
|
|||
|
||||
## Obtain LWA Credentials
|
||||
|
||||
To access AVS, your product needs to obtain Login with Amazon (LWA) credentials to establish a connection and make requests on behald of the user. Instructions are available for **Remote Authorization** and **Local Authorization**.
|
||||
To access AVS, your product needs to obtain Login with Amazon (LWA) credentials to establish a connection and make requests on behalf of the user. Instructions are available for **Remote Authorization** and **Local Authorization**.
|
||||
|
||||
**Remote Authorization**
|
||||
|
||||
|
@ -79,19 +79,24 @@ To access AVS, your product needs to obtain Login with Amazon (LWA) credentials
|
|||
|
||||
### Test Credentials
|
||||
|
||||
For initial testing, instructions are available to quickly obtain a `clientId`, `clientSecret`, and `refreshToken`. This method should not be used as a replacement for **Remote Authorization** or **Local Authorization**. For additional information, see **Appendix A**.
|
||||
For initial testing, instructions are available to quickly obtain a `clientId`, `clientSecret`, and `refreshToken`. This method should not be used as a replacement for **Remote Authorization** or **Local Authorization**. For additional information, see [**Appendix A**](#appendix-a-obtain-test-credentials).
|
||||
|
||||
## Setup Your AuthDelegate
|
||||
### Create the AlexaClientSDKConfig.json file
|
||||
|
||||
The role of AuthDelegate is to exchange LWA credentials for an access token, and pass the access token to the ACL when `getToken()` is called.
|
||||
After you've obtained LWA credentials, you need to create `AlexaClientSDKConfig.json`:
|
||||
|
||||
After you've obtained LWA credentials, you need to edit your AuthDelegate configuration (`/AuthDelegate/src/Config.cpp`). Replace all values prefixed with `INSERT_YOUR`:
|
||||
|
||||
```cpp
|
||||
static const std::string DEFAULT_CLIENT_ID = "INSERT_YOUR_CLIENT_ID";
|
||||
static const std::string DEFAULT_REFRESH_TOKEN = "INSERT_YOUR_REFRESH_TOKEN";
|
||||
static const std::string DEFAULT_CLIENT_SECRET = "INSERT_YOUR_CLIENT_SECRET";
|
||||
```
|
||||
1. Navigate to the `Integration` folder.
|
||||
2. Create a file named `AlexaClientSDKConfig.json`.
|
||||
3. Populate the file with the following key/value pairs. Replace all values prefixed with `INSERT_YOUR_`:
|
||||
```
|
||||
{
|
||||
"authDelegate" : {
|
||||
"clientId" : "INSERT_YOUR_CLIENT_ID_HERE",
|
||||
"refreshToken" : "INSERT_YOUR_REFRESH_TOKEN_HERE",
|
||||
"clientSecret" : "INSERT_YOUR_CLIENT_SECRET_HERE"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After adjusting the configuration, follow the instructions for your OS to create an out-of-source build.
|
||||
|
||||
|
@ -156,23 +161,11 @@ Ensure that all tests are passed before you begin integration testing.
|
|||
|
||||
Integration tests ensure that your build can make a request and receive a response from AVS. **All requests to AVS require auth credentials.**
|
||||
|
||||
**Important**: Integration tests for v0.2 **do not** use AuthDelegate. Instead, the tests reference an `AuthDelegate.config` file, which you must create.
|
||||
**Important**: Integration tests for v0.2.1 reference an `AlexaClientSDKConfig.json` file, which you must create.
|
||||
See the `Create the AlexaClientSDKConfig.json file` section (above), if you have not already done this.
|
||||
|
||||
### Create the AuthDelegate.config file
|
||||
|
||||
To create `AuthDelegate.config`:
|
||||
|
||||
1. Navigate to the `Integration` folder.
|
||||
2. Create a file named `AuthDelegate.config`.
|
||||
3. Populate the file with the following key/value pairs.
|
||||
```
|
||||
clientId=YOUR_CLIENT_ID_HERE
|
||||
refreshToken=YOUR_REFRESH_TOKEN_HERE
|
||||
clientSecret=YOUR_CLIENT_SECRET_HERE
|
||||
```
|
||||
|
||||
After you've entered your credentials, save the file and run this command:
|
||||
`make all integration`
|
||||
To exercise the integration tests run this command:
|
||||
`make all integration`
|
||||
|
||||
## Alexa Client SDK API Documentation
|
||||
To build the Alexa Client SDK API documentation, run this command from your build directory: `make doc`.
|
||||
|
@ -236,16 +229,6 @@ In this step we'll make a `POST` request to obtain a **Refresh Token**. Ensure t
|
|||
**Note**: Some of these values may need to be URL encoded. If you don't have a tool, you can use a tool like [http://www.urlencoder.org](http://www.urlencoder.org).
|
||||
3. If successful, a JSON string will print to your console. Locate `refresh_token` and record the value.
|
||||
|
||||
### Step 4: Update AuthDelegate
|
||||
|
||||
Now that you've obtained your **Client ID**, **Client Secret**, and **Refresh Token**, you need to edit your AuthDelegate configuration (`/AuthDelegate/src/Config.cpp`). Replace all values prefixed with `INSERT_YOUR`:
|
||||
|
||||
```cpp
|
||||
static const std::string DEFAULT_CLIENT_ID = "INSERT_YOUR_CLIENT_ID";
|
||||
static const std::string DEFAULT_REFRESH_TOKEN = "INSERT_YOUR_REFRESH_TOKEN";
|
||||
static const std::string DEFAULT_CLIENT_SECRET = "INSERT_YOUR_CLIENT_SECRET";
|
||||
```
|
||||
|
||||
## Appendix B: Memory Profile
|
||||
|
||||
This appendix provides the memory profiles for various modules of the Alexa Client SDK. The numbers were observed running integration tests on a machine running Ubuntu 16.04.2 LTS.
|
||||
|
@ -274,13 +257,51 @@ Unique size set (USS) and proportional size set (PSS) were measured by SMEM whil
|
|||
|
||||

|
||||
|
||||
## Appendix D: Runtime Configuration of path to CA Certificates
|
||||
|
||||
By default libcurl is built with paths to a CA bundle and a directory containing CA certificates. You can direct the Alexa Client SDK to configure libcurl to use an additional path to directories containing CA certificates via the [CURLOPT_CAPATH](https://curl.haxx.se/libcurl/c/CURLOPT_CAPATH.html) setting. This is done by adding a `"libcurlUtils/CURLOPT_CAPATH"` entry to the `AlexaClientSDKConfig.json` file. Here is an example:
|
||||
|
||||
```
|
||||
{
|
||||
"authDelegate" : {
|
||||
"clientId" : "INSERT_YOUR_CLIENT_ID_HERE",
|
||||
"refreshToken" : "INSERT_YOUR_REFRESH_TOKEN_HERE",
|
||||
"clientSecret" : "INSERT_YOUR_CLIENT_SECRET_HERE"
|
||||
},
|
||||
"libcurlUtils" : {
|
||||
"CURLOPT_CAPATH" : "INSERT_YOUR_CA_CERTIFICATE_PATH_HERE"
|
||||
}
|
||||
}
|
||||
```
|
||||
**Note** If you want to assure that libcurl is *only* using CA certificates from this path you may need to reconfigure libcurl with the `--without-ca-bundle` and `--without-ca-path` options and rebuild it to suppress the default paths. See [The libcurl documention](https://curl.haxx.se/docs/sslcerts.html) for more information.
|
||||
|
||||
## Release Notes
|
||||
|
||||
v0.2 of the Alexa Client SDK includes components for authentication, communications, message orchestration, and focus management. These include AuthDelegate, ACL, ADSL, AFML, and associated APIs.
|
||||
v0.2.1 of the Alexa Client SDK includes components for authentication, communications, message orchestration, and focus management. These include AuthDelegate, ACL, ADSL, AFML, and associated APIs.
|
||||
|
||||
Version v0.2.1 released 5/3/2017:
|
||||
* Replaced the configuration file `AuthDelegate.config` with `AlexaClientSDKConfig.json`.
|
||||
* Add the ability to specify a `CURLOPT_CAPATH` value to be used when libcurl is used by ACL and AuthDelegate. See [**Appendix D**](#appendix-d-runtime-configuration-of-path-to-ca-certificates) for details.
|
||||
* Changes to ADSL interfaces:
|
||||
The v0.2 interface for registering directive handlers (`DirectiveSequencer::setDirectiveHandlers()`) was problematic because it canceled the ongoing processing of directives and dropped further directives until it completed. The revised API makes the operation immediate without canceling or dropping any handling. However, it does create the possibility that `DirectiveHandlerInterface` methods `preHandleDirective()` and `handleDirective()` may be called on different handlers for the same directive.
|
||||
* `DirectiveSequencerInterface::setDirectiveHandlers()` was replaced by `addDirectiveHandlers()` and `removeDirectiveHandlers()`.
|
||||
* `DirectiveHandlerInterface::shutdown()` was replaced with `onDeregistered()`.
|
||||
* `DirectiveHandlerInterface::preHandleDirective()` now takes a `std::unique_ptr` instead of a `std::shared_ptr` to `DirectiveHandlerResultInterface`.
|
||||
* `DirectiveHandlerInterface::handleDirective()` now returns a bool indicating if the handler recognizes the `messageId`.
|
||||
* Bug fixes:
|
||||
* ACL and AuthDelegate now require TLSv1.2.
|
||||
* `onDirective()` now sends `ExceptionEncountered` for unhandled directives.
|
||||
* `DirectiveSequencer::shutdown()` no longer sends `ExceptionEncountered()` for queued directives.
|
||||
|
||||
| Version | Release Date | Notes |
|
||||
|---------|--------------|-------|
|
||||
| v0.2 | 3/27/2017 | Added memory profiling for ACL and ADSL; added command to build API documentation. |
|
||||
| v0.2 | 3/9/2017 | Alexa Client SDK v0.2 released: Architecture diagram has been updated to include the ADSL and AMFL. CMake build types and options have been updated. New documentation for libcurl optimization included. |
|
||||
| v0.1 | 2/10/2017 | Alexa Client SDK v0.1 released. |
|
||||
Version v0.2 updated 3/27/2017:
|
||||
* Added memory profiling for ACL and ADSL. See [**Appendix B**](#appendix-b-mempry-profile).
|
||||
* Added command to build API documentation.
|
||||
|
||||
Version v0.2 released 3/9/2017:
|
||||
* Alexa Client SDK v0.2 released.
|
||||
* Architecture diagram has been updated to include the ADSL and AMFL.
|
||||
* CMake build types and options have been updated.
|
||||
* New documentation for libcurl optimization included.
|
||||
|
||||
Version v0.1 released 2/10/2017:
|
||||
* Alexa Client SDK v0.1 released.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue