CHAPTER 02 · Hello World
Hello World 与测试底座
本章只实现一个可测试的 V8 求值器。产品程序输出 Hello World;KJ test runner 验证正常返回和脚本异常。后续每个小步骤都沿用同一套测试命令与结果说明。
本章新增:
Engine。 它拥有 Platform、Isolate 和 Context,并提供唯一公开方法evaluate(source)。把求值从main()抽出来,是为了单元测试不必启动子进程解析 stdout。
本章新增:KJ test runner + CTest。
KJ_TEST定义测试,KJ_EXPECT验证值,KJ_EXPECT_THROW_MESSAGE验证错误;CTest 负责统一发现和运行可执行测试。
本章新增:锁定工具链文件。 CMake 通过
v8-pinned.cmake选择第一章安装的 Clang、libc++、Ninja 和 V8 静态归档。后续命令不会根据当前 shell 的PATH猜测编译器。
1. 建立构建目标
CMakeLists.txt 定义三个目标:可复用 v8lab_engine、产品程序 v8lab、测试程序 engine-test。测试通过 add_test() 注册给 CTest。
完整文件 · ch02/CMakeLists.txt
正在加载源码...export LAB_ROOT=/home/zq/v8v8
CMAKE="$LAB_ROOT/.deps/cmake-3.31.8/bin/cmake"
SOURCE="$LAB_ROOT/v8-mod-tutor/src/code/ch02"
BUILD="$SOURCE/build-v137"
"$CMAKE" -S "$SOURCE" -B "$BUILD" -GNinja \
-DCMAKE_TOOLCHAIN_FILE="$LAB_ROOT/v8-mod-tutor/toolchain/v8-pinned.cmake" \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_PREFIX_PATH="$LAB_ROOT/.deps/v137"
"$CMAKE" --build "$BUILD" --target v8lab engine-test
- 运行
"$CMAKE" --build "$BUILD" --target engine-test- 预期输出
- 最后出现
Linking CXX executable engine-test,且退出码为 0。 - 原因
- Ninja 已使用锁定的 Clang 编译 Engine 和测试,并链接预编译 V8、KJ 与 kj-test;此时还没有执行测试。
2. 实现 Engine 生命周期
头文件只暴露求值接口,隐藏 V8 handle:
完整文件 · ch02/src/engine.h
正在加载源码...实现按 Platform → Isolate → Context 创建,按相反顺序销毁。evaluate() 每次建立 HandleScope 和 ContextScope,使用 TryCatch 将脚本异常转换成 KJ exception。
完整文件 · ch02/src/engine.c++
正在加载源码...- 运行
"$CMAKE" --build "$BUILD" --target v8lab_engine- 预期输出
- 首次构建出现
Linking CXX static library libv8lab_engine.a;重复构建显示ninja: no work to do.。 - 原因
- 第一次输出证明 Engine 编译完成;第二种输出表示输入未改变,Ninja 正确复用了结果。静态库阶段不会产生最终 V8 链接。
3. 输出 Hello World
main() 只创建 Engine、求值并打印;它不复制初始化代码。
完整文件 · ch02/src/main.c++
正在加载源码..."$BUILD/v8lab"
- 预期输出
Hello, World!,只占一行,退出码为 0。- 原因
- V8 计算两个字符串字面量的加法,`Utf8Value` 将结果转换为 C++ 可打印文本。
4. 写第一组单元测试
第一个测试验证返回值;第二个测试验证脚本异常不会终止进程,而会变成带稳定前缀的 KJ exception。
完整文件 · ch02/test/engine-test.c++
正在加载源码..."$BUILD/engine-test"
"$LAB_ROOT/.deps/cmake-3.31.8/bin/ctest" \
--test-dir "$BUILD" --output-on-failure
- 预期输出
- KJ 先分别打印两个
[PASS],末行是2 test(s) passed;CTest 随后打印1/1 Test #1: engine ... Passed和100% tests passed。 - 原因
- 正常求值和异常转换两个
KJ_TEST都通过;CTest 按测试可执行文件统计,所以它显示 1 个 CTest,而不是 2 个 KJ case。
5. 以后每一步怎么测
后续章节保持三层测试:
- 纯 C++ 单元测试:状态机、编码、队列和限制。
- KJ 内存 I/O 测试:timer、HTTP 和 WebSocket,不绑定真实端口。
- 章节验收:运行完整服务,用 curl 或浏览器验证用户行为。
每个小步骤都给出预期输出和形成原因。若输出不同,先停在当前步骤修正,不要继续叠加模块。