blob: 73cd5ca1a4507188b8f1abc7463593fb780cfcea [file] [log] [blame]
// Copyright 2022 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/developer/forensics/feedback/annotations/annotation_manager.h"
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/task.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/feedback/annotations/provider.h"
#include "src/developer/forensics/feedback/annotations/types.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
namespace forensics::feedback {
namespace {
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::UnorderedElementsAreArray;
constexpr bool kIsMissingNonPlatform = true;
constexpr bool kNotIsMissingNonPlatform = false;
auto MakePair(const char* key, const char* value) { return Pair(key, ErrorOr<std::string>(value)); }
auto MakePair(const char* key, const Error error) { return Pair(key, ErrorOr<std::string>(error)); }
class DynamicNonPlatform : public NonPlatformAnnotationProvider {
public:
DynamicNonPlatform(const bool exempt = kNotIsMissingNonPlatform)
: is_missing_annotations_(exempt) {}
Annotations Get() override {
++calls_;
if (is_missing_annotations_) {
return {};
}
return {{"num_calls", std::to_string(calls_)}};
}
bool IsMissingAnnotations() const override { return is_missing_annotations_; }
private:
size_t calls_{0};
bool is_missing_annotations_;
};
using AnnotationManagerTest = UnitTestFixture;
TEST_F(AnnotationManagerTest, ImmediatelyAvailable) {
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
DynamicNonPlatform non_platform;
{
AnnotationManager manager(dispatcher(), {"annotation1", "annotation2", "num_calls"},
static_annotations, &non_platform);
EXPECT_THAT(manager.ImmediatelyAvailable(), UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
}));
}
}
TEST_F(AnnotationManagerTest, StaticAllowlist) {
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
DynamicNonPlatform counter;
{
AnnotationManager manager(dispatcher(), {}, static_annotations, nullptr, {&counter});
EXPECT_THAT(manager.ImmediatelyAvailable(), IsEmpty());
}
{
AnnotationManager manager(dispatcher(), {"annotation1"}, static_annotations, nullptr,
{&counter});
EXPECT_THAT(manager.ImmediatelyAvailable(), UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
}));
}
{
AnnotationManager manager(dispatcher(), {"annotation1", "num_calls"}, static_annotations,
nullptr, {&counter});
EXPECT_THAT(manager.ImmediatelyAvailable(), UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("num_calls", "3"),
}));
}
}
TEST_F(AnnotationManagerTest, IsNotMissingNonPlatform) {
DynamicNonPlatform non_platform(kNotIsMissingNonPlatform);
{
AnnotationManager manager(dispatcher(), {}, {}, &non_platform);
EXPECT_THAT(manager.ImmediatelyAvailable(), UnorderedElementsAreArray({
MakePair("num_calls", "1"),
}));
EXPECT_FALSE(manager.IsMissingNonPlatformAnnotations());
}
}
TEST_F(AnnotationManagerTest, IsMissingNonPlatform) {
DynamicNonPlatform non_platform(kIsMissingNonPlatform);
{
AnnotationManager manager(dispatcher(), {}, {}, &non_platform);
EXPECT_THAT(manager.ImmediatelyAvailable(), IsEmpty());
EXPECT_TRUE(manager.IsMissingNonPlatformAnnotations());
}
}
TEST_F(AnnotationManagerTest, UniqueKeys) {
ASSERT_DEATH(
{
AnnotationManager manager(dispatcher(), {"annotation1"});
manager.InsertStatic({{"annotation1", "value1"}});
manager.InsertStatic({{"annotation1", "value2"}});
},
"Attempting to re-insert annotation1");
}
class SimpleStaticAsync : public StaticAsyncAnnotationProvider {
public:
SimpleStaticAsync(async_dispatcher_t* dispatcher, Annotations annotations,
const zx::duration delay = zx::sec(0))
: dispatcher_(dispatcher), annotations_(std::move(annotations)), delay_(delay) {}
std::set<std::string> GetKeys() const override {
std::set<std::string> keys;
for (const auto& [k, _] : annotations_) {
keys.insert(k);
}
return keys;
}
void GetOnce(::fit::callback<void(Annotations)> callback) override {
async::PostDelayedTask(
dispatcher_, [this, cb = std::move(callback)]() mutable { cb(annotations_); }, delay_);
}
private:
async_dispatcher_t* dispatcher_;
Annotations annotations_;
zx::duration delay_;
};
TEST_F(AnnotationManagerTest, GetAllNoStaticAsyncProviders) {
async::Executor executor(dispatcher());
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
DynamicNonPlatform non_platform;
{
AnnotationManager manager(dispatcher(), {"annotation1", "annotation2", "num_calls"},
static_annotations, &non_platform);
Annotations annotations;
// Use a timeout of 0 because only immediately available annotations are returned.
executor.schedule_task(
manager.GetAll(zx::sec(0))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopUntilIdle();
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
}));
}
}
TEST_F(AnnotationManagerTest, GetAllStaticAsyncProviders) {
async::Executor executor(dispatcher());
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
SimpleStaticAsync immediate_static(dispatcher(), {
{"annotation3", "value3"},
});
SimpleStaticAsync five_second_static(dispatcher(),
{
{"annotation4", "value4"},
},
zx::sec(5));
SimpleStaticAsync ten_second_static(dispatcher(),
{
{"annotation5", "value5"},
},
zx::sec(10));
DynamicNonPlatform non_platform;
AnnotationManager manager(dispatcher(),
{
"annotation1",
"annotation2",
"num_calls",
"annotation3",
"annotation4",
"annotation5",
},
static_annotations, &non_platform, {},
{&immediate_static, &five_second_static, &ten_second_static});
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(0))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopUntilIdle();
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
MakePair("annotation3", "value3"),
MakePair("annotation4", Error::kTimeout),
MakePair("annotation5", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(5))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(5));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "2"),
MakePair("annotation3", "value3"),
MakePair("annotation4", "value4"),
MakePair("annotation5", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(5))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(5));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "3"),
MakePair("annotation3", "value3"),
MakePair("annotation4", "value4"),
MakePair("annotation5", "value5"),
}));
}
}
class SimpleDynamicAsync : public DynamicAsyncAnnotationProvider {
public:
SimpleDynamicAsync(async_dispatcher_t* dispatcher, std::set<std::string> keys,
const zx::duration delay = zx::sec(0))
: dispatcher_(dispatcher), keys_(std::move(keys)), delay_(delay) {}
std::set<std::string> GetKeys() const override { return keys_; }
void Get(::fit::callback<void(Annotations)> callback) override {
async::PostDelayedTask(
dispatcher_,
[this, cb = std::move(callback)]() mutable {
const std::string value = "call" + std::to_string(++count_);
Annotations annotations;
for (const auto& key : keys_) {
annotations.insert_or_assign(key, value);
}
cb(annotations);
},
delay_);
}
private:
async_dispatcher_t* dispatcher_;
std::set<std::string> keys_;
zx::duration delay_;
size_t count_{0};
};
TEST_F(AnnotationManagerTest, GetAllNoDyanmicAsyncProviders) {
async::Executor executor(dispatcher());
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
DynamicNonPlatform non_platform;
{
AnnotationManager manager(dispatcher(), {"annotation1", "annotation2", "num_calls"},
static_annotations, &non_platform);
Annotations annotations;
// Use a timeout of 0 because only immediately available annotations are returned.
executor.schedule_task(
manager.GetAll(zx::sec(0))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopUntilIdle();
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
}));
}
}
TEST_F(AnnotationManagerTest, GetAllDynamicAsyncProviders) {
async::Executor executor(dispatcher());
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
SimpleDynamicAsync immediate_dynamic(dispatcher(), {"annotation3"});
SimpleDynamicAsync five_second_dynamic(dispatcher(), {"annotation4"}, zx::sec(5));
SimpleDynamicAsync ten_second_dynamic(dispatcher(), {"annotation5"}, zx::sec(10));
DynamicNonPlatform non_platform;
AnnotationManager manager(dispatcher(),
{
"annotation1",
"annotation2",
"num_calls",
"annotation3",
"annotation4",
"annotation5",
},
static_annotations, &non_platform, {}, {},
{&immediate_dynamic, &five_second_dynamic, &ten_second_dynamic});
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(0))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopUntilIdle();
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
MakePair("annotation3", "call1"),
MakePair("annotation4", Error::kTimeout),
MakePair("annotation5", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(5))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(5));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "2"),
MakePair("annotation3", "call2"),
MakePair("annotation4", "call2"),
MakePair("annotation5", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(10))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(10));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "3"),
MakePair("annotation3", "call3"),
MakePair("annotation4", "call3"),
MakePair("annotation5", "call3"),
}));
}
}
TEST_F(AnnotationManagerTest, GetAll) {
async::Executor executor(dispatcher());
const Annotations static_annotations({
{"annotation1", "value1"},
{"annotation2", Error::kMissingValue},
});
SimpleStaticAsync three_second_static(dispatcher(), {{"annotation3", "value3"}}, zx::sec(3));
SimpleDynamicAsync five_second_dynamic(dispatcher(), {"annotation4"}, zx::sec(5));
DynamicNonPlatform non_platform;
AnnotationManager manager(dispatcher(),
{
"annotation1",
"annotation2",
"num_calls",
"annotation3",
"annotation4",
},
static_annotations, &non_platform, {}, {&three_second_static},
{&five_second_dynamic});
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(0))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopUntilIdle();
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "1"),
MakePair("annotation3", Error::kTimeout),
MakePair("annotation4", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(3))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(3));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "2"),
MakePair("annotation3", "value3"),
MakePair("annotation4", Error::kTimeout),
}));
}
{
Annotations annotations;
executor.schedule_task(
manager.GetAll(zx::sec(5))
.and_then([&annotations](Annotations& result) { annotations = std::move(result); })
.or_else([]() { FX_LOGS(FATAL) << "Unreachable error reached"; }));
RunLoopFor(zx::sec(5));
EXPECT_THAT(annotations, UnorderedElementsAreArray({
MakePair("annotation1", "value1"),
MakePair("annotation2", Error::kMissingValue),
MakePair("num_calls", "3"),
MakePair("annotation3", "value3"),
MakePair("annotation4", "call3"),
}));
}
}
} // namespace
} // namespace forensics::feedback