- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
导读
tools/hangar/__snapshots__/test_corpus/valid/目录下的 Markdown 快照文件,是 Wing 工具链测试框架 Hangar 对tests/valid/中每一个合法.w测试程序在指定目标(sim 或 tf-aws)下运行/编译结果的固化记录。本文以 bring_awscdk.test.w_test_sim.md 为切入点,完整解析这份快照的内容含义、其背后被测的 bring_awscdk.test.w 源码,以及 Wing 语言通过bring引入外部 jsii 库(aws-cdk-lib)的底层实现机制,同时结合 Hangar 测试框架源码说明快照的生成原理。读完本文,你将掌握如何阅读和理解 Wing 仓库中的 Hangar 快照、wing test -t sim与wing compile -t tf-aws在外部库场景下的行为差异,以及如何在本地复现这些结果。
一、快照文档概览:Hangar 测试语料快照体系
在 Wing 仓库中,tools/hangar/__snapshots__/test_corpus/目录存放着两类由 Hangar 自动生成并固化的快照:
{测试文件}.test.w_test_sim.md:对应wing test -t sim在本地模拟器(simulator)平台上运行测试程序后的输出记录;{测试文件}.test.w_compile_tf-aws.md:对应wing compile -t tf-aws将程序编译(合成)到 AWS 目标后的产物记录(main.tf.json、preflight.cjs、inflight.*.cjs等)。
快照文件的头部统一采用# {wing文件} | {testCase} | {target}格式。例如本快照第一行:
# [bring_awscdk.test.w](https://link.gitcode.com/i/44c1458d5820e07bc8519ed2e4709e06) | test | sim其中test表示测试用例类型,sim表示运行目标。该相对链接从快照文件所在位置回溯到测试源码,在文章中统一以仓库根目录相对路径 tests/valid/bring_awscdk.test.w 表示。这一命名与内容组织方式由 tools/hangar/src/utils.ts 中的createMarkdownSnapshot函数负责:它按{目录}/{wing文件名}_{testCase}_{target}.md计算快照路径,并遍历fileMap中的文件(排序后)逐个以## {文件名}+ 代码块形式写入 Markdown。
二、逐行解读 test_sim 快照输出
本快照正文只有一个## stdout.log代码块,内容如下:
pass ─ bring_awscdk.test.wsim (no tests) Tests (0) Snapshots 1 skipped Test Files 1 passed (1) Duration <DURATION>逐行含义:
pass ─ bring_awscdk.test.wsim (no tests):测试运行器将该文件作为单个测试用例执行并判定为通过(pass)。(no tests)说明该.w文件中没有定义任何test块,因此没有实际测试断言被运行;Tests (0):执行的测试数量为 0,与上一行的(no tests)相互印证;Snapshots 1 skipped:本次运行跳过了 1 个快照断言(即本文件本身作为一次快照记录被跳过后不再重复校验);Test Files 1 passed (1):共 1 个测试文件,全部通过;Duration <DURATION>:实际运行时长被 Hangar 的标准化逻辑替换为占位符。这是因为 tools/hangar/src/utils.ts 的sanitizeOutput会使用正则/Duration \d+m[\d.]+s/g将时长统一替换为Duration <DURATION>,避免每次运行产生的毫秒/秒级时长差异破坏快照的确定性。同理,该函数还会把绝对路径、时间戳、端口号、IP 等易变信息分别替换为<ABSOLUTE>、<TIMESTAMP>、<PORT>、<IP>:<PORT>等占位符,这正是快照能被稳定提交进仓库的前提。
从工程角度看,这份快照的判定结论是:一个仅创建构造(construct)而不含test块的外部库集成程序,在 sim 目标下能够顺利通过编译、合成与加载,整体流程无异常。
三、被测程序源码:在 Wing 中直接使用 AWS CDK 构造
快照所记录的被测程序是 tests/valid/bring_awscdk.test.w,全文如下:
bring "aws-cdk-lib" as cdk; class CdkDockerImageFunction { function: cdk.aws_lambda.DockerImageFunction; new() { let eventBridge = new cdk.aws_events.EventBus(eventBusName: "test") as "EventBridge"; let rule = new cdk.aws_events.CfnRule( name: "test", eventBusName: eventBridge.eventBusName, eventPattern: {}, targets: [{ arn: "", id: "", }], ); new cdk.aws_events.Rule(eventPattern: { detailType: [""], }); this.function = new cdk.aws_lambda.DockerImageFunction({ code: cdk.aws_lambda.DockerImageCode.fromImageAsset("./test.ts"), }) as "DockerImageFunction"; } } // Test creating an `App` construct new cdk.App();这段代码集中展示了 Wing 语言与外部 jsii 库互操作的几个关键语言特性:
bring "aws-cdk-lib" as cdk;:Wing 通过bring语句引入外部 npm/jsii 包,并为其绑定命名空间别名cdk。这是 Wing 的 JSII 互操作能力的直接体现——仓库中大量合法测试(如 tests/valid/bring_jsii.test.w、tests/valid/new_jsii.test.w)都在验证这一机制。类型标注直接使用外部库类型:
function: cdk.aws_lambda.DockerImageFunction;表明 Wing 的类字段可以声明为任意外部 jsii 类型,编译器据此生成对应的构造调用。new表达式支持命名参数与构造 ID:new cdk.aws_events.EventBus(eventBusName: "test")以命名参数方式传入构造属性;as "EventBridge"为构造节点显式指定 ID,避免依赖编译器自动生成的 ID;- 嵌套结构体字面量
targets: [{ arn: "", id: "" }]直接构造 CDK 属性对象; this.function = new ... as "DockerImageFunction"将构造结果赋给类字段。
直接实例化 CDK 的 App:末尾
new cdk.App();验证了外部库构造可以直接参与 Wing 应用的构建,而无须经过 Wing SDK 的资源封装层。
作为补充佐证,仓库中还有一个更完整的 awscdk 集成示例 tests/valid/dynamo_awscdk.test.w。它演示了在自定义类中持有awscdk.aws_dynamodb.Table、通过extern引入 TypeScript 实现 inflight 方法、并在bind回调中为aws.Function添加 IAM 策略语句等进阶用法,同时用if @target != "awscdk" { throw ... }对编译目标做了显式校验。该文件头部skip: true注释注明“此示例仅能在 AWS 上运行”,因此未被纳入常规快照语料。
四、底层原理:Wing 如何把外部构造调用翻译成$ClassFactory
要理解为什么外部 CDK 构造能被无缝创建,需要看 compile_tf-aws 快照 中的preflight.cjs。编译后的 JS 代码揭示了关键翻译规则:
const cdk = require("aws-cdk-lib"); ... const eventBridge = globalThis.$ClassFactory.new("aws-cdk-lib.aws_events.EventBus", cdk.aws_events.EventBus, this, "EventBridge", { eventBusName: "test" }); const rule = globalThis.$ClassFactory.new("aws-cdk-lib.aws_events.CfnRule", cdk.aws_events.CfnRule, this, "CfnRule", { name: "test", eventBusName: eventBridge.eventBusName, eventPattern: ({}), targets: [({"arn": "", "id": ""})] }); globalThis.$ClassFactory.new("aws-cdk-lib.aws_events.Rule", cdk.aws_events.Rule, this, "Rule", { eventPattern: ({"detailType": [""]}) }); this.function = globalThis.$ClassFactory.new("aws-cdk-lib.aws_lambda.DockerImageFunction", cdk.aws_lambda.DockerImageFunction, this, "DockerImageFunction", ({"code": (cdk.aws_lambda.DockerImageCode.fromImageAsset("./test.ts"))}));翻译后的调用包含四个要素:完全限定名(FQN)(如aws-cdk-lib.aws_events.EventBus)、构造器引用(cdk.aws_events.EventBus)、scope 与 id(this, "EventBridge")以及构造参数对象。最外层globalThis.$ClassFactory.new("aws-cdk-lib.App", cdk.App, )则创建了 CDK 的根 App。
$ClassFactory是 Wing SDK 平台抽象的核心组件,其实现在 packages/@winglang/sdk/src/core/class-factory.ts:
- ClassFactory.new 先调用
tryNewInstance检查是否有平台注册的覆盖实现(override),若平台提供了该 FQN 的实例化逻辑则优先使用;否则回退到new ctor(scope, id, ...args)直接调用原构造器;两者都没有时才抛出Unsupported resource type错误。 - 每个 Wing
App都绑定一个ClassFactory(通过register方法挂在 app 实例上),ClassFactory.of(scope)可从任意构造回溯到所属工厂。 $ClassFactory全局变量由平台管理器在创建 App 时注入:platform-manager.ts 中globalThis.$ClassFactory = this.createClassFactory(),随后在 createApp 时通过classFactory属性传入App。可见 AppProps.classFactory 是 App 必需的组成部分。
这套设计的价值在于:Wing 资源(如cloud.Bucket)在 sim / tf-aws 等不同平台上有不同实现,编译器统一发出$ClassFactory.new(FQN, ctor, ...),再由平台决定是走覆盖实现还是原构造器。对于像aws-cdk-lib这样的纯外部 jsii 库,没有平台覆盖,因此直接实例化原始 CDK 构造——这就是外部构造“开箱即用”的原因。
五、sim 与 tf-aws 目标下的产物差异
对比两份快照可以清楚看到不同目标的差异:
sim 目标(本快照):wing test -t sim在本地模拟器中运行程序,验证的是程序能否被编译、加载并正常执行,因此快照只记录运行器输出(stdout.log),不包含任何基础设施产物。
tf-aws 目标(compile_tf-aws 快照):wing compile -t tf-aws将程序合成为 Terraform JSON 与预检 JS 代码,快照捕获了三个文件:
main.tf.json:仅包含"aws": [{}]的 provider 声明、backend 与 stackName 元数据。这是因为本示例中的 CDK 构造由 CDK 自行管理其资源生命周期,并未注册到 Wing SDK 的 Terraform 资源映射表,因此合成出的 Terraform 模板中除 provider 外没有具体resource条目(注意sanitize_json_paths在写入快照前会移除terraform字段与 cdktf 版本号,见 utils.ts)。preflight.cjs:完整的预检逻辑,即上文分析的$ClassFactory调用序列。inflight.CdkDockerImageFunction-1.cjs:inflight 侧产物。由于类中没有定义任何inflight方法,生成的飞行客户端仅是一个空类骨架,_liftMap中仅包含空数组的$inflight_init——这正对应 sim 快照中(no tests)的结果:没有任何运行时行为需要被模拟或测试。
这一差异说明:快照体系分别验证了“运行正确性”(sim)与“合成产物正确性”(tf-aws)两个维度,二者互补。
六、快照如何生成:Hangar 测试框架的工作流
了解快照的生成流程,能帮助读者判断快照的权威性,也便于修改测试后重新生成快照。Hangar 位于 tools/hangar,核心流程如下:
测试用例生成:tools/hangar/src/generate_tests.ts 递归扫描
tests/valid等语料目录中的.w文件,为每个文件生成一个{file}.test.tsVitest 用例,其中固定包含两个测试:wing compile -t tf-aws(调用compileTest)和wing test -t sim(调用testTest)。执行与产物收集:tools/hangar/src/generated_test_targets.ts 中的实现细节:
compileTest以tf-aws平台运行wing compile,随后收集目标目录.wing子目录下所有文件名包含preflight.、inflight.、extern/、proc、.Struct.js的文件内容(跳过 sourcemap),并检查产物中不存在绝对路径require;testTest仅对入口文件(*.main.w、*.test.w、main.w)执行,以sim平台运行wing test,并将 stdout/stderr 收入fileMap。本文快照正是testTest对入口文件bring_awscdk.test.w的结果。
命令执行与输出清洗:runWingCommand 通过
execa调用编译好的 wing 二进制(路径来自 paths.ts 的wingBin),带--no-update-check --no-analytics参数;其输出再经sanitizeOutput做时变信息归一化(如Duration <DURATION>)。快照落盘与校验:
createMarkdownSnapshot计算快照路径(__snapshots__/test_corpus/{目录}/{文件名}_{testCase}_{target}.md)并写入;测试运行时 Vitest 的toMatchFileSnapshot将其与磁盘上已有快照对比,若不一致则测试失败,提示开发者确认是否为预期变更并更新快照。
因此,bring_awscdk.test.w_test_sim.md不是手写文档,而是wing test -t sim对bring_awscdk.test.w的确定性运行记录,它保证了外部 jsii 库集成用例的结果可被持续回归验证。
七、本地复现与验证
若要在本地复现该快照,需要满足仓库的测试依赖。tests/valid目录是一个独立的 npm 工作区,其 package.json 中显式声明了"aws-cdk-lib": "^2.147.0"(以及constructs、cdktf、jsii-fixture等),这正是bring "aws-cdk-lib"能被解析的前提。典型验证步骤:
- 在仓库根目录按官方开发文档安装依赖并构建 wing CLI;
- 在
tests/valid目录下执行wing test -t sim bring_awscdk.test.w,预期输出与快照中的stdout.log一致(pass ─ ... (no tests)、Tests (0)等); - 执行
wing compile -t tf-aws bring_awscdk.test.w,在target/bring_awscdk.test.tfaws目录下检查main.tf.json、.wing/preflight.cjs与.wing/inflight.*.cjs,应与 compile 快照中的三个产物一致; - 如需整体回归,可进入
tools/hangar运行其 Vitest 测试(Hangar 的入口脚本参见 generate_tests.ts 与 generated_test_targets.ts),快照不匹配时 Vitest 会给出 diff 提示。
需要注意两点:快照中<DURATION>等占位符意味着运行时长等易变信息不会影响比对;而tests/valid/package.json中aws-cdk-lib的版本范围决定了复现结果可能与仓库固化的快照在依赖升级后产生预期差异,这正是快照机制存在的意义——任何非预期的行为变化都会被第一时间暴露。
总结
bring_awscdk.test.w_test_sim.md这份快照虽小,却浓缩了 Wing 工具链的多个核心机制:bring外部 jsii 库的语法与编译翻译、$ClassFactory的平台无关实例化设计、sim / tf-aws 双目标的分工验证,以及 Hangar 对易变输出的确定性归一化。通过源码与快照的对照阅读,可以快速建立起“Wing 程序 → 预检 JS → 平台产物 → 回归快照”的完整认知链路,这也是理解 Wing 仓库测试体系的最佳入口之一。
- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
相关推荐
Wing 语言 `bring` 模块引入实战:从 `@winglibs` 库测试到 Hangar 快照验证
Wing 语言 bring 模块引入实战:从 @winglibs 库测试到 Hangar 快照验证 导读 bring 是 Wing 云编程语言(Wing Clo
编程语言编译器云原生语言运行时Wing 语言类型注解验证解析:以 std 标准库 datetime 与 regex 类型为例(含 Hangar 快照测试机制)
Wing 语言类型注解验证解析:以 std 标准库 datetime 与 regex 类型为例(含 Hangar 快照测试机制) 本文围绕 Wing 云端编程语
编程语言编译器云原生语言运行时Wing 语言 bring 别名机制详解:从 bring_alias 测试与 tf-aws 编译快照看模块导入
Wing 语言 bring 别名机制详解:从 bring_alias 测试与 tf aws 编译快照看模块导入 本篇基于 Wing 开源仓库中的 bring_a
编程语言编译器云原生语言运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考