bulloak
A simple, fast, and easy-to-use Solidity test generator based on the Branching Tree Technique.
Installation
Usage
Say you have a foo.tree file with the following contents:
FooTest
└── When stuff called
└── It should revert.
If you pass it to bulloak like so, you will get the skeleton
of a test contract printed to stdout:
$ bulloak foo.tree
pragma solidity 0.8.0;
contract FooTest {
modifier whenStuffCalled() {
_;
}
function test_RevertWhen_StuffCalled()
external
whenStuffCalled
{
// It should revert.
}
}
Scaffold Multiple Trees
If you are working in a solidity project and you have
multiple trees you want to scaffold, you can use the -w option.
$ bulloak -w ./**/*.tree
This will create solidity files with the same name as the .tree
files with the result of scaffolding each tree.
If there exists a file with a title that matches the name at the
root node of the .tree, then bulloak will skip writing that file.
However, you may override this behaviour with the -f flag. This
will write to the filesystem overwriting any files that exist.
$ bulloak -wf ./**/*.tree
Compiler Errors
Another feature of bulloak is reporting errors in your input trees.
For example, say you have a buggy foo.tree file, which is missing a
└ character. Running bulloak foo.tree would report the error like this:
•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
bulloak error: unexpected `when` keyword
── when the id references a null stream
^^^^
file: foo.tree
CLI Options
Usage: bulloak [OPTIONS] [FILES]...
Arguments:
[FILES]... .tree files to process
Options:
-c
Whether to print `it` branches as comments in the output code
-i <INDENT>
The indentation of the output code [default: 2]
-w, --write-files
Whether to write to files instead of stdout
-f, --force-write
When `write_files` is specified, use `--force-write` to overwrite the output files
-s, --solidity-version <SOLIDITY_VERSION>
Sets a solidity version for the test contracts [default: 0.8.0]
-h, --help
Print help (see more with '--help')
-V, --version
Print version
Trees
bulloak scaffolds solidity test files based on .tree specifications
that follow the Branching Tree Technique.
Currently, there is on-going discussion on how to handle different edge-cases to better empower the solidity community. This section is a description of the current implementation of the compiler.
Terminology
- Condition:
when/givenbranches of a tree. - Action:
itbranches of a tree. Every action is a leaf node of the tree.
Spec
Each tree file should describe a function under test. Trees follow these rules:
- The line at the top of the file is the name of the contract.
bulloakexpects you to use├and└characters to denote branches.- Every branch must start with one of
when,givenorit. - If a branch starts with either
whenorgiven, it is a condition. whenandgivenare interchangeable.- If a branch starts with
it, it is an action. - Keywords are case-insensitive:
itis the same asItandIT.
Take the following solidity function:
function hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? hash(a, b) : hash(b, a);
}
A reasonable spec for the above function would be:
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))`.
There is a top-level action which would generate a test to check the function invariant that it should never revert.
Then, we have the two possible preconditions: a < b and a >= b. Both branches end in an action that will make bulloak generate the respective test.
Note the following things:
- Actions are written with ending dots but conditions are not. This is because actions support any character, but conditions don't. Since conditions are transformed into modifiers, they have to be valid solidity identifiers.
- You can have top-level actions without conditions. Currently,
bulloakalso supports actions with sibling conditions, but this might get removed in a future version per this discussion. - The root of the tree will be emitted as the name of the test contract.
Output
There are a few things to keep in mind about the scaffolded solidity test:
- The contract filename is the same as the
.treebut with a.t.solextension. E.g.test.treewould correspond totest.t.sol. - Test are emitted in the order their corresponding actions appear in the
.treefile. - Currently, we generate one modifier per condition, but this might change per this discussion.
- Test names follow Foundry's best practices.
Contributing
Please refer to CONTRIBUTING.md.
License
This project is licensed under either of:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0).
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT).