blob: 650b0fc3aa78a6ae060796532a7fff049c439179 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/output_stream.h"
#include <utility>
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "media/audio/audio_io.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "mojo/edk/system/core.h"
#include "mojo/public/cpp/bindings/associated_binding.h"
#include "services/audio/test/mock_log.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::AtMost;
using testing::DeleteArg;
using testing::Mock;
using testing::NiceMock;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;
using testing::_;
namespace audio {
class MockStream : public media::AudioOutputStream {
public:
MockStream() {}
MOCK_METHOD0(Open, bool());
MOCK_METHOD1(Start, void(AudioSourceCallback* callback));
MOCK_METHOD0(Stop, void());
MOCK_METHOD1(SetVolume, void(double volume));
MOCK_METHOD1(GetVolume, void(double* volume));
MOCK_METHOD0(Close, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockStream);
};
class MockClient : public media::mojom::AudioOutputStreamClient {
public:
MockClient() : binding_(this) {}
// Should only be called once.
media::mojom::AudioOutputStreamClientPtr MakePtr() {
media::mojom::AudioOutputStreamClientPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
binding_.set_connection_error_handler(base::BindOnce(
&MockClient::BindingConnectionError, base::Unretained(this)));
return ptr;
}
void CloseBinding() { binding_.Close(); }
MOCK_METHOD0(OnError, void());
MOCK_METHOD0(BindingConnectionError, void());
private:
mojo::Binding<media::mojom::AudioOutputStreamClient> binding_;
DISALLOW_COPY_AND_ASSIGN(MockClient);
};
class MockObserver : public media::mojom::AudioOutputStreamObserver {
public:
MockObserver() : binding_(this) {}
// Should only be called once.
media::mojom::AudioOutputStreamObserverAssociatedPtr MakePtr() {
media::mojom::AudioOutputStreamObserverAssociatedPtr ptr;
binding_.Bind(mojo::MakeRequestAssociatedWithDedicatedPipe(&ptr));
binding_.set_connection_error_handler(base::BindOnce(
&MockObserver::BindingConnectionError, base::Unretained(this)));
return ptr;
}
void CloseBinding() { binding_.Close(); }
MOCK_METHOD0(DidStartPlaying, void());
MOCK_METHOD0(DidStopPlaying, void());
MOCK_METHOD1(DidChangeAudibleState, void(bool));
MOCK_METHOD0(BindingConnectionError, void());
private:
mojo::AssociatedBinding<media::mojom::AudioOutputStreamObserver> binding_;
DISALLOW_COPY_AND_ASSIGN(MockObserver);
};
// Aliases for use with MockCreatedCallback::Created().
namespace {
const bool successfully_ = true;
const bool unsuccessfully_ = false;
} // namespace
class MockCreatedCallback {
public:
MockCreatedCallback() {}
MOCK_METHOD1(Created, void(bool /*valid*/));
void OnCreated(media::mojom::AudioDataPipePtr ptr) { Created(!!ptr); }
OutputStream::CreatedCallback Get() {
return base::BindOnce(&MockCreatedCallback::OnCreated,
base::Unretained(this));
}
private:
DISALLOW_COPY_AND_ASSIGN(MockCreatedCallback);
};
// Instantiates various classes that we're going to want in most test cases.
class TestEnvironment {
public:
TestEnvironment()
: audio_manager_(std::make_unique<media::TestAudioThread>(false)) {
mojo::edk::SetDefaultProcessErrorCallback(bad_message_callback_.Get());
}
~TestEnvironment() { audio_manager_.Shutdown(); }
using MockDeleteCallback = base::MockCallback<OutputStream::DeleteCallback>;
using MockBadMessageCallback =
base::MockCallback<base::RepeatingCallback<void(const std::string&)>>;
std::unique_ptr<OutputStream> CreateStream(
media::mojom::AudioOutputStreamRequest request) {
return std::make_unique<OutputStream>(
created_callback_.Get(), delete_callback_.Get(), std::move(request),
client_.MakePtr(), observer_.MakePtr(), log_.MakePtr(), &audio_manager_,
"", media::AudioParameters::UnavailableDeviceParams());
}
media::MockAudioManager& audio_manager() { return audio_manager_; }
MockClient& client() { return client_; }
MockObserver& observer() { return observer_; }
MockLog& log() { return log_; }
MockCreatedCallback& created_callback() { return created_callback_; }
MockDeleteCallback& delete_callback() { return delete_callback_; }
MockBadMessageCallback& bad_message_callback() {
return bad_message_callback_;
}
private:
base::test::ScopedTaskEnvironment tasks_;
media::MockAudioManager audio_manager_;
StrictMock<MockClient> client_;
StrictMock<MockObserver> observer_;
NiceMock<MockLog> log_;
StrictMock<MockCreatedCallback> created_callback_;
StrictMock<MockDeleteCallback> delete_callback_;
StrictMock<MockBadMessageCallback> bad_message_callback_;
DISALLOW_COPY_AND_ASSIGN(TestEnvironment);
};
TEST(OutputStreamTest, ConstructDestruct) {
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
EXPECT_CALL(env.log(), OnCreated(_, _));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(env.log(), OnClosed());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
}
TEST(OutputStreamTest, ConstructStreamAndDestructObserver_DestructsStream) {
TestEnvironment env;
MockStream mock_stream;
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(env.created_callback(), Created(successfully_));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.client(), BindingConnectionError());
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
env.observer().CloseBinding();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&env.delete_callback());
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.client());
}
TEST(OutputStreamTest, ConstructStreamAndDestructClient_DestructsStream) {
TestEnvironment env;
MockStream mock_stream;
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(env.created_callback(), Created(successfully_));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
env.client().CloseBinding();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&env.delete_callback());
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.observer());
}
TEST(OutputStreamTest, ConstructStreamAndReleaseStreamPtr_DestructsStream) {
TestEnvironment env;
MockStream mock_stream;
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(env.created_callback(), Created(successfully_));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
stream_ptr.reset();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&env.delete_callback());
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.client());
Mock::VerifyAndClear(&env.observer());
}
TEST(OutputStreamTest, Play_Plays) {
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Start(NotNull()));
EXPECT_CALL(env.log(), OnStarted());
EXPECT_CALL(env.observer(), DidStartPlaying());
// May or may not get an audibility notification depending on if power
// monitoring is enabled.
EXPECT_CALL(env.observer(), DidChangeAudibleState(true)).Times(AtMost(1));
stream_ptr->Play();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.observer());
EXPECT_CALL(mock_stream, Stop());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), DidChangeAudibleState(false)).Times(AtMost(1));
EXPECT_CALL(env.observer(), DidStopPlaying()).Times(AtMost(1));
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
}
TEST(OutputStreamTest, PlayAndPause_PlaysAndStops) {
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Start(NotNull()));
EXPECT_CALL(env.observer(), DidStartPlaying());
// May or may not get an audibility notification depending on if power
// monitoring is enabled.
EXPECT_CALL(env.observer(), DidChangeAudibleState(true)).Times(AtMost(1));
stream_ptr->Play();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.observer());
EXPECT_CALL(mock_stream, Stop());
EXPECT_CALL(env.log(), OnStopped());
EXPECT_CALL(env.observer(), DidChangeAudibleState(false)).Times(AtMost(1));
EXPECT_CALL(env.observer(), DidStopPlaying());
stream_ptr->Pause();
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.observer());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
}
TEST(OutputStreamTest, SetVolume_SetsVolume) {
double new_volume = 0.618;
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, SetVolume(new_volume));
EXPECT_CALL(env.log(), OnSetVolume(new_volume));
stream_ptr->SetVolume(new_volume);
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
}
TEST(OutputStreamTest, SetNegativeVolume_BadMessage) {
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
EXPECT_CALL(env.bad_message_callback(), Run(_));
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
stream_ptr->SetVolume(-0.1);
base::RunLoop().RunUntilIdle();
}
TEST(OutputStreamTest, SetVolumeGreaterThanOne_BadMessage) {
TestEnvironment env;
MockStream mock_stream;
EXPECT_CALL(env.created_callback(), Created(successfully_));
env.audio_manager().SetMakeOutputStreamCB(base::BindRepeating(
[](media::AudioOutputStream* stream, const media::AudioParameters& params,
const std::string& device_id) { return stream; },
&mock_stream));
EXPECT_CALL(mock_stream, Open()).WillOnce(Return(true));
EXPECT_CALL(mock_stream, SetVolume(1));
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&mock_stream);
Mock::VerifyAndClear(&env.created_callback());
EXPECT_CALL(mock_stream, Close());
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), BindingConnectionError());
EXPECT_CALL(env.bad_message_callback(), Run(_));
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
stream_ptr->SetVolume(1.1);
base::RunLoop().RunUntilIdle();
}
TEST(OutputStreamTest, ConstructWithStreamCreationFailure_SignalsError) {
TestEnvironment env;
// By default, the MockAudioManager fails to create a stream.
media::mojom::AudioOutputStreamPtr stream_ptr;
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
EXPECT_CALL(env.created_callback(), Created(unsuccessfully_));
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.log(), OnError());
EXPECT_CALL(env.client(), OnError());
EXPECT_CALL(env.client(), BindingConnectionError());
EXPECT_CALL(env.delete_callback(), Run(stream.release()))
.WillOnce(DeleteArg<0>());
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&env.delete_callback());
Mock::VerifyAndClear(&env.client());
Mock::VerifyAndClear(&env.observer());
}
TEST(OutputStreamTest,
ConstructWithStreamCreationFailureAndDestructBeforeErrorFires_NoCrash) {
// The main purpose of this test is to make sure that that delete callback
// call is deferred, and that it is canceled in case of destruction.
TestEnvironment env;
// By default, the MockAudioManager fails to create a stream.
media::mojom::AudioOutputStreamPtr stream_ptr;
{
EXPECT_CALL(env.created_callback(), Created(unsuccessfully_));
std::unique_ptr<OutputStream> stream =
env.CreateStream(mojo::MakeRequest(&stream_ptr));
EXPECT_CALL(env.observer(), BindingConnectionError());
EXPECT_CALL(env.client(), OnError());
EXPECT_CALL(env.client(), BindingConnectionError());
}
base::RunLoop().RunUntilIdle();
Mock::VerifyAndClear(&env.client());
Mock::VerifyAndClear(&env.observer());
}
} // namespace audio