Flutter 引擎 Fuchsia 平台的 Dart Runner 集成测试解析:从构建、发布到运行验证

  • 跨平台
  • 移动开发
  • 前端
  • UI组件
  • OpenHarmony
  • 图形学

【免费下载链接】flutter_flutter

本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。

项目地址: https://gitcode.com/CPF-Flutter/flutter_flutter
点击查看 免费下载

导读

Dart Runner(Dart 应用运行器)是 Flutter 引擎在 Fuchsia 平台上用于启动、运行非 Flutter 的纯 Dart 组件的核心组件。本文以仓库中 dart_runner/tests/README.md 为主线,结合其下的 startup_integration_test 子目录源码,完整还原 Dart Runner 测试的组织方式、AOT/JIT 两套集成测试的构建发布流程,并深入剖析测试所依赖的组件模型、Realm Builder 测试骨架与 Echo 服务协议实现。读完本文,你将掌握如何在本地 Fuchsia 模拟器上跑通 Dart Runner 集成测试,并理解测试背后的组件化运行原理。

测试目录概览:两类测试与一个工具包

dart_runner/tests 目录(查看目录结构)包含 Dart Runner 的测试及其配套工具代码,主体由三部分组成:

组成部分路径定位
AOT 集成测试startup_integration_test/dart_aot_runner验证 Dart AOT(Ahead-of-Time)运行器能否成功启动并响应请求
JIT 集成测试startup_integration_test/dart_jit_runner验证 Dart JIT(Just-in-Time)运行器能否成功启动并响应请求
测试用 Echo 服务startup_integration_test/dart_echo_server提供 dart.test.Echo FIDL 协议,供两个集成测试共同消费

此外,suite_impl_unittests.cc 是 Dart 测试运行器的单元测试入口。当前它仅包含一个占位性的 SuiteImplTest.EQUALITY 用例(内部保留 TODO,说明针对 dart_test_runner 的完整单元测试尚未补齐),其对应构建目标为 BUILD.gn 中 enable_unittests 分支下的 dart_test_runner_unittests(输出名为 dart_runner_tests)。

从 startup_integration_test/BUILD.gn 可以看到,顶层 startup_integration_test group 聚合了 dart_aot_runner:tests 与 dart_jit_runner:tests 两个子目标,而这两个子目标又被 dart_runner/BUILD.gn 中的 tests group 纳入,最终统一汇入整个引擎的 Fuchsia 测试体系。

Dart Runner 是什么:被测对象的技术背景

在深入测试细节之前,有必要先理解被测对象本身。根据 dart_runner/README.md,Dart Runner 实现了 fuchsia::component::runner::ComponentRunner FIDL 接口,用于启动并运行不使用 Flutter 的 Dart 应用程序。换句话说,在 Fuchsia 组件模型中,凡是 program.runner 声明为 dart_aot_runner 或 dart_jit_runner 的 Dart 组件,都会由 Dart Runner 接管其生命周期。

Runner 的核心入口与运行逻辑集中在 dart_runner.cc:

  • VM 初始化参数:代码第 39-57 行定义了 kDartVMArgs。AOT 模式启用 --precompilation,JIT 模式则禁用 mirrors(--enable_mirrors=false);在非 product 且非 profile 的构建中追加 --enable_asserts。
  • 启动分派:Start 方法(第 242-274 行)首先通过 IsTestProgram 解析组件 program 字段中的 args,判断是否为测试组件。若是,则走 RunTestApplication 路径,由 DartTestComponentController 对外提供 fuchsia.test.Suite 协议;否则走常规的 RunApplication 路径,由 DartComponentController 承载应用运行。
  • 测试模式判定:IsTestProgram(第 167-187 行)用 fml::CommandLine 解析启动参数,当出现 --is_test=true 时将该组件视为测试组件。

测试组件与普通组件的关键差异,在 dart_test_component_controller.h 的类注释中说明得很清楚:DartTestComponentController 必须额外实现 fuchsia.test.Suite 协议(GetTests 与 Run 两个方法),由 test_manager 驱动执行;而普通组件只需实现 ComponentController。这是为了在 test_manager 与真实测试执行之间去掉额外一层间接调用。

运行模式的构建差异:AOT 与 JIT

