avs-device-sdk/shared/KWD/acsdkKWDImplementations/test/AbstractKeywordDetectorTest...

381 lines
15 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 <memory>
#include <acsdkKWDInterfaces/KeywordDetectorStateNotifierInterface.h>
#include <acsdkKWDInterfaces/KeywordNotifierInterface.h>
#include <acsdkNotifierInterfaces/internal/MockNotifier.h>
#include <AVSCommon/AVS/AudioInputStream.h>
#include <AVSCommon/SDKInterfaces/KeyWordDetectorStateObserverInterface.h>
#include <AVSCommon/SDKInterfaces/KeyWordObserverInterface.h>
#include <AVSCommon/Utils/AudioFormat.h>
#include "acsdkKWDImplementations/AbstractKeywordDetector.h"
namespace alexaClientSDK {
namespace acsdkKWDImplementations {
namespace test {
/// Reader timeout.
static constexpr std::chrono::milliseconds TIMEOUT{1000};
/// The size of reader buffer is one page long.
static constexpr size_t TEST_BUFFER_SIZE{4096u};
// No Words read from buffer.
static constexpr ssize_t ZERO_WORDS_READ = 0;
// Number of words to read from buffer.
static constexpr ssize_t WORDS_TO_READ = 1;
using namespace ::testing;
/// A test observer that mocks out the KeyWordObserverInterface##onKeyWordDetected() call.
class MockKeyWordObserver : public avsCommon::sdkInterfaces::KeyWordObserverInterface {
public:
MOCK_METHOD5(
onKeyWordDetected,
void(
std::shared_ptr<avsCommon::avs::AudioInputStream> stream,
std::string keyword,
avsCommon::avs::AudioInputStream::Index beginIndex,
avsCommon::avs::AudioInputStream::Index endIndex,
std::shared_ptr<const std::vector<char>> KWDMetadata));
};
/// A test observer that mocks out the KeyWordDetectorStateObserverInterface##onStateChanged() call.
class MockStateObserver : public avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface {
public:
MOCK_METHOD1(
onStateChanged,
void(avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState
keyWordDetectorState));
};
/// A test KeywordNotifier.
class MockKeywordNotifier
: public acsdkNotifierInterfaces::test::MockNotifier<avsCommon::sdkInterfaces::KeyWordObserverInterface> {};
/// A test KeywordDetectorStateNotifier.
class MockKeywordDetectorStateNotifier
: public acsdkNotifierInterfaces::test::MockNotifier<
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface> {};
/**
* A mock Keyword Detector that inherits from KeyWordDetector.
*/
class MockKeyWordDetector : public AbstractKeywordDetector {
public:
/**
* Constructor.
*
* @param keyWordNotifier The object with which to notifiy observers of keyword detections.
* @param KeyWordDetectorStateNotifier The object with which to notify observers of state changes in the engine.
*/
MockKeyWordDetector(
std::shared_ptr<acsdkKWDInterfaces::KeywordNotifierInterface> keywordNotifier,
std::shared_ptr<acsdkKWDInterfaces::KeywordDetectorStateNotifierInterface> keywordDetectorStateNotifier) :
AbstractKeywordDetector(keywordNotifier, keywordDetectorStateNotifier) {
}
/**
* Notifies all KeyWordObservers with dummy values.
*/
void sendKeyWordCallToObservers();
/**
* Notifies all KeyWordDetectorStateObservers.
*
* @param state The state to notify observers of.
*/
void sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState state);
ssize_t protectedReadFromStream(
std::shared_ptr<avsCommon::avs::AudioInputStream::Reader> reader,
std::shared_ptr<avsCommon::avs::AudioInputStream> stream,
void* buf,
size_t nWords,
std::chrono::milliseconds timeout,
bool* errorOccurred);
static bool protectedIsByteswappingRequired(avsCommon::utils::AudioFormat audioFormat);
};
void MockKeyWordDetector::sendKeyWordCallToObservers() {
notifyKeyWordObservers(nullptr, "ALEXA", 0, 0);
}
void MockKeyWordDetector::sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState state) {
notifyKeyWordDetectorStateObservers(state);
}
ssize_t MockKeyWordDetector::protectedReadFromStream(
std::shared_ptr<avsCommon::avs::AudioInputStream::Reader> reader,
std::shared_ptr<avsCommon::avs::AudioInputStream> stream,
void* buf,
size_t nWords,
std::chrono::milliseconds timeout,
bool* errorOccurred) {
return readFromStream(reader, stream, buf, nWords, timeout, errorOccurred);
}
bool MockKeyWordDetector::protectedIsByteswappingRequired(avsCommon::utils::AudioFormat audioFormat) {
return isByteswappingRequired(audioFormat);
}
class AbstractKeyWordDetectorTest : public ::testing::Test {
protected:
std::shared_ptr<MockKeyWordDetector> m_detector;
std::shared_ptr<MockKeyWordObserver> m_keyWordObserver;
std::shared_ptr<MockStateObserver> m_stateObserver;
std::shared_ptr<MockKeywordNotifier> m_keywordNotifier;
std::shared_ptr<MockKeywordDetectorStateNotifier> m_keywordDetectorStateNotifier;
std::shared_ptr<avsCommon::avs::AudioInputStream::Buffer> m_buffer;
std::unique_ptr<avsCommon::avs::AudioInputStream> m_sds;
std::unique_ptr<avsCommon::avs::AudioInputStream::Writer> m_writer;
std::unique_ptr<avsCommon::avs::AudioInputStream::Reader> m_reader;
virtual void SetUp() {
m_keywordNotifier = std::make_shared<MockKeywordNotifier>();
m_keywordDetectorStateNotifier = std::make_shared<MockKeywordDetectorStateNotifier>();
m_detector = std::make_shared<MockKeyWordDetector>(m_keywordNotifier, m_keywordDetectorStateNotifier);
m_keyWordObserver = std::make_shared<MockKeyWordObserver>();
m_stateObserver = std::make_shared<MockStateObserver>();
m_buffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(TEST_BUFFER_SIZE);
m_sds = avsCommon::avs::AudioInputStream::create(m_buffer, 2, 1);
ASSERT_TRUE(m_sds);
m_writer = m_sds->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::BLOCKING);
ASSERT_TRUE(m_writer);
m_reader = m_sds->createReader(avsCommon::avs::AudioInputStream::Reader::Policy::BLOCKING);
ASSERT_TRUE(m_reader);
// Make calls to KWDStateNotifier pass through to observer.
ON_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_))
.WillByDefault(Invoke(
[this](std::function<void(
const std::shared_ptr<avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface>&)>
notifyFn) { notifyFn(m_stateObserver); }));
// Make calls to KWNotifier pass through to observer.
ON_CALL(*m_keywordNotifier, notifyObservers(_))
.WillByDefault(Invoke(
[this](std::function<void(const std::shared_ptr<avsCommon::sdkInterfaces::KeyWordObserverInterface>&)>
notifyFn) { notifyFn(m_keyWordObserver); }));
// Initialize Detector State to Active from Closed.
m_detector->sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::ACTIVE);
}
};
/**
* Tests adding a Keyword Observer to the KWD.
*/
TEST_F(AbstractKeyWordDetectorTest, test_addKeyWordObserver) {
EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordObserver(m_keyWordObserver);
}
/**
* Tests Notifying a Keyword Observer.
*/
TEST_F(AbstractKeyWordDetectorTest, test_notifyKeyWordObserver) {
// add kw observer
EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordObserver(m_keyWordObserver);
EXPECT_CALL(*m_keywordNotifier, notifyObservers(_)).Times(1);
EXPECT_CALL(*m_keyWordObserver, onKeyWordDetected(_, _, _, _, _)).Times(1);
m_detector->sendKeyWordCallToObservers();
}
/**
* Tests removing a Keyword Observer to the KWD.
*/
TEST_F(AbstractKeyWordDetectorTest, test_removeKeyWordObserver) {
EXPECT_CALL(*m_keywordNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordObserver(m_keyWordObserver);
EXPECT_CALL(*m_keywordNotifier, removeObserver(_)).Times(1);
m_detector->removeKeyWordObserver(m_keyWordObserver);
}
/**
* Tests adding a Detector State Observer to the KWD.
*/
TEST_F(AbstractKeyWordDetectorTest, test_addStateObserver) {
EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordDetectorStateObserver(m_stateObserver);
}
/**
* Tests notifying a KeywordDetectorStateObserver.
*/
TEST_F(AbstractKeyWordDetectorTest, test_notifyStateObserver) {
EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordDetectorStateObserver(m_stateObserver);
EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1);
EXPECT_CALL(
*m_stateObserver,
onStateChanged(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED))
.Times(1);
m_detector->sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED);
}
/**
* Tests removing a Detector State Observer to the KWD.
*/
TEST_F(AbstractKeyWordDetectorTest, test_removeStateObserver) {
EXPECT_CALL(*m_keywordDetectorStateNotifier, addObserver(_)).Times(1);
m_detector->addKeyWordDetectorStateObserver(m_stateObserver);
EXPECT_CALL(*m_keywordDetectorStateNotifier, removeObserver(_)).Times(1);
m_detector->removeKeyWordDetectorStateObserver(m_stateObserver);
}
/**
* Tests that Detector State Observers aren't notified if there is no change in state.
*/
TEST_F(AbstractKeyWordDetectorTest, test_observersDontGetNotifiedOfSameStateTwice) {
m_detector->addKeyWordDetectorStateObserver(m_stateObserver);
EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1);
EXPECT_CALL(
*m_stateObserver,
onStateChanged(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED))
.Times(1);
m_detector->sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED);
EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(0);
EXPECT_CALL(*m_stateObserver, onStateChanged(_)).Times(0);
m_detector->sendStateChangeCallObservers(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED);
}
/**
* Tests if byte swapping of the audio stream is required by comparing format endianness to platform endianness.
*/
TEST_F(AbstractKeyWordDetectorTest, test_isByteSwappingRequired) {
int num = 1;
char* firstBytePtr = reinterpret_cast<char*>(&num);
avsCommon::utils::AudioFormat audioFormat;
if (*firstBytePtr == 1) {
// Test Platform is little endian.
audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE;
EXPECT_FALSE(m_detector->protectedIsByteswappingRequired(audioFormat));
audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::BIG;
EXPECT_TRUE(m_detector->protectedIsByteswappingRequired(audioFormat));
} else {
// Test Platform is big endian.
audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::LITTLE;
EXPECT_TRUE(m_detector->protectedIsByteswappingRequired(audioFormat));
audioFormat.endianness = avsCommon::utils::AudioFormat::Endianness::BIG;
EXPECT_FALSE(m_detector->protectedIsByteswappingRequired(audioFormat));
}
}
/**
* Tests that KWD is able to read from stream successfully.
*/
TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamSuccessful) {
// Write random data into the m_sds.
std::vector<int16_t> randomData(50, 0);
m_writer->write(randomData.data(), randomData.size());
// Attempt to read from stream with no errors occuring
bool errorOccurred = false;
EXPECT_EQ(
WORDS_TO_READ,
m_detector->protectedReadFromStream(
std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred));
EXPECT_FALSE(errorOccurred);
}
/**
* Test reading from stream while the stream is closed.
*/
TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamWhileStreamClosed) {
m_reader->close();
// Attempt to read a word from the closed stream.
// Expect that zero words are read and that an error has occured.
// Expect that state observers are notified of detector state change.
bool errorOccurred = false;
EXPECT_CALL(*m_keywordDetectorStateNotifier, notifyObservers(_)).Times(1);
EXPECT_CALL(
*m_stateObserver,
onStateChanged(
avsCommon::sdkInterfaces::KeyWordDetectorStateObserverInterface::KeyWordDetectorState::STREAM_CLOSED))
.Times(1);
EXPECT_EQ(
ZERO_WORDS_READ,
m_detector->protectedReadFromStream(
std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred));
EXPECT_TRUE(errorOccurred);
}
/**
* Test reading from stream when the m_buffer is overrun.
*/
TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamBufferOverrun) {
// Create Reader and Writers for test
auto buffer = std::make_shared<avsCommon::avs::AudioInputStream::Buffer>(TEST_BUFFER_SIZE);
auto sds = avsCommon::avs::AudioInputStream::create(buffer, 2, 1);
ASSERT_TRUE(sds);
auto writer = sds->createWriter(avsCommon::avs::AudioInputStream::Writer::Policy::NONBLOCKABLE);
ASSERT_TRUE(writer);
auto reader = sds->createReader(avsCommon::avs::AudioInputStream::Reader::Policy::NONBLOCKING);
ASSERT_TRUE(reader);
// Write to buffer twice to overrun it.
std::vector<int16_t> randomData(TEST_BUFFER_SIZE, 0);
writer->write(randomData.data(), randomData.size());
writer->write(randomData.data(), randomData.size());
bool errorOccurred = false;
EXPECT_EQ(
avsCommon::avs::AudioInputStream::Reader::Error::OVERRUN,
m_detector->protectedReadFromStream(
std::move(reader), std::move(sds), buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred));
EXPECT_FALSE(errorOccurred);
}
/**
* Test reading from stream times out.
*/
TEST_F(AbstractKeyWordDetectorTest, test_readFromStreamTimedOut) {
bool errorOccurred = false;
EXPECT_EQ(
avsCommon::avs::AudioInputStream::Reader::Error::TIMEDOUT,
m_detector->protectedReadFromStream(
std::move(m_reader), std::move(m_sds), m_buffer->data(), WORDS_TO_READ, TIMEOUT, &errorOccurred));
EXPECT_FALSE(errorOccurred);
}
} // namespace test
} // namespace acsdkKWDImplementations
} // namespace alexaClientSDK