blob: bbf32f1796b0d6ccf334bf71cf29ffa16b58eb65 [file] [log] [blame]
// Copyright 2020 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/activity_dispatcher.h"
#include <lib/fidl/cpp/binding.h>
#include <gtest/gtest.h>
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
namespace media::audio {
using RenderActivity = ActivityDispatcherImpl::RenderActivity;
using CaptureActivity = ActivityDispatcherImpl::CaptureActivity;
using RenderUsageVector = std::vector<fuchsia::media::AudioRenderUsage>;
using CaptureUsageVector = std::vector<fuchsia::media::AudioCaptureUsage>;
namespace {
RenderActivity UsageVectorToActivity(
const std::vector<fuchsia::media::AudioRenderUsage>& usage_vector) {
RenderActivity activity;
for (const auto& usage : usage_vector) {
activity.set(static_cast<int>(usage));
}
return activity;
}
CaptureActivity UsageVectorToActivity(
const std::vector<fuchsia::media::AudioCaptureUsage>& usage_vector) {
CaptureActivity activity;
for (const auto& usage : usage_vector) {
activity.set(static_cast<int>(usage));
}
return activity;
}
} // namespace
typedef ::testing::Types<RenderUsageVector, CaptureUsageVector> UsageVectorTypes;
TYPED_TEST_SUITE(ActivityDispatcherTest, UsageVectorTypes);
template <typename Type>
class ActivityDispatcherTest : public gtest::TestLoopFixture {
protected:
fuchsia::media::ActivityReporterPtr GetClient() {
fuchsia::media::ActivityReporterPtr activity_reporter;
activity_dispatcher_.GetFidlRequestHandler()(activity_reporter.NewRequest());
return activity_reporter;
}
void SetErrorHandler(fuchsia::media::ActivityReporterPtr& reporter,
std::function<void(zx_status_t status)> err) {
reporter.set_error_handler(err);
}
void WatchActivity(fuchsia::media::ActivityReporterPtr& reporter,
std::function<void(const Type& v)> cb) {
WatchActivity<Type>(reporter, cb);
}
template <typename UsageVector>
void WatchActivity(fuchsia::media::ActivityReporterPtr& reporter,
std::function<void(const UsageVector& v)> cb);
template <>
void WatchActivity<RenderUsageVector>(fuchsia::media::ActivityReporterPtr& reporter,
std::function<void(const RenderUsageVector& v)> cb) {
reporter->WatchRenderActivity(cb);
}
template <>
void WatchActivity<CaptureUsageVector>(fuchsia::media::ActivityReporterPtr& reporter,
std::function<void(const CaptureUsageVector& v)> cb) {
reporter->WatchCaptureActivity(cb);
}
// Simulates a new set of usages being active.
void UpdateActivity(const Type& usage_vector) { UpdateActivity<Type>(usage_vector); }
template <typename UsageVector>
void UpdateActivity(const UsageVector& usage_vector);
template <>
void UpdateActivity<RenderUsageVector>(const RenderUsageVector& usage_vector) {
activity_dispatcher_.OnRenderActivityChanged(UsageVectorToActivity(usage_vector));
}
template <>
void UpdateActivity<CaptureUsageVector>(const CaptureUsageVector& usage_vector) {
activity_dispatcher_.OnCaptureActivityChanged(UsageVectorToActivity(usage_vector));
}
// Methods to produce templatized usage vectors.
Type EmptyVector() { return {}; }
Type SingleVector() { return SingleVector<Type>(); }
template <typename UsageVector>
UsageVector SingleVector();
template <>
RenderUsageVector SingleVector<RenderUsageVector>() {
return {fuchsia::media::AudioRenderUsage::BACKGROUND};
}
template <>
CaptureUsageVector SingleVector<CaptureUsageVector>() {
return {fuchsia::media::AudioCaptureUsage::BACKGROUND};
}
Type MultiVector() { return MultiVector<Type>(); }
template <typename UsageVector>
UsageVector MultiVector();
template <>
RenderUsageVector MultiVector<RenderUsageVector>() {
return {fuchsia::media::AudioRenderUsage::BACKGROUND,
fuchsia::media::AudioRenderUsage::SYSTEM_AGENT};
}
template <>
CaptureUsageVector MultiVector<CaptureUsageVector>() {
return {fuchsia::media::AudioCaptureUsage::BACKGROUND,
fuchsia::media::AudioCaptureUsage::SYSTEM_AGENT};
}
private:
ActivityDispatcherImpl activity_dispatcher_;
};
TYPED_TEST(ActivityDispatcherTest, FirstWatchReturnsImmediately) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool called = false;
this->WatchActivity(activity_reporter, [&](auto v) { called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(called);
}
TYPED_TEST(ActivityDispatcherTest, SecondWatchHangsWithoutUpdate) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool first_called = false;
this->WatchActivity(activity_reporter, [&](auto v) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
// Check that the Watch does not return without an update in the activity.
bool second_called = false;
this->WatchActivity(activity_reporter, [&](auto v) { second_called = true; });
this->RunLoopUntilIdle();
EXPECT_FALSE(second_called);
}
TYPED_TEST(ActivityDispatcherTest, SecondWatchReturnsWithUpdate) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool first_called = false;
this->WatchActivity(activity_reporter, [&](auto activity) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
bool second_called = false;
auto actual_usages = this->EmptyVector();
this->WatchActivity(activity_reporter, [&](auto usages) {
second_called = true;
actual_usages = usages;
});
this->RunLoopUntilIdle();
EXPECT_FALSE(second_called);
auto expected_usages = this->SingleVector();
this->UpdateActivity(expected_usages);
// Check that the Watch does return with an update in the activity.
this->RunLoopUntilIdle();
EXPECT_TRUE(second_called);
EXPECT_EQ(expected_usages, actual_usages);
}
TYPED_TEST(ActivityDispatcherTest, WatchReturnsCachedValue) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
auto expected_usages = this->SingleVector();
this->UpdateActivity(expected_usages);
this->RunLoopUntilIdle();
bool called = false;
auto actual_usages = this->EmptyVector();
this->WatchActivity(activity_reporter, [&](auto usages) {
called = true;
actual_usages = usages;
});
this->RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(expected_usages, actual_usages);
}
TYPED_TEST(ActivityDispatcherTest, WatchSkipsTransientValue) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool first_called = false;
this->WatchActivity(activity_reporter, [&](auto activity) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
auto transient_usages = this->SingleVector();
this->UpdateActivity(transient_usages);
this->RunLoopUntilIdle();
auto expected_usages = this->MultiVector();
this->UpdateActivity(expected_usages);
this->RunLoopUntilIdle();
// Check that the Watch returns the latest value and not the transient one.
bool second_called = false;
auto actual_usages = this->EmptyVector();
this->WatchActivity(activity_reporter, [&](auto usages) {
second_called = true;
actual_usages = usages;
});
this->RunLoopUntilIdle();
EXPECT_TRUE(second_called);
EXPECT_EQ(expected_usages, actual_usages);
}
TYPED_TEST(ActivityDispatcherTest, WatchHangsAfterFlap) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool first_called = false;
this->WatchActivity(activity_reporter, [&](auto activity) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
auto transient_usages = this->SingleVector();
this->UpdateActivity(transient_usages);
this->RunLoopUntilIdle();
auto original_usages = this->EmptyVector();
this->UpdateActivity(original_usages);
this->RunLoopUntilIdle();
// Check that the Watch does not return if original activity is restored before next Watch.
bool second_called = false;
this->WatchActivity(activity_reporter, [&](auto usages) { second_called = true; });
this->RunLoopUntilIdle();
EXPECT_FALSE(second_called);
}
TYPED_TEST(ActivityDispatcherTest, WatchHangsOnRedundantChange) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool first_called = false;
this->WatchActivity(activity_reporter, [&](auto activity) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
auto redundant_usages = this->EmptyVector();
this->UpdateActivity(redundant_usages);
this->RunLoopUntilIdle();
// Check that redundant changes are not dispatched.
bool second_called = false;
this->WatchActivity(activity_reporter, [&](auto usages) { second_called = true; });
this->RunLoopUntilIdle();
EXPECT_FALSE(second_called);
}
TYPED_TEST(ActivityDispatcherTest, MultipleClients) {
fuchsia::media::ActivityReporterPtr client = this->GetClient();
// First client gets first activity.
bool first_called = false;
this->WatchActivity(client, [&](auto activity) { first_called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(first_called);
auto expected_usages = this->SingleVector();
this->UpdateActivity(expected_usages);
this->RunLoopUntilIdle();
// First client gets second activity.
bool second_called = false;
auto actual_usages = this->EmptyVector();
this->WatchActivity(client, [&](auto usages) {
second_called = true;
actual_usages = usages;
});
this->RunLoopUntilIdle();
EXPECT_TRUE(second_called);
EXPECT_EQ(expected_usages, actual_usages);
fuchsia::media::ActivityReporterPtr second_client = this->GetClient();
// Second client gets second activty.
bool third_called = false;
auto other_actual_usages = this->EmptyVector();
this->WatchActivity(second_client, [&](auto usages) {
third_called = true;
other_actual_usages = usages;
});
this->RunLoopUntilIdle();
EXPECT_TRUE(third_called);
EXPECT_EQ(expected_usages, other_actual_usages);
// Both clients get the activity update.
bool first_client_called = false;
bool second_client_called = false;
auto first_client_new_actual_usages = this->EmptyVector();
auto second_client_new_actual_usages = this->EmptyVector();
this->WatchActivity(client, [&](auto usages) {
first_client_called = true;
first_client_new_actual_usages = usages;
});
this->WatchActivity(second_client, [&](auto usages) {
second_client_called = true;
second_client_new_actual_usages = usages;
});
this->RunLoopUntilIdle();
auto new_expected_usages = this->MultiVector();
this->UpdateActivity(new_expected_usages);
this->RunLoopUntilIdle();
EXPECT_TRUE(first_client_called);
EXPECT_TRUE(second_client_called);
EXPECT_EQ(new_expected_usages, first_client_new_actual_usages);
EXPECT_EQ(new_expected_usages, second_client_new_actual_usages);
}
TYPED_TEST(ActivityDispatcherTest, TwoHangingGetsTriggerError) {
fuchsia::media::ActivityReporterPtr activity_reporter = this->GetClient();
bool client_error_handler_invoked = false;
zx_status_t client_error_handler_status = ZX_OK;
this->SetErrorHandler(activity_reporter, [&](zx_status_t status) {
client_error_handler_status = status;
client_error_handler_invoked = true;
});
bool called = false;
this->WatchActivity(activity_reporter, [&](auto activity) { called = true; });
this->RunLoopUntilIdle();
EXPECT_TRUE(called);
this->WatchActivity(activity_reporter, [&](auto activity) {});
this->WatchActivity(activity_reporter, [&](auto activity) {});
this->RunLoopUntilIdle();
ASSERT_TRUE(client_error_handler_invoked);
EXPECT_EQ(client_error_handler_status, ZX_ERR_PEER_CLOSED);
}
} // namespace media::audio