From 58b2fa2ac70c5a2cbed960bd8f98172e8886a7a3 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 20 Sep 2024 15:44:47 +0200 Subject: [PATCH 1/4] Remove aiken.lock from versioning. Pointless and annoying for libraries. --- .gitignore | 2 ++ aiken.lock | 16 ---------------- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 aiken.lock diff --git a/.gitignore b/.gitignore index 12db6b2..5281ce6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ artifacts/ build/ # Aiken's default documentation export docs/ +# Aiken's auto-generated lock file +aiken.lock diff --git a/aiken.lock b/aiken.lock deleted file mode 100644 index 152f364..0000000 --- a/aiken.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file was generated by Aiken -# You typically do not need to edit this file - -[[requirements]] -name = "aiken-lang/stdlib" -version = "v2" -source = "github" - -[[packages]] -name = "aiken-lang/stdlib" -version = "v2" -requirements = [] -source = "github" - -[etags] -"aiken-lang/stdlib@v2" = [{ secs_since_epoch = 1725461004, nanos_since_epoch = 782863973 }, "d79382d2b6ecb3aee9b0755c31d8a5bbafe88a7b3706d7fb8a52fd4d05818501"] From 8c4d004d5abb1603b7d59cc245149e47df641d99 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 24 Sep 2024 13:43:00 +0200 Subject: [PATCH 2/4] Add few more helpers for combining fuzzers. --- lib/aiken/fuzz.ak | 226 +++++++++++++++++++++++++++++++++++++++++ lib/aiken/fuzz.test.ak | 92 ++++++++++++++++- 2 files changed, 316 insertions(+), 2 deletions(-) diff --git a/lib/aiken/fuzz.ak b/lib/aiken/fuzz.ak index 01ad2dc..0e5726f 100644 --- a/lib/aiken/fuzz.ak +++ b/lib/aiken/fuzz.ak @@ -324,6 +324,13 @@ pub fn list_with_elem(fuzzer: Fuzzer) -> Fuzzer<(List, a)> { (xs, x) } +/// Pick an element from a list, returning its index. +pub fn pick(xs: List) -> Fuzzer<(Int, a)> { + let ix <- map(int_between(0, list.length(xs) - 1)) + expect Some(x) = list.at(xs, ix) + (ix, x) +} + /// Generate a random list of **unique** elements (a.k.a. a set) from a given fuzzer. /// The list contains *at most `20`* elements, and has a higher probability of /// generating smaller lists. @@ -453,6 +460,225 @@ pub fn either(left: Fuzzer, right: Fuzzer) -> Fuzzer { } } +/// Choose either of three fuzzers with an equal probability. +pub fn either3( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 85 { + a + } else if ix < 170 { + b + } else { + c + } +} + +/// Choose either of four fuzzers with an equal probability. +pub fn either4( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 128 { + if ix < 64 { + a + } else { + b + } + } else { + if ix < 192 { + c + } else { + d + } + } +} + +/// Choose either of five fuzzers with an equal probability. +pub fn either5( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, + e: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 102 { + if ix < 51 { + a + } else { + b + } + } else if ix < 204 { + if ix < 153 { + c + } else { + d + } + } else { + e + } +} + +/// Choose either of six fuzzers with an equal probability. +pub fn either6( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, + e: Fuzzer, + f: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 128 { + if ix < 42 { + a + } else if ix < 85 { + b + } else { + c + } + } else { + if ix < 170 { + d + } else if ix < 212 { + e + } else { + f + } + } +} + +/// Choose either of seven fuzzers with an equal probability. +pub fn either7( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, + e: Fuzzer, + f: Fuzzer, + g: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 110 { + if ix < 36 { + a + } else if ix < 72 { + b + } else { + c + } + } else { + if ix < 182 { + if ix < 145 { + d + } else { + e + } + } else { + if ix < 218 { + f + } else { + g + } + } + } +} + +/// Choose either of height fuzzers with an equal probability. +pub fn either8( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, + e: Fuzzer, + f: Fuzzer, + g: Fuzzer, + h: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 128 { + if ix < 64 { + if ix < 32 { + a + } else { + b + } + } else { + if ix < 96 { + c + } else { + d + } + } + } else { + if ix < 192 { + if ix < 160 { + e + } else { + f + } + } else { + if ix < 224 { + g + } else { + h + } + } + } +} + +/// Choose either of nine fuzzers with an equal probability. +pub fn either9( + a: Fuzzer, + b: Fuzzer, + c: Fuzzer, + d: Fuzzer, + e: Fuzzer, + f: Fuzzer, + g: Fuzzer, + h: Fuzzer, + i: Fuzzer, +) -> Fuzzer { + let ix <- and_then(byte()) + if ix < 112 { + if ix < 56 { + if ix < 28 { + a + } else { + b + } + } else { + if ix < 84 { + c + } else { + d + } + } + } else { + if ix < 172 { + if ix < 144 { + e + } else { + f + } + } else { + if ix < 200 { + g + } else if ix < 228 { + h + } else { + i + } + } + } +} + /// Transform the result of a [Fuzzer](https://aiken-lang.github.io/prelude/aiken.html#Fuzzer) using a function. /// This function works great with [backpassing](https://aiken-lang.org/language-tour/functions#backpassing-). /// diff --git a/lib/aiken/fuzz.test.ak b/lib/aiken/fuzz.test.ak index ec54a7a..6073c89 100644 --- a/lib/aiken/fuzz.test.ak +++ b/lib/aiken/fuzz.test.ak @@ -1,7 +1,8 @@ use aiken/collection/list use aiken/fuzz.{ - and_then, bool, int, int_between, label, list_between, list_with_elem, map, - one_of, set, set_between, sublist, such_that, + and_then, bool, constant, either3, either4, either5, either6, either7, either8, + either9, int, int_between, label, list_between, list_with_elem, map, one_of, + set, set_between, sublist, such_that, } use aiken/math use aiken/primitive/bytearray @@ -209,6 +210,93 @@ test prop_set_between_distribution(n via set_between(int_between(0, 50), 3, 13)) // True // } +test prop_either3( + lbl via either3(constant(@"a"), constant(@"b"), constant(@"c")), +) { + label(lbl) +} + +test prop_either4( + lbl via either4( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + ), +) { + label(lbl) +} + +test prop_either5( + lbl via either5( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + constant(@"e"), + ), +) { + label(lbl) +} + +test prop_either6( + lbl via either6( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + constant(@"e"), + constant(@"f"), + ), +) { + label(lbl) +} + +test prop_either7( + lbl via either7( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + constant(@"e"), + constant(@"f"), + constant(@"g"), + ), +) { + label(lbl) +} + +test prop_either8( + lbl via either8( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + constant(@"e"), + constant(@"f"), + constant(@"g"), + constant(@"h"), + ), +) { + label(lbl) +} + +test prop_either9( + lbl via either9( + constant(@"a"), + constant(@"b"), + constant(@"c"), + constant(@"d"), + constant(@"e"), + constant(@"f"), + constant(@"g"), + constant(@"h"), + constant(@"i"), + ), +) { + label(lbl) +} + /// A small function for automatically labelling a range of ints. fn buckets(n, start, end, increment) -> Void { expect n >= start From d57f6787f6dbcaf3968e76f19ac15fb91698a81d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 24 Sep 2024 14:20:53 +0200 Subject: [PATCH 3/4] Add few more primitives and helpers to fuzz. --- CHANGELOG.md | 14 ++++++++++++++ lib/aiken/fuzz.ak | 37 ++++++++++++++++++++++++++++++++----- lib/aiken/fuzz.test.ak | 4 ++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1223f5..fd837af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v2.1.0 - 2024-09-24 + +### Added + +- More primitives for combining fuzzers: + - `either2`, `either3`, `either4`, `either5`, `either6`, `either7`, `either8`, `either9` + - `pick(xs: List) -> Fuzzer<(Int, a)>` + - `label_if(str: String, predicate: Bool) -> Void` + +### Changed + +- Have bytearray primitives simplify towards the null bytestring (32 zeroes), + so that it's easier to identify in simplified examples. + ## v2.0.0 - 2024-09-01 ### Changed diff --git a/lib/aiken/fuzz.ak b/lib/aiken/fuzz.ak index 0e5726f..e88b032 100644 --- a/lib/aiken/fuzz.ak +++ b/lib/aiken/fuzz.ak @@ -32,10 +32,14 @@ pub fn byte() -> Fuzzer { pub fn bytearray() -> Fuzzer { let lsb <- and_then(rand) let msb <- map(rand) - "" - |> builtin.cons_bytearray(lsb, _) - |> builtin.cons_bytearray(msb, _) - |> builtin.blake2b_256 + if lsb + msb == 0 { + #"0000000000000000000000000000000000000000000000000000000000000000" + } else { + "" + |> builtin.cons_bytearray(lsb, _) + |> builtin.cons_bytearray(msb, _) + |> builtin.blake2b_256 + } } /// Generate a random [`ByteArray`](https://aiken-lang.github.io/prelude/aiken.html#ByteArray) of @@ -1108,7 +1112,8 @@ pub fn label(str: String) -> Void { } /// Apply a label when a predicate is true, or fallback to a default label. -/// Useful for labelling specific scenarios based on a predicate. +/// Useful for labelling dichotomies while ensuring that the final label +/// distribution reflects all cases. /// /// ```aiken /// test prop_u16(operands via fuzz.both(byte(), byte())) { @@ -1131,6 +1136,28 @@ pub fn label_when(predicate: Bool, str: String, default: String) -> Void { } } +/// Apply a label when a predicate is true, or do nothing. Useful for +/// conditionally labelling scenarios in a single line. +/// +/// ```aiken +/// test post_conditions(steps via any_scenario()) { +/// let (is_register, is_reregister, is_unregister, is_forward) = +/// classify_steps(steps) +/// +/// @"contains solo registration" |> label_if(is_register) +/// @"contains re-registration" |> label_if(is_reregister) +/// @"contains solo unregistration" |> label_if(is_unregister) +/// @"contains forward-only" |> label_if(is_forward) +/// } +/// ``` +pub fn label_if(str: String, predicate: Bool) -> Void { + if predicate { + label(str) + } else { + Void + } +} + // Internal const max_rand = 255 diff --git a/lib/aiken/fuzz.test.ak b/lib/aiken/fuzz.test.ak index 6073c89..a6e2e2d 100644 --- a/lib/aiken/fuzz.test.ak +++ b/lib/aiken/fuzz.test.ak @@ -39,6 +39,10 @@ test prop_int_between_large( n <= 18_446_744_073_709_551_615 } +test prop_bytearray_simplify(bytes via fuzz.bytearray()) fail once { + bytes == "" +} + test prop_bool_distribution(is_true via bool()) { label( if is_true { From f4725e2b8430083e9e4f2ffe8e0f0b325e904487 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 24 Sep 2024 14:22:05 +0200 Subject: [PATCH 4/4] Use aiken==v1.1.3 --- .github/workflows/continuous-integration.yml | 5 +++-- aiken.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 538317b..4149e6e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -23,8 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: 🧰 Install Aiken - run: cargo install --verbose --git https://github.com/aiken-lang/aiken.git + - uses: aiken-lang/setup-aiken@v1 + with: + version: v1.1.3 - run: aiken fmt --check - run: aiken check -D - run: aiken build diff --git a/aiken.toml b/aiken.toml index 81e4b84..d19db56 100644 --- a/aiken.toml +++ b/aiken.toml @@ -1,6 +1,6 @@ name = "aiken-lang/fuzz" version = "main" -compiler = "v1.1.0" +compiler = "v1.1.3" plutus = "v3" license = "Apache-2.0" description = "A library for writing Aiken's fuzzers."