[dart_test_runner] Adapt the dart runner to implement the suite fidl protocol so that it can be used the the v2 testing framework.
diff --git a/shell/platform/fuchsia/dart_runner/BUILD.gn b/shell/platform/fuchsia/dart_runner/BUILD.gn
index 3d3fad3..be67387 100644
--- a/shell/platform/fuchsia/dart_runner/BUILD.gn
+++ b/shell/platform/fuchsia/dart_runner/BUILD.gn
@@ -34,10 +34,14 @@
       "dart_component_controller_v2.h",
       "dart_runner.cc",
       "dart_runner.h",
+      "dart_test_component_controller_v2.cc",
+      "dart_test_component_controller_v2.h",
       "logging.h",
       "main.cc",
       "service_isolate.cc",
       "service_isolate.h",
+      "string_printf.cc",
+      "string_printf.h",
     ]
 
     defines = extra_defines
@@ -61,6 +65,8 @@
              "//flutter/shell/platform/fuchsia/dart-pkg/fuchsia",
              "//flutter/shell/platform/fuchsia/dart-pkg/zircon",
              "$fuchsia_sdk_root/fidl:fuchsia.component.runner",
+             "$fuchsia_sdk_root/fidl:fuchsia.logger",
+             "$fuchsia_sdk_root/fidl:fuchsia.sys.test",
              "$fuchsia_sdk_root/pkg:async",
              "$fuchsia_sdk_root/pkg:async-cpp",
              "$fuchsia_sdk_root/pkg:async-default",
@@ -70,10 +76,12 @@
              "$fuchsia_sdk_root/pkg:fidl_cpp",
              "$fuchsia_sdk_root/pkg:sys_inspect_cpp",
              "$fuchsia_sdk_root/pkg:sys_cpp",
+             "$fuchsia_sdk_root/pkg:sys_cpp_testing",
              "$fuchsia_sdk_root/pkg:syslog",
              "$fuchsia_sdk_root/pkg:trace",
              "$fuchsia_sdk_root/pkg:trace-provider-so",
              "$fuchsia_sdk_root/pkg:vfs_cpp",
+             "$fuchsia_sdk_root/pkg:zx",
              "//flutter/third_party/tonic",
            ] + dart_deps + extra_deps
   }
diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.cc b/shell/platform/fuchsia/dart_runner/dart_runner.cc
index fba5805..c089f63 100644
--- a/shell/platform/fuchsia/dart_runner/dart_runner.cc
+++ b/shell/platform/fuchsia/dart_runner/dart_runner.cc
@@ -13,25 +13,113 @@
 
 #include <cerrno>
 #include <memory>
+#include <sstream>
 #include <thread>
 #include <utility>
 
 #include "dart_component_controller.h"
 #include "dart_component_controller_v2.h"
+#include "dart_test_component_controller_v2.h"
+#include "flutter/fml/command_line.h"
+#include "flutter/fml/logging.h"
 #include "flutter/fml/trace_event.h"
 #include "logging.h"
 #include "runtime/dart/utils/inlines.h"
+#include "runtime/dart/utils/program_metadata.h"
 #include "runtime/dart/utils/vmservice_object.h"
 #include "service_isolate.h"
 #include "third_party/dart/runtime/include/bin/dart_io_api.h"
 #include "third_party/tonic/dart_microtask_queue.h"
 #include "third_party/tonic/dart_state.h"
 
+// #include "fuchsia_pkg_url.h"
+
 #if defined(AOT_RUNTIME)
 extern "C" uint8_t _kDartVmSnapshotData[];
 extern "C" uint8_t _kDartVmSnapshotInstructions[];
 #endif
 
+namespace {
+struct ComponentArgs {
+  std::string legacy_url;
+  std::shared_ptr<sys::ServiceDirectory> test_component_svc;
+  fidl::InterfaceHandle<fuchsia::io::Directory> component_pkg;
+  std::vector<fuchsia::component::runner::ComponentNamespaceEntry> ns;
+};
+
+// fpromise::result<ComponentArgs, fuchsia::component::Error> GetComponentArgs(
+//     fuchsia::component::runner::ComponentStartInfo& start_info) {
+//   // component::FuchsiaPkgUrl url;
+//   // if (!url.Parse(start_info.resolved_url())) {
+//   //   // FX_LOGS(WARNING) << "cannot run test: " <<
+//   start_info.resolved_url()
+//   //   //                  << ", as we cannot parse url.";
+//   //   return fpromise::error(fuchsia::component::Error::INVALID_ARGUMENTS);
+//   // }
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS ";
+//   if (!start_info.program().has_entries()) {
+//     FML_LOG(INFO) << "cannot run test: " << start_info.resolved_url()
+//                   << ", as it has no program entry.";
+//     return fpromise::error(fuchsia::component::Error::INVALID_ARGUMENTS);
+//   }
+
+//   auto ns = std::move(*start_info.mutable_ns());
+//   // const std::string& legacy_manifest = it->value->str();
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 2";
+
+//   auto pkg_it = std::find_if(
+//       ns.begin(), ns.end(),
+//       [](fuchsia::component::runner::ComponentNamespaceEntry& entry) {
+//         return entry.path() == "/pkg";
+//       });
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 3";
+
+//   auto component_pkg = std::move(*pkg_it->mutable_directory());
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 4";
+
+//   // auto fd = fsl::OpenChannelAsFileDescriptor(component_pkg.TakeChannel());
+//   // fsl::SizedVmo vmo;
+//   // if (!fsl::VmoFromFilenameAt(fd.get(), legacy_manifest, &vmo)) {
+//   //   FX_LOGS(WARNING) << "cannot run test: " << start_info.resolved_url()
+//   //                    << ", as cannot read legacy manifest file.";
+//   //   return
+//   fpromise::error(fuchsia::component::Error::INSTANCE_CANNOT_START);
+//   // }
+
+//   // const uint64_t size = vmo.size();
+//   // std::string cmx_str(size, ' ');
+//   // auto status = vmo.vmo().read(cmx_str.data(), 0, size);
+//   // if (status != ZX_OK) {
+//   //   FX_LOGS(WARNING) << "cannot run test: " << start_info.resolved_url()
+//   //                    << ", as cannot read legacy manifest file: "
+//   //                    << zx_status_get_string(status) << ".";
+//   //   return
+//   fpromise::error(fuchsia::component::Error::INSTANCE_CANNOT_START);
+//   // }
+//   // auto legacy_url = url.package_path() + "#" + legacy_manifest;
+
+//   auto legacy_url = start_info.resolved_url();
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 5";
+//   auto svc_it = std::find_if(
+//       ns.begin(), ns.end(),
+//       [](fuchsia::component::runner::ComponentNamespaceEntry& entry) {
+//         return entry.path() == "/svc";
+//       });
+
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 6";
+
+//   auto component_svc = std::make_shared<sys::ServiceDirectory>(
+//       std::move(*svc_it->mutable_directory()));
+//   FML_LOG(INFO) << "DART RUNNER : GET COMPONENT ARGS 7";
+//   return fpromise::ok(
+//       ComponentArgs{.legacy_url = std::move(legacy_url),
+//                     .test_component_svc = std::move(component_svc),
+//                     .component_pkg = std::move(component_pkg),
+//                     .ns = std::move(ns)});
+// }
+
+}  // namespace
+
 namespace dart_runner {
 
 namespace {
@@ -152,6 +240,39 @@
   }
 }
 
+void RunTestApplicationV2(
+    DartRunner* runner,
+    fuchsia::component::runner::ComponentStartInfo start_info,
+    std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
+    fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
+        controller,
+    fit::function<void(std::unique_ptr<DartTestComponentControllerV2>)>
+        create_callback,
+    fit::function<void(DartTestComponentControllerV2*)> done_callback) {
+  const int64_t start = Dart_TimelineGetMicros();
+
+  auto test_component = std::make_unique<DartTestComponentControllerV2>(
+      std::move(start_info), runner_incoming_services, std::move(controller),
+      std::move(done_callback));
+
+  // DartTestComponentControllerV2 app(
+  //     std::move(start_info), runner_incoming_services,
+  //     std::move(controller));
+  test_component->SetUp();
+  // const bool success = app.SetUp();
+  FML_LOG(INFO) << "DART COMPONENT SET UP: ";
+  const int64_t end = Dart_TimelineGetMicros();
+  Dart_TimelineEvent("DartTestComponentControllerV2::Setup", start, end,
+                     Dart_Timeline_Event_Duration, 0, NULL, NULL);
+  create_callback(std::move(test_component));
+
+  // app.Run();
+
+  if (Dart_CurrentIsolate()) {
+    Dart_ShutdownIsolate();
+  }
+}
+
 bool EntropySource(uint8_t* buffer, intptr_t count) {
   zx_cprng_draw(buffer, count);
   return true;
@@ -159,6 +280,86 @@
 
 }  // namespace
 
