/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include /* required for sandboxing */ #include #include #ifdef _WIN32 # include # include # include # include # define _MAIN_CC __cdecl # define stat(path, st) _stat(path, st) # define mkdir(path, mode) _mkdir(path) # define chdir(path) _chdir(path) # define access(path, mode) _access(path, mode) # define strdup(str) _strdup(str) # ifndef __MINGW32__ # pragma comment(lib, "shell32") # define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) # define W_OK 02 # define S_ISDIR(x) ((x & _S_IFDIR) != 0) # define snprint_eq(buf,sz,fmt,a,b) _snprintf_s(buf,sz,_TRUNCATE,fmt,a,b) # else # define snprint_eq snprintf # endif typedef struct _stat STAT_T; #else # ifdef UNITTEST_DEBUG # include # endif # include /* waitpid(2) */ # include # define _MAIN_CC # define snprint_eq snprintf typedef struct stat STAT_T; #endif #include "clar.h" #define CL_WITHIN(n, min, max) ((n) >= (min) && (n) <= (max)) bool clar_expecting_passert = false; bool clar_passert_occurred = false; jmp_buf clar_passert_jmp_buf; static void fs_rm(const char *_source); static void fs_copy(const char *_source, const char *dest); static const char * fixture_path(const char *base, const char *fixture_name); struct clar_error { const char *test; int test_number; const char *suite; const char *file; int line_number; const char *error_msg; char *description; struct clar_error *next; }; static struct { const char *active_test; const char *active_suite; int suite_errors; int total_errors; int test_count; int report_errors_only; int exit_on_error; struct clar_error *errors; struct clar_error *last_error; void (*local_cleanup)(void *); void *local_cleanup_payload; jmp_buf trampoline; int trampoline_enabled; } _clar; struct clar_func { const char *name; void (*ptr)(void); }; struct clar_suite { int index; const char *name; struct clar_func initialize; struct clar_func cleanup; const char **categories; const struct clar_func *tests; size_t test_count; }; /* From clar_print_*.c */ static void clar_print_init(int test_count, int suite_count, const char *suite_names); static void clar_print_shutdown(int test_count, int suite_count, int error_count); static void clar_print_error(int num, const struct clar_error *error); static void clar_print_ontest(const char *test_name, int test_number, int failed); static void clar_print_onsuite(const char *suite_name, int suite_index); static void clar_print_onabort(const char *msg, ...); /* From clar_sandbox.c */ static void clar_unsandbox(void); static int clar_sandbox(void); /* From clar_mock.c */ static void clar_mock_reset(void); static void clar_mock_cleanup(void); /* From clar_categorize.c */ static int clar_category_is_suite_enabled(const struct clar_suite *); static void clar_category_enable(const char *category); static void clar_category_enable_all(size_t, const struct clar_suite *); static void clar_category_print_enabled(const char *prefix); /* Event callback overrides */ ${clar_event_overrides} /* Autogenerated test data by clar */ ${clar_callbacks} ${clar_categories} static const struct clar_suite _clar_suites[] = { ${clar_suites} }; static size_t _clar_suite_count = ${clar_suite_count}; static size_t _clar_callback_count = ${clar_callback_count}; /* Core test functions */ static void clar_report_errors(void) { int i = 1; struct clar_error *error, *next; error = _clar.errors; while (error != NULL) { next = error->next; clar_print_error(i++, error); free(error->description); free(error); error = next; } _clar.errors = _clar.last_error = NULL; } static void clar_run_test( const struct clar_func *test, const struct clar_func *initialize, const struct clar_func *cleanup) { int error_st = _clar.suite_errors; clar_on_test(); _clar.trampoline_enabled = 1; clar_passert_occurred = false; clar_expecting_passert = false; if (setjmp(_clar.trampoline) == 0) { if (initialize->ptr != NULL) initialize->ptr(); printf("\n#------------------------------------------------------------------------"); printf("\n# Running test: %s...\n", test->name); test->ptr(); } _clar.trampoline_enabled = 0; if (_clar.local_cleanup != NULL) _clar.local_cleanup(_clar.local_cleanup_payload); if (cleanup->ptr != NULL) cleanup->ptr(); _clar.test_count++; /* remove any local-set cleanup methods */ _clar.local_cleanup = NULL; _clar.local_cleanup_payload = NULL; if (_clar.report_errors_only) clar_report_errors(); else clar_print_ontest( test->name, _clar.test_count, (_clar.suite_errors > error_st) ); } static void clar_run_suite(const struct clar_suite *suite) { const struct clar_func *test = suite->tests; size_t i; if (!clar_category_is_suite_enabled(suite)) return; if (_clar.exit_on_error && _clar.total_errors) return; if (!_clar.report_errors_only) clar_print_onsuite(suite->name, suite->index); clar_on_suite(); _clar.active_suite = suite->name; _clar.suite_errors = 0; for (i = 0; i < suite->test_count; ++i) { _clar.active_test = test[i].name; clar_run_test(&test[i], &suite->initialize, &suite->cleanup); if (_clar.exit_on_error && _clar.total_errors) return; } } #if 0 /* temporarily disabled */ static void clar_run_single(const struct clar_func *test, const struct clar_suite *suite) { _clar.suite_errors = 0; _clar.active_suite = suite->name; _clar.active_test = test->name; clar_run_test(test, &suite->initialize, &suite->cleanup); } #endif static void clar_usage(const char *arg) { printf("Usage: %s [options]\n\n", arg); printf("Options:\n"); printf(" -sXX\t\tRun only the suite number or name XX\n"); printf(" -i\tInclude category tests\n"); printf(" -q \t\tOnly report tests that had an error\n"); printf(" -Q \t\tQuit as soon as a test fails\n"); printf(" -l \t\tPrint suite, category, and test names\n"); printf(" -tXX\t\tRun a specifc test by name\n"); exit(-1); } static void clar_parse_args(int argc, char **argv) { int i; for (i = 1; i < argc; ++i) { char *argument = argv[i]; if (argument[0] != '-') clar_usage(argv[0]); switch (argument[1]) { case 's': { /* given suite number, name, or prefix */ int num = 0, offset = (argument[2] == '=') ? 3 : 2; int len = 0, is_num = 1, has_colon = 0, j; for (argument += offset; *argument; ++argument) { len++; if (*argument >= '0' && *argument <= '9') num = (num * 10) + (*argument - '0'); else { is_num = 0; if (*argument == ':') has_colon = 1; } } argument = argv[i] + offset; if (!len) clar_usage(argv[0]); else if (is_num) { if ((size_t)num >= _clar_suite_count) { clar_print_onabort("Suite number %d does not exist.\n", num); exit(-1); } clar_run_suite(&_clar_suites[num]); } else if (!has_colon || argument[-1] == ':') { for (j = 0; j < (int)_clar_suite_count; ++j) if (strncmp(argument, _clar_suites[j].name, len) == 0) clar_run_suite(&_clar_suites[j]); } else { for (j = 0; j < (int)_clar_suite_count; ++j) if (strcmp(argument, _clar_suites[j].name) == 0) { clar_run_suite(&_clar_suites[j]); break; } } if (_clar.active_suite == NULL) { clar_print_onabort("No suite matching '%s' found.\n", argument); exit(-1); } break; } // Run a particular test only. This code was added on top of base clar. case 't': { int offset = (argument[2] == '=') ? 3 : 2; char *test_name = argument + offset; const struct clar_suite *suite = &_clar_suites[0]; const struct clar_func *test_funcs = suite->tests; const struct clar_func *test = NULL; for (int i = 0; i < suite->test_count; i++) { if (!strcmp(test_funcs[i].name, test_name)) { test = &test_funcs[i]; break; } } if (test == NULL) { clar_print_onabort("No test named '%s'.\n", argument); exit(-1); } _clar.active_suite = suite->name; _clar.suite_errors = 0; _clar.active_test = test->name; clar_run_test(test, &suite->initialize, &suite->cleanup); break; } case 'q': _clar.report_errors_only = 1; break; case 'Q': _clar.exit_on_error = 1; break; case 'i': { int offset = (argument[2] == '=') ? 3 : 2; if (strcasecmp("all", argument + offset) == 0) clar_category_enable_all(_clar_suite_count, _clar_suites); else clar_category_enable(argument + offset); break; } case 'l': { size_t j; printf("Test suites (use -s to run just one):\n"); for (j = 0; j < _clar_suite_count; ++j) printf(" %3d: %s\n", (int)j, _clar_suites[j].name); printf("\nCategories (use -i to include):\n"); clar_category_enable_all(_clar_suite_count, _clar_suites); clar_category_print_enabled(" - "); printf("\nTest names (use -t to run just one):\n"); for (j = 0; j < _clar_suite_count; ++j) { const struct clar_suite *suite = &_clar_suites[j]; for (int i = 0; i < suite->test_count; i++) { printf(" %s\n", suite->tests[i].name); } } printf("\n"); exit(0); } default: clar_usage(argv[0]); } } } static int clar_test(int argc, char **argv) { clar_print_init( (int)_clar_callback_count, (int)_clar_suite_count, "" ); if (clar_sandbox() < 0) { clar_print_onabort("Failed to sandbox the test runner.\n"); exit(-1); } clar_mock_reset(); clar_on_init(); if (argc > 1) clar_parse_args(argc, argv); if (_clar.active_suite == NULL) { size_t i; for (i = 0; i < _clar_suite_count; ++i) clar_run_suite(&_clar_suites[i]); } clar_print_shutdown( _clar.test_count, (int)_clar_suite_count, _clar.total_errors ); clar_on_shutdown(); clar_mock_cleanup(); clar_unsandbox(); return _clar.total_errors; } char *strdup(const char *s1); void clar__assert( int condition, const char *file, int line, const char *error_msg, const char *description, int should_abort) { struct clar_error *error; if (condition) return; #ifndef _WIN32 #ifdef UNITTEST_DEBUG // Break in debugger: raise(SIGINT); #endif #endif error = calloc(1, sizeof(struct clar_error)); if (_clar.errors == NULL) _clar.errors = error; if (_clar.last_error != NULL) _clar.last_error->next = error; _clar.last_error = error; error->test = _clar.active_test; error->test_number = _clar.test_count; error->suite = _clar.active_suite; error->file = file; error->line_number = line; error->error_msg = error_msg; if (description != NULL) error->description = strdup(description); _clar.suite_errors++; _clar.total_errors++; if (should_abort) { if (!_clar.trampoline_enabled) { clar_print_onabort( "Fatal error: a cleanup method raised an exception."); clar_report_errors(); exit(-1); } longjmp(_clar.trampoline, -1); } } void clar__assert_equal_s( const char *s1, const char *s2, const char *file, int line, const char *err, int should_abort) { int match = (s1 == NULL || s2 == NULL) ? (s1 == s2) : (strcmp(s1, s2) == 0); if (!match) { char buf[4096]; snprint_eq(buf, 4096, "'%s' != '%s'", s1, s2); clar__assert(0, file, line, err, buf, should_abort); } } void clar__assert_equal_i( int i1, int i2, const char *file, int line, const char *err, int should_abort) { if (i1 != i2) { char buf[128]; snprint_eq(buf, 128, "%d != %d", i1, i2); clar__assert(0, file, line, err, buf, should_abort); } } void clar__assert_equal_d( double d1, double d2, const char *file, int line, const char *err, int should_abort) { if (d1 != d2) { char buf[128]; snprint_eq(buf, 128, "%f != %f", d1, d2); clar__assert(0, file, line, err, buf, should_abort); } } void clar__assert_within( int n, int min, int max, const char *file, int line, const char *err, int should_abort) { if (!CL_WITHIN(n, min, max)) { char buf[256]; snprint_eq(buf, 256, "%d not within [%d, %d]", n, min, max); clar__assert(0, file, line, err, buf, should_abort); } } void clar__assert_near( int i1, int i2, int abs_err, const char *file, int line, const char *err, int should_abort) { if (abs(i1 - i2) > abs_err) { char buf[256]; snprint_eq(buf, 256, "Difference between %d and %d exceeds %d", i1, i2, abs_err); clar__assert(0, file, line, err, buf, should_abort); } } void clar__assert_cmp_i( int i1, int i2, ClarCmpOp op, const char *file, int line, const char *err, int should_abort) { char *fmt; bool rv = false; switch (op) { case ClarCmpOp_EQ: fmt = "NOT =="; rv = (i1 == i2); break; case ClarCmpOp_LE: rv = (i1 <= i2); fmt = "NOT <="; break; case ClarCmpOp_LT: rv = (i1 < i2); fmt = "NOT <"; break; case ClarCmpOp_GE: rv = (i1 >= i2); fmt = "NOT >="; break; case ClarCmpOp_GT: rv = (i1 > i2); fmt = "NOT >"; break; case ClarCmpOp_NE: rv = (i1 != i2); fmt = "NOT !="; break; } if (!rv) { char buf[256] = {}; snprintf(buf, 256, "%d %s %d", i1, fmt, i2); clar__assert(0, file, line, err, buf, should_abort); } } // Shamelessly stolen from // http://stackoverflow.com/questions/7775991/how-to-get-hexdump-of-a-structure-data void hex_dump(char *desc, void *addr, int len) { int i; unsigned char buff[17]; unsigned char *pc = (unsigned char*)addr; // Output description if given. if (desc != NULL) { printf("%s:\n", desc); } // Process every byte in the data. for (i = 0; i < len; i++) { // Multiple of 16 means new line (with line offset). if ((i % 16) == 0) { // Just don't print ASCII for the zeroth line. if (i != 0) { printf(" %s\n", buff); } // Output the offset. printf(" %04x ", i); } // Now the hex code for the specific character. printf(" %02x", pc[i]); // And store a printable ASCII character for later. if ((pc[i] < 0x20) || (pc[i] > 0x7e)) { buff[i % 16] = '.'; } else { buff[i % 16] = pc[i]; } buff[(i % 16) + 1] = '\0'; } // Pad out last line if not exactly 16 characters. while ((i % 16) != 0) { printf(" "); i++; } // And print the final ASCII bit. printf(" %s\n", buff); } void clar__assert_equal_m( uint8_t *m1, uint8_t *m2, int n, const char *file, int line, const char *err, int should_abort) { if (m1 == m2 || n == 0) { return; } if (m1 == NULL || m2 == NULL) { char buf[4096]; snprint_eq(buf, 4096, "'%p' != '%p'", m1, m2); clar__assert(0, file, line, err, buf, should_abort); } for (int offset = 0; offset < n; offset++) { if (m1[offset] == m2[offset]) { continue; } int len = 4096; int off = 0; char buf[len]; off += snprint_eq(buf + off, len - off, "Mismatch at offset %d. Look above for diff.\n", offset); hex_dump("m1:", m1, n); hex_dump("m2:", m2, n); fflush(stdout); clar__assert(0, file, line, err, buf, should_abort); } } void cl_set_cleanup(void (*cleanup)(void *), void *opaque) { _clar.local_cleanup = cleanup; _clar.local_cleanup_payload = opaque; } ${clar_modules} int _MAIN_CC main(int argc, char *argv[]) { return clar_test(argc, argv); }