From c97ebdd17138d373fb7be3d348f8edd6669c612b Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Wed, 16 Sep 2026 07:02:37 +1000 Subject: [PATCH] four error tests no longer leak memory These tests expecting errors previously leaked memory if a student's solution 'succeeded' - affine-cipher - intergalactic-transmission - protein-translation - variable-length-quantity --- .../affine-cipher/test_affine_cipher.zig | 18 ++++++++++--- .../test_intergalactic_transmission.zig | 14 +++++++--- .../test_protein_translation.zig | 10 +++++-- .../test_variable_length_quantity.zig | 12 ++++++--- generators/exercises/affine_cipher.py | 27 ++++++++++++------- .../exercises/intergalactic_transmission.py | 18 ++++++++----- generators/exercises/protein_translation.py | 11 +++++--- .../exercises/variable_length_quantity.py | 19 ++++++++----- 8 files changed, 89 insertions(+), 40 deletions(-) diff --git a/exercises/practice/affine-cipher/test_affine_cipher.zig b/exercises/practice/affine-cipher/test_affine_cipher.zig index 58701559..faa09833 100644 --- a/exercises/practice/affine-cipher/test_affine_cipher.zig +++ b/exercises/practice/affine-cipher/test_affine_cipher.zig @@ -6,6 +6,18 @@ const encode = affine_cipher.encode; const decode = affine_cipher.decode; const AffineCipherError = affine_cipher.AffineCipherError; +fn testEncodeError(phrase: []const u8, a: u8, b: u8) !void { + const actual = encode(testing.allocator, phrase, a, b); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(AffineCipherError.NotCoprime, actual); +} + +fn testDecodeError(phrase: []const u8, a: u8, b: u8) !void { + const actual = decode(testing.allocator, phrase, a, b); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(AffineCipherError.NotCoprime, actual); +} + test "encode yes" { const expected: []const u8 = "xbt"; const actual = try encode(testing.allocator, "yes", 5, 7); @@ -63,8 +75,7 @@ test "encode all the letters" { } test "encode with a not coprime to m" { - const actual = encode(testing.allocator, "This is a test.", 6, 17); - try testing.expectError(AffineCipherError.NotCoprime, actual); + try testEncodeError("This is a test.", 6, 17); } test "decode exercism" { @@ -110,8 +121,7 @@ test "decode with too many spaces" { } test "decode with a not coprime to m" { - const actual = decode(testing.allocator, "Test", 13, 5); - try testing.expectError(AffineCipherError.NotCoprime, actual); + try testDecodeError("Test", 13, 5); } test "encode boundary characters" { diff --git a/exercises/practice/intergalactic-transmission/test_intergalactic_transmission.zig b/exercises/practice/intergalactic-transmission/test_intergalactic_transmission.zig index 6c63f6c5..a026315f 100644 --- a/exercises/practice/intergalactic-transmission/test_intergalactic_transmission.zig +++ b/exercises/practice/intergalactic-transmission/test_intergalactic_transmission.zig @@ -6,6 +6,12 @@ const transmitSequence = intergalactic_transmission.transmitSequence; const decodeMessage = intergalactic_transmission.decodeMessage; const TransmissionError = intergalactic_transmission.TransmissionError; +fn testDecodeMessageError(message: []const u8) !void { + const actual = decodeMessage(testing.allocator, message); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(TransmissionError.WrongParity, actual); +} + test "calculate transmit sequences-empty message" { const message = [_]u8{}; const expected = [_]u8{}; @@ -136,12 +142,12 @@ test "decode received messages-0x2881 is decoded to 0x29" { test "decode received messages-first byte has wrong parity" { const message = [_]u8{ 0x07, 0x00 }; - try testing.expectError(TransmissionError.WrongParity, decodeMessage(testing.allocator, &message)); + try testDecodeMessageError(&message); } test "decode received messages-second byte has wrong parity" { const message = [_]u8{ 0x03, 0x68 }; - try testing.expectError(TransmissionError.WrongParity, decodeMessage(testing.allocator, &message)); + try testDecodeMessageError(&message); } test "decode received messages-0xcf4b00 is decoded to 0xce94" { @@ -178,7 +184,7 @@ test "decode received messages-seven byte message" { test "decode received messages-last byte has wrong parity" { const message = [_]u8{ 0x47, 0xb8, 0x99, 0xac, 0x17, 0xa0, 0xc5, 0x43 }; - try testing.expectError(TransmissionError.WrongParity, decodeMessage(testing.allocator, &message)); + try testDecodeMessageError(&message); } test "decode received messages-eight byte message" { @@ -199,5 +205,5 @@ test "decode received messages-twenty byte message" { test "decode received messages-wrong parity on 16th byte" { const message = [_]u8{ 0x44, 0xbd, 0x18, 0xaf, 0x27, 0x1b, 0xa5, 0xe7, 0x6c, 0x90, 0x1b, 0x2e, 0x33, 0x03, 0x84, 0xef, 0x65, 0xb8, 0xdb, 0xed, 0xd7, 0x28, 0x84 }; - try testing.expectError(TransmissionError.WrongParity, decodeMessage(testing.allocator, &message)); + try testDecodeMessageError(&message); } diff --git a/exercises/practice/protein-translation/test_protein_translation.zig b/exercises/practice/protein-translation/test_protein_translation.zig index a7eecbe9..9b08fa58 100644 --- a/exercises/practice/protein-translation/test_protein_translation.zig +++ b/exercises/practice/protein-translation/test_protein_translation.zig @@ -6,6 +6,12 @@ const proteins = protein_translation.proteins; const Protein = protein_translation.Protein; const TranslationError = protein_translation.TranslationError; +fn testProteinsError(strand: []const u8) !void { + const actual = proteins(testing.allocator, strand); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(TranslationError.InvalidCodon, actual); +} + test "Empty RNA sequence results in no proteins" { const expected = [_]Protein{}; const actual = try proteins(testing.allocator, ""); @@ -196,11 +202,11 @@ test "Sequence of two non-STOP codons does not translate to a STOP codon" { } test "Unknown amino acids, not part of a codon, can't translate" { - try testing.expectError(TranslationError.InvalidCodon, proteins(testing.allocator, "XYZ")); + try testProteinsError("XYZ"); } test "Incomplete RNA sequence can't translate" { - try testing.expectError(TranslationError.InvalidCodon, proteins(testing.allocator, "AUGU")); + try testProteinsError("AUGU"); } test "Incomplete RNA sequence can translate if valid until a STOP codon" { diff --git a/exercises/practice/variable-length-quantity/test_variable_length_quantity.zig b/exercises/practice/variable-length-quantity/test_variable_length_quantity.zig index 4bb675b8..89c3ee7f 100644 --- a/exercises/practice/variable-length-quantity/test_variable_length_quantity.zig +++ b/exercises/practice/variable-length-quantity/test_variable_length_quantity.zig @@ -6,6 +6,12 @@ const encode = variable_length_quantity.encode; const decode = variable_length_quantity.decode; const DecodeError = variable_length_quantity.DecodeError; +fn testDecodeError(integers: []const u8) !void { + const actual = decode(testing.allocator, integers); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(DecodeError.IncompleteSequence, actual); +} + test "encode - zero" { const expected = [_]u8{0}; const integers = [_]u32{0}; @@ -232,14 +238,12 @@ test "decode - maximum 32-bit integer" { test "decode - incomplete sequence causes error" { const integers = [_]u8{255}; - const actual = decode(testing.allocator, &integers); - try testing.expectError(DecodeError.IncompleteSequence, actual); + try testDecodeError(&integers); } test "decode - incomplete sequence causes error, even if value is zero" { const integers = [_]u8{128}; - const actual = decode(testing.allocator, &integers); - try testing.expectError(DecodeError.IncompleteSequence, actual); + try testDecodeError(&integers); } test "decode - multiple values" { diff --git a/generators/exercises/affine_cipher.py b/generators/exercises/affine_cipher.py index 61202c6c..eb34f42a 100644 --- a/generators/exercises/affine_cipher.py +++ b/generators/exercises/affine_cipher.py @@ -1,10 +1,21 @@ from lib import zstr, is_error -HEADER = ( - "const encode = affine_cipher.encode;\n" - "const decode = affine_cipher.decode;\n" - "const AffineCipherError = affine_cipher.AffineCipherError;" -) +HEADER = """const encode = affine_cipher.encode; +const decode = affine_cipher.decode; +const AffineCipherError = affine_cipher.AffineCipherError; + +fn testEncodeError(phrase: []const u8, a: u8, b: u8) !void { + const actual = encode(testing.allocator, phrase, a, b); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(AffineCipherError.NotCoprime, actual); +} + +fn testDecodeError(phrase: []const u8, a: u8, b: u8) !void { + const actual = decode(testing.allocator, phrase, a, b); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(AffineCipherError.NotCoprime, actual); +} +""" def describe(case, parent): @@ -21,10 +32,8 @@ def gen_case(case): e = case["expected"] if is_error(e): - return ( - f" const actual = {prop}(testing.allocator, {phrase}, {a}, {b});\n" - f" try testing.expectError(AffineCipherError.NotCoprime, actual);\n" - ) + helper = {"encode": "testEncodeError", "decode": "testDecodeError"}[prop] + return f" try {helper}({phrase}, {a}, {b});\n" return ( f" const expected: []const u8 = {zstr(e)};\n" diff --git a/generators/exercises/intergalactic_transmission.py b/generators/exercises/intergalactic_transmission.py index a389aec5..42e943b6 100644 --- a/generators/exercises/intergalactic_transmission.py +++ b/generators/exercises/intergalactic_transmission.py @@ -1,10 +1,15 @@ from lib import is_error -HEADER = ( - "const transmitSequence = intergalactic_transmission.transmitSequence;\n" - "const decodeMessage = intergalactic_transmission.decodeMessage;\n" - "const TransmissionError = intergalactic_transmission.TransmissionError;\n" -) +HEADER = """const transmitSequence = intergalactic_transmission.transmitSequence; +const decodeMessage = intergalactic_transmission.decodeMessage; +const TransmissionError = intergalactic_transmission.TransmissionError; + +fn testDecodeMessageError(message: []const u8) !void { + const actual = decodeMessage(testing.allocator, message); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(TransmissionError.WrongParity, actual); +} +""" def _bytes_lit(values): @@ -20,9 +25,10 @@ def gen_case(case): msg_lit = _bytes_lit(message) if is_error(expected): + assert prop == "decodeMessage", f"unexpected error case for {prop}" return ( f" const message = [_]u8{msg_lit};\n" - f" try testing.expectError(TransmissionError.WrongParity, {prop}(testing.allocator, &message));\n" + " try testDecodeMessageError(&message);\n" ) exp_lit = _bytes_lit(expected) diff --git a/generators/exercises/protein_translation.py b/generators/exercises/protein_translation.py index 1aebffff..e795190b 100644 --- a/generators/exercises/protein_translation.py +++ b/generators/exercises/protein_translation.py @@ -3,6 +3,12 @@ HEADER = """const proteins = protein_translation.proteins; const Protein = protein_translation.Protein; const TranslationError = protein_translation.TranslationError; + +fn testProteinsError(strand: []const u8) !void { + const actual = proteins(testing.allocator, strand); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(TranslationError.InvalidCodon, actual); +} """ @@ -10,10 +16,7 @@ def gen_case(case): strand = zstr(case["input"]["strand"]) expected = case["expected"] if is_error(expected): - return ( - " try testing.expectError(" - f"TranslationError.InvalidCodon, proteins(testing.allocator, {strand}));\n" - ) + return f" try testProteinsError({strand});\n" tags = ", ".join("." + p.lower() for p in expected) arr = f"[_]Protein{{{tags}}}" if not tags else f"[_]Protein{{ {tags} }}" return ( diff --git a/generators/exercises/variable_length_quantity.py b/generators/exercises/variable_length_quantity.py index f1d57735..99fef9a8 100644 --- a/generators/exercises/variable_length_quantity.py +++ b/generators/exercises/variable_length_quantity.py @@ -1,10 +1,15 @@ from lib import zint, is_error -HEADER = ( - "const encode = variable_length_quantity.encode;\n" - "const decode = variable_length_quantity.decode;\n" - "const DecodeError = variable_length_quantity.DecodeError;\n" -) +HEADER = """const encode = variable_length_quantity.encode; +const decode = variable_length_quantity.decode; +const DecodeError = variable_length_quantity.DecodeError; + +fn testDecodeError(integers: []const u8) !void { + const actual = decode(testing.allocator, integers); + defer if (actual) |slice| testing.allocator.free(slice) else |_| {}; + try testing.expectError(DecodeError.IncompleteSequence, actual); +} +""" def describe(case, parent): @@ -26,10 +31,10 @@ def gen_case(case): ) if is_error(expected): + assert prop == "decode", f"unexpected error case for {prop}" return ( f" const integers = [_]{in_ty}{integers_lit};\n" - f" const actual = {prop}(testing.allocator, &integers);\n" - f" try testing.expectError(DecodeError.IncompleteSequence, actual);\n" + " try testDecodeError(&integers);\n" ) out_ty = "u8" if prop == "encode" else "u32"