-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
clarinet fuzz
command and heterogeneous test-suites
#398
Comments
Observing Test Case DistributionIt is important to be aware of the distribution of test cases: if the test data is not well distributed then conclusions drawn from the test results may be invalid. Based on my current research, fast-check can monitor the underlying test data but that's separate from the actual test run(s); it is purely informational and doesn’t have a threshold below which it will fail the test(s). "Hello, world!" exampleMonitor how frequently each Clarinet account gets picked up by the generator below. According to the docs, this generator makes each account be almost equally likely chosen: Clarinet.test({
name: "fc.statistics/fc.constantFrom/clarinet.accounts",
async fn(_: Chain, accounts: Map<string, Account>) {
fc.statistics(
fc.constantFrom(...accounts.values()),
(account) =>
account.name === "deployer" ? "deployer"
: account.name === "wallet_1" ? "wallet_1"
: account.name === "wallet_2" ? "wallet_2"
: account.name === "wallet_3" ? "wallet_3"
: account.name === "wallet_4" ? "wallet_4"
: account.name === "wallet_5" ? "wallet_5"
: account.name === "wallet_6" ? "wallet_6"
: account.name === "wallet_7" ? "wallet_7"
: account.name === "wallet_8" ? "wallet_8"
: account.name === "wallet_9" ? "wallet_9"
: "wallet_10",
{ numRuns: 1000, unbiased: true },
);
},
}); Prints:
The first step towardsThe first step towards supporting property-based (and fuzz) tests is getting something like this to @@ -156,14 +156,12 @@ function CargoCommands(accounts: Map<string, Account>) {
// },
// });
Clarinet.test({
name: "fc.statistics/fc.constantFrom/clarinet.accounts",
- async fn(_: Chain, accounts: Map<string, Account>) {
+ async fn(_: Chain, accounts: Map<string, Account>, account: Account) {
fc.statistics(
- fc.constantFrom(...accounts.values()),
- (account) =>
account.name === "deployer" ? "deployer"
: account.name === "wallet_1" ? "wallet_1"
: account.name === "wallet_2" ? "wallet_2"
: account.name === "wallet_3" ? "wallet_3"
: account.name === "wallet_4" ? "wallet_4" And perhaps now it's time for a checklist. (To be added in the next comment.) |
In order to separate concrete (current) tests from property/fuzz-based ones, the default signature of the test method should change so that there's no parameters in concrete tests: @@ -2,21 +2,21 @@
import { Clarinet, Tx, Chain, Account, types } from 'https://deno.land/x/clarinet@v0.31.0/index.ts';
import { assertEquals } from 'https://deno.land/std@0.90.0/testing/asserts.ts';
Clarinet.test({
name: "Ensure that <...>",
- async fn(chain: Chain, accounts: Map<string, Account>) {
- let block = chain.mineBlock([
+ async fn() {
+ let block = ctx.chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]);
assertEquals(block.receipts.length, 0);
assertEquals(block.height, 2);
- block = chain.mineBlock([
+ block = ctx.chain.mineBlock([
/*
* Add transactions with:
* Tx.contractCall(...)
*/
]); Then |
Using the latest from #511 by @lgalabru, I took a stub at implementing a Step 1: Take an existing Clarinet testClarinet.test({
name: 'write-sup returns expected string',
async fn(chain: Chain, accounts: Map<string, Account>) {
// Arrange
const account = accounts.get('deployer')!;
const msg = types.utf8("lorem ipsum");
const stx = types.uint(123);
// Act
const block = chain.mineBlock([
Tx.contractCall(
'sup', 'write-sup', [msg, stx], account.address)
]);
const result = block.receipts[0].result;
// Assert
result
.expectOk()
.expectAscii('Sup written successfully');
}
}); Step 2: Change
|
Next stepsOnce deno config in Clarinet
|
Absolutely feel free to re-open and/or ping me in case you want help with this essential feature. |
We are in the process of re-architecting clarinet testing approach, and our timeline is aggressive. |
More context here: #1022 |
That's great news 👍 Yes, I could show to @hugocaillard all the pieces I got. However, kindly note that I am currently on vacation and I may be slow to respond. |
Great idea! The current POC currently looks like that using Node 20 test runner (would also work we Jest, Mocha, etc) import { main } from "../../../../hiro/clarinet/components/clarinet-sdk/dist/index.js";
// soon it will be `import { main } from "@clarinet-sdk/unit-test"` or smth like that
import { before, describe, it } from "node:test";
import assert from "node:assert/strict";
import { Cl } from "@stacks/transactions";
describe("test counter", () => {
let session;
before(async () => {
session = await main();
await session.initSession(process.cwd(), "./Clarinet.toml");
});
it("gets counter value", () => {
let count = session.callReadOnlyFn({
sender: "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5",
contract: "counter",
method: "get-counter",
args: [],
});
assert.deepEqual(count.result, Cl.int(1))
});
}); Currently the clarinet-sdk lib is kind of raw but the plan is to improve it to make it simpler and more user friendly (and opinionated). When do you get back from vacation? |
Here are some unit tests in clarity that should be supported as well Currently, using a clarinet extension: Extension by @MarvinJanssen : https://github.com/Trust-Machines/stacks-sbtc/blob/cc4c5e9faf0233ed33839b0d7bb74c0b4bdf628c/sbtc-mini/ext/generate-tests.ts Instructions how to use it: https://github.com/Trust-Machines/stacks-sbtc/tree/cc4c5e9faf0233ed33839b0d7bb74c0b4bdf628c/sbtc-mini#unit-testing |
In the context of the new clarinet-sdk, a possible integration with fast-check could look like this: prop("ensures that <add> adds up the right amout", (n: UIntCV) => {
const { result } = simnet.callPublicFn("counter", "add", [n], address1);
expect(result).toBeOk(Cl.bool(true));
const counter = simnet.getDataVar("counter", "counter");
expect(counter).toBe(n);
}).check(); Note that In addition to that, we can also customize/override the built-in arbitrary, for example: prop("ensures that <add> adds up the right amount", (n: UIntCV) => {
const { result } = simnet.callPublicFn("counter", "add", [n], address1);
expect(result).toBeOk(Cl.bool(true));
const counter = simnet.getDataVar("counter", "counter");
expect(counter).toBe(n);
}).check({
runs: 1000,
logs: true,
data: {
n: { min: 123 },
}
}); Or, explore a possible integration with @fast-check/vitest and provide a set of built-in arbitraries (where arbitrary is a pair of a generator and a shrinker): prop([tx.UIntCV({ min: 0 })])(
"ensures that <add> adds up the right amount",
(n: UIntCV) => {
const { result } = simnet.callPublicFn("counter", "add", [n], address1);
expect(result).toBeOk(Cl.bool(true));
const counter = simnet.getDataVar("counter", "counter");
expect(counter).toBe(n);
},
); Of course, users are free to write and use their own arbitraries. This can cover pretty much all the scenarios for stateless property-based tests. |
@moodmosaic |
@hugocaillard, the easiest step forward is to explore a possible integration with @fast-check/vitest and decide whether it makes sense to build a set of custom fast-check arbitraries for Clarity types: prop([arb.UIntCV({ min: 0 })])(
"ensures that <add> adds up the right amount",
(n: UIntCV) => {
const { result } = simnet.callPublicFn("counter", "add", [n], address1);
expect(result).toBeOk(Cl.bool(true));
const counter = simnet.getDataVar("counter", "counter");
expect(counter).toBe(n);
},
); In this example, the (hypothetical) custom fast-check arbitrary The approach I've taken with deno, before the creation of clarinet-sdk, was by just passing parameters to the tests and not have the user think about fast-check and arbitraries. — Here's how this could translate into clarinet-sdk: prop("ensures that <add> adds up the right amount", (n: UIntCV) => {
const { result } = simnet.callPublicFn("counter", "add", [n], address1);
expect(result).toBeOk(Cl.bool(true));
const counter = simnet.getDataVar("counter", "counter");
expect(counter).toBe(n);
}).check({
runs: 1000,
logs: true,
data: {
n: { min: 123 },
}
}); If any of the above matches with something we'd want to provide to the users, issue can remain open otherwise can be closed. |
This was started as two separate GitHub issues, but then I thought I should merge them. — The idea is:
clarinet test
can support property tests, and those tests may be written in either TypeScript or Clarityclarinet fuzz
can then turn those tests into fuzz testsContext
Clarinet tests are essentially Deno tests. This has the interesting side-effect of being able to use what's already available in JS/TS ecosystem when testing Clarity code. (For example, fast-check and dspec can be used, as we've done here with @LNow.)
A typical workflow is declaring functions in Clarity and then writing tests in TypeScript. Clarinet can then discover those tests (with the help of Deno) and run them. It would be practical however to also discover tests written in Clarity and run those as well.
Discoverability
In addition to existing test suite(s) in TypeScript, Clarinet can execute as a test any Clarity function that meets the following criteria:
test
(configurable inClarinet.toml
)(If a public function named
beforeEach
is present it can be executed before each test is run. Such a function can exist in the style of testing @jcnelson does here, may be discussed also on a separate thread, however.)test
test
test
*Also configurable, in
Clarinet.toml
.Fuzz mode can be enabled via new
clarinet fuzz
command. Essentially it can work the same asclarinet test
but use a different fast-check configuration.Libraries
Both test suites (TypeScript, and Clarity (hypothetically)) can be discovered and run from the context of Deno, and in the case of property tests and fuzz tests, the generated data can be provided by fast-check.
I have been using (and getting an inside look into) fast-check recently. It has all the modern features of a prop/fuzz testing library, e.g. model testing, integrated shrinking, control over the scope of generated values, and many other useful functions.
Pros and cons
What are the advantages and disadvantages of having the option to write tests in Clarity?
The text was updated successfully, but these errors were encountered: