2023-10-16 18:20:08 +07:00

292 lines
6.8 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2013-2019 Michael Drake <tlsa@netsurf-browser.org>
*/
#ifndef TTEST_H
#define TTEST_H
#include <string.h>
/**
* Test cleanup client callback.
*
* The caller passes this callback function to ttest_start, and it is called
* in whichever tlsa-test function finishes the test (ttest_pass, ttest_fail,
* or ttest_todo).
*
* \param[in] Client data to clean up.
*/
typedef void (*ttest_cleanup_fn)(void *data);
/**
* Internal tlsa-test report context.
*
* Clients should not touch this directly, it is only exposed in this
* header because tlsa-test is arranged to be header-only, for convenience
* of utilisation.
*/
typedef struct ttest_report_ctx {
/** Space/comma separated list of tests to run. */
const char *test_list;
size_t test_list_len;
bool quiet; /**< Whether to print only the report summary. */
unsigned tests; /**< Number of tests started. */
unsigned todo; /**< Number of tests marked as unimplemented. */
unsigned passed; /**< Number of tests passed. */
} ttest_report_ctx_t;
/**
* Internal tlsa-test test context.
*
* Clients should not touch this directly, it is only exposed in this
* header because tlsa-test is arranged to be header-only, for convenience
* of utilisation.
*/
typedef struct ttest_ctx {
ttest_report_ctx_t *report; /**< The tlsa-test report context. */
const char *name; /**< The unit test name. */
ttest_cleanup_fn cleanup; /**< Client's unit test cleanup function. */
void *cleanup_data; /**< Client's unit test cleanup context. */
} ttest_ctx_t;
/**
* Initialise a tlsa-test report context.
*
* \param[in] test_list Space/comma separated list of tests to run.
* \param[in] quiet Whether report should be a minimal summary.
* \return initialised tlsa-test report context.
*/
static inline ttest_report_ctx_t ttest_init(
const char *test_list,
bool quiet)
{
ttest_report_ctx_t rc = {
.test_list = test_list,
.test_list_len = 0,
.quiet = quiet,
};
if (test_list != NULL) {
rc.test_list_len = strlen(test_list);
}
return rc;
}
/**
* Determine whether test of given name should be run.
*
* \param[in] report The tlsa-test report context.
* \param[in] name The name of the test to consider.
* \return true if test should be run, false otherwise.
*/
static inline bool ttest__run_test(
ttest_report_ctx_t *report,
const char *name)
{
size_t len;
size_t pos = 0;
size_t name_len;
if (report->test_list == NULL || report->test_list_len == 0) {
return true;
}
name_len = strlen(name);
while (pos < report->test_list_len) {
/* Skip commas and spaces. */
pos += strspn(report->test_list + pos, ", ");
len = strcspn(report->test_list + pos, ", ");
if (len == name_len) {
if (memcmp(report->test_list + pos, name, len) == 0) {
return true;
}
}
pos += len;
}
return false;
}
/**
* Start a unit test.
*
* The when complete, the test must be competed by calling either ttest_pass,
* ttest_fail, or ttest_todo.
*
* \param[in] report The tlsa-test report context.
* \param[in] name Name for this unit test.
* \param[in] cleanup Cleanup function to call on test completion.
* \param[in] cleanup_data Pointer to client cleanup context.
* \param[out] test_ctx_out Returns the unit test context on success.
* The test context must be passed to the test
* completion function.
* \return true if the test should be started, false otherwise.
*/
static inline bool ttest_start(
ttest_report_ctx_t *report,
const char *name,
ttest_cleanup_fn cleanup,
void *cleanup_data,
ttest_ctx_t *test_ctx_out)
{
ttest_ctx_t tc = {
.name = name,
.report = report,
.cleanup = cleanup,
.cleanup_data = cleanup_data,
};
if (!ttest__run_test(report, name)) {
return false;
}
report->tests++;
*test_ctx_out = tc;
return true;
}
/**
* Pass a unit test.
*
* This function competes a unit test. It should be called when a test passes.
* This function always returns true.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \return true.
*/
static inline bool ttest_pass(
const ttest_ctx_t *tc)
{
assert(tc != NULL);
assert(tc->report != NULL);
tc->report->passed++;
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
if (tc->report->quiet == false) {
fprintf(stderr, " PASS: %s\n", tc->name);
}
return true;
}
/**
* Fail a unit test.
*
* This function competes a unit test. It should be called when a test fails.
* This function always returns false. Prints the test result.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \param[in] reason Format string to explain reason for test failure.
* \param[in] ... Additional arguments for formatted rendering.
* \return false.
*/
static inline bool ttest_fail(
const ttest_ctx_t *tc,
const char *reason, ...)
{
va_list args;
assert(tc != NULL);
assert(tc->report != NULL);
fprintf(stderr, " FAIL: %s (", tc->name);
va_start(args, reason);
vfprintf(stderr, reason, args);
va_end(args);
fprintf(stderr, ")\n");
/* Cleanup after printing result, in case `reason` refers to cleaned up
* memory. */
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
return false;
}
/**
* Make a unit test as unimplemented.
*
* This function competes a unit test. Should be called on unimplemented tests.
* This function always returns true.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \return true.
*/
static inline bool ttest_todo(
const ttest_ctx_t *tc)
{
assert(tc != NULL);
assert(tc->report != NULL);
tc->report->todo++;
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
if (tc->report->quiet == false) {
fprintf(stderr, " TODO: %s\n", tc->name);
}
return true;
}
/**
* Print a visual divider in the test output.
*/
static inline void ttest_divider(void)
{
fprintf(stderr, "========================================"
"========================================\n");
}
/**
* Print a test heading.
*
* \param[in] tr The tlsa-test report context.
* \param[in] heading The heading to print.
*/
static inline void ttest_heading(
const ttest_report_ctx_t *tr,
const char *heading)
{
if (tr->test_list == NULL || tr->test_list_len == 0) {
if (!tr->quiet) {
ttest_divider();
fprintf(stderr, "TEST: %s\n", heading);
ttest_divider();
}
}
}
/**
* Print the test report summary.
*
* \param[in] tr The tlsa-test report context.
*/
static inline void ttest_report(
const ttest_report_ctx_t *tr)
{
ttest_divider();
if (tr->todo > 0) {
fprintf(stderr, "TODO: %u test%s unimplemented.\n",
tr->todo, (tr->todo > 1) ? "s" : "");
}
fprintf(stderr, "%s: %u of %u tests passed.\n",
(tr->passed == tr->tests - tr->todo) ? "PASS" : "FAIL",
tr->passed, tr->tests - tr->todo);
ttest_divider();
}
#endif