From b65621e8d0426d3355175ed2e00832bb15bc4bed Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sat, 20 Jun 2026 14:39:38 +1000 Subject: [PATCH 1/2] generate binary_search triangle --- .../binary-search/test_binary_search.zig | 4 +- exercises/practice/triangle/test_triangle.zig | 2 +- generators/exercises/binary_search.py | 56 +++++++++++++++++++ generators/exercises/triangle.py | 40 +++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 generators/exercises/binary_search.py create mode 100644 generators/exercises/triangle.py diff --git a/exercises/practice/binary-search/test_binary_search.zig b/exercises/practice/binary-search/test_binary_search.zig index e1c1f914..32b8caa3 100644 --- a/exercises/practice/binary-search/test_binary_search.zig +++ b/exercises/practice/binary-search/test_binary_search.zig @@ -33,9 +33,9 @@ test "finds a value at the end of an array" { } test "finds a value in an array of odd length" { - const expected: ?usize = 5; + const expected: ?usize = 9; const array = [_]i16{ 1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634 }; - const actual = binarySearch(i16, 21, &array); + const actual = binarySearch(i16, 144, &array); try testing.expectEqual(expected, actual); } diff --git a/exercises/practice/triangle/test_triangle.zig b/exercises/practice/triangle/test_triangle.zig index 9f6a1a0b..a55e59fe 100644 --- a/exercises/practice/triangle/test_triangle.zig +++ b/exercises/practice/triangle/test_triangle.zig @@ -44,7 +44,7 @@ test "isosceles first and last sides are equal" { } test "equilateral triangles are also isosceles" { - const actual = try triangle.Triangle.init(4, 3, 4); + const actual = try triangle.Triangle.init(4, 4, 4); try testing.expect(actual.isIsosceles()); } diff --git a/generators/exercises/binary_search.py b/generators/exercises/binary_search.py new file mode 100644 index 00000000..685e3bd5 --- /dev/null +++ b/generators/exercises/binary_search.py @@ -0,0 +1,56 @@ +from lib import zarray + +IMPORT_SELF = True +HEADER = "const binarySearch = binary_search.binarySearch;\n" + +# The committed target cycles a different integer type through each case, in order. +_TYPE_BY_DESC = { + "finds a value in an array with one element": "i4", + "finds a value in the middle of an array": "u4", + "finds a value at the beginning of an array": "i8", + "finds a value at the end of an array": "u8", + "finds a value in an array of odd length": "i16", + "finds a value in an array of even length": "u16", + "identifies that a value is not included in the array": "i32", + "a value smaller than the array's smallest value is not found": "u32", + "a value larger than the array's largest value is not found": "i64", + "nothing is found in an empty array": "u64", + "nothing is found when the left and right bounds cross": "isize", +} + +_ORDER = list(_TYPE_BY_DESC.keys()) + +# The committed target searches for 13 in the not-found "empty array" and +# "bounds cross" cases (canonical uses 1 and 0 respectively). +_VALUE_OVERRIDE = { + "nothing is found in an empty array": 13, + "nothing is found when the left and right bounds cross": 13, +} + + +def order_key(case): + try: + return _ORDER.index(case["description"]) + except ValueError: + return len(_ORDER) + + +def gen_case(case): + desc = case["description"] + ty = _TYPE_BY_DESC[desc] + inp = case["input"] + array = zarray([str(v) for v in inp["array"]], ty) + value = _VALUE_OVERRIDE.get(desc, inp["value"]) + expected = case["expected"] + + if isinstance(expected, dict) and "error" in expected: + idx = "null" + else: + idx = str(expected) + + return ( + f" const expected: ?usize = {idx};\n" + f" const array = {array};\n" + f" const actual = binarySearch({ty}, {value}, &array);\n" + " try testing.expectEqual(expected, actual);\n" + ) diff --git a/generators/exercises/triangle.py b/generators/exercises/triangle.py new file mode 100644 index 00000000..7f0c3577 --- /dev/null +++ b/generators/exercises/triangle.py @@ -0,0 +1,40 @@ +_METHODS = { + "equilateral": "isEquilateral", + "isosceles": "isIsosceles", + "scalene": "isScalene", +} + + +def describe(case, parent): + desc = case.get("description") + prop = case.get("property") + if prop and prop not in desc: + return f"{prop} {desc}" + return desc + + +def _fmt(n): + # Integers stay integral (2 -> "2"); floats keep their literal form (0.5 -> "0.5"). + if isinstance(n, float) and not n.is_integer(): + return repr(n) + return str(int(n)) + + +def _is_valid(a, b, c): + return not (a + b <= c or a + c <= b or b + c <= a) + + +def gen_case(case): + a, b, c = case["input"]["sides"] + method = _METHODS[case["property"]] + args = f"{_fmt(a)}, {_fmt(b)}, {_fmt(c)}" + if not _is_valid(a, b, c): + return ( + f" const actual = triangle.Triangle.init({args});\n" + f" try testing.expectError(triangle.TriangleError.Invalid, actual);\n" + ) + bang = "" if case["expected"] else "!" + return ( + f" const actual = try triangle.Triangle.init({args});\n" + f" try testing.expect({bang}actual.{method}());\n" + ) From 9380c9e786cb67b80efaf080ee068c7103654288 Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Sat, 20 Jun 2026 20:35:34 +1000 Subject: [PATCH 2/2] reject comptime solutions https://forum.exercism.org/t/zig-track-needs-tests-against-comptime-abuse-and-other-kinds-of-cheating/59830/ --- .../binary-search/test_binary_search.zig | 60 +++++-------------- exercises/practice/triangle/test_triangle.zig | 50 ++++++++-------- generators/exercises/binary_search.py | 17 +++--- generators/exercises/triangle.py | 13 +++- 4 files changed, 59 insertions(+), 81 deletions(-) diff --git a/exercises/practice/binary-search/test_binary_search.zig b/exercises/practice/binary-search/test_binary_search.zig index 32b8caa3..454abd32 100644 --- a/exercises/practice/binary-search/test_binary_search.zig +++ b/exercises/practice/binary-search/test_binary_search.zig @@ -2,81 +2,51 @@ const std = @import("std"); const testing = std.testing; const binary_search = @import("binary_search.zig"); -const binarySearch = binary_search.binarySearch; + +fn testBinarySearch(comptime T: type, target: T, items: []const T, expected: ?usize) !void { + try testing.expectEqual(expected, binary_search.binarySearch(T, target, items)); +} test "finds a value in an array with one element" { - const expected: ?usize = 0; - const array = [_]i4{6}; - const actual = binarySearch(i4, 6, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(i4, 6, &[_]i4{6}, 0); } test "finds a value in the middle of an array" { - const expected: ?usize = 3; - const array = [_]u4{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(u4, 6, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(u4, 6, &[_]u4{ 1, 3, 4, 6, 8, 9, 11 }, 3); } test "finds a value at the beginning of an array" { - const expected: ?usize = 0; - const array = [_]i8{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(i8, 1, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(i8, 1, &[_]i8{ 1, 3, 4, 6, 8, 9, 11 }, 0); } test "finds a value at the end of an array" { - const expected: ?usize = 6; - const array = [_]u8{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(u8, 11, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(u8, 11, &[_]u8{ 1, 3, 4, 6, 8, 9, 11 }, 6); } test "finds a value in an array of odd length" { - const expected: ?usize = 9; - const array = [_]i16{ 1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634 }; - const actual = binarySearch(i16, 144, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(i16, 144, &[_]i16{ 1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634 }, 9); } test "finds a value in an array of even length" { - const expected: ?usize = 5; - const array = [_]u16{ 1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377 }; - const actual = binarySearch(u16, 21, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(u16, 21, &[_]u16{ 1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377 }, 5); } test "identifies that a value is not included in the array" { - const expected: ?usize = null; - const array = [_]i32{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(i32, 7, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(i32, 7, &[_]i32{ 1, 3, 4, 6, 8, 9, 11 }, null); } test "a value smaller than the array's smallest value is not found" { - const expected: ?usize = null; - const array = [_]u32{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(u32, 0, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(u32, 0, &[_]u32{ 1, 3, 4, 6, 8, 9, 11 }, null); } test "a value larger than the array's largest value is not found" { - const expected: ?usize = null; - const array = [_]i64{ 1, 3, 4, 6, 8, 9, 11 }; - const actual = binarySearch(i64, 13, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(i64, 13, &[_]i64{ 1, 3, 4, 6, 8, 9, 11 }, null); } test "nothing is found in an empty array" { - const expected: ?usize = null; - const array = [_]u64{}; - const actual = binarySearch(u64, 13, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(u64, 13, &[_]u64{}, null); } test "nothing is found when the left and right bounds cross" { - const expected: ?usize = null; - const array = [_]isize{ 1, 2 }; - const actual = binarySearch(isize, 13, &array); - try testing.expectEqual(expected, actual); + try testBinarySearch(isize, 13, &[_]isize{ 1, 2 }, null); } diff --git a/exercises/practice/triangle/test_triangle.zig b/exercises/practice/triangle/test_triangle.zig index a55e59fe..3b78e4b0 100644 --- a/exercises/practice/triangle/test_triangle.zig +++ b/exercises/practice/triangle/test_triangle.zig @@ -2,108 +2,106 @@ const std = @import("std"); const testing = std.testing; const triangle = @import("triangle.zig"); +fn makeTriangle(a: f64, b: f64, c: f64) triangle.TriangleError!triangle.Triangle { + return triangle.Triangle.init(a, b, c); +} test "equilateral all sides are equal" { - const actual = try triangle.Triangle.init(2, 2, 2); + const actual = try makeTriangle(2, 2, 2); try testing.expect(actual.isEquilateral()); } test "equilateral any side is unequal" { - const actual = try triangle.Triangle.init(2, 3, 2); + const actual = try makeTriangle(2, 3, 2); try testing.expect(!actual.isEquilateral()); } test "equilateral no sides are equal" { - const actual = try triangle.Triangle.init(5, 4, 6); + const actual = try makeTriangle(5, 4, 6); try testing.expect(!actual.isEquilateral()); } test "equilateral all zero sides is not a triangle" { - const actual = triangle.Triangle.init(0, 0, 0); - try testing.expectError(triangle.TriangleError.Invalid, actual); + try testing.expectError(triangle.TriangleError.Invalid, makeTriangle(0, 0, 0)); } test "equilateral sides may be floats" { - const actual = try triangle.Triangle.init(0.5, 0.5, 0.5); + const actual = try makeTriangle(0.5, 0.5, 0.5); try testing.expect(actual.isEquilateral()); } test "isosceles last two sides are equal" { - const actual = try triangle.Triangle.init(3, 4, 4); + const actual = try makeTriangle(3, 4, 4); try testing.expect(actual.isIsosceles()); } test "isosceles first two sides are equal" { - const actual = try triangle.Triangle.init(4, 4, 3); + const actual = try makeTriangle(4, 4, 3); try testing.expect(actual.isIsosceles()); } test "isosceles first and last sides are equal" { - const actual = try triangle.Triangle.init(4, 3, 4); + const actual = try makeTriangle(4, 3, 4); try testing.expect(actual.isIsosceles()); } test "equilateral triangles are also isosceles" { - const actual = try triangle.Triangle.init(4, 4, 4); + const actual = try makeTriangle(4, 4, 4); try testing.expect(actual.isIsosceles()); } test "isosceles no sides are equal" { - const actual = try triangle.Triangle.init(2, 3, 4); + const actual = try makeTriangle(2, 3, 4); try testing.expect(!actual.isIsosceles()); } test "isosceles first triangle inequality violation" { - const actual = triangle.Triangle.init(1, 1, 3); - try testing.expectError(triangle.TriangleError.Invalid, actual); + try testing.expectError(triangle.TriangleError.Invalid, makeTriangle(1, 1, 3)); } test "isosceles second triangle inequality violation" { - const actual = triangle.Triangle.init(1, 3, 1); - try testing.expectError(triangle.TriangleError.Invalid, actual); + try testing.expectError(triangle.TriangleError.Invalid, makeTriangle(1, 3, 1)); } test "isosceles third triangle inequality violation" { - const actual = triangle.Triangle.init(3, 1, 1); - try testing.expectError(triangle.TriangleError.Invalid, actual); + try testing.expectError(triangle.TriangleError.Invalid, makeTriangle(3, 1, 1)); } test "isosceles sides may be floats" { - const actual = try triangle.Triangle.init(0.5, 0.4, 0.5); + const actual = try makeTriangle(0.5, 0.4, 0.5); try testing.expect(actual.isIsosceles()); } test "scalene no sides are equal" { - const actual = try triangle.Triangle.init(5, 4, 6); + const actual = try makeTriangle(5, 4, 6); try testing.expect(actual.isScalene()); } test "scalene all sides are equal" { - const actual = try triangle.Triangle.init(4, 4, 4); + const actual = try makeTriangle(4, 4, 4); try testing.expect(!actual.isScalene()); } test "scalene first and second sides are equal" { - const actual = try triangle.Triangle.init(4, 4, 3); + const actual = try makeTriangle(4, 4, 3); try testing.expect(!actual.isScalene()); } test "scalene first and third sides are equal" { - const actual = try triangle.Triangle.init(3, 4, 3); + const actual = try makeTriangle(3, 4, 3); try testing.expect(!actual.isScalene()); } test "scalene second and third sides are equal" { - const actual = try triangle.Triangle.init(4, 3, 3); + const actual = try makeTriangle(4, 3, 3); try testing.expect(!actual.isScalene()); } test "scalene may not violate triangle inequality" { - const actual = triangle.Triangle.init(7, 3, 2); - try testing.expectError(triangle.TriangleError.Invalid, actual); + try testing.expectError(triangle.TriangleError.Invalid, makeTriangle(7, 3, 2)); } test "scalene sides may be floats" { - const actual = try triangle.Triangle.init(0.5, 0.4, 0.6); + const actual = try makeTriangle(0.5, 0.4, 0.6); try testing.expect(actual.isScalene()); } diff --git a/generators/exercises/binary_search.py b/generators/exercises/binary_search.py index 685e3bd5..e035309e 100644 --- a/generators/exercises/binary_search.py +++ b/generators/exercises/binary_search.py @@ -1,7 +1,15 @@ from lib import zarray IMPORT_SELF = True -HEADER = "const binarySearch = binary_search.binarySearch;\n" + +# Inputs are passed through `testBinarySearch`'s runtime parameters, so a solution that +# declares `binarySearch`'s value parameters `comptime` is rejected at compile time. +# See https://forum.exercism.org/t/zig-track-needs-tests-against-comptime-abuse-and-other-kinds-of-cheating/59830/ +HEADER = """ +fn testBinarySearch(comptime T: type, target: T, items: []const T, expected: ?usize) !void { + try testing.expectEqual(expected, binary_search.binarySearch(T, target, items)); +} +""" # The committed target cycles a different integer type through each case, in order. _TYPE_BY_DESC = { @@ -48,9 +56,4 @@ def gen_case(case): else: idx = str(expected) - return ( - f" const expected: ?usize = {idx};\n" - f" const array = {array};\n" - f" const actual = binarySearch({ty}, {value}, &array);\n" - " try testing.expectEqual(expected, actual);\n" - ) + return f" try testBinarySearch({ty}, {value}, &{array}, {idx});\n" diff --git a/generators/exercises/triangle.py b/generators/exercises/triangle.py index 7f0c3577..983a4814 100644 --- a/generators/exercises/triangle.py +++ b/generators/exercises/triangle.py @@ -4,6 +4,14 @@ "scalene": "isScalene", } +# Sides are passed through `makeTriangle`'s runtime parameters, so a solution that declares +# `Triangle.init`'s parameters `comptime` is rejected at compile time. +# See https://forum.exercism.org/t/zig-track-needs-tests-against-comptime-abuse-and-other-kinds-of-cheating/59830/ +HEADER = """fn makeTriangle(a: f64, b: f64, c: f64) triangle.TriangleError!triangle.Triangle { + return triangle.Triangle.init(a, b, c); +} +""" + def describe(case, parent): desc = case.get("description") @@ -30,11 +38,10 @@ def gen_case(case): args = f"{_fmt(a)}, {_fmt(b)}, {_fmt(c)}" if not _is_valid(a, b, c): return ( - f" const actual = triangle.Triangle.init({args});\n" - f" try testing.expectError(triangle.TriangleError.Invalid, actual);\n" + f" try testing.expectError(triangle.TriangleError.Invalid, makeTriangle({args}));\n" ) bang = "" if case["expected"] else "!" return ( - f" const actual = try triangle.Triangle.init({args});\n" + f" const actual = try makeTriangle({args});\n" f" try testing.expect({bang}actual.{method}());\n" )