Zig track needs tests against comptime abuse and other kinds of cheating

It happens again and again as I see requests for mentoring. Student gets overexited about comptime and uses it where it does not make any sense. You can search “comptime” in Isogram exercise Community Solutions. You will find comptime function parameter there. This makes the function useless in real program where you take user input in run-time. We do not want to teach the wrong lesson. We need to add a test.

Recent example. Raindrop exercise. Here is the solution that pissed me off so much that I went here on the forum:

const std = @import("std");

pub fn convert(buffer: []u8, comptime n: u32) []const u8 {
    _ = buffer;
    return comptime blk: {
        const p3str = (if (n % 3 == 0) "Pling" else "") ++ 
            (if (n % 5 == 0) "Plang" else "") ++ 
            (if (n % 7 == 0) "Plong" else "");
        if (p3str.len > 0) break :blk p3str
        else {
            var buff: [15]u8 = undefined;

            const nstr = std.fmt.bufPrint(&buff, "{}", .{n})
                catch unreachable;

            break :blk nstr[0..nstr.len] ++ "";
        }
    };
}

It passed all the tests! This function abuses comptime and it can not process run-time known input. It also ignores the buffer. The implied API contract is that the returned slice references the buffer. All kinds of bad things could happen in real program if you return something else. But no test checkes it.

Here is an example of what tests we could add:

test "can handle run-time known user input" {
    var data: u32 = undefined;
    data = 42;
    var buffer: [15]u8 = undefined;
    try std.testing.expectEqualStrings("PlingPlong", convert(&buffer, data));
}

test "buffer is used for output" {
    var buffer: [15]u8 = undefined;
    const actual = convert(&buffer, 42);
    try std.testing.expectEqual(buffer[0..actual.len], actual);
}

We should add tests against comptime function parameters in most exercises. We may need to add tests against ignored buffer in other exercises that involve such buffer.

Is the following possible?

The students are well aware comptime is only for input known at compile time, and are asking for guidance in how to best use their newly discovered comptime superpowers. They might have already solved a dozen exercises without comptime, and not asked for mentoring on those solutions.

I had a long back and forth with a student for a few days and I had hard time convincing them that comptime and const are different concepts and that Zig does not compile at run-time unlike JavaScript.

No, they are not. Every student I had a chance to chat with had some kind of misunderstanding about these concepts.

Yes, they are. Recent example is Nucleotide Count. It did not involve comptime function parameter though. You can use inline for in this exercise to do some smart comptime meta-programming.

No. Isogram is literally the first exercise after Hello World. And you can see the abuse of comptime there.

Actually the opposite is true. They solve dozens of exercise with comptime abuse, and do not ask for mentoring because they are unaware of the issues with their solutions.

I can deal with people who ask mentoring. The ones who do not ask worry me. They convince themselves that they have learned a good performance optimization and they will be very disappointed when time comes to apply it to a real program. Will they feel that Exercism did a good service to them?

I think it is better to enforce the assumption that parameters are run-time known. Add some tests.

Without a learning track, it’s hard to get these kinds of concepts in front of eyes. Maybe it’s a good case for a zig analyzer.

2 Likes

I do not know nothing about analyzer. I do not think we have one on Zig track.

I assume adding one test against comptime to every exercise does not need much effort compared to creating a brand new analizer. The tests are straight forward. They are visible to the student. They are part of the language. Anyways, I would be happy with any automation that would give some feedback to students.

A good case for analyzer would be restricting allocators. Some exercises are designed to be solved without heap allocation. They do not pass the allocator as function parameter. Still, some students try to use an allocator. For example, they use heap allocated hashmap in Isogram and Pangram exercises.

I have now generated tests for all the existing exercises (some in PR 555, most of the rest in draft PR 556, two more exercise generators not yet uploaded).

One option is to add a test with different logic, another is to change the logic in existing tests.

For an example of the work involved in having custom logic for a single extra test case, see
pascals-triangle/.meta/supplements.json
generators/exercises/pascals_triangle.py

It is often easiest to instead change the logic in all the existing test cases.

For raindrops it would be an edit to
generators/exercises/raindrops.py

Either way, we want existing submissions retested, so now-failing comptime submissions are no longer listed in Community Solutions. This retesting has has a cost. For example, Zig Track Management shows Raindrops has 653 completions.

Perhaps we should only update a handful of exercises.

1 Like

Raindrops is the easiest one to fix. Just drop comptime from testConvert() signature.

Pascal Triangle is immune to comptime abuse as it is.

Isogram could be fixed similarly to Pascal Triangle:

fn testIsogram(input: []const u8, expected: bool) !void {
    try testing.expectEqual(isogram.isIsogram(input), expected);
}

test "empty string" {
    try testIsogram("", true);
}

This makes sense.

The list of exercises vulnerable to comptime abuse:

Summary

isogram
leap
nucleotide-count
pangram
raindrops
reverse-string
scrabble-score
sum-of-multiples
triangle
two-fer
armstrong-numbers
collatz-conjecture
darts
difference-of-squares
eliuds-eggs
grains
hamming
high-scores
resistor-color
resistor-color-duo
square-root
allergies
binary-search
bob
bottle-song
the whole other can of worms in dnd-character
isbn-verifier
kindergarten-garden
micro-blog
perfect-numbers
phone-number
prime-factors
protein-translation
queen-attack
secret-handshake
sieve
sublist
twelve-days
all-your-base
atbash-cipher
crypto-square
house
largest-series-product
luhn
matching-brackets
nth-prime
pig-latin
robot-simulator
roman-numerals
state-of-tic-tac-toe
yacht
affine-cipher
food-chain
killer-sudoku-helper
meetup
ocr-numbers
pythagorean-triplet
saddle-points
series
variable-length-quantity
change
piecing-it-together
split-second-stopwatch
two-bucket
book-store
dominoes
rectangles
wordy
connect
intergalactic-transmission
palindrome-products