#tree #solidity #testing #compiler #cli

app bulloak

基于分支树技术的Solidity测试生成器

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 魔法豆

Download history 124/week @ 2024-04-15 14/week @ 2024-04-22 23/week @ 2024-05-06 41/week @ 2024-05-13 10/week @ 2024-05-20 6/week @ 2024-06-03 134/week @ 2024-06-10 7/week @ 2024-06-17 1/week @ 2024-06-24 165/week @ 2024-07-01 127/week @ 2024-07-08 13/week @ 2024-07-15 111/week @ 2024-07-22 35/week @ 2024-07-29

每月下载量287次

MIT/Apache

290KB
5.5K SLoC

bulloak

基于分支树技术的Solidity测试生成器。

[!WARNING] 注意,bulloak仍处于0.*.*版本,因此可能随时发生破坏性更改。如果您必须依赖bulloak,我们建议将其锁定到特定版本,即=0.y.z

安装

cargo install bulloak

VSCode

以下VSCode扩展不是必需的,但它们可以提供更好的用户体验

使用方法

bulloak实现了两个命令

  • bulloak scaffold
  • bulloak 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 文件中有一个合同,其名称与规范根节点匹配。
  • 每个构造,如由 bulloak scaffold 生成,都必须存在于 Solidity 文件中。
  • 每个构造的顺序,如由 bulloak scaffold 生成,必须与规范顺序匹配。
    • 允许任何有效的 Solidity 构造,并且只检查由 bulloak scaffold 生成的构造。这意味着可以添加任意数量的额外函数、修改符等。到文件中。

编译器错误

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 预期您使用 字符来表示分支。
  • 如果一个分支以 whengiven 开头,则它是一个条件。
    • whengiven 可以互换。
  • 如果一个分支以 it 开头,则它是一个动作。
    • 动作的任何子分支都称为动作描述。
  • 关键字不区分大小写:itItIT 相同。
  • // 开头的任何内容都是注释,并将从输出中删除。
  • 可以在同一文件中定义多个树,以描述不同的函数,按照相同的规则进行,用两个新行分隔。

以下是一个 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 < ba >= b。两个分支都以一个动作结束,该动作将使 bulloak scaffold 生成相应的测试。

请注意以下几点

  • 动作以句点结尾,但条件不是。这是因为动作支持任何字符,但条件不支持。由于条件被转换为修饰符,它们必须是有效的 Solidity 标识符。
  • 可以没有条件地进行顶层动作。目前,bulloak 也支持具有兄弟条件的动作,但根据此 讨论,这可能会在未来版本中删除。
  • 树的根将被输出为测试合约的名称。

假设您有其他您想在同一测试合约中测试的 Solidity 函数,例如 Utilsutils.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的最佳实践

许可

本项目许可协议为以下之一

依赖项

~27–44MB
~735K SLoC