blob: 3d42fb85692b59c495c200d85ab751a10c62c27b [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 "src/media/audio/audio_core/test/audio_device_test.h"
#include <fuchsia/media/cpp/fidl.h>
#include <lib/gtest/real_loop_fixture.h>
#include <cmath>
#include <cstring>
#include <utility>
#include "gtest/gtest.h"
#include "lib/component/cpp/environment_services_helper.h"
#include "src/lib/fxl/logging.h"
#include "src/media/audio/audio_core/test/audio_tests_shared.h"
namespace media::audio::test {
//
// AudioDeviceTest static variables
//
std::shared_ptr<const ::component::Services>
AudioDeviceTest::environment_services_;
fuchsia::virtualaudio::ControlSyncPtr AudioDeviceTest::control_sync_;
uint16_t AudioDeviceTest::initial_input_device_count_ = kInvalidDeviceCount;
uint16_t AudioDeviceTest::initial_output_device_count_ = kInvalidDeviceCount;
uint64_t AudioDeviceTest::initial_input_default_ = ZX_KOID_INVALID;
uint64_t AudioDeviceTest::initial_output_default_ = ZX_KOID_INVALID;
float AudioDeviceTest::initial_input_gain_db_ = NAN;
float AudioDeviceTest::initial_output_gain_db_ = NAN;
uint32_t AudioDeviceTest::initial_input_gain_flags_ = 0;
uint32_t AudioDeviceTest::initial_output_gain_flags_ = 0;
//
// AudioDeviceTest implementation
//
// static
void AudioDeviceTest::SetEnvironmentServices(
std::shared_ptr<const ::component::Services> environment_services) {
environment_services_ = environment_services;
}
// static
void AudioDeviceTest::SetControl(
fuchsia::virtualaudio::ControlSyncPtr control_sync) {
AudioDeviceTest::control_sync_ = std::move(control_sync);
}
// static
void AudioDeviceTest::ResetVirtualDevices() {
DisableVirtualDevices();
zx_status_t status = control_sync_->Enable();
ASSERT_EQ(status, ZX_OK);
}
// static
void AudioDeviceTest::DisableVirtualDevices() {
zx_status_t status = control_sync_->Disable();
ASSERT_EQ(status, ZX_OK);
uint32_t num_inputs = -1, num_outputs = -1, num_tries = 0;
do {
status = control_sync_->GetNumDevices(&num_inputs, &num_outputs);
ASSERT_EQ(status, ZX_OK);
++num_tries;
} while ((num_inputs != 0 || num_outputs != 0) && num_tries < 100);
ASSERT_EQ(num_inputs, 0u);
ASSERT_EQ(num_outputs, 0u);
}
// static
void AudioDeviceTest::TearDownTestSuite() { DisableVirtualDevices(); }
void AudioDeviceTest::SetUp() {
::gtest::RealLoopFixture::SetUp();
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
environment_services_->ConnectToService(audio_dev_enum_.NewRequest());
audio_dev_enum_.set_error_handler(err_handler);
}
void AudioDeviceTest::TearDown() {
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(audio_dev_enum_.is_bound());
::gtest::RealLoopFixture::TearDown();
}
bool AudioDeviceTest::ExpectCallback() {
received_callback_ = false;
received_device_ = kInvalidDeviceInfo;
received_removed_token_ = kInvalidDeviceToken;
received_default_token_ = received_old_token_ = kInvalidDeviceToken;
received_gain_token_ = kInvalidDeviceToken;
received_gain_info_ = kInvalidGainInfo;
bool timed_out = !RunLoopWithTimeoutOrUntil(
[this]() { return error_occurred_ || received_callback_; },
kDurationResponseExpected, kDurationGranularity);
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(audio_dev_enum_.is_bound());
EXPECT_FALSE(timed_out);
EXPECT_TRUE(received_callback_);
bool return_val = !error_occurred_ && !timed_out;
return return_val;
}
// TODO(mpuryear): Refactor tests to eliminate "wait for nothing bad to happen".
bool AudioDeviceTest::ExpectTimeout() {
received_callback_ = false;
received_device_ = kInvalidDeviceInfo;
received_removed_token_ = kInvalidDeviceToken;
received_default_token_ = received_old_token_ = kInvalidDeviceToken;
received_gain_token_ = kInvalidDeviceToken;
received_gain_info_ = kInvalidGainInfo;
bool timed_out = !RunLoopWithTimeoutOrUntil(
[this]() { return error_occurred_ || received_callback_; },
kDurationTimeoutExpected);
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(audio_dev_enum_.is_bound());
EXPECT_TRUE(timed_out);
EXPECT_FALSE(received_callback_);
if (received_callback_) {
EXPECT_EQ(received_device_.token_id, kInvalidDeviceToken)
<< "Received Add event";
EXPECT_EQ(received_removed_token_, kInvalidDeviceToken)
<< "Received Remove event";
EXPECT_EQ(received_default_token_, kInvalidDeviceToken)
<< "Received Default event";
EXPECT_EQ(received_old_token_, kInvalidDeviceToken)
<< "Received Default event";
EXPECT_EQ(received_gain_token_, kInvalidDeviceToken)
<< "Received Gain event";
}
bool return_val = !error_occurred_ && !received_callback_;
return return_val;
}
void AudioDeviceTest::SetOnDeviceAddedEvent() {
audio_dev_enum_.events().OnDeviceAdded =
[this](fuchsia::media::AudioDeviceInfo dev) {
received_callback_ = true;
received_device_ = std::move(dev);
};
}
void AudioDeviceTest::SetOnDeviceRemovedEvent() {
audio_dev_enum_.events().OnDeviceRemoved = [this](uint64_t token_id) {
received_callback_ = true;
received_removed_token_ = token_id;
};
}
void AudioDeviceTest::SetOnDeviceGainChangedEvent() {
audio_dev_enum_.events().OnDeviceGainChanged =
[this](uint64_t dev_token, fuchsia::media::AudioGainInfo dev_gain_info) {
received_callback_ = true;
received_gain_token_ = dev_token;
received_gain_info_ = dev_gain_info;
};
}
void AudioDeviceTest::SetOnDefaultDeviceChangedEvent() {
audio_dev_enum_.events().OnDefaultDeviceChanged =
[this](uint64_t old_default_token, uint64_t new_default_token) {
received_callback_ = true;
received_default_token_ = new_default_token;
received_old_token_ = old_default_token;
};
}
uint32_t AudioDeviceTest::GainFlagsFromBools(bool can_mute, bool cur_mute,
bool can_agc, bool cur_agc) {
return ((can_mute && cur_mute) ? fuchsia::media::AudioGainInfoFlag_Mute
: 0u) |
(can_agc ? fuchsia::media::AudioGainInfoFlag_AgcSupported : 0u) |
((can_agc && cur_agc) ? fuchsia::media::AudioGainInfoFlag_AgcEnabled
: 0u);
}
uint32_t AudioDeviceTest::SetFlagsFromBools(bool set_gain, bool set_mute,
bool set_agc) {
return (set_gain ? fuchsia::media::SetAudioGainFlag_GainValid : 0u) |
(set_mute ? fuchsia::media::SetAudioGainFlag_MuteValid : 0u) |
(set_agc ? fuchsia::media::SetAudioGainFlag_AgcValid : 0u);
}
void AudioDeviceTest::RetrieveDefaultDevInfoUsingGetDevices(bool get_input) {
audio_dev_enum_->GetDevices(
[this,
get_input](const std::vector<fuchsia::media::AudioDeviceInfo>& devices) {
received_callback_ = true;
for (auto& dev : devices) {
if (dev.is_default && (dev.is_input == get_input)) {
received_device_ = dev;
}
}
});
EXPECT_TRUE(ExpectCallback());
}
bool AudioDeviceTest::RetrieveGainInfoUsingGetDevices(uint64_t token) {
audio_dev_enum_->GetDevices(
[this,
token](const std::vector<fuchsia::media::AudioDeviceInfo>& devices) {
received_callback_ = true;
for (auto& dev : devices) {
if (dev.token_id == token) {
received_gain_info_ = dev.gain_info;
}
}
});
return ExpectCallback();
}
void AudioDeviceTest::RetrieveGainInfoUsingGetDeviceGain(uint64_t token,
bool valid_token) {
audio_dev_enum_->GetDeviceGain(
token,
[this](uint64_t dev_token, fuchsia::media::AudioGainInfo dev_gain_info) {
received_callback_ = true;
received_gain_token_ = dev_token;
received_gain_info_ = dev_gain_info;
});
EXPECT_TRUE(ExpectCallback());
EXPECT_EQ(received_gain_token_, (valid_token ? token : ZX_KOID_INVALID));
}
void AudioDeviceTest::RetrieveTokenUsingGetDefault(bool is_input) {
auto get_default_handler = [this](uint64_t device_token) {
received_callback_ = true;
received_default_token_ = device_token;
};
if (is_input) {
audio_dev_enum_->GetDefaultInputDevice(get_default_handler);
} else {
audio_dev_enum_->GetDefaultOutputDevice(get_default_handler);
}
EXPECT_TRUE(ExpectCallback());
}
void AudioDeviceTest::RetrievePreExistingDevices() {
if (AudioDeviceTest::initial_input_device_count_ != kInvalidDeviceCount &&
AudioDeviceTest::initial_output_device_count_ != kInvalidDeviceCount) {
return;
}
// Wait until all completion (not disconnect) callbacks drain out, then go on.
while (!error_occurred_) {
if (ExpectTimeout()) {
break;
}
}
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(audio_dev_enum_.is_bound());
audio_dev_enum_->GetDevices(
[this](const std::vector<fuchsia::media::AudioDeviceInfo>& devices) {
received_callback_ = true;
AudioDeviceTest::initial_input_device_count_ = 0;
AudioDeviceTest::initial_output_device_count_ = 0;
for (auto& dev : devices) {
if (dev.is_input) {
++AudioDeviceTest::initial_input_device_count_;
if (dev.is_default) {
AudioDeviceTest::initial_input_default_ = dev.token_id;
AudioDeviceTest::initial_input_gain_db_ = dev.gain_info.gain_db;
AudioDeviceTest::initial_input_gain_flags_ = dev.gain_info.flags;
}
} else {
++AudioDeviceTest::initial_output_device_count_;
if (dev.is_default) {
AudioDeviceTest::initial_output_default_ = dev.token_id;
AudioDeviceTest::initial_output_gain_db_ = dev.gain_info.gain_db;
AudioDeviceTest::initial_output_gain_flags_ = dev.gain_info.flags;
}
}
}
});
EXPECT_TRUE(ExpectCallback());
}
bool AudioDeviceTest::HasPreExistingDevices() {
RetrievePreExistingDevices();
EXPECT_NE(AudioDeviceTest::initial_input_device_count_, kInvalidDeviceCount);
EXPECT_NE(AudioDeviceTest::initial_output_device_count_, kInvalidDeviceCount);
return ((AudioDeviceTest::initial_input_device_count_ +
AudioDeviceTest::initial_output_device_count_) > 0);
}
//
// AudioDeviceTest test cases
//
// Basic validation: we don't disconnect and callback is delivered.
// Later tests use RetrievePreExistingDevices which further validates
// GetDevices().
TEST_F(AudioDeviceTest, ReceivesGetDevicesCallback) {
audio_dev_enum_->GetDevices(
[this](const std::vector<fuchsia::media::AudioDeviceInfo>& devices) {
received_callback_ = true;
});
EXPECT_TRUE(ExpectCallback());
}
TEST_F(AudioDeviceTest, GetDevicesHandlesLackOfDevices) {
if (HasPreExistingDevices()) {
FXL_DLOG(INFO) << "Test case requires an environment with no audio devices";
return;
}
uint16_t num_devs = kInvalidDeviceCount;
audio_dev_enum_->GetDevices(
[this,
&num_devs](const std::vector<fuchsia::media::AudioDeviceInfo>& devices) {
received_callback_ = true;
num_devs = devices.size();
});
EXPECT_TRUE(ExpectCallback());
EXPECT_EQ(num_devs, 0u);
}
TEST_F(AudioDeviceTest, GetDefaultInputDeviceHandlesLackOfDevices) {
if (HasPreExistingDevices()) {
FXL_DLOG(INFO) << "Test case requires an environment with no audio devices";
return;
}
RetrieveTokenUsingGetDefault(true);
EXPECT_EQ(received_default_token_, ZX_KOID_INVALID);
}
TEST_F(AudioDeviceTest, GetDefaultOutputDeviceHandlesLackOfDevices) {
if (HasPreExistingDevices()) {
FXL_DLOG(INFO) << "Test case requires an environment with no audio devices";
return;
}
RetrieveTokenUsingGetDefault(false);
EXPECT_EQ(received_default_token_, ZX_KOID_INVALID);
}
// Given invalid token to GetDeviceGain, callback should be received with
// ZX_KOID_INVALID device; FIDL interface should not disconnect.
TEST_F(AudioDeviceTest, GetDeviceGainHandlesNullToken) {
RetrieveGainInfoUsingGetDeviceGain(ZX_KOID_INVALID);
}
// Given invalid token to GetDeviceGain, callback should be received with
// ZX_KOID_INVALID device; FIDL interface should not disconnect.
TEST_F(AudioDeviceTest, GetDeviceGainHandlesBadToken) {
RetrieveGainInfoUsingGetDeviceGain(kInvalidDeviceToken, false);
}
// Given null token to GetDeviceGain, FIDL interface should not disconnect.
TEST_F(AudioDeviceTest, SetDeviceGainHandlesNullToken) {
audio_dev_enum_->SetDeviceGain(ZX_KOID_INVALID,
{.gain_db = 0.0f, .flags = 0u},
fuchsia::media::SetAudioGainFlag_GainValid);
EXPECT_TRUE(ExpectTimeout());
}
// Given invalid token to SetDeviceGain, FIDL interface should not disconnect.
TEST_F(AudioDeviceTest, SetDeviceGainHandlesBadToken) {
audio_dev_enum_->SetDeviceGain(kInvalidDeviceToken,
{.gain_db = 0.0f, .flags = 0u},
fuchsia::media::SetAudioGainFlag_GainValid);
EXPECT_TRUE(ExpectTimeout());
}
// Given invalid token to GetDeviceGain, callback should be received with
// ZX_KOID_INVALID device; FIDL interface should not disconnect.
TEST_F(AudioDeviceTest, OnDeviceGainChangedIgnoresSetDeviceGainNullToken) {
SetOnDeviceGainChangedEvent();
audio_dev_enum_->SetDeviceGain(ZX_KOID_INVALID,
{.gain_db = 0.0f, .flags = 0u},
fuchsia::media::SetAudioGainFlag_GainValid);
EXPECT_TRUE(ExpectTimeout());
}
TEST_F(AudioDeviceTest, OnDeviceGainChangedIgnoresSetDeviceGainBadToken) {
SetOnDeviceGainChangedEvent();
audio_dev_enum_->SetDeviceGain(kInvalidDeviceToken,
{.gain_db = 0.0f, .flags = 0u},
fuchsia::media::SetAudioGainFlag_GainValid);
EXPECT_TRUE(ExpectTimeout());
}
} // namespace media::audio::test