+// "data" and "assets" are arguments that are specific to the Flutter runner.
+// They will likely go away if we migrate to the ELF runner.
+constexpr char kDataKey[] = "data";
+constexpr char kAssetsKey[] = "assets";
+
+// "args" are how the component specifies arguments to the runner.
+constexpr char kArgsKey[] = "args";
+constexpr char kOldGenHeapSizeKey[] = "old_gen_heap_size";
+constexpr char kExposeDirsKey[] = "expose_dirs";
+
+// constexpr char kTmpPath[] = "/tmp";
+// constexpr char kServiceRootPath[] = "/svc";
+// constexpr char kRunnerConfigPath[] = "/config/data/flutter_runner_config";
+
+/// Parses the |args| field from the "program" field into
+/// |metadata|.
+void ParseArgs(std::vector<std::string>& args, ProgramMetadata* metadata) {
+  // fml::CommandLine expects the first argument to be the name of the program,
+  // so we prepend a dummy argument so we can use fml::CommandLine to parse the
+  // arguments for us.
+  std::vector<std::string> command_line_args = {""};
+  command_line_args.insert(command_line_args.end(), args.begin(), args.end());
+  fml::CommandLine parsed_args = fml::CommandLineFromIterators(
+      command_line_args.begin(), command_line_args.end());
+
+  std::string is_test;
+  if (parsed_args.GetOptionValue("is_test", &is_test)) {
+    // FML_LOG(INFO) << "HIT";
+    // FML_LOG(INFO) << is_test;
+    metadata->is_test = is_test == "true";
+  }
+
+  std::string old_gen_heap_size_option;
+  if (parsed_args.GetOptionValue(kOldGenHeapSizeKey,
+                                 &old_gen_heap_size_option)) {
+    int64_t specified_old_gen_heap_size = strtol(
+        old_gen_heap_size_option.c_str(), nullptr /* endptr */, 10 /* base */);
+    if (specified_old_gen_heap_size != 0) {
+      metadata->old_gen_heap_size = specified_old_gen_heap_size;
+    } else {
+      // FML_LOG(ERROR) << "Invalid old_gen_heap_size: "
+      //                << old_gen_heap_size_option;
+    }
+  }
+
+  std::string expose_dirs_option;
+  if (parsed_args.GetOptionValue(kExposeDirsKey, &expose_dirs_option)) {
+    // Parse the comma delimited string
+    std::vector<std::string> expose_dirs;
+    std::stringstream s(expose_dirs_option);
+    while (s.good()) {
+      std::string dir;
+      getline(s, dir, ',');  // get first string delimited by comma
+      metadata->expose_dirs.push_back(dir);
+    }
+  }
+}
+
+ProgramMetadata ParseProgramMetadata(
+    const fuchsia::data::Dictionary& program_metadata) {
+  ProgramMetadata result;
+
+  for (const auto& entry : program_metadata.entries()) {
+    if (entry.key.compare(kDataKey) == 0 && entry.value != nullptr) {
+      result.data_path = "pkg/" + entry.value->str();
+    } else if (entry.key.compare(kAssetsKey) == 0 && entry.value != nullptr) {
+      result.assets_path = "pkg/" + entry.value->str();
+    } else if (entry.key.compare(kArgsKey) == 0 && entry.value != nullptr) {
+      ParseArgs(entry.value->str_vec(), &result);
+    }
+  }
+
+  // assets_path defaults to the same as data_path if omitted.
+  if (result.assets_path.empty()) {
+    result.assets_path = result.data_path;
+  }
+
+  return result;
+}
+
 DartRunner::DartRunner(sys::ComponentContext* context) : context_(context) {
   context_->outgoing()->AddPublicService<fuchsia::sys::Runner>(
       [this](fidl::InterfaceRequest<fuchsia::sys::Runner> request) {
@@ -236,6 +437,7 @@
   // move or change, so we make a copy to pass to TRACE_DURATION.
   // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments
   // eagerly.
+  FML_LOG(INFO) << "RESOLVED URL V1: " << package.resolved_url;
   std::string url_copy = package.resolved_url;
   TRACE_EVENT1("dart", "StartComponent", "url", url_copy.c_str());
   std::thread thread(RunApplicationV1, this, std::move(package),
@@ -248,11 +450,36 @@
     fuchsia::component::runner::ComponentStartInfo start_info,
     fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
         controller) {
-  std::string url_copy = start_info.resolved_url();
-  TRACE_EVENT1("dart", "Start", "url", url_copy.c_str());
-  std::thread thread(RunApplicationV2, this, std::move(start_info),
-                     context_->svc(), std::move(controller));
-  thread.detach();
+  ProgramMetadata metadata = ParseProgramMetadata(start_info.program());
+  FML_LOG(INFO) << "RESOLVED URL V2: " << start_info.resolved_url();
+
+  if (metadata.is_test) {
+    FML_LOG(INFO) << "RESOLVED URL: " << start_info.resolved_url();
+
+    std::string url_copy = start_info.resolved_url();
+    TRACE_EVENT1("dart", "Start", "url", url_copy.c_str());
+    std::thread thread(
+        RunTestApplicationV2, this, std::move(start_info), context_->svc(),
+        std::move(controller),
+        [this](std::unique_ptr<DartTestComponentControllerV2> ptr) {
+          FML_LOG(INFO) << "TEST COMPONENT SAVED";
+          test_components_.emplace(ptr.get(), std::move(ptr));
+        },
+        [this](DartTestComponentControllerV2* ptr) {
+          FML_LOG(INFO) << "TEST COMPONENT ERASED";
+          auto it = test_components_.find(ptr);
+          if (it != test_components_.end()) {
+            test_components_.erase(it);
+          }
+        });
+    thread.detach();
+  } else {
+    std::string url_copy = start_info.resolved_url();
+    TRACE_EVENT1("dart", "Start", "url", url_copy.c_str());
+    std::thread thread(RunApplicationV2, this, std::move(start_info),
+                       context_->svc(), std::move(controller));
+    thread.detach();
+  }
 }
 
 }  // namespace dart_runner
diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.h b/shell/platform/fuchsia/dart_runner/dart_runner.h
index ce47eec..5688ca5 100644
--- a/shell/platform/fuchsia/dart_runner/dart_runner.h
+++ b/shell/platform/fuchsia/dart_runner/dart_runner.h
@@ -10,7 +10,9 @@
 #include <lib/fidl/cpp/binding_set.h>
 #include <lib/sys/cpp/component_context.h>
 
+#include "dart_test_component_controller_v2.h"
 #include "runtime/dart/utils/mapped_resource.h"
+// #include "test_component.h"
 
 namespace dart_runner {
 
@@ -33,12 +35,18 @@
       fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
           controller) override;
 
+  std::map<DartTestComponentControllerV2*,
+           std::shared_ptr<DartTestComponentControllerV2>>
+      test_components_;
+
   // Not owned by DartRunner.
   sys::ComponentContext* context_;
   fidl::BindingSet<fuchsia::sys::Runner> bindings_;
   fidl::BindingSet<fuchsia::component::runner::ComponentRunner>
       component_runner_bindings_;
 
+  std::shared_ptr<sys::ServiceDirectory> svc_;
+
 #if !defined(AOT_RUNTIME)
   dart_utils::MappedResource vm_snapshot_data_;
   dart_utils::MappedResource vm_snapshot_instructions_;
diff --git a/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc
new file mode 100644
index 0000000..30136a7
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.cc
@@ -0,0 +1,690 @@
+// Copyright 2013 The Flutter 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 "dart_test_component_controller_v2.h"
+
+#include <fcntl.h>
+#include <fml/logging.h>
+#include <fuchsia/sys/test/cpp/fidl.h>
+#include <lib/async-loop/loop.h>
+#include <lib/async/cpp/task.h>
+#include <lib/async/default.h>
+#include <lib/fdio/directory.h>
+#include <lib/fdio/fd.h>
+#include <lib/fdio/namespace.h>
+#include <lib/fidl/cpp/string.h>
+#include <lib/fpromise/barrier.h>
+#include <lib/sys/cpp/service_directory.h>
+#include <lib/syslog/global.h>
+#include <lib/zx/clock.h>
+#include <lib/zx/thread.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <zircon/status.h>
+
+#include <regex>
+#include <utility>
+
+#include "runtime/dart/utils/files.h"
+#include "runtime/dart/utils/handle_exception.h"
+#include "runtime/dart/utils/inlines.h"
+#include "runtime/dart/utils/tempfs.h"
+#include "third_party/dart/runtime/include/dart_tools_api.h"
+#include "third_party/tonic/converter/dart_converter.h"
+#include "third_party/tonic/dart_message_handler.h"
+#include "third_party/tonic/dart_microtask_queue.h"
+#include "third_party/tonic/dart_state.h"
+#include "third_party/tonic/logging/dart_error.h"
+#include "third_party/tonic/logging/dart_invoke.h"
+
+#include "builtin_libraries.h"
+#include "logging.h"
+
+using tonic::ToDart;
+
+namespace dart_runner {
+
+namespace {
+
+constexpr char kTmpPath[] = "/tmp";
+
+constexpr zx::duration kIdleWaitDuration = zx::sec(2);
+constexpr zx::duration kIdleNotifyDuration = zx::msec(500);
+constexpr zx::duration kIdleSlack = zx::sec(1);
+
+void AfterTask(async_loop_t*, void*) {
+  tonic::DartMicrotaskQueue* queue =
+      tonic::DartMicrotaskQueue::GetForCurrentThread();
+  // Verify that the queue exists, as this method could have been called back as
+  // part of the exit routine, after the destruction of the microtask queue.
+  if (queue) {
+    queue->RunMicrotasks();
+  }
+}
+
+constexpr async_loop_config_t kLoopConfig = {
+    .default_accessors =
+        {
+            .getter = async_get_default_dispatcher,
+            .setter = async_set_default_dispatcher,
+        },
+    .make_default_for_current_thread = true,
+    .epilogue = &AfterTask,
+};
+
+// Find the last path of the component.
+// fuchsia-pkg://fuchsia.com/hello_dart#meta/hello_dart.cmx -> hello_dart.cmx
+std::string GetLabelFromUrl(const std::string& url) {
+  for (size_t i = url.length() - 1; i > 0; i--) {
+    if (url[i] == '/') {
+      return url.substr(i + 1, url.length() - 1);
+    }
+  }
+  return url;
+}
+
+// Find the name of the component.
+// fuchsia-pkg://fuchsia.com/hello_dart#meta/hello_dart.cm -> hello_dart
+std::string GetComponentNameFromUrl(const std::string& url) {
+  const std::string label = GetLabelFromUrl(url);
+  for (size_t i = 0; i < label.length(); ++i) {
+    if (label[i] == '.') {
+      return label.substr(0, i);
+    }
+  }
+  return label;
+}
+
+}  // namespace
+
+DartTestComponentControllerV2::DartTestComponentControllerV2(
+    fuchsia::component::runner::ComponentStartInfo start_info,
+    std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
+    fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
+        controller,
+    DoneCallback done_callback)
+    : loop_(new async::Loop(&kLoopConfig)),
+      label_(GetLabelFromUrl(start_info.resolved_url())),
+      url_(std::move(start_info.resolved_url())),
+      runner_incoming_services_(runner_incoming_services),
+      start_info_(std::move(start_info)),
+      binding_(this),
+      done_callback_(std::move(done_callback)) {
+  // TODO(fxb/84537): This data path is configured based how we build Flutter
+  // applications in tree currently, but the way we build the Flutter
+  // application may change. We should avoid assuming the data path and let the
+  // CML file specify this data path instead.
+  const std::string component_name = GetComponentNameFromUrl(url_);
+  FX_LOGF(INFO, LOG_TAG, "Component Name from URL: %s.",
+          component_name.c_str());
+  data_path_ = "pkg/data/" + component_name;
+
+  if (controller.is_valid()) {
+    binding_.Bind(std::move(controller));
+    binding_.set_error_handler([this](zx_status_t status) { Kill(); });
+  } else {
+    FX_LOG(ERROR, LOG_TAG,
+           "Fuchsia component controller endpoint is not valid.");
+  }
+}
+
+DartTestComponentControllerV2::~DartTestComponentControllerV2() {
+  if (namespace_) {
+    fdio_ns_destroy(namespace_);
+    namespace_ = nullptr;
+  }
+  close(stdout_fd_);
+  close(stderr_fd_);
+}
+
+void DartTestComponentControllerV2::SetUp() {
+  // Name the thread after the url of the component being launched.
+  zx::thread::self()->set_property(ZX_PROP_NAME, label_.c_str(), label_.size());
+  Dart_SetThreadName(label_.c_str());
+
+  if (!CreateAndBindNamespace()) {
+    return;
+  }
+
+  if (SetUpFromAppSnapshot()) {
+    FX_LOGF(INFO, LOG_TAG, "%s is running from an app snapshot", url_.c_str());
+  } else if (SetUpFromKernel()) {
+    FX_LOGF(INFO, LOG_TAG, "%s is running from kernel", url_.c_str());
+  } else {
+    FX_LOGF(ERROR, LOG_TAG, "Failed to set up component controller for %s.",
+            url_.c_str());
+    return;
+    // return false;
+  }
+
+  // return true;
+  // suite_ = std::make_unique<Suite>(url_, loop_);
+  suite_context_ = sys::ComponentContext::Create();
+  suite_context_->outgoing()->AddPublicService(
+      suite_bindings_.GetHandler(this, loop_->dispatcher()));
+  suite_context_->outgoing()->Serve(
+      start_info_.mutable_outgoing_dir()->TakeChannel(), loop_->dispatcher());
+}
+
+bool DartTestComponentControllerV2::CreateAndBindNamespace() {
+  if (!start_info_.has_ns()) {
+    FX_LOG(ERROR, LOG_TAG, "Component start info does not have a namespace.");
+    return false;
+  }
+
+  const zx_status_t ns_create_status = fdio_ns_create(&namespace_);
+  if (ns_create_status != ZX_OK) {
+    FX_LOGF(ERROR, LOG_TAG, "Failed to create namespace: %s",
+            zx_status_get_string(ns_create_status));
+  }
+
+  dart_utils::RunnerTemp::SetupComponent(namespace_);
+
+  // Bind each directory in start_info's namespace to the controller's namespace
+  // instance.
+  for (auto& ns_entry : *start_info_.mutable_ns()) {
+    // TODO(akbiggs): Under what circumstances does a namespace entry not
+    // have a path or directory? Should we log an error for these?
+    if (!ns_entry.has_path() || !ns_entry.has_directory()) {
+      continue;
+    }
+
+    if (ns_entry.path() == kTmpPath) {
+      // /tmp is covered by the local memfs.
+      continue;
+    }
+
+    // We move ownership of the directory & path since RAII is used to keep
+    // the handle open.
+    fidl::InterfaceHandle<::fuchsia::io::Directory> dir =
+        std::move(*ns_entry.mutable_directory());
+    const std::string path = std::move(*ns_entry.mutable_path());
+
+    const zx_status_t ns_bind_status =
+        fdio_ns_bind(namespace_, path.c_str(), dir.TakeChannel().release());
+    if (ns_bind_status != ZX_OK) {
+      FX_LOGF(ERROR, LOG_TAG, "Failed to bind %s to namespace: %s",
+              path.c_str(), zx_status_get_string(ns_bind_status));
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool DartTestComponentControllerV2::SetUpFromKernel() {
+  dart_utils::MappedResource manifest;
+  if (!dart_utils::MappedResource::LoadFromNamespace(
+          namespace_, data_path_ + "/app.dilplist", manifest)) {
+    return false;
+  }
+
+  if (!dart_utils::MappedResource::LoadFromNamespace(
+          nullptr, "/pkg/data/isolate_core_snapshot_data.bin",
+          isolate_snapshot_data_)) {
+    return false;
+  }
+  if (!dart_utils::MappedResource::LoadFromNamespace(
+          nullptr, "/pkg/data/isolate_core_snapshot_instructions.bin",
+          isolate_snapshot_instructions_, true /* executable */)) {
+    return false;
+  }
+
+  if (!CreateIsolate(isolate_snapshot_data_.address(),
+                     isolate_snapshot_instructions_.address())) {
+    return false;
+  }
+
+  Dart_EnterScope();
+
+  std::string str(reinterpret_cast<const char*>(manifest.address()),
+                  manifest.size());
+  Dart_Handle library = Dart_Null();
+  for (size_t start = 0; start < manifest.size();) {
+    size_t end = str.find("\n", start);
+    if (end == std::string::npos) {
+      FX_LOG(ERROR, LOG_TAG, "Malformed manifest");
+      Dart_ExitScope();
+      return false;
+    }
+
+    std::string path = data_path_ + "/" + str.substr(start, end - start);
+    start = end + 1;
+
+    dart_utils::MappedResource kernel;
+    if (!dart_utils::MappedResource::LoadFromNamespace(namespace_, path,
+                                                       kernel)) {
+      FX_LOGF(ERROR, LOG_TAG, "Cannot load kernel from namespace: %s",
+              path.c_str());
+      Dart_ExitScope();
+      return false;
+    }
+    library = Dart_LoadLibraryFromKernel(kernel.address(), kernel.size());
+    if (Dart_IsError(library)) {
+      FX_LOGF(ERROR, LOG_TAG, "Cannot load library from kernel: %s",
+              Dart_GetError(library));
+      Dart_ExitScope();
+      return false;
+    }
+
+    kernel_peices_.emplace_back(std::move(kernel));
+  }
+  Dart_SetRootLibrary(library);
+
+  Dart_Handle result = Dart_FinalizeLoading(false);
+  if (Dart_IsError(result)) {
+    FX_LOGF(ERROR, LOG_TAG, "Failed to FinalizeLoading: %s",
+            Dart_GetError(result));
+    Dart_ExitScope();
+    return false;
+  }
+
+  return true;
+}
+
+bool DartTestComponentControllerV2::SetUpFromAppSnapshot() {
+#if !defined(AOT_RUNTIME)
+  return false;
+#else
+  // Load the ELF snapshot as available, and fall back to a blobs snapshot
+  // otherwise.
+  const uint8_t *isolate_data, *isolate_instructions;
+  if (elf_snapshot_.Load(namespace_, data_path_ + "/app_aot_snapshot.so")) {
+    isolate_data = elf_snapshot_.IsolateData();
+    isolate_instructions = elf_snapshot_.IsolateInstrs();
+    if (isolate_data == nullptr || isolate_instructions == nullptr) {
+      return false;
+    }
+  } else {
+    if (!dart_utils::MappedResource::LoadFromNamespace(
+            namespace_, data_path_ + "/isolate_snapshot_data.bin",
+            isolate_snapshot_data_)) {
+      return false;
+    }
+    if (!dart_utils::MappedResource::LoadFromNamespace(
+            namespace_, data_path_ + "/isolate_snapshot_instructions.bin",
+            isolate_snapshot_instructions_, true /* executable */)) {
+      return false;
+    }
+  }
+  return CreateIsolate(isolate_data, isolate_instructions);
+#endif  // defined(AOT_RUNTIME)
+}
+
+bool DartTestComponentControllerV2::CreateIsolate(
+    const uint8_t* isolate_snapshot_data,
+    const uint8_t* isolate_snapshot_instructions) {
+  // Create the isolate from the snapshot.
+  char* error = nullptr;
+
+  // TODO(dart_runner): Pass if we start using tonic's loader.
+  intptr_t namespace_fd = -1;
+
+  // Freed in IsolateShutdownCallback.
+  auto state = new std::shared_ptr<tonic::DartState>(new tonic::DartState(
+      namespace_fd, [this](Dart_Handle result) { MessageEpilogue(result); }));
+
+  isolate_ = Dart_CreateIsolateGroup(
+      url_.c_str(), label_.c_str(), isolate_snapshot_data,
+      isolate_snapshot_instructions, nullptr /* flags */, state, state, &error);
+  if (!isolate_) {
+    FX_LOGF(ERROR, LOG_TAG, "Dart_CreateIsolateGroup failed: %s", error);
+    return false;
+  }
+
+  state->get()->SetIsolate(isolate_);
+
+  tonic::DartMessageHandler::TaskDispatcher dispatcher =
+      [loop = loop_.get()](auto callback) {
+        async::PostTask(loop->dispatcher(), std::move(callback));
+      };
+  state->get()->message_handler().Initialize(dispatcher);
+
+  state->get()->SetReturnCodeCallback(
+      [this](uint32_t return_code) { return_code_ = return_code; });
+
+  return true;
+}
+
+// void DartTestComponentControllerV2::Run() {
+//   async::PostTask(loop_->dispatcher(), [loop = loop_.get(), app = this] {
+//     if (!app->RunDartMain()) {
+//       loop->Quit();
+//     }
+//   });
+//   loop_->Run();
+
+//   if (binding_.is_bound()) {
+//     // From the documentation for ComponentController, ZX_OK should be sent
+//     when
+//     // the ComponentController receives a termination request. However, if
+//     the
+//     // component exited with a non-zero return code, we indicate this by
+//     sending
+//     // an INTERNAL epitaph instead.
+//     //
+//     // TODO(fxb/86666): Communicate return code from the ComponentController
+//     // once v2 has support.
+//     if (return_code_ == 0) {
+//       binding_.Close(ZX_OK);
+//     } else {
+//       FML_LOG(ERROR) << "Component exited with non-zero return code: "
+//                      << return_code_;
+//       binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL));
+//     }
+//   }
+// }
+
+// |fuchsia::test::Suite|
+DartTestComponentControllerV2::CaseIterator::CaseIterator(
+    fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request,
+    async_dispatcher_t* dispatcher,
+    fit::function<void(CaseIterator*)> done_callback)
+    : binding_(this, std::move(request), dispatcher),
+      done_callback_(std::move(done_callback)) {}
+
+static const char kTestCaseName[] = "dart_test";
+void DartTestComponentControllerV2::CaseIterator::GetNext(
+    GetNextCallback callback) {
+  FML_LOG(INFO) << "DartTestComponentControllerV2 GetNext() called";
+  if (get_next_call_count == 0) {
+    fuchsia::sys::test::Case test_case;
+    test_case.set_name(std::string(kTestCaseName));
+    test_case.set_enabled(true);
+    std::vector<fuchsia::sys::test::Case> cases;
+    cases.push_back(std::move(test_case));
+    callback(std::move(cases));
+    get_next_call_count++;
+
+  } else {
+    std::vector<fuchsia::sys::test::Case> cases;
+    callback(std::move(cases));
+    // this would be removed
+    done_callback_(this);
+  }
+}
+
+void DartTestComponentControllerV2::GetTests(
+    fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> iterator) {
+  FML_LOG(INFO) << "DART RUNNER HIT SUITE GetTests()";
+
+  auto case_iterator =
+      std::make_unique<CaseIterator>(std::move(iterator), loop_->dispatcher(),
+                                     [this](CaseIterator* case_iterator) {
+                                       RemoveCaseInterator(case_iterator);
+                                     });
+  case_iterators_.emplace(case_iterator.get(), std::move(case_iterator));
+}
+
+std::unique_ptr<DartTestComponentControllerV2::CaseIterator>
+DartTestComponentControllerV2::RemoveCaseInterator(
+    CaseIterator* case_iterator) {
+  auto it = case_iterators_.find(case_iterator);
+  std::unique_ptr<DartTestComponentControllerV2::CaseIterator>
+      case_iterator_ptr;
+  if (it != case_iterators_.end()) {
+    case_iterator_ptr = std::move(it->second);
+    case_iterators_.erase(it);
+  }
+  return case_iterator_ptr;
+}
+
+// |fuchsia::test::Suite|
+void DartTestComponentControllerV2::Run(
+    std::vector<fuchsia::sys::test::Invocation> tests,
+    fuchsia::sys::test::RunOptions options,
+    fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) {
+  FML_LOG(INFO) << "DART RUNNER HIT SUITE Run()";
+
+  std::vector<std::string> args;
+  if (options.has_arguments()) {
+    args = std::move(*options.mutable_arguments());
+  }
+
+  if (options.has_parallel()) {
+    FML_LOG(WARNING) << "Ignoring 'parallel'. Pass test specific flags, eg: "
+                        "for rust test pass in "
+                        "--test-threads="
+                     << options.parallel();
+  }
+
+  auto listener_proxy = listener.Bind();
+  fpromise::barrier barrier;
+  for (auto it = tests.begin(); it != tests.end(); it++) {
+    auto invocation = std::move(*it);
+    std::string test_case_name;
+    if (invocation.has_name()) {
+      test_case_name = invocation.name();
+    }
+    FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN - TEST CASE: "
+                  << test_case_name;
+    zx::socket out, err, out_client, err_client;
+    auto status = zx::socket::create(0, &out, &out_client);
+    if (status != ZX_OK) {
+      FML_LOG(FATAL) << "cannot create socket: "
+                     << zx_status_get_string(status);
+    }
+    status = zx::socket::create(0, &err, &err_client);
+    if (status != ZX_OK) {
+      FML_LOG(FATAL) << "cannot create socket: "
+                     << zx_status_get_string(status);
+    }
+
+    fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener;
+
+    fuchsia::sys::test::StdHandles std_handles;
+    std_handles.set_err(std::move(err_client));
+    std_handles.set_out(std::move(out_client));
+
+    listener_proxy->OnTestCaseStarted(std::move(invocation),
+                                      std::move(std_handles),
+                                      case_listener.NewRequest());
+
+    async::PostTask(loop_->dispatcher(), [loop = loop_.get(), app = this] {
+      if (!app->RunDartMain()) {
+        loop->Quit();
+      }
+    });
+    loop_->Run();
+
+    auto ret_status = return_code_ == 0 ? fuchsia::sys::test::Status::PASSED
+                                        : fuchsia::sys::test::Status::FAILED;
+    fuchsia::sys::test::Result result;
+    result.set_status(ret_status);
+    FML_LOG(INFO) << "Sending finished event for dart tests";
+    case_listener->Finished(std::move(result));
+  }
+
+  if (binding_.is_bound()) {
+    // From the documentation for ComponentController, ZX_OK should be sent when
+    // the ComponentController receives a termination request. However, if the
+    // component exited with a non-zero return code, we indicate this by sending
+    // an INTERNAL epitaph instead.
+    //
+    // TODO(fxb/86666): Communicate return code from the ComponentController
+    // once v2 has support.
+    if (return_code_ == 0) {
+      binding_.Close(ZX_OK);
+    } else {
+      FML_LOG(ERROR) << "Component exited with non-zero return code: "
+                     << return_code_;
+      binding_.Close(zx_status_t(fuchsia::component::Error::INTERNAL));
+    }
+  }
+}
+
+bool DartTestComponentControllerV2::RunDartMain() {
+  FML_CHECK(namespace_ != nullptr);
+  Dart_EnterScope();
+
+  tonic::DartMicrotaskQueue::StartForCurrentThread();
+
+  // TODO(fxb/88384): Create a file descriptor for each component that is
+  // launched and listen for anything that is written to the component. When
+  // something is written to the component, forward that message along to the
+  // Fuchsia logger and decorate it with the tag that it came from the
+  // component.
+  stdout_fd_ = fileno(stdout);
+  stderr_fd_ = fileno(stderr);
+
+  fidl::InterfaceRequest<fuchsia::io::Directory> outgoing_dir =
+      std::move(*start_info_.mutable_outgoing_dir());
+  FML_LOG(INFO) << "DART TEST COMPONENT CONTROLLER: RUNMAIN()";
+  FML_LOG(INFO) << url_;
+  InitBuiltinLibrariesForIsolate(
+      url_, namespace_, stdout_fd_, stderr_fd_, nullptr /* environment */,
+      outgoing_dir.TakeChannel(), false /* service_isolate */);
+
+  Dart_ExitScope();
+  Dart_ExitIsolate();
+  char* error = Dart_IsolateMakeRunnable(isolate_);
+  if (error != nullptr) {
+    Dart_EnterIsolate(isolate_);
+    Dart_ShutdownIsolate();
+    FX_LOGF(ERROR, LOG_TAG, "Unable to make isolate runnable: %s", error);
+    free(error);
+    return false;
+  }
+  Dart_EnterIsolate(isolate_);
+  Dart_EnterScope();
+
+  // TODO(fxb/88383): Support argument passing.
+  Dart_Handle corelib = Dart_LookupLibrary(ToDart("dart:core"));
+  Dart_Handle string_type =
+      Dart_GetNonNullableType(corelib, ToDart("String"), 0, NULL);
+  Dart_Handle dart_arguments =
+      Dart_NewListOfTypeFilled(string_type, Dart_EmptyString(), 0);
+
+  if (Dart_IsError(dart_arguments)) {
+    FX_LOGF(ERROR, LOG_TAG, "Failed to allocate Dart arguments list: %s",
+            Dart_GetError(dart_arguments));
+    Dart_ExitScope();
+    return false;
+  }
+
+  Dart_Handle user_main = Dart_GetField(Dart_RootLibrary(), ToDart("main"));
+
+  if (Dart_IsError(user_main)) {
+    FX_LOGF(ERROR, LOG_TAG,
+            "Failed to locate user_main in the root library: %s",
+            Dart_GetError(user_main));
+    Dart_ExitScope();
+    return false;
+  }
+
+  Dart_Handle fuchsia_lib = Dart_LookupLibrary(tonic::ToDart("dart:fuchsia"));
+
+  if (Dart_IsError(fuchsia_lib)) {
+    FX_LOGF(ERROR, LOG_TAG, "Failed to locate dart:fuchsia: %s",
+            Dart_GetError(fuchsia_lib));
+    Dart_ExitScope();
+    return false;
+  }
+
+  Dart_Handle main_result = tonic::DartInvokeField(
+      fuchsia_lib, "_runUserMainForDartRunner", {user_main, dart_arguments});
+
+  if (Dart_IsError(main_result)) {
+    auto dart_state = tonic::DartState::Current();
+    if (!dart_state->has_set_return_code()) {
+      // The program hasn't set a return code meaning this exit is unexpected.
+      FX_LOG(ERROR, LOG_TAG, Dart_GetError(main_result));
+      return_code_ = tonic::GetErrorExitCode(main_result);
+
+      dart_utils::HandleIfException(runner_incoming_services_, url_,
+                                    main_result);
+    }
+    Dart_ExitScope();
+    return false;
+  }
+
+  Dart_ExitScope();
+  return true;
+}
+
+void DartTestComponentControllerV2::Kill() {
+  FML_LOG(INFO) << "DartTestComponentControllerV2 KILL CALLED";
+  if (Dart_CurrentIsolate()) {
+    tonic::DartMicrotaskQueue* queue =
+        tonic::DartMicrotaskQueue::GetForCurrentThread();
+    if (queue) {
+      queue->Destroy();
+    }
+
+    loop_->Quit();
+
+    // TODO(rosswang): The docs warn of threading issues if doing this again,
+    // but without this, attempting to shut down the isolate finalizes app
+    // contexts that can't tell a shutdown is in progress and so fatal.
+    Dart_SetMessageNotifyCallback(nullptr);
+
+    Dart_ShutdownIsolate();
+  }
+  done_callback_(this);
+}
+
+void DartTestComponentControllerV2::MessageEpilogue(Dart_Handle result) {
+  auto dart_state = tonic::DartState::Current();
+  // If the Dart program has set a return code, then it is intending to shut
+  // down by way of a fatal error, and so there is no need to override
+  // return_code_.
+  if (dart_state->has_set_return_code()) {
+    Dart_ShutdownIsolate();
+    return;
+  }
+
+  dart_utils::HandleIfException(runner_incoming_services_, url_, result);
+
+  // Otherwise, see if there was any other error.
+  return_code_ = tonic::GetErrorExitCode(result);
+  if (return_code_ != 0) {
+    Dart_ShutdownIsolate();
+    return;
+  }
+
+  idle_start_ = zx::clock::get_monotonic();
+  zx_status_t status =
+      idle_timer_.set(idle_start_ + kIdleWaitDuration, kIdleSlack);
+  if (status != ZX_OK) {
+    FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s",
+            zx_status_get_string(status));
+  }
+}
+
+void DartTestComponentControllerV2::Stop() {
+  Kill();
+}
+
+void DartTestComponentControllerV2::OnIdleTimer(
+    async_dispatcher_t* dispatcher,
+    async::WaitBase* wait,
+    zx_status_t status,
+    const zx_packet_signal* signal) {
+  if ((status != ZX_OK) || !(signal->observed & ZX_TIMER_SIGNALED) ||
+      !Dart_CurrentIsolate()) {
+    // Timer closed or isolate shutdown.
+    return;
+  }
+
+  zx::time deadline = idle_start_ + kIdleWaitDuration;
+  zx::time now = zx::clock::get_monotonic();
+  if (now >= deadline) {
+    // No Dart message has been processed for kIdleWaitDuration: assume we'll
+    // stay idle for kIdleNotifyDuration.
+    Dart_NotifyIdle((now + kIdleNotifyDuration).get());
+    idle_start_ = zx::time(0);
+    idle_timer_.cancel();  // De-assert signal.
+  } else {
+    // Early wakeup or message pushed idle time forward: reschedule.
+    zx_status_t status = idle_timer_.set(deadline, kIdleSlack);
+    if (status != ZX_OK) {
+      FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s",
+              zx_status_get_string(status));
+    }
+  }
+  wait->Begin(dispatcher);  // ignore errors
+}
+
+}  // namespace dart_runner
diff --git a/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h
new file mode 100644
index 0000000..c15d498
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/dart_test_component_controller_v2.h
@@ -0,0 +1,166 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_
+
+#include <memory>
+
+#include <fuchsia/component/runner/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/sys/test/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async/cpp/wait.h>
+#include <lib/fdio/namespace.h>
+#include <lib/sys/cpp/component_context.h>
+#include <lib/sys/cpp/service_directory.h>
+#include <lib/zx/timer.h>
+
+#include <lib/fidl/cpp/binding_set.h>
+#include "lib/fidl/cpp/binding.h"
+#include "runtime/dart/utils/mapped_resource.h"
+#include "third_party/dart/runtime/include/dart_api.h"
+
+// #include "suite.h"
+namespace dart_runner {
+
+// struct TestComponentArgs {
+//   std::string legacy_url;
+//   zx::channel outgoing_dir;
+//   fuchsia::sys::EnvironmentPtr parent_env;
+//   std::shared_ptr<sys::ServiceDirectory> parent_env_svc;
+//   std::shared_ptr<sys::ServiceDirectory> test_component_svc;
+
+//   async_dispatcher_t* dispatcher;
+// };
+
+/// Starts a Dart component written in CFv2.
+class DartTestComponentControllerV2
+    : public fuchsia::component::runner::ComponentController,
+      public fuchsia::sys::test::Suite {
+  using DoneCallback = fit::function<void(DartTestComponentControllerV2*)>;
+
+ public:
+  DartTestComponentControllerV2(
+      fuchsia::component::runner::ComponentStartInfo start_info,
+      std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
+      fidl::InterfaceRequest<fuchsia::component::runner::ComponentController>
+          controller,
+      DoneCallback done_callback);
+
+  ~DartTestComponentControllerV2() override;
+
+  /// Sets up the controller.
+  ///
+  /// This should be called before |Run|.
+  void SetUp();
+
+  /// Runs the Dart component in a task, sending the return code back to
+  /// the Fuchsia component controller.
+  ///
+  /// This should be called after |SetUp|.
+  //   void Run();
+
+  /// |Suite| protocol implementation.
+  void GetTests(fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator>
+                    iterator) override;
+
+  /// |Suite| protocol implementation.
+  void Run(
+      std::vector<fuchsia::sys::test::Invocation> tests,
+      fuchsia::sys::test::RunOptions options,
+      fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) override;
+
+ private:
+  /// Helper for actually running the Dart main. Returns true if successful,
+  /// false otherwise.
+  bool RunDartMain();
+
+  /// Creates and binds the namespace for this component. Returns true if
+  /// successful, false otherwise.
+  bool CreateAndBindNamespace();
+
+  bool SetUpFromKernel();
+  bool SetUpFromAppSnapshot();
+
+  bool CreateIsolate(const uint8_t* isolate_snapshot_data,
+                     const uint8_t* isolate_snapshot_instructions);
+
+  // |ComponentController|
+  void Kill() override;
+  void Stop() override;
+
+  // Idle notification.
+  void MessageEpilogue(Dart_Handle result);
+  void OnIdleTimer(async_dispatcher_t* dispatcher,
+                   async::WaitBase* wait,
+                   zx_status_t status,
+                   const zx_packet_signal* signal);
+
+  class CaseIterator final : public fuchsia::sys::test::CaseIterator {
+   public:
+    CaseIterator(
+        fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request,
+        async_dispatcher_t* dispatcher,
+        fit::function<void(CaseIterator*)> done_callback);
+
+    void GetNext(GetNextCallback callback) override;
+
+   private:
+    int get_next_call_count = 0;
+    fidl::Binding<fuchsia::sys::test::CaseIterator> binding_;
+    fit::function<void(CaseIterator*)> done_callback_;
+  };
+
+  std::unique_ptr<CaseIterator> RemoveCaseInterator(CaseIterator*);
+
+  // |Suite|
+  // async_dispatcher_t* dispatcher_;
+  std::unique_ptr<Suite> suite_;
+  std::map<CaseIterator*, std::unique_ptr<CaseIterator>> case_iterators_;
+  /// Exposes suite protocol on behalf of test component.
+  std::unique_ptr<sys::ComponentContext> suite_context_;
+  fidl::BindingSet<fuchsia::sys::test::Suite> suite_bindings_;
+
+  // The loop must be the first declared member so that it gets destroyed after
+  // binding_ which expects the existence of a loop.
+  std::unique_ptr<async::Loop> loop_;
+
+  std::string label_;
+  std::string url_;
+  std::shared_ptr<sys::ServiceDirectory> runner_incoming_services_;
+  std::string data_path_;
+  std::unique_ptr<sys::ComponentContext> context_;
+
+  fuchsia::component::runner::ComponentStartInfo start_info_;
+  fidl::Binding<fuchsia::component::runner::ComponentController> binding_;
+  DoneCallback done_callback_;
+
+  fdio_ns_t* namespace_ = nullptr;
+  int stdout_fd_ = -1;
+  int stderr_fd_ = -1;
+
+  dart_utils::ElfSnapshot elf_snapshot_;                      // AOT snapshot
+  dart_utils::MappedResource isolate_snapshot_data_;          // JIT snapshot
+  dart_utils::MappedResource isolate_snapshot_instructions_;  // JIT snapshot
+  std::vector<dart_utils::MappedResource> kernel_peices_;
+
+  Dart_Isolate isolate_;
+  int32_t return_code_ = 0;
+
+  zx::time idle_start_{0};
+  zx::timer idle_timer_;
+  async::WaitMethod<DartTestComponentControllerV2,
+                    &DartTestComponentControllerV2::OnIdleTimer>
+      idle_wait_{this};
+
+  // Disallow copy and assignment.
+  DartTestComponentControllerV2(const DartTestComponentControllerV2&) = delete;
+  DartTestComponentControllerV2& operator=(
+      const DartTestComponentControllerV2&) = delete;
+};
+
+}  // namespace dart_runner
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_DART_TEST_COMPONENT_CONTROLLER_V2_H_
diff --git a/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc
new file mode 100644
index 0000000..8ac5ad2
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.cc
@@ -0,0 +1,122 @@
+// 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 "fuchsia_pkg_url.h"
+
+// #include <re2/re2.h>
+#include <regex>
+#include <string>
+#include <string_view>
+
+namespace component {
+
+// Subsitutue functions copied from fxl.
+static std::string SubstituteWithArray(std::string_view format,
+                                       std::string_view* args,
+                                       size_t nargs);
+
+std::string Substitute(std::string_view format, std::string_view arg0) {
+  std::array<std::string_view, 1> arr = {arg0};
+  return SubstituteWithArray(format, arr.data(), arr.size());
+}
+
+std::string Substitute(std::string_view format,
+                       std::string_view arg0,
+                       std::string_view arg1) {
+  std::array<std::string_view, 2> arr = {arg0, arg1};
+  return SubstituteWithArray(format, arr.data(), arr.size());
+}
+
+std::string Substitute(std::string_view format,
+                       std::string_view arg0,
+                       std::string_view arg1,
+                       std::string_view arg2) {
+  std::array<std::string_view, 3> arr = {arg0, arg1, arg2};
+  return SubstituteWithArray(format, arr.data(), arr.size());
+}
+
+static const std::string kFuchsiaPkgPrefix = "fuchsia-pkg://";
+
+// kFuchsiaPkgRexp has the following group matches:
+// 1: user/domain/port/etc (everything after scheme, before path)
+// 2: package name
+// 3: package variant
+// 4: package merkle-root hash
+// 5: resource path
+static const std::regex* kFuchsiaPkgRexp = std::regex(
+    "^fuchsia-pkg://([^/]+)/([^/#?]+)(?:/([^/"
+    "#?]+))?(?:\\?hash=([^&#]+))?(?:#(.+))?$");
+
+// static
+bool FuchsiaPkgUrl::IsFuchsiaPkgScheme(const std::string& url) {
+  return url.compare(0, kFuchsiaPkgPrefix.length(), kFuchsiaPkgPrefix) == 0;
+}
+
+std::string FuchsiaPkgUrl::GetDefaultComponentCmxPath() const {
+  return fxl::Substitute("meta/$0.cmx", package_name());
+}
+
+bool FuchsiaPkgUrl::Parse(const std::string& url) {
+  package_name_.clear();
+  resource_path_.clear();
+
+  // if (!re2::RE2::FullMatch(url, *kFuchsiaPkgRexp, &host_name_,
+  // &package_name_,
+  //                          &variant_, &hash_, &resource_path_)) {
+  //   return false;
+  // }
+
+  char* cstr = new char[url.size()];
+  std::copy(url.begin(), url.end(), cstr);
+
+  std::cmatch cm;
+  bool full_match = std::regex_match(cstr, cm, kFuchsiaPkgRexp);
+  if (!full_match) {
+    return false;
+  }
+
+  host_name_ = cm[1];
+  package_name_ = cm[2];
+  variant_ = cm[3];
+  hash_ = cm[4];
+  resource_path_ = cm[5];
+
+  url_ = url;
+
+  if (variant_.empty()) {
+    // TODO(fxbug.dev/4002): Currently this defaults to "0" if not present, but
+    // variant will eventually be required in fuchsia-pkg URLs.
+    variant_ = "0";
+  }
+
+  return true;
+}
+
+bool FuchsiaPkgUrl::operator==(const FuchsiaPkgUrl& rhs) const {
+  return (this->host_name() == rhs.host_name() &&
+          this->package_name() == rhs.package_name() &&
+          this->variant() == rhs.variant() &&
+          this->resource_path() == rhs.resource_path() &&
+          this->hash() == rhs.hash());
+}
+
+std::string FuchsiaPkgUrl::pkgfs_dir_path() const {
+  return fxl::Substitute("/pkgfs/packages/$0/$1", package_name_, variant_);
+}
+
+std::string FuchsiaPkgUrl::package_path() const {
+  std::string query = "";
+  if (!hash_.empty()) {
+    query = fxl::Substitute("?hash=$0", hash_);
+  }
+
+  return fxl::Substitute("fuchsia-pkg://$0/$1/$2$3", host_name_, package_name_,
+                         variant_, query);
+}
+
+const std::string& FuchsiaPkgUrl::ToString() const {
+  return url_;
+}
+
+}  // namespace component
diff --git a/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h
new file mode 100644
index 0000000..8a05ebe
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/fuchsia_pkg_url.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_
+
+#include <string>
+
+// #include "src/lib/fxl/macros.h"
+
+namespace component {
+
+class FuchsiaPkgUrl {
+ public:
+  FuchsiaPkgUrl() = default;
+  FuchsiaPkgUrl(const FuchsiaPkgUrl&) = default;
+  FuchsiaPkgUrl& operator=(const FuchsiaPkgUrl&) = default;
+  FuchsiaPkgUrl(FuchsiaPkgUrl&&) = default;
+  FuchsiaPkgUrl& operator=(FuchsiaPkgUrl&&) = default;
+
+  static bool IsFuchsiaPkgScheme(const std::string& url);
+
+  // Returns the default component's .cmx path of this package, i.e.
+  // meta/<package_name>.cmx
+  std::string GetDefaultComponentCmxPath() const;
+
+  bool Parse(const std::string& url);
+
+  bool operator==(const FuchsiaPkgUrl&) const;
+  bool operator!=(const FuchsiaPkgUrl& rhs) const { return !(*this == rhs); }
+
+  const std::string& host_name() const { return host_name_; }
+  const std::string& package_name() const { return package_name_; }
+  const std::string& variant() const { return variant_; }
+  const std::string& hash() const { return hash_; }
+  const std::string& resource_path() const { return resource_path_; }
+  std::string package_path() const;
+  std::string pkgfs_dir_path() const;
+
+  const std::string& ToString() const;
+
+ private:
+  std::string url_;
+  std::string host_name_;
+  std::string package_name_;
+  std::string variant_;
+  std::string hash_;
+  std::string resource_path_;
+};
+
+}  // namespace component
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_FUCHSIA_PKG_URL_H_
diff --git a/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml b/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml
index 2552f7e..545ed80 100644
--- a/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml
+++ b/shell/platform/fuchsia/dart_runner/meta/dart_jit_runner.cml
@@ -17,6 +17,14 @@
             path: "/svc/fuchsia.component.runner.ComponentRunner",
         },
     ],
+    use: [
+        {
+            protocol: [
+                "fuchsia.sys.Environment",
+                "fuchsia.sys.Loader",
+            ],
+        },
+    ],
     expose: [
         {
             runner: "dart_jit_runner",
diff --git a/shell/platform/fuchsia/dart_runner/string_printf.cc b/shell/platform/fuchsia/dart_runner/string_printf.cc
new file mode 100644
index 0000000..801f54e
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/string_printf.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 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 "string_printf.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include <memory>
+
+namespace fxl {
+namespace {
+
+void StringVAppendfHelper(std::string* dest, const char* format, va_list ap) {
+  // Size of the small stack buffer to use first. This should be kept in sync
+  // with the numbers in StringPrintfTest.StringPrintf_Boundary.
+  constexpr size_t kStackBufferSize = 1024u;
+
+  // First, try with a small buffer on the stack.
+  char stack_buf[kStackBufferSize];
+  // Copy |ap| (which can only be used once), in case we need to retry.
+  va_list ap_copy;
+  va_copy(ap_copy, ap);
+  int result = vsnprintf(stack_buf, kStackBufferSize, format, ap_copy);
+  va_end(ap_copy);
+  if (result < 0) {
+    // As far as I can tell, we'd only get |EOVERFLOW| if the result is so large
+    // that it can't be represented by an |int| (in which case retrying would be
+    // futile), so Chromium's implementation is wrong.
+    return;
+  }
+  // |result| should be the number of characters we need, not including the
+  // terminating null. However, |vsnprintf()| always null-terminates!
+  size_t output_size = static_cast<size_t>(result);
+  // Check if the output fit into our stack buffer. This is "<" not "<=", since
+  // |vsnprintf()| will null-terminate.
+  if (output_size < kStackBufferSize) {
+    // It fit.
+    dest->append(stack_buf, static_cast<size_t>(result));
+    return;
+  }
+
+  // Since we have the required output size, we can just heap allocate that.
+  // (Add 1 because |vsnprintf()| will always null-terminate.)
+  size_t heap_buf_size = output_size + 1u;
+  std::unique_ptr<char[]> heap_buf(new char[heap_buf_size]);
+  result = vsnprintf(heap_buf.get(), heap_buf_size, format, ap);
+  if (result < 0 || static_cast<size_t>(result) > output_size) {
+    assert(false);
+    return;
+  }
+  assert(static_cast<size_t>(result) == output_size);
+  dest->append(heap_buf.get(), static_cast<size_t>(result));
+}
+
+}  // namespace
+
+std::string StringPrintf(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  std::string rv;
+  StringVAppendf(&rv, format, ap);
+  va_end(ap);
+  return rv;
+}
+
+std::string StringVPrintf(const char* format, va_list ap) {
+  std::string rv;
+  StringVAppendf(&rv, format, ap);
+  return rv;
+}
+
+void StringAppendf(std::string* dest, const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  StringVAppendf(dest, format, ap);
+  va_end(ap);
+}
+
+void StringVAppendf(std::string* dest, const char* format, va_list ap) {
+  int old_errno = errno;
+  StringVAppendfHelper(dest, format, ap);
+  errno = old_errno;
+}
+
+}  // namespace fxl
diff --git a/shell/platform/fuchsia/dart_runner/string_printf.h b/shell/platform/fuchsia/dart_runner/string_printf.h
new file mode 100644
index 0000000..7ec78e9
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/string_printf.h
@@ -0,0 +1,37 @@
+// 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.
+
+// |printf()|-like formatting functions that output/append to C++ strings.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_
+
+#include <stdarg.h>
+
+#include <string>
+
+// #include "src/lib/fxl/fxl_export.h"
+// #include "src/lib/fxl/macros.h"
+
+namespace fxl {
+
+// Formats |printf()|-like input and returns it as an |std::string|.
+[[nodiscard, gnu::format(printf, 1, 2)]] std::string StringPrintf(
+    const char* format,
+    ...);
+
+// Formats |vprintf()|-like input and returns it as an |std::string|.
+[[nodiscard]] std::string StringVPrintf(const char* format, va_list ap);
+
+// Formats |printf()|-like input and appends it to |*dest|.
+[[gnu::format(printf, 2, 3)]] void StringAppendf(std::string* dest,
+                                                 const char* format,
+                                                 ...);
+
+// Formats |vprintf()|-like input and appends it to |*dest|.
+void StringVAppendf(std::string* dest, const char* format, va_list ap);
+
+}  // namespace fxl
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_STRING_PRINTF_H_
diff --git a/shell/platform/fuchsia/dart_runner/suite.cc b/shell/platform/fuchsia/dart_runner/suite.cc
new file mode 100644
index 0000000..0645dbc
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/suite.cc
@@ -0,0 +1,297 @@
+// 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 "suite.h"
+
+#include <fuchsia/component/runner/cpp/fidl.h>
+#include <fuchsia/io/cpp/fidl.h>
+#include <fuchsia/logger/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/sys/test/cpp/fidl.h>
+#include <lib/async/cpp/executor.h>
+#include <lib/async/cpp/task.h>
+#include <lib/async/dispatcher.h>
+#include <lib/fidl/cpp/interface_handle.h>
+#include <lib/fidl/cpp/interface_ptr.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/fpromise/barrier.h>
+#include <lib/fpromise/bridge.h>
+#include <lib/fpromise/promise.h>
+#include <lib/sys/cpp/service_directory.h>
+#include <lib/sys/cpp/termination_reason.h>
+#include <lib/sys/cpp/testing/enclosing_environment.h>
+// #include <lib/syslog/cpp/macros.h>
+#include <lib/zx/socket.h>
+#include <unistd.h>
+#include <zircon/processargs.h>
+#include <zircon/status.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "flutter/fml/logging.h"
+#include "string_printf.h"
+
+static const char kTestCaseName[] = "dart_test";
+// constexpr char kEnvPrefix[] = "test_env_";
+
+Suite::Suite(std::string legacy_url, async_dispatcher_t* dispatcher)
+    : legacy_url_(std::move(legacy_url)),
+      test_components_(std::make_shared<ComponentMap>()),
+      dispatcher_(dispatcher),
+      executor_(dispatcher) {
+  FML_LOG(INFO) << "SUITE CLASS CREATED";
+}
+
+Suite::~Suite() = default;
+
+Suite::CaseIterator::CaseIterator(
+    fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request,
+    async_dispatcher_t* dispatcher,
+    fit::function<void(CaseIterator*)> done_callback)
+    : binding_(this, std::move(request), dispatcher),
+      done_callback_(std::move(done_callback)) {}
+
+void Suite::CaseIterator::GetNext(GetNextCallback callback) {
+  if (get_next_call_count == 0) {
+    fuchsia::sys::test::Case test_case;
+    test_case.set_name(std::string(kTestCaseName));
+    test_case.set_enabled(true);
+    std::vector<fuchsia::sys::test::Case> cases;
+    cases.push_back(std::move(test_case));
+    callback(std::move(cases));
+    get_next_call_count++;
+
+  } else {
+    std::vector<fuchsia::sys::test::Case> cases;
+    callback(std::move(cases));
+    // this would be removed
+    done_callback_(this);
+  }
+}
+
+void Suite::GetTests(
+    fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> iterator) {
+  FML_LOG(INFO) << "DART RUNNER HIT SUITE GETTESTS";
+  auto case_iterator = std::make_unique<CaseIterator>(
+      std::move(iterator), dispatcher_, [this](CaseIterator* case_iterator) {
+        RemoveCaseInterator(case_iterator);
+      });
+  case_iterators_.emplace(case_iterator.get(), std::move(case_iterator));
+}
+
+std::unique_ptr<Suite::CaseIterator> Suite::RemoveCaseInterator(
+    CaseIterator* case_iterator) {
+  auto it = case_iterators_.find(case_iterator);
+  std::unique_ptr<Suite::CaseIterator> case_iterator_ptr;
+  if (it != case_iterators_.end()) {
+    case_iterator_ptr = std::move(it->second);
+    case_iterators_.erase(it);
+  }
+  return case_iterator_ptr;
+}
+
+void Suite::Run(
+    std::vector<fuchsia::sys::test::Invocation> tests,
+    fuchsia::sys::test::RunOptions options,
+    fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) {
+  FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN";
+  auto listener_proxy = listener.Bind();
+  std::vector<std::string> args;
+  if (options.has_arguments()) {
+    args = std::move(*options.mutable_arguments());
+  }
+
+  if (options.has_parallel()) {
+    FML_LOG(WARNING) << "Ignoring 'parallel'. Pass test specific flags, eg: "
+                        "for rust test pass in "
+                        "--test-threads="
+                     << options.parallel();
+  }
+
+  fpromise::barrier barrier;
+  for (auto it = tests.begin(); it != tests.end(); it++) {
+    auto invocation = std::move(*it);
+    std::string test_case_name;
+    if (invocation.has_name()) {
+      test_case_name = invocation.name();
+    }
+    FML_LOG(INFO) << "DART RUNNER HIT SUITE RUN - TEST CASE: "
+                  << test_case_name;
+    zx::socket out, err, out_client, err_client;
+    auto status = zx::socket::create(0, &out, &out_client);
+    if (status != ZX_OK) {
+      FML_LOG(FATAL) << "cannot create socket: "
+                     << zx_status_get_string(status);
+    }
+    status = zx::socket::create(0, &err, &err_client);
+    if (status != ZX_OK) {
+      FML_LOG(FATAL) << "cannot create socket: "
+                     << zx_status_get_string(status);
+    }
+
+    fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener;
+
+    fuchsia::sys::test::StdHandles std_handles;
+    std_handles.set_err(std::move(err_client));
+    std_handles.set_out(std::move(out_client));
+
+    listener_proxy->OnTestCaseStarted(std::move(invocation),
+                                      std::move(std_handles),
+                                      case_listener.NewRequest());
+    // if (test_case_name != kTestCaseName) {
+    //   FML_LOG(INFO) << "TEST CASE: " << test_case_name;
+    //   FML_LOG(INFO) << "kTEST CASE: " << kTestCaseName;
+    //   std::string msg =
+    //       fxl::StringPrintf("Invalid test case, expected: %s, got: %s\n",
+    //                         kTestCaseName, test_case_name.c_str());
+    //   err.write(0, msg.c_str(), msg.length(), nullptr);
+    //   fuchsia::sys::test::Result result;
+    //   result.set_status(fuchsia::sys::test::Status::FAILED);
+    //   case_listener->Finished(std::move(result));
+    // } else {
+    //   auto promise = RunTest(std::move(out), std::move(err), args,
+    //                          std::move(case_listener))
+    //                      .wrap_with(barrier);
+    //   executor_.schedule_task(std::move(promise));
+    // }
+  }
+
+  auto sync_promise =
+      fpromise::make_promise([listener_proxy = std::move(listener_proxy)] {
+        FML_LOG(INFO) << "Sending OnFinished for legacy tests";
+        listener_proxy->OnFinished();
+      });
+  executor_.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
+}
+
+// fpromise::promise<> Suite::RunTest(
+//     zx::socket out,
+//     zx::socket err,
+//     const std::vector<std::string>& arguments,
+//     fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener) {
+//   sys::testing::EnvironmentServices::ParentOverrides parent_overrides;
+//   parent_overrides.debug_data_service_ = std::make_shared<vfs::Service>(
+//       [namespace_services = test_component_svc_](
+//           zx::channel channel, async_dispatcher_t* /*unused*/) {
+//         // namespace_services->Connect(
+//         //     fuchsia::sys::test::DebugData::Name_,
+//         //     std::move(channel));
+//       });
+
+//   auto test_env_services =
+//       sys::testing::EnvironmentServices::CreateWithParentOverrides(
+//           parent_env_, std::move(parent_overrides));
+
+//   // Add these services to the environment if they are not injected and test
+//   // doesn't request sys version of them.
+//   std::set<std::string> services_to_add = {
+//       fuchsia::logger::LogSink::Name_, fuchsia::logger::Log::Name_,
+//       fuchsia::diagnostics::ArchiveAccessor::Name_};
+
+//   // Compute a common random suffix for the environments created to run the
+//   // test.
+//   uint32_t env_rand_suffix;
+//   zx_cprng_draw(&env_rand_suffix, sizeof(env_rand_suffix));
+
+//   for (const auto& service : services_to_add) {
+//     test_env_services->AddService<void>(
+//         [namespace_services = test_component_svc_,
+//          service](fidl::InterfaceRequest<void> request) {
+//           namespace_services->Connect(service, request.TakeChannel());
+//         },
+//         service);
+//   }
+
+//   fuchsia::sys::EnvironmentOptions env_opt;
+//   // std::string env_label = std::format("%s%08x", kEnvPrefix,
+//   // env_rand_suffix);
+//   std::string env_label = kEnvPrefix;
+//   env_opt.delete_storage_on_death = true;
+
+//   auto enclosing_env = sys::testing::EnclosingEnvironment::Create(
+//       env_label, parent_env_, std::move(test_env_services), env_opt);
+
+//   auto launcher = enclosing_env->launcher_ptr();
+
+//   auto info =
+//       fuchsia::sys::LaunchInfo{.url = legacy_url_, .arguments = arguments};
+//   auto out_collector = AddOutputFileDescriptor(STDOUT_FILENO, std::move(out),
+//                                                dispatcher_, &info.out);
+//   auto err_collector = AddOutputFileDescriptor(STDERR_FILENO, std::move(err),
+//                                                dispatcher_, &info.err);
+
+//   auto svc =
+//   sys::ServiceDirectory::CreateWithRequest(&info.directory_request);
+
+//   // fuchsia::component::runner::ComponentControllerPtr contoller;
+//   fuchsia::sys::ComponentControllerPtr contoller;
+//   launcher->CreateComponent(std::move(info), contoller.NewRequest());
+//   auto test_component = std::make_unique<run::Component>(
+//       std::move(out_collector), std::move(err_collector),
+//       std::move(contoller), std::move(svc));
+//   fpromise::bridge<> bridge;
+//   test_component->controller().events().OnTerminated =
+//       [this, url = legacy_url_, enclosing_env = std::move(enclosing_env),
+//        completer = std::move(bridge.completer),
+//        case_listener = std::move(case_listener),
+//        this_ptr = test_component.get()](
+//           int64_t return_code,
+//           fuchsia::sys::TerminationReason termination_reason) mutable {
+//         if (termination_reason != fuchsia::sys::TerminationReason::EXITED) {
+//           FML_LOG(WARNING) << "Test " << url << " failed with "
+//                            << sys::HumanReadableTerminationReason(
+//                                   termination_reason);
+//         }
+
+//         FML_LOG(INFO) << "Legacy test exited with return code " <<
+//         return_code
+//                       << ", collecting stdout";
+
+//         auto status = return_code == 0 ? fuchsia::sys::test::Status::PASSED
+//                                        : fuchsia::sys::test::Status::FAILED;
+
+//         auto output_promise = this_ptr->SignalWhenOutputCollected();
+
+//         FML_LOG(INFO) << "Killing environment for legacy test";
+//         fpromise::bridge<> bridge;
+//         enclosing_env->Kill(bridge.completer.bind());
+//         auto promise = bridge.consumer.promise().and_then(
+//             [enclosing_env = std::move(enclosing_env),
+//              case_listener = std::move(case_listener), status = status,
+//              completer = std::move(completer)]() mutable {
+//               fuchsia::sys::test::Result result;
+//               result.set_status(status);
+//               FML_LOG(INFO) << "Sending finished event for legacy tests";
+//               case_listener->Finished(std::move(result));
+//               completer.complete_ok();
+//             });
+//         this->executor_.schedule_task(output_promise.and_then([this,
+//                                                                this_ptr]() {
+//           FML_LOG(INFO) << "Done collecting standard output for legacy tests
+//           "; RemoveComponent(this_ptr);
+//         }));
+//         this->executor_.schedule_task(std::move(promise));
+//       };
+
+//   test_components_->emplace(test_component.get(), std::move(test_component));
+
+//   return bridge.consumer.promise();
+// }
+
+// std::unique_ptr<run::Component> Suite::RemoveComponent(run::Component* ptr) {
+//   if (!test_components_) {
+//     return nullptr;
+//   }
+//   auto it = test_components_->find(ptr);
+//   std::unique_ptr<run::Component> test_component_ptr;
+//   if (it != test_components_->end()) {
+//     test_component_ptr = std::move(it->second);
+//     test_components_->erase(it);
+//   }
+//   return test_component_ptr;
+// }
diff --git a/shell/platform/fuchsia/dart_runner/suite.h b/shell/platform/fuchsia/dart_runner/suite.h
new file mode 100644
index 0000000..30052e0
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/suite.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <fuchsia/sys/test/cpp/fidl.h>
+#include <lib/async/cpp/executor.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/fpromise/promise.h>
+#include <lib/sys/cpp/service_directory.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+/// Implement and expose Suite protocol on behalf of wrapped legacy dart test
+/// component.
+class Suite final : public fuchsia::sys::test::Suite {
+  using ComponentMap =
+      std::map<run::Component*, std::unique_ptr<run::Component>>;
+
+ public:
+  Suite(std::string legacy_url, std::unique_ptr<async::Loop> loop);
+  ~Suite() override;
+
+  void GetTests(fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator>
+                    iterator) override;
+
+  void Run(
+      std::vector<fuchsia::sys::test::Invocation> tests,
+      fuchsia::sys::test::RunOptions options,
+      fidl::InterfaceHandle<fuchsia::sys::test::RunListener> listener) override;
+
+  fidl::InterfaceRequestHandler<fuchsia::sys::test::Suite> GetHandler() {
+    return bindings_.GetHandler(this, dispatcher_);
+  }
+
+  void AddBinding(zx::channel request) {
+    bindings_.AddBinding(
+        this,
+        fidl::InterfaceRequest<fuchsia::sys::test::Suite>(std::move(request)),
+        dispatcher_);
+  }
+
+ private:
+  fpromise::promise<> RunTest(
+      zx::socket out,
+      zx::socket err,
+      const std::vector<std::string>& arguments,
+      fidl::InterfacePtr<fuchsia::sys::test::CaseListener> case_listener);
+
+  class CaseIterator final : public fuchsia::sys::test::CaseIterator {
+   public:
+    CaseIterator(
+        fidl::InterfaceRequest<fuchsia::sys::test::CaseIterator> request,
+        async_dispatcher_t* dispatcher,
+        fit::function<void(CaseIterator*)> done_callback);
+
+    void GetNext(GetNextCallback callback) override;
+
+   private:
+    int get_next_call_count = 0;
+    fidl::Binding<fuchsia::sys::test::CaseIterator> binding_;
+    fit::function<void(CaseIterator*)> done_callback_;
+  };
+
+  // const std::shared_ptr<sys::ServiceDirectory> test_component_svc_;
+  const std::string legacy_url_;
+  std::shared_ptr<ComponentMap> test_components_;
+  std::map<CaseIterator*, std::unique_ptr<CaseIterator>> case_iterators_;
+  async_dispatcher_t* dispatcher_;
+  fidl::BindingSet<fuchsia::sys::test::Suite> bindings_;
+  async::Executor executor_;
+
+  std::unique_ptr<CaseIterator> RemoveCaseInterator(CaseIterator*);
+  std::unique_ptr<run::Component> RemoveComponent(run::Component* ptr);
+};
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_SUITE_H_
diff --git a/shell/platform/fuchsia/dart_runner/test_component.cc b/shell/platform/fuchsia/dart_runner/test_component.cc
new file mode 100644
index 0000000..0a5660d
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/test_component.cc
@@ -0,0 +1,36 @@
+// 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 "test_component.h"
+
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/sys/cpp/component_context.h>
+
+#include "flutter/fml/logging.h"
+#include "suite.h"
+
+TestComponent::TestComponent(TestComponentArgs args, DoneCallback done_callback)
+    : dispatcher_(args.dispatcher),
+      binding_(this, std::move(args.request), args.dispatcher),
+      done_callback_(std::move(done_callback)) {
+  suite_ = std::make_unique<Suite>(std::move(args.parent_env_svc),
+                                   std::move(args.parent_env),
+                                   std::move(args.test_component_svc),
+                                   std::move(args.legacy_url), dispatcher_);
+  suite_context_ = sys::ComponentContext::Create();
+  suite_context_->outgoing()->AddPublicService(suite_->GetHandler());
+  suite_context_->outgoing()->Serve(std::move(args.outgoing_dir), dispatcher_);
+}
+
+TestComponent::~TestComponent() = default;
+
+void TestComponent::Stop() {
+  Kill();
+}
+
+void TestComponent::Kill() {
+  binding_.Close(ZX_OK);
+  // this object would be killed after this call
+  done_callback_(this);
+}
diff --git a/shell/platform/fuchsia/dart_runner/test_component.h b/shell/platform/fuchsia/dart_runner/test_component.h
new file mode 100644
index 0000000..cf9c0a5
--- /dev/null
+++ b/shell/platform/fuchsia/dart_runner/test_component.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_
+
+#include <fuchsia/component/runner/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/sys/cpp/component_context.h>
+#include <lib/sys/cpp/service_directory.h>
+
+#include <memory>
+
+#include "suite.h"
+
+using ComponentController = fuchsia::component::runner::ComponentController;
+
+struct TestComponentArgs {
+  std::string legacy_url;
+  zx::channel outgoing_dir;
+  fuchsia::sys::EnvironmentPtr parent_env;
+  std::shared_ptr<sys::ServiceDirectory> parent_env_svc;
+  std::shared_ptr<sys::ServiceDirectory> test_component_svc;
+
+  fidl::InterfaceRequest<ComponentController> request;
+  async_dispatcher_t* dispatcher;
+};
+
+/// Implements component controller on behalf of the runner and also
+/// stores/controls running test component.
+class TestComponent final : public ComponentController {
+  using DoneCallback = fit::function<void(TestComponent*)>;
+
+ public:
+  explicit TestComponent(TestComponentArgs args, DoneCallback done_callback);
+  ~TestComponent() override;
+
+  void Stop() override;
+
+  void Kill() override;
+
+ private:
+  async_dispatcher_t* dispatcher_;
+  fidl::Binding<ComponentController> binding_;
+
+  /// For safe keeping while the component is running.
+  std::unique_ptr<Suite> suite_;
+
+  /// Exposes suite protocol on behalf of test component.
+  std::unique_ptr<sys::ComponentContext> suite_context_;
+
+  DoneCallback done_callback_;
+};
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_DART_RUNNER_TEST_COMPONENT_H_
diff --git a/shell/platform/fuchsia/flutter/component_v2.cc b/shell/platform/fuchsia/flutter/component_v2.cc
index b068369..532da61 100644
--- a/shell/platform/fuchsia/flutter/component_v2.cc
+++ b/shell/platform/fuchsia/flutter/component_v2.cc
@@ -116,6 +116,16 @@
       result.assets_path = "pkg/" + entry.value->str();
     } else if (entry.key.compare(kArgsKey) == 0 && entry.value != nullptr) {
       ParseArgs(entry.value->str_vec(), &result);
+      FML_LOG(INFO) << "FLUTTER RUNNER PROGRAM ARGS: ";
+      std::string s;
+      for (const auto& piece : entry.value->str_vec())
+        s += piece;
+      FML_LOG(INFO) << s;
+
+      std::string d;
+      for (const auto& piece : result.expose_dirs)
+        d += piece;
+      FML_LOG(INFO) << d;
     }
   }
 
diff --git a/shell/platform/fuchsia/flutter/runner.cc b/shell/platform/fuchsia/flutter/runner.cc
index 6c59e6c..377ec61 100644
--- a/shell/platform/fuchsia/flutter/runner.cc
+++ b/shell/platform/fuchsia/flutter/runner.cc
@@ -308,6 +308,7 @@
   // change, so we make a copy to pass to TRACE_DURATION.
   // TODO(PT-169): Remove this copy when TRACE_DURATION reads string arguments
   // eagerly.
+  FML_LOG(INFO) << "RESOLVED URL V2: " << start_info.resolved_url();
   const std::string url_copy = start_info.resolved_url();
   TRACE_EVENT1("flutter", "Start", "url", url_copy.c_str());
 
diff --git a/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h b/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h
new file mode 100644
index 0000000..afb2a50
--- /dev/null
+++ b/shell/platform/fuchsia/runtime/dart/utils/program_metadata.h
@@ -0,0 +1,38 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_
+
+#include <optional>
+#include <string>
+
+namespace dart_runner {
+
+/// The metadata that can be passed by a Flutter component via
+/// the `program` field.
+struct ProgramMetadata {
+  /// The path where data for the Flutter component should
+  /// be stored.
+  std::string data_path = "";
+
+  bool is_test = false;
+
+  /// The path where assets for the Flutter component should
+  /// be stored.
+  ///
+  /// TODO(fxb/89246): No components appear to be using this field. We
+  /// may be able to get rid of this.
+  std::string assets_path = "";
+
+  /// The preferred heap size for the Flutter component in megabytes.
+  std::optional<int64_t> old_gen_heap_size = std::nullopt;
+
+  /// A list of additional directories that we will expose in out/
+  std::vector<std::string> expose_dirs;
+};
+
+}  // namespace dart_runner
+
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_RUNTIME_DART_UTILS_PROGRAM_METADATA_H_
diff --git a/tools/gn b/tools/gn
index abf2836..8b07b21 100755
--- a/tools/gn
+++ b/tools/gn
@@ -414,7 +414,7 @@
         gn_args['fuchsia_target_api_level'] = int(f.read().strip())
 
     # Impeller flags.
-    gn_args['impeller_enable_playground'] = args.enable_impeller_playground
+    # gn_args['impeller_enable_playground'] = args.enable_impeller_playground
 
     return gn_args
 
@@ -528,8 +528,8 @@
   parser.add_argument('--fuchsia-target-api-level', dest='fuchsia_target_api_level')
 
   # Impeller flags.
-  parser.add_argument('--enable-impeller-playground', default=False, action='store_true',
-                      help='Whether impeller unit tests run in playground mode')
+  # parser.add_argument('--enable-impeller-playground', default=False, action='store_true',
+  #                     help='Whether impeller unit tests run in playground mode')
 
   # Sanitizers.
   parser.add_argument('--asan', default=False, action='store_true')