dart_runner/BUILD.gn 中的 runner 模板(第 77-107 行)以 product 参数区分发行版与调试版,四个实例分别是:

  • dart_jit_runner_bin / dart_jit_product_runner_bin:链接 libdart_jit,走 JIT 运行时;
  • dart_aot_runner_bin / dart_aot_product_runner_bin:定义 AOT_RUNTIME 宏,链接 libdart_aotruntime,并依赖 embedder:dart_aot_snapshot_cc 提供内置的 AOT 快照。

对应的归档目标 aot_runner_package / jit_runner_package 会把 vmservice_snapshot.so(AOT)、vm_snapshot_data.bin / isolate_core_snapshot_data.bin(JIT)及 profiler symbols 一并打包进 FAR 归档。注意第 267-279 行的 oot_dart_aot_runner / oot_dart_jit_runner:这是供测试使用的 "OOT"(out-of-tree)运行器副本,用于避开基础 Fuchsia 镜像中同名运行器的冲突(见其中的 TODO(fxbug.dev/106575),未来计划用 subpackage 机制解决)。集成测试的组件 URL 正是引用这两个 OOT 包。

AOT 集成测试:构建、发布与运行的完整流程

dart_aot_runner 的 README 给出了从模拟器搭建到测试运行的四步完整流程。特别注意其 Notes 结论:AOT 集成测试必须使用 profile 运行时模式,否则快照无法生成或会生成错误——这与下文 JIT 测试必须用 debug 模式形成严格对照。

第一步:搭建模拟器与 PM serve

fx set terminal.qemu-x64
ffx emu start --headless

fx serve
  • fx set terminal.qemu-x64:为 Fuchsia 源码树设置 terminal.qemu-x64 产品配置(x64 架构的终端模拟镜像);
  • ffx emu start --headless:以无头模式(headless)启动 Fuchsia 模拟器,便于在无显示器的 CI 环境运行;
  • fx serve:启动本地软件包仓库服务,供后续 ffx repository publish 发布并让模拟器下载组件包。

注:README 中留有 TODO(erkln),说明该流程未来会由专门的测试运行脚本取代,但当前步骤仍须手动执行。

第二步:生成构建文件并编译二进制

$ENGINE_DIR/flutter/tools/gn --fuchsia --no-lto --runtime-mode=profile
ninja -C $ENGINE_DIR/out/fuchsia_profile_x64 flutter/shell/platform/fuchsia fuchsia_tests
  • --fuchsia:指示 GN 生成 Fuchsia 平台的构建文件;--no-lto 关闭链接时优化,加速构建;--runtime-mode=profile 是 AOT 测试的前提(与上述 Notes 呼应);
  • ninja -C .../fuchsia_profile_x64:在 fuchsia_profile_x64 输出目录执行增量构建,目标为 flutter/shell/platform/fuchsia(引擎平台代码)与 fuchsia_tests(测试归档)。

第三步:将产物发布到 PM(Package Manager)

$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_profile_x64/dart-aot-runner-integration-test-0.far

$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_profile_x64/oot_dart_aot_runner-0.far

$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_profile_x64/gen/flutter/shell/platform/fuchsia/dart_runner/tests/startup_integration_test/dart_echo_server/dart_aot_echo_server/dart_aot_echo_server.far

三次发布分别对应三个 FAR 归档:

  1. 测试组件本身:dart-aot-runner-integration-test-0.far;
  2. OOT AOT 运行器:oot_dart_aot_runner-0.far(来自 dart_runner/BUILD.gn 的 oot_dart_aot_runner 目标);
  3. 测试用 Echo 组件:dart_aot_echo_server.far(来自 dart_echo_server/BUILD.gn 的 aot_echo_package 目标,其中内嵌了 oot_dart_aot_runner)。

发布目标目录 $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files 通过读取 .fx-build-dir 文件动态定位当前 Fuchsia 构建目录,即 fx serve 服务的软件包仓库根。

第四步:运行测试

ffx test run "fuchsia-pkg://fuchsia.com/dart-aot-runner-integration-test#meta/dart-aot-runner-integration-test.cm"

该命令通过 ffx test run 启动测试组件,组件 URL 由包名(fuchsia.com 下的 dart-aot-runner-integration-test)与清单路径(meta/dart-aot-runner-integration-test.cm)两部分构成。

JIT 集成测试:与 AOT 的流程对照

