avs-device-sdk/capabilities/Alerts/acsdkAlerts/test/Renderer/RendererTest.cpp

459 lines
17 KiB
C++

/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <AVSCommon/SDKInterfaces/MockSpeakerManager.h>
#include <AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h>
#include <AVSCommon/Utils/MediaPlayer/SourceConfig.h>
#include <RegistrationManager/CustomerDataManager.h>
#include <Settings/DeviceSettingsManager.h>
#include <Settings/MockSetting.h>
#include "acsdkAlerts/Renderer/Renderer.h"
namespace alexaClientSDK {
namespace acsdkAlerts {
namespace renderer {
namespace test {
using namespace avsCommon::sdkInterfaces::test;
using namespace avsCommon::utils::mediaPlayer::test;
using namespace settings::types;
using namespace settings::test;
using namespace testing;
using MediaPlayerState = avsCommon::utils::mediaPlayer::MediaPlayerState;
/// Amount of time that the renderer observer should wait for a task to finish.
static const std::chrono::milliseconds TEST_TIMEOUT{100};
/// Default media player state to report for all playback events
static const MediaPlayerState DEFAULT_MEDIA_PLAYER_STATE = {std::chrono::milliseconds(0)};
/// Test source Id that exists for the tests
static const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TEST_SOURCE_ID_GOOD = 1234;
/// Test source Id that does not exist for the tests
static const avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId TEST_SOURCE_ID_BAD = 5678;
/// Test URLs for the renderer
static const std::string TEST_URL1 = "fake.url.one";
static const std::string TEST_URL2 = "fake.url.two";
/// Loop pause for the renderer.
static const std::chrono::milliseconds TEST_LOOP_PAUSE{100};
/// Loop count for the renderer.
static const int TEST_LOOP_COUNT = 2;
/// Loop background pause for the renderer.
static const auto TEST_BACKGROUND_LOOP_PAUSE = std::chrono::seconds(1);
/// Amount of time that the renderer observer should wait for a task to finish.
static const auto TEST_BACKGROUND_TIMEOUT = std::chrono::seconds(5);
static const std::string ALARM_NAME = "ALARM";
class MockRendererObserver : public RendererObserverInterface {
public:
bool waitFor(RendererObserverInterface::State newState) {
std::unique_lock<std::mutex> lock(m_mutex);
return m_conditionVariable.wait_for(lock, TEST_TIMEOUT, [this, newState] { return m_state == newState; });
}
bool waitFor(RendererObserverInterface::State newState, std::chrono::milliseconds maxWait) {
std::unique_lock<std::mutex> lock(m_mutex);
return m_conditionVariable.wait_for(lock, maxWait, [this, newState] { return m_state == newState; });
}
void onRendererStateChange(RendererObserverInterface::State newState, const std::string& reason) {
std::lock_guard<std::mutex> lock(m_mutex);
m_state = newState;
m_conditionVariable.notify_all();
}
private:
std::mutex m_mutex;
std::condition_variable m_conditionVariable;
RendererObserverInterface::State m_state;
};
class TestMediaPlayer : public MockMediaPlayer {
public:
TestMediaPlayer() {
m_sourceIdRetVal = TEST_SOURCE_ID_GOOD;
m_playRetVal = true;
m_stopRetVal = true;
}
static std::shared_ptr<testing::NiceMock<TestMediaPlayer>> create() {
return std::make_shared<testing::NiceMock<TestMediaPlayer>>();
}
bool play(SourceId id) override {
return m_playRetVal;
}
bool stop(SourceId id) override {
return m_stopRetVal;
}
SourceId setSource(
const std::string& url,
std::chrono::milliseconds offset = std::chrono::milliseconds::zero(),
const avsCommon::utils::mediaPlayer::SourceConfig& config = avsCommon::utils::mediaPlayer::emptySourceConfig(),
bool repeat = false,
const avsCommon::utils::mediaPlayer::PlaybackContext& playbackContext =
avsCommon::utils::mediaPlayer::PlaybackContext()) override {
std::lock_guard<std::mutex> lock(m_mutex);
m_sourceConfig = config;
m_sourceChanged.notify_one();
return m_sourceIdRetVal;
}
SourceId setSource(
std::shared_ptr<std::istream> stream,
bool repeat,
const avsCommon::utils::mediaPlayer::SourceConfig& config,
avsCommon::utils::MediaType format) override {
std::lock_guard<std::mutex> lock(m_mutex);
m_sourceConfig = config;
m_sourceChanged.notify_one();
return m_sourceIdRetVal;
}
SourceId setSource(
std::shared_ptr<avsCommon::avs::attachment::AttachmentReader> attachmentReader,
const avsCommon::utils::AudioFormat* audioFormat,
const avsCommon::utils::mediaPlayer::SourceConfig& config) override {
return m_sourceIdRetVal;
}
void setSourceRetVal(SourceId sourceRetVal) {
m_sourceIdRetVal = sourceRetVal;
}
void setPlayRetVal(bool playRetVal) {
m_playRetVal = playRetVal;
}
void setStopRetVal(bool stopRetVal) {
m_stopRetVal = stopRetVal;
}
/*
* Wait for sourceConfig value to be set.
*/
std::pair<bool, avsCommon::utils::mediaPlayer::SourceConfig> waitForSourceConfig(
std::chrono::milliseconds timeout) {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_sourceChanged.wait_for(lock, timeout) != std::cv_status::timeout) {
return std::make_pair(true, m_sourceConfig);
} else {
return std::make_pair(false, avsCommon::utils::mediaPlayer::emptySourceConfig());
}
}
private:
SourceId m_sourceIdRetVal;
bool m_playRetVal;
bool m_stopRetVal;
/// A lock to guard against source changes.
std::mutex m_mutex;
/// A condition variable to wait for source changes.
std::condition_variable m_sourceChanged;
/// The latest source config.
avsCommon::utils::mediaPlayer::SourceConfig m_sourceConfig;
};
class RendererTest : public ::testing::Test {
public:
RendererTest();
~RendererTest();
void SetUpTest();
void TearDown() override;
protected:
std::shared_ptr<MockRendererObserver> m_observer;
std::shared_ptr<TestMediaPlayer> m_mediaPlayer;
std::shared_ptr<Renderer> m_renderer;
static std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType> audioFactoryFunc() {
return std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>(
std::unique_ptr<std::istream>(new std::stringstream()), avsCommon::utils::MediaType::MPEG);
}
};
RendererTest::RendererTest() :
m_observer{std::make_shared<MockRendererObserver>()},
m_mediaPlayer{TestMediaPlayer::create()},
m_renderer{Renderer::create(m_mediaPlayer, nullptr)} {
}
RendererTest::~RendererTest() {
m_mediaPlayer.reset();
}
void RendererTest::SetUpTest() {
std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> audioFactory =
RendererTest::audioFactoryFunc;
std::vector<std::string> urls = {TEST_URL1, TEST_URL2};
m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_LOOP_PAUSE);
}
void RendererTest::TearDown() {
m_mediaPlayer->setSourceRetVal(TEST_SOURCE_ID_GOOD);
m_mediaPlayer->setPlayRetVal(true);
m_mediaPlayer->setStopRetVal(true);
}
/**
* Test if the Renderer class creates an object appropriately and fails when it must
*/
TEST_F(RendererTest, test_create) {
/// m_renderer was created using create() in the constructor. Check if not null
ASSERT_NE(m_renderer, nullptr);
/// confirm we return a nullptr if a nullptr was passed in
ASSERT_EQ(Renderer::create(nullptr, nullptr), nullptr);
}
/**
* Test if the Renderer starts
*/
TEST_F(RendererTest, test_start) {
SetUpTest();
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::UNSET));
m_mediaPlayer->shutdown();
}
/**
* Test if the Renderer stops
*/
TEST_F(RendererTest, test_stop) {
SetUpTest();
m_renderer->stop();
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::ERROR));
}
/**
* Test if the Renderer stops then restarts successfully
*/
TEST_F(RendererTest, test_restart) {
std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> audioFactory =
RendererTest::audioFactoryFunc;
std::vector<std::string> urls = {TEST_URL1, TEST_URL2};
m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_LOOP_PAUSE);
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED));
m_renderer->stop();
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::ERROR));
m_renderer->onPlaybackStopped(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STOPPED));
m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_LOOP_PAUSE);
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED));
}
/**
* Test if the Renderer errors out when it cant stop
*/
TEST_F(RendererTest, test_stopError) {
SetUpTest();
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED));
m_mediaPlayer->setStopRetVal(false);
const avsCommon::utils::mediaPlayer::ErrorType& errorType =
avsCommon::utils::mediaPlayer::ErrorType::MEDIA_ERROR_INVALID_REQUEST;
std::string errorMsg = "testError";
m_renderer->stop();
/// if stop fails, we should receive a PlaybackError from mediaplayer
m_renderer->onPlaybackError(TEST_SOURCE_ID_GOOD, errorType, errorMsg, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::ERROR));
}
/**
* Test if the Renderer correctly handles Playback starting
*/
TEST_F(RendererTest, test_onPlaybackStarted) {
SetUpTest();
/// shouldn't start if the source is bad
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_BAD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STARTED));
/// should start if the source is good
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED));
}
/**
* Test if the Renderer correctly handles Playback stopping
*/
TEST_F(RendererTest, test_onPlaybackStopped) {
SetUpTest();
/// shouldn't stop if the source is bad
m_renderer->onPlaybackStopped(TEST_SOURCE_ID_BAD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED));
/// should stop if the source is good
m_renderer->onPlaybackStopped(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STOPPED));
}
/**
* Test if the Renderer gracefully handles errors when Playback finishing
*/
TEST_F(RendererTest, test_onPlaybackFinishedError) {
SetUpTest();
/// shouldn't finish even if the source is good, if the media player is errored out
m_mediaPlayer->setSourceRetVal(avsCommon::utils::mediaPlayer::MediaPlayerInterface::ERROR);
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED));
/// shouldn't finish even if the source is good, if the media player can't play it
m_mediaPlayer->setSourceRetVal(TEST_SOURCE_ID_GOOD);
m_mediaPlayer->setPlayRetVal(false);
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::STOPPED));
}
/**
* Test if the Renderer correctly handles Playback erroring out
*/
TEST_F(RendererTest, test_onPlaybackError) {
const avsCommon::utils::mediaPlayer::ErrorType& errorType =
avsCommon::utils::mediaPlayer::ErrorType::MEDIA_ERROR_INVALID_REQUEST;
std::string errorMsg = "testError";
SetUpTest();
/// shouldn't respond with errors if the source is bad
m_renderer->onPlaybackError(TEST_SOURCE_ID_BAD, errorType, errorMsg, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_FALSE(m_observer->waitFor(RendererObserverInterface::State::ERROR));
/// shouldn't respond with errors if the source is good
m_renderer->onPlaybackError(TEST_SOURCE_ID_GOOD, errorType, errorMsg, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::ERROR));
}
/**
* Test empty URL with non-zero loop pause, simulating playing a default alarm audio on background
*/
TEST_F(RendererTest, testTimer_emptyURLNonZeroLoopPause) {
std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> audioFactory =
RendererTest::audioFactoryFunc;
std::vector<std::string> urls;
// pass empty URLS with 10s pause and no loop count
// this simulates playing a default alarm audio on background
// it is expected to renderer to play the alert sound continuously at loop pause intervals
m_renderer->start(m_observer, audioFactory, true, urls, TEST_LOOP_COUNT, TEST_BACKGROUND_LOOP_PAUSE);
// mediaplayer starts playing the alarm audio, in this case audio is of 0 length
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
// record the time audio starts playing
auto now = std::chrono::high_resolution_clock::now();
// expect the renderer state to change to 'STARTED'
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED, TEST_BACKGROUND_TIMEOUT));
// mediaplayer finishes playing the alarm audio
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
// mediaplayer starts playing the alarm audio, in this case audio is of 0 length
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
// mediaplayer finishes playing the alarm audio
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::STARTED, TEST_BACKGROUND_TIMEOUT));
// expect the renderer state to change to 'STOPPED' after TEST_BACKGROUND_LOOP_PAUSE
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::COMPLETED, TEST_BACKGROUND_TIMEOUT));
// get the elapsed time
auto elapsed = std::chrono::high_resolution_clock::now() - now;
// check the elapsed time is ~TEST_BACKGROUND_LOOP_PAUSE
ASSERT_TRUE((elapsed >= TEST_BACKGROUND_LOOP_PAUSE) && (elapsed < TEST_BACKGROUND_TIMEOUT));
}
/**
* Test alarmVolumeRampRendering.
*/
TEST_F(RendererTest, test_alarmVolumeRampRendering) {
std::function<std::pair<std::unique_ptr<std::istream>, const avsCommon::utils::MediaType>()> audioFactory =
RendererTest::audioFactoryFunc;
std::vector<std::string> urls;
// Pause interval for this test.
const auto loopPause = std::chrono::seconds(1);
// Create a thread that will observe the FadeIn config that is set to the MediaPlayer;
std::thread sourceConfigObserver([this, loopPause]() {
avsCommon::utils::mediaPlayer::SourceConfig config;
bool ok;
// Check that the initial gain is 0.
std::tie(ok, config) = m_mediaPlayer->waitForSourceConfig(6 * loopPause);
ASSERT_TRUE(ok);
ASSERT_EQ(config.fadeInConfig.startGain, 0);
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
// Check that the gain increases at each repetition.
std::tie(ok, config) = m_mediaPlayer->waitForSourceConfig(6 * loopPause);
ASSERT_TRUE(ok);
ASSERT_GT(config.fadeInConfig.startGain, 0);
m_renderer->onPlaybackStarted(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
m_renderer->onPlaybackFinished(TEST_SOURCE_ID_GOOD, DEFAULT_MEDIA_PLAYER_STATE);
});
// pass empty URLS with 1s pause
// this simulates playing a default alarm audio on background
// it is expected to renderer to play the alert sound continuously at loop pause intervals
constexpr int testLoopCount = 2;
m_renderer->start(m_observer, audioFactory, true, urls, testLoopCount, loopPause);
sourceConfigObserver.join();
ASSERT_TRUE(m_observer->waitFor(RendererObserverInterface::State::COMPLETED));
}
} // namespace test
} // namespace renderer
} // namespace acsdkAlerts
} // namespace alexaClientSDK