avs-device-sdk/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp

1278 lines
42 KiB
C++
Raw Normal View History

Version 1.1.0 alexa-client-sdk - Changes in this update: - Better GStreamer error reporting. MediaPlayer used to only report `MEDIA_ERROR_UNKNOWN`, now reports more specific errors as defined in `ErrorType.h`. - Codebase has been formatted for easier reading. - `DirectiveRouter::removeDirectiveHandler()` signature changed and now returns a bool indicating if given handler should be successfully removed or not. - Cleanup of raw and shared pointers in the creation of `Transport` objects. - `HTTP2Stream`s now have IDs assigned as they are acquired as opposed to created, making associated logs easier to interpret. - `AlertsCapabilityAgent` has been refactored. - Alert management has been factored out into an `AlertScheduler` class. - Creation of Reminder (implements Alert) class. - Added new capability agent for `PlaybackController` with unit tests. - Added Settings interface with unit tests. - Return type of `getOffsetInMilliseconds()` changed from `int64_t` to `std::chronology::milliseconds`. - Added `AudioPlayer` unit tests. - Added teardown for all Integration tests except Alerts. - Implemented PlaylistParser. - Bug fixes: - AIP getting stuck in `LISTENING` or `THINKING` and refusing user input on network outage. - SampleApp crashing if running for 5 minutes after network disconnect. - Issue where on repeated user barge-ins, `AudioPlayer` would not pause. Specifically, the third attempt to “Play iHeartRadio” would not result in currently-playing music pausing. - Utterances being ignored after particularly long TTS. - GStreamer errors cropping up on SampleApp exit as a result of accessing the pipeline before it’s been setup. - Crashing when playing one URL after another. - Buffer overrun in Alerts Renderer. - [SampleApp crashing when issuing "Alexa skip" command with iHeartRadio.](https://github.com/alexa/avs-device-sdk/issues/153) - [`HTTP2Transport` network thread triggering a join on itself.](https://github.com/alexa/avs-device-sdk/issues/127) - [`HTTP2Stream` request handling truncating exception messages.](https://github.com/alexa/avs-device-sdk/issues/67) - [`AudioPlayer` was attempting an incorrect state transition from `STOPPED` to `PLAYING` through a `playbackResumed`.](https://github.com/alexa/avs-device-sdk/issues/138)
2017-10-02 22:59:05 +00:00
/*
* AudioPlayerTest.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 AudioPlayerTest.cpp
#include <chrono>
#include <future>
#include <memory>
#include <map>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <AVSCommon/Utils/JSON/JSONUtils.h>
#include <AVSCommon/SDKInterfaces/MockExceptionEncounteredSender.h>
#include <AVSCommon/AVS/Attachment/AttachmentManagerInterface.h>
#include <AVSCommon/SDKInterfaces/MockContextManager.h>
#include <AVSCommon/SDKInterfaces/MockFocusManager.h>
#include <AVSCommon/SDKInterfaces/MockMessageSender.h>
#include <AVSCommon/SDKInterfaces/MockDirectiveSequencer.h>
#include <AVSCommon/SDKInterfaces/MockDirectiveHandlerResult.h>
#include <AVSCommon/AVS/Attachment/AttachmentManager.h>
#include "AudioPlayer/AudioPlayer.h"
namespace alexaClientSDK {
namespace capabilityAgents {
namespace audioPlayer {
namespace test {
using namespace avsCommon::utils::json;
using namespace avsCommon::utils;
using namespace avsCommon;
using namespace avsCommon::avs;
using namespace avsCommon::avs::attachment;
using namespace avsCommon::sdkInterfaces;
using namespace avsCommon::sdkInterfaces::test;
using namespace avsCommon::utils::mediaPlayer;
using namespace ::testing;
using namespace rapidjson;
/// Plenty of time for a test to complete.
static std::chrono::milliseconds WAIT_TIMEOUT(1000);
/// Default time parameter.
static std::chrono::milliseconds DEFAULT_TIME(50);
/// The name of the @c FocusManager channel used by the @c AudioPlayer.
static const std::string CHANNEL_NAME("Content");
/// The activity Id used with the @c FocusManager by @c AudioPlayer.
static const std::string FOCUS_MANAGER_ACTIVITY_ID("AudioPlayer.Play");
/// Namespace for AudioPlayer.
static const std::string NAMESPACE_AUDIO_PLAYER("AudioPlayer");
/// Name for AudioPlayer Play directive.
static const std::string NAME_PLAY("Play");
/// Name for AudioPlayer Stop directive.
static const std::string NAME_STOP("Stop");
/// Name for AudioPlayer ClearQueue directive.
static const std::string NAME_CLEARQUEUE("ClearQueue");
/// The @c NamespaceAndName to send to the @c ContextManager.
static const NamespaceAndName NAMESPACE_AND_NAME_PLAYBACK_STATE{NAMESPACE_AUDIO_PLAYER, "PlaybackState"};
/// Message Id for testing.
static const std::string MESSAGE_ID_TEST("MessageId_Test");
/// Another message Id for testing.
static const std::string MESSAGE_ID_TEST_2("MessageId_Test2");
/// PlayRequestId for testing.
static const std::string PLAY_REQUEST_ID_TEST("PlayRequestId_Test");
/// Context ID for testing
static const std::string CONTEXT_ID_TEST("ContextId_Test");
/// Context ID for testing
static const std::string CONTEXT_ID_TEST_2("ContextId_Test2");
/// Token for testing.
static const std::string TOKEN_TEST("Token_Test");
/// Previous token for testing.
static const std::string PREV_TOKEN_TEST("Prev_Token_Test");
/// Format of the audio.
static const std::string FORMAT_TEST("AUDIO_MPEG");
/// URL for testing.
static const std::string URL_TEST("cid:Test");
/// ENQUEUE playBehavior.
static const std::string NAME_ENQUEUE("ENQUEUE");
/// CLEAR_ALL clearBehavior.
static const std::string NAME_CLEAR_ALL("CLEAR_ALL");
/// audioItemId for testing.
static const std::string AUDIO_ITEM_ID("testID");
/// The @c FINISHED state of the @c AudioPlayer.
static const std::string FINISHED_STATE("FINISHED");
/// The @c PLAYING state of the @c AudioPlayer
static const std::string PLAYING_STATE{"PLAYING"};
/// The @c IDLE state of the @c AudioPlayer
static const std::string IDLE_STATE{"IDLE"};
/// The offset in milliseconds returned by the mock media player.
static const long OFFSET_IN_MILLISECONDS_TEST{100};
/// ExpiryTime for testing. Needs to be in ISO 8601 format.
static const std::string EXPIRY_TEST("481516234248151623421088");
/// progressReportDelayInMilliseconds for testing.
static const long PROGRESS_REPORT_DELAY{200};
/// progressReportIntervalInMilliseconds for testing.
static const long PROGRESS_REPORT_INTERVAL{100};
/// A payload for testing.
// clang-format off
static const std::string ENQUEUE_PAYLOAD_TEST =
"{"
"\"playBehavior\":\"" + NAME_ENQUEUE + "\","
"\"audioItem\": {"
"\"audioItemId\":\"" + AUDIO_ITEM_ID + "\","
"\"stream\": {"
"\"url\":\"" + URL_TEST + "\","
"\"streamFormat\":\"" + FORMAT_TEST + "\","
"\"offsetInMilliseconds\":" + std::to_string(OFFSET_IN_MILLISECONDS_TEST) + ","
"\"expiryTime\":\"" + EXPIRY_TEST + "\","
"\"progressReport\": {"
"\"progressReportDelayInMilliseconds\":" + std::to_string(PROGRESS_REPORT_DELAY) + ","
"\"progressReportIntervalInMilliseconds\":" + std::to_string(PROGRESS_REPORT_INTERVAL) +
"},"
"\"token\":\"" + TOKEN_TEST + "\","
"\"expectedPreviousToken\":\"\""
"}"
"}"
"}";
// clang-format on
/// Empty payload for testing.
static const std::string EMPTY_PAYLOAD_TEST = "{}";
/// CLEAR_ALL payload for testing.
// clang-format off
static const std::string CLEAR_ALL_PAYLOAD_TEST =
"{"
"\"clearBehavior\":\"" + NAME_CLEAR_ALL + "\""
"}";
// clang-format on
/// Token JSON key.
static const std::string TOKEN_KEY = "token";
/// Offset JSON key.
static const std::string OFFSET_KEY = "offsetInMilliseconds";
/// Player activity JSON key.
static const std::string ACTIVITY_KEY = "playerActivity";
/// The expected state when the @c AudioPlayer is not handling any directive.
// clang-format off
static const std::string IDLE_STATE_TEST =
"{"
"\"token\":\"\","
"\"offsetInMilliseconds\":" + std::to_string(0) + ","
"\"playerActivity\":\"" + IDLE_STATE + "\""
"}";
// clang-format on
/// Provide State Token for testing.
static const unsigned int PROVIDE_STATE_TOKEN_TEST{1};
/// JSON key for the event section of a message.
static const std::string MESSAGE_EVENT_KEY = "event";
/// JSON key for the header section of a message.
static const std::string MESSAGE_HEADER_KEY = "header";
/// JSON key for the name section of a message.
static const std::string MESSAGE_NAME_KEY = "name";
/// Name of PlaybackStarted event
static const std::string PLAYBACK_STARTED_NAME = "PlaybackStarted";
/// Name of PlaybackNearlyFinished event
static const std::string PLAYBACK_NEARLY_FINISHED_NAME = "PlaybackNearlyFinished";
/// Name of PlaybackFinished event
static const std::string PLAYBACK_FINISHED_NAME = "PlaybackFinished";
/// Name of PlaybackStopped event
static const std::string PLAYBACK_STOPPED_NAME = "PlaybackStopped";
/// Name of PlaybackPaused event
static const std::string PLAYBACK_PAUSED_NAME = "PlaybackPaused";
/// Name of PlaybackFailed event
static const std::string PLAYBACK_FAILED_NAME = "PlaybackFailed";
/// Name of PlaybackResumed event
static const std::string PLAYBACK_RESUMED_NAME = "PlaybackResumed";
/// Name of PlaybackStutterStarted event
static const std::string PLAYBACK_STUTTER_STARTED_NAME = "PlaybackStutterStarted";
/// Name of PlaybackStutterFinished event
static const std::string PLAYBACK_STUTTER_FINISHED_NAME = "PlaybackStutterFinished";
/// Name of ProgressReportDelayElapsed event
static const std::string PROGRESS_REPORT_DELAY_ELAPSED_NAME = "ProgressReportDelayElapsed";
/// Name of ProgressReportIntervalElapsed event
static const std::string PROGRESS_REPORT_INTERVAL_ELAPSED_NAME = "ProgressReportIntervalElapsed";
/// String to identify log entries originating from this file.
static const std::string TAG("AudioPlayerTest");
/**
* 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::avsCommon::utils::logger::LogEntry(TAG, event)
class MockMediaPlayer : public MediaPlayerInterface {
public:
/// Constructor.
MockMediaPlayer();
/// Destructor.
~MockMediaPlayer();
/**
* Creates an instance of the @c MockMediaPlayer.
*
* @return An instance of the @c MockMediaPlayer.
*/
static std::shared_ptr<NiceMock<MockMediaPlayer>> create();
// 'override' commented out to avoid needless warnings generated because MOCK_METHOD* does not use it.
void setObserver(
std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface> playerObserver) /*override*/;
MOCK_METHOD1(
setSource,
MediaPlayerStatus(std::shared_ptr<avsCommon::avs::attachment::AttachmentReader> attachmentReader));
MOCK_METHOD2(setSource, MediaPlayerStatus(std::shared_ptr<std::istream> stream, bool repeat));
#ifdef __clang__
// Remove warnings when compiling with clang.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Woverloaded-virtual"
#endif
MOCK_METHOD1(setSource, MediaPlayerStatus(const std::string& url));
#ifdef __clang__
#pragma clang diagnostic pop
#endif
MOCK_METHOD0(play, MediaPlayerStatus());
MOCK_METHOD0(stop, MediaPlayerStatus());
MOCK_METHOD0(pause, MediaPlayerStatus());
MOCK_METHOD0(resume, MediaPlayerStatus());
MOCK_METHOD0(getOffset, std::chrono::milliseconds());
MOCK_METHOD0(getOffsetInMilliseconds, int64_t());
MOCK_METHOD1(setOffset, MediaPlayerStatus(std::chrono::milliseconds offset));
/**
* This is a mock method which will signal to @c waitForPlay to send the play started notification to the observer.
*
* @return @c SUCCESS.
*/
MediaPlayerStatus mockPlay();
/**
* This is a mock method which will signal to @c waitForStop to send the play finished notification to the observer.
*
* @return @c SUCCESS.
*/
MediaPlayerStatus mockStop();
/**
* This is a mock method which will signal to @c waitForPause to send the play finished notification to the
* observer.
*
* @return @c SUCCESS.
*/
MediaPlayerStatus mockPause();
/**
* This is a mock method which will signal to @c waitForResume to send the play finished notification to the
* observer.
*
* @return @c SUCCESS.
*/
MediaPlayerStatus mockResume();
/**
* Waits for play to be called. It notifies the observer that play has started.
*
* @param duration Time to wait for a play to be called before notifying observer that an error occurred.
* @return @c true if play was called within the timeout duration else @c false.
*/
bool waitForPlay(const std::chrono::milliseconds duration = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for stop to be called. It notifies the observer that play has finished.
*
* @param duration Time to wait for a stop to be called before notifying observer that an error occurred.
* @return @c true if stop was called within the timeout duration else @c false.
*/
bool waitForStop(const std::chrono::milliseconds duration = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for pause to be called. It notifies the observer that play has been paused.
*
* @param duration Time to wait for a pause to be called before notifying observer that an error occurred.
* @return @c true if pause was called within the timeout duration else @c false.
*/
bool waitForPause(const std::chrono::milliseconds duration = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for resume to be called. It notifies the observer that play should resume.
*
* @param duration Time to wait for a resume to be called before notifying observer that an error occurred.
* @return @c true if resume was called within the timeout duration else @c false.
*/
bool waitForResume(const std::chrono::milliseconds duration = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for the promise @c m_wakePlayPromise to be fulfilled and the future to be notified of call to @c play.
*
* @param timeout The duration to wait for the future to be ready.
* @return @c true if @c play was called within the @c timeout else @c false.
*/
bool waitUntilPlaybackStarted(const std::chrono::milliseconds timeout = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for the promise @c m_wakeStopPromise to be fulfilled and the future to be notified of call to @c stop.
*
* @param timeout The duration to wait for the future to be ready.
* @return @c true if @c stop was called within the @c timeout else @c false.
*/
bool waitUntilPlaybackFinished(const std::chrono::milliseconds timeout = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for the promise @c m_wakePausePromise to be fulfilled and the future to be notified of call to @c pause.
*
* @param timeout The duration to wait for the future to be ready.
* @return @c true if @c pause was called within the @c timeout else @c false.
*/
bool waitUntilPlaybackPaused(const std::chrono::milliseconds timeout = std::chrono::milliseconds(DEFAULT_TIME));
/**
* Waits for the promise @c m_wakeResumePromise to be fulfilled and the future to be notified of call to @c resume.
*
* @param timeout The duration to wait for the future to be ready.
* @return @c true if @c resume was called within the @c timeout else @c false.
*/
bool waitUntilPlaybackResumed(const std::chrono::milliseconds timeout = std::chrono::milliseconds(DEFAULT_TIME));
/// Condition variable to wake the @ waitForPlay.
std::condition_variable m_wakeTriggerPlay;
/// Condition variable to wake the @ waitForStop.
std::condition_variable m_wakeTriggerStop;
/// Condition variable to wake the @ waitForPause.
std::condition_variable m_wakeTriggerPause;
/// Condition variable to wake the @ waitForResume.
std::condition_variable m_wakeTriggerResume;
/// mutex to protect @c m_play, @c m_stop and @c m_shutdown.
std::mutex m_mutex;
/// Flag to indicate @c play was called.
bool m_play;
/// Flag to indicate @c stop was called.
bool m_stop;
/// Flag to indicate @c pause was called
bool m_pause;
/// Flag to indicate @c resume was called
bool m_resume;
/// Flag to indicate when MockMediaPlayer is shutting down.
bool m_shutdown;
/// Thread to run @c waitForPlay asynchronously.
std::thread m_playThread;
/// Second thread to run @c waitForPlay asynchronously, to test returning to the PLAYING state
std::thread m_playThread_2;
/// Thread to run @c waitForStop asynchronously.
std::thread m_stopThread;
/// Thread to run @c waitForPause asynchronously.
std::thread m_pauseThread;
/// Thread to run @c waitForResume asynchronously.
std::thread m_resumeThread;
/// Promise to be fulfilled when @c play is called.
std::promise<void> m_wakePlayPromise;
/// Future to notify when @c play is called.
std::future<void> m_wakePlayFuture;
/// Promise to be fulfilled when @c stop is called.
std::promise<void> m_wakeStopPromise;
/// Future to notify when @c stop is called.
std::future<void> m_wakeStopFuture;
/// Promise to be fulfilled when @c pause is called.
std::promise<void> m_wakePausePromise;
/// Future to notify when @c pause is called.
std::future<void> m_wakePauseFuture;
/// Promise to be fulfilled when @c resume is called.
std::promise<void> m_wakeResumePromise;
/// Future to notify when @c resume is called.
std::future<void> m_wakeResumeFuture;
/// The player observer to be notified of the media player state changes.
std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface> m_playerObserver;
};
std::shared_ptr<NiceMock<MockMediaPlayer>> MockMediaPlayer::create() {
auto result = std::make_shared<NiceMock<MockMediaPlayer>>();
ON_CALL(*result.get(), play()).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockPlay));
ON_CALL(*result.get(), stop()).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockStop));
ON_CALL(*result.get(), pause()).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockPause));
ON_CALL(*result.get(), resume()).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockResume));
return result;
}
MockMediaPlayer::MockMediaPlayer() :
m_play{false},
m_stop{false},
m_pause{false},
m_resume{false},
m_shutdown{false},
m_wakePlayPromise{},
m_wakePlayFuture{m_wakePlayPromise.get_future()},
m_wakeStopPromise{},
m_wakeStopFuture{m_wakeStopPromise.get_future()},
m_wakePausePromise{},
m_wakePauseFuture{m_wakePausePromise.get_future()},
m_playerObserver{nullptr} {
}
MockMediaPlayer::~MockMediaPlayer() {
{
std::lock_guard<std::mutex> lock(m_mutex);
m_shutdown = true;
}
m_wakeTriggerPlay.notify_all();
m_wakeTriggerStop.notify_all();
m_wakeTriggerPause.notify_all();
m_wakeTriggerResume.notify_all();
if (m_playThread.joinable()) {
m_playThread.join();
}
if (m_playThread_2.joinable()) {
m_playThread_2.join();
}
if (m_stopThread.joinable()) {
m_stopThread.join();
}
if (m_pauseThread.joinable()) {
m_pauseThread.join();
}
if (m_resumeThread.joinable()) {
m_resumeThread.join();
}
}
void MockMediaPlayer::setObserver(
std::shared_ptr<avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface> playerObserver) {
m_playerObserver = playerObserver;
}
MediaPlayerStatus MockMediaPlayer::mockPlay() {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_play) {
m_playThread = std::thread(&MockMediaPlayer::waitForPlay, this, DEFAULT_TIME);
} else {
m_wakePlayPromise = std::promise<void>();
m_playThread_2 = std::thread(&MockMediaPlayer::waitForPlay, this, DEFAULT_TIME);
}
m_play = true;
m_wakeTriggerPlay.notify_one();
return MediaPlayerStatus::SUCCESS;
}
MediaPlayerStatus MockMediaPlayer::mockStop() {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_stop) {
m_stopThread = std::thread(&MockMediaPlayer::waitForStop, this, DEFAULT_TIME);
m_stop = true;
m_wakeTriggerStop.notify_one();
}
return MediaPlayerStatus::SUCCESS;
}
MediaPlayerStatus MockMediaPlayer::mockPause() {
std::unique_lock<std::mutex> lock(m_mutex);
m_pauseThread = std::thread(&MockMediaPlayer::waitForPause, this, DEFAULT_TIME);
m_pause = true;
m_wakeTriggerPause.notify_one();
return MediaPlayerStatus::SUCCESS;
}
MediaPlayerStatus MockMediaPlayer::mockResume() {
std::unique_lock<std::mutex> lock(m_mutex);
m_resumeThread = std::thread(&MockMediaPlayer::waitForResume, this, DEFAULT_TIME);
m_resume = true;
m_wakeTriggerResume.notify_one();
return MediaPlayerStatus::SUCCESS;
}
bool MockMediaPlayer::waitForPlay(const std::chrono::milliseconds duration) {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_wakeTriggerPlay.wait_for(lock, duration, [this]() { return (m_play || m_shutdown); })) {
if (m_playerObserver) {
m_playerObserver->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "waitForPlay timed out");
}
return false;
}
m_wakePlayPromise.set_value();
if (m_playerObserver) {
m_playerObserver->onPlaybackStarted();
}
return true;
}
bool MockMediaPlayer::waitForStop(const std::chrono::milliseconds duration) {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_wakeTriggerStop.wait_for(lock, duration, [this]() { return (m_stop || m_shutdown); })) {
if (m_playerObserver) {
m_playerObserver->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "waitForStop timed out");
}
return false;
}
m_wakeStopPromise.set_value();
if (m_playerObserver) {
m_playerObserver->onPlaybackFinished();
}
return true;
}
bool MockMediaPlayer::waitForPause(const std::chrono::milliseconds duration) {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_wakeTriggerPause.wait_for(lock, duration, [this]() { return (m_pause || m_shutdown); })) {
if (m_playerObserver) {
m_playerObserver->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "waitForPause timed out");
}
return false;
}
m_wakePausePromise.set_value();
if (m_playerObserver) {
m_playerObserver->onPlaybackPaused();
}
return true;
}
bool MockMediaPlayer::waitForResume(const std::chrono::milliseconds duration) {
std::unique_lock<std::mutex> lock(m_mutex);
if (!m_wakeTriggerResume.wait_for(lock, duration, [this]() { return (m_resume || m_shutdown); })) {
if (m_playerObserver) {
m_playerObserver->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "waitForResume timed out");
}
return false;
}
m_wakeResumePromise.set_value();
if (m_playerObserver) {
m_playerObserver->onPlaybackResumed();
}
return true;
}
bool MockMediaPlayer::waitUntilPlaybackStarted(std::chrono::milliseconds timeout) {
return m_wakePlayFuture.wait_for(timeout) == std::future_status::ready;
}
bool MockMediaPlayer::waitUntilPlaybackFinished(std::chrono::milliseconds timeout) {
return m_wakeStopFuture.wait_for(timeout) == std::future_status::ready;
}
bool MockMediaPlayer::waitUntilPlaybackPaused(std::chrono::milliseconds timeout) {
return m_wakePauseFuture.wait_for(timeout) == std::future_status::ready;
}
bool MockMediaPlayer::waitUntilPlaybackResumed(std::chrono::milliseconds timeout) {
return m_wakeResumeFuture.wait_for(timeout) == std::future_status::ready;
}
class AudioPlayerTest : public ::testing::Test {
public:
AudioPlayerTest();
void SetUp() override;
void TearDown() override;
/// @c AudioPlayer to test
std::shared_ptr<AudioPlayer> m_audioPlayer;
/// Player to send the audio to.
std::shared_ptr<MockMediaPlayer> m_mockMediaPlayer;
/// @c ContextManager to provide state and update state.
std::shared_ptr<MockContextManager> m_mockContextManager;
/// @c FocusManager to request focus to the DIALOG channel.
std::shared_ptr<MockFocusManager> m_mockFocusManager;
/// A directive handler result to send the result to.
std::unique_ptr<MockDirectiveHandlerResult> m_mockDirectiveHandlerResult;
/// A message sender used to send events to AVS.
std::shared_ptr<MockMessageSender> m_mockMessageSender;
/// An exception sender used to send exception encountered events to AVS.
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionSender;
/// Attachment manager used to create a reader.
std::shared_ptr<AttachmentManager> m_attachmentManager;
/// Map for expected messages testing
std::map<std::string, bool> m_expectedMessages;
/**
* This is invoked in response to a @c setState call.
*
* @return @c SUCCESS.
*/
SetStateResult wakeOnSetState();
/// Promise to be fulfilled when @c setState is called.
std::promise<void> m_wakeSetStatePromise;
/// Future to notify when @c setState is called.
std::future<void> m_wakeSetStateFuture;
/**
* This is invoked in response to a @c acquireChannel call.
*
* @return @c true
*/
bool wakeOnAcquireChannel();
/// Promise to be fulfilled when @c acquireChannel is called.
std::promise<void> m_wakeAcquireChannelPromise;
/// Future to notify when @c acquireChannel is called.
std::future<void> m_wakeAcquireChannelFuture;
/**
* This is invoked in response to a @c releaseChannel call.
*
* @return @c true
*/
std::future<bool> wakeOnReleaseChannel();
/// Future to notify when @c releaseChannel is called.
std::future<void> m_wakeReleaseChannelFuture;
/**
* Fulfills the @c m_wakeSendMessagePromise. This is invoked in response to a @c sendMessage call.
*/
void wakeOnSendMessage();
/// Promise to be fulfilled when @c sendMessage is called.
std::promise<void> m_wakeSendMessagePromise;
/// Future to notify when @c sendMessage is called.
std::future<void> m_wakeSendMessageFuture;
/**
* Consolidate code to send Play directive.
*/
void sendPlayDirective();
/**
* Consolidate code to send ClearQueue directive
*/
void sendClearQueueDirective();
/**
* Verify that the message name matches the expected name
*
* @param request The @c MessageRequest to verify
* @param expectedName The expected name to find in the json header
*/
void verifyMessage(
std::shared_ptr<avsCommon::avs::MessageRequest> request,
std::map<std::string, bool>* expectedMessages);
/**
* Verify that the providede state matches the expected state
*
* @param jsonState The state to verify
* @param expectedState The expected state
*/
void verifyState(const std::string& providedState, const std::string& expectedState);
/// Condition variable to wake on a message being sent
std::condition_variable messageSentTrigger;
/// Mutex for messages
std::mutex messageMutex;
};
AudioPlayerTest::AudioPlayerTest() :
m_wakeSetStatePromise{},
m_wakeSetStateFuture{m_wakeSetStatePromise.get_future()},
m_wakeAcquireChannelPromise{},
m_wakeAcquireChannelFuture{m_wakeAcquireChannelPromise.get_future()},
m_wakeSendMessagePromise{},
m_wakeSendMessageFuture{m_wakeSendMessagePromise.get_future()} {
}
void AudioPlayerTest::SetUp() {
m_mockContextManager = std::make_shared<NiceMock<MockContextManager>>();
m_mockFocusManager = std::make_shared<NiceMock<MockFocusManager>>();
m_mockMessageSender = std::make_shared<NiceMock<MockMessageSender>>();
m_mockExceptionSender = std::make_shared<NiceMock<MockExceptionEncounteredSender>>();
m_attachmentManager = std::make_shared<AttachmentManager>(AttachmentManager::AttachmentType::IN_PROCESS);
m_mockMediaPlayer = MockMediaPlayer::create();
m_audioPlayer = AudioPlayer::create(
m_mockMediaPlayer,
m_mockMessageSender,
m_mockFocusManager,
m_mockContextManager,
m_attachmentManager,
m_mockExceptionSender);
m_mockDirectiveHandlerResult.reset(new MockDirectiveHandlerResult);
ASSERT_TRUE(m_audioPlayer);
}
void AudioPlayerTest::TearDown() {
m_audioPlayer->shutdown();
}
SetStateResult AudioPlayerTest::wakeOnSetState() {
m_wakeSetStatePromise.set_value();
return SetStateResult::SUCCESS;
}
bool AudioPlayerTest::wakeOnAcquireChannel() {
m_wakeAcquireChannelPromise.set_value();
return true;
}
std::future<bool> AudioPlayerTest::wakeOnReleaseChannel() {
std::promise<bool> releaseChannelSuccess;
std::future<bool> returnValue = releaseChannelSuccess.get_future();
releaseChannelSuccess.set_value(true);
return returnValue;
}
void AudioPlayerTest::wakeOnSendMessage() {
m_wakeSendMessagePromise.set_value();
}
void AudioPlayerTest::sendPlayDirective() {
auto avsMessageHeader =
std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST);
std::shared_ptr<AVSDirective> playDirective =
AVSDirective::create("", avsMessageHeader, ENQUEUE_PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST);
EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, FOCUS_MANAGER_ACTIVITY_ID))
.Times(1)
.WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel));
m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult));
m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
m_audioPlayer->onFocusChanged(FocusState::FOREGROUND);
}
void AudioPlayerTest::sendClearQueueDirective() {
auto avsClearMessageHeader = std::make_shared<AVSMessageHeader>(
NAMESPACE_AUDIO_PLAYER, NAME_CLEARQUEUE, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST);
std::shared_ptr<AVSDirective> clearQueueDirective =
AVSDirective::create("", avsClearMessageHeader, CLEAR_ALL_PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST);
m_audioPlayer->CapabilityAgent::preHandleDirective(clearQueueDirective, std::move(m_mockDirectiveHandlerResult));
m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST);
}
void AudioPlayerTest::verifyMessage(
std::shared_ptr<avsCommon::avs::MessageRequest> request,
std::map<std::string, bool>* expectedMessages) {
rapidjson::Document document;
document.Parse(request->getJsonContent().c_str());
EXPECT_FALSE(document.HasParseError())
<< "rapidjson detected a parsing error at offset:" + std::to_string(document.GetErrorOffset()) +
", error message: " + GetParseError_En(document.GetParseError());
auto event = document.FindMember(MESSAGE_EVENT_KEY);
EXPECT_NE(event, document.MemberEnd());
auto header = event->value.FindMember(MESSAGE_HEADER_KEY);
EXPECT_NE(header, event->value.MemberEnd());
std::string requestName;
jsonUtils::retrieveValue(header->value, MESSAGE_NAME_KEY, &requestName);
if (expectedMessages->find(requestName) != expectedMessages->end()) {
expectedMessages->at(requestName) = true;
}
}
void AudioPlayerTest::verifyState(const std::string& providedState, const std::string& expectedState) {
rapidjson::Document providedStateParsed;
providedStateParsed.Parse(providedState);
rapidjson::Document expectedStateParsed;
expectedStateParsed.Parse(expectedState);
EXPECT_EQ(providedStateParsed, expectedStateParsed);
}
/**
* Test create() with nullptrs
*/
TEST_F(AudioPlayerTest, testCreateWithNullPointers) {
std::shared_ptr<AudioPlayer> testAudioPlayer;
testAudioPlayer = AudioPlayer::create(
m_mockMediaPlayer,
nullptr,
m_mockFocusManager,
m_mockContextManager,
m_attachmentManager,
m_mockExceptionSender);
EXPECT_EQ(testAudioPlayer, nullptr);
testAudioPlayer = AudioPlayer::create(
m_mockMediaPlayer,
m_mockMessageSender,
nullptr,
m_mockContextManager,
m_attachmentManager,
m_mockExceptionSender);
EXPECT_EQ(testAudioPlayer, nullptr);
testAudioPlayer = AudioPlayer::create(
m_mockMediaPlayer,
m_mockMessageSender,
m_mockFocusManager,
nullptr,
m_attachmentManager,
m_mockExceptionSender);
EXPECT_EQ(testAudioPlayer, nullptr);
testAudioPlayer = AudioPlayer::create(
m_mockMediaPlayer,
m_mockMessageSender,
m_mockFocusManager,
m_mockContextManager,
nullptr,
m_mockExceptionSender);
EXPECT_EQ(testAudioPlayer, nullptr);
testAudioPlayer = AudioPlayer::create(
m_mockMediaPlayer, m_mockMessageSender, m_mockFocusManager, m_mockContextManager, m_attachmentManager, nullptr);
EXPECT_EQ(testAudioPlayer, nullptr);
}
/**
* Test transition from Idle to Playing
*/
TEST_F(AudioPlayerTest, testTransitionFromIdleToPlaying) {
EXPECT_CALL(*(m_mockMediaPlayer.get()), play()).Times(AtLeast(1));
sendPlayDirective();
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackStarted());
}
/**
* Test transition from Playing to Stopped with Stop Directive
*/
TEST_F(AudioPlayerTest, testTransitionFromPlayingToStopped) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), stop()).Times(AtLeast(1));
// now send Stop directive
auto avsStopMessageHeader =
std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_STOP, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST);
std::shared_ptr<AVSDirective> stopDirective =
AVSDirective::create("", avsStopMessageHeader, EMPTY_PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST);
m_audioPlayer->CapabilityAgent::preHandleDirective(stopDirective, std::move(m_mockDirectiveHandlerResult));
m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackFinished());
}
/**
* Test transition from Playing to Stopped with ClearQueue.CLEAR_ALL Directive
*/
TEST_F(AudioPlayerTest, testTransitionFromPlayingToStoppedWithClear) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), stop()).Times(AtLeast(1));
sendClearQueueDirective();
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackFinished());
}
/**
* Test transition from Stopped to Playing after issuing second Play directive
*/
TEST_F(AudioPlayerTest, testTransitionFromStoppedToPlaying) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), stop()).Times(AtLeast(1));
sendClearQueueDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), play()).Times(AtLeast(1));
/// send a second Play directive
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2);
std::shared_ptr<AVSDirective> playDirective =
AVSDirective::create("", avsMessageHeader, ENQUEUE_PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackStarted());
m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(m_mockDirectiveHandlerResult));
m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST_2);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackStarted());
m_audioPlayer->onFocusChanged(FocusState::FOREGROUND);
}
/**
* Test transition from Playing to Paused when focus changes to Dialog channel
*/
TEST_F(AudioPlayerTest, testTransitionFromPlayingToPaused) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), pause()).Times(AtLeast(1));
// simulate focus change
m_audioPlayer->onFocusChanged(FocusState::BACKGROUND);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackPaused());
}
/**
* Test transition from Paused to Stopped on ClearQueue.CLEAR_ALL directive
*/
TEST_F(AudioPlayerTest, testTransitionFromPausedToStopped) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), stop()).Times(AtLeast(1));
// simulate focus change in order to pause
m_audioPlayer->onFocusChanged(FocusState::BACKGROUND);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackPaused());
sendClearQueueDirective();
}
/**
* Test transition from Paused to Playing after resume
*/
TEST_F(AudioPlayerTest, testResumeAfterPaused) {
sendPlayDirective();
EXPECT_CALL(*(m_mockMediaPlayer.get()), stop()).Times(AtLeast(1));
// simulate focus change in order to pause
m_audioPlayer->onFocusChanged(FocusState::BACKGROUND);
ASSERT_TRUE(m_mockMediaPlayer->waitUntilPlaybackPaused());
EXPECT_CALL(*(m_mockMediaPlayer.get()), resume()).Times(AtLeast(1));
m_audioPlayer->onFocusChanged(FocusState::FOREGROUND);
}
/**
* Test @c provideState while IDLE
*/
TEST_F(AudioPlayerTest, testCallingProvideStateWhenIdle) {
EXPECT_CALL(
*(m_mockContextManager.get()),
setState(NAMESPACE_AND_NAME_PLAYBACK_STATE, _, StateRefreshPolicy::NEVER, PROVIDE_STATE_TOKEN_TEST))
.Times(1)
.WillOnce(DoAll(
// need to include all four arguments, but only care about jsonState
Invoke([this](
const avs::NamespaceAndName& namespaceAndName,
const std::string& jsonState,
const avs::StateRefreshPolicy& refreshPolicy,
const unsigned int stateRequestToken) { verifyState(jsonState, IDLE_STATE_TEST); }),
InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnSetState)));
m_audioPlayer->provideState(PROVIDE_STATE_TOKEN_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
}
/**
* Test @c onPlaybackError and expect a PlaybackFailed message
*/
TEST_F(AudioPlayerTest, testOnPlaybackError) {
m_expectedMessages.insert({PLAYBACK_STARTED_NAME, false});
m_expectedMessages.insert({PLAYBACK_NEARLY_FINISHED_NAME, false});
m_expectedMessages.insert({PLAYBACK_FAILED_NAME, false});
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([this](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
if (!m_mockMediaPlayer->m_stop) {
std::lock_guard<std::mutex> lock(messageMutex);
verifyMessage(request, &m_expectedMessages);
messageSentTrigger.notify_one();
}
}));
sendPlayDirective();
m_audioPlayer->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR");
std::unique_lock<std::mutex> lock(messageMutex);
bool result;
result = messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] {
for (auto messageStatus : m_expectedMessages) {
if (!messageStatus.second) {
return false;
}
}
return true;
});
ASSERT_TRUE(result);
}
/**
* Test @c onPlaybackPaused and expect a PlaybackPaused message
*/
TEST_F(AudioPlayerTest, testOnPlaybackPaused) {
m_expectedMessages.insert({PLAYBACK_STARTED_NAME, false});
m_expectedMessages.insert({PLAYBACK_NEARLY_FINISHED_NAME, false});
m_expectedMessages.insert({PLAYBACK_PAUSED_NAME, false});
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([this](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
if (!m_mockMediaPlayer->m_stop) {
std::lock_guard<std::mutex> lock(messageMutex);
verifyMessage(request, &m_expectedMessages);
messageSentTrigger.notify_one();
}
}));
sendPlayDirective();
m_audioPlayer->onPlaybackPaused();
std::unique_lock<std::mutex> lock(messageMutex);
bool result;
result = messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] {
for (auto messageStatus : m_expectedMessages) {
if (!messageStatus.second) {
return false;
}
}
return true;
});
ASSERT_TRUE(result);
}
/**
* Test @c onPlaybackResumed and expect a PlaybackResumed message
*/
TEST_F(AudioPlayerTest, testOnPlaybackResumed) {
m_expectedMessages.insert({PLAYBACK_STARTED_NAME, false});
m_expectedMessages.insert({PLAYBACK_NEARLY_FINISHED_NAME, false});
m_expectedMessages.insert({PLAYBACK_RESUMED_NAME, false});
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([this](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
if (!m_mockMediaPlayer->m_stop) {
std::lock_guard<std::mutex> lock(messageMutex);
verifyMessage(request, &m_expectedMessages);
messageSentTrigger.notify_one();
}
}));
sendPlayDirective();
m_audioPlayer->onPlaybackResumed();
std::unique_lock<std::mutex> lock(messageMutex);
bool result;
result = messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] {
for (auto messageStatus : m_expectedMessages) {
if (!messageStatus.second) {
return false;
}
}
return true;
});
ASSERT_TRUE(result);
}
/**
* Test @c onBufferUnderrun and expect a PlaybackStutterStarted message
*/
TEST_F(AudioPlayerTest, testOnBufferUnderrun) {
m_expectedMessages.insert({PLAYBACK_STARTED_NAME, false});
m_expectedMessages.insert({PLAYBACK_NEARLY_FINISHED_NAME, false});
m_expectedMessages.insert({PLAYBACK_STUTTER_STARTED_NAME, false});
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([this](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
if (!m_mockMediaPlayer->m_stop) {
std::lock_guard<std::mutex> lock(messageMutex);
verifyMessage(request, &m_expectedMessages);
messageSentTrigger.notify_one();
}
}));
sendPlayDirective();
m_audioPlayer->onBufferUnderrun();
std::unique_lock<std::mutex> lock(messageMutex);
bool result;
result = messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] {
for (auto messageStatus : m_expectedMessages) {
if (!messageStatus.second) {
return false;
}
}
return true;
});
ASSERT_TRUE(result);
}
/**
* Test @c onBufferRefilled and expect a PlaybackStutterFinished message
*/
TEST_F(AudioPlayerTest, testOnBufferRefilled) {
m_expectedMessages.insert({PLAYBACK_STARTED_NAME, false});
m_expectedMessages.insert({PLAYBACK_NEARLY_FINISHED_NAME, false});
m_expectedMessages.insert({PLAYBACK_STUTTER_FINISHED_NAME, false});
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(Invoke([this](std::shared_ptr<avsCommon::avs::MessageRequest> request) {
if (!m_mockMediaPlayer->m_stop) {
std::lock_guard<std::mutex> lock(messageMutex);
verifyMessage(request, &m_expectedMessages);
messageSentTrigger.notify_one();
}
}));
sendPlayDirective();
m_audioPlayer->onBufferRefilled();
std::unique_lock<std::mutex> lock(messageMutex);
bool result;
result = messageSentTrigger.wait_for(lock, WAIT_TIMEOUT, [this] {
for (auto messageStatus : m_expectedMessages) {
if (!messageStatus.second) {
return false;
}
}
return true;
});
ASSERT_TRUE(result);
}
/**
* Test @c cancelDirective
* Expect the @c handleDirective call to the cancelled directive returns false
*/
TEST_F(AudioPlayerTest, testCancelDirective) {
sendPlayDirective();
m_audioPlayer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST);
ASSERT_FALSE(m_audioPlayer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST));
}
} // namespace test
} // namespace audioPlayer
} // namespace capabilityAgents
} // namespace alexaClientSDK