dart_jit_runner 的 README 与 AOT 流程结构完全一致,仅在运行时模式、产物路径与组件 URL 上有差异:

# 1. 模拟器与 serve
fx set terminal.qemu-x64
ffx emu start --headless
fx serve

# 2. 构建(注意:debug 模式,且不带 --runtime-mode)
$ENGINE_DIR/flutter/tools/gn --fuchsia --no-lto
ninja -C $ENGINE_DIR/out/fuchsia_debug_x64 flutter/shell/platform/fuchsia fuchsia_tests

# 3. 发布三个 FAR
$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_debug_x64/dart-jit-runner-integration-test-0.far

$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_debug_x64/oot_dart_jit_runner-0.far

$FUCHSIA_DIR/.jiri_root/bin/ffx repository publish \
  $FUCHSIA_DIR/$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files \
  --package-archive $ENGINE_DIR/out/fuchsia_debug_x64/gen/flutter/shell/platform/fuchsia/dart_runner/tests/startup_integration_test/dart_echo_server/dart_jit_echo_server/dart_jit_echo_server.far

# 4. 运行
ffx test run "fuchsia-pkg://fuchsia.com/dart-jit-runner-integration-test#meta/dart-jit-runner-integration-test.cm"

Notes 要点:JIT 集成测试必须使用 debug 运行时模式,快照(snapshot)生成对运行时模式敏感,模式用错会导致快照生成失败或内容错误。

两种模式差异的根源

AOT 测试之所以要求 profile,是因为 AOT 快照依赖预先编译的指令快照与内核快照,profile 模式提供与生产部署一致(含性能剖析能力)的 AOT 产物;JIT 测试要求 debug,是因为 JIT 运行器需要可执行代码生成、断言开启等调试能力,而 debug 模式的 kernel 快照与 VM 配置恰好满足这些要求。两者在 dart_runner/BUILD.gn 中的链接依赖(libdart_jit 与 libdart_aotruntime)与宏定义(AOT_RUNTIME)差异,正是两种运行模式在构建层面的体现。

Echo 服务:集成测试的"靶子"组件

dart_echo_server(目录结构)是集成测试中被运行器启动的 Dart 组件,用于验证"运行器能否真正拉起并驱动一个 Dart 组件"。其 README 说明它当前产出两个目标:

  • dart-aot-echo-server:由 AOT 运行器启动,被 AOT 集成测试消费;
  • dart-jit-echo-server:由 JIT 运行器启动,被 JIT 集成测试消费。

Dart 侧的 Echo 实现

main.dart 是整个 Echo 服务的业务逻辑,仅 13 行:

import 'dart:fuchsia.builtin';

String? OnEchoString(String? str) {
  print('Got echo string: $str');
  return str;
}

void main(List<String> args) {
  receiveEchoStringCallback = OnEchoString;
}

它通过 dart:fuchsia.builtin 库注册 receiveEchoStringCallback 回调:任何到达的字符串都会原样回显并打印日志。这个极简实现使得测试断言可以聚焦在"运行器成功启动组件、FIDL 调用正确抵达 Dart 代码"这一核心目标上,而无需关注复杂业务逻辑。

FIDL 协议定义

dart.test.Echo 协议定义在 echo.fidl:

library dart.test;

const MAX_STRING_LENGTH uint64 = 32;

@discoverable
closed protocol Echo {
    /// Returns the input.
    strict EchoString(struct {
        value string:<MAX_STRING_LENGTH, optional>;
    }) -> (struct {
        response string:<MAX_STRING_LENGTH, optional>;
    });
};

要点解读:

  • 协议名为 dart.test,方法 EchoString 接收一个可选字符串并原样返回;
  • string:<MAX_STRING_LENGTH, optional> 限制字符串最大长度 32 字节且可为空,保证测试负载轻量可控;
  • @discoverable 允许协议在组件能力发现机制中被暴露;closed / strict 表示协议不允许未知方法、方法为严格语义(违反会产生错误而非忽略);
  • 该 FIDL 库的构建目标在 fidl/BUILD.gn 中定义为 dart_test(library_name 为 dart.test)。

组件清单(CML)的两种形态

两个 Echo 组件共享同一份 Dart 源码与 FIDL 协议,区别体现在组件清单(CML)中声明的 runner 上:

{
    "include": [ "syslog/client.shard.cml", "inspect/client.shard.cml" ],
    "program": {
        "data": "data/dart-aot-echo-server",
        "runner": "dart_aot_runner"
    },
    "capabilities": [ { "protocol": "dart.test.Echo" } ],
    "expose": [ { "protocol": "dart.test.Echo", "from": "self" } ]
}
{
    "include": [ "syslog/client.shard.cml" ],
    "program": {
        "data": "data/dart-jit-echo-server",
        "runner": "dart_jit_runner"
    },
    "capabilities": [ { "protocol": "dart.test.Echo" } ],
    "expose": [ { "protocol": "dart.test.Echo", "from": "self" } ]
}

两处 CML 的关键差异:

  1. program.runner:AOT 版本声明 dart_aot_runner,JIT 版本声明 dart_jit_runner——这正是测试要验证的"不同运行器能正确启动 Dart 组件"的直接证据;
  2. include 差异:AOT 版本额外包含 inspect/client.shard.cml(组件健康检查/诊断能力),JIT 版本只包含 syslog;
  3. 两者的 program.data 指向各自的编译产物(data/dart-aot-echo-server / data/dart-jit-echo-server),由 dart_echo_server/BUILD.gn 中 aot_component 与 jit_component 两个 dart_component 目标分别产出。

测试实现剖析:Realm Builder 组件测试骨架

两个集成测试的 C++ 测试体结构几乎一致(AOT 版在 dart-aot-runner-integration-test.cc,JIT 版在 dart-jit-runner-integration-test.cc),核心用例均为 RealmBuilderTest.DartRunnerStartsUp,采用 Fuchsia 组件测试标准范式 —— Realm Builder + loop_fixture::RealLoop。

测试步骤分解(以 AOT 为例)

1. 注册运行器子组件与环境

auto realm_builder = RealmBuilder::Create();
// 将 OOT 的 AOT 运行器作为 realm 子组件加入
realm_builder.AddChild(kDartAotRunner, kDartAotRunnerUrl);
// kDartAotRunnerUrl = "fuchsia-pkg://fuchsia.com/oot_dart_aot_runner#meta/dart_aot_runner.cm"

随后构造名为 dart_runner_env 的环境声明(继承自 REALM),并把运行器注册为该环境的 runner:

fuchsia::component::decl::RunnerRegistration dart_aot_runner_reg;
dart_aot_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild(
    fuchsia::component::decl::ChildRef{.name = kDartAotRunner}));
dart_aot_runner_reg.set_source_name(kDartAotRunner);
dart_aot_runner_reg.set_target_name(kDartAotRunner);
environment_runners->push_back(std::move(dart_aot_runner_reg));

这对应了 Program.runner 机制:Dart 组件必须在一个显式声明了 dart_aot_runner 运行器注册的环境里才能启动。

2. 将 Echo 组件挂入该环境

realm_builder.AddChild(kDartAotEchoServer, kDartAotEchoServerUrl,
                       ChildOptions{.environment = kDartRunnerEnvironment});

Echo 组件通过 ChildOptions{.environment = "dart_runner_env"} 被放进上述环境,从而由 oot_dart_aot_runner 接管启动。

3. 路由基础能力

realm_builder.AddRoute(
    Route{.capabilities = {Protocol{"fuchsia.logger.LogSink"},
                           Protocol{"fuchsia.tracing.provider.Registry"},
                           Protocol{"fuchsia.posix.socket.Provider"},
                           Protocol{"fuchsia.intl.PropertyProvider"},
                           Protocol{"fuchsia.vulkan.loader.Loader"},
                           Protocol{"fuchsia.inspect.InspectSink"},
                           Directory{"config-data"}},
          .source = ParentRef(),
          .targets = {kDartAotRunnerRef, kDartAotEchoServerRef}});

运行器与 Echo 组件所需的日志、追踪、POSIX socket、国际化、Vulkan loader、inspect 等能力均来自 Parent(测试组件所在上层)。AOT 测试额外多路由一个 fuchsia.vulkan.loader.Loader(JIT 测试没有),这与 AOT 快照可能依赖的图形栈有关。

4. 路由 Echo 协议并构建 Realm

realm_builder.AddRoute(Route{.capabilities = {Protocol{"dart.test.Echo"}},
                             .source = kDartAotEchoServerRef,
                             .targets = {ParentRef()}});
auto realm = realm_builder.Build(dispatcher());

Echo 协议从 Echo 组件路由到 Parent(即测试组件),随后 Build 将整个组件拓扑实例化。

5. 发起同步 FIDL 调用并断言

auto echo = realm.component().ConnectSync<dart::test::Echo>();
fidl::StringPtr response;
echo->EchoString("hello", &response);
ASSERT_EQ(response, "hello");

测试通过 ConnectSync 建立同步连接,调用 EchoString("hello"),断言返回值与原输入相等。这一步同时验证了三件事:运行器成功启动了 Echo 组件、dart.test.Echo 协议在组件间正确路由、Dart 回调 OnEchoString 正确执行并返回。

测试组件的 CML 配置

测试组件自身的清单(AOT 版 CML / JIT 版 CML)包含:

  • include:gtest_runner.shard.cml(gtest 测试运行框架)、realm_builder_absolute.shard.cml(Realm Builder 库)、system-test.shard.cml(系统测试级别)、syslog/client.shard.cml(日志)等;AOT 版额外包含 inspect/client.shard.cml;
  • offer:向 #realm_builder 提供 fuchsia.sysmem.Allocator、fuchsia.sysmem2.Allocator、fuchsia.tracing.provider.Registry、fuchsia.vulkan.loader.Loader、fuchsia.posix.socket.Provider、fuchsia.intl.PropertyProvider 等协议,以及 config-data 目录(pkg/config 子目录重映射);JIT 版额外提供 tzdata-icu 目录(时区数据,供 Dart 的 ICU 使用);
  • facets:fuchsia.test.deprecated-allowed-packages 列出 dart_aot_echo_server / dart_jit_echo_server、test_manager、oot_dart_aot_runner / oot_dart_jit_runner,允许测试引用这些外部包(注释提到 TODO(fxbug.dev/114584):未来希望能作为 deps 引入而非写死在 facets 中)。

注意 CML 注释对 AOT 版的特别说明:该测试同时需要 vulkan facet 与 hermetic-tier-2 facet,因此被强制声明为 system test。

测试目标的构建封装

dart_aot_runner/BUILD.gn 展示了测试二进制的组织方式:

  • dart-aot-runner-integration-test-bin:编译 dart-aot-runner-integration-test.cc,依赖 fuchsia.logger、fuchsia.tracing.provider、async-loop-testing、sys_component_cpp_testing(Realm Builder 的 C++ 绑定)、dart_test FIDL 库、fml、gtest 等,并通过 libs = [".../libzircon.so"] 链接 Zircon 符号;
  • fuchsia_test_archive("dart-aot-runner-integration-test"):将二进制封装为可发布的 FAR 归档,即 README 第三步中发布的 dart-aot-runner-integration-test-0.far。

测试的验证价值与运行前提小结

综合 README 与源码,可以得出这套集成测试的完整验证闭环:

  1. 运行器可启动:RealmBuilderTest.DartRunnerStartsUp 用例证明 AOT/JIT 运行器都能在组件沙箱中成功初始化 Dart VM 并启动 Dart 组件;
  2. 组件能力可用:dart.test.Echo 协议的声明、路由与响应,证明 fuchsia::component::runner::ComponentRunner 的启动链路与 Dart 侧回调注册机制(dart:fuchsia.builtin)配合正常;
  3. 模式选择是关键前提:AOT 必须 --runtime-mode=profile(输出目录 fuchsia_profile_x64),JIT 必须 debug 模式(输出目录 fuchsia_debug_x64),运行时模式选错会直接导致快照生成失败;
  4. 发布三步缺一不可:测试组件、OOT 运行器、Echo 组件三个 FAR 必须全部发布到 PM 仓库,缺失任何一个都会导致 ffx test run 时组件解析失败。

这套测试目前仍依赖手动流程(README 中的 TODO(erkln) 表明未来会纳入统一测试脚本),但它作为 Dart Runner 在 Fuchsia 平台上"能启动、能通信、能测试"的最小闭环验证,是引擎平台代码质量保障体系中值得参照的组件测试样板。

延伸阅读

  • 跨平台
  • 移动开发
  • 前端
  • UI组件
  • OpenHarmony
  • 图形学

【免费下载链接】flutter_flutter

本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。

项目地址: https://gitcode.com/CPF-Flutter/flutter_flutter
点击查看 免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值