{"id":21120922,"url":"https://github.com/alexfertel/bulloak","last_synced_at":"2025-05-15T04:05:08.314Z","repository":{"id":186143096,"uuid":"674393599","full_name":"alexfertel/bulloak","owner":"alexfertel","description":"Generate tests based on the Branching Tree Technique.","archived":false,"fork":false,"pushed_at":"2025-05-06T13:06:41.000Z","size":1298,"stargazers_count":324,"open_issues_count":15,"forks_count":19,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-10T00:34:40.428Z","etag":null,"topics":["cli","solidity","testing","tree"],"latest_commit_sha":null,"homepage":"https://bulloak.dev","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexfertel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-03T20:55:39.000Z","updated_at":"2025-05-09T22:09:51.000Z","dependencies_parsed_at":"2024-01-12T15:49:30.990Z","dependency_job_id":"437efd7e-5f82-4566-9fee-b3fcee39dbce","html_url":"https://github.com/alexfertel/bulloak","commit_stats":{"total_commits":138,"total_committers":5,"mean_commits":27.6,"dds":"0.036231884057971064","last_synced_commit":"1ad73955b8b6ee5b621dee29474fbe0f84af8dc3"},"previous_names":["alexfertel/bulloak"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfertel%2Fbulloak","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfertel%2Fbulloak/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfertel%2Fbulloak/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexfertel%2Fbulloak/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexfertel","download_url":"https://codeload.github.com/alexfertel/bulloak/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253346998,"owners_count":21894276,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","solidity","testing","tree"],"created_at":"2024-11-20T03:21:53.347Z","updated_at":"2025-05-15T04:05:03.300Z","avatar_url":"https://github.com/alexfertel.png","language":"Rust","funding_links":[],"categories":["Tools"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://github.com/user-attachments/assets/036bad22-3b0d-4ea3-9338-faecd017a290\" width=\"200\"\u003e\u003c/a\u003e\n    \u003cbr\u003e\n    \u003ca href=\"https://crates.io/crates/bulloak/\"\u003e\n        \u003cimg src=\"https://img.shields.io/crates/v/bulloak?style=flat\u0026labelColor=1C2C2E\u0026color=C96329\u0026logo=Rust\u0026logoColor=white\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://codecov.io/gh/alexfertel/bulloak\"\u003e\n        \u003cimg src=\"https://codecov.io/github/alexfertel/bulloak/coverage.svg?branch=main\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n# bulloak\n\nA Solidity test generator based on the\n[Branching Tree Technique](https://twitter.com/PaulRBerg/status/1682346315806539776).\n\n- [Installation](#installation)\n  - [VSCode](#vscode)\n- [Usage](#usage)\n  - [`bulloak scaffold`](#scaffold-solidity-files)\n  - [`bulloak check`](#check-that-your-code-and-spec-match)\n    - [Rules](#rules)\n  - [Compiler Errors](#compiler-errors)\n- [Trees](#trees)\n  - [Terminology](#terminology)\n  - [Spec](#spec)\n- [Output](#output)\n- [Examples](#examples)\n- [Contributing](#contributing)\n- [Publishing](#publishing)\n- [Supported By](#supported-by)\n- [License](#license)\n\n\u003e [!WARNING]\n\u003e Note that `bulloak` is still `0.*.*`, so breaking changes\n\u003e [may occur at any time](https://semver.org/#spec-item-4). If you must depend\n\u003e on `bulloak`, we recommend pinning to a specific version, i.e., `=0.y.z`.\n\n## Installation\n\n```bash\ncargo install bulloak\n```\n\n### VSCode\n\nThe following VSCode extensions are not essential but they are recommended for a\nbetter user experience:\n\n- [Solidity Inspector](https://marketplace.visualstudio.com/items?itemName=PraneshASP.vscode-solidity-inspector) - syntax highlighting for `.tree` files\n- [Ascii Tree Generator](https://marketplace.visualstudio.com/items?itemName=aprilandjan.ascii-tree-generator):\n  convenient way to generate ASCII trees\n\n## Usage\n\n`bulloak` implements two commands:\n\n- `bulloak scaffold`\n- `bulloak check`\n\n### Scaffold Solidity Files\n\nSay you have a `foo.tree` file with the following contents:\n\n```tree\nFooTest\n└── When stuff is called // Comments are supported.\n    └── When a condition is met\n        └── It should revert.\n            └── Because we shouldn't allow it.\n```\n\nYou can use `bulloak scaffold` to generate a Solidity contract containing\nmodifiers and tests that match the spec described in `foo.tree`. The following\nwill be printed to `stdout`:\n\n```solidity\n// $ bulloak scaffold foo.tree\n// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.8.0;\n\ncontract FooTest {\n    modifier whenStuffIsCalled() {\n        _;\n    }\n\n    function test_RevertWhen_AConditionIsMet() external whenStuffIsCalled {\n        // It should revert.\n        //     Because we shouldn't allow it.\n    }\n}\n```\n\nYou can use the `-w` option to write the generated contracts to the file system.\nSay we have a bunch of `.tree` files in the current working directory. If we run\nthe following:\n\n```text\n$ bulloak scaffold -w ./**/*.tree\n```\n\n`bulloak` will create a `.t.sol` file per `.tree` file and write the generated\ncontents to it.\n\nIf a `.t.sol` file's title matches a `.tree` in the same directory, then\n`bulloak` will skip writing to that file. However, you may override this\nbehaviour with the `-f` flag. This will force `bulloak` to overwrite the\ncontents of the file.\n\n```text\n$ bulloak scaffold -wf ./**/*.tree\n```\n\nNote all tests are showing as passing when their body is empty. To prevent this,\nyou can use the `-S` (or `--vm-skip`) option to add a `vm.skip(true);` at the\nbeginning of each test function. This option will also add an import for\nforge-std's `Test.sol` and all test contracts will inherit from it.\n\nYou can skip emitting the modifiers by passing the `-m` (or `--skip--modifiers`)\noption. This way, the generated files will only include the test functions.\n\n### Check That Your Code And Spec Match\n\nYou can use `bulloak check` to make sure that your Solidity files match your\nspec. For example, any missing tests will be reported to you.\n\nSay you have the following spec:\n\n```tree\nHashPairTest\n├── It should never revert.\n├── When first arg is smaller than second arg\n│   └── It should match the result of `keccak256(abi.encodePacked(a,b))`.\n└── When first arg is bigger than second arg\n    └── It should match the result of `keccak256(abi.encodePacked(b,a))`.\n```\n\nAnd a matching Solidity file:\n\n```solidity\npragma solidity 0.8.0;\n\ncontract HashPairTest {\n  function test_ShouldNeverRevert() external {\n    // It should never revert.\n  }\n\n  function test_WhenFirstArgIsSmallerThanSecondArg() external {\n    // It should match the result of `keccak256(abi.encodePacked(a,b))`.\n  }\n}\n```\n\nThis Solidity file is missing the tests for the branch\n`When first arg is bigger than second arg`, which would be reported after\nrunning `bulloak check tests/scaffold/basic.tree`, like so:\n\n```text\nwarn: function \"test_WhenFirstArgIsBiggerThanSecondArg\" is missing in .sol\n     + fix: run `bulloak check --fix tests/scaffold/basic.tree`\n   --\u003e tests/scaffold/basic.tree:5\n\nwarn: 1 check failed (run `bulloak check --fix \u003c.tree files\u003e` to apply 1 fix)\n```\n\nAs you can see in the above message, `bulloak` can fix the issue automatically.\nIf we run the command with the `--stdout` flag, the output is:\n\n```solidity\n--\u003e tests/scaffold/basic.t.sol\npragma solidity 0.8.0;\n\ncontract HashPairTest {\n    function test_ShouldNeverRevert() external {\n        // It should never revert.\n    }\n\n    function test_WhenFirstArgIsSmallerThanSecondArg() external {\n        // It should match the result of `keccak256(abi.encodePacked(a,b))`.\n    }\n\n    function test_WhenFirstArgIsBiggerThanSecondArg() external {\n        // It should match the result of `keccak256(abi.encodePacked(b,a))`.\n    }\n}\n\u003c--\n\nsuccess: 1 issue fixed.\n```\n\nRunning the command without the `--stdout` flag will overwrite the contents of\nthe solidity file with the fixes applied. Note that not all issues can be\nautomatically fixed, and bulloak's output will reflect that.\n\n```text\nwarn: 13 checks failed (run `bulloak check --fix \u003c.tree files\u003e` to apply 11 fixes)\n```\n\nYou can skip checking that the modifiers are present by passing the `-m`\n(or `--skip--modifiers`) option. This way, `bulloak` will not warn when a\nmodifier is missing from the generated file.\n\n#### Rules\n\nThe following rules are currently implemented:\n\n- A Solidity file matching the spec file must exist and be readable.\n  - The spec and the Solidity file match if the difference between their names\n    is only `.tree` \u0026 `.t.sol`.\n- There is a contract in the Solidity file and its name matches the root node of\n  the spec.\n- Every construct, as it would be generated by `bulloak scaffold`, is present in\n  the Solidity file.\n- The order of every construct, as it would be generated by `bulloak scaffold`,\n  matches the spec order.\n  - Any valid Solidity construct is allowed and only constructs that would be\n    generated by `bulloak scaffold` are checked. This means that any number of\n    extra functions, modifiers, etc. can be added to the file.\n\n### Compiler Errors\n\nAnother feature of `bulloak` is reporting errors in your input trees.\n\nFor example, say you have a buggy `foo.tree` file, which is missing a `└`\ncharacter. Running `bulloak scaffold foo.tree` would report the error like this:\n\n```text\n•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••\nbulloak error: unexpected `when` keyword\n\n── when the id references a null stream\n   ^^^^\n\n--- (line 2, column 4) ---\nfile: foo.tree\n```\n\n## Trees\n\n`bulloak scaffold` scaffolds Solidity test files based on `.tree` specifications\nthat follow the\n[Branching Tree Technique](https://twitter.com/PaulRBerg/status/1682346315806539776).\n\nCurrently, there is on-going\n[discussion](https://github.com/alexfertel/bulloak/discussions) on how to handle\ndifferent edge-cases to better empower the Solidity community. This section is a\ndescription of the current implementation of the compiler.\n\n### Terminology\n\n- _Condition_: `when/given` branches of a tree.\n- _Action_: `it` branches of a tree.\n- _Action Description_: Children of an action.\n\n### Spec\n\nEach `tree` file should describe at least one function under test. Trees follow\nthese rules:\n\n- The first line is the root tree identifier, composed of the contract and\n  function names which should be delimited by a double colon.\n- `bulloak` expects you to use `├` and `└` characters to denote branches.\n- If a branch starts with either `when` or `given`, it is a condition.\n  - `when` and `given` are interchangeable.\n- If a branch starts with `it`, it is an action.\n  - Any child branch an action has is called an action description.\n- Keywords are case-insensitive: `it` is the same as `It` and `IT`.\n- Anything starting with a `//` is a comment and will be stripped from the\n  output.\n- Multiple trees can be defined in the same file to describe different functions\n  by following the same rules, separating them with two newlines.\n\nTake the following Solidity function:\n\n```solidity\nfunction hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {\n    return a \u003c b ? hash(a, b) : hash(b, a);\n}\n```\n\nA reasonable spec for the above function would be:\n\n```tree\nHashPairTest\n├── It should never revert.\n├── When first arg is smaller than second arg\n│   └── It should match the result of `keccak256(abi.encodePacked(a,b))`.\n└── When first arg is bigger than second arg\n    └── It should match the result of `keccak256(abi.encodePacked(b,a))`.\n```\n\nThere is a top-level action that will generate a test to check the function\ninvariant that it should never revert.\n\nThen, we have the two possible preconditions: `a \u003c b` and `a \u003e= b`. Both\nbranches end in an action that will make `bulloak scaffold` generate the\nrespective test.\n\nNote the following things:\n\n- Actions are written with ending dots but conditions are not. This is because\n  actions support any character, but conditions don't. Since conditions are\n  transformed into modifiers, they have to be valid Solidity identifiers.\n- You can have top-level actions without conditions. Currently, `bulloak` also\n  supports actions with sibling conditions, but this might get removed in a\n  future version per this\n  [discussion](https://github.com/alexfertel/bulloak/issues/22).\n- The root of the tree will be emitted as the name of the test contract.\n\nSuppose you have additional Solidity functions that you want to test in the same\ntest contract, say `Utils` within `utils.t.sol`:\n\n```solidity\nfunction min(uint256 a, uint256 b) private pure returns (uint256) {\n    return a \u003c b ? a : b;\n}\n\nfunction max(uint256 a, uint256 b) private pure returns (uint256) {\n    return a \u003e b ? a : b;\n}\n```\n\nThe full spec for all the above functions would be:\n\n```tree\nUtils::hashPair\n├── It should never revert.\n├── When first arg is smaller than second arg\n│   └── It should match the result of `keccak256(abi.encodePacked(a,b))`.\n└── When first arg is bigger than second arg\n    └── It should match the result of `keccak256(abi.encodePacked(b,a))`.\n\n\nUtils::min\n├── It should never revert.\n├── When first arg is smaller than second arg\n│   └── It should match the value of `a`.\n└── When first arg is bigger than second arg\n    └── It should match the value of `b`.\n\n\nUtils::max\n├── It should never revert.\n├── When first arg is smaller than second arg\n│   └── It should match the value of `b`.\n└── When first arg is bigger than second arg\n    └── It should match the value of `a`.\n```\n\nNote the following things:\n\n- Contract identifiers must be present in all roots.\n- Contract identifiers that are missing from subsequent trees, or otherwise\n  mismatched from the first tree root identifier, will cause `bulloak` to error.\n  This violation is not currently fixable with `bulloak check --fix` so will\n  need to be manually corrected.\n- Duplicate conditions between separate trees will be deduplicated when\n  transformed into Solidity modifiers.\n- The function part of the root identifier for each tree will be emitted as part\n  of the name of the Solidity test (e.g. `test_MinShouldNeverRevert`).\n\n## Output\n\nThere are a few things to keep in mind about the scaffolded Solidity test:\n\n- The contract filename is the same as the `.tree` but with a `.t.sol`\n  extension. E.g. `test.tree` would correspond to `test.t.sol`.\n- Tests are emitted in the order their corresponding actions appear in the\n  `.tree` file.\n- We generate one modifier per condition, except for leaf condition nodes.\n- Test names follow\n  [Foundry's best practices](https://book.getfoundry.sh/tutorials/best-practices?highlight=best#tests).\n\n## Examples\n\nYou can find practical examples of using BTT here:\n\n- [BTT Examples by Paul Berg](https://github.com/PaulRBerg/btt-examples)\n- [CreateX](https://github.com/pcaversaccio/createx/tree/main)\n\n## Contributing\n\nPlease refer to [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## Publishing\n\nThese are the current steps taken to publish `bulloak`:\n\n- Bump the version field in [Cargo.toml](./Cargo.toml).\n- Update the [CHANGELOG.md](./CHANGELOG.md) file with\n  `git cliff -o CHANGELOG.md`. This step includes setting the proper header for\n  the latest tag.\n- Commit the changes.\n- Run `cargo publish --dry-run` to make sure that everything looks good.\n- Create the corresponding git tag named after the version.\n- Push to origin.\n- Run `cargo publish`.\n\n## Supported By\n\nThis project has been possible thanks to the support of:\n\n- [Sense Finance](https://sense.finance)\n- [Sablier](https://github.com/sablier-labs)\n\n## License\n\nThis project is licensed under either of:\n\n- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or\n  https://www.apache.org/licenses/LICENSE-2.0).\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or\n  https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfertel%2Fbulloak","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexfertel%2Fbulloak","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexfertel%2Fbulloak/lists"}