blob: b00ed2f775b0b940af4d211015e8646334834337 [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 <fuchsia/feedback/cpp/fidl.h>
#include <fuchsia/hwinfo/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/update/channel/cpp/fidl.h>
#include <lib/async/cpp/executor.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fit/result.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <cstdint>
#include "garnet/public/lib/fostr/fidl/fuchsia/feedback/formatting.h"
#include "src/developer/feedback/feedback_agent/constants.h"
#include "src/developer/feedback/feedback_agent/feedback_agent.h"
#include "src/developer/feedback/feedback_agent/tests/zx_object_util.h"
#include "src/developer/feedback/testing/gmatchers.h"
#include "src/developer/feedback/utils/archive.h"
#include "src/lib/files/file.h"
#include "src/lib/files/glob.h"
#include "src/lib/fsl/handles/object_info.h"
#include "src/lib/fsl/vmo/strings.h"
#include "src/lib/fxl/logging.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/lib/syslog/cpp/logger.h"
#include "src/ui/lib/escher/test/gtest_vulkan.h"
#include "third_party/googletest/googlemock/include/gmock/gmock.h"
#include "third_party/googletest/googletest/include/gtest/gtest.h"
namespace feedback {
namespace {
using fuchsia::feedback::Attachment;
using fuchsia::feedback::Data;
using fuchsia::feedback::DataProvider_GetData_Result;
using fuchsia::feedback::DataProviderPtr;
using fuchsia::feedback::DataProviderSyncPtr;
using fuchsia::feedback::ImageEncoding;
using fuchsia::feedback::Screenshot;
using fuchsia::hwinfo::BoardInfo;
using fuchsia::hwinfo::BoardPtr;
using fuchsia::hwinfo::ProductInfo;
using fuchsia::hwinfo::ProductPtr;
using inspect::testing::PropertyList;
using inspect::testing::UintIs;
class LogListener : public fuchsia::logger::LogListener {
public:
LogListener(std::shared_ptr<sys::ServiceDirectory> services) : binding_(this) {
binding_.Bind(log_listener_.NewRequest());
fuchsia::logger::LogPtr logger = services->Connect<fuchsia::logger::Log>();
logger->Listen(std::move(log_listener_), /*options=*/nullptr);
}
bool HasLogs() { return has_logs_; }
private:
// |fuchsia::logger::LogListener|
void LogMany(::std::vector<fuchsia::logger::LogMessage> log) { has_logs_ = true; }
void Log(fuchsia::logger::LogMessage log) { has_logs_ = true; }
void Done() { FXL_NOTIMPLEMENTED(); }
fidl::Binding<fuchsia::logger::LogListener> binding_;
fuchsia::logger::LogListenerPtr log_listener_;
bool has_logs_ = false;
};
// Smoke-tests the real environment service for the fuchsia.feedback.DataProvider FIDL interface,
// connecting through FIDL.
class FeedbackAgentIntegrationTest : public sys::testing::TestWithEnvironment {
public:
FeedbackAgentIntegrationTest()
: test_name_(testing::UnitTest::GetInstance()->current_test_info()->name()) {}
void SetUp() override { environment_services_ = sys::ServiceDirectory::CreateFromNamespace(); }
void TearDown() override {
if (inspect_test_app_controller_) {
TerminateInspectTestApp();
}
}
protected:
// Makes sure the component serving fuchsia.logger.Log is up and running as the DumpLogs() request
// could time out on machines where the component is too slow to start.
//
// Syslog are generally handled by a single logger that implements two protocols:
// (1) fuchsia.logger.LogSink to write syslog messages
// (2) fuchsia.logger.Log to read syslog messages and kernel log messages.
// Returned syslog messages are restricted to the ones that were written using its LogSink while
// kernel log messages are the same for all loggers.
//
// In this integration test, we inject a "fresh copy" of archivist.cmx for fuchsia.logger.Log so
// we can retrieve the syslog messages. But we do _not_ inject that same archivist.cmx for
// fuchsia.logger.LogSink as it would swallow all the error and warning messages the other
// injected services could produce and make debugging really hard. Therefore, the injected
// archivist.cmx does not have any syslog messages and will only have the global kernel log
// messages.
//
// When archivist.cmx spawns, it will start collecting asynchronously kernel log messages. But if
// DumpLogs() is called "too soon", it will immediately return empty logs instead of waiting on
// the kernel log collection (fxb/4665), resulting in a flaky test (fxb/8303). We thus spawn
// archivist.cmx on advance and wait for it to have at least one message before running the actual
// test.
void WaitForLogger() {
LogListener log_listener(environment_services_);
RunLoopUntil([&log_listener] { return log_listener.HasLogs(); });
}
// Makes sure the component serving fuchsia.update.channel.Provider is up and running as the
// GetCurrent() request could time out on machines where the component is too slow to start.
void WaitForChannelProvider() {
fuchsia::update::channel::ProviderSyncPtr channel_provider;
environment_services_->Connect(channel_provider.NewRequest());
std::string unused;
ASSERT_EQ(channel_provider->GetCurrent(&unused), ZX_OK);
}
// Makes sure there is at least one component in the test environment that exposes some Inspect
// data.
//
// This is useful as we are excluding system_objects paths from the Inspect discovery and the test
// component itself only has a system_objects Inspect node.
void WaitForInspect() {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = "fuchsia-pkg://fuchsia.com/feedback_agent_tests#meta/inspect_test_app.cmx";
environment_ = CreateNewEnclosingEnvironment("inspect_test_app_environment", CreateServices());
environment_->CreateComponent(std::move(launch_info),
inspect_test_app_controller_.NewRequest());
bool ready = false;
inspect_test_app_controller_.events().OnDirectoryReady = [&ready] { ready = true; };
RunLoopUntil([&ready] { return ready; });
}
// Makes sure the component serving fuchsia.hwinfo.BoardInfo is up and running as the
// GetInfo() request could time out on machines where the component is too slow to start.
void WaitForBoardProvider() {
fuchsia::hwinfo::BoardPtr board_provider;
environment_services_->Connect(board_provider.NewRequest());
bool ready = false;
board_provider->GetInfo([&](BoardInfo board_info) { ready = true; });
RunLoopUntil([&ready] { return ready; });
}
// Makes sure the component serving fuchsia.hwinfo.ProductInfo is up and running as the
// GetInfo() request could time out on machines where the component is too slow to start.
void WaitForProductProvider() {
fuchsia::hwinfo::ProductPtr product_provider;
environment_services_->Connect(product_provider.NewRequest());
bool ready = false;
product_provider->GetInfo([&](ProductInfo product_info) { ready = true; });
RunLoopUntil([&ready] { return ready; });
}
// Creates an enclosing environment for the test to run in isolation, and returns it.
//
// Use this |EnclosingEnvironment| to connect to its DataProvider service. This environment does
// not support |*SyncPtr|.
//
// Using this environment provides a fresh copy of |feedback_agent.cmx|, and resets Inspect
// across test cases (especially |total_num_connections|).
std::unique_ptr<sys::testing::EnclosingEnvironment> CreateDataProviderEnvironment() {
std::unique_ptr<sys::testing::EnvironmentServices> services = CreateServices();
// We inject a fresh copy of |feedback_agent.cmx| in the environment.
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = "fuchsia-pkg://fuchsia.com/feedback_agent#meta/feedback_agent.cmx";
services->AddServiceWithLaunchInfo(std::move(launch_info), "fuchsia.feedback.DataProvider");
// We inherit the other injected services from the parent environment.
services->AllowParentService("fuchsia.cobalt.LoggerFactory");
services->AllowParentService("fuchsia.hwinfo.Board");
services->AllowParentService("fuchsia.hwinfo.Product");
services->AllowParentService("fuchsia.boot.ReadOnlyLog");
services->AllowParentService("fuchsia.logger.Log");
services->AllowParentService("fuchsia.update.channel.Provider");
auto env = CreateNewEnclosingEnvironment(test_name_, std::move(services));
WaitForEnclosingEnvToStart(env.get());
return env;
}
// Waits for the process serving the DataProvider connection to be spawned.
void WaitForDataProvider(DataProviderPtr* provider) {
ASSERT_NE(provider, nullptr);
// As the connection is asynchronous, we make a call and wait for a response to make sure the
// connection is established and the process for the service spawned.
bool done = false;
(*provider)->GetData([&done](DataProvider_GetData_Result res) { done = true; });
RunLoopUntil([&done] { return done; });
}
// EXPECTs that there is a "feedback_agent.cmx" process running in a child job of the test
// environment job and that this process has |expected_num_feedback_data_providers| sibling
// processes.
void CheckNumberOfFeedbackDataProviders(const uint32_t expected_num_feedback_data_providers) {
uint32_t num_feedback_agents = 0;
uint32_t num_feedback_data_providers = 0;
RunLoopUntil([&] {
GetNumberOfFeedbackDataProviders(&num_feedback_agents, &num_feedback_data_providers);
return num_feedback_data_providers == expected_num_feedback_data_providers;
});
EXPECT_EQ(num_feedback_data_providers, expected_num_feedback_data_providers);
EXPECT_EQ(num_feedback_agents, 1u);
}
// Returns the current number of processes named "feedback_agent.cmx" and
// "feedback_data_provider" in the test environment.
void GetNumberOfFeedbackDataProviders(uint32_t* num_feedback_agents,
uint32_t* num_feedback_data_providers) {
// We want to check how many feedback_data_provider subprocesses feedback_agent.cmx has spawned.
//
// The job and process hierarchy looks like this under the test environment:
// j: 109762 $test_name_
// j: 109993
// p: 109998 feedback_agent_integration_test
// j: 112299
// p: 112304 vulkan_loader.cmx
// j: 115016
// p: 115021 feedback_agent.cmx
// p: 115022 feedback_data_provider
// p: 115023 feedback_data_provider
// p: 115024 feedback_data_provider
// j: 116540
// p: 116545 archivist.cmx
//
// There is basically a job the for the test component and a job for each injected service. The
// one of interest is feedback_agent.cmx and we check the number of sibling processes named
// "feedback_data_provider".
// We first get a handle to the test environment job.
fuchsia::sys::JobProviderSyncPtr job_provider;
files::Glob glob(fxl::Substitute("/hub/r/$0/*/job", test_name_));
ASSERT_EQ(glob.size(), 1u);
ASSERT_EQ(
fdio_service_connect(*glob.begin(), job_provider.NewRequest().TakeChannel().release()),
ZX_OK);
zx::job test_env_job;
ASSERT_EQ(job_provider->GetJob(&test_env_job), ZX_OK);
ASSERT_STREQ(fsl::GetObjectName(test_env_job.get()).c_str(), test_name_.c_str());
// We then get the child jobs under the test environment job.
// Child jobs are for the test component and each injected service, including DataProvider.
auto child_jobs = GetChildJobs(test_env_job.get());
ASSERT_GE(child_jobs.size(), 1u);
*num_feedback_agents = 0;
*num_feedback_data_providers = 0;
for (const auto& child_job : child_jobs) {
auto processes = GetChildProcesses(child_job.get());
ASSERT_GE(processes.size(), 1u);
for (const auto& process : processes) {
const std::string process_name = fsl::GetObjectName(process.get());
if (process_name == "feedback_agent.cmx") {
(*num_feedback_agents)++;
} else if (process_name == "feedback_data_provider") {
(*num_feedback_data_providers)++;
}
}
}
}
// Checks the Inspect tree for "feedback_agent.cmx".
void CheckFeedbackAgentInspectTree(const uint64_t expected_total_num_connections,
const uint64_t expected_current_num_connections) {
const std::string glob_pattern = fxl::Substitute(
"/hub/r/$0/*/c/feedback_agent.cmx/*/*/diagnostics/root.inspect", test_name_);
// Wait until the |root.inspect| file is created.
RunLoopUntil([&glob_pattern] {
files::Glob glob(glob_pattern);
return glob.size() > 0;
});
files::Glob glob(glob_pattern);
EXPECT_EQ(glob.size(), 1u);
std::vector<uint8_t> buffer;
ASSERT_TRUE(files::ReadFileToVector(*glob.begin(), &buffer));
inspect::Hierarchy tree = inspect::ReadFromBuffer(std::move(buffer)).take_value();
EXPECT_THAT(tree.node(),
PropertyList(testing::UnorderedElementsAreArray({
UintIs("total_num_connections", expected_total_num_connections),
UintIs("current_num_connections", expected_current_num_connections),
})));
}
private:
void TerminateInspectTestApp() {
inspect_test_app_controller_->Kill();
bool is_inspect_test_app_terminated = false;
inspect_test_app_controller_.events().OnTerminated =
[&is_inspect_test_app_terminated](int64_t code, fuchsia::sys::TerminationReason reason) {
FXL_CHECK(reason == fuchsia::sys::TerminationReason::EXITED);
is_inspect_test_app_terminated = true;
};
RunLoopUntil([&is_inspect_test_app_terminated] { return is_inspect_test_app_terminated; });
}
protected:
std::shared_ptr<sys::ServiceDirectory> environment_services_;
private:
std::unique_ptr<sys::testing::EnclosingEnvironment> environment_;
fuchsia::sys::ComponentControllerPtr inspect_test_app_controller_;
std::string test_name_;
};
// We use VK_TEST instead of the regular TEST macro because Scenic needs Vulkan to operate properly
// and take a screenshot. Note that calls to Scenic hang indefinitely for headless devices so this
// test assumes the device has a display like the other Scenic tests, see SCN-1281.
VK_TEST_F(FeedbackAgentIntegrationTest, GetScreenshot_SmokeTest) {
DataProviderSyncPtr data_provider;
environment_services_->Connect(data_provider.NewRequest());
std::unique_ptr<Screenshot> out_screenshot;
ASSERT_EQ(data_provider->GetScreenshot(ImageEncoding::PNG, &out_screenshot), ZX_OK);
// We cannot expect a particular payload in the response because Scenic might return a screenshot
// or not depending on which device the test runs.
}
TEST_F(FeedbackAgentIntegrationTest, GetData_CheckKeys) {
// We make sure the components serving the services GetData() connects to are up and running.
WaitForLogger();
WaitForChannelProvider();
WaitForInspect();
WaitForBoardProvider();
WaitForProductProvider();
DataProviderSyncPtr data_provider;
environment_services_->Connect(data_provider.NewRequest());
DataProvider_GetData_Result out_result;
ASSERT_EQ(data_provider->GetData(&out_result), ZX_OK);
fit::result<Data, zx_status_t> result = std::move(out_result);
ASSERT_TRUE(result.is_ok());
const Data& data = result.value();
// We cannot expect a particular value for each annotation or attachment because values might
// depend on which device the test runs (e.g., board name) or what happened prior to running this
// test (e.g., logs). But we should expect the keys to be present.
ASSERT_TRUE(data.has_annotations());
EXPECT_THAT(data.annotations(), testing::UnorderedElementsAreArray({
MatchesKey(kAnnotationBuildBoard),
MatchesKey(kAnnotationBuildIsDebug),
MatchesKey(kAnnotationBuildLatestCommitDate),
MatchesKey(kAnnotationBuildProduct),
MatchesKey(kAnnotationBuildVersion),
MatchesKey(kAnnotationChannel),
MatchesKey(kAnnotationDeviceBoardName),
MatchesKey(kAnnotationDeviceUptime),
MatchesKey(kAnnotationDeviceUTCTime),
MatchesKey(kAnnotationHardwareBoardName),
MatchesKey(kAnnotationHardwareBoardRevision),
MatchesKey(kAnnotationHardwareProductSKU),
MatchesKey(kAnnotationHardwareProductLanguage),
MatchesKey(kAnnotationHardwareProductRegulatoryDomain),
MatchesKey(kAnnotationHardwareProductLocaleList),
MatchesKey(kAnnotationHardwareProductName),
MatchesKey(kAnnotationHardwareProductModel),
MatchesKey(kAnnotationHardwareProductManufacturer),
}));
ASSERT_TRUE(data.has_attachment_bundle());
const auto& attachment_bundle = data.attachment_bundle();
EXPECT_STREQ(attachment_bundle.key.c_str(), kAttachmentBundle);
std::vector<Attachment> unpacked_attachments;
ASSERT_TRUE(Unpack(attachment_bundle.value, &unpacked_attachments));
EXPECT_THAT(unpacked_attachments, testing::UnorderedElementsAreArray({
MatchesKey(kAttachmentAnnotations),
MatchesKey(kAttachmentBuildSnapshot),
MatchesKey(kAttachmentInspect),
MatchesKey(kAttachmentLogKernel),
MatchesKey(kAttachmentLogSystem),
}));
}
TEST_F(FeedbackAgentIntegrationTest, OneDataProviderPerRequest) {
auto env = CreateDataProviderEnvironment();
DataProviderPtr data_provider_1;
env->ConnectToService(data_provider_1.NewRequest());
WaitForDataProvider(&data_provider_1);
CheckNumberOfFeedbackDataProviders(/*expected_num_feedback_data_providers=*/1u);
CheckFeedbackAgentInspectTree(/*expected_total_num_connections=*/1u,
/*expected_current_num_connections=*/1u);
DataProviderPtr data_provider_2;
env->ConnectToService(data_provider_2.NewRequest());
WaitForDataProvider(&data_provider_2);
CheckNumberOfFeedbackDataProviders(/*expected_num_feedback_data_providers=*/2u);
CheckFeedbackAgentInspectTree(/*expected_total_num_connections=*/2u,
/*expected_current_num_connections=*/2u);
data_provider_1.Unbind();
CheckNumberOfFeedbackDataProviders(/*expected_num_feedback_data_providers=*/1u);
CheckFeedbackAgentInspectTree(/*expected_total_num_connections=*/2u,
/*expected_current_num_connections=*/1u);
DataProviderPtr data_provider_3;
env->ConnectToService(data_provider_3.NewRequest());
WaitForDataProvider(&data_provider_3);
CheckNumberOfFeedbackDataProviders(/*expected_num_feedback_data_providers=*/2u);
CheckFeedbackAgentInspectTree(/*expected_total_num_connections=*/3u,
/*expected_current_num_connections=*/2u);
data_provider_2.Unbind();
data_provider_3.Unbind();
CheckNumberOfFeedbackDataProviders(/*expected_num_feedback_data_providers=*/0u);
CheckFeedbackAgentInspectTree(/*expected_total_num_connections=*/3u,
/*expected_current_num_connections=*/0u);
}
} // namespace
} // namespace feedback