Criterion is a C testing framework with a clean API and good output formatting. I chose it over Unity and Check because test registration is macro-based and does not require a central test list — each test file is self-contained.
Finding Criterion with pkg-config
Criterion does not ship a CMake config file, so find_package(Criterion) will not work. Use pkg-config instead:
find_package(PkgConfig REQUIRED)
pkg_check_modules(CRITERION REQUIRED criterion)
target_link_libraries(first-test
PRIVATE libfirst ${CRITERION_LIBRARIES})
target_include_directories(first-test
PRIVATE ${CRITERION_INCLUDE_DIRS})
This works as long as Criterion is installed system-wide. On Ubuntu: apt install libcriterion-dev. On macOS: brew install criterion.
The fmemopen pattern
For lexer tests, the most useful pattern is to open a string as a FILE* using fmemopen(), create a lexer from it, and assert on the token sequence. This got common enough that it's now a shared helper in test/test_helpers.c rather than repeated per test:
Lexer *create_lexer_for_string(const char *input) { if (input == NULL) { return lex_create(NULL); } size_t length = strlen(input); FILE *stream = fmemopen((void *)input, length, "r"); return lex_create(stream); }
Which makes each individual test a one-liner to set up:
Test(lexer, number_token) {
Token tok;
Lexer *l = create_lexer_for_string("42");
lex_next_token(l, &tok);
cr_assert_eq(tok.type, TT_NUMBER);
cr_assert_str_eq(tok.value, "42");
free(tok.value);
lex_destroy(l);
}
Note that lex_destroy() calls fclose() on the stream, so you do not close it separately. Also note lex_create() takes only the FILE* now, not a filename — worth double-checking against include/lexer.h if you're working from an older reference.
What did not work
Running ctest directly without building first. CTest does not automatically trigger a build — cmake --build build && ctest --test-dir build is the correct sequence. The other footgun: forgetting to free(tok.value) between assertions. Criterion runs each test in a subprocess so leaks do not accumulate across tests, but it is still worth keeping tidy.