blob: 6cdf1a00c2d4c603c18efb2a723f9cde2cc375e6 [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/media/cpp/fidl.h>
#include <fuchsia/scheduler/cpp/fidl.h>
#include <fuchsia/virtualaudio/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include "src/lib/fxl/logging.h"
namespace media::audio::test {
namespace {
// 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";
fit::function<fuchsia::sys::LaunchInfo()> LaunchInfoWithIsolatedDevmgrForUrl(
const char* url, std::vector<std::string> args,
std::shared_ptr<sys::ServiceDirectory> services) {
return [url, args = std::move(args), services = std::move(services)] {
zx::channel devfs = services->Connect<fuchsia::io::Directory>(kIsolatedDevmgrServiceName)
.Unbind()
.TakeChannel();
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = url;
launch_info.arguments.reset(args);
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));
return launch_info;
};
}
fit::function<fuchsia::sys::LaunchInfo()> AudioCoreLaunchInfo(
std::shared_ptr<sys::ServiceDirectory> services) {
return LaunchInfoWithIsolatedDevmgrForUrl(
"fuchsia-pkg://fuchsia.com/audio_core#meta/audio_core_nodevfs.cmx",
{"--disable-device-settings-writeback"}, services);
}
fit::function<fuchsia::sys::LaunchInfo()> VirtualAudioLaunchInfo(
std::shared_ptr<sys::ServiceDirectory> services) {
return LaunchInfoWithIsolatedDevmgrForUrl(
"fuchsia-pkg://fuchsia.com/virtual_audio_service#meta/virtual_audio_service_nodevfs.cmx", {},
services);
}
// 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 EnvironmentMain(HermeticAudioEnvironment* env) {
async::Loop loop{&kAsyncLoopConfigAttachToThread};
env->Start(&loop);
loop.Run();
}
} // namespace
HermeticAudioEnvironment::HermeticAudioEnvironment() {
// 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;
ConnectToService(devfs_dir.NewRequest(), kIsolatedDevmgrServiceName);
fuchsia::io::NodeInfo info;
zx_status_t status = devfs_dir->Describe(&info);
FXL_CHECK(status == ZX_OK);
}
void HermeticAudioEnvironment::Start(async::Loop* loop) {
FXL_CHECK(loop_ == nullptr);
loop_ = loop;
auto real_services = sys::ServiceDirectory::CreateFromNamespace();
auto real_env = real_services->Connect<fuchsia::sys::Environment>();
// Add in the services that will be available in our hermetic environment. Note the '_nodevfs'
// cmx files; these 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.
auto services = sys::testing::EnvironmentServices::Create(real_env);
services->AddServiceWithLaunchInfo("audio_core", AudioCoreLaunchInfo(real_services),
fuchsia::media::AudioCore::Name_);
services->AddServiceWithLaunchInfo("audio_core", AudioCoreLaunchInfo(real_services),
fuchsia::media::AudioDeviceEnumerator::Name_);
services->AddServiceWithLaunchInfo("virtual_audio", VirtualAudioLaunchInfo(real_services),
fuchsia::virtualaudio::Control::Name_);
services->AddServiceWithLaunchInfo("virtual_audio", VirtualAudioLaunchInfo(real_services),
fuchsia::virtualaudio::Input::Name_);
services->AddServiceWithLaunchInfo("virtual_audio", VirtualAudioLaunchInfo(real_services),
fuchsia::virtualaudio::Output::Name_);
services->AllowParentService("fuchsia.logger.LogSink");
services->AllowParentService(fuchsia::scheduler::ProfileProvider::Name_);
services->AllowParentService(kIsolatedDevmgrServiceName);
std::unique_lock<std::mutex> lock(mutex_);
hermetic_environment_ =
sys::testing::EnclosingEnvironment::Create("audio_test", 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() {
if (loop_) {
async::PostTask(loop_->dispatcher(), [loop = loop_] { loop->Quit(); });
env_thread_.join();
}
}
} // namespace media::audio::test