8 个稳定版本

1.3.4 2024年8月5日
1.3.2 2024年3月3日
1.3.1 2023年10月1日
1.3.0 2023年6月11日
1.0.0 2022年10月2日

#12 in 模拟器

Download history 35/week @ 2024-07-29 123/week @ 2024-08-05

每月158次下载

MIT 许可证

39KB
965

evunit

这是一个用于 Game Boy ROM 的单元测试应用程序。它只包含 CPU 模拟器;没有 PPU、内存映射器或 I/O。通过使用真实二进制文件作为输入,您可以在完成 ROM 上运行单元测试,而无需重新构建。

命令行工具从 TOML 文件加载测试配置。您也可以将其用作 Rust 库,并从 Rust 代码配置您的测试。

变更日志

安装

cargo安装 evunit

配置测试

在测试配置中,您可以为您要运行的每个测试创建一个标题,并为寄存器分配默认值和预期值。第一个标题(例如,add-one)确定初始状态,而 ".result" 标题(例如,add-one.result)描述预期结果。如果预期结果与最终状态不匹配,则测试将失败。

[add-one]
b = 1
[add-one.result]
a = 2

[add-two]
b = 2
[add-two.result]
a = 3

您可以将任何 CPU 寄存器分配给一个整数。此外,如果加载了 symfile,16 位寄存器可以分配一个带引号的标签。要确定每个测试中应运行哪个函数,请将标签分配给 pc。可能的寄存器有

  • a
  • b
  • c
  • d
  • e
  • h
  • l
  • bc
  • de
  • hl
  • pc
  • sp

此外,标志可以分配给 truefalse。可能的标志有

  • f.z
  • f.n
  • f.h
  • f.c

请注意,由于点,标志必须在配置文件中 带引号

"f.z" = false

最后,您可以通过在配置文件中将标签名称或地址用方括号括起来来为内存分配一个值。您可以分配 8 位整数、字符串*或它们的数组。像标志一样,由于方括号,内存地址必须带引号。

# Writes a string to wString, followed by a 0
"[wString]" = ["Hello, world!", 0]

# Writes a series of bytes to WRAM0
"[0xC000]" = [ 0x01, 0x02, 0x03, 0x04 ]

* = 注意,字符串将被转换为它们的 ASCII 表示。包含非 ASCII 字符的字符串将返回错误。

全局配置

有时您有一些应该应用于所有测试的配置,例如全局变量或栈指针。文件顶部的配置(在标题之前)都是全局的,并应用于所有测试。

sp = "wStack.end"

[my-test]
pc = "MyTest"
a = 42
[my-test.result]
b = 42

如果测试结果不存在,除非测试崩溃,否则测试总是通过。

手动创建一组详尽的测试可能很繁琐,所以请记住,您可以使用Rust中的evunit库来生成测试:

use evunit::prelude::*;
use std::{path::Path, process::exit};

let rom = "bin.gb";
let sym = Some(Path::new("bin.sym"));
let symfile = read_symfile(sym);

let mut tests = Vec::new();

for i in 0..8 {
	let mut test = TestConfig::new(format!("my-test{i}"));

	// Initial state
	test.initial = Registers::new()
		.with_pc(symfile["GetBitA"].0 as u16)
		.with_a(i);

	// Expected state
	test.result = Some(Registers::new()
		.with_a(i));

	tests.push(test);
}

let result = run_tests(&rom, &tests, SilenceLevel::Passing);

if result.is_err() {
	exit(1);
}

然后将其输入evunit。您可以使用-从stdin读取。

./config_generator | evunit -c - bin/rom.gb

您还可以始终使用cat将手写的文件添加到混合中。

./config_generator | cat config.toml - | evunit -c - bin/rom.gb

终止测试

当达到崩溃地址、测试超时或pc等于配置文件中指定的caller(默认为0xFFFF)时,测试完成。evunit在运行测试之前将caller值推送到栈上,这意味着在大多数情况下,一个ret将结束测试。当成功达到caller值时,evunit将检查结果是否符合预期。

配置选项

除了寄存器外,还有一些其他选项可以配置。所有这些都可以全局配置,也可以按测试配置。

caller

设置调用地址。当测试开始时,该地址被推送到栈上,允许ret结束测试。

caller = "Main"

默认情况下,caller设置为0xFFFF

crash

将地址标记为“崩溃”,如果pc达到它,则测试失败。这对于崩溃处理函数(如rst $38)很有用。

crash = 0x38

也可以使用值数组。

crash = [0x38, "crash"]

enable-breakpoints

启用或禁用在执行ld b, bld d, d后打印寄存器信息。默认启用。此配置只能全局使用。

enable-breakpoints = true
enable-breakpoints = false

exit

将地址标记为“退出”,如果pc达到它,则测试结束。然后将验证结果。

exit = "SomeFunction.exit"

也可以使用值数组。

timeout

设置测试失败前的最大周期数。如果您有易于陷入无限循环或需要极长时间完成的代码,这很有用。默认值为65536。

timeout = 65536

stack

指定在测试运行之前推送到栈上的数据(在推送到caller之前)。一些函数期望它们的参数位于栈上,因此此选项允许这样做。您可以分配一个8位整数、一个字符串或它们的数组。

stack = [ 0x04, 0x71, 0xff, "\n" ]

请注意,值是反向推送到栈上的。例如,上述示例的初始栈值如下(假设sp = 0xD000):

| Address | Data         |
| ------- | ------------ |
| 0xCFFF  | 0x0A         |
| 0xCFFF  | 0xFF         |
| 0xCFFF  | 0x71         |
| 0xCFFE  | 0x04         |
| 0xCFFD  | high(caller) |
| 0xCFFC  | low(caller)  |

诊断失败

当测试失败时,根据失败原因输出一些CPU寄存器,以帮助您诊断问题。然而,有时您还需要检查内存的状态;这可以通过使用--dump-dir--d)标志来完成。将目录传递给此标志,当任何测试失败时,将内存的文本转储放置在提供的目录中。

evunit -c fail.toml -d dump/ rom.gb

转储只是每个内存类型的大列表,并带有标题。

[WRAM 0]
[WRAM]
0xc000: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc020: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc030: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc040: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc050: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc060: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
...

依赖关系

~2.2–3MB
~58K SLoC