blob: 63d310d9ce598b51b92d85d80d2c6ac26037ceff [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/shared/reporter.h"
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <optional>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
#include "src/media/audio/audio_core/shared/audio_admin.h"
namespace media::audio {
namespace {
using ::inspect::testing::BoolIs;
using ::inspect::testing::ChildrenMatch;
using ::inspect::testing::DoubleIs;
using ::inspect::testing::IntIs;
using ::inspect::testing::NameMatches;
using ::inspect::testing::NodeMatches;
using ::inspect::testing::PropertyList;
using ::inspect::testing::StringIs;
using ::inspect::testing::UintIs;
using ::testing::AllOf;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
::testing::Matcher<const ::inspect::Hierarchy&> NodeAlive(const std::string& name) {
return NodeMatches(
AllOf(NameMatches(name), PropertyList(Contains(UintIs("time since death (ns)", 0)))));
}
::testing::Matcher<const ::inspect::Hierarchy&> NodeDead(const std::string& name) {
return NodeMatches(
AllOf(NameMatches(name), Not(PropertyList(Contains(UintIs("time since death (ns)", 0))))));
}
class ReporterTest : public gtest::TestLoopFixture {
public:
ReporterTest()
: under_test_(*component_context_provider_.context(), dispatcher(), dispatcher(), false) {}
inspect::Hierarchy GetHierarchy() {
zx::vmo duplicate = under_test_.inspector().DuplicateVmo();
if (duplicate.get() == ZX_HANDLE_INVALID) {
return inspect::Hierarchy();
}
auto ret = inspect::ReadFromVmo(duplicate);
EXPECT_TRUE(ret.is_ok());
if (ret.is_ok()) {
return ret.take_value();
}
return inspect::Hierarchy();
}
inspect::Hierarchy GetHierarchyLazyValues() {
fpromise::result<inspect::Hierarchy> result;
fpromise::single_threaded_executor exec;
exec.schedule_task(
inspect::ReadFromInspector(under_test_.inspector())
.then([&](fpromise::result<inspect::Hierarchy>& res) { result = std::move(res); }));
exec.run();
EXPECT_TRUE(result.is_ok());
return result.take_value();
}
sys::testing::ComponentContextProvider component_context_provider_;
Reporter under_test_;
};
// Test reporter initial state.
TEST_F(ReporterTest, InitialState) {
auto hierarchy = GetHierarchy();
// Expect metrics with default values in the root node.
EXPECT_THAT(hierarchy,
NodeMatches(AllOf(NameMatches("root"),
PropertyList(IsSupersetOf({
UintIs("count of failures to connect to device", 0),
UintIs("count of failures to obtain device stream channel", 0),
UintIs("count of failures to start a device", 0),
})))));
// Expect empty child nodes for devices and client ports.
EXPECT_THAT(
hierarchy,
ChildrenMatch(UnorderedElementsAre(
AllOf(NodeMatches(AllOf(NameMatches("output devices"), PropertyList(IsEmpty()),
PropertyList(IsEmpty()))),
ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("input devices"), PropertyList(IsEmpty()),
PropertyList(IsEmpty()))),
ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("renderers"), PropertyList(IsEmpty()),
PropertyList(IsEmpty()))),
ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("capturers"), PropertyList(IsEmpty()),
PropertyList(IsEmpty()))),
ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(
AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 1))))),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0))))))))),
AllOf(NodeMatches(NameMatches("thermal state transitions")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("1"),
PropertyList(IsSupersetOf({
BoolIs("active", true),
StringIs("state", "normal"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0))))))))),
AllOf(NodeMatches(AllOf(NameMatches("volume controls"), PropertyList(IsEmpty()),
PropertyList(IsEmpty()))),
ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(
DoubleIs("none gain db", 0.0), DoubleIs("duck gain db", 0.0),
DoubleIs("mute gain db", 0.0))))),
ChildrenMatch(Contains(NodeMatches(
AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", true)))))))))));
}
// Test methods that update metrics in the root node.
TEST_F(ReporterTest, RootMetrics) {
under_test_.FailedToConnectToDevice("", false, 0);
under_test_.FailedToObtainStreamChannel("", false, 0);
under_test_.FailedToObtainStreamChannel("", false, 0);
under_test_.FailedToStartDevice("");
under_test_.FailedToStartDevice("");
under_test_.FailedToStartDevice("");
EXPECT_THAT(GetHierarchy(),
NodeMatches(AllOf(NameMatches("root"),
PropertyList(IsSupersetOf({
UintIs("count of failures to connect to device", 1),
UintIs("count of failures to obtain device stream channel", 2u),
UintIs("count of failures to start a device", 3u),
})))));
}
// Test methods that add and remove devices.
TEST_F(ReporterTest, AddRemoveDevices) {
std::vector<Reporter::Container<Reporter::OutputDevice, Reporter::kObjectsToCache>::Ptr> outputs;
std::vector<Reporter::Container<Reporter::InputDevice, Reporter::kObjectsToCache>::Ptr> inputs;
for (size_t k = 0; k < 5; k++) {
outputs.push_back(under_test_.CreateOutputDevice(fxl::StringPrintf("output_device_%lu", k),
fxl::StringPrintf("output_thread_%lu", k)));
}
for (size_t k = 0; k < 5; k++) {
inputs.push_back(under_test_.CreateInputDevice(fxl::StringPrintf("input_device_%lu", k),
fxl::StringPrintf("input_thread_%lu", k)));
}
EXPECT_THAT(GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(
NodeAlive("output_device_0"), NodeAlive("output_device_1"),
NodeAlive("output_device_2"), NodeAlive("output_device_3"),
NodeAlive("output_device_4")))),
AllOf(NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(
NodeAlive("input_device_0"), NodeAlive("input_device_1"),
NodeAlive("input_device_2"), NodeAlive("input_device_3"),
NodeAlive("input_device_4")))),
})));
outputs[0].Drop();
outputs[1].Drop();
outputs[2].Drop();
outputs[3].Drop();
inputs[0].Drop();
inputs[1].Drop();
inputs[2].Drop();
inputs[3].Drop();
EXPECT_THAT(GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(
NodeDead("output_device_0"), NodeDead("output_device_1"),
NodeDead("output_device_2"), NodeDead("output_device_3"),
NodeAlive("output_device_4")))),
AllOf(NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(
NodeDead("input_device_0"), NodeDead("input_device_1"),
NodeDead("input_device_2"), NodeDead("input_device_3"),
NodeAlive("input_device_4")))),
})));
outputs[4].Drop();
inputs[4].Drop();
// Garbage collect [0].
EXPECT_THAT(GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(
NodeDead("output_device_1"), NodeDead("output_device_2"),
NodeDead("output_device_3"), NodeDead("output_device_4")))),
AllOf(NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(
NodeDead("input_device_1"), NodeDead("input_device_2"),
NodeDead("input_device_3"), NodeDead("input_device_4")))),
})));
}
// Test methods that change device metrics.
TEST_F(ReporterTest, DeviceMetrics) {
auto output_device = under_test_.CreateOutputDevice("output_device", "output_thread");
auto input_device = under_test_.CreateInputDevice("input_device", "input_thread");
// Note: GetHierachy uses ReadFromVmo, which cannot read lazy values.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(UnorderedElementsAre(
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("output_device"),
PropertyList(UnorderedElementsAre(
StringIs("mixer thread name", "output_thread"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", 0),
IntIs("time of latest internal delay change", 0),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", 0),
IntIs("time of latest external delay change", 0),
UintIs("driver transfer (bytes)", 0),
StringIs("name", "unknown"))))),
NodeMatches(AllOf(
NameMatches("format"),
PropertyList(UnorderedElementsAre(StringIs("sample format", "unknown"),
UintIs("channels", 0),
UintIs("frames per second", 0))))),
NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
BoolIs("agc supported", false), BoolIs("agc enabled", false))))),
NodeMatches(AllOf(NameMatches("device underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))),
NodeMatches(AllOf(NameMatches("pipeline underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))))))))),
AllOf(NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("input_device"),
PropertyList(UnorderedElementsAre(
StringIs("mixer thread name", "input_thread"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", 0),
IntIs("time of latest internal delay change", 0),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", 0),
IntIs("time of latest external delay change", 0),
UintIs("driver transfer (bytes)", 0),
StringIs("name", "unknown"))))),
NodeMatches(AllOf(
NameMatches("format"),
PropertyList(UnorderedElementsAre(StringIs("sample format", "unknown"),
UintIs("channels", 0),
UintIs("frames per second", 0))))),
NodeMatches(AllOf(NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
BoolIs("agc supported", false),
BoolIs("agc enabled", false))))))))))),
AllOf(NodeMatches(NameMatches("renderers")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(NameMatches("capturers")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(
AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 1))))),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0))))))))),
AllOf(NodeMatches(NameMatches("thermal state transitions")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("1"),
PropertyList(IsSupersetOf({
BoolIs("active", true),
StringIs("state", "normal"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0))))))))),
AllOf(NodeMatches(NameMatches("volume controls")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(
DoubleIs("none gain db", 0.0), DoubleIs("duck gain db", 0.0),
DoubleIs("mute gain db", 0.0))))),
ChildrenMatch(Contains(NodeMatches(
AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", true)))))))))));
output_device->StartSession(zx::time(0));
output_device->DeviceUnderflow(zx::time(10), zx::time(15));
output_device->DeviceUnderflow(zx::time(25), zx::time(30));
output_device->StopSession(zx::time(50));
output_device->StartSession(zx::time(90));
output_device->DeviceUnderflow(zx::time(91), zx::time(92));
output_device->PipelineUnderflow(zx::time(93), zx::time(96));
output_device->StopSession(zx::time(100));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(Contains(ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(
NameMatches("device underflows"),
PropertyList(UnorderedElementsAre(UintIs("count", 3), UintIs("duration (ns)", 11),
UintIs("session count", 2))))),
NodeMatches(AllOf(
NameMatches("pipeline underflows"),
PropertyList(UnorderedElementsAre(UintIs("count", 1), UintIs("duration (ns)", 3),
UintIs("session count", 2))))),
}))))))));
}
// Test method Device::SetGainInfo.
TEST_F(ReporterTest, DeviceSetGainInfo) {
auto output_device = under_test_.CreateOutputDevice("output_device", "output_thread");
// Expect initial device metric values.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(UnorderedElementsAre(
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("output_device"),
PropertyList(UnorderedElementsAre(
StringIs("mixer thread name", "output_thread"))))),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
BoolIs("agc supported", false), BoolIs("agc enabled", false))))))))))),
AllOf(NodeMatches(NameMatches("input devices")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(NameMatches("renderers")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(NameMatches("capturers")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(
AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 1))))),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0))))))))),
AllOf(NodeMatches(NameMatches("thermal state transitions")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("1"),
PropertyList(IsSupersetOf({
BoolIs("active", true),
StringIs("state", "normal"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0))))))))),
AllOf(NodeMatches(NameMatches("volume controls")), ChildrenMatch(IsEmpty())),
AllOf(NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(
DoubleIs("none gain db", 0.0), DoubleIs("duck gain db", 0.0),
DoubleIs("mute gain db", 0.0))))),
ChildrenMatch(Contains(NodeMatches(
AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", true)))))))))));
fuchsia::media::AudioGainInfo gain_info_a{
.gain_db = -1.0f,
.flags = fuchsia::media::AudioGainInfoFlags::MUTE |
fuchsia::media::AudioGainInfoFlags::AGC_SUPPORTED |
fuchsia::media::AudioGainInfoFlags::AGC_ENABLED};
output_device->SetGainInfo(gain_info_a, {});
// Expect initial device metric values to remain, since no AudioGainValidFlags were set.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
BoolIs("agc supported", false), BoolIs("agc enabled", false))))))))))))));
output_device->SetGainInfo(gain_info_a, fuchsia::media::AudioGainValidFlags::GAIN_VALID);
// Expect a gain change.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -1.0), BoolIs("muted", false),
BoolIs("agc supported", false), BoolIs("agc enabled", false))))))))))))));
output_device->SetGainInfo(gain_info_a, fuchsia::media::AudioGainValidFlags::MUTE_VALID);
// Expect a mute change.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -1.0), BoolIs("muted", true),
BoolIs("agc supported", false), BoolIs("agc enabled", false))))))))))))));
output_device->SetGainInfo(gain_info_a, fuchsia::media::AudioGainValidFlags::AGC_VALID);
// Expect an AGC change.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -1.0), BoolIs("muted", true),
BoolIs("agc supported", true), BoolIs("agc enabled", true))))))))))))));
fuchsia::media::AudioGainInfo gain_info_b{
.gain_db = -2.0f, .flags = fuchsia::media::AudioGainInfoFlags::AGC_SUPPORTED};
output_device->SetGainInfo(gain_info_b, fuchsia::media::AudioGainValidFlags::GAIN_VALID |
fuchsia::media::AudioGainValidFlags::MUTE_VALID |
fuchsia::media::AudioGainValidFlags::AGC_VALID);
// Expect all changes.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(Contains(NodeMatches(AllOf(
NameMatches("device gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -2.0), BoolIs("muted", false),
BoolIs("agc supported", true), BoolIs("agc enabled", false))))))))))))));
}
// Test the method that updates the delays reported by the device.
TEST_F(ReporterTest, DeviceDelays) {
auto output_device = under_test_.CreateOutputDevice("output_device", "output_thread");
auto input_device = under_test_.CreateInputDevice("input_device", "input_thread");
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", 0),
IntIs("time of latest internal delay change", 0),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", 0),
IntIs("time of latest external delay change", 0),
UintIs("driver transfer (bytes)", 0),
StringIs("name", "unknown"))))),
})))))),
AllOf(NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("input_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", 0),
IntIs("time of latest internal delay change", 0),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", 0),
IntIs("time of latest external delay change", 0),
UintIs("driver transfer (bytes)", 0),
StringIs("name", "unknown"))))),
})))))),
})));
// For output device, update internal delay; external delay is unknown (and thus not updated).
const auto kChangeTime1 = 7654321ull;
const auto kIntDelay1 = 4321ull;
// For input device, update internal and external delays.
const auto kChangeTime2 = 1234ull;
const auto kIntDelay2 = 1234567ull;
const auto kExtDelay2 = 654321ull;
output_device->UpdateDelays(zx::time(kChangeTime1), zx::nsec(kIntDelay1), std::nullopt);
input_device->UpdateDelays(zx::time(kChangeTime2), zx::nsec(kIntDelay2), zx::nsec(kExtDelay2));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(IsSupersetOf({
AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(
NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", kIntDelay1),
IntIs("time of latest internal delay change", kChangeTime1),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", 0),
IntIs("time of latest external delay change", 0),
UintIs("driver transfer (bytes)", 0), StringIs("name", "unknown"))))),
})))))),
AllOf(
NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("input_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(
NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", kIntDelay2),
IntIs("time of latest internal delay change", kChangeTime2),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", kExtDelay2),
IntIs("time of latest external delay change", kChangeTime2),
UintIs("driver transfer (bytes)", 0), StringIs("name", "unknown"))))),
})))))),
})));
// For output, update both delays at a time less than previous change. Internal delay should not
// change, but external delay should (its most recent value is the initial value at time 0).
const auto kChangeTime3 = 654321ull; // < kChangeTime1
const auto kIntDelay3 = 54321ull;
const auto kExtDelay3 = 12345ull;
// For input, update internal delay only.
const auto kChangeTime4 = 12345678ull;
const auto kIntDelay4 = 123456ull;
output_device->UpdateDelays(zx::time(kChangeTime3), zx::nsec(kIntDelay3), zx::nsec(kExtDelay3));
input_device->UpdateDelays(zx::time(kChangeTime4), zx::nsec(kIntDelay4), std::nullopt);
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(IsSupersetOf({
AllOf(
NodeMatches(NameMatches("output devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("output_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(
NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", kIntDelay1),
IntIs("time of latest internal delay change", kChangeTime1),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", kExtDelay3),
IntIs("time of latest external delay change", kChangeTime3),
UintIs("driver transfer (bytes)", 0), StringIs("name", "unknown"))))),
})))))),
AllOf(
NodeMatches(NameMatches("input devices")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(NameMatches("input_device")),
ChildrenMatch(IsSupersetOf({
NodeMatches(AllOf(
NameMatches("driver"),
PropertyList(UnorderedElementsAre(
UintIs("initial internal delay (ns)", 0),
UintIs("current internal delay (ns)", kIntDelay4),
IntIs("time of latest internal delay change", kChangeTime4),
UintIs("initial external delay (ns)", 0),
UintIs("current external delay (ns)", kExtDelay2),
IntIs("time of latest external delay change", kChangeTime2),
UintIs("driver transfer (bytes)", 0), StringIs("name", "unknown"))))),
})))))),
})));
}
// Test methods that add and remove client ports.
TEST_F(ReporterTest, AddRemoveClientPorts) {
std::vector<Reporter::Container<Reporter::Renderer, Reporter::kObjectsToCache>::Ptr> renderers;
std::vector<Reporter::Container<Reporter::Capturer, Reporter::kObjectsToCache>::Ptr> capturers;
for (size_t k = 0; k < 5; k++) {
renderers.push_back(under_test_.CreateRenderer());
}
for (size_t k = 0; k < 5; k++) {
capturers.push_back(under_test_.CreateCapturer(fxl::StringPrintf("capture_thread_%lu", k)));
}
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeAlive("1"), NodeAlive("2"), NodeAlive("3"),
NodeAlive("4"), NodeAlive("5")))),
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeAlive("1"), NodeAlive("2"), NodeAlive("3"),
NodeAlive("4"), NodeAlive("5")))),
})));
renderers[0].Drop();
renderers[1].Drop();
renderers[2].Drop();
renderers[3].Drop();
capturers[0].Drop();
capturers[1].Drop();
capturers[2].Drop();
capturers[3].Drop();
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeDead("1"), NodeDead("2"), NodeDead("3"),
NodeDead("4"), NodeAlive("5")))),
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeDead("1"), NodeDead("2"), NodeDead("3"),
NodeDead("4"), NodeAlive("5")))),
})));
renderers[4].Drop();
capturers[4].Drop();
// Garbage collect [0].
EXPECT_THAT(GetHierarchyLazyValues(),
ChildrenMatch(IsSupersetOf({
AllOf(NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeDead("2"), NodeDead("3"),
NodeDead("4"), NodeDead("5")))),
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeDead("2"), NodeDead("3"),
NodeDead("4"), NodeDead("5")))),
})));
}
// Tests methods that change renderer metrics, that aren't tested in other cases.
TEST_F(ReporterTest, RendererMetrics) {
auto renderer = under_test_.CreateRenderer();
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("1"),
PropertyList(UnorderedElementsAre(
UintIs("initial min lead time (ns)", 0),
UintIs("current min lead time (ns)", 0),
IntIs("time of latest min lead time change", 0),
StringIs("usage", "default"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("format"),
PropertyList(UnorderedElementsAre(
StringIs("sample format", "unknown"), UintIs("channels", 0),
UintIs("frames per second", 0))))),
NodeMatches(
AllOf(NameMatches("gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
UintIs("calls to SetGainWithRamp", 0),
DoubleIs("complete stream gain (post-volume) dbfs", 0.0))))),
NodeMatches(AllOf(NameMatches("presentation timestamps"),
PropertyList(UnorderedElementsAre(
DoubleIs("pts continuity threshold (s)", 0.0),
UintIs("pts units denominator", 1),
UintIs("pts units numerator", 1'000'000'000))))),
AllOf(NodeMatches(NameMatches("payload buffers")), ChildrenMatch(IsEmpty())),
NodeMatches(AllOf(NameMatches("packet queue underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))),
NodeMatches(AllOf(NameMatches("continuity underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))),
NodeMatches(AllOf(NameMatches("timestamp underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))))))))))));
renderer->SetUsage(RenderUsage::MEDIA);
renderer->SetFormat(
Format::Create({
.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16,
.channels = 2,
.frames_per_second = 48000,
})
.take_value());
renderer->AddPayloadBuffer(0, 4096);
renderer->AddPayloadBuffer(10, 8192);
renderer->SendPacket(fuchsia::media::StreamPacket{
.payload_buffer_id = 10,
});
renderer->SetGain(-1.0);
renderer->SetMute(true);
renderer->SetGainWithRamp(-1.0, zx::sec(1), fuchsia::media::audio::RampType::SCALE_LINEAR);
renderer->SetGainWithRamp(-1.0, zx::sec(1), fuchsia::media::audio::RampType::SCALE_LINEAR);
renderer->SetCompleteGain(-6.0);
renderer->SetPtsContinuityThreshold(5.0);
renderer->SetPtsUnits(1234567, 3);
renderer->StartSession(zx::time(0));
renderer->PacketQueueUnderflow(zx::time(10), zx::time(15));
renderer->ContinuityUnderflow(zx::time(20), zx::time(30));
renderer->ContinuityUnderflow(zx::time(40), zx::time(50));
renderer->TimestampUnderflow(zx::time(0), zx::time(15));
renderer->TimestampUnderflow(zx::time(30), zx::time(45));
renderer->TimestampUnderflow(zx::time(60), zx::time(75));
renderer->StopSession(zx::time(100));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("1"),
PropertyList(UnorderedElementsAre(
UintIs("initial min lead time (ns)", 0),
UintIs("current min lead time (ns)", 0),
IntIs("time of latest min lead time change", 0),
StringIs("usage", "RenderUsage::MEDIA"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(
NameMatches("format"),
PropertyList(UnorderedElementsAre(
StringIs("sample format", "SIGNED_16"), UintIs("channels", 2),
UintIs("frames per second", 48000))))),
NodeMatches(AllOf(
NameMatches("gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -1.0), BoolIs("muted", true),
UintIs("calls to SetGainWithRamp", 2),
DoubleIs("complete stream gain (post-volume) dbfs", -6.0))))),
NodeMatches(AllOf(NameMatches("presentation timestamps"),
PropertyList(UnorderedElementsAre(
DoubleIs("pts continuity threshold (s)", 5.0),
UintIs("pts units denominator", 3),
UintIs("pts units numerator", 1234567))))),
AllOf(NodeMatches(NameMatches("payload buffers")),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("0"),
PropertyList(UnorderedElementsAre(
UintIs("size", 4096), UintIs("packets", 0))))),
NodeMatches(AllOf(NameMatches("10"),
PropertyList(UnorderedElementsAre(
UintIs("size", 8192), UintIs("packets", 1)))))))),
NodeMatches(AllOf(NameMatches("packet queue underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 1), UintIs("duration (ns)", 5),
UintIs("session count", 1))))),
NodeMatches(AllOf(NameMatches("continuity underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 2), UintIs("duration (ns)", 20),
UintIs("session count", 1))))),
NodeMatches(AllOf(NameMatches("timestamp underflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 3), UintIs("duration (ns)", 45),
UintIs("session count", 1))))))))))))));
}
// Tests methods that change renderer minimum lead time metrics.
TEST_F(ReporterTest, RendererMinLeadTime) {
auto renderer = under_test_.CreateRenderer();
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"), PropertyList(IsSupersetOf({
UintIs("initial min lead time (ns)", 0),
UintIs("current min lead time (ns)", 0),
IntIs("time of latest min lead time change", 0),
}))))))))));
// SetInitialMinLeadTime is optional; UpdateMinLeadTime can be called immediately.
constexpr auto kCurrentMinLeadTime1 = 321ull;
constexpr auto kTimeOfMinLeadTimeChange1 = 123ll;
renderer->UpdateMinLeadTime(zx::nsec(kCurrentMinLeadTime1), zx::time(kTimeOfMinLeadTimeChange1));
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial min lead time (ns)", 0), // Retains value from ctor
UintIs("current min lead time (ns)", kCurrentMinLeadTime1),
IntIs("time of latest min lead time change", kTimeOfMinLeadTimeChange1),
}))))))))));
// We expect the initial and current values to change, and the time-of-update to be reset.
constexpr auto kInitialMinLeadTime2 = 1'000'000ull;
renderer->SetInitialMinLeadTime(zx::nsec(kInitialMinLeadTime2));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"), PropertyList(IsSupersetOf({
UintIs("initial min lead time (ns)", kInitialMinLeadTime2),
UintIs("current min lead time (ns)", kInitialMinLeadTime2),
IntIs("time of latest min lead time change", 0), // Was reset
}))))))))));
// We expect the current value and time-of-update value to change.
constexpr auto kCurrentMinLeadTime3 = 12'345'678ull;
constexpr auto kTimeOfMinLeadTimeChange3 = 987'654'321ll;
renderer->UpdateMinLeadTime(zx::nsec(kCurrentMinLeadTime3), zx::time(kTimeOfMinLeadTimeChange3));
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial min lead time (ns)", kInitialMinLeadTime2),
UintIs("current min lead time (ns)", kCurrentMinLeadTime3),
IntIs("time of latest min lead time change", kTimeOfMinLeadTimeChange3),
}))))))))));
// The time-of-update is before the previous one, so we expect no change.
constexpr auto kCurrentMinLeadTime4 = 1'234'567ull;
constexpr auto kTimeOfMinLeadTimeChange4 = 87'654'321ll; // less than kTimeOfMinLeadTimeChange3
renderer->UpdateMinLeadTime(zx::nsec(kCurrentMinLeadTime4), zx::time(kTimeOfMinLeadTimeChange4));
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("renderers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial min lead time (ns)", kInitialMinLeadTime2),
UintIs("current min lead time (ns)", kCurrentMinLeadTime3),
IntIs("time of latest min lead time change", kTimeOfMinLeadTimeChange3),
}))))))))));
}
// Tests methods that change capturer metrics, that aren't tested in other cases.
TEST_F(ReporterTest, CapturerMetrics) {
auto capturer = under_test_.CreateCapturer("thread");
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(
NameMatches("1"),
PropertyList(UnorderedElementsAre(
UintIs("initial presentation delay (ns)", 0),
UintIs("current presentation delay (ns)", 0),
IntIs("time of latest presentation delay change", 0),
StringIs("usage", "default"), StringIs("mixer thread name", "thread"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("format"),
PropertyList(UnorderedElementsAre(
StringIs("sample format", "unknown"), UintIs("channels", 0),
UintIs("frames per second", 0))))),
NodeMatches(
AllOf(NameMatches("gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", 0.0), BoolIs("muted", false),
UintIs("calls to SetGainWithRamp", 0),
DoubleIs("complete stream gain (post-volume) dbfs", 0.0))))),
AllOf(NodeMatches(NameMatches("payload buffers")), ChildrenMatch(IsEmpty())),
NodeMatches(AllOf(NameMatches("overflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 0), UintIs("duration (ns)", 0),
UintIs("session count", 0))))))))))))));
capturer->SetUsage(CaptureUsage::FOREGROUND);
capturer->SetFormat(
Format::Create({
.sample_format = fuchsia::media::AudioSampleFormat::SIGNED_16,
.channels = 2,
.frames_per_second = 48000,
})
.take_value());
capturer->AddPayloadBuffer(0, 4096);
capturer->AddPayloadBuffer(10, 8192);
capturer->SendPacket(fuchsia::media::StreamPacket{
.payload_buffer_id = 10,
});
capturer->SetGain(-1.0);
capturer->SetMute(true);
capturer->SetGainWithRamp(-1.0, zx::sec(1), fuchsia::media::audio::RampType::SCALE_LINEAR);
capturer->SetGainWithRamp(-1.0, zx::sec(1), fuchsia::media::audio::RampType::SCALE_LINEAR);
capturer->StartSession(zx::time(0));
capturer->Overflow(zx::time(60), zx::time(65));
capturer->StopSession(zx::time(100));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(AllOf(
NodeMatches(AllOf(NameMatches("1"),
PropertyList(UnorderedElementsAre(
UintIs("initial presentation delay (ns)", 0),
UintIs("current presentation delay (ns)", 0),
IntIs("time of latest presentation delay change", 0),
StringIs("usage", "CaptureUsage::FOREGROUND"),
StringIs("mixer thread name", "thread"))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(
NameMatches("format"),
PropertyList(UnorderedElementsAre(StringIs("sample format", "SIGNED_16"),
UintIs("channels", 2),
UintIs("frames per second", 48000))))),
NodeMatches(
AllOf(NameMatches("gain"),
PropertyList(UnorderedElementsAre(
DoubleIs("gain db", -1.0), BoolIs("muted", true),
UintIs("calls to SetGainWithRamp", 2),
DoubleIs("complete stream gain (post-volume) dbfs", 0.0))))),
AllOf(NodeMatches(NameMatches("payload buffers")),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("0"),
PropertyList(UnorderedElementsAre(
UintIs("size", 4096), UintIs("packets", 0))))),
NodeMatches(AllOf(NameMatches("10"),
PropertyList(UnorderedElementsAre(
UintIs("size", 8192), UintIs("packets", 1)))))))),
NodeMatches(AllOf(NameMatches("overflows"),
PropertyList(UnorderedElementsAre(
UintIs("count", 1), UintIs("duration (ns)", 5),
UintIs("session count", 1))))))))))))));
}
// Tests methods that change capturer presentation delay metrics.
TEST_F(ReporterTest, CapturerPresentationDelay) {
auto capturer = under_test_.CreateCapturer("capture_thread");
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"), PropertyList(IsSupersetOf({
UintIs("initial presentation delay (ns)", 0),
UintIs("current presentation delay (ns)", 0),
IntIs("time of latest presentation delay change", 0),
}))))))))));
// SetInitialPresentationDelay is optional; UpdatePresentationDelay can be called immediately.
constexpr auto kCurrentPresDelay1 = 432ull;
constexpr auto kTimeOfPresDelayChange1 = 234ll;
capturer->UpdatePresentationDelay(zx::nsec(kCurrentPresDelay1),
zx::time(kTimeOfPresDelayChange1));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial presentation delay (ns)", 0), // Retains value from ctor.
UintIs("current presentation delay (ns)", kCurrentPresDelay1),
IntIs("time of latest presentation delay change", kTimeOfPresDelayChange1),
}))))))))));
// We expect the initial and current values to change, and the time-of-update to be reset.
constexpr auto kInitialPresentationDelay2 = 2'000'000ull;
capturer->SetInitialPresentationDelay(zx::nsec(kInitialPresentationDelay2));
EXPECT_THAT(GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial presentation delay (ns)", kInitialPresentationDelay2),
UintIs("current presentation delay (ns)", kInitialPresentationDelay2),
IntIs("time of latest presentation delay change", 0), // Was reset
}))))))))));
// We expect the current value and time-of-update value to change.
constexpr auto kCurrentPresDelay3 = 23'456'789ull;
constexpr auto kTimeOfPresDelayChange3 = 876'543'210ll;
capturer->UpdatePresentationDelay(zx::nsec(kCurrentPresDelay3),
zx::time(kTimeOfPresDelayChange3));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial presentation delay (ns)", kInitialPresentationDelay2),
UintIs("current presentation delay (ns)", kCurrentPresDelay3),
IntIs("time of latest presentation delay change", kTimeOfPresDelayChange3),
}))))))))));
// The time-of-update is before the previous one, so we expect no change.
constexpr auto kCurrentPresDelay4 = 2'345'678ull;
constexpr auto kTimeOfPresDelayChange4 = 76'543'210ll; // Less than kTime...Change3
capturer->UpdatePresentationDelay(zx::nsec(kCurrentPresDelay4),
zx::time(kTimeOfPresDelayChange4));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("capturers")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"),
PropertyList(IsSupersetOf({
UintIs("initial presentation delay (ns)", kInitialPresentationDelay2),
UintIs("current presentation delay (ns)", kCurrentPresDelay3),
IntIs("time of latest presentation delay change", kTimeOfPresDelayChange3),
}))))))))));
}
// Tests ThermalStateTracker methods.
TEST_F(ReporterTest, SetThermalStateMetrics) {
under_test_.SetNumThermalStates(3);
under_test_.SetThermalState(0);
// Expect first thermal state metric values.
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 3))))),
ChildrenMatch(UnorderedElementsAre(NodeMatches(
AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0))))))))))));
// Expect second thermal state metric values, with first thermal state metrics stored.
under_test_.SetThermalState(2);
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 3))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("2"), Not(PropertyList(Contains(
UintIs("total duration (ns)", 0))))))))))));
// Expect values to be unchanged, since state 2 has already been triggered.
under_test_.SetThermalState(2);
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("thermal state"),
PropertyList(UnorderedElementsAre(UintIs("num thermal states", 3))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("normal"),
Not(PropertyList(Contains(UintIs("total duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("2"), Not(PropertyList(Contains(
UintIs("total duration (ns)", 0))))))))))));
}
// Test caching of ThermalStates up to limit Reporter::kThermalStatesToCache == 8.
TEST_F(ReporterTest, CacheThermalStateTransitions) {
// Reporter initializes thermal state to 0.
under_test_.SetThermalState(1); // ThermalState 2, first cached
under_test_.SetThermalState(2);
under_test_.SetThermalState(0);
under_test_.SetThermalState(1);
under_test_.SetThermalState(2);
under_test_.SetThermalState(1);
under_test_.SetThermalState(2);
under_test_.SetThermalState(2); // Skip duplicate.
under_test_.SetThermalState(0); // ThermalState 9, final cached
under_test_.SetThermalState(1); // ThermalState 10, alive
// Expect most recent 8 thermal state metric values.
EXPECT_THAT(
GetHierarchyLazyValues(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("thermal state transitions")),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("2"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "1"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("3"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "2"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("4"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "normal"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("5"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "1"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("6"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "2"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("7"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "1"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("8"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "2"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("9"),
PropertyList(IsSupersetOf({
BoolIs("active", false),
StringIs("state", "normal"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0)))))),
NodeMatches(AllOf(NameMatches("10"),
PropertyList(IsSupersetOf({
BoolIs("active", true),
StringIs("state", "1"),
})),
Not(PropertyList(Contains(UintIs("duration (ns)", 0))))))))))));
}
// Test VolumeControl methods.
TEST_F(ReporterTest, VolumeControlMetrics) {
auto volume_control = under_test_.CreateVolumeControl();
// Expect initial volume control metrics.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("volume controls")),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("1"), PropertyList(UnorderedElementsAre(
UintIs("client count", 0),
StringIs("name", "unknown - no clients"))))),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("volume settings")),
ChildrenMatch(UnorderedElementsAre(NodeMatches(AllOf(
NameMatches("1"), PropertyList(UnorderedElementsAre(
BoolIs("active", true), BoolIs("mute", false),
DoubleIs("volume", 0.0)))))))))))))))));
volume_control->SetVolumeMute(0.5, true);
volume_control->AddBinding("RenderUsage::MEDIA");
volume_control->AddBinding("RenderUsage::MEDIA");
// Expect |volume_control| settings to be reflected, with past volume settings cached.
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(NameMatches("volume controls")),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("1"), PropertyList(UnorderedElementsAre(
UintIs("client count", 2),
StringIs("name", "RenderUsage::MEDIA"))))),
ChildrenMatch(Contains(
AllOf(NodeMatches(NameMatches("volume settings")),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("1"),
PropertyList(UnorderedElementsAre(
BoolIs("active", false), BoolIs("mute", false),
DoubleIs("volume", 0.0))))),
NodeMatches(AllOf(NameMatches("2"),
PropertyList(UnorderedElementsAre(
BoolIs("active", true), BoolIs("mute", true),
DoubleIs("volume", 0.5)))))))))))))))));
}
// Test methods that change audio policy metrics.
TEST_F(ReporterTest, AudioPolicyMetrics) {
// Expect behavior gains to be logged, and initial active audio policy to have no active usages.
under_test_.SetAudioPolicyBehaviorGain(
AudioAdmin::BehaviorGain({.none_gain_db = 0., .duck_gain_db = -10., .mute_gain_db = -100.}));
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(DoubleIs("none gain db", 0.0),
DoubleIs("duck gain db", -10.0),
DoubleIs("mute gain db", -100.0))))),
ChildrenMatch(Contains(NodeMatches(
AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", true)))))))))));
// Structures to hold active usages and usage behaviors.
std::vector<fuchsia::media::Usage> active_usages;
std::array<fuchsia::media::Behavior, fuchsia::media::RENDER_USAGE_COUNT> render_usage_behaviors;
std::array<fuchsia::media::Behavior, fuchsia::media::CAPTURE_USAGE_COUNT> capture_usage_behaviors;
render_usage_behaviors.fill(fuchsia::media::Behavior::NONE);
capture_usage_behaviors.fill(fuchsia::media::Behavior::NONE);
// Expect active RenderUsage::MEDIA to be logged, with default policy NONE.
active_usages.push_back(
fuchsia::media::Usage::WithRenderUsage((fuchsia::media::AudioRenderUsage::MEDIA)));
under_test_.UpdateActiveUsagePolicy(active_usages, render_usage_behaviors,
capture_usage_behaviors);
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(DoubleIs("none gain db", 0.0),
DoubleIs("duck gain db", -10.0),
DoubleIs("mute gain db", -100.0))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", false))))),
NodeMatches(AllOf(
NameMatches("2"),
PropertyList(UnorderedElementsAre(
BoolIs("active", true), StringIs("RenderUsage::MEDIA", "NONE")))))))))));
// Expect active RenderUsage::MEDIA and CaptureUsage::SYSTEM_AGENT to be logged, with DUCK applied
// to MEDIA.
active_usages.push_back(
fuchsia::media::Usage::WithCaptureUsage((fuchsia::media::AudioCaptureUsage::SYSTEM_AGENT)));
render_usage_behaviors[static_cast<int>(fuchsia::media::AudioRenderUsage::MEDIA)] =
fuchsia::media::Behavior::DUCK;
under_test_.UpdateActiveUsagePolicy(active_usages, render_usage_behaviors,
capture_usage_behaviors);
EXPECT_THAT(
GetHierarchy(),
ChildrenMatch(Contains(AllOf(
NodeMatches(AllOf(NameMatches("active usage policies"),
PropertyList(UnorderedElementsAre(DoubleIs("none gain db", 0.0),
DoubleIs("duck gain db", -10.0),
DoubleIs("mute gain db", -100.0))))),
ChildrenMatch(UnorderedElementsAre(
NodeMatches(AllOf(NameMatches("1"), PropertyList(Contains(BoolIs("active", false))))),
NodeMatches(AllOf(NameMatches("2"), PropertyList(UnorderedElementsAre(
BoolIs("active", false),
StringIs("RenderUsage::MEDIA", "NONE"))))),
NodeMatches(AllOf(NameMatches("3"),
PropertyList(UnorderedElementsAre(
BoolIs("active", true), StringIs("RenderUsage::MEDIA", "DUCK"),
StringIs("CaptureUsage::SYSTEM_AGENT", "NONE")))))))))));
}
} // namespace
} // namespace media::audio