/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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> create(); // 'override' commented out to avoid needless warnings generated because MOCK_METHOD* does not use it. void setObserver( std::shared_ptr playerObserver) /*override*/; MOCK_METHOD1( setSource, MediaPlayerStatus(std::shared_ptr attachmentReader)); MOCK_METHOD2(setSource, MediaPlayerStatus(std::shared_ptr 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 m_wakePlayPromise; /// Future to notify when @c play is called. std::future m_wakePlayFuture; /// Promise to be fulfilled when @c stop is called. std::promise m_wakeStopPromise; /// Future to notify when @c stop is called. std::future m_wakeStopFuture; /// Promise to be fulfilled when @c pause is called. std::promise m_wakePausePromise; /// Future to notify when @c pause is called. std::future m_wakePauseFuture; /// Promise to be fulfilled when @c resume is called. std::promise m_wakeResumePromise; /// Future to notify when @c resume is called. std::future m_wakeResumeFuture; /// The player observer to be notified of the media player state changes. std::shared_ptr m_playerObserver; }; std::shared_ptr> MockMediaPlayer::create() { auto result = std::make_shared>(); 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 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 playerObserver) { m_playerObserver = playerObserver; } MediaPlayerStatus MockMediaPlayer::mockPlay() { std::unique_lock lock(m_mutex); if (!m_play) { m_playThread = std::thread(&MockMediaPlayer::waitForPlay, this, DEFAULT_TIME); } else { m_wakePlayPromise = std::promise(); 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 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 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 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 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 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 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 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 m_audioPlayer; /// Player to send the audio to. std::shared_ptr m_mockMediaPlayer; /// @c ContextManager to provide state and update state. std::shared_ptr m_mockContextManager; /// @c FocusManager to request focus to the DIALOG channel. std::shared_ptr m_mockFocusManager; /// A directive handler result to send the result to. std::unique_ptr m_mockDirectiveHandlerResult; /// A message sender used to send events to AVS. std::shared_ptr m_mockMessageSender; /// An exception sender used to send exception encountered events to AVS. std::shared_ptr m_mockExceptionSender; /// Attachment manager used to create a reader. std::shared_ptr m_attachmentManager; /// Map for expected messages testing std::map 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 m_wakeSetStatePromise; /// Future to notify when @c setState is called. std::future 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 m_wakeAcquireChannelPromise; /// Future to notify when @c acquireChannel is called. std::future m_wakeAcquireChannelFuture; /** * This is invoked in response to a @c releaseChannel call. * * @return @c true */ std::future wakeOnReleaseChannel(); /// Future to notify when @c releaseChannel is called. std::future 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 m_wakeSendMessagePromise; /// Future to notify when @c sendMessage is called. std::future 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 request, std::map* 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>(); m_mockFocusManager = std::make_shared>(); m_mockMessageSender = std::make_shared>(); m_mockExceptionSender = std::make_shared>(); m_attachmentManager = std::make_shared(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 AudioPlayerTest::wakeOnReleaseChannel() { std::promise releaseChannelSuccess; std::future 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(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST); std::shared_ptr 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( NAMESPACE_AUDIO_PLAYER, NAME_CLEARQUEUE, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST); std::shared_ptr 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 request, std::map* 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 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(NAMESPACE_AUDIO_PLAYER, NAME_STOP, MESSAGE_ID_TEST, PLAY_REQUEST_ID_TEST); std::shared_ptr 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(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, MESSAGE_ID_TEST_2); std::shared_ptr 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 request) { if (!m_mockMediaPlayer->m_stop) { std::lock_guard lock(messageMutex); verifyMessage(request, &m_expectedMessages); messageSentTrigger.notify_one(); } })); sendPlayDirective(); m_audioPlayer->onPlaybackError(ErrorType::MEDIA_ERROR_UNKNOWN, "TEST_ERROR"); std::unique_lock 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 request) { if (!m_mockMediaPlayer->m_stop) { std::lock_guard lock(messageMutex); verifyMessage(request, &m_expectedMessages); messageSentTrigger.notify_one(); } })); sendPlayDirective(); m_audioPlayer->onPlaybackPaused(); std::unique_lock 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 request) { if (!m_mockMediaPlayer->m_stop) { std::lock_guard lock(messageMutex); verifyMessage(request, &m_expectedMessages); messageSentTrigger.notify_one(); } })); sendPlayDirective(); m_audioPlayer->onPlaybackResumed(); std::unique_lock 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 request) { if (!m_mockMediaPlayer->m_stop) { std::lock_guard lock(messageMutex); verifyMessage(request, &m_expectedMessages); messageSentTrigger.notify_one(); } })); sendPlayDirective(); m_audioPlayer->onBufferUnderrun(); std::unique_lock 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 request) { if (!m_mockMediaPlayer->m_stop) { std::lock_guard lock(messageMutex); verifyMessage(request, &m_expectedMessages); messageSentTrigger.notify_one(); } })); sendPlayDirective(); m_audioPlayer->onBufferRefilled(); std::unique_lock 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