Files
Aethera/render_3D/third_party/datoviz/testing/testing.h
T
2026-08-20 10:23:47 +08:00

399 lines
14 KiB
C

/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/*************************************************************************************************/
/* Generic testing framework */
/*************************************************************************************************/
#ifndef TST_HEADER
#define TST_HEADER
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "datoviz/common/macros.h"
EXTERN_C_ON
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define TST_DEFAULT_CAPACITY 32
#define TST_LOG_CAPTURE_DEFAULT_CAPACITY 16
/*************************************************************************************************/
/* Test assertions */
/*************************************************************************************************/
#define AT(x) \
if (!(x)) \
{ \
tst_assert_fail(NULL, __FILE__, __LINE__, #x); \
return 1; \
}
#define AEn(n, x, y) \
{ \
for (uint32_t k = 0; k < (n); k++) \
AT((x)[k] == (y)[k]); \
}
#define AIN(x, m, M) AT((m) <= (x) && (x) <= (M))
#define AC(x, y, eps) AIN(((x) - (y)), -(eps), +(eps))
#define ACn(n, x, y, eps) \
for (uint32_t i = 0; i < (n); i++) \
AC((x)[i], (y)[i], (eps));
#define EPS 1e-6
/*************************************************************************************************/
/* Platform helpers */
/*************************************************************************************************/
static inline int tst_setenv(const char* name, const char* value)
{
#if defined(_WIN32)
return _putenv_s(name, value);
#else
return setenv(name, value, 1);
#endif
}
static inline int tst_unsetenv(const char* name)
{
#if defined(_WIN32)
return _putenv_s(name, "");
#else
return unsetenv(name);
#endif
}
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
#define TST_MODULE(suite, module_name) tst_suite_module((suite), (module_name))
#define TST_GROUP(group_name) tst_suite_group((suite), (group_name))
#define TST_CASE(test) \
do \
{ \
TstCaseDesc _tst_desc = tst_case_desc(#test, #test, (test)); \
_tst_desc.tags = tags; \
tst_suite_add_case((suite), _tst_desc); \
} while (0)
#define TST_CASE_EX(test, desc_expr) \
do \
{ \
TstCaseDesc _tst_desc = (desc_expr); \
if (_tst_desc.name == NULL) \
_tst_desc.name = #test; \
if (_tst_desc.function_name == NULL) \
_tst_desc.function_name = #test; \
_tst_desc.test = (test); \
tst_suite_add_case((suite), _tst_desc); \
} while (0)
#define AT_EXPECTED_ERROR(suite, x) \
do \
{ \
tst_expect_error_begin((suite)); \
AT((x)); \
(void)tst_expect_error_end((suite)); \
} while (0)
#define AT_EXPECTED_ERROR_STRICT(suite, x) \
do \
{ \
tst_expect_error_begin((suite)); \
AT((x)); \
AT(tst_expect_error_end((suite)) == 0); \
} while (0)
#define AT_EXPECTED_LOG_STRICT(suite, level, x) \
do \
{ \
tst_expect_log_begin((suite), (level)); \
AT((x)); \
AT(tst_expect_error_end((suite)) == 0); \
} while (0)
/*************************************************************************************************/
/* Enums */
/*************************************************************************************************/
typedef enum
{
TST_RES_NONE = 0,
TST_RES_CPU = 1u << 0,
TST_RES_GPU = 1u << 1,
TST_RES_VULKAN = 1u << 2,
TST_RES_GLFW = 1u << 3,
TST_RES_FILESYSTEM = 1u << 4,
TST_RES_ENV = 1u << 5,
TST_RES_VIDEO = 1u << 6,
TST_RES_LOG_CAPTURE = 1u << 7,
TST_RES_GLOBAL_STATE = 1u << 8,
} TstResourceFlags;
typedef enum
{
TST_ISOLATION_SERIAL = 0,
TST_ISOLATION_PROCESS = 1,
TST_ISOLATION_THREAD_SAFE = 2,
TST_ISOLATION_EXCLUSIVE = 3,
} TstIsolation;
typedef enum
{
TST_STATUS_NOT_RUN = 0,
TST_STATUS_PASS = 1,
TST_STATUS_FAIL = 2,
TST_STATUS_SKIP = 3,
} TstStatus;
typedef enum
{
TST_FIXTURE_SCOPE_NONE = 0,
TST_FIXTURE_SCOPE_CASE = 1,
TST_FIXTURE_SCOPE_WORKER = 2,
TST_FIXTURE_SCOPE_PROCESS = 3,
TST_FIXTURE_SCOPE_EXCLUSIVE = 4,
} TstFixtureScope;
/*************************************************************************************************/
/* Typedefs */
/*************************************************************************************************/
typedef struct TstCase TstCase;
typedef struct TstCaseDesc TstCaseDesc;
typedef struct TstContext TstContext;
typedef struct TstLogAdapter TstLogAdapter;
typedef struct TstLogRecord TstLogRecord;
typedef struct TstRunSummary TstRunSummary;
typedef struct TstSuite TstSuite;
typedef int (*TstFunction)(TstContext* suite, const TstCase* item);
typedef const char* (*TstSkipFunction)(TstContext* suite, const TstCase* item);
typedef void (*TstLogInstall)(TstContext* ctx, void* user_data);
typedef void (*TstLogUninstall)(void* user_data);
typedef void* (*TstFixtureCreate)(TstSuite* suite, uint32_t worker_index);
typedef void (*TstFixtureDestroy)(void* fixture);
/*************************************************************************************************/
/* Structs */
/*************************************************************************************************/
struct TstLogRecord
{
int level;
int line;
char file[64];
char message[512];
};
struct TstCaseDesc
{
const char* name;
const char* function_name;
const char* tags;
uint64_t resources;
TstIsolation isolation;
uint64_t timeout_ms;
TstFunction test;
TstFunction setup;
TstFunction teardown;
TstSkipFunction skip;
const char* fixture;
TstFixtureScope fixture_scope;
void* user_data;
};
struct TstCase
{
const char* module;
const char* group;
const char* name;
const char* function_name;
const char* tags;
uint64_t resources;
TstIsolation isolation;
uint64_t timeout_ms;
TstFunction test;
TstFunction setup;
TstFunction teardown;
TstSkipFunction skip;
const char* fixture;
TstFixtureScope fixture_scope;
void* user_data;
TstStatus status;
int result;
uint64_t repeat_index;
uint64_t order_index;
uint32_t shard_index;
uint64_t start_ns;
uint64_t end_ns;
uint64_t elapsed_ns;
uint64_t fixture_setup_ns;
const char* skip_reason;
};
struct TstLogAdapter
{
TstLogInstall install;
TstLogUninstall uninstall;
void* user_data;
int error_level;
};
struct TstContext
{
TstSuite* suite;
const TstCase* test;
void* user_data;
bool capture_logs;
bool expect_error_active;
bool expect_error_seen;
int expect_log_level;
bool unexpected_error_seen;
bool suppress_expected_error_output;
bool strict_unexpected_errors;
uint32_t captured_log_count;
uint32_t captured_log_capacity;
TstLogRecord* captured_logs;
const char* skip_reason;
const char* failure_message;
uint32_t worker_index;
uint64_t fixture_setup_ns;
void* fixture_state;
};
struct TstRunSummary
{
uint32_t selected_count;
uint32_t passed_count;
uint32_t failed_count;
uint32_t skipped_count;
uint64_t summed_case_ns;
uint64_t runner_elapsed_ns;
};
struct TstSuite
{
uint32_t n_cases;
uint32_t capacity;
TstCase* cases;
void* context;
const char* current_module;
const char* current_group;
bool strict_unexpected_errors;
TstLogAdapter log_adapter;
TstRunSummary last_summary;
void* fixture_registry;
};
/*************************************************************************************************/
/* Main testing functions */
/*************************************************************************************************/
TstSuite tst_suite(void);
TstCaseDesc tst_case_desc(const char* name, const char* function_name, TstFunction test);
void tst_suite_module(TstSuite* suite, const char* module);
void tst_suite_group(TstSuite* suite, const char* group);
void tst_suite_add_case(TstSuite* suite, TstCaseDesc desc);
void tst_suite_set_log_adapter(TstSuite* suite, const TstLogAdapter* adapter);
void tst_suite_register_fixture(
TstSuite* suite, const char* name, TstFixtureScope scope, TstFixtureCreate create,
TstFixtureDestroy destroy);
int tst_suite_run(TstSuite* suite, int argc, char** argv);
void tst_suite_destroy(TstSuite* suite);
void tst_log_capture_begin(TstContext* ctx);
void tst_log_capture_end(TstContext* ctx);
uint32_t tst_log_capture_count(const TstContext* ctx);
const TstLogRecord* tst_log_capture_get(const TstContext* ctx, uint32_t index);
void tst_expect_error_begin(TstContext* ctx);
void tst_expect_log_begin(TstContext* ctx, int level);
int tst_expect_error_end(TstContext* ctx);
void tst_skip(TstContext* ctx, const char* reason);
void tst_set_strict_unexpected_errors(TstSuite* suite, bool enabled);
void* tst_context_fixture(TstContext* ctx, const char* name);
int tst_context_log(TstContext* ctx, int level, const char* file, int line, const char* message);
void tst_assert_fail(TstContext* ctx, const char* file, int line, const char* expr);
EXTERN_C_OFF
#endif