From c03d12b98c4e53fd3b8a991c59313ac756981269 Mon Sep 17 00:00:00 2001 From: waalge Date: Tue, 6 Feb 2024 11:12:44 +0000 Subject: [PATCH] initial thoughts --- .../aiken-lang/src/parser/definition/test.rs | 10 +- examples/moonrat/.gitignore | 1 + examples/moonrat/README.md | 114 ++++++++++++++++++ examples/moonrat/aiken.lock | 16 +++ examples/moonrat/aiken.toml | 8 ++ examples/moonrat/lib/tests.ak | 4 + examples/moonrat/plutus.json | 89 ++++++++++++++ flake.nix | 3 +- 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 examples/moonrat/.gitignore create mode 100644 examples/moonrat/README.md create mode 100644 examples/moonrat/aiken.lock create mode 100644 examples/moonrat/aiken.toml create mode 100644 examples/moonrat/lib/tests.ak create mode 100644 examples/moonrat/plutus.json diff --git a/crates/aiken-lang/src/parser/definition/test.rs b/crates/aiken-lang/src/parser/definition/test.rs index 6691edb2..8e39355d 100644 --- a/crates/aiken-lang/src/parser/definition/test.rs +++ b/crates/aiken-lang/src/parser/definition/test.rs @@ -3,7 +3,7 @@ use chumsky::prelude::*; use crate::{ ast, expr::UntypedExpr, - parser::{error::ParseError, expr, token::Token}, + parser::{error::ParseError, expr, token::Token, definition::function::param}, }; pub fn parser() -> impl Parser { @@ -13,7 +13,15 @@ pub fn parser() -> impl Parser name}) + .then( + param(false) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)) + .map_with_span(|arguments, span| (arguments, span)), + ) .then_ignore(just(Token::LeftParen)) + .then_ignore(just(Token::RightParen)) .then(just(Token::Fail).ignored().or_not()) .map_with_span(|name, span| (name, span)) diff --git a/examples/moonrat/.gitignore b/examples/moonrat/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/examples/moonrat/.gitignore @@ -0,0 +1 @@ +build diff --git a/examples/moonrat/README.md b/examples/moonrat/README.md new file mode 100644 index 00000000..987ae3dc --- /dev/null +++ b/examples/moonrat/README.md @@ -0,0 +1,114 @@ +# Moonrat + +> Hedgehog's spineless cousin + +## Aims + +Property based testing for aiken inspired by hedgehog and elm-test. + +Aims: + +- Default gen and shrinking auto derived for any types +- Support custom gen/shrinking +- Friendly output (progress, sensible feedback such as diffs on large data) +- Reasonably speedy + +Non-aims: + +- e2e testing. +This is intended for functions rather than testing full txs against validators. +Although it should still be possible, it is not our aim here to make writing and testing txs ergonomic. + + +## Interface + +An aiken file + +```aiken +// my_tests.ak + +type T0 { + f0 : Int, + ... +} + +fn gen_t0(seed : Int, complexity : Int) -> T0 { + ... +} + +fn shrink_t0(x : T0) -> List { + // TODO : what should the signature of this be?! + ... +} + +type T1 { + f0 : Int, + ... +} + +test prop_x ( + a0 : T0 via (gen_t0(0), shrink_t0), + a1 : T0 via (gen_t0(1), shrink_t0), + a2 : T0, + a2 : T1, +) { + todo! +} +``` + +Comments on the sample. +`prop_x` is our test - now supporting arguments. +There is new syntax `via`. +We have a custom generator and shrinker for `T0` which we may or may not use. +In the absence of a specified gen/shrink pair, the default, autoderived one is used. + +Run 100 times +``` +aiken check -m "my_lib/my_test.{prop_x}" +``` + +Run 1000 cases with a specified seed and shrink limit +``` +aiken check --repeat 1000 --seed 123212123 --shrink-limit 5 +``` + +Reporting: +```sample +Testing ... + +my_test + prop_x PASS [100/100] +``` + +```sample +Testing ... + +my_test + prop_x FAIL (after 16 tests and 5 shrinks): + a0 = T0 { f0 : 120201, ... } + a1 = T0 { ... } + ... + + RHS = True + LHS = False + + seed = 123212123 + + Rerun with + aiken check -m "my_lib/my_test.{prop_x}" --args " [ T0 { }] ... " +``` + +## Functionality + +Aiken compiler finds all tests. +Any tests with args are assumed subject to property based testing. + +[Property config](https://hackage.haskell.org/package/hedgehog-1.4/docs/Hedgehog-Internal-Property.html#t:PropertyConfig) is global, rather than local. + +The test is compiled as if it were a parametrized validator. +Separate gen and shrink functions are also compiled. + +To evaluate the test, the generator(s) are run to generate input for the test. +Then the args are applied, and the code evaluated. +On success this is repeated until `repeat` number of successes. +On failure, the shrinker is employed to seek a simpler failure case. diff --git a/examples/moonrat/aiken.lock b/examples/moonrat/aiken.lock new file mode 100644 index 00000000..25cace41 --- /dev/null +++ b/examples/moonrat/aiken.lock @@ -0,0 +1,16 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "main" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "main" +requirements = [] +source = "github" + +[etags] +"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1707160390, nanos_since_epoch = 895305443 }, "cf946239d3dd481ed41f20e56bf24910b5229ea35aa171a708edc2a47fc20a7b"] diff --git a/examples/moonrat/aiken.toml b/examples/moonrat/aiken.toml new file mode 100644 index 00000000..ceddc3c9 --- /dev/null +++ b/examples/moonrat/aiken.toml @@ -0,0 +1,8 @@ +name = "aiken-lang/moonrat" +version = "0.0.0" +description = "" + +[[dependencies]] +name = 'aiken-lang/stdlib' +version = 'main' +source = 'github' diff --git a/examples/moonrat/lib/tests.ak b/examples/moonrat/lib/tests.ak new file mode 100644 index 00000000..b453731e --- /dev/null +++ b/examples/moonrat/lib/tests.ak @@ -0,0 +1,4 @@ + +test test_with_arg(x : Int) { + x - x == 0 +} diff --git a/examples/moonrat/plutus.json b/examples/moonrat/plutus.json new file mode 100644 index 00000000..87491b39 --- /dev/null +++ b/examples/moonrat/plutus.json @@ -0,0 +1,89 @@ +{ + "preamble": { + "title": "aiken-lang/acceptance_test_089", + "version": "0.0.0", + "plutusVersion": "v2", + "compiler": { + "name": "Aiken", + "version": "v1.0.24-alpha+982eff4" + } + }, + "validators": [ + { + "title": "test2.simple_oneshot", + "redeemer": { + "title": "_r", + "schema": { + "$ref": "#/definitions/Void" + } + }, + "parameters": [ + { + "title": "utxo_ref", + "schema": { + "$ref": "#/definitions/aiken~1transaction~1OutputReference" + } + } + ], + "compiledCode": "58d40100003232323232323232322225333006323232323232533300c3370e900018058018991919299980799b8748000c0380044c8c94ccc044cdc3a40000022944528180780099801002119baf3004300e00100d163300100323375e6006601a00201844646600200200644a6660280022980103d87a8000132325333013300500213374a90001980b80125eb804cc010010004c060008c0580048c04800458dd61808000980400198070009807001180600098020008a4c26cac4600a6ea80048c00cdd5000ab9a5573aaae7955cfaba05742ae89", + "hash": "dd850cc95e173d7dbb3357a4a021afc350f405a3cc2e85ace58bfe8d" + } + ], + "definitions": { + "ByteArray": { + "dataType": "bytes" + }, + "Int": { + "dataType": "integer" + }, + "Void": { + "title": "Unit", + "description": "The nullary constructor.", + "anyOf": [ + { + "dataType": "constructor", + "index": 0, + "fields": [] + } + ] + }, + "aiken/transaction/OutputReference": { + "title": "OutputReference", + "description": "An `OutputReference` is a unique reference to an output on-chain. The `output_index`\n corresponds to the position in the output list of the transaction (identified by its id)\n that produced that output", + "anyOf": [ + { + "title": "OutputReference", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "transaction_id", + "$ref": "#/definitions/aiken~1transaction~1TransactionId" + }, + { + "title": "output_index", + "$ref": "#/definitions/Int" + } + ] + } + ] + }, + "aiken/transaction/TransactionId": { + "title": "TransactionId", + "description": "A unique transaction identifier, as the hash of a transaction body. Note that the transaction id\n isn't a direct hash of the `Transaction` as visible on-chain. Rather, they correspond to hash\n digests of transaction body as they are serialized on the network.", + "anyOf": [ + { + "title": "TransactionId", + "dataType": "constructor", + "index": 0, + "fields": [ + { + "title": "hash", + "$ref": "#/definitions/ByteArray" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/flake.nix b/flake.nix index 1708de99..c63e95a1 100644 --- a/flake.nix +++ b/flake.nix @@ -78,9 +78,10 @@ openssl cargo-insta + (pkgs.rust-bin.stable.latest.default.override { - extensions = [ "rust-src" "clippy" "rustfmt" ]; + extensions = [ "rust-src" "clippy" "rustfmt" "rust-analyzer" ]; }) ] ++ osxDependencies;