diff --git a/README.md b/README.md index 1b942ef..8f75b64 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ chmod +x ./install ## Run tests ``` -chmod +x ./test ./test ``` diff --git a/src/large_assignment_01/large_assignment_01.sml b/src/large_assignment_01/large_assignment_01.sml index 670995b..5fffcd2 100644 --- a/src/large_assignment_01/large_assignment_01.sml +++ b/src/large_assignment_01/large_assignment_01.sml @@ -9,8 +9,6 @@ (* - * `triangle` - * * Type: `int * int * int -> bool` * Description: The triangle inequality theorem states that the sum of any * two sides of a triangle must be greater than or equal to the @@ -24,15 +22,13 @@ fun triangle(0, _, _) = false (* - * `triangleR` - * - * Type: `real * real * real -> bool` - * Description: The triangle inequality theorem states that the sum - * of any two sides of a triangle must be greater than - * or equal to the third side. This function should - * return true if the three real numbers can make a - * triangle and false otherwise. - *) + * Type: `real * real * real -> bool` + * Description: The triangle inequality theorem states that the sum + * of any two sides of a triangle must be greater than + * or equal to the third side. This function should + * return true if the three real numbers can make a + * triangle and false otherwise. + *) fun triangleR(a, b, c) = let val epsilon = 1.0E~10 @@ -42,3 +38,59 @@ fun triangleR(a, b, c) = not(is_zero(a)) andalso not(is_zero(b)) andalso not(is_zero(c)) andalso sum_geq_to(a, b, c) andalso sum_geq_to(a, c, b) andalso sum_geq_to(b, c, a) end; + + +(* + * Type: `int * 'a list -> 'a list` + * Description: Given an integer n and a list l, cycle n elements + * from the list to the end of the list. You can assume + * the input for n will be non-negative. + * Example: cycle(4,[1,2,3,4,5,6,7]) → [5,6,7,1,2,3,4] + *) + fun cycle(0, li) = li + | cycle(_, []) = [] + | cycle(n, x::li) = cycle(n - 1, li @ [x]); + + +(* + * Type: `'a list -> 'a list` + * Description: Mirror the list. You may not use any reverse function + * (even as a helper function). + * Example: mirror [1,2,3,4] → [1,2,3,4,4,3,2,1] + *) + fun mirror([]) = [] + | mirror(x::li) = x::mirror(li) @ [x]; + + +(* + * Type: `int list * int -> int list` + * Description: Take a list l and an integer n and return a list that + * contains all the elements in l that are greater than + * n. Keep the same relative order of items. + *) + fun gtList(_, []) = [] + | gtList(n, x::li) = if x > n then x::gtList(n, li) else gtList(n, li); + + +(* + * Type: `''a list * ''a list -> bool` + * Description: Return true if the first list is a suffix of the + * second list and false otherwise. Do not reverse + * either of the lists. + *) + fun suffix([], _) = true + | suffix(_, []) = false + | suffix(li1, li2) = + let + fun listLength([]) = 0 + | listLength(x::li) = 1 + listLength(li) + fun truncatePrefix(count, x::li) = if count = 0 then x::li else truncatePrefix(count - 1, li) + fun suffixEqual([], []) = true + | suffixEqual(x::a, y::b) = if x = y then suffixEqual(a, b) else false + val truncateCount = listLength(li2) - listLength(li1) + in + if truncateCount < 0 then false + else suffixEqual(li1, truncatePrefix(truncateCount, li2)) + end; + + diff --git a/test-la-01.sh b/test-la-01.sh index 9178abc..9481da0 100755 --- a/test-la-01.sh +++ b/test-la-01.sh @@ -1,3 +1,3 @@ #!/bin/bash # -./test --ignore-glob "tests-ica05/*" --failed-first --log-cli-level WARNING +./test --rootdir tests/tests-large_assignment_01 --failed-first --log-cli-level WARNING $@ diff --git a/test.py b/test.py index bbe39cc..df37f6c 100644 --- a/test.py +++ b/test.py @@ -55,6 +55,9 @@ parser.add_argument( "--nf", "--new-first", action="store_true", help="Run tests from new files first." ) +parser.add_argument( + "--nl", "--new-last", action="store_true", help="Run tests from new files last." +) parser.add_argument( "--sw-skip", "--stepwise-skip", @@ -198,12 +201,8 @@ def clear_cache(): logging.debug(f"Cache file on start: {cache_}") logging.debug(f"all_seen_tests in cache: {cache_.get('all_seen_tests')}") logging.debug(f"failed_tests in cache: {cache_.get('failed_tests')}") - all_seen_tests = set( - Path(test) for test in cache_.get("all_seen_tests", []) - ) - last_failed_tests = set( - Path(test) for test in cache_.get("failed_tests", []) - ) + all_seen_tests = set(Path(test) for test in cache_.get("all_seen_tests", [])) + last_failed_tests = set(Path(test) for test in cache_.get("failed_tests", [])) except Exception as e: logging.error(f"Error reading cache file: {e}") @@ -241,7 +240,6 @@ def clear_cache(): break if skip_test: continue - # If --last-failed is set, only include the tests that are in the 'failed_tests' cache if args.lf and test not in last_failed_tests: continue @@ -257,11 +255,15 @@ def clear_cache(): if args.nf and test not in all_seen_tests: tests.appendleft(Path(test)) continue - + tests.append(Path(test)) logging.info(f"Found {len(tests)} tests") +# If --new-last is set, sort by mtime +if args.nl: + tests = deque(sorted(tests, key=lambda x: x.stat().st_mtime)) + def format_test_list(tests: Set[Path]) -> str: return "\n\t".join([test.name for test in tests]) @@ -282,6 +284,7 @@ def format_test_list(tests: Set[Path]) -> str: maxfail = 1 # Only skip the first failing test if --sw-skip is set os.environ["SML_TEST_MAXFAIL"] = str(maxfail) + def check_for_runtime_error(output: str) -> bool: if "uncaught exception" in output: return True @@ -344,12 +347,15 @@ def run_test(test_path: Path): logging.critical("Error with test runner script") print(f"Error: {error}") + logging.info("Running tests") logging.debug(f"All Tests: {tests}") for test in tests: run_test(test) if passed_tests: - logging.info(f"Passed all tests in files:\n\t[green]{format_test_list(passed_tests)}[/green]") + logging.info( + f"Passed all tests in files:\n\t[green]{format_test_list(passed_tests)}[/green]" + ) if compile_error_tests: logging.critical( f"Compilation error in test files:[bold red]\n\t{format_test_list(compile_error_tests)}[/bold red]" @@ -359,7 +365,9 @@ def run_test(test_path: Path): f"Runtimes errors in test files:\n\t[red]{format_test_list(runtime_error_tests)}[/red]" ) if failed_tests: - logging.warning(f"Failed tests in files:\n\t[dim red]{format_test_list(failed_tests)}[/dim red]") + logging.warning( + f"Failed tests in files:\n\t[dim red]{format_test_list(failed_tests)}[/dim red]" + ) passed_all = len(passed_tests) == len(tests) if passed_all: logging.info("All tests passed!") @@ -376,18 +384,18 @@ def run_test(test_path: Path): clear_cache() cache = {} + def write_(test_set: Set[Path], cache_key: str): if len(test_set) == 0: cache[cache_key] = [] return - + cache[cache_key] = [str(test.resolve()) for test in test_set] + write_(runtime_error_tests, "runtime_error_tests") write_(failed_tests, "failed_tests") write_(passed_tests, "passed_tests") -write_( - runtime_error_tests.union(failed_tests).union(passed_tests), "all_seen_tests" -) +write_(runtime_error_tests.union(failed_tests).union(passed_tests), "all_seen_tests") with open(args.cache_path, "w") as cache_file: json.dump(cache, cache_file) diff --git a/tests/tests-ica05/test_addLists.sml b/tests/tests-ica05/test_addLists.sml index 8c97e2e..afe7d93 100644 --- a/tests/tests-ica05/test_addLists.sml +++ b/tests/tests-ica05/test_addLists.sml @@ -12,4 +12,6 @@ val testCasesAddLists = [ (addLists, ([1, 2, 3], [4, 5, 6, 7]), [5, 7, 9]), (addLists, ([1, 2, 3, 4, 5], [4, 5, 6, 0, 0]), [5, 7, 9, 4, 5]) ]; -runTestCasesIntListIntList(testCasesAddLists); + +fun addListsParamsToString(li1, li2) = "addLists(" ^ valueToStringIntList(li1) ^ ", " ^ valueToStringIntList(li2) ^ ")"; +runTests(testCasesAddLists, addListsParamsToString, valueToStringIntList); \ No newline at end of file diff --git a/tests/tests-ica05/test_andList.sml b/tests/tests-ica05/test_andList.sml index daf06b7..699f876 100644 --- a/tests/tests-ica05/test_andList.sml +++ b/tests/tests-ica05/test_andList.sml @@ -4,11 +4,13 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesAndList = [ - (andList, [true, true, true], true), - (andList, [true, false, true], false), - (andList, [false, false, false], false), - (andList, [], true), (* Empty list case *) - (andList, [true], true), - (andList, [false], false) + (andList, ([true, true, true]), true), + (andList, ([true, false, true]), false), + (andList, ([false, false, false]), false), + (andList, ([]), true), (* Empty list case *) + (andList, ([true]), true), + (andList, ([false]), false) ]; -runTestCasesBoolListBool(testCasesAndList); + +fun andListParamsToString(li) = "andList(" ^ valueToStringBoolList(li) ^ ")"; +runTests(testCasesAndList, andListParamsToString, valueToStringBool); \ No newline at end of file diff --git a/tests/tests-ica05/test_combineLists.sml b/tests/tests-ica05/test_combineLists.sml index f52c430..945a0c5 100644 --- a/tests/tests-ica05/test_combineLists.sml +++ b/tests/tests-ica05/test_combineLists.sml @@ -9,19 +9,21 @@ fun mockDiv(x, y) = x div y; fun doNothing(x, y) = x; val testCasesCombineLists = [ - (combineLists, [1, 2, 3], [4, 5, 6], mockAdd, [5, 7, 9]), - (combineLists, [1, 2], [4, 5, 6], mockAdd, [5, 7]), - (combineLists, [], [4, 5, 6], mockModulus, []), - (combineLists, [], [], mockModulus, []), - (combineLists, [1, 2, 3], [4, 5, 6], mockDiv, [0, 0, 0]), - (combineLists, [1, 2], [4, 5, 6], mockDiv, [0, 0]), - (combineLists, [8, 8, 3], [4], mockDiv, [2]), - (combineLists, [4], [4, 5, 6], mockDiv, [1]), - (combineLists, [], [], mockDiv, []), - (combineLists, [1, 2, 3], [4, 5, 6], doNothing, [1, 2, 3]), - (combineLists, [1, 2], [4, 5, 6], doNothing, [1, 2]), - (combineLists, [1, 2, 3], [], doNothing, []), - (combineLists, [], [4, 5, 6], doNothing, []), - (combineLists, [], [], doNothing, []) + (combineLists, ([1, 2, 3], [4, 5, 6], mockAdd), [5, 7, 9]), + (combineLists, ([1, 2], [4, 5, 6], mockAdd), [5, 7]), + (combineLists, ([], [4, 5, 6], mockModulus), []), + (combineLists, ([], [], mockModulus), []), + (combineLists, ([1, 2, 3], [4, 5, 6], mockDiv), [0, 0, 0]), + (combineLists, ([1, 2], [4, 5, 6], mockDiv), [0, 0]), + (combineLists, ([8, 8, 3], [4], mockDiv), [2]), + (combineLists, ([4], [4, 5, 6], mockDiv), [1]), + (combineLists, ([], [], mockDiv), []), + (combineLists, ([1, 2, 3], [4, 5, 6], doNothing), [1, 2, 3]), + (combineLists, ([1, 2], [4, 5, 6], doNothing), [1, 2]), + (combineLists, ([1, 2, 3], [], doNothing), []), + (combineLists, ([], [4, 5, 6], doNothing), []), + (combineLists, ([], [], doNothing), []) ]; -runTestCasesIntListIntListOperatorToIntList(testCasesCombineLists); \ No newline at end of file + +fun combineListsParamsToString(li1, li2, _) = "combineLists(" ^ valueToStringIntList(li1) ^ ", " ^ valueToStringIntList(li2) ^ ")"; +runTests(testCasesCombineLists, combineListsParamsToString, valueToStringIntList); \ No newline at end of file diff --git a/tests/tests-ica05/test_countZeros.sml b/tests/tests-ica05/test_countZeros.sml index 3a5bfd8..b6b9e24 100644 --- a/tests/tests-ica05/test_countZeros.sml +++ b/tests/tests-ica05/test_countZeros.sml @@ -4,14 +4,16 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesCountZeros = [ - (countZeros, [0, 1, 0, 1, 0], 3), - (countZeros, [1, 1, 1, 1, 1], 0), - (countZeros, [0, 0, 0, 0, 0], 5), - (countZeros, [], 0), - (countZeros, [0], 1), - (countZeros, [1], 0), - (countZeros, [0, 0, 0, 1, 0], 4), - (countZeros, [1, 0, 1, 0, 1], 2), - (countZeros, [0, 0, 1, 0, 0, 0], 5) + (countZeros, ([0, 1, 0, 1, 0]), 3), + (countZeros, ([1, 1, 1, 1, 1]), 0), + (countZeros, ([0, 0, 0, 0, 0]), 5), + (countZeros, ([]), 0), + (countZeros, ([0]), 1), + (countZeros, ([1]), 0), + (countZeros, ([0, 0, 0, 1, 0]), 4), + (countZeros, ([1, 0, 1, 0, 1]), 2), + (countZeros, ([0, 0, 1, 0, 0, 0]), 5) ]; -runTestCasesIntListInt(testCasesCountZeros); \ No newline at end of file + +fun countZerosParamsToString(li) = "countZeros(" ^ valueToStringIntList(li) ^ ")"; +runTests(testCasesCountZeros, countZerosParamsToString, Int.toString); \ No newline at end of file diff --git a/tests/tests-ica05/test_factorial.sml b/tests/tests-ica05/test_factorial.sml index 894a510..8f6f2b4 100644 --- a/tests/tests-ica05/test_factorial.sml +++ b/tests/tests-ica05/test_factorial.sml @@ -4,15 +4,17 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesFactorial = [ - (factorial, 0, 1), (* 0! = 1 *) - (factorial, 1, 1), (* 1! = 1 *) - (factorial, 2, 2), (* 2! = 2 *) - (factorial, 3, 6), (* 3! = 6 *) - (factorial, 4, 24), (* 4! = 24 *) - (factorial, 5, 120), (* 5! = 120 *) - (factorial, 6, 720), (* 6! = 720 *) - (factorial, 7, 5040), (* 7! = 5040 *) - (factorial, 8, 40320), (* 8! = 40320 *) - (factorial, 10, 3628800) (* 10! = 3628800 *) + (factorial, (0), 1), (* 0! = 1 *) + (factorial, (1), 1), (* 1! = 1 *) + (factorial, (2), 2), (* 2! = 2 *) + (factorial, (3), 6), (* 3! = 6 *) + (factorial, (4), 24), (* 4! = 24 *) + (factorial, (5), 120),(* 5! = 120 *) + (factorial, (6), 720),(* 6! = 720 *) + (factorial, (7), 5040),(* 7! = 5040 *) + (factorial, (8), 40320),(* 8! = 40320 *) + (factorial, (10), 3628800) (* 10! = 3628800 *) ]; -runTestCasesIntInt(testCasesFactorial); \ No newline at end of file + +fun factorialParamsToString(n) = "factorial(" ^ Int.toString(n) ^ ")"; +runTests(testCasesFactorial, factorialParamsToString, Int.toString); \ No newline at end of file diff --git a/tests/tests-ica05/test_fib.sml b/tests/tests-ica05/test_fib.sml index 67f05c2..3a602e8 100644 --- a/tests/tests-ica05/test_fib.sml +++ b/tests/tests-ica05/test_fib.sml @@ -5,16 +5,18 @@ use "tests/utils.sml"; val testCasesFib = [ (* a_n = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} *) - (fib, 0, 0), - (fib, 1, 1), - (fib, 2, 1), - (fib, 3, 2), - (fib, 4, 3), - (fib, 5, 5), - (fib, 6, 8), - (fib, 7, 13), - (fib, 8, 21), - (fib, 9, 34), - (fib, 10, 55) + (fib, (0), 0), + (fib, (1), 1), + (fib, (2), 1), + (fib, (3), 2), + (fib, (4), 3), + (fib, (5), 5), + (fib, (6), 8), + (fib, (7), 13), + (fib, (8), 21), + (fib, (9), 34), + (fib, (10), 55) ]; -runTestCasesIntInt(testCasesFib); \ No newline at end of file + +fun fibParamsToString(n) = "fib(" ^ Int.toString(n) ^ ")"; +runTests(testCasesFib, fibParamsToString, Int.toString); \ No newline at end of file diff --git a/tests/tests-ica05/test_log2.sml b/tests/tests-ica05/test_log2.sml index d59cd9b..58380c7 100644 --- a/tests/tests-ica05/test_log2.sml +++ b/tests/tests-ica05/test_log2.sml @@ -4,14 +4,16 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesLog2 = [ - (log2, 1, 0), (* 2^0 = 1 *) - (log2, 2, 1), (* 2^1 = 2 *) - (log2, 4, 2), (* 2^2 = 4 *) - (log2, 8, 3), (* 2^3 = 8 *) - (log2, 16, 4), (* 2^4 = 16 *) - (log2, 32, 5), (* 2^5 = 32 *) - (log2, 64, 6), (* 2^6 = 64 *) - (log2, 128, 7), (* 2^7 = 128 *) - (log2, 256, 8) (* 2^8 = 256 *) + (log2, (1), 0), (* 2^0 = 1 *) + (log2, (2), 1), (* 2^1 = 2 *) + (log2, (4), 2), (* 2^2 = 4 *) + (log2, (8), 3), (* 2^3 = 8 *) + (log2, (16), 4), (* 2^4 = 16 *) + (log2, (32), 5), (* 2^5 = 32 *) + (log2, (64), 6), (* 2^6 = 64 *) + (log2, (128), 7),(* 2^7 = 128 *) + (log2, (256), 8) (* 2^8 = 256 *) ]; -runTestCasesIntInt(testCasesLog2); + +fun log2ParamsToString(n) = "log2(" ^ Int.toString(n) ^ ")"; +runTests(testCasesLog2, log2ParamsToString, Int.toString); \ No newline at end of file diff --git a/tests/tests-ica05/test_orList.sml b/tests/tests-ica05/test_orList.sml index 4cefc07..4ddc353 100644 --- a/tests/tests-ica05/test_orList.sml +++ b/tests/tests-ica05/test_orList.sml @@ -4,15 +4,17 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesOrList = [ - (orList, [true, true, true], true), - (orList, [true, false, true], true), - (orList, [false, false, false], false), - (orList, [true, true, false], true), - (orList, [false, false, true], true), - (orList, [false, false, false, false], false), - (orList, [false, true, false, true], true), - (orList, [], false), (* Empty list case *) - (orList, [false], false), (* Single false element *) - (orList, [true], true) (* Single true element *) + (orList, ([true, true, true]), true), + (orList, ([true, false, true]), true), + (orList, ([false, false, false]), false), + (orList, ([true, true, false]), true), + (orList, ([false, false, true]), true), + (orList, ([false, false, false, false]), false), + (orList, ([false, true, false, true]), true), + (orList, ([]), false), (* Empty list case *) + (orList, ([false]), false), (* Single false element *) + (orList, ([true]), true) (* Single true element *) ]; -runTestCasesBoolListBool(testCasesOrList); + +fun orListParamsToString(li) = "orList(" ^ valueToStringBoolList(li) ^ ")"; +runTests(testCasesOrList, orListParamsToString, valueToStringBool); \ No newline at end of file diff --git a/tests/tests-ica05/test_removeZeros.sml b/tests/tests-ica05/test_removeZeros.sml index 3976cad..1fc5e86 100644 --- a/tests/tests-ica05/test_removeZeros.sml +++ b/tests/tests-ica05/test_removeZeros.sml @@ -4,13 +4,15 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesRemoveZeros = [ - (removeZeros, [0, 1, 0, 2], [1, 2]), - (removeZeros, [0, 0, 0], []), - (removeZeros, [1, 2, 3], [1, 2, 3]), - (removeZeros, [], []), - (removeZeros, [0], []), - (removeZeros, [1], [1]), - (removeZeros, [0, 0, 0, 1, 0], [1]), - (removeZeros, [1, 0, 1, 0, 1], [1, 1, 1]) + (removeZeros, ([0, 1, 0, 2]), [1, 2]), + (removeZeros, ([0, 0, 0]), []), + (removeZeros, ([1, 2, 3]), [1, 2, 3]), + (removeZeros, ([]), []), + (removeZeros, ([0]), []), + (removeZeros, ([1]), [1]), + (removeZeros, ([0, 0, 0, 1, 0]), [1]), + (removeZeros, ([1, 0, 1, 0, 1]), [1, 1, 1]) ]; -runTestCasesIntListIntList(testCasesRemoveZeros); + +fun removeZerosParamsToString(li) = "removeZeros(" ^ valueToStringIntList(li) ^ ")"; +runTests(testCasesRemoveZeros, removeZerosParamsToString, valueToStringIntList); \ No newline at end of file diff --git a/tests/tests-ica05/test_reverseList.sml b/tests/tests-ica05/test_reverseList.sml index 5166070..6863b4e 100644 --- a/tests/tests-ica05/test_reverseList.sml +++ b/tests/tests-ica05/test_reverseList.sml @@ -4,13 +4,15 @@ use "src/ica05/ica5.sml"; use "tests/utils.sml"; val testCasesReverseList = [ - (reverseList, [1, 2, 3], [3, 2, 1]), - (reverseList, [1], [1]), - (reverseList, [], []), - (reverseList, [4, 5, 6, 7], [7, 6, 5, 4]), - (reverseList, [1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), - (reverseList, [1, 2, 3, 4, 5, 6], [6, 5, 4, 3, 2, 1]), - (reverseList, [1, 0, 0, 1, 0], [0, 1, 0, 0, 1]), - (reverseList, [0, 0, 0], [0, 0, 0]) + (reverseList, ([1, 2, 3]), [3, 2, 1]), + (reverseList, ([1]), [1]), + (reverseList, ([]), []), + (reverseList, ([4, 5, 6, 7]), [7, 6, 5, 4]), + (reverseList, ([1, 2, 3, 4, 5]), [5, 4, 3, 2, 1]), + (reverseList, ([1, 2, 3, 4, 5, 6]), [6, 5, 4, 3, 2, 1]), + (reverseList, ([1, 0, 0, 1, 0]), [0, 1, 0, 0, 1]), + (reverseList, ([0, 0, 0]), [0, 0, 0]) ]; -runTestCasesIntListIntList(testCasesReverseList); + +fun reverseListParamsToString(li) = "reverseList(" ^ valueToStringIntList(li) ^ ")"; +runTests(testCasesReverseList, reverseListParamsToString, valueToStringIntList); \ No newline at end of file diff --git a/tests/tests-large_assignment_01/test_cycle.sml b/tests/tests-large_assignment_01/test_cycle.sml new file mode 100644 index 0000000..0db0a1d --- /dev/null +++ b/tests/tests-large_assignment_01/test_cycle.sml @@ -0,0 +1,43 @@ +(* Large Assignment 01 Tests *) + +use "src/large_assignment_01/large_assignment_01.sml"; +use "tests/utils.sml"; + +val testCycle = [ + (* 1. Simple case: cycle 4 elements from the front to the back *) + (cycle, (4, [1,2,3,4,5,6,7]), [5,6,7,1,2,3,4]), + + (* 2. Cycling by 0 should return the list unchanged *) + (cycle, (0, [1,2,3,4,5,6,7]), [1,2,3,4,5,6,7]), + + (* 3. Cycling by 1 should move the first element to the end *) + (cycle, (1, [1,2,3,4,5]), [2,3,4,5,1]), + + (* 4. Cycling by the list length should return the list unchanged *) + (cycle, (7, [1,2,3,4,5,6,7]), [1,2,3,4,5,6,7]), + + (* 5. Cycling by more than the list length (n > length) *) + (cycle, (11, [1,2,3,4,5]), [2,3,4,5,1]), + + (* 6. Empty list should return an empty list *) + (cycle, (3, []), []), + + (* 7. Single element list, cycling should return the same list *) + (cycle, (2, [42]), [42]), + + (* 8. Cycling by a number greater than the length in a single element list *) + (cycle, (5, [99]), [99]), (* No effect regardless of cycle length *) + + (* 9. Cycling by the full length of the list *) + (cycle, (3, [1,2,3]), [1,2,3]), (* Equivalent to cycling by 3 mod 3 = 0, no change *) + + (* 10. Cycling by 2 elements in a list of 4 *) + (cycle, (2, [10,20,30,40]), [30,40,10,20]), + + (* 11. Cycling by a multiple of the list returns the list unchanged *) + (cycle, (36, [1,2,3,4,5,6]), [1,2,3,4,5,6]) +]; + +fun cycleParamsToString((n, li)) = "cycle(" ^ Int.toString(n) ^ ", " ^ valueToStringIntList(li) ^ ")"; + +runTests(testCycle, cycleParamsToString, valueToStringIntList); \ No newline at end of file diff --git a/tests/tests-large_assignment_01/test_gtList.sml b/tests/tests-large_assignment_01/test_gtList.sml new file mode 100644 index 0000000..f88b99b --- /dev/null +++ b/tests/tests-large_assignment_01/test_gtList.sml @@ -0,0 +1,53 @@ +(* Large Assignment 01 Tests *) + +use "src/large_assignment_01/large_assignment_01.sml"; +use "tests/utils.sml"; + +val testCasesGtList = [ + (* 1. Empty list case *) + (gtList, (0, []), []), + + (* 2. All elements greater than n *) + (gtList, (3, [4, 5, 6]), [4, 5, 6]), + + (* 3. No elements greater than n *) + (gtList, (10, [1, 2, 3, 4, 5]), []), + + (* 4. Some elements greater than n *) + (gtList, (3, [1, 4, 2, 5, 3, 6]), [4, 5, 6]), + + (* 5. Single element list, element greater than n *) + (gtList, (0, [1]), [1]), + + (* 6. Single element list, element not greater than n *) + (gtList, (2, [1]), []), + + (* 7. List with duplicate elements, some greater than n *) + (gtList, (2, [2, 3, 3, 1, 4, 2]), [3, 3, 4]), + + (* 8. List with negative elements, looking for positives greater than n *) + (gtList, (0, [~1, ~2, 1, 2, ~3]), [1, 2]), + + (* 9. List with mixed positive and negative numbers *) + (gtList, (~1, [~5, 0, ~2, 2, 3]), [0, 2, 3]), + + (* 10. List with all elements less than n *) + (gtList, (10, [5, 6, 7, 8, 9]), []), + + (* 11. List with all elements equal to n *) + (gtList, (3, [3, 3, 3, 3]), []), + + (* 12. List with elements that alternate greater and less than n *) + (gtList, (3, [1, 4, 2, 5, 3, 6]), [4, 5, 6]), + + (* 13. List where only the last element is greater than n *) + (gtList, (2, [1, 2, 1, 2, 1, 3]), [3]), + + (* 14. List with a large n, no elements greater *) + (gtList, (100, [50, 60, 70]), []), + + (* 15. List with a large n, some elements greater *) + (gtList, (60, [50, 65, 70, 40, 80]), [65, 70, 80]) +]; + +runTests(testCasesGtList, fn (n, li) => "(" ^ Int.toString n ^ ", " ^ valueToStringIntList li ^ ")", valueToStringIntList); diff --git a/tests/tests-large_assignment_01/test_mirror.sml b/tests/tests-large_assignment_01/test_mirror.sml new file mode 100644 index 0000000..1fa057d --- /dev/null +++ b/tests/tests-large_assignment_01/test_mirror.sml @@ -0,0 +1,58 @@ +(* Large Assignment 01 Tests *) + +use "src/large_assignment_01/large_assignment_01.sml"; +use "tests/utils.sml"; + +val testCasesMirrorInt = [ + (* 1. Empty list case *) + (mirror, ([]), []), + + (* 2. Single element list *) + (mirror, ([1]), [1, 1]), + + (* 3. Two element list *) + (mirror, ([1, 2]), [1, 2, 2, 1]), + + (* 4. Three element list *) + (mirror, ([1, 2, 3]), [1, 2, 3, 3, 2, 1]), + + (* 5. Four element list *) + (mirror, ([1, 2, 3, 4]), [1, 2, 3, 4, 4, 3, 2, 1]), + + (* 6. List with duplicate elements *) + (mirror, ([1, 1, 1]), [1, 1, 1, 1, 1, 1]), + + (* 7. List with alternating elements *) + (mirror, ([1, 0, 1, 0]), [1, 0, 1, 0, 0, 1, 0, 1]), + + (* 8. List with negative numbers *) + (mirror, ([~1, ~2, ~3]), [~1, ~2, ~3, ~3, ~2, ~1]), + + (* 14. Longer list (6 elements) *) + (mirror, ([1, 2, 3, 4, 5, 6]), [1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1]) +]; + +runTests(testCasesMirrorInt, valueToStringIntList, valueToStringIntList); + +val testCasesMirrorBools = [ + (* 9. List of booleans *) + (mirror, ([true, false, true]), [true, false, true, true, false, true]), + + (* 12. List of boolean values with all true *) + (mirror, ([true, true, true]), [true, true, true, true, true, true]), + + (* 13. List of boolean values with all false *) + (mirror, ([false, false]), [false, false, false, false]) +]; + +runTests(testCasesMirrorBools, valueToStringBoolList, valueToStringBoolList); + +val testCasesMirrorStrings = [ + (* 10. List of strings *) + (mirror, (["a", "b", "c"]), ["a", "b", "c", "c", "b", "a"]), + + (* 11. List with mixed types (strings and integers) *) + (mirror, (["hello", "world"]), ["hello", "world", "world", "hello"]) +]; + +runTests(testCasesMirrorStrings, valueToStringStringList, valueToStringStringList); \ No newline at end of file diff --git a/tests/tests-large_assignment_01/test_suffix.sml b/tests/tests-large_assignment_01/test_suffix.sml new file mode 100644 index 0000000..2108c42 --- /dev/null +++ b/tests/tests-large_assignment_01/test_suffix.sml @@ -0,0 +1,115 @@ +(* Large Assignment 01 Tests *) + +use "src/large_assignment_01/large_assignment_01.sml"; +use "tests/utils.sml"; + +val testCasesSuffixInt = [ + (* 1. Both lists are empty (suffix of each other) *) + (suffix, ([], []), true), + + (* 2. First list is empty, second list is non-empty (suffix of any list) *) + (suffix, ([], [1, 2, 3]), true), + + (* 3. Second list is empty, first list is non-empty (not a suffix) *) + (suffix, ([1, 2, 3], []), false), + + (* 4. First list is a suffix of the second *) + (suffix, ([3, 4], [1, 2, 3, 4]), true), + + (* 5. First list is not a suffix of the second *) + (suffix, ([2, 3], [1, 2, 3, 4]), false), + + (* 6. Both lists are equal (a list is a suffix of itself) *) + (suffix, ([1, 2, 3], [1, 2, 3]), true), + + (* 7. First list is longer than the second (cannot be a suffix) *) + (suffix, ([1, 2, 3, 4], [3, 4]), false), + + (* 8. First list is an exact suffix (lengths equal, last elements match) *) + (suffix, ([4], [1, 2, 3, 4]), true), + + (* 9. First list is not a suffix due to one element mismatch *) + (suffix, ([3, 5], [1, 2, 3, 4]), false), + + (* 10. List with duplicate elements (valid suffix) *) + (suffix, ([3, 3], [2, 3, 3]), true), + + (* 11. List with duplicate elements *) + (suffix, ([3, 3], [3, 2, 3, 3]), true) +]; + +runTests(testCasesSuffixInt, fn (li1, li2) => "(" ^ valueToStringIntList li1 ^ ", " ^ valueToStringIntList li2 ^ ")", valueToStringBool); + +val testCasesSuffixBool = [ + (* 1. Both lists are empty (suffix of each other) *) + (suffix, ([], []), true), + + (* 2. First list is empty, second list is non-empty (suffix of any list) *) + (suffix, ([], [true, false, true]), true), + + (* 3. Second list is empty, first list is non-empty (not a suffix) *) + (suffix, ([true, false, true], []), false), + + (* 4. First list is a suffix of the second *) + (suffix, ([true, false], [true, false, true, false]), true), + + (* 5. First list is not a suffix of the second *) + (suffix, ([false, true], [true, false, true, false]), false), + + (* 6. Both lists are equal (a list is a suffix of itself) *) + (suffix, ([true, false, true], [true, false, true]), true), + + (* 7. First list is longer than the second (cannot be a suffix) *) + (suffix, ([true, false, true, false], [true, false]), false), + + (* 8. First list is an exact suffix (lengths equal, last elements match) *) + (suffix, ([false], [true, false]), true), + + (* 9. First list is not a suffix due to one element mismatch *) + (suffix, ([true, false], [true, false, true]), false), + + (* 10. List with duplicate elements (valid suffix) *) + (suffix, ([true, true], [false, true, true]), true), + + (* 11. List with duplicate elements *) + (suffix, ([true, true], [true, false, true, true]), true) +]; + +runTests(testCasesSuffixBool, fn (li1, li2) => "(" ^ valueToStringBoolList li1 ^ ", " ^ valueToStringBoolList li2 ^ ")", valueToStringBool); + +val testCasesSuffixString = [ + (* 1. Both lists are empty (suffix of each other) *) + (suffix, ([], []), true), + + (* 2. First list is empty, second list is non-empty (suffix of any list) *) + (suffix, ([], ["a", "b", "c"]), true), + + (* 3. Second list is empty, first list is non-empty (not a suffix) *) + (suffix, (["a", "b", "c"], []), false), + + (* 4. First list is a suffix of the second *) + (suffix, (["b", "c"], ["a", "b", "c"]), true), + + (* 5. First list is not a suffix of the second *) + (suffix, (["a", "b"], ["a", "b", "c"]), false), + + (* 6. Both lists are equal (a list is a suffix of itself) *) + (suffix, (["a", "b", "c"], ["a", "b", "c"]), true), + + (* 7. First list is longer than the second (cannot be a suffix) *) + (suffix, (["a", "b", "c", "d"], ["b", "c"]), false), + + (* 8. First list is an exact suffix (lengths equal, last elements match) *) + (suffix, (["d"], ["a", "b", "c", "d"]), true), + + (* 9. First list is not a suffix due to one element mismatch *) + (suffix, (["c", "e"], ["a", "b", "c", "d"]), false), + + (* 10. List with duplicate elements (valid suffix) *) + (suffix, (["c", "c"], ["b", "c", "c"]), true), + + (* 11. List with duplicate elements *) + (suffix, (["c", "c"], ["c", "b", "c", "c"]), true) +]; + +runTests(testCasesSuffixString, fn (li1, li2) => "(" ^ valueToStringStringList li1 ^ ", " ^ valueToStringStringList li2 ^ ")", valueToStringBool); diff --git a/tests/tests-large_assignment_01/test_triangle.sml b/tests/tests-large_assignment_01/test_triangle.sml index 58cd2ee..47c8559 100644 --- a/tests/tests-large_assignment_01/test_triangle.sml +++ b/tests/tests-large_assignment_01/test_triangle.sml @@ -7,81 +7,82 @@ val testTriangle = [ (* Test Cases That Should Fail (Return false) *) (* 1. One side is too long *) - (triangle, 1, 2, 4, false), (* 1 + 2 < 4, should fail *) + (triangle, (1, 2, 4), false), (* 1 + 2 < 4, should fail *) (* 2. All sides are zero *) - (triangle, 0, 0, 0, false), (* No valid triangle can have zero-length sides *) + (triangle, (0, 0, 0), false), (* No valid triangle can have zero-length sides *) (* 3. One side is zero *) - (triangle, 0, 5, 7, false), (* One side being zero makes it an invalid triangle *) + (triangle, (0, 5, 7), false), (* One side being zero makes it an invalid triangle *) (* 4. Extremely large sides that violate inequality *) - (triangle, 1000000, 1, 1, false), (* 1000000 + 1 < 1, should fail *) + (triangle, (1000000, 1, 1), false), (* 1000000 + 1 < 1, should fail *) (* 5. Two equal sides but still invalid *) - (triangle, 10, 10, 25, false), (* 10 + 10 < 25, should fail *) + (triangle, (10, 10, 25), false), (* 10 + 10 < 25, should fail *) (* 6. All negative sides *) - (triangle, ~1, ~1, ~1, false), (* Negative values cannot form a triangle *) + (triangle, (~1, ~1, ~1), false), (* Negative values cannot form a triangle *) (* 7. One negative side *) - (triangle, ~3, 4, 5, false), (* Negative sides make it invalid *) + (triangle, (~3, 4, 5), false), (* Negative sides make it invalid *) (* 8. One side is larger than the sum of the other two *) - (triangle, 1, 1, 3, false), (* 1 + 1 < 3, should fail *) + (triangle, (1, 1, 3), false), (* 1 + 1 < 3, should fail *) (* 9. Middle arg is zero *) - (triangle, 2, 0, 2, false), (* You cannot make a triangle with a zero-length side *) + (triangle, (2, 0, 2), false), (* You cannot make a triangle with a zero-length side *) (* 10. Last arg is zero *) - (triangle, 2, 2, 0, false), (* You cannot make a triangle with a zero-length side *) + (triangle, (2, 2, 0), false), (* You cannot make a triangle with a zero-length side *) (* 11. Two sides are zero *) - (triangle, 0, 2, 0, false), (* You cannot make a triangle with a zero-length side *) + (triangle, (0, 2, 0), false), (* You cannot make a triangle with a zero-length side *) (* 12. Two sides are zero *) - (triangle, 2, 0, 0, false), (* You cannot make a triangle with a zero-length side *) + (triangle, (2, 0, 0), false), (* You cannot make a triangle with a zero-length side *) (* 13. Two sides are zero *) - (triangle, 0, 0, 2, false), (* You cannot make a triangle with a zero-length side *) + (triangle, (0, 0, 2), false), (* You cannot make a triangle with a zero-length side *) (* Test Cases That Should Pass (Return true) *) (* 1. Simple valid triangle *) - (triangle, 3, 4, 5, true), (* Classic 3-4-5 triangle, should pass *) + (triangle, (3, 4, 5), true), (* Classic 3-4-5 triangle, should pass *) (* 2. Equilateral triangle *) - (triangle, 5, 5, 5, true), (* All sides equal, valid triangle *) + (triangle, (5, 5, 5), true), (* All sides equal, valid triangle *) (* 3. Isosceles triangle *) - (triangle, 5, 5, 8, true), (* Two sides equal, valid triangle *) + (triangle, (5, 5, 8), true), (* Two sides equal, valid triangle *) (* 4. All sides equal but larger values *) - (triangle, 100, 100, 100, true), (* All sides equal, valid triangle *) + (triangle, (100, 100, 100), true), (* All sides equal, valid triangle *) (* 5. Large valid triangle *) - (triangle, 1000000, 1000000, 1000000, true), (* Large triangle, all sides equal *) + (triangle, (1000000, 1000000, 1000000), true), (* Large triangle, all sides equal *) (* 6. Right triangle *) - (triangle, 6, 8, 10, true), (* Pythagorean triple, valid triangle *) + (triangle, (6, 8, 10), true), (* Pythagorean triple, valid triangle *) (* 7. One small side, two large sides *) - (triangle, 1, 1000, 1001, true), (* 1 + 1000 > 1001, should pass *) + (triangle, (1, 1000, 1001), true), (* 1 + 1000 > 1001, should pass *) (* 8. Two equal sides, third side small but valid *) - (triangle, 10, 10, 15, true), (* 10 + 10 > 15, valid triangle *) + (triangle, (10, 10, 15), true), (* 10 + 10 > 15, valid triangle *) (* 9. Minimal valid triangle *) - (triangle, 1, 1, 1, true), (* Smallest valid triangle, all sides 1 *) + (triangle, (1, 1, 1), true), (* Smallest valid triangle, all sides 1 *) (* 10. Very large triangle with different sides *) - (triangle, 500000, 600000, 700000, true), (* Large triangle with different sides *) + (triangle, (500000, 600000, 700000), true), (* Large triangle with different sides *) (* 11. Two sides equal the third *) - (triangle, 2, 2, 4, true), (* 2 + 2 = 4, but no strict inequality, so shuld pass *) + (triangle, (2, 2, 4), true), (* 2 + 2 = 4, but no strict inequality, so should pass *) (* 12. Two large sides, one very small side *) - (triangle, 9999, 9999, 1, true) (* 9999 + 1 > 9999, should pass always *) + (triangle, (9999, 9999, 1), true) (* 9999 + 1 > 9999, should pass always *) ]; -runTestCasesIntIntIntBool(testTriangle); +fun triangleParamsToString(a, b, c) = "triangle(" ^ valueToStringInt(a) ^ ", " ^ valueToStringInt(b) ^ ", " ^ valueToStringInt(c) ^ ")"; +runTests(testTriangle, triangleParamsToString, valueToStringBool); diff --git a/tests/tests-large_assignment_01/test_triangleR.sml b/tests/tests-large_assignment_01/test_triangleR.sml index 611b382..8739cbd 100644 --- a/tests/tests-large_assignment_01/test_triangleR.sml +++ b/tests/tests-large_assignment_01/test_triangleR.sml @@ -3,30 +3,32 @@ use "src/large_assignment_01/large_assignment_01.sml"; use "tests/utils.sml"; -val testTriangleR = [ - (* Test Cases That Should Fail (Return false) *) - (triangleR, 1.0, 2.0, 4.0, false), - (triangleR, 0.0, 0.0, 0.0, false), - (triangleR, 0.0, 5.0, 7.0, false), - (triangleR, 1000000.0, 1.0, 1.0, false), - (triangleR, 10.0, 10.0, 25.0, false), - (triangleR, ~1.0, ~1.0, ~1.0, false), - (triangleR, ~3.0, 4.0, 5.0, false), - (triangleR, 1.0, 1.0, 3.0, false), - (triangleR, 2.0, 0.0, 2.0, false), - (triangleR, 2.0, 2.0, 0.0, false), +val testCasesTriangleR = [ + (* Test Cases That Should Return false *) + (triangleR, (1.0, 2.0, 4.0), false), + (triangleR, (0.0, 0.0, 0.0), false), + (triangleR, (0.0, 5.0, 7.0), false), + (triangleR, (1000000.0, 1.0, 1.0), false), + (triangleR, (10.0, 10.0, 25.0), false), + (triangleR, (~1.0, ~1.0, ~1.0), false), + (triangleR, (~3.0, 4.0, 5.0), false), + (triangleR, (1.0, 1.0, 3.0), false), + (triangleR, (2.0, 0.0, 2.0), false), + (triangleR, (2.0, 2.0, 0.0), false), - (* Test Cases That Should Pass (Return true) *) - (triangleR, 3.0, 4.0, 5.0, true), - (triangleR, 5.0, 5.0, 5.0, true), - (triangleR, 5.0, 5.0, 8.0, true), - (triangleR, 100.0, 100.0, 100.0, true), - (triangleR, 1000000.0, 1000000.0, 1000000.0, true), - (triangleR, 6.0, 8.0, 10.0, true), - (triangleR, 1.0, 1000.0, 1001.0, true), - (triangleR, 10.0, 10.0, 15.0, true), - (triangleR, 1.0, 1.0, 1.0, true), - (triangleR, 500000.0, 600000.0, 700000.0, true) + (* Test Cases That Should Return true *) + (triangleR, (3.0, 4.0, 5.0), true), + (triangleR, (5.0, 5.0, 5.0), true), + (triangleR, (5.0, 5.0, 8.0), true), + (triangleR, (100.0, 100.0, 100.0), true), + (triangleR, (1000000.0, 1000000.0, 1000000.0), true), + (triangleR, (6.0, 8.0, 10.0), true), + (triangleR, (1.0, 1000.0, 1001.0), true), + (triangleR, (10.0, 10.0, 15.0), true), + (triangleR, (1.0, 1.0, 1.0), true), + (triangleR, (500000.0, 600000.0, 700000.0), true) ]; -runTestCasesRealRealRealBool(testTriangleR); + +fun triangleRParamsToString((a, b, c)) = "triangleR(" ^ Real.toString(a) ^ ", " ^ Real.toString(b) ^ ", " ^ Real.toString(c) ^ ")"; +runTests(testCasesTriangleR, triangleRParamsToString, valueToStringBool); diff --git a/tests/utils.sml b/tests/utils.sml index 9b47368..30eff82 100644 --- a/tests/utils.sml +++ b/tests/utils.sml @@ -1,3 +1,7 @@ +(* Test case generator chat session: https://chatgpt.com/c/66f74278-0154-800a-9ca0-abe87126291e *) + +(* --------------------------- toString Functions --------------------------- *) + (* Function to convert int to string *) fun valueToStringInt(n: int) = Int.toString(n); @@ -7,144 +11,45 @@ fun valueToStringBool(true) = "true" (* Function to convert int list to string *) fun valueToStringIntList([]) = "[]" - | valueToStringIntList(lst) = "[" ^ String.concatWith ", " (List.map Int.toString lst) ^ "]"; + | valueToStringIntList(lst) = + "[" ^ String.concatWith ", " (List.map Int.toString lst) ^ "]"; -(* Function to color the output in the terminal *) -fun red(s) = "\027[31m" ^ s ^ "\027[0m" -fun green(s) = "\027[32m" ^ s ^ "\027[0m"; +(* Function to convert bool list to string *) +fun valueToStringBoolList([]) = "[]" + | valueToStringBoolList(lst) = + "[" ^ String.concatWith ", " (List.map valueToStringBool lst) ^ "]"; -(* Generic test runner for int * int * int -> bool functions *) -fun runTestCasesIntIntIntBool([]) = () - | runTestCasesIntIntIntBool(testCaseList) = - let - val (f, param1, param2, param3, expected) = hd(testCaseList); - val result = f(param1, param2, param3); - val testMessage = - if result <> expected then - red("Test failed\n") ^ - "Arguments: " ^ valueToStringInt(param1) ^ ", " ^ valueToStringInt(param2) ^ ", " ^ valueToStringInt(param3) ^ "\n" ^ - "Expected: " ^ valueToStringBool(expected) ^ "\n" ^ - "Actual: " ^ valueToStringBool(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesIntIntIntBool(tl(testCaseList)) - end; +(* Function to convert string list to string *) +fun valueToStringStringList([]) = "[]" + | valueToStringStringList(lst) = + "[" ^ String.concatWith ", " lst ^ "]"; -(* Generic test runner for real * real * real -> bool functions *) -fun runTestCasesRealRealRealBool([]) = () - | runTestCasesRealRealRealBool(testCaseList) = - let - val (f, param1, param2, param3, expected) = hd(testCaseList); - val result = f(param1, param2, param3); - val testMessage = - if result <> expected then - red("Test failed\n") ^ - "Arguments: " ^ Real.toString(param1) ^ ", " ^ Real.toString(param2) ^ ", " ^ Real.toString(param3) ^ "\n" ^ - "Expected: " ^ valueToStringBool(expected) ^ "\n" ^ - "Actual: " ^ valueToStringBool(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesRealRealRealBool(tl(testCaseList)) - end; - -(* Generic test runner for int * int -> bool functions *) - -(* Generic test runner for int -> int functions *) -fun runTestCasesIntInt([]) = () - | runTestCasesIntInt(testCaseList) = - let - val (f, param, expected) = hd(testCaseList); - val result = f(param); - val testMessage = - if result <> expected then - red("Test failed\n") ^ - "Expected: " ^ valueToStringInt(expected) ^ "\n" ^ - "Actual: " ^ valueToStringInt(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesIntInt(tl(testCaseList)) - end; +(* ------------------------------- Formatters ------------------------------- *) -(* Generic test runner for bool list -> bool functions *) -fun runTestCasesBoolListBool([]) = () - | runTestCasesBoolListBool(testCaseList) = - let - val (f, param, expected) = hd(testCaseList); - val result = f(param); - val testMessage = - if result <> expected then - red("Test failed\n") ^ - "Expected: " ^ valueToStringBool(expected) ^ "\n" ^ - "Actual: " ^ valueToStringBool(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesBoolListBool(tl(testCaseList)) - end; +(* Function to color the output in the terminal *) +fun red(s) = "\027[31m" ^ s ^ "\027[0m"; +fun green(s) = "\027[32m" ^ s ^ "\027[0m"; -fun runTestCasesIntListInt([]) = () -(* going from int list to a single int *) - | runTestCasesIntListInt(testCaseList) = - let - val (f, param, expected) = hd(testCaseList); - val result = f(param); - val testMessage = - if result <> expected then - red("Test failed\n") ^ - "Expected: " ^ valueToStringInt(expected) ^ "\n" ^ - "Actual: " ^ valueToStringInt(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesIntListInt(tl(testCaseList)) - end; +(* ------------------------------ Test Runners ------------------------------ *) -(* Generic test runner for int list -> int list functions *) -fun runTestCasesIntListIntList([]) = () - | runTestCasesIntListIntList(testCaseList) = - let - val (f, param, expected) = hd(testCaseList); - val result = f(param); - val testMessage = +(* Generic test runner *) +fun runTests([], _, _) = () + | runTests(testCase::testCaseList, paramToString, resultToString) = + let + val (f, params, expected) = testCase + val result = f(params) + val testMessage = if result <> expected then - red("Test failed\n") ^ - "Expected: " ^ valueToStringIntList(expected) ^ "\n" ^ - "Actual: " ^ valueToStringIntList(result) ^ "\n\n" - else - green("Test passed\n"); + red("Test failed\n") ^ + "Arguments: " ^ paramToString(params) ^ "\n" ^ + "Expected: " ^ resultToString(expected) ^ "\n" ^ + "Actual: " ^ resultToString(result) ^ "\n\n" + else + green("Test passed\n") in print(testMessage); (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesIntListIntList(tl(testCaseList)) + if result <> expected then raise Fail("Test failed") + else runTests(testCaseList, paramToString, resultToString) end; -(* Generic test runner for int list -> int list -> int list -> int list functions *) -fun runTestCasesIntListIntListOperatorToIntList([]) = () - | runTestCasesIntListIntListOperatorToIntList(testCaseList) = - let - val (f, param1, param2, operator, expected) = hd(testCaseList); - val result = f(param1, param2, operator); - val testMessage = - if result <> expected then - red("Test failed\n") ^ "Expected: " ^ valueToStringIntList(expected) ^ "\n" ^ - "Actual: " ^ valueToStringIntList(result) ^ "\n\n" - else - green("Test passed\n"); - in - print(testMessage); - (* Error code so GH actions can detect if a test failed *) - if result <> expected then raise Fail("Test failed") else runTestCasesIntListIntListOperatorToIntList(tl(testCaseList)) - end;