20个不稳定版本 (4个重大更改)
| 0.8.0 | 2024年7月27日 |
|---|---|
| 0.7.2 | 2024年6月10日 |
| 0.7.0 | 2024年2月25日 |
| 0.6.1 | 2023年12月9日 |
| 0.5.4 | 2023年11月16日 |
#1183 in 魔法豆
每月下载量287次
290KB
5.5K SLoC
bulloak
基于分支树技术的Solidity测试生成器。
[!WARNING] 注意,
bulloak仍处于0.*.*版本,因此可能随时发生破坏性更改。如果您必须依赖bulloak,我们建议将其锁定到特定版本,即=0.y.z。
安装
cargo install bulloak
VSCode
以下VSCode扩展不是必需的,但它们可以提供更好的用户体验
- Solidity Inspector - 为
.tree文件提供语法高亮显示 - Ascii Tree Generator:方便生成ASCII树
使用方法
bulloak实现了两个命令
bulloak scaffoldbulloak check
生成Solidity文件
假设您有一个包含以下内容的foo.tree文件
FooTest
└── When stuff is called // Comments are supported.
└── When a condition is met
└── It should revert.
└── Because we shouldn't allow it.
您可以使用bulloak scaffold生成一个Solidity合约,其中包含与foo.tree中描述的规范相匹配的修饰符和测试。以下内容将打印到stdout
// $ bulloak scaffold foo.tree
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;
contract FooTest {
modifier whenStuffIsCalled() {
_;
}
function test_RevertWhen_AConditionIsMet() external whenStuffIsCalled {
// It should revert.
// Because we shouldn't allow it.
}
}
您可以使用-w选项将生成的合约写入文件系统。假设我们在当前工作目录中有一堆.tree文件。如果我们运行以下
$ bulloak scaffold -w ./**/*.tree
bulloak 将为每个 .tree 文件创建一个 .t.sol 文件,并将生成的内容写入其中。
如果一个 .t.sol 文件的标题与同一目录中的 .tree 匹配,那么 bulloak 将跳过写入该文件。但是,您可以使用 -f 标志来覆盖此行为。这将强制 bulloak 覆盖文件的内容。
$ bulloak scaffold -wf ./**/*.tree
请注意,当测试体为空时,所有测试都显示为通过。为防止这种情况,您可以使用 -S(或 --vm-skip)选项在每个测试函数的开始处添加一个 vm.skip(true);。此选项还将添加对 forge-std 的 Test.sol 的导入,并且所有测试合约都将继承它。
您可以通过传递 -m(或 --skip--modifiers)选项来跳过修改符的生成。这样,生成的文件将只包含测试函数。
检查您的代码和规范是否匹配
您可以使用 bulloak check 确保您的 Solidity 文件与规范匹配。例如,任何缺失的测试都会向您报告。
如果您有如下规范
HashPairTest
├── It should never revert.
├── When first arg is smaller than second arg
│ └── It should match the result of `keccak256(abi.encodePacked(a,b))`.
└── When first arg is bigger than second arg
└── It should match the result of `keccak256(abi.encodePacked(b,a))`.
以及相应的 Solidity 文件
pragma solidity 0.8.0;
contract HashPairTest {
function test_ShouldNeverRevert() external {
// It should never revert.
}
function test_WhenFirstArgIsSmallerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(a,b))`.
}
}
此 Solidity 文件缺少分支 When first arg is bigger than second arg 的测试,这将运行 bulloak check tests/scaffold/basic.tree 后报告。
warn: function "test_WhenFirstArgIsBiggerThanSecondArg" is missing in .sol
+ fix: run `bulloak check --fix tests/scaffold/basic.tree`
--> tests/scaffold/basic.tree:5
warn: 1 check failed (run `bulloak check --fix <.tree files>` to apply 1 fix)
如上所述,bulloak 可以自动修复此问题。如果我们使用带有 --stdout 标志的命令运行,输出将是
--> tests/scaffold/basic.t.sol
pragma solidity 0.8.0;
contract HashPairTest {
function test_ShouldNeverRevert() external {
// It should never revert.
}
function test_WhenFirstArgIsSmallerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(a,b))`.
}
function test_WhenFirstArgIsBiggerThanSecondArg() external {
// It should match the result of `keccak256(abi.encodePacked(b,a))`.
}
}
<--
success: 1 issue fixed.
不带 --stdout 标志运行命令将使用修复后的内容覆盖 Solidity 文件的内容。请注意,并非所有问题都可以自动修复,bulloak 的输出将反映这一点。
warn: 13 checks failed (run `bulloak check --fix <.tree files>` to apply 11 fixes)
您可以通过传递 -m(或 --skip--modifiers)选项来跳过检查修改符的存在。这样,bulloak 将不会警告生成文件中缺少修改符。
规则
以下规则目前正在实施
- 匹配的规范文件必须存在且可读。
- 规范和 Solidity 文件匹配,如果它们的名字之间的区别只是
.tree&.t.sol。
- 规范和 Solidity 文件匹配,如果它们的名字之间的区别只是
- Solidity 文件中有一个合同,其名称与规范根节点匹配。
- 每个构造,如由
bulloak scaffold生成,都必须存在于 Solidity 文件中。 - 每个构造的顺序,如由
bulloak scaffold生成,必须与规范顺序匹配。- 允许任何有效的 Solidity 构造,并且只检查由
bulloak scaffold生成的构造。这意味着可以添加任意数量的额外函数、修改符等。到文件中。
- 允许任何有效的 Solidity 构造,并且只检查由
编译器错误
bulloak 的另一个功能是报告输入树中的错误。
例如,假设您有一个存在错误的 foo.tree 文件,它缺少一个 └ 字符。运行 bulloak scaffold foo.tree 会报告如下错误
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
bulloak error: unexpected `when` keyword
── when the id references a null stream
^^^^
--- (line 2, column 4) ---
file: foo.tree
树
bulloak scaffold 根据遵循 分支树技术 的 .tree 规范构建 Solidity 测试文件。
目前,有关如何处理不同边缘情况以更好地赋能 Solidity 社区的讨论正在进行中。本节是关于编译器当前实现的描述。
术语
- 条件:树的
when/given分支。 - 动作:树的
it分支。 - 动作描述:动作的子节点。
规范
每个 tree 文件应描述至少一个待测试函数。树遵循以下规则
- 第一行是根树标识符,由合约和函数名组成,应以双冒号分隔。
bulloak预期您使用├和└字符来表示分支。- 如果一个分支以
when或given开头,则它是一个条件。when和given可以互换。
- 如果一个分支以
it开头,则它是一个动作。- 动作的任何子分支都称为动作描述。
- 关键字不区分大小写:
it与It和IT相同。 - 以
//开头的任何内容都是注释,并将从输出中删除。 - 可以在同一文件中定义多个树,以描述不同的函数,按照相同的规则进行,用两个新行分隔。
以下是一个 Solidity 函数
function hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? hash(a, b) : hash(b, a);
}
上述函数的合理规范可能是
HashPairTest
├── It should never revert.
├── When first arg is smaller than second arg
│ └── It should match the result of `keccak256(abi.encodePacked(a,b))`.
└── When first arg is bigger than second arg
└── It should match the result of `keccak256(abi.encodePacked(b,a))`.
存在一个顶层动作,它将生成一个测试来检查函数不变量,即它永远不会回滚。
然后,我们有两种可能的先决条件:a < b 和 a >= b。两个分支都以一个动作结束,该动作将使 bulloak scaffold 生成相应的测试。
请注意以下几点
- 动作以句点结尾,但条件不是。这是因为动作支持任何字符,但条件不支持。由于条件被转换为修饰符,它们必须是有效的 Solidity 标识符。
- 可以没有条件地进行顶层动作。目前,
bulloak也支持具有兄弟条件的动作,但根据此 讨论,这可能会在未来版本中删除。 - 树的根将被输出为测试合约的名称。
假设您有其他您想在同一测试合约中测试的 Solidity 函数,例如 Utils 在 utils.t.sol 中的 Utils
function min(uint256 a, uint256 b) private pure returns (uint256) {
return a < b ? a : b;
}
function max(uint256 a, uint256 b) private pure returns (uint256) {
return a > b ? a : b;
}
上述所有函数的完整规范可能是
Utils::hashPair
├── It should never revert.
├── When first arg is smaller than second arg
│ └── It should match the result of `keccak256(abi.encodePacked(a,b))`.
└── When first arg is bigger than second arg
└── It should match the result of `keccak256(abi.encodePacked(b,a))`.
Utils::min
├── It should never revert.
├── When first arg is smaller than second arg
│ └── It should match the value of `a`.
└── When first arg is bigger than second arg
└── It should match the value of `b`.
Utils::max
├── It should never revert.
├── When first arg is smaller than second arg
│ └── It should match the value of `b`.
└── When first arg is bigger than second arg
└── It should match the value of `a`.
请注意以下几点
- 合约标识符必须在所有根中存在。
- 如果后续树中缺少合约标识符,或与第一个树的根标识符不匹配,则会导致
bulloak错误。这种违规行为目前无法通过bulloak check --fix进行修复,因此需要手动更正。 - 在转换为Solidity修饰符时,不同树之间的重复条件将被去重。
- 每个树的根标识符的部分函数将被作为Solidity测试名称的一部分发出(例如,
test_MinShouldNeverRevert)。
输出
关于生成的Solidity测试,有以下几点需要注意
- 合约文件名与
.tree相同,但文件扩展名为.t.sol。例如,test.tree对应于test.t.sol。 - 测试的发出顺序与它们在
.tree文件中对应动作出现的顺序相同。 - 除叶子条件节点外,我们为每个条件生成一个修饰符。
- 测试名称遵循Foundry的最佳实践。
许可
本项目许可协议为以下之一
- Apache License,版本2.0,(LICENSE-APACHE或https://apache.ac.cn/licenses/LICENSE-2.0)。
- MIT许可协议(LICENSE-MIT或https://open-source.org.cn/licenses/MIT)。
依赖项
~27–44MB
~735K SLoC