【免费下载链接】flutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
导读
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 归档:
- 测试组件本身:
dart-aot-runner-integration-test-0.far; - OOT AOT 运行器:
oot_dart_aot_runner-0.far(来自 dart_runner/BUILD.gn 的oot_dart_aot_runner目标); - 测试用 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 的关键差异:
program.runner:AOT 版本声明dart_aot_runner,JIT 版本声明dart_jit_runner——这正是测试要验证的"不同运行器能正确启动 Dart 组件"的直接证据;include差异:AOT 版本额外包含inspect/client.shard.cml(组件健康检查/诊断能力),JIT 版本只包含 syslog;- 两者的
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_testFIDL 库、fml、gtest等,并通过libs = [".../libzircon.so"]链接 Zircon 符号;fuchsia_test_archive("dart-aot-runner-integration-test"):将二进制封装为可发布的 FAR 归档,即 README 第三步中发布的dart-aot-runner-integration-test-0.far。
测试的验证价值与运行前提小结
综合 README 与源码,可以得出这套集成测试的完整验证闭环:
- 运行器可启动:
RealmBuilderTest.DartRunnerStartsUp用例证明 AOT/JIT 运行器都能在组件沙箱中成功初始化 Dart VM 并启动 Dart 组件; - 组件能力可用:
dart.test.Echo协议的声明、路由与响应,证明fuchsia::component::runner::ComponentRunner的启动链路与 Dart 侧回调注册机制(dart:fuchsia.builtin)配合正常; - 模式选择是关键前提:AOT 必须
--runtime-mode=profile(输出目录fuchsia_profile_x64),JIT 必须 debug 模式(输出目录fuchsia_debug_x64),运行时模式选错会直接导致快照生成失败; - 发布三步缺一不可:测试组件、OOT 运行器、Echo 组件三个 FAR 必须全部发布到 PM 仓库,缺失任何一个都会导致
ffx test run时组件解析失败。
这套测试目前仍依赖手动流程(README 中的 TODO(erkln) 表明未来会纳入统一测试脚本),但它作为 Dart Runner 在 Fuchsia 平台上"能启动、能通信、能测试"的最小闭环验证,是引擎平台代码质量保障体系中值得参照的组件测试样板。
延伸阅读
- Dart Runner 组件源码目录:运行器本体、控制器、VM 服务 isolate 等实现
- dart_runner/BUILD.gn:AOT/JIT/product/OOT 多形态构建目标
- dart_runner/tests:测试目录全貌(集成测试 + 单元测试)
- startup_integration_test:集成测试聚合入口与三组件实现
- echo.fidl:测试用
dart.test.Echo协议定义
【免费下载链接】flutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



