blob: 663958bf4d3d955798346308a2a090c730fcf6e8 [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/lib/test/hermetic_audio_environment.h"
#include <fuchsia/inspect/cpp/fidl.h>
#include <fuchsia/media/audio/cpp/fidl.h>
#include <fuchsia/media/cpp/fidl.h>
#include <fuchsia/media/tuning/cpp/fidl.h>
#include <fuchsia/scheduler/cpp/fidl.h>
#include <fuchsia/thermal/cpp/fidl.h>
#include <fuchsia/ultrasound/cpp/fidl.h>
#include <fuchsia/virtualaudio/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/directory.h>
#include <lib/inspect/service/cpp/reader.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/device/vfs.h>
#include <zircon/status.h>
#include <test/thermal/cpp/fidl.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/glob.h"
#include "src/lib/fxl/strings/substitute.h"
namespace media::audio::test {
namespace {
// The label used for our hermetic audio environment.
constexpr const char kIsolatedEnvironmentLabel[] = "hermetic_audio_test";
// The IsolatedDevmgr will expose a fuchsia.io.Directory protocol under this service name in the
// devmgrs public directory.
constexpr const char kIsolatedDevmgrServiceName[] = "fuchsia.media.AudioTestDevmgr";
std::function<fuchsia::sys::LaunchInfo()> LaunchInfoWithIsolatedDevmgrForUrl(
std::string url, std::shared_ptr<sys::ServiceDirectory> services,
std::string config_data_path = "",
std::vector<std::string> arguments = std::vector<std::string>()) {
return [url, services = std::move(services), config_data_path, arguments] {
zx::channel devfs = services->Connect<fuchsia::io::Directory>(kIsolatedDevmgrServiceName)
.Unbind()
.TakeChannel();
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = url;
launch_info.flat_namespace = fuchsia::sys::FlatNamespace::New();
launch_info.flat_namespace->paths.push_back("/dev");
launch_info.flat_namespace->directories.push_back(std::move(devfs));
if (!arguments.empty()) {
launch_info.arguments = arguments;
}
zx::channel config_data;
if (config_data_path != "") {
FX_LOGS(INFO) << "Using path '" << config_data_path << "' for /config/data directory for "
<< url << ".";
zx::channel remote;
zx::channel::create(0, &config_data, &remote);
zx_status_t status = fdio_open(
config_data_path.c_str(),
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_FLAG_DIRECTORY, remote.release());
if (status == ZX_OK) {
launch_info.flat_namespace->paths.push_back("/config/data");
launch_info.flat_namespace->directories.push_back(std::move(config_data));
} else {
FX_PLOGS(ERROR, status) << "Unable to open '" << config_data_path << ".";
}
} else {
FX_LOGS(INFO) << "No config_data provided for " << url;
}
return launch_info;
};
}
std::string ComponentManifestFromURL(std::string component_url) {
const char* kMeta = "#meta/";
auto k = component_url.find(kMeta);
FX_CHECK(k != std::string::npos);
return component_url.substr(k + strlen(kMeta));
}
} // namespace
// Runs a thread with a dedicated loop for managing the enclosing environment. We use a thread here
// for a few reasons. First, and most importantly, we want to share a hermetic audio_core instance
// across all the tests in a test suite. To do this, we need to provide the EnclosingEnvironment
// with an async loop that is not scoped to the lifetime of a single test (as is done when using
// gtest::RealLoopFixture).
//
// Secondly, if we reuse the primary test loop we can under some circumstances run into deadlock
// when, for example, using a sync pointer since that will block the async loop before the backing
// service has a chance to be created.
void HermeticAudioEnvironment::EnvironmentMain(HermeticAudioEnvironment* env) {
async::Loop loop{&kAsyncLoopConfigAttachToCurrentThread};
env->StartEnvThread(&loop);
loop.Run();
}
HermeticAudioEnvironment::HermeticAudioEnvironment(Options options) : options_(options) {
// Create the thread here to ensure the rest of the class has fully initialized before starting
// the new thread, which takes a reference to |this|.
env_thread_ = std::thread(EnvironmentMain, this);
// Wait for the worker thread to start and finish some initialization.
std::unique_lock<std::mutex> lock(mutex_);
while (!hermetic_environment_ || !hermetic_environment_->is_running()) {
cv_.wait(lock);
}
// IsolatedDevmgr will not serve any messages on the directory until /dev/test/virtual_audio
// is ready. Run a simple Describe operation to ensure the devmgr is ready for traffic.
//
// Note we specifically use the |TextFixture| overrides of the virtual methods. This is needed
// because some test fixtures override these methods and include some asserts that will not
// be valid when this is run.
fuchsia::io::DirectorySyncPtr devfs_dir;
devmgr_services_->Connect(devfs_dir.NewRequest(), kIsolatedDevmgrServiceName);
fuchsia::io::NodeInfo info;
zx_status_t status = devfs_dir->Describe(&info);
FX_CHECK(status == ZX_OK) << status;
}
void HermeticAudioEnvironment::StartEnvThread(async::Loop* loop) {
FX_CHECK(loop_ == nullptr);
loop_ = loop;
auto real_services = sys::ServiceDirectory::CreateFromNamespace();
auto real_env = real_services->Connect<fuchsia::sys::Environment>();
fuchsia::sys::LaunchInfo devmgr_launch_info;
// This URL should be made more flexible for future tests.
devmgr_launch_info.url = options_.devmgr_url;
devmgr_services_ =
sys::ServiceDirectory::CreateWithRequest(&devmgr_launch_info.directory_request);
// Launch AudioTestDevmgr per-environment.
fuchsia::sys::LauncherPtr launcher;
real_env->GetLauncher(launcher.NewRequest());
launcher->CreateComponent(std::move(devmgr_launch_info), controller_.NewRequest());
// The '_nodevfs' cmx files are needed to allow us to map in our isolated devmgr under /dev for
// each component, otherwise these components would still be provided the shared/global devmgr.
std::string audio_core_url = options_.audio_core_base_url;
if (options_.audio_core_config_data_path != "") {
// When a custom config is specified, don't bother loading the default config data.
audio_core_url += "#meta/audio_core_nodevfs_noconfigdata.cmx";
} else {
audio_core_url += "#meta/audio_core_nodevfs.cmx";
}
// clang-format off
std::string virtual_audio_url =
"fuchsia-pkg://fuchsia.com/virtual-audio-service-for-test#meta/virtual_audio_service_nodevfs.cmx";
// clang-format on
std::string thermal_test_control_url =
"fuchsia-pkg://fuchsia.com/thermal-test-control#meta/thermal_test_control.cmx";
// Add in the services that will be available in our hermetic environment.
struct ComponentLaunchInfo {
ComponentType type;
std::string url;
std::function<fuchsia::sys::LaunchInfo()> launch_info;
std::vector<const char*> service_names;
};
std::vector<ComponentLaunchInfo> to_launch{
{
.type = kAudioCoreComponent,
.url = audio_core_url,
.launch_info = LaunchInfoWithIsolatedDevmgrForUrl(audio_core_url, devmgr_services_,
options_.audio_core_config_data_path,
options_.audio_core_arguments),
.service_names =
{
fuchsia::media::ActivityReporter::Name_,
fuchsia::media::Audio::Name_,
fuchsia::media::AudioCore::Name_,
fuchsia::media::AudioDeviceEnumerator::Name_,
fuchsia::media::tuning::AudioTuner::Name_,
fuchsia::media::UsageGainReporter::Name_,
fuchsia::media::UsageReporter::Name_,
fuchsia::media::audio::EffectsController::Name_,
fuchsia::ultrasound::Factory::Name_,
},
},
{
.type = kVirtualAudioComponent,
.url = virtual_audio_url,
.launch_info = LaunchInfoWithIsolatedDevmgrForUrl(virtual_audio_url, devmgr_services_),
.service_names =
{
fuchsia::virtualaudio::Control::Name_,
fuchsia::virtualaudio::Input::Name_,
fuchsia::virtualaudio::Output::Name_,
},
},
{
.type = kThermalTestControlComponent,
.url = thermal_test_control_url,
.launch_info =
LaunchInfoWithIsolatedDevmgrForUrl(thermal_test_control_url, devmgr_services_),
.service_names =
{
fuchsia::thermal::Controller::Name_,
::test::thermal::Control::Name_,
},
},
};
auto services = sys::testing::EnvironmentServices::Create(real_env);
for (auto& c : to_launch) {
component_urls_[c.type] = c.url;
for (auto n : c.service_names) {
services->AddServiceWithLaunchInfo(c.url, c.launch_info, n);
}
}
services->AllowParentService("fuchsia.logger.LogSink");
services->AllowParentService("fuchsia.tracing.provider.Registry");
services->AllowParentService(fuchsia::scheduler::ProfileProvider::Name_);
for (const auto& service : options_.extra_allowed_parent_services) {
services->AllowParentService(service);
}
std::unique_lock<std::mutex> lock(mutex_);
hermetic_environment_ = sys::testing::EnclosingEnvironment::Create(
kIsolatedEnvironmentLabel, real_env, std::move(services), {});
hermetic_environment_->SetRunningChangedCallback([this](bool running) {
std::unique_lock<std::mutex> lock(mutex_);
if (running) {
cv_.notify_all();
}
});
}
HermeticAudioEnvironment::~HermeticAudioEnvironment() {
FX_CHECK(loop_);
async::PostTask(loop_->dispatcher(), [loop = loop_] { loop->Quit(); });
env_thread_.join();
}
const inspect::Hierarchy HermeticAudioEnvironment::ReadInspect(ComponentType component_type) {
auto it = component_urls_.find(component_type);
FX_CHECK(it != component_urls_.end()) << "unknown component " << component_type;
files::Glob glob(fxl::Substitute("/hub/r/$0/*/c/$1/*/out/diagnostics/fuchsia.inspect.Tree",
kIsolatedEnvironmentLabel,
ComponentManifestFromURL(it->second)));
FX_CHECK(glob.size() == 1) << "could not find unique fuchsia.inspect.Tree, found " << glob.size()
<< " matches";
auto path = std::string(*glob.begin());
fuchsia::inspect::TreeSyncPtr tree;
auto status = fdio_service_connect(path.c_str(), tree.NewRequest().TakeChannel().release());
FX_CHECK(status == ZX_OK) << "could not connect to fuchsia.inspect.Tree: " << status;
fuchsia::inspect::TreeContent c;
status = tree->GetContent(&c);
FX_CHECK(status == ZX_OK) << "could not get VMO from fuchsia.inspect.Tree" << status;
FX_CHECK(c.has_buffer());
return inspect::ReadFromVmo(c.buffer().vmo).take_value();
}
} // namespace media::audio::test