Rupt / c-explicitly-controlled-testing

You can test C code without hiding control flow in macros.
MIT License
2 stars 0 forks source link
c testing

TECT: Explicitly Controlled Testing

You can test C code without hiding control flow in macros.

This repository is a goofy illustration of that, written in GNU C23.

Example

Macros tect_once and tect_report hide checking and reporting logic.

// example_one.c
#include "tect.h"

int check_for_tea_too() {
  const int answer = 6 * 9;

  if (!tect_once(answer == 42)) // Triggers at most _once_ by hiding state.
    return tect_report("answer == %d", answer); // Appends to the output log.

  if (!tect_once(3 == 4)) // We next check this.
    return tect_report();

  return 0; // => We are done. Other return values are yours to interpret.
}

int main() {
  while (check_for_tea_too())
    ;
}

This outputs:

example_one.c:6: check_for_tea_too: !tect_once(answer == 42); answer == 54
example_one.c:9: check_for_tea_too: !tect_once(3 == 4); 

Note that we run check_example twice. Returning and retrying gives you freedom to interpret and handle the returned value however the language allows.

See example_two.c for a more involved example.

This repeated-run style is inspired by (and not an implementation of) Andrei Alexandrescu's "Unit Test Should Nest", which is inspired by Catch2.

Non-Example

Common C testing tools use such powerful macros that their code does not resemble C.

They instead define new, domain-specific languages, because their macros hide both declarations and control flow.

For example, a pastiche of the C testing tools from Awesome C could look like:

DECLARE_TEST(context, "some associated text") {
    CHECK(6 * 9 == 42);
    CHECK(3 == 4);
}

By hiding core language features, such designs make me I feel uncomfortably incapable of composing these tools with core language features.

Our tect_* macros also hide state and output.

And they are arguably disgusting abuses of language extensions.

But they do give users control of control flow.

Usage

Install tools in Ubuntu 23.04:

sudo apt install clang-format gcc-13 make

Build and run our examples:

make

Other make commands:

make check # Run some basic tests on the examples' outputs.

make fmt # Auto-format our C source files.

make README.md # Generate `README.md` from the template file.

For development, please run make -s README.md fmt check before each commit.

Documentation

We generate this section from comments in tect.h.

tect_once (function-like macro)

(expression) -> int

Return assertion's integral value, and print when it is first false.

You should follow tect_once by calling tect_report as follows:

int check_example() {
  const int result = square(4);

  if (!tect_once(result == 16))
    return tect_report("result == %d", result);

  if (!tect_once(square(0) == 0))
    return tect_report();

  return 0;
}

To fully run this test, use a loop such as:

 while (check_example())
   ;

We track the first activation in a mutating static variable.

On a false assertion, our printed message adapts assert's style.

tect_report (function-like macro)

(const char *format, ...args) -> int, or () -> int

Call printf with any arguments, print '\n', and return (int) 1.

See tect_once for usage.