// Property-Based Testing Library for Lux // // This module provides generators and helper functions for property-based testing. // // Usage: // Use the generator functions (genInt, genIntList, genString, etc.) in your // property tests to generate random test data. // // Example: // fn testReverseInvolutive(n: Int): Unit with {Console, Test, Random} = { // if n <= 0 then // Console.print(" PASS reverse(reverse(xs)) == xs") // else { // let xs = genIntList(0, 100, 20) // if List.reverse(List.reverse(xs)) == xs then // testReverseInvolutive(n - 1) // else // Test.fail("Property failed") // } // } // ============================================================ // Generator Module // ============================================================ // Character set for string generation let GEN_CHARS = "abcdefghijklmnopqrstuvwxyz" let GEN_ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" // Generate a random integer in a range fn genInt(min: Int, max: Int): Int with {Random} = Random.int(min, max) // Generate a random integer from 0 to max fn genIntUpTo(max: Int): Int with {Random} = Random.int(0, max) // Generate a random positive integer fn genPositiveInt(max: Int): Int with {Random} = Random.int(1, max) // Generate a random boolean fn genBool(): Bool with {Random} = Random.bool() // Generate a random character (lowercase letter) fn genChar(): String with {Random} = { let idx = Random.int(0, 25) String.substring(GEN_CHARS, idx, idx + 1) } // Generate a random alphanumeric character fn genAlphaNum(): String with {Random} = { let idx = Random.int(0, 61) String.substring(GEN_ALPHANUM, idx, idx + 1) } // Generate a random string of given max length fn genString(maxLen: Int): String with {Random} = { let len = Random.int(0, maxLen) genStringOfLength(len) } // Generate a random string of exact length fn genStringOfLength(len: Int): String with {Random} = { if len <= 0 then "" else genChar() + genStringOfLength(len - 1) } // Generate a random list of integers fn genIntList(min: Int, max: Int, maxLen: Int): List with {Random} = { let len = Random.int(0, maxLen) genIntListOfLength(min, max, len) } // Generate a random list of integers with exact length fn genIntListOfLength(min: Int, max: Int, len: Int): List with {Random} = { if len <= 0 then [] else List.concat([Random.int(min, max)], genIntListOfLength(min, max, len - 1)) } // Generate a random list of booleans fn genBoolList(maxLen: Int): List with {Random} = { let len = Random.int(0, maxLen) genBoolListOfLength(len) } fn genBoolListOfLength(len: Int): List with {Random} = { if len <= 0 then [] else List.concat([Random.bool()], genBoolListOfLength(len - 1)) } // Generate a random list of strings fn genStringList(maxStrLen: Int, maxLen: Int): List with {Random} = { let len = Random.int(0, maxLen) genStringListOfLength(maxStrLen, len) } fn genStringListOfLength(maxStrLen: Int, len: Int): List with {Random} = { if len <= 0 then [] else List.concat([genString(maxStrLen)], genStringListOfLength(maxStrLen, len - 1)) } // ============================================================ // Shrinking Module // ============================================================ // Shrink an integer towards zero fn shrinkInt(n: Int): List = { if n == 0 then [] else if n > 0 then [0, n / 2, n - 1] else [0, n / 2, n + 1] } // Shrink a list by removing elements fn shrinkList(xs: List): List> = { let len = List.length(xs) if len == 0 then [] else if len == 1 then [[]] else { // Return: empty list, first half, second half let half = len / 2 let firstHalf = List.take(xs, half) let secondHalf = List.drop(xs, half) [[], firstHalf, secondHalf] } } // Shrink a string by removing characters fn shrinkString(s: String): List = { let len = String.length(s) if len == 0 then [] else if len == 1 then [""] else { let half = len / 2 ["", String.substring(s, 0, half), String.substring(s, half, len)] } } // ============================================================ // Common Properties // ============================================================ // Check that a list is sorted fn isSorted(xs: List): Bool = { match List.head(xs) { None => true, Some(first) => { match List.tail(xs) { None => true, Some(rest) => isSortedHelper(first, rest) } } } } fn isSortedHelper(prev: Int, xs: List): Bool = { match List.head(xs) { None => true, Some(curr) => { if prev <= curr then { match List.tail(xs) { None => true, Some(rest) => isSortedHelper(curr, rest) } } else false } } } // Check that two lists have the same elements (ignoring order) fn sameElements(xs: List, ys: List): Bool = { List.length(xs) == List.length(ys) && List.all(xs, fn(x) => List.contains(ys, x)) && List.all(ys, fn(y) => List.contains(xs, y)) }