How it works

Is it magic?

Believe it or not, CST doesn't use magic to work, instead, it uses a few tricks that are important to take note of if you really want to use CST to its maximum potential.

Test registration

Test registration is compiler-driven. CST takes advantage of GCC’s __attribute__((constructor)) extension to register tests automatically at program startup, before main() is executed. This is the main reason why CST requires GNU C99 as a minimum.

CST implements this at the TEST macro defined in cst.h. The macro relies on __COUNTER__ to generate unique function identifiers. These are used to build two functions:

  • __cst_ctor_N: Constructor that registers the test with cst_register_test.

  • __cst_fn_N: Function containing the actual test logic.

So this macro:

TEST("math", "addition") {
    ASSERT_INT_EQUALS(2 + 2, 4);
}

Expands to something like this:

Note: This is an example. It may not represent actual CST code
static void __cst_fn_0(void);

__attribute__((constructor))
static void __cst_ctor_0(void) {
    cst_register_test("math", "addition", -1, __cst_fn_0);
}

static void __cst_fn_0(void) {
    ASSERT_INT_EQUALS(2 + 2, 4);
}

Test execution

CST isolates every test by running it in its own process, created using the POSIX fork() system call. This ensures that each test is completely independent and cannot affect the execution of others.

Why does this matter?

  • A crash (SIGSEGV, SIGABRT, etc.) in one test does not stop the rest of the suite.

  • Memory leaks or invalid memory accesses are contained within the child process.

  • Timeouts are enforced safely: if a test exceeds its time limit, CST terminates it with SIGKILL.

When a test is executed, CST forks the process:

  • The child process executes the test function. If it finishes successfully, it terminates with _exit(EXIT_SUCCESS). Assertions also terminate the child process upon failure, reporting errors.

  • The parent process waits for the child and captures its exit code. If the child crashes or times out, CST reports the failure without affecting other tests.

As tests are isolated, they won't affect your program's global state. For that purpose, you can use lifecycle hooks, which always run on the main process.

Last updated

Was this helpful?