Introduce clar testing framework
Subsystems (2)
Re: [PATCH v5 2/9] t: import the clar unit testing framework
In reply to Johannes SchindelinHi Dscho
Quoted 7 lines · Johannes Schindelin
Hi Phillip,
thank you for looping me in; I meant to answer earlier.
I am very, very happy to see that there is a chance to adopt a test framework like
clar. Therefore, I am very interested in assisting in every which way I can.
That's great, I like the basic ideas in clar but I think the assertions could use some improvement.
Best Wishes
Phillip
Quoted 2,775 lines · Phillip Wood
Quoted 4 lines
Johannes - are these format specifiers supported by the version of MSVCRT that is used by git for windows? c.f.
https://lore.kernel.org/git/[email protected]/The format specifiers _should_ work well both with GCC and Visual C. What did not work well was using
struct statwith_stat()(note that the latter has a leading underscore and the former does not).There were other issues, top of which was the bit tricky part to adapt the
CMakeLists.txtdefinition, in particular the part whereclar.suiteis generated. But I managed to squeeze in that work somehow and Patrick was kind enough to already integrate the patches.Ciao,
JohannesQuoted 2,756 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c line 69
Quoted 10 lines
t/unit-tests/clar/clar.c +7
-0+#ifdef _WIN32 +# ifndef PRIuZ +# define PRIuZ "Iu" +# endif +# ifndef PRIxZ +# define PRIxZ "Ix" +# endif Thanks
Phillip
Quoted 2,739 lines
t/unit-tests/clar/clar.c +123
-0+# if defined(_MSC_VER) || defined(__MINGW32__) +typedef struct stat STAT_T ; +# else +typedef struct _stat STAT_T ; +# endif +#else +# include <sys/wait.h> /* waitpid(2) */ +# include <unistd.h> +# define _MAIN_CC +# define p_snprintf snprintf +# ifndef PRIuZ +# define PRIuZ "zu" +# endif +# ifndef PRIxZ +# define PRIxZ "zx" +# endif +typedef struct stat STAT_T ; +#endif + +#define MAX (x, y) (((x) > (y)) ? (x) : (y)) + +#include "clar.h" + +static void fs_rm (const char *_source); +static void fs_copy (const char *_source,const char *dest); + +#ifdef CLAR_FIXTURE_PATH +static const char * +fixture_path (const char *base,const char *fixture_name); +#endif + +struct clar_error { +const char *file; +const char *function; +size_t line_number; +const char *error_msg; +char *description; + +struct clar_error *next; +}; + +struct clar_explicit { +size_t suite_idx; +const char *filter; + +struct clar_explicit *next; +}; + +struct clar_report { +const char *test; +int test_number; +const char *suite; + +enum cl_test_status status; +time_t start; +double elapsed; + +struct clar_error *errors; +struct clar_error *last_error; + +struct clar_report *next; +}; + +struct clar_summary { +const char *filename; +FILE *fp; +}; + +static struct { +enum cl_test_status test_status; + +const char *active_test; +const char *active_suite; + +int total_skipped; +int total_errors; + +int tests_ran; +int suites_ran; + +enum cl_output_format output_format; + +int report_errors_only; +int exit_on_error; +int verbosity; + +int write_summary; +char *summary_filename; +struct clar_summary *summary; + +struct clar_explicit *explicit; +struct clar_explicit *last_explicit; + +struct clar_report *reports; +struct clar_report *last_report; + +void (*local_cleanup)(void *); +void *local_cleanup_payload; + +jmp_buf trampoline; +int trampoline_enabled; + +cl_trace_cb *pfn_trace_cb; +void *trace_payload; + +} _clar; + +struct clar_func { +const char *name; +void (*ptr)(void ); +}; + +struct clar_suite { +const char *name; +struct clar_func initialize; +struct clar_func cleanup; +const struct clar_func *tests; +size_t test_count; +int enabled; +}; + +/* From clar_print_*.c */ +static void clar_print_init(int test_count, int suite_count, const char
*suite_names);
+static voidclar_print_shutdown(int test_count, int suite_count, int error_count);
+static voidclar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static voidclar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);t/unit-tests/clar/clar.c +16
-0+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 summary.h */ +static struct clar_summary *clar_summary_init (const char *filename); +static int clar_summary_shutdown (struct clar_summary *fp); + +/* Load the declarations for the test suite */ +#include "clar.suite" + + +#define CL_TRACE (ev)\
t/unit-tests/clar/clar.c +1
-0+ do {\
t/unit-tests/clar/clar.c +1
-0+if (_clar.pfn_trace_cb)\
t/unit-tests/clar/clar.c +1
-0+ _clar.pfn_trace_cb(ev,\
t/unit-tests/clar/clar.c +1
-0+ _clar.active_suite,\
t/unit-tests/clar/clar.c +1
-0+ _clar.active_test,\
+
_clar.trace_payload); \t/unit-tests/clar/clar.c +119
-0+ }while (0 ) + +void cl_trace_register(cl_trace_cb *cb, void *payload) +{ + _clar.pfn_trace_cb = cb; + _clar.trace_payload = payload; +} + + +/* Core test functions */ +static void +clar_report_errors (struct clar_report *report) +{ +struct clar_error *error; +int i =1 ; + +for (error = report->errors; error; error = error->next) +clar_print_error (i++, _clar.last_report, error); +} + +static void +clar_report_all (void ) +{ +struct clar_report *report; +struct clar_error *error; +int i =1 ; + +for (report = _clar.reports; report; report = report->next) { +if (report->status !=CL_TEST_FAILURE ) +continue ; + +for (error = report->errors; error; error = error->next) +clar_print_error (i++, report, error); + } +} + +#ifdef WIN32 +# define clar_time DWORD + +static void clar_time_now (clar_time *out) +{ + *out =GetTickCount (); +} + +static double clar_time_diff (clar_time *start,clar_time *end) +{ +return ((double )*end - (double )*start) /1000 ; +} +#else +# include <sys/time.h> + +# define clar_time struct timeval + +static void clar_time_now (clar_time *out) +{ +struct timezone tz; + +gettimeofday (out, &tz); +} + +static double clar_time_diff (clar_time *start,clar_time *end) +{ +return ((double )end->tv_sec + (double )end->tv_usec /1.0E6 ) - + ((double )start->tv_sec + (double )start->tv_usec /1.0E6 ); +} +#endif + +static void +clar_run_test ( +const struct clar_suite *suite, +const struct clar_func *test, +const struct clar_func *initialize, +const struct clar_func *cleanup) +{ +clar_time start, end; + + _clar.trampoline_enabled =1 ; + +CL_TRACE (CL_TRACE__TEST__BEGIN ); + + _clar.last_report->start =time (NULL ); +clar_time_now (&start); + +if (setjmp (_clar.trampoline) ==0 ) { +if (initialize->ptr !=NULL ) + initialize->ptr (); + +CL_TRACE (CL_TRACE__TEST__RUN_BEGIN ); + test->ptr (); +CL_TRACE (CL_TRACE__TEST__RUN_END ); + } + +clar_time_now (&end); + + _clar.trampoline_enabled =0 ; + +if (_clar.last_report->status ==CL_TEST_NOTRUN ) + _clar.last_report->status =CL_TEST_OK ; + + _clar.last_report->elapsed =clar_time_diff (&start, &end); + +if (_clar.local_cleanup !=NULL ) + _clar.local_cleanup (_clar.local_cleanup_payload); + +if (cleanup->ptr !=NULL ) + cleanup->ptr (); + +CL_TRACE (CL_TRACE__TEST__END ); + + _clar.tests_ran++; + +/* remove any local-set cleanup methods */ + _clar.local_cleanup =NULL ; + _clar.local_cleanup_payload =NULL ; + +if (_clar.report_errors_only) { +clar_report_errors (_clar.last_report); + }else { + clar_print_ontest(suite->name, test->name, _clar.tests_ran,
_clar.last_report->status);t/unit-tests/clar/clar.c +66
-0+ } +} + +static void +clar_run_suite (const struct clar_suite *suite,const char *filter) +{ +const struct clar_func *test = suite->tests; +size_t i, matchlen; +struct clar_report *report; +int exact =0 ; + +if (!suite->enabled) +return ; + +if (_clar.exit_on_error && _clar.total_errors) +return ; + +if (!_clar.report_errors_only) +clar_print_onsuite (suite->name, ++_clar.suites_ran); + + _clar.active_suite = suite->name; + _clar.active_test =NULL ; +CL_TRACE (CL_TRACE__SUITE_BEGIN ); + +if (filter) { +size_t suitelen =strlen (suite->name); + matchlen =strlen (filter); +if (matchlen <= suitelen) { + filter =NULL ; + }else { + filter += suitelen; +while (*filter ==':' ) + ++filter; + matchlen =strlen (filter); + +if (matchlen && filter[matchlen -1 ] =='$' ) { + exact =1 ; + matchlen--; + } + } + } + +for (i =0 ; i < suite->test_count; ++i) { +if (filter &&strncmp (test[i].name, filter, matchlen)) +continue ; + +if (exact &&strlen (test[i].name) != matchlen) +continue ; + + _clar.active_test = test[i].name; + + report =calloc (1 ,sizeof (struct clar_report )); + report->suite = _clar.active_suite; + report->test = _clar.active_test; + report->test_number = _clar.tests_ran; + report->status =CL_TEST_NOTRUN ; + +if (_clar.reports ==NULL ) + _clar.reports = report; + +if (_clar.last_report !=NULL ) + _clar.last_report->next = report; + + _clar.last_report = report; + + clar_run_test(suite, &test[i], &suite->initialize,
&suite->cleanup);t/unit-tests/clar/clar.c +15
-0+ +if (_clar.exit_on_error && _clar.total_errors) +return ; + } + + _clar.active_test =NULL ; +CL_TRACE (CL_TRACE__SUITE_END ); +} + +static void +clar_usage (const char *arg) +{ +printf ("Usage: %s [options]\n\n" , arg); +printf ("Options:\n" ); + printf(" -sname Run only the suite with `name` (can go toindividual test name)\n");
t/unit-tests/clar/clar.c +8
-0+printf (" -iname Include the suite with `name`\n" ); +printf (" -xname Exclude the suite with `name`\n" ); +printf (" -v Increase verbosity (show suite names)\n" ); +printf (" -q Only report tests that had an error\n" ); +printf (" -Q Quit as soon as a test fails\n" ); +printf (" -t Display results in tap format\n" ); +printf (" -l Print suite names\n" ); + printf(" -r[filename] Write summary file (to the optionalfilename)\n");
t/unit-tests/clar/clar.c +38
-0+exit (-1 ); +} + +static void +clar_parse_args (int argc,char **argv) +{ +int i; + +/* Verify options before execute */ +for (i =1 ; i < argc; ++i) { +char *argument = argv[i]; + +if (argument[0 ] !='-' || argument[1 ] =='\0' + ||strchr ("sixvqQtlr" , argument[1 ]) ==NULL ) { +clar_usage (argv[0 ]); + } + } + +for (i =1 ; i < argc; ++i) { +char *argument = argv[i]; + +switch (argument[1 ]) { +case 's' : +case 'i' : +case 'x' : {/* given suite name */ +int offset = (argument[2 ] =='=' ) ?3 :2 , found =0 ; +char action = argument[1 ]; +size_t j, arglen, suitelen, cmplen; + + argument += offset; + arglen =strlen (argument); + +if (arglen ==0 ) +clar_usage (argv[0 ]); + +for (j =0 ; j < _clar_suite_count; ++j) { + suitelen =strlen (_clar_suites[j].name); + cmplen = (arglen < suitelen) ? arglen :suitelen;
t/unit-tests/clar/clar.c +2
-0+ + if (strncmp(argument, _clar_suites[j].name,cmplen) == 0) {
t/unit-tests/clar/clar.c +3
-0+int exact = (arglen >= suitelen); + +/* Do we have a real suite prefix separated by a
t/unit-tests/clar/clar.c +1
-0+* trailing '::' or just a matching substring? */
t/unit-tests/clar/clar.c +1
-0+ if (arglen > suitelen &&(argument[suitelen] != ':'
t/unit-tests/clar/clar.c +1
-0+ || argument[suitelen + 1]!= ':'))
t/unit-tests/clar/clar.c +6
-0+continue ; + + ++found; + +if (!exact) + _clar.verbosity =
MAX(_clar.verbosity, 1);t/unit-tests/clar/clar.c +4
-0+ +switch (action) { +case 's' : { +struct clar_explicit *explicit=
t/unit-tests/clar/clar.c +1
-0+ calloc(1,
sizeof(struct clar_explicit));t/unit-tests/clar/clar.c +7
-0+assert (explicit); + + explicit->suite_idx = j; + explicit->filter = argument; + +if (_clar.explicit ==NULL ) + _clar.explicit =explicit;
t/unit-tests/clar/clar.c +2
-0+ + if (_clar.last_explicit !=NULL)
+_clar.last_explicit->next= explicit;t/unit-tests/clar/clar.c +3
-0+ + _clar_suites[j].enabled =1 ; + _clar.last_explicit =explicit;
t/unit-tests/clar/clar.c +3
-0+break ; + } +case 'i' : _clar_suites[j].enabled =1 ;break;
t/unit-tests/clar/clar.c +1
-0+case 'x' : _clar_suites[j].enabled =0 ;break;
t/unit-tests/clar/clar.c +9
-0+ } + +if (exact) +break ; + } + } + +if (!found) { + clar_print_onabort("No suite matching '%s'found.\n", argument);
t/unit-tests/clar/clar.c +20
-0+exit (-1 ); + } +break ; + } + +case 'q' : + _clar.report_errors_only =1 ; +break ; + +case 'Q' : + _clar.exit_on_error =1 ; +break ; + +case 't' : + _clar.output_format =CL_OUTPUT_TAP ; +break ; + +case 'l' : { +size_t j; + printf("Test suites (use -s<name> to run justone):\n");
t/unit-tests/clar/clar.c +2
-0+for (j =0 ; j < _clar_suite_count; ++j) + printf(" %3d: %s\n", (int)j,_clar_suites[j].name);
t/unit-tests/clar/clar.c +12
-0+ +exit (0 ); + } + +case 'v' : + _clar.verbosity++; +break ; + +case 'r' : + _clar.write_summary =1 ; +free (_clar.summary_filename); + _clar.summary_filename = *(argument + 2) ?strdup(argument + 2) : NULL;
t/unit-tests/clar/clar.c +51
-0+break ; + +default : +assert (!"Unexpected commandline argument!" ); + } + } +} + +void +clar_test_init (int argc,char **argv) +{ +const char *summary_env; + +if (argc >1 ) +clar_parse_args (argc, argv); + +clar_print_init ( + (int )_clar_callback_count, + (int )_clar_suite_count, +"" + ); + +if (!_clar.summary_filename && + (summary_env =getenv ("CLAR_SUMMARY" )) !=NULL ) { + _clar.write_summary =1 ; + _clar.summary_filename =strdup (summary_env); + } + +if (_clar.write_summary && !_clar.summary_filename) + _clar.summary_filename =strdup ("summary.xml" ); + +if (_clar.write_summary && + !(_clar.summary =clar_summary_init (_clar.summary_filename))) { +clar_print_onabort ("Failed to open the summary file\n" ); +exit (-1 ); + } + +if (clar_sandbox () <0 ) { +clar_print_onabort ("Failed to sandbox the test runner.\n" ); +exit (-1 ); + } +} + +int +clar_test_run(void) +{ + size_t i; + struct clar_explicit *explicit; + + if (_clar.explicit) { + for (explicit = _clar.explicit; explicit; explicit =
explicit->next)t/unit-tests/clar/clar.c +1
-0+ clar_run_suite(&_clar_suites[explicit->suite_idx],
explicit->filter);t/unit-tests/clar/clar.c +57
-0+ }else { +for (i =0 ; i < _clar_suite_count; ++i) +clar_run_suite (&_clar_suites[i],NULL ); + } + +return _clar.total_errors; +} + +void +clar_test_shutdown (void ) +{ +struct clar_explicit *explicit, *explicit_next; +struct clar_report *report, *report_next; + +clar_print_shutdown ( + _clar.tests_ran, + (int )_clar_suite_count, + _clar.total_errors + ); + +clar_unsandbox (); + +if (_clar.write_summary &&clar_summary_shutdown (_clar.summary) <0 ) { +clar_print_onabort ("Failed to write the summary file\n" ); +exit (-1 ); + } + +for (explicit = _clar.explicit; explicit; explicit = explicit_next) { + explicit_next = explicit->next; +free (explicit); + } + +for (report = _clar.reports; report; report = report_next) { + report_next = report->next; +free (report); + } + +free (_clar.summary_filename); +} + +int +clar_test (int argc,char **argv) +{ +int errors; + +clar_test_init (argc, argv); + errors =clar_test_run (); +clar_test_shutdown (); + +return errors; +} + +static void abort_test (void ) +{ +if (!_clar.trampoline_enabled) { + clar_print_onabort( + "Fatal error: a cleanup method raised anexception.");
t/unit-tests/clar/clar.c +61
-0+clar_report_errors (_clar.last_report); +exit (-1 ); + } + +CL_TRACE (CL_TRACE__TEST__LONGJMP ); +longjmp (_clar.trampoline,-1 ); +} + +void clar__skip (void ) +{ + _clar.last_report->status =CL_TEST_SKIP ; + _clar.total_skipped++; +abort_test (); +} + +void clar__fail ( +const char *file, +const char *function, +size_t line, +const char *error_msg, +const char *description, +int should_abort) +{ +struct clar_error *error =calloc (1 ,sizeof (struct clar_error )); + +if (_clar.last_report->errors ==NULL ) + _clar.last_report->errors = error; + +if (_clar.last_report->last_error !=NULL ) + _clar.last_report->last_error->next = error; + + _clar.last_report->last_error = error; + + error->file = file; + error->function = function; + error->line_number = line; + error->error_msg = error_msg; + +if (description !=NULL ) + error->description =strdup (description); + + _clar.total_errors++; + _clar.last_report->status =CL_TEST_FAILURE ; + +if (should_abort) +abort_test (); +} + +void clar__assert ( +int condition, +const char *file, +const char *function, +size_t line, +const char *error_msg, +const char *description, +int should_abort) +{ +if (condition) +return ; + + clar__fail(file, function, line, error_msg, description,
should_abort);t/unit-tests/clar/clar.c +26
-0+} + +void clar__assert_equal ( +const char *file, +const char *function, +size_t line, +const char *err, +int should_abort, +const char *fmt, + ...) +{ +va_list args; +char buf[4096 ]; +int is_equal =1 ; + +va_start (args, fmt); + +if (!strcmp ("%s" , fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp (s1, s2); + +if (!is_equal) { +if (s1 && s2) { +int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] &&s2[pos]; ++pos)
t/unit-tests/clar/clar.c +2
-0+/* find differing byte offset */ ; + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (atbyte %d)",
t/unit-tests/clar/clar.c +3
-0+ s1, s2, pos); + }else { + p_snprintf(buf, sizeof(buf), "'%s' != '%s'",s1, s2);
t/unit-tests/clar/clar.c +16
-0+ } + } + } +else if (!strcmp ("%.*s" , fmt)) { + const char *s1 = va_arg(args, const char *); + const char *s2 = va_arg(args, const char *); +int len =va_arg (args, int); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp (s1, s2, len); + +if (!is_equal) { +if (s1 && s2) { +int pos; +for (pos =0 ; s1[pos] == s2[pos] && pos < len; ++pos) +/* find differing byte offset */ ; + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'(at byte %d)",
t/unit-tests/clar/clar.c +3
-0+ len, s1, len, s2, pos); + }else { + p_snprintf(buf, sizeof(buf), "'%.*s' !='%.*s'", len, s1, len, s2);
t/unit-tests/clar/clar.c +7
-0+ } + } + } +else if (!strcmp ("%ls" , fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1,wcs2);
t/unit-tests/clar/clar.c +5
-0+ +if (!is_equal) { +if (wcs1 && wcs2) { +int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] &&wcs1[pos] && wcs2[pos]; ++pos)
t/unit-tests/clar/clar.c +2
-0+/* find differing byte offset */ ; + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'(at byte %d)",
t/unit-tests/clar/clar.c +3
-0+ wcs1, wcs2, pos); + }else { + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'",wcs1, wcs2);
t/unit-tests/clar/clar.c +8
-0+ } + } + } +else if (!strcmp ("%.*ls" , fmt)) { + const wchar_t *wcs1 = va_arg(args, const wchar_t *); + const wchar_t *wcs2 = va_arg(args, const wchar_t *); +int len =va_arg (args, int); + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1,wcs2, len);
t/unit-tests/clar/clar.c +5
-0+ + if (!is_equal) { + if (wcs1 && wcs2) { + int pos; + for (pos = 0; wcs1[pos] == wcs2[pos] && pos <len; ++pos)
t/unit-tests/clar/clar.c +2
-0+/* find differing byte offset */ ; + p_snprintf(buf, sizeof(buf), "'%.*ls' !='%.*ls' (at byte %d)",
t/unit-tests/clar/clar.c +3
-0+ len, wcs1, len, wcs2, pos); + }else { + p_snprintf(buf, sizeof(buf), "'%.*ls' !='%.*ls'", len, wcs1, len, wcs2);
t/unit-tests/clar/clar.c +10
-0+ } + } + } +else if (!strcmp ("%" PRIuZ, fmt) || !strcmp ("%" PRIxZ, fmt)) { +size_t sz1 =va_arg (args, size_t), sz2 =va_arg (args, size_t); + is_equal = (sz1 == sz2); +if (!is_equal) { +int offset =p_snprintf (buf,sizeof (buf), fmt, sz1); +strncat (buf," != " ,sizeof (buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,fmt, sz2);
t/unit-tests/clar/clar.c +15
-0+ } + } +else if (!strcmp ("%p" , fmt)) { + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); + is_equal = (p1 == p2); +if (!is_equal) +p_snprintf (buf,sizeof (buf),"%p != %p" , p1, p2); + } +else { +int i1 =va_arg (args, int), i2 =va_arg (args, int); + is_equal = (i1 == i2); +if (!is_equal) { +int offset =p_snprintf (buf,sizeof (buf), fmt, i1); +strncat (buf," != " ,sizeof (buf) - offset); + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,fmt, i2);
t/unit-tests/clar/clar.c +20
-0+ } + } + +va_end (args); + +if (!is_equal) +clar__fail (file, function, line, err, buf, should_abort); +} + +void cl_set_cleanup (void (*cleanup)(void *),void *opaque) +{ + _clar.local_cleanup = cleanup; + _clar.local_cleanup_payload = opaque; +} + +#include "clar/sandbox.h" +#include "clar/fixtures.h" +#include "clar/fs.h" +#include "clar/print.h" +#include "clar/summary.h" t/unit-tests/clar/clar.h new file +89
-0@@ -0,0 +1,173 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ +#ifndef __CLAR_TEST_H__ +#define __CLAR_TEST_H__ + +#include <stdlib.h> + +enum cl_test_status { +CL_TEST_OK , +CL_TEST_FAILURE , +CL_TEST_SKIP , +CL_TEST_NOTRUN , +}; + +enum cl_output_format { +CL_OUTPUT_CLAP , +CL_OUTPUT_TAP , +}; + +/** Setup clar environment */ +void clar_test_init (int argc,char *argv[]); +int clar_test_run (void ); +void clar_test_shutdown (void ); + +/** One shot setup & run */ +int clar_test (int argc,char *argv[]); + +const char *clar_sandbox_path (void ); + +void cl_set_cleanup (void (*cleanup)(void *),void *opaque); +void cl_fs_cleanup (void ); + +/** +* cl_trace_* is a hook to provide a simple global tracing +* mechanism. +* +* The goal here is to let main() provide clar-proper +* with a callback to optionally write log info for +* test operations into the same stream used by their +* actual tests. This would let them print test names +* and maybe performance data as they choose. +* +* The goal is NOT to alter the flow of control or to +* override test selection/skipping. (So the callback +* does not return a value.) +* +* The goal is NOT to duplicate the existing +* pass/fail/skip reporting. (So the callback +* does not accept a status/errorcode argument.) +* +*/ +typedef enum cl_trace_event { +CL_TRACE__SUITE_BEGIN , +CL_TRACE__SUITE_END , +CL_TRACE__TEST__BEGIN , +CL_TRACE__TEST__END , +CL_TRACE__TEST__RUN_BEGIN , +CL_TRACE__TEST__RUN_END , +CL_TRACE__TEST__LONGJMP , +}cl_trace_event ; + +typedef void (cl_trace_cb )( +cl_trace_event ev, +const char *suite_name, +const char *test_name, +void *payload); + +/** +* Register a callback into CLAR to send global trace events. +* Pass NULL to disable. +*/ +void cl_trace_register (cl_trace_cb *cb,void *payload); + + +#ifdef CLAR_FIXTURE_PATH +const char *cl_fixture (const char *fixture_name); +void cl_fixture_sandbox (const char *fixture_name); +void cl_fixture_cleanup (const char *fixture_name); +const char *cl_fixture_basename (const char *fixture_name); +#endif + +/** +* Assertion macros with explicit error message +*/ +#define cl_must_pass_ (expr, desc) clar__assert((expr) >= 0, __FILE__,
__func__,__LINE__, "Function call failed: " #expr, desc, 1) +#definecl_must_fail_(expr, desc)clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)+#definecl_assert_(expr, desc)clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)t/unit-tests/clar/clar.h new file +5
-0+ +/** +* Check macros with explicit error message +*/ +#define cl_check_pass_ (expr, desc) clar__assert((expr) >= 0, __FILE__,
__func__,__LINE__, "Function call failed: " #expr, desc, 0) +#definecl_check_fail_(expr, desc)clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)+#definecl_check_(expr, desc)clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)t/unit-tests/clar/clar.h new file +19
-0+ +/** +* Assertion macros with no error message +*/ +#define cl_must_pass (expr) cl_must_pass_(expr, NULL) +#define cl_must_fail (expr) cl_must_fail_(expr, NULL) +#define cl_assert (expr) cl_assert_(expr, NULL) + +/** +* Check macros with no error message +*/ +#define cl_check_pass (expr) cl_check_pass_(expr, NULL) +#define cl_check_fail (expr) cl_check_fail_(expr, NULL) +#define cl_check (expr) cl_check_(expr, NULL) + +/** +* Forced failure/warning +*/ +#define cl_fail (desc) clar__fail(__FILE__, __func__, __LINE__, "Testfailed.", desc, 1)
+#definecl_warning(desc)clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)t/unit-tests/clar/clar.h new file +7
-0+ +#define cl_skip () clar__skip() + +/** +* Typed assertion macros +*/ +#define cl_assert_equal_s (s1,s2)
clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
+#definecl_assert_equal_s_(s1,s2,note)clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_wcs (wcs1,wcs2)
clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
+#definecl_assert_equal_wcs_(wcs1,wcs2,note)clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_strn (s1,s2,len)
clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
+#definecl_assert_equal_strn_(s1,s2,len,note)clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_wcsn (wcs1,wcs2,len)clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
+#definecl_assert_equal_wcsn_(wcs1,wcs2,len,note)clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_i (i1,i2)
clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
+#definecl_assert_equal_i_(i1,i2,note)clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
+#definecl_assert_equal_i_fmt(i1,i2,fmt)clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_b (b1,b2)
clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))t/unit-tests/clar/clar.h new file +2
-0+ +#define cl_assert_equal_p (p1,p2)
clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))t/unit-tests/clar/clar.h new file +30
-0+ +void clar__skip (void ); + +void clar__fail ( +const char *file, +const char *func, +size_t line, +const char *error, +const char *description, +int should_abort); + +void clar__assert ( +int condition, +const char *file, +const char *func, +size_t line, +const char *error, +const char *description, +int should_abort); + +void clar__assert_equal ( +const char *file, +const char *func, +size_t line, +const char *err, +int should_abort, +const char *fmt, + ...); + +#endif t/unit-tests/clar/clar/fixtures.h new file +50
-0@@ -0,0 +1,50 @@ +#ifdef CLAR_FIXTURE_PATH +static const char * +fixture_path (const char *base,const char *fixture_name) +{ +static char _path[4096 ]; +size_t root_len; + + root_len =strlen (base); +strncpy (_path, base,sizeof (_path)); + +if (_path[root_len -1 ] !='/' ) + _path[root_len++] ='/' ; + +if (fixture_name[0 ] =='/' ) + fixture_name++; + +strncpy (_path + root_len, + fixture_name, +sizeof (_path) - root_len); + +return _path; +} + +const char *cl_fixture (const char *fixture_name) +{ +return fixture_path (CLAR_FIXTURE_PATH , fixture_name); +} + +void cl_fixture_sandbox (const char *fixture_name) +{ +fs_copy (cl_fixture (fixture_name), _clar_path); +} + +const char *cl_fixture_basename (const char *fixture_name) +{ +const char *p; + +for (p = fixture_name; *p; p++) { +if (p[0 ] =='/' && p[1 ] && p[1 ] !='/' ) + fixture_name = p+1 ; + } + +return fixture_name; +} + +void cl_fixture_cleanup (const char *fixture_name) +{ +fs_rm (fixture_path (_clar_path,cl_fixture_basename (fixture_name))); +} +#endif t/unit-tests/clar/clar/fs.h new file +108
-0@@ -0,0 +1,522 @@ +/* +* By default, use a read/write loop to copy files on POSIX systems. +* On Linux, use sendfile by default as it's slightly faster. On +* macOS, we avoid fcopyfile by default because it's slightly slower. +*/ +#undef USE_FCOPYFILE +#define USE_SENDFILE 1 + +#ifdef _WIN32 + +#ifdef CLAR_WIN32_LONGPATHS +# define CLAR_MAX_PATH 4096 +#else +# define CLAR_MAX_PATH MAX_PATH +#endif + +#define RM_RETRY_COUNT 5 +#define RM_RETRY_DELAY 10 + +#ifdef __MINGW32__ + +/* These security-enhanced functions are not available + * in MinGW, so just use the vanilla ones */ +#define wcscpy_s(a, b, c) wcscpy((a), (c)) +#define wcscat_s(a, b, c) wcscat((a), (c)) + +#endif /* __MINGW32__ */ + +static int +fs__dotordotdot(WCHAR *_tocheck) +{ + return _tocheck[0] == '.' && + (_tocheck[1] == '\0' || + (_tocheck[1] == '.' && _tocheck[2] == '\0')); +} + +static int +fs_rmdir_rmdir(WCHAR *_wpath) +{ + unsigned retries = 1; + + while (!RemoveDirectoryW(_wpath)) { + /* Only retry when we have retries remaining, and the + * error was ERROR_DIR_NOT_EMPTY. */ + if (retries++ > RM_RETRY_COUNT || + ERROR_DIR_NOT_EMPTY != GetLastError()) + return -1; + + /* Give whatever has a handle to a child item some time + * to release it before trying again */ + Sleep(RM_RETRY_DELAY * retries * retries); + } + + return 0; +} + +static void translate_path(WCHAR *path, size_t path_size) +{ + size_t path_len, i; + + if (wcsncmp(path, L"\\\\?\\", 4) == 0) + return; + + path_len = wcslen(path); + cl_assert(path_size > path_len + 4); + + for (i = path_len; i > 0; i--) { + WCHAR c = path[i - 1]; + + if (c == L'/') + path[i + 3] = L'\\'; + else + path[i + 3] = path[i - 1]; + } + + path[0] = L'\\'; + path[1] = L'\\'; + path[2] = L'?'; + path[3] = L'\\'; + path[path_len + 4] = L'\0'; +} + +static void +fs_rmdir_helper(WCHAR *_wsource) +{ + WCHAR buffer[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buffer_prefix_len; + + /* Set up the buffer and capture the length */ + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource); + translate_path(buffer, CLAR_MAX_PATH); + wcscat_s(buffer, CLAR_MAX_PATH, L"\\"); + buffer_prefix_len = wcslen(buffer); + + /* FindFirstFile needs a wildcard to match multiple items */ + wcscat_s(buffer, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buffer, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH -
buffer_prefix_len, find_data.cFileName);t/unit-tests/clar/clar/fs.h new file +5
-0+ +if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) +fs_rmdir_helper (buffer); +else { +/* If set, the +R bit must be cleared before deleting */
t/unit-tests/clar/clar/fs.h new file +1
-0+ if (FILE_ATTRIBUTE_READONLY &find_data.dwFileAttributes)
t/unit-tests/clar/clar/fs.h new file +1
-0+ cl_assert(SetFileAttributesW(buffer,find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
t/unit-tests/clar/clar/fs.h new file +69
-0+ +cl_assert (DeleteFileW (buffer)); + } + } +while (FindNextFileW (find_handle, &find_data)); + +/* Ensure that we successfully completed the enumeration */ +cl_assert (ERROR_NO_MORE_FILES ==GetLastError ()); + +/* Close the find handle */ +FindClose (find_handle); + +/* Now that the directory is empty, remove it */ +cl_assert (0 ==fs_rmdir_rmdir (_wsource)); +} + +static int +fs_rm_wait (WCHAR *_wpath) +{ +unsigned retries =1 ; +DWORD last_error; + +do { +if (INVALID_FILE_ATTRIBUTES ==GetFileAttributesW (_wpath)) + last_error =GetLastError (); +else + last_error =ERROR_SUCCESS ; + +/* Is the item gone? */ +if (ERROR_FILE_NOT_FOUND == last_error || +ERROR_PATH_NOT_FOUND == last_error) +return 0 ; + +Sleep (RM_RETRY_DELAY * retries * retries); + } +while (retries++ <=RM_RETRY_COUNT ); + +return -1 ; +} + +static void +fs_rm (const char *_source) +{ +WCHAR wsource[CLAR_MAX_PATH ]; +DWORD attrs; + +/* The input path is UTF-8. Convert it to wide characters +* for use with the Windows API */ +cl_assert (MultiByteToWideChar (CP_UTF8 , +MB_ERR_INVALID_CHARS , + _source, +-1 ,/* Indicates NULL termination */ + wsource, +CLAR_MAX_PATH )); + +translate_path (wsource,CLAR_MAX_PATH ); + +/* Does the item exist? If not, we have no work to do */ + attrs =GetFileAttributesW (wsource); + +if (INVALID_FILE_ATTRIBUTES == attrs) +return ; + +if (FILE_ATTRIBUTE_DIRECTORY & attrs) +fs_rmdir_helper (wsource); +else { +/* The item is a file. Strip the +R bit */ +if (FILE_ATTRIBUTE_READONLY & attrs) + cl_assert(SetFileAttributesW(wsource, attrs &~FILE_ATTRIBUTE_READONLY));
t/unit-tests/clar/clar/fs.h new file +41
-0+ +cl_assert (DeleteFileW (wsource)); + } + +/* Wait for the DeleteFile or RemoveDirectory call to complete */ +cl_assert (0 ==fs_rm_wait (wsource)); +} + +static void +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) +{ + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH]; + HANDLE find_handle; + WIN32_FIND_DATAW find_data; + size_t buf_source_prefix_len, buf_dest_prefix_len; + + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource); + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\"); + translate_path(buf_source, CLAR_MAX_PATH); + buf_source_prefix_len = wcslen(buf_source); + + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest); + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\"); + translate_path(buf_dest, CLAR_MAX_PATH); + buf_dest_prefix_len = wcslen(buf_dest); + + /* Get an enumerator for the items in the source. */ + wcscat_s(buf_source, CLAR_MAX_PATH, L"*"); + find_handle = FindFirstFileW(buf_source, &find_data); + cl_assert(INVALID_HANDLE_VALUE != find_handle); + + /* Create the target directory. */ + cl_assert(CreateDirectoryW(_wdest, NULL)); + + do { + /* FindFirstFile/FindNextFile gives back . and .. + * entries at the beginning */ + if (fs__dotordotdot(find_data.cFileName)) + continue; + + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH -
buf_source_prefix_len, find_data.cFileName);t/unit-tests/clar/clar/fs.h new file +1
-0+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH -
buf_dest_prefix_len, find_data.cFileName);t/unit-tests/clar/clar/fs.h new file +60
-0+ +if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) +fs_copydir_helper (buf_source, buf_dest); +else +cl_assert (CopyFileW (buf_source, buf_dest, TRUE)); + } +while (FindNextFileW (find_handle, &find_data)); + +/* Ensure that we successfully completed the enumeration */ +cl_assert (ERROR_NO_MORE_FILES ==GetLastError ()); + +/* Close the find handle */ +FindClose (find_handle); +} + +static void +fs_copy (const char *_source,const char *_dest) +{ +WCHAR wsource[CLAR_MAX_PATH ], wdest[CLAR_MAX_PATH ]; +DWORD source_attrs, dest_attrs; +HANDLE find_handle; +WIN32_FIND_DATAW find_data; + +/* The input paths are UTF-8. Convert them to wide characters +* for use with the Windows API. */ +cl_assert (MultiByteToWideChar (CP_UTF8 , +MB_ERR_INVALID_CHARS , + _source, +-1 , + wsource, +CLAR_MAX_PATH )); + +cl_assert (MultiByteToWideChar (CP_UTF8 , +MB_ERR_INVALID_CHARS , + _dest, +-1 , + wdest, +CLAR_MAX_PATH )); + +translate_path (wsource,CLAR_MAX_PATH ); +translate_path (wdest,CLAR_MAX_PATH ); + +/* Check the source for existence */ + source_attrs =GetFileAttributesW (wsource); +cl_assert (INVALID_FILE_ATTRIBUTES != source_attrs); + +/* Check the target for existence */ + dest_attrs =GetFileAttributesW (wdest); + +if (INVALID_FILE_ATTRIBUTES != dest_attrs) { +/* Target exists; append last path part of source to target. +* Use FindFirstFile to parse the path */ + find_handle =FindFirstFileW (wsource, &find_data); +cl_assert (INVALID_HANDLE_VALUE != find_handle); +wcscat_s (wdest,CLAR_MAX_PATH ,L"\\" ); +wcscat_s (wdest,CLAR_MAX_PATH , find_data.cFileName); +FindClose (find_handle); + +/* Check the new target for existence */ + cl_assert(INVALID_FILE_ATTRIBUTES ==GetFileAttributesW(wdest));
t/unit-tests/clar/clar/fs.h new file +108
-0+ } + +if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) +fs_copydir_helper (wsource, wdest); +else +cl_assert (CopyFileW (wsource, wdest, TRUE)); +} + +void +cl_fs_cleanup (void ) +{ +#ifdef CLAR_FIXTURE_PATH +fs_rm (fixture_path (_clar_path,"*" )); +#endif +} + +#else + +#include <errno.h> +#include <string.h> +#include <limits.h> +#include <dirent.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +#if defined(__linux__) +# include <sys/sendfile.h> +#endif + +#if defined(__APPLE__) +# include <copyfile.h> +#endif + +static void basename_r (const char **out,int *out_len,const char *in) +{ +size_t in_len =strlen (in), start_pos; + +for (in_len =strlen (in); in_len; in_len--) { +if (in[in_len -1 ] !='/' ) +break ; + } + +for (start_pos = in_len; start_pos; start_pos--) { +if (in[start_pos -1 ] =='/' ) +break ; + } + +cl_assert (in_len - start_pos <INT_MAX ); + +if (in_len - start_pos >0 ) { + *out = &in[start_pos]; + *out_len = (in_len - start_pos); + }else { + *out ="/" ; + *out_len =1 ; + } +} + +static char *joinpath (const char *dir,const char *base,int base_len) +{ +char *out; +int len; + +if (base_len ==-1 ) { +size_t bl =strlen (base); + +cl_assert (bl <INT_MAX ); + base_len = (int )bl; + } + + len =strlen (dir) + base_len +2 ; +cl_assert (len >0 ); + +cl_assert (out =malloc (len)); +cl_assert (snprintf (out, len,"%s/%.*s" , dir, base_len, base) < len); + +return out; +} + +static void +fs_copydir_helper (const char *source,const char *dest,int dest_mode) +{ +DIR *source_dir; +struct dirent *d; + +mkdir (dest, dest_mode); + +cl_assert_ (source_dir =opendir (source),"Could not open source dir" ); +while ((d = (errno =0 ,readdir (source_dir))) !=NULL ) { +char *child; + +if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +continue ; + + child =joinpath (source, d->d_name,-1 ); +fs_copy (child, dest); +free (child); + } + +cl_assert_ (errno ==0 ,"Failed to iterate source dir" ); + +closedir (source_dir); +} + +static void +fs_copyfile_helper(const char *source, size_t source_len, const char *dest,int
dest_mode)t/unit-tests/clar/clar/fs.h new file +14
-0+{ + int in, out; + + cl_must_pass((in = open(source, O_RDONLY))); + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode))); + +#if USE_FCOPYFILE && defined(__APPLE__) + ((void)(source_len)); /* unused */ + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA)); +#elif USE_SENDFILE && defined(__linux__) + { + ssize_t ret = 0; + + while (source_len && (ret = sendfile(out, in, NULL,
source_len)) > 0) {t/unit-tests/clar/clar/fs.h new file +36
-0+ source_len -= (size_t)ret; + } +cl_assert (ret >=0 ); + } +#else + { +char buf[131072 ]; +ssize_t ret; + + ((void )(source_len));/* unused */ + +while ((ret =read (in, buf,sizeof (buf))) >0 ) { +size_t len = (size_t )ret; + +while (len && (ret =write (out, buf, len)) >0 ) { +cl_assert (ret <= (ssize_t )len); + len -= ret; + } +cl_assert (ret >=0 ); + } +cl_assert (ret ==0 ); + } +#endif + +close (in); +close (out); +} + +static void +fs_copy (const char *source,const char *_dest) +{ +char *dbuf =NULL ; +const char *dest =NULL ; +struct stat source_st, dest_st; + + cl_must_pass_(lstat(source, &source_st), "Failed to stat copysource");
t/unit-tests/clar/clar/fs.h new file +23
-0+ +if (lstat (_dest, &dest_st) ==0 ) { +const char *base; +int base_len; + +/* Target exists and is directory; append basename */ +cl_assert (S_ISDIR (dest_st.st_mode)); + +basename_r (&base, &base_len, source); +cl_assert (base_len <INT_MAX ); + + dbuf =joinpath (_dest, base, base_len); + dest = dbuf; + }else if (errno !=ENOENT ) { +cl_fail ("Cannot copy; cannot stat destination" ); + }else { + dest = _dest; + } + +if (S_ISDIR (source_st.st_mode)) { +fs_copydir_helper (source, dest, source_st.st_mode); + }else { + fs_copyfile_helper(source, source_st.st_size, dest,source_st.st_mode);
t/unit-tests/clar/clar/fs.h new file +55
-0+ } + +free (dbuf); +} + +static void +fs_rmdir_helper (const char *path) +{ +DIR *dir; +struct dirent *d; + +cl_assert_ (dir =opendir (path),"Could not open dir" ); +while ((d = (errno =0 ,readdir (dir))) !=NULL ) { +char *child; + +if (!strcmp (d->d_name,"." ) || !strcmp (d->d_name,".." )) +continue ; + + child =joinpath (path, d->d_name,-1 ); +fs_rm (child); +free (child); + } + +cl_assert_ (errno ==0 ,"Failed to iterate source dir" ); +closedir (dir); + +cl_must_pass_ (rmdir (path),"Could not remove directory" ); +} + +static void +fs_rm (const char *path) +{ +struct stat st; + +if (lstat (path, &st)) { +if (errno ==ENOENT ) +return ; + +cl_fail ("Cannot copy; cannot stat destination" ); + } + +if (S_ISDIR (st.st_mode)) { +fs_rmdir_helper (path); + }else { +cl_must_pass (unlink (path)); + } +} + +void +cl_fs_cleanup (void ) +{ +clar_unsandbox (); +clar_sandbox (); +} +#endif t/unit-tests/clar/clar/print.h new file +3
-0@@ -0,0 +1,211 @@ +/* clap: clar protocol, the traditional clar output format */ + +static void clar_print_clap_init(int test_count, int suite_count, constchar
*suite_names)t/unit-tests/clar/clar/print.h new file +4
-0+{ + (void )test_count; +printf ("Loaded %d suites: %s\n" , (int )suite_count, suite_names); + printf("Started (test status codes: OK='.' FAILURE='F'SKIPPED='S')\n");
t/unit-tests/clar/clar/print.h new file +3
-0+} + +static void clar_print_clap_shutdown(int test_count, int suite_count, int
error_count)t/unit-tests/clar/clar/print.h new file +10
-0+{ + (void )test_count; + (void )suite_count; + (void )error_count; + +printf ("\n\n" ); +clar_report_all (); +} + +static void clar_print_clap_error(int num, const struct clar_report*report, const struct
clar_error*error)t/unit-tests/clar/clar/print.h new file +19
-0+{ +printf (" %d) Failure:\n" , num); + +printf ("%s::%s [%s:%" PRIuZ"]\n" , + report->suite, + report->test, + error->file, + error->line_number); + +printf (" %s\n" , error->error_msg); + +if (error->description !=NULL ) +printf (" %s\n" , error->description); + +printf ("\n" ); +fflush (stdout); +} + +static void clar_print_clap_ontest(const char *suite_name, const char
*test_name, inttest_number, enumcl_test_statusstatus)t/unit-tests/clar/clar/print.h new file +26
-0+{ + (void )test_name; + (void )test_number; + +if (_clar.verbosity >1 ) { +printf ("%s::%s: " , suite_name, test_name); + +switch (status) { +case CL_TEST_OK :printf ("ok\n" );break ; +case CL_TEST_FAILURE :printf ("fail\n" );break ; +case CL_TEST_SKIP :printf ("skipped" );break ; +case CL_TEST_NOTRUN :printf ("notrun" );break ; + } + }else { +switch (status) { +case CL_TEST_OK :printf ("." );break ; +case CL_TEST_FAILURE :printf ("F" );break ; +case CL_TEST_SKIP :printf ("S" );break ; +case CL_TEST_NOTRUN :printf ("N" );break ; + } + +fflush (stdout); + } +} + +static void clar_print_clap_onsuite(const char *suite_name, int
suite_index)t/unit-tests/clar/clar/print.h new file +15
-0+{ +if (_clar.verbosity ==1 ) +printf ("\n%s" , suite_name); + + (void )suite_index; +} + +static void clar_print_clap_onabort (const char *fmt,va_list arg) +{ +vfprintf (stderr, fmt, arg); +} + +/* tap: test anywhere protocol format */ + +static void clar_print_tap_init(int test_count, int suite_count, const char
*suite_names)t/unit-tests/clar/clar/print.h new file +8
-0+{ + (void )test_count; + (void )suite_count; + (void )suite_names; +printf ("TAP version 13\n" ); +} + +static void clar_print_tap_shutdown(int test_count, int suite_count, int
error_count)t/unit-tests/clar/clar/print.h new file +8
-0+{ + (void )suite_count; + (void )error_count; + +printf ("1..%d\n" , test_count); +} + +static void clar_print_tap_error(int num, const struct clar_report *report,const struct
clar_error*error)t/unit-tests/clar/clar/print.h new file +20
-0+{ + (void )num; + (void )report; + (void )error; +} + +static void print_escaped (const char *str) +{ +char *c; + +while ((c =strchr (str,'\'' )) !=NULL ) { +printf ("%.*s" , (int )(c - str), str); +printf ("''" ); + str = c +1 ; + } + +printf ("%s" , str); +} + +static void clar_print_tap_ontest(const char *suite_name, const char
*test_name, inttest_number, enumcl_test_statusstatus)t/unit-tests/clar/clar/print.h new file +9
-0+{ +const struct clar_error *error = _clar.last_report->errors; + + (void )test_name; + (void )test_number; + +switch (status) { +case CL_TEST_OK : + printf("ok %d - %s::%s\n", test_number, suite_name,
test_name);t/unit-tests/clar/clar/print.h new file +3
-0+break ; +case CL_TEST_FAILURE : + printf("not ok %d - %s::%s\n", test_number, suite_name,
test_name);t/unit-tests/clar/clar/print.h new file +10
-0+ +printf (" ---\n" ); +printf (" reason: |\n" ); +printf (" %s\n" , error->error_msg); + +if (error->description) +printf (" %s\n" , error->description); + +printf (" at:\n" ); +printf (" file: '" );print_escaped (error->file);
printf("'\n");t/unit-tests/clar/clar/print.h new file +8
-0+printf (" line: %" PRIuZ"\n" , error->line_number); +printf (" function: '%s'\n" , error->function); +printf (" ---\n" ); + +break ; +case CL_TEST_SKIP : +case CL_TEST_NOTRUN : + printf("ok %d - # SKIP %s::%s\n", test_number, suite_name,
test_name);t/unit-tests/clar/clar/print.h new file +34
-0+break ; + } + +fflush (stdout); +} + +static void clar_print_tap_onsuite (const char *suite_name,int suite_index) +{ +printf ("# start of suite %d: %s\n" , suite_index, suite_name); +} + +static void clar_print_tap_onabort (const char *fmt,va_list arg) +{ +printf ("Bail out! " ); +vprintf (fmt, arg); +fflush (stdout); +} + +/* indirection between protocol output selection */ + +#define FN , ...) do { \ + switch (_clar.output_format) { \ + case CL_OUTPUT_CLAP: \ + clar_print_clap_##FN (__VA_ARGS__); \ + break; \ + case CL_OUTPUT_TAP: \ + clar_print_tap_##FN (__VA_ARGS__); \ + break; \ + default: \ + abort(); \ + } \ + } while (0) + +static void clar_print_init(int test_count, int suite_count, const char
*suite_names)t/unit-tests/clar/clar/print.h new file +5
-0+{ +
error_count)t/unit-tests/clar/clar/print.h new file +5
-0+{ +const struct
clar_error*error)t/unit-tests/clar/clar/print.h new file +5
-0+{ +
*test_name, inttest_number, enumcl_test_statusstatus)t/unit-tests/clar/clar/print.h new file +16
-0+{ +static void clar_print_onsuite (const char *suite_name,int suite_index) +{ +static void clar_print_onabort (const char *msg, ...) +{ +va_list argp; +va_start (argp, msg); +va_end (argp); +}t/unit-tests/clar/clar/sandbox.h new file +39
-0@@ -0,0 +1,153 @@ +#ifdef __APPLE__ +#include <sys/syslimits.h> +#endif + +static char _clar_path[4096 + 1]; + +static int +is_valid_tmp_path(const char *path) +{ + STAT_T st; + + if (stat(path, &st) != 0) + return 0; + + if (!S_ISDIR(st.st_mode)) + return 0; + + return (access(path, W_OK) == 0); +} + +static int +find_tmp_path(char *buffer, size_t length) +{ +#ifndef _WIN32 + static const size_t var_count = 5; + static const char *env_vars[] = { + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" + }; + + size_t i; + + for (i = 0; i < var_count; ++i) { + const char *env = getenv(env_vars[i]); + if (!env) + continue; + + if (is_valid_tmp_path(env)) { +#ifdef __APPLE__ + if (length >= PATH_MAX && realpath(env, buffer) !=NULL)
t/unit-tests/clar/clar/sandbox.h new file +21
-0+return 0 ; +#endif +strncpy (buffer, env, length -1 ); + buffer[length -1 ] ='\0' ; +return 0 ; + } + } + +/* If the environment doesn't say anything, try to use /tmp */ +if (is_valid_tmp_path ("/tmp" )) { +#ifdef __APPLE__ +if (length >=PATH_MAX &&realpath ("/tmp" , buffer) !=NULL ) +return 0 ; +#endif +strncpy (buffer,"/tmp" , length -1 ); + buffer[length -1 ] ='\0' ; +return 0 ; + } + +#else + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer,(DWORD)length);
t/unit-tests/clar/clar/sandbox.h new file +93
-0+if (env_len >0 && env_len < (DWORD )length) +return 0 ; + +if (GetTempPath ((DWORD )length, buffer)) +return 0 ; +#endif + +/* This system doesn't like us, try to use the current directory */ +if (is_valid_tmp_path ("." )) { +strncpy (buffer,"." , length -1 ); + buffer[length -1 ] ='\0' ; +return 0 ; + } + +return -1 ; +} + +static void clar_unsandbox (void ) +{ +if (_clar_path[0 ] =='\0' ) +return ; + +cl_must_pass (chdir (".." )); + +fs_rm (_clar_path); +} + +static int build_sandbox_path (void ) +{ +#ifdef CLAR_TMPDIR +const char path_tail[] =CLAR_TMPDIR "_XXXXXX" ; +#else +const char path_tail[] ="clar_tmp_XXXXXX" ; +#endif + +size_t len; + +if (find_tmp_path (_clar_path,sizeof (_clar_path)) <0 ) +return -1 ; + + len =strlen (_clar_path); + +#ifdef _WIN32 + {/* normalize path to POSIX forward slashes */ +size_t i; +for (i =0 ; i < len; ++i) { +if (_clar_path[i] =='\\' ) + _clar_path[i] ='/' ; + } + } +#endif + +if (_clar_path[len -1 ] !='/' ) { + _clar_path[len++] ='/' ; + } + +strncpy (_clar_path + len, path_tail,sizeof (_clar_path) - len); + +#if defined(__MINGW32__) +if (_mktemp (_clar_path) ==NULL ) +return -1 ; + +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#elif defined(_WIN32) +if (_mktemp_s (_clar_path,sizeof (_clar_path)) !=0 ) +return -1 ; + +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#else +if (mkdtemp (_clar_path) ==NULL ) +return -1 ; +#endif + +return 0 ; +} + +static int clar_sandbox (void ) +{ +if (_clar_path[0 ] =='\0' &&build_sandbox_path () <0 ) +return -1 ; + +if (chdir (_clar_path) !=0 ) +return -1 ; + +return 0 ; +} + +const char *clar_sandbox_path (void ) +{ +return _clar_path; +}t/unit-tests/clar/clar/summary.h new file +40
-0@@ -0,0 +1,143 @@ + +#include <stdio.h> +#include <time.h> + +static int clar_summary_close_tag ( +struct clar_summary *summary,const char *tag,int indent) +{ +const char *indt; + +if (indent ==0 ) indt ="" ; +else if (indent ==1 ) indt ="\t" ; +else indt ="\t\t" ; + +return fprintf (summary->fp,"%s</%s>\n" , indt, tag); +} + +static int clar_summary_testsuites (struct clar_summary *summary) +{ +return fprintf (summary->fp,"<testsuites>\n" ); +} + +static int clar_summary_testsuite (struct clar_summary *summary, +int idn,const char *name,time_t timestamp, +int test_count,int fail_count,int error_count) +{ +struct tm *tm =localtime (×tamp); +char iso_dt[20 ]; + +if (strftime (iso_dt,sizeof (iso_dt),"%Y-%m-%dT%H:%M:%S" , tm) ==0 ) +return -1 ; + + return fprintf(summary->fp, "\t<testsuite" + " id=\"%d\"" + " name=\"%s\"" + " hostname=\"localhost\"" + " timestamp=\"%s\"" + " tests=\"%d\"" + " failures=\"%d\"" + " errors=\"%d\">\n", + idn, name, iso_dt, test_count, fail_count,
error_count);t/unit-tests/clar/clar/summary.h new file +58
-0+} + +static int clar_summary_testcase (struct clar_summary *summary, +const char *name,const char *classname,double elapsed) +{ +return fprintf (summary->fp, +"\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n" , + name, classname, elapsed); +} + +static int clar_summary_failure (struct clar_summary *summary, +const char *type,const char *message,const char *desc) +{ +return fprintf (summary->fp, +"\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n" , + type, message, desc); +} + +static int clar_summary_skipped (struct clar_summary *summary) +{ +return fprintf (summary->fp,"\t\t\t<skipped />\n" ); +} + +struct clar_summary *clar_summary_init (const char *filename) +{ +struct clar_summary *summary; +FILE *fp; + +if ((fp =fopen (filename,"w" )) ==NULL ) { +perror ("fopen" ); +return NULL ; + } + +if ((summary =malloc (sizeof (struct clar_summary ))) ==NULL ) { +perror ("malloc" ); +fclose (fp); +return NULL ; + } + + summary->filename = filename; + summary->fp = fp; + +return summary; +} + +int clar_summary_shutdown(struct clar_summary *summary) +{ + struct clar_report *report; + const char *last_suite = NULL; + + if (clar_summary_testsuites(summary) < 0) + goto on_error; + + report = _clar.reports; + while (report != NULL) { + struct clar_error *error = report->errors; + + if (last_suite == NULL || strcmp(last_suite, report->suite) !=0) {
t/unit-tests/clar/clar/summary.h new file +2
-0+ if (clar_summary_testsuite(summary, 0, report->suite, + report->start, _clar.tests_ran,_clar.total_errors, 0) < 0)
t/unit-tests/clar/clar/summary.h new file +6
-0+ goto on_error; + } + + last_suite = report->suite; + + clar_summary_testcase(summary, report->test, report->suite,
report->elapsed);t/unit-tests/clar/clar/summary.h new file +19
-0+ +while (error !=NULL ) { +if (clar_summary_failure (summary,"assert" , + error->error_msg, error->description) <0 ) + goto on_error; + + error = error->next; + } + +if (report->status ==CL_TEST_SKIP ) +clar_summary_skipped (summary); + +if (clar_summary_close_tag (summary,"testcase" ,2 ) <0 ) + goto on_error; + + report = report->next; + +if (!report ||strcmp (last_suite, report->suite) !=0 ) { + if (clar_summary_close_tag(summary, "testsuite", 1) <0)
t/unit-tests/clar/clar/summary.h new file +18
-0+ goto on_error; + } + } + +if (clar_summary_close_tag (summary,"testsuites" ,0 ) <0 || +fclose (summary->fp) !=0 ) + goto on_error; + +printf ("written summary file to %s\n" , summary->filename); + +free (summary); +return 0 ; + +on_error: +fclose (summary->fp); +free (summary); +return -1 ; +}t/unit-tests/clar/generate.py new file +25
-0@@ -0,0 +1,266 @@ +#!/usr/bin/env python +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +from __future__ import with_statement +from string import Template +import re, fnmatch, os, sys, codecs, pickle + +class Module(object): + class Template(object): + def __init__(self, module): + self.module = module + + def _render_callback(self, cb): + if not cb: + return ' { NULL, NULL }' + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) + + class DeclarationTemplate(Template): + def render(self): + out = "\n".join("extern %s;" % cb['declaration'] for cb inself.module.callbacks) + "\n"
t/unit-tests/clar/generate.py new file +12
-0+ + for initializer in self.module.initializers: + out += "extern %s;\n" % initializer['declaration'] + + if self.module.cleanup: + out += "extern %s;\n" % self.module.cleanup['declaration'] + + return out + + class CallbacksTemplate(Template): + def render(self): + out = "static const struct clar_func _clar_cb_%s[] = {\n" %self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in
self.module.callbacks)t/unit-tests/clar/generate.py new file +14
-0+ out += "\n};\n" + return out + + class InfoTemplate(Template): + def render(self): + templates = [] + + initializers = self.module.initializers + if len(initializers) == 0: + initializers = [ None ] + + for initializer in initializers: + name = self.module.clean_name() + if initializer andinitializer['short_name'].startswith('initialize_'):
+ variant =
initializer['short_name'][len('initialize_'):]t/unit-tests/clar/generate.py new file +45
-0+ name += " (%s)" % variant.replace('_', ' ') + + template = Template( + r""" + { + "${clean_name}", + ${initialize}, + ${cleanup}, + ${cb_ptr}, ${cb_count}, ${enabled} + }""" + ).substitute( + clean_name = name, + initialize = self._render_callback(initializer), + cleanup = self._render_callback(self.module.cleanup), + cb_ptr = "_clar_cb_%s" % self.module.name, + cb_count = len(self.module.callbacks), + enabled = int(self.module.enabled) + ) + templates.append(template) + + return ','.join(templates) + + def __init__(self, name): + self.name = name + + self.mtime = None + self.enabled = True + self.modified = False + + def clean_name(self): + return self.name.replace("_", "::") + + def _skip_comments(self, text): + SKIP_COMMENTS_REGEX = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def _replacer(match): + s = match.group(0) + return "" if s.startswith('/') else s + + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) + + def parse(self, contents): + TEST_FUNC_REGEX =r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
t/unit-tests/clar/generate.py new file +71
-0+ + contents = self._skip_comments(contents) + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) + + self.callbacks = [] + self.initializers = [] + self.cleanup = None + + for (declaration, symbol, short_name) in regex.findall(contents): + data = { + "short_name" : short_name, + "declaration" : declaration, + "symbol" : symbol + } + + if short_name.startswith('initialize'): + self.initializers.append(data) + elif short_name == 'cleanup': + self.cleanup = data + else: + self.callbacks.append(data) + + return self.callbacks != [] + + def refresh(self, path): + self.modified = False + + try: + st = os.stat(path) + + # Not modified + if st.st_mtime == self.mtime: + return True + + self.modified = True + self.mtime = st.st_mtime + + with codecs.open(path, encoding='utf-8') as fp: + raw_content = fp.read() + + except IOError: + return False + + return self.parse(raw_content) + +class TestSuite(object): + + def __init__(self, path, output): + self.path = path + self.output = output + + def should_generate(self, path): + if not os.path.isfile(path): + return True + + if any(module.modified for module in self.modules.values()): + return True + + return False + + def find_modules(self): + modules = [] + for root, _, files in os.walk(self.path): + module_root = root[len(self.path):] + module_root = [c for c in module_root.split(os.sep) if c] + + tests_in_module = fnmatch.filter(files, "*.c") + + for test_file in tests_in_module: + full_path = os.path.join(root, test_file) + module_name = "_".join(module_root +[test_file[:-2]]).replace("-", "_")
t/unit-tests/clar/generate.py new file +44
-0+ + modules.append((full_path, module_name)) + + return modules + + def load_cache(self): + path = os.path.join(self.output, '.clarcache') + cache = {} + + try: + fp = open(path, 'rb') + cache = pickle.load(fp) + fp.close() + except (IOError, ValueError): + pass + + return cache + + def save_cache(self): + path = os.path.join(self.output, '.clarcache') + with open(path, 'wb') as cache: + pickle.dump(self.modules, cache) + + def load(self, force = False): + module_data = self.find_modules() + self.modules = {} if force else self.load_cache() + + for path, name in module_data: + if name not in self.modules: + self.modules[name] = Module(name) + + if not self.modules[name].refresh(path): + del self.modules[name] + + def disable(self, excluded): + for exclude in excluded: + for module in self.modules.values(): + name = module.clean_name() + if name.startswith(exclude): + module.enabled = False + module.modified = True + + def suite_count(self): + return sum(max(1, len(m.initializers)) for m in
self.modules.values())t/unit-tests/clar/generate.py new file +3
-0+ + def callback_count(self): + return sum(len(module.callbacks) for module in
self.modules.values())t/unit-tests/clar/generate.py new file +9
-0+ + def write(self): + output = os.path.join(self.output, 'clar.suite') + + if not self.should_generate(output): + return False + + with open(output, 'w') as data: + modules = sorted(self.modules.values(), key=lambda module:module.name)
t/unit-tests/clar/generate.py new file +10
-0+ + for module in modules: + t = Module.DeclarationTemplate(module) + data.write(t.render()) + + for module in modules: + t = Module.CallbacksTemplate(module) + data.write(t.render()) + + suites = "static struct clar_suite _clar_suites[] = {" +','.join(
t/unit-tests/clar/generate.py new file +6
-0+ Module.InfoTemplate(module).render() for module in modules + ) + "\n};\n" + + data.write(suites) + + data.write("static const size_t _clar_suite_count = %d;\n" %
self.suite_count())
+ data.write("static constsize_t_clar_callback_count= %d;\n" %self.callback_count())t/unit-tests/clar/generate.py new file +9
-0+ + self.save_cache() + return True + +if __name__ == '__main__': + from optparse import OptionParser + + parser = OptionParser() + parser.add_option('-f', '--force', action="store_true", dest='force',default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append',
default=[])t/unit-tests/clar/generate.py new file +14
-0+ parser.add_option('-o', '--output', dest='output') + + options, args = parser.parse_args() + if len(args) > 1: + print("More than one path given") + sys.exit(1) + + path = args.pop() if args else '.' + output = options.output or path + suite = TestSuite(path, output) + suite.load(options.force) + suite.disable(options.excluded) + if suite.write(): + print("Written `clar.suite` (%d tests in %d suites)" %(
suite.callback_count(),suite.suite_count()))t/unit-tests/clar/test/.gitignore new file +4
-0@@ -0,0 +1,4 @@ +clar.suite +.clarcache +clar_test +*.ot/unit-tests/clar/test/Makefile new file +39
-0@@ -0,0 +1,39 @@ +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +# +# Set up the path to the clar sources and to the fixtures directory +# +# The fixture path needs to be an absolute path so it can be used +# even after we have chdir'ed into the test directory while testing. +# +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE))) +CLAR_PATH := $(dir $(TEST_DIRECTORY)) +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/ + +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\" + +.PHONY: clean + +# list the objects that go into our test +objects = main.o sample.o + +# build the test executable itself +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects) + +# test object files depend on clar macros +$(objects) : $(CLAR_PATH)clar.h + +# build the clar.suite file of test metadata +clar.suite: + python "$(CLAR_PATH)generate.py" . + +# remove all generated files +clean: + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYMt/unit-tests/clar/test/clar_test.h new file +16
-0@@ -0,0 +1,16 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ +#ifndef __CLAR_TEST__ +#define __CLAR_TEST__ + +/* Import the standard clar helper functions */ +#include "clar.h" + +/* Your custom shared includes / defines here */ +extern int global_test_counter; + +#endif t/unit-tests/clar/test/main.c new file +40
-0@@ -0,0 +1,40 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ + +#include "clar_test.h" + +/* +* Sample main() for clar tests. +* +* You should write your own main routine for clar tests that does specific +* setup and teardown as necessary for your application. The only required +* line is the call to `clar_test(argc, argv)`, which will execute the test +* suite. If you want to check the return value of the test application, +* your main() should return the same value returned by clar_test(). +*/ + +int global_test_counter =0 ; + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ +int ret; + +/* Your custom initialization here */ + global_test_counter =0 ; + +/* Run the test suite */ + ret =clar_test (argc, argv); + +/* Your custom cleanup here */ +cl_assert_equal_i (8 , global_test_counter); + +return ret; +}t/unit-tests/clar/test/main.c.sample new file +13
-0@@ -0,0 +1,27 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ + +#include "clar_test.h" + +/* + * Minimal main() for clar tests. + * + * Modify this with any application specific setup or teardown that youneed.
+ * The only required line is the call toclar_test(argc, argv), which will
+ * execute the test suite. If you want to check the return value of the test
+ * application,main()should return the same value returned byclar_test().
+ */t/unit-tests/clar/test/main.c.sample new file +10
-0+ +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + /* Run the test suite */ + return clar_test(argc, argv); +}t/unit-tests/clar/test/resources/test/file new file +1
-0@@ -0,0 +1 @@ +Filet/unit-tests/clar/test/sample.c new file +84
-0@@ -0,0 +1,84 @@ +#include "clar_test.h" +#include <sys/stat.h> + +static int file_size (const char *filename) +{ +struct stat st; + +if (stat (filename, &st) ==0 ) +return (int )st.st_size; +return -1 ; +} + +void test_sample__initialize (void ) +{ + global_test_counter++; +} + +void test_sample__cleanup (void ) +{ +cl_fixture_cleanup ("test" ); + +cl_assert (file_size ("test/file" ) ==-1 ); +} + +void test_sample__1 (void ) +{ +cl_assert (1 ); +cl_must_pass (0 );/* 0 == success */ +cl_must_fail (-1 );/* <0 == failure */ +cl_must_pass (-1 );/* demonstrate a failing call */ +} + +void test_sample__2 (void ) +{ +cl_fixture_sandbox ("test" ); + +cl_assert (file_size ("test/nonexistent" ) ==-1 ); +cl_assert (file_size ("test/file" ) >0 ); +cl_assert (100 ==101 ); +} + +void test_sample__strings (void ) +{ +const char *actual ="expected" ; +cl_assert_equal_s ("expected" , actual); +cl_assert_equal_s_ ("expected" , actual,"second try with annotation" ); +cl_assert_equal_s_ ("mismatched" , actual,"this one fails" ); +} + +void test_sample__strings_with_length (void ) +{ +const char *actual ="expected" ; +cl_assert_equal_strn ("expected_" , actual,8 ); +cl_assert_equal_strn ("exactly" , actual,2 ); +cl_assert_equal_strn_ ("expected_" , actual,8 ,"with annotation" ); +cl_assert_equal_strn_ ("exactly" , actual,3 ,"this one fails" ); +} + +void test_sample__int (void ) +{ +int value =100 ; +cl_assert_equal_i (100 , value); +cl_assert_equal_i_ (101 , value,"extra note on failing test" ); +} + +void test_sample__int_fmt (void ) +{ +int value =100 ; +cl_assert_equal_i_fmt (022 , value,"%04o" ); +} + +void test_sample__bool (void ) +{ +int value =100 ; +cl_assert_equal_b (1 , value);/* test equality as booleans */ +cl_assert_equal_b (0 , value); +} + +void test_sample__ptr (void ) +{ +const char *actual ="expected" ; +cl_assert_equal_p (actual, actual);/* pointers to same object */ +cl_assert_equal_p (&actual, actual); +}
Re: [PATCH v5 2/9] t: import the clar unit testing framework
In reply to Patrick Steinhardtt/unit-tests/clar/clar.c line 25
Quoted 13 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new file +6
-0@@ -0,0 +1,842 @@ + +#ifdef _WIN32 +# include <windows.h> +# include <io.h> +# include <shellapi.h> +# include <direct.h>
This seems to break Windows build
In file included from t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error: shellapi.h: No such file or directory
89 | #include <shellapi.h>
| ^~~~~~~~~~~~compilation terminated.
https://github.com/git/git/actions/runs/10459342252/job/28963152995
As we have other topics that I do want to do well in 'seen' before merging them to 'next', I'd temporarily drop this topic from 'seen' and push out the rest of the topics.
RE: [PATCH v5 2/9] t: import the clar unit testing framework
Quoted 22 lines · Junio C Hamano
t/unit-tests/clar/clar.c line 25
Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0
-0file mode 100644 index 0000000000..3fc2c76815
t/unit-tests/clar/clar.c new file +6
-0@@ -0,0 +1,842 @@ + +#ifdef _WIN32 +# include <windows.h> +# include <io.h> +# include <shellapi.h> +# include <direct.h> This seems to break Windows build
In file included from
t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error:shellapi.h:
No such file or directory89 | #include <shellapi.h> | ^~~~~~~~~~~~
Quoted 7 lines
compilation terminated.
https://github.com/git/git/actions/runs/10459342252/job/28963152995
As we have other topics that I do want to do well in 'seen' before merging them to 'next', I'd temporarily drop this topic from 'seen'
and push out the rest of the topics.
shellapi.h is not portable. This breaks the NonStop build also.
Re: [PATCH v5 2/9] t: import the clar unit testing framework
Quoted 25 lines · <[email protected]> writes:
Quoted 24 lines · Junio C Hamano
t/unit-tests/clar/clar.c line 25
Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0
-0file mode 100644 index 0000000000..3fc2c76815
t/unit-tests/clar/clar.c new file +6
-0@@ -0,0 +1,842 @@ + +#ifdef _WIN32 +# include <windows.h> +# include <io.h> +# include <shellapi.h> +# include <direct.h> This seems to break Windows build
In file included from
t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error:shellapi.h:
No such file or directory89 | #include <shellapi.h> | ^~~~~~~~~~~~compilation terminated.
Quoted 7 lines
Quoted 5 lines
https://github.com/git/git/actions/runs/10459342252/job/28963152995
As we have other topics that I do want to do well in 'seen' before merging them to 'next', I'd temporarily drop this topic from 'seen'
and push out the rest of the topics.
shellapi.his not portable. This breaks the NonStop build also.
Even inside "#ifdef _WIN32"?
RE: [PATCH v5 2/9] t: import the clar unit testing framework
Quoted 30 lines · Junio C Hamano
Quoted 18 lines · <[email protected]> writes:
Quoted 17 lines · Junio C Hamano
t/unit-tests/clar/clar.c line 25
Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0
-0file mode 100644 index 0000000000..3fc2c76815
t/unit-tests/clar/clar.c new file +6
-0@@ -0,0 +1,842 @@ + +#ifdef _WIN32 +# include <windows.h> +# include <io.h> +# include <shellapi.h> +# include <direct.h> This seems to break Windows build
Quoted 2 lines
Quoted 2 lines
In file included from
t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error:
shellapi.h:Quoted 7 lines
Quoted 7 lines
No such file or directory
89 | #include <shellapi.h> | ^~~~~~~~~~~~compilation terminated.
https://github.com/git/git/actions/runs/10459342252/job/28963152995
Quoted 7 lines
Quoted 5 lines
Quoted 3 lines
As we have other topics that I do want to do well in 'seen' before merging them to 'next', I'd temporarily drop this topic from 'seen' and push out the rest of the topics.
shellapi.his not portable. This breaks the NonStop build also.Even inside "#ifdef
_WIN32"?
Good point, but I did not see that in the prior review. My apologies.
Re: [PATCH v5 2/9] t: import the clar unit testing framework
In reply to Junio C HamanoQuoted 22 lines · Junio C Hamano
t/unit-tests/clar/clar.c line 25
Quoted 13 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new file +6
-0@@ -0,0 +1,842 @@ + +#ifdef _WIN32 +# include <windows.h> +# include <io.h> +# include <shellapi.h> +# include <direct.h> This seems to break Windows build
In file included from
t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error:shellapi.h: No such file or directory89 | #include <shellapi.h> | ^~~~~~~~~~~~
Quoted 7 lines
compilation terminated.
https://github.com/git/git/actions/runs/10459342252/job/28963152995
As we have other topics that I do want to do well in 'seen' before merging them to 'next', I'd temporarily drop this topic from 'seen' and push out the rest of the topics.
Makes sense. I (or rather Dscho, thanks!) will have a deeper look at Windows and then also rebase the branch to fix conflicts with the updated tests in "master".
Patrick
PATCH v5 3/9 t/clar: fix compatibility with NonStop
In reply to Patrick SteinhardtThe NonStop platform does not have mkdtemp() available, which we rely on in build_sandbox_path(). Fix this issue by using mktemp() and mkdir() instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
--- t/unit-tests/clar/clar/sandbox.h | 6 ++++++ 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)if (_mktemp (_clar_path) ==NULL )return -1 ; +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#elif defined(__TANDEM) +if (mktemp (_clar_path) ==NULL ) +return -1 ; +if (mkdir (_clar_path,0700 ) !=0 )return -1 ;#elif defined(_WIN32)
Signature · 1 line
2.46.0.46.g406f326d27.dirty
PATCH v5 4/9 Makefile: fix sparse dependency on GENERATED_H
In reply to Patrick SteinhardtThe "check" Makefile target is essentially an alias around the "sparse" target. The one difference though is that it will tell users to instead run the "test" target in case they do not have sparse(1) installed, as chances are high that they wanted to execute the test suite rather than doing semantic checks.
But even though the "check" target ultimately just ends up executing make sparse, it still depends on our generated headers. This does not make any sense though: they are irrelevant for the "test" target advice, and if these headers are required for the "sparse" target they must be declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as concurrent builds may then end up generating the headers and running sparse concurrently. Instead, we make them a dependency of the specific objects. While that is overly broad, it does ensure correct ordering. The alternative, specifying which file depends on what generated header explicitly, feels rather unmaintainable.
--- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
@@ -3253,7 +3253,7 @@ check-sha1:: t/helper/test-tool$X SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) -$(SP_OBJ): %.sp: %.c %.o +$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ -Wsparse-error \ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3294,7 +3294,7 @@ style: git clang-format --style file --diff --extensions c,h .PHONY: check -check: $(GENERATED_H) +check: @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \
Signature · 1 line
2.46.0.46.g406f326d27.dirty
PATCH v5 5/9 Makefile: make hdr-check depend on generated headers
In reply to Patrick SteinhardtThe "hdr-check" Makefile target compiles each of our headers as a standalone code unit to ensure that they are not missing any type declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework, which will have the effect that some headers start depending on generated ones. While we could declare that dependency explicitly, it does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects depend on all of our generated headers. While again overly broad, it is easy to maintain and generating headers is not an expensive thing to do anyway.
--- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
@@ -3283,7 +3283,7 @@ HCC = $(HCO:hco=hcc) @echo '#include "git-compat-util.h"' >$@ @echo '#include "$<"' >>$@ -$(HCO): %.hco: %.hcc FORCE +$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE $(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $< .PHONY: hdr-check $(HCO)
Signature · 1 line
2.46.0.46.g406f326d27.dirty
PATCH v5 6/9 Makefile: do not use sparse on third-party sources
In reply to Patrick SteinhardtWe have several third-party sources in our codebase that we have imported from upstream projects. These sources are mostly excluded from our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
--- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
@@ -3251,7 +3251,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) check-sha1:: t/helper/test-tool$X t/helper/test-sha1.sh -SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) +SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS))) +SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC)) $(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
Signature · 1 line
2.46.0.46.g406f326d27.dirty
PATCH v5 7/9 Makefile: wire up the clar unit testing framework
In reply to Patrick SteinhardtWire up the clar unit testing framework by introducing a new "unit-tests" executable. In contrast to the existing framework, this will result in a single executable for all test suites. The ability to pick specific tests to execute is retained via functionality built into the clar itself.
Note that we need to be a bit careful about how we need to invalidate our Makefile rules. While we obviously have to regenerate the clar suite when our test suites change, we also have to invalidate it in case any of the test suites gets removed. We do so by using our typical pattern of creating a GIT-TEST-SUITES file that gets updated whenever the set of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test functions are neither static, nor do they have external declarations. This is because they are getting parsed via "generate.py", which then creates the external generations that get populated into an array. These declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes" errors from our compiler for each of these test functions. To fix those errors, we extract the extern declarations from "clar.suite" and put them into a standalone header that then gets included by each of our unit tests. This gets rid of compiler warnings for every function which has been extracted by "generate.py". More importantly though, it does _not_ get rid of warnings in case a function really isn't being used by anything. Thus, it would cause a compiler error if a function name was mistyped and thus not picked up by "generate.py".
--- .gitignore | 1 + Makefile | 36 +++++++++++++++++++++--- t/Makefile | 1 + t/unit-tests/.gitignore | 2 ++ t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++ t/unit-tests/unit-test.c | 18 ++++++++++++ t/unit-tests/unit-test.h | 3 ++ 7 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 t/unit-tests/clar-generate.awk create mode 100644 t/unit-tests/unit-test.c create mode 100644 t/unit-tests/unit-test.h
.gitignore +1 -0
@@ -9,6 +9,7 @@ /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-SPATCH-DEFINES +/GIT-TEST-SUITES /GIT-USER-AGENT /GIT-VERSION-FILE /bin-wrappers/
Makefile +32 -4
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a GENERATED_H += command-list.h GENERATED_H += config-list.h GENERATED_H += hook-list.h +GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h +GENERATED_H += $(UNIT_TEST_DIR)/clar.suite .PHONY: generated-hdrs generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash
@@ -2713,6 +2720,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o
@@ -3215,7 +3223,7 @@ endif test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) -all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) +all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template)
@@ -3647,7 +3655,7 @@ endif artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \ GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \ - $(UNIT_TEST_PROGS) $(MOFILES) + $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES) $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \ SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3703,6 +3711,7 @@ cocciclean: clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) + $(RM) GIT-TEST-SUITES $(RM) po/git.pot po/git-core.pot $(RM) git.res $(RM) $(OBJECTS)
@@ -3862,7 +3871,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) +GIT-TEST-SUITES: FORCE + @FLAGS='$(UNIT_TESTS_SUITES)'; \ + if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \ + echo >&2 " * new test suites"; \ + echo "$$FLAGS" >GIT-TEST-SUITES; \ + fi + +$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES + $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \ + sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \ + done >$@ +$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h + $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite +$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h +$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) +$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS + $(call mkdir_p_parent_template) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + .PHONY: build-unit-tests unit-tests -build-unit-tests: $(UNIT_TEST_PROGS) -unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X +build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) +unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests
t/Makefile +1 -0
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) +UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X) UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS)) UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
t/unit-tests/.gitignore +2 -0
@@ -1 +1,3 @@ /bin +/clar.suite +/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0
50 lines not on this page · raw message
t/unit-tests/unit-test.c new file +18 -0
@@ -0,0 +1,18 @@ +#include "unit-test.h" + +int cmd_main (int argc,const char **argv) +{ +const char **argv_copy; +int ret; + +/* Append the "-t" flag such that the tests generate TAP output. */ +ALLOC_ARRAY (argv_copy, argc +2 ); +COPY_ARRAY (argv_copy, argv, argc); + argv_copy[argc++] ="-t" ; + argv_copy[argc] =NULL ; + + ret =clar_test (argc, (char **) argv_copy); + +free (argv_copy); +return ret; +}
t/unit-tests/unit-test.h new file +3 -0
@@ -0,0 +1,3 @@ +#include "git-compat-util.h" +#include "clar/clar.h" +#include "clar-decls.h"
Signature · 1 line
2.46.0.46.g406f326d27.dirty
PATCH v5 8/9 t/unit-tests: convert strvec tests to use clar
In reply to Patrick SteinhardtConvert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22The binary also supports some parameters that allow us to run only a subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [options] Options:
-sname Run only the suite with name (can go to individual test name)
-iname Include the suite with name
-xname Exclude the suite with name
-v Increase verbosity (show suite names)
-q Only report tests that had an error
-Q Quit as soon as a test fails
-t Display results in tap format
-l Print suite names
-r[filename] Write summary file (to the optional filename)
Furthermore, running make unit-tests runs the binary along with all the other unit tests we have.
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
t/unit-tests/unit-test.c | 3 +-
3 files changed, 45 insertions(+), 79 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)Makefile +1 -1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1353,7 +1354,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged UNIT_TEST_PROGRAMS += t-reftable-record UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strcmp-offset -UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-trailer UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +43 -76
261 lines not on this page · raw message
t/unit-tests/unit-test.c +1 -2
@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)int ret;/* Append the "-t" flag such that the tests generate TAP output. */ -ALLOC_ARRAY (argv_copy, argc +2 ); +ALLOC_ARRAY (argv_copy, argc +1 );COPY_ARRAY (argv_copy, argv, argc); argv_copy[argc++] ="-t" ; - argv_copy[argc] =NULL ; ret =clar_test (argc, (char **) argv_copy);
Signature · 1 line
2.46.0.46.g406f326d27.dirty
Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
Hi Patrick
t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12
Quoted 13 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +1
-2@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)int ret;/* Append the "-t" flag such that the tests generate TAP output. */ -ALLOC_ARRAY (argv_copy, argc +2 ); +ALLOC_ARRAY (argv_copy, argc +1 );COPY_ARRAY (argv_copy, argv, argc); argv_copy[argc++] ="-t" ; - argv_copy[argc] =NULL ;
I'm confused by this - it looks like it belongs in the previous patch but why are we deleting the line that adds the terminating NULL to argv in the first place?
Thanks
Phillip
Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
Quoted 12 lines · Phillip Wood
Hi Patrick
Quoted 9 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +0
-0@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)int ret;/* Append the "-t" flag such that the tests generate TAP output. */
t/unit-tests/unit-test.c +0
-1-ALLOC_ARRAY (argv_copy, argc +2 );
Quoted 8 lines
t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12
Quoted 4 lines
t/unit-tests/unit-test.c +1
-1+ALLOC_ARRAY (argv_copy, argc +1 );COPY_ARRAY (argv_copy, argv, argc); argv_copy[argc++] ="-t" ; - argv_copy[argc] =NULL ;I'm confused by this - it looks like it belongs in the previous patch but why are we deleting the line that adds the terminating NULL to argv in the first place?
I agree with you that it belongs to the previous step. clar_test(), unlike the usual convention used by main(), works solely with argc to tell where the list of argument ends, without requiring argv[] to be an NULL terminated array. I do not know if clar _promises_ that as the calling convention to its users, but if it does so, and if we want to take advantage of that promise and leave the terminating NULL out of argv[], we should do so from the beginning.
A NULL-terminated argv[], as long as we make sure argc only counts the real arguments without counting that terminating NULL, would not harm the clar_test(argc, argv) call, so I actually do not mind the way this caller was originally written, with terminating NULL. It looks a lot more familiar, even though we may be wasting memory at the end of argv[] array.
Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
Quoted 23 lines · Junio C Hamano
Quoted 12 lines · Phillip Wood
Hi Patrick
Quoted 9 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +0
-0@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)int ret;/* Append the "-t" flag such that the tests generate TAP output. */
t/unit-tests/unit-test.c +0
-1-ALLOC_ARRAY (argv_copy, argc +2 );Quoted 8 lines
t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12
Quoted 4 lines
t/unit-tests/unit-test.c +1
-1+ALLOC_ARRAY (argv_copy, argc +1 );COPY_ARRAY (argv_copy, argv, argc); argv_copy[argc++] ="-t" ; - argv_copy[argc] =NULL ;I'm confused by this - it looks like it belongs in the previous patch but why are we deleting the line that adds the terminating NULL to argv in the first place?
Quoted 7 lines
I agree with you that it belongs to the previous step.
clar_test(), unlike the usual convention used bymain(), works solely with argc to tell where the list of argument ends, without requiring argv[] to be an NULL terminated array. I do not know if clar _promises_ that as the calling convention to its users, but if it does so, and if we want to take advantage of that promise and leave the terminating NULL out of argv[], we should do so from the beginning.
Agreed.
Quoted 6 lines
A NULL-terminated argv[], as long as we make sure argc only counts the real arguments without counting that terminating NULL, would not harm the
clar_test(argc, argv)call, so I actually do not mind the way this caller was originally written, with terminating NULL. It looks a lot more familiar, even though we may be wasting memory at the end of argv[] array.
Okay. It's not necessary, but I don't mind it much, either.
Patrick
PATCH v5 9/9 t/unit-tests: convert ctype tests to use clar
In reply to Patrick SteinhardtConvert the ctype tests to use the new clar unit testing framework.
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
2 files changed, 59 insertions(+), 14 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)Makefile +1 -1
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += ctype UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o -UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash UNIT_TEST_PROGRAMS += t-mem-pool
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +58 -13
97 lines not on this page · raw message
Signature · 1 line
2.46.0.46.g406f326d27.dirty
Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
Hi Patrick
Quoted 5 lines · Patrick Steinhardt
#define TEST_CHAR_CLASS(class, string) do { \ size_t len = ARRAY_SIZE(string) - 1 + \ BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \ BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +0
-1-int skip =test__run_begin (); \
t/unit-tests/ctype.c line 8
Quoted 10 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +2
-8-if (!skip) { \ -for (int i =0 ; i <256 ; i++) { \ - if (!check_int(class(i), ==, !!memchr(string, i, len)))\ -test_msg (" i: 0x%02x" , i); \ - } \ -check (!class (EOF )); \ - } \ - test__run_end(!skip, TEST_LOCATION(), #class " works"); \ +for (int i =0 ; i <256 ; i++) \ +cl_assert_equal_i (class (i), !!memchr (string, i, len)); \
If this fails how are we supposed to know which character it was checking?
Thanks
Phillip
Quoted 80 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +55
-3+cl_assert (!class (EOF )); \ }while (0 ) #define DIGIT "0123456789"@@ -33,21 +27,72 @@"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \"\x7f" -int cmd_main(int argc, const char **argv) { +void test_ctype__isspace(void) +{TEST_CHAR_CLASS (isspace," \n\r\t" ); +} + +void test_ctype__isdigit (void ) +{TEST_CHAR_CLASS (isdigit,DIGIT ); +} + +void test_ctype__isalpha (void ) +{ TEST_CHAR_CLASS(isalpha, LOWER UPPER); +} + +void test_ctype__isalnum (void ) +{ TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT); +} + +void test_ctype__is_glob_special (void ) +{TEST_CHAR_CLASS (is_glob_special,"*?[\\" ); +} + +void test_ctype__is_regex_special (void ) +{TEST_CHAR_CLASS (is_regex_special,"$()*+.?[\\^{|" ); +} + +void test_ctype__is_pathspec_magic (void ) +{TEST_CHAR_CLASS (is_pathspec_magic,"!\"#%&',-/:;<=>@_`~" ); +} + +void test_ctype__isascii (void ) +{TEST_CHAR_CLASS (isascii,ASCII ); +} + +void test_ctype__islower (void ) +{TEST_CHAR_CLASS (islower,LOWER ); +} + +void test_ctype__isupper (void ) +{TEST_CHAR_CLASS (isupper,UPPER ); +} + +void test_ctype__iscntrl (void ) +{TEST_CHAR_CLASS (iscntrl,CNTRL ); +} + +void test_ctype__ispunct (void ) +{TEST_CHAR_CLASS (ispunct,PUNCT ); +} + +void test_ctype__isxdigit (void ) +{TEST_CHAR_CLASS (isxdigit,DIGIT "abcdefABCDEF" ); - TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); +} -return test_done (); +void test_ctype__isprint (void ) +{ + TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); }
Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
Quoted 21 lines · Phillip Wood
Hi Patrick
Quoted 5 lines · Patrick Steinhardt
#define TEST_CHAR_CLASS(class, string) do { \ size_t len = ARRAY_SIZE(string) - 1 + \ BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \ BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +0
-1-int skip =test__run_begin (); \t/unit-tests/ctype.c line 8
Quoted 10 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +2
-8-if (!skip) { \ -for (int i =0 ; i <256 ; i++) { \ - if (!check_int(class(i), ==, !!memchr(string, i, len)))\ -test_msg (" i: 0x%02x" , i); \ - } \ -check (!class (EOF )); \ - } \ - test__run_end(!skip, TEST_LOCATION(), #class " works"); \ +for (int i =0 ; i <256 ; i++) \ +cl_assert_equal_i (class (i), !!memchr (string, i, len)); \If this fails how are we supposed to know which character it was checking?
Quoted 1 line
Thanks
I'll introduce a new function cl_failf() that allows us to print information like this.
Patrick
Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
In reply to Patrick SteinhardtQuoted 8 lines · Patrick Steinhardt
Convert the ctype tests to use the new clar unit testing framework.
Signed-off-by Patrick Steinhardt <[email protected]>--- Makefile | 2 +- t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 14 deletions(-) rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
In 'next' we use the if_test stuff to simplify the ctype unit tests. This gives a good example to illustrate the strengths and weakness of these approaches. I resolved the conflict in 'seen' with this topic in favor of clar tests so that it becomes easier to compare.
Quoted 17 lines
#define
TEST_CHAR_CLASS(class, string)do { \t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +3
-9size_t len =ARRAY_SIZE (string) -1 + \BUILD_ASSERT_OR_ZERO (ARRAY_SIZE (string) >0 ) + \BUILD_ASSERT_OR_ZERO (sizeof (string[0 ]) ==sizeof (char )); \ -int skip =test__run_begin (); \ -if (!skip) { \ -for (int i =0 ; i <256 ; i++) { \ - if (!check_int(class(i), ==, !!memchr(string, i, len)))\ -test_msg (" i: 0x%02x" , i); \ - } \ -check (!class (EOF )); \ - } \ - test__run_end(!skip, TEST_LOCATION(), #class " works"); \ +for (int i =0 ; i <256 ; i++) \ +cl_assert_equal_i (class (i), !!memchr (string, i, len)); \ +cl_assert (!class (EOF )); \ }while (0 )
Hi Patrick
Quoted 4 lines · Patrick Steinhardt
Hi,
this is the fifth version of my patch series that introduces the clar testing framework for our unit tests.
Thanks for working on this, I'm broadly in favor of this change. I like the way it keeps each test as a function and adds automatic test registration with support for setup and teardown functions. I am keen though to keep an emphasis on good diagnostic messages when tests fail. Looking at the conversions in this series all of the test_msg() lines that provide useful debugging context are removed. I'm not sure using yaml to report errors rather than human readable messages is an improvement either.
I wonder if we want to either improve the assertions offered by clar or write our own. I find the names of the cl_assert_equal_?() functions are a bit cumbersome. The aim of the check_* names was to try and be both concise and descriptive. Adding our own check_* macros on top of clar would also make it easier to port our existing tests.
Here are some thought having read through the assertion and error reporting code:
- As I think you've pointed out elsewhere there are no equivalents for check_int(a, <|<=|>|>=, b) so we're forced to use cl_assert() and forego the better diagnostic messages that come from a dedicated comparison macro. We should fix this as a priority.
- cl_assert_equal_i() casts its arguments to int whereas check_int() and check_uint() are careful to avoid truncation and keep the original signedness (if that's a word). I think that's unlikely to be a problem with our current test but could trip us up in the future.
- cl_assert_equal_s() prints each argument as-is. This means that it passes NULL arguments through to snprintf() which is undefined according to the C standard. Compare this to check_str() that is NULL safe and is careful to escape control characters and add delimiters to the beginning and end of the string to make it obvious when a string contains leading or trailing whitespace.
- The cl_assert_equal_?() macros lack type safety for the arguments being compared as they are wrappers around a variadic function. That could be fixed by having each macros wrap a dedicated function that wraps clar__fail().
- There is no equivalent of test_todo() to mark assertions that are expected to fail. We're not using that yet in our tests but our experience with the integration tests suggests that we are likely to want this in the future.
- To me the "sandbox" feature is mis-named as it does not provide any confinement. It is instead a useful mechanism for running a test in a temporary directory created from a template.
- There are no checks for failing memory allocations - the return value of calloc() and strdup() are used without checking for NULL.
- The use of longjmp is a bit of a double edged sword as it makes it easy to leak resources on test failures.
Best Wishes
Phillip
Quoted 301 lines
Changes compared to v4:
- The whitespace fixes have been merged upstream, so I've updated the embedded copy of clar and dropped the subsequent patch that fixed them in our copy. The NonStop compatibility fixes have not yet been merged as the pull request needs some more work.
- Both "
clar-decls.h" and "clar.suite" are now part ofGENERATED_H. This brings removal of these files via "make clean" for free.- The "sparse" target already depends on
GENERATED_H, but in a broken way. I've fixed that in a new commit.- The "sparse" target no longer checks external sources, including the clar sources.
- The "hdr-check" target now depends on
GENERATED_H, as well. This avoids having to manually wire up dependencies on generated headers per file, which seems rather unmaintainable to me.With this, the "hdr-check" and "sparse" targets all work on my machine now.
Thanks!
Patrick
Patrick Steinhardt (9):
t: do not passGIT_TEST_OPTSto unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency onGENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar.gitignore | 1 + Documentation/technical/unit-tests.txt | 2 + Makefile | 53 +- t/Makefile | 4 +- t/run-test.sh | 2 +- t/unit-tests/.gitignore | 2 + t/unit-tests/clar-generate.awk | 50 ++ t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 522 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 159 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ t/unit-tests/{t-ctype.c => ctype.c} | 71 +- t/unit-tests/{t-strvec.c => strvec.c} | 119 ++- t/unit-tests/unit-test.c | 17 + t/unit-tests/unit-test.h | 3 + 29 files changed, 3166 insertions(+), 102 deletions(-)create mode 100644
t/unit-tests/clar-generate.awkcreate mode 100644t/unit-tests/clar/.github/workflows/ci.ymlcreate mode 100644 t/unit-tests/clar/COPYING
create mode 100644t/unit-tests/clar/README.mdcreate mode 100644t/unit-tests/clar/clar.c
create mode 100644t/unit-tests/clar/clar.h
create mode 100644t/unit-tests/clar/clar/fixtures.hcreate mode 100644t/unit-tests/clar/clar/fs.hcreate mode 100644t/unit-tests/clar/clar/print.hcreate mode 100644t/unit-tests/clar/clar/sandbox.hcreate mode 100644t/unit-tests/clar/clar/summary.hcreate mode 100755t/unit-tests/clar/generate.pycreate mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644t/unit-tests/clar/test/clar_test.hcreate mode 100644t/unit-tests/clar/test/main.ccreate mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644t/unit-tests/clar/test/sample.crenamet/unit-tests/{t-ctype.c=>ctype.c} (71%) renamet/unit-tests/{t-strvec.c=>strvec.c} (54%) create mode 100644t/unit-tests/unit-test.c
create mode 100644t/unit-tests/unit-test.hRange-diff against v4:
1:086dd728a7! 1:832dc0496ft: do not passGIT_TEST_OPTSto unit tests with prove
@@ Commit message
environment variable. Like this, we can conditionally forward it to our test scripts, only.+ Signed-off-by: Patrick Steinhardt <[email protected]>
+
## t/Makefile ##
@@ t/Makefile: failed:
test -z "$$failed" || $(MAKE) $$failed
2:5c22e0b3b9! 2: 3690607933 t: import the clar unit testing framework@@ Metadata ## Commit message ## t: import the clar unit testing framework - Import the clar unit testing framework at commit faa8419 (Merge pull - request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework + Import the clar unit testing framework at commit 1516124 (Merge pull + request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits. Signed-off-by: Patrick Steinhardt <[email protected]> @@ t/unit-tests/clar/clar/fs.h (new) + ERROR_PATH_NOT_FOUND == last_error) + return 0; + -+ Sleep(RM_RETRY_DELAY * retries * retries); ++ Sleep(RM_RETRY_DELAY * retries * retries); + } + while (retries++ <= RM_RETRY_COUNT); + @@ t/unit-tests/clar/clar/sandbox.h (new) + static const size_t var_count = 5; + static const char *env_vars[] = { + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" -+ }; ++ }; + -+ size_t i; ++ size_t i; + + for (i = 0; i < var_count; ++i) { + const char *env = getenv(env_vars[i]); @@ t/unit-tests/clar/clar/sandbox.h (new) +{ + return _clar_path; +} -+ ## t/unit-tests/clar/clar/summary.h (new) ## @@ @@ t/unit-tests/clar/generate.py (new) + suite.disable(options.excluded) + if suite.write(): + print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) -+ ## t/unit-tests/clar/test/.gitignore (new) ## @@ @@ t/unit-tests/clar/test/.gitignore (new) +.clarcache +clar_test +*.o -+ ## t/unit-tests/clar/test/Makefile (new) ## @@ 4: 75e097dfa4 = 3: db53673294 t/clar: fix compatibility with NonStop 3: e0f99874cc ! 4: b6199c88dd t/clar: fix whitespace errors@@ Metadata
Author: Patrick Steinhardt <[email protected]>## Commit message ##
- t/clar: fix whitespace errors
-
- Fix whitespace errors in the clar that make git-apply(1) unhappy. This
- has been cherry-picked from the upstream pull request at [1].
-
- [1]: https://github.com/clar-test/clar/pull/97
+Makefile: fix sparse dependency onGENERATED_H
+
+ The "check"Makefiletarget is essentially an alias around the "sparse"
+ target. The one difference though is that it will tell users to instead
+ run the "test" target in case they do not have sparse(1) installed, as
+ chances are high that they wanted to execute the test suite rather than
+ doing semantic checks.
+
+ But even though the "check" target ultimately just ends up executing
+make sparse, it still depends on our generated headers. This does not
+ make any sense though: they are irrelevant for the "test" target advice,
+ and if these headers are required for the "sparse" target they must be
+ declared as a dependency on the aliased target, not the alias.
+
+ But even moving the dependency to the "sparse" target is wrong, as
+ concurrent builds may then end up generating the headers and running
+ sparse concurrently. Instead, we make them a dependency of the specific
+ objects. While that is overly broad, it does ensure correct ordering.
+ The alternative, specifying which file depends on what generated header
+ explicitly, feels rather unmaintainable.Signed-off-by Patrick Steinhardt <[email protected]>- ##
t/unit-tests/clar/clar/fs.h##
-@@t/unit-tests/clar/clar/fs.h:fs_rm_wait(WCHAR *_wpath)
-ERROR_PATH_NOT_FOUND==last_error)
- return 0;
-
--Sleep(RM_RETRY_DELAY * retries * retries);
-+Sleep(RM_RETRY_DELAY * retries * retries);
- }
- while (retries++ <=RM_RETRY_COUNT);
+ ##Makefile##
+@@Makefile: check-sha1:: t/helper/test-tool$X-
- ##t/unit-tests/clar/clar/sandbox.h##
-@@t/unit-tests/clar/clar/sandbox.h:find_tmp_path(char *buffer, size_t length)
- static constsize_tvar_count= 5;
- static const char *env_vars[] = {
- "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
-- };
-+ };
+SP_OBJ= $(patsubst %.o,%.sp,$(OBJECTS))--
size_ti;
-+size_ti;
+-$(SP_OBJ): %.sp: %.c %.o
++$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
+ $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
+ -Wsparse-error \
+ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
+@@Makefile: style:
+ git clang-format --style file --diff --extensions c,h- for (i = 0; i <
var_count; ++i) {
- const char *env =getenv(env_vars[i]);
-@@t/unit-tests/clar/clar/sandbox.h: const char*clar_sandbox_path(void)
- {
- return_clar_path;
- }
--
-
- ##t/unit-tests/clar/generate.py##
-@@t/unit-tests/clar/generate.py: def write(self):
-suite.disable(options.excluded)
- ifsuite.write():
- print("Writtenclar.suite(%d tests in %d suites)" % (suite.callback_count(),suite.suite_count()))
--
-
- ## t/unit-tests/clar/test/.gitignore ##
-@@ t/unit-tests/clar/test/.gitignore: clar.suite
- .clarcache
-clar_test
- *.o
--
+ .PHONY: check
+-check: $(GENERATED_H)
++check:
+ @if sparse; \
+ then \
+ echo >&2 "Use 'make sparse' instead"; \-: ---------- > 5: 06364b2b72 Makefile: make hdr-check depend on generated headers -: ---------- > 6: 88ea94ce16 Makefile: do not use sparse on third-party sources 5: 5b8a64ae79 ! 7: 05bcb5bef6 Makefile: wire up the clar unit testing framework @@ .gitignore /bin-wrappers/ ## Makefile ## +@@ Makefile: REFTABLE_TEST_LIB = reftable/libreftable_test.a + GENERATED_H += command-list.h + GENERATED_H += config-list.h + GENERATED_H += hook-list.h ++GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h ++GENERATED_H += $(UNIT_TEST_DIR)/clar.suite + + .PHONY: generated-hdrs + generated-hdrs: $(GENERATED_H) @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% @@ Makefile: endif bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template) -@@ Makefile: CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H)) - HCO = $(patsubst %.h,%.hco,$(CHK_HDRS)) - HCC = $(HCO:hco=hcc) - -+$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h - %.hcc: %.h - @echo '#include "git-compat-util.h"' >$@ - @echo '#include "$<"' >>$@ @@ Makefile: endif artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \ @@ Makefile: cocciclean: clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) -+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h ++ $(RM) GIT-TEST-SUITES $(RM) po/git.pot po/git-core.pot $(RM) git.res $(RM) $(OBJECTS) 6: bc4e23d666 = 8: 8f56b4d626 t/unit-tests: convert strvec tests to use clar 7: 0a7fe8775a = 9: ca09d19fd5 t/unit-tests: convert ctype tests to use clarbase-commit:
406f326d271e0bacecdb00425422c5fa3f314930
Quoted 23 lines · Phillip Wood
Hi Patrick
Quoted 4 lines · Patrick Steinhardt
Hi,
this is the fifth version of my patch series that introduces the clar testing framework for our unit tests.
Thanks for working on this, I'm broadly in favor of this change. I like the way it keeps each test as a function and adds automatic test registration with support for setup and teardown functions. I am keen though to keep an emphasis on good diagnostic messages when tests fail. Looking at the conversions in this series all of the
test_msg()lines that provide useful debugging context are removed. I'm not sure using yaml to report errors rather than human readable messages is an improvement either.I wonder if we want to either improve the assertions offered by clar or write our own. I find the names of the cl_assert_equal_?() functions are a bit cumbersome. The aim of the check_* names was to try and be both concise and descriptive. Adding our own check_* macros on top of clar would also make it easier to port our existing tests.
Quoted 7 lines
Here are some thought having read through the assertion and error reporting code:
- As I think you've pointed out elsewhere there are no equivalents for
check_int(a, <|<=|>|>=, b)so we're forced to usecl_assert()and forego the better diagnostic messages that come from a dedicated comparison macro. We should fix this as a priority.
Agreed, this one also feels rather limiting to me. Are you okay with me doing this as a follow-up in case this series lands?
Quoted 5 lines
-
cl_assert_equal_i()casts its arguments to int whereascheck_int()andcheck_uint()are careful to avoid truncation and keep the original signedness (if that's a word). I think that's unlikely to be a problem with our current test but could trip us up in the future.
Yeah. If it ever becomes a problem we can likely just introduce something like cl_assert_equal_u() to achieve the same for unsigned. Both should probably end up casting to intmax_t and uintmax_t, respectively.
Quoted 6 lines
-
cl_assert_equal_s()prints each argument as-is. This means that it passes NULL arguments through tosnprintf()which is undefined according to the C standard. Compare this tocheck_str()that is NULL safe and is careful to escape control characters and add delimiters to the beginning and end of the string to make it obvious when a string contains leading or trailing whitespace.
Good point indeed, and something I'm happy to fix upstream.
Quoted 4 lines
- The cl_assert_equal_?() macros lack type safety for the arguments being compared as they are wrappers around a variadic function. That could be fixed by having each macros wrap a dedicated function that wraps
clar__fail().
Some of them do indeed, others generate issues. I don't think we have to have dedicated functions, but could do something about this with __attribute__((format (printf, ...))).
Quoted 4 lines
- There is no equivalent of
test_todo()to mark assertions that are expected to fail. We're not using that yet in our tests but our experience with the integration tests suggests that we are likely to want this in the future.
Heh, funny that you mention this. I had this discussion some 6 years ago I think, where I also mentioned that this should exist as a feature. In any case, I agree.
Quoted 3 lines
- To me the "sandbox" feature is mis-named as it does not provide any confinement. It is instead a useful mechanism for running a test in a temporary directory created from a template.
I guess we'll either just have to not use it or ignore that it's named a bit awkwardly. Changing this in clar probably wouldn't work well because other projects may depend on it.
Quoted 2 lines
- There are no checks for failing memory allocations - the return value of
calloc()andstrdup()are used without checking for NULL.
I'll commit to fixing this upstream if this lands.
Quoted 2 lines
- The use of longjmp is a bit of a double edged sword as it makes it easy to leak resources on test failures.
I have to say that this is one of the best features of the clar to me. The current test framework we use doesn't, which in theory requires you to always return whenever there was an error. But that results in code that is both awful to read and write, so for most of the tests simply don't bother at all. And consequently, the tests are quite likely to cause segfaults once one of the checks fails because we didn't abort running the testcase, but things are broken.
In practice, I'd claim that you don't typically care all that much about memory leaks once your basic assertions start to fail.
So, things that need addressing and that I'm happy to do as follow ups:
- Introduce functions that compare integers.
- Improve type safety of the cl_assert_equal_?() macros.
- Adapt cl_assert_equal_s() to handle NULL pointers.
- Introduce checks for failing memory allocations.
Nice to have would be support for known-failing tests.
Patrick
Hi Patrick
Quoted 5 lines · Patrick Steinhardt
Quoted 4 lines · Phillip Wood
Hi Patrick
On 16/08/2024 08:04, Patrick Steinhardt wrote:
Quoted 7 lines
Quoted 4 lines
- As I think you've pointed out elsewhere there are no equivalents for
check_int(a, <|<=|>|>=, b)so we're forced to usecl_assert()and forego the better diagnostic messages that come from a dedicated comparison macro. We should fix this as a priority.Agreed, this one also feels rather limiting to me. Are you okay with me doing this as a follow-up in case this series lands?
Yes
Quoted 10 lines
Quoted 5 lines
-
cl_assert_equal_i()casts its arguments to int whereascheck_int()andcheck_uint()are careful to avoid truncation and keep the original signedness (if that's a word). I think that's unlikely to be a problem with our current test but could trip us up in the future.Yeah. If it ever becomes a problem we can likely just introduce something like
cl_assert_equal_u()to achieve the same for unsigned. Both should probably end up casting tointmax_tanduintmax_t, respectively.
Supporting wider arguments would make sense. At the moment clar__assert_equal() does not support PRIiMAX, only the non-standard PRIuZ.
Quoted 8 lines
Quoted 6 lines
-
cl_assert_equal_s()prints each argument as-is. This means that it passes NULL arguments through tosnprintf()which is undefined according to the C standard. Compare this tocheck_str()that is NULL safe and is careful to escape control characters and add delimiters to the beginning and end of the string to make it obvious when a string contains leading or trailing whitespace.Good point indeed, and something I'm happy to fix upstream.
That's great
Quoted 8 lines
Quoted 4 lines
- The cl_assert_equal_?() macros lack type safety for the arguments being compared as they are wrappers around a variadic function. That could be fixed by having each macros wrap a dedicated function that wraps
clar__fail().Some of them do indeed, others generate issues. I don't think we have to have dedicated functions, but could do something about this with
__attribute__((format (printf, ...))).
I wondered about suggesting '__attribute__((format (printf, ...)))' but we'd need to double up the format argument in order to use it which is kind of messy. At the moment we pass "%i" with two integers.
Quoted 8 lines
Quoted 4 lines
- There is no equivalent of
test_todo()to mark assertions that are expected to fail. We're not using that yet in our tests but our experience with the integration tests suggests that we are likely to want this in the future.Heh, funny that you mention this. I had this discussion some 6 years ago I think, where I also mentioned that this should exist as a feature. In any case, I agree.
Excellent!
Quoted 7 lines
Quoted 3 lines
- To me the "sandbox" feature is mis-named as it does not provide any confinement. It is instead a useful mechanism for running a test in a temporary directory created from a template.
I guess we'll either just have to not use it or ignore that it's named a bit awkwardly. Changing this in clar probably wouldn't work well because other projects may depend on it.
Yes it's probably too late to rename it. I think being able to create a test directory from a template directory could be useful, we just need to be mindful that the test code is not confined by a sandbox.
Quoted 4 lines
Quoted 2 lines
- There are no checks for failing memory allocations - the return value of
calloc()andstrdup()are used without checking for NULL.I'll commit to fixing this upstream if this lands.
Great
Quoted 10 lines
Quoted 2 lines
- The use of longjmp is a bit of a double edged sword as it makes it easy to leak resources on test failures.
I have to say that this is one of the best features of the clar to me. The current test framework we use doesn't, which in theory requires you to always
returnwhenever there was an error. But that results in code that is both awful to read and write, so for most of the tests simply don't bother at all. And consequently, the tests are quite likely to cause segfaults once one of the checks fails because we didn't abort running the testcase, but things are broken.
I thought that the tests took care to bail out early where it made sense. Sometimes it is useful to continue for example when checking an strbuf we might want to check alloc and len before bailing out. We're probably not losing much by not doing that though.
Quoted 2 lines
In practice, I'd claim that you don't typically care all that much about memory leaks once your basic assertions start to fail.
I tend to agree. I was thinking more about exhausting fds and cleaning up files but that's probably not a big issue in practice.
Quoted 11 lines
So, things that need addressing and that I'm happy to do as follow ups:
- Introduce functions that compare integers.
- Improve type safety of the
cl_assert_equal_?()macros.- Adapt
cl_assert_equal_s()to handle NULL pointers.- Introduce checks for failing memory allocations.
Nice to have would be support for known-failing tests.
This all sounds good to me
Sorry for missing this mail earlier.
Phillip
Quoted 1 line
Patrick
v6 cover Introduce clar testing framework
In reply to Patrick SteinhardtHi,
this is another version of my patch series that introduces the clar testing framework for our C unit tests.
Changes compared to v5:
- Rebased on top of master to fix some merge conflicts. This series
is now built on bb9c16bd4f (The sixth batch, 2024-08-19).
- Introduce cl_failf() as a nice way to print formatted error messages.
- NULL-terminate the argv array again and move the changes into the correct patch.
- Most of the changes were done by Dscho to port clar over to Windows, including CMake build instructions to make this usable with MSVC. I plan to upstream these changes when this series lands in Git.
- Introduce CMake support, also by Dscho.
Thanks for all the feedback, and thanks to Dscho for helping out with porting this to Windows.
GitHub pipeline: https://github.com/git/git/actions/runs/10472008177/
GitLab pipeline: https://gitlab.com/gitlab-org/git/-/pipelines/1419946971
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unused fs_copy()
clar: stop including shellapi.h unnecessarily
clar: add CMake support
Patrick Steinhardt (9):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 70 +-
t/unit-tests/strvec.c | 222 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 18 +
t/unit-tests/unit-test.h | 10 +
31 files changed, 3409 insertions(+), 235 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (70%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.hRange-diff against v5:
1: 832dc0496fb = 1: e48a6461137 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 36906079330 = 2: 1710e9f9ff7 t: import the clar unit testing framework
3: db53673294e = 3: 5c21aa87aa2 t/clar: fix compatibility with NonStop
-: ----------- > 4: 06d2bce0d82 clar: avoid compile error with mingw-w64
-: ----------- > 5: f88b3421a09 clar(win32): avoid compile error due to unused `fs_copy()`
-: ----------- > 6: 5fb4c55be33 clar: stop including `shellapi.h` unnecessarily
4: b6199c88dd7 = 7: e0dcbd5ca83 Makefile: fix sparse dependency on GENERATED_H
5: 06364b2b722 = 8: 77a03f8df70 Makefile: make hdr-check depend on generated headers
6: 88ea94ce16c = 9: c91dd7327e3 Makefile: do not use sparse on third-party sources
7: 05bcb5bef6c = 10: 115c15aa9ae Makefile: wire up the clar unit testing framework
8: 8f56b4d6264 ! 11: b3b8df04872 t/unit-tests: convert strvec tests to use clar
@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
-@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
- UNIT_TEST_PROGRAMS += t-reftable-record
+@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-record
+ UNIT_TEST_PROGRAMS += t-reftable-tree
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
- ## t/unit-tests/t-strvec.c => t/unit-tests/strvec.c ##
+ ## t/unit-tests/strvec.c (new) ##
@@
--#include "test-lib.h"
+#include "unit-test.h"
- #include "strbuf.h"
- #include "strvec.h"
-
- #define check_strvec(vec, ...) \
- do { \
- const char *expect[] = { __VA_ARGS__ }; \
-- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
-- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
-- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
-- check_uint((vec)->nr, <=, (vec)->alloc)) { \
-- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
-- if (!check_str((vec)->v[i], expect[i])) { \
-- test_msg(" i: %"PRIuMAX, \
-- (uintmax_t)i); \
-- break; \
-- } \
-- } \
-- } \
++#include "strbuf.h"
++#include "strvec.h"
++
++#define check_strvec(vec, ...) \
++ do { \
++ const char *expect[] = { __VA_ARGS__ }; \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
- } while (0)
-
--static void t_static_init(void)
++ } while (0)
++
+void test_strvec__init(void)
- {
- struct strvec vec = STRVEC_INIT;
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec = STRVEC_INIT;
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_dynamic_init(void)
++}
++
+void test_strvec__dynamic_init(void)
- {
- struct strvec vec;
- strvec_init(&vec);
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec;
++ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_clear(void)
++}
++
+void test_strvec__clear(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_push(&vec, "foo");
- strvec_clear(&vec);
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_push(&vec, "foo");
++ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_push(void)
++}
++
+void test_strvec__push(void)
- {
- struct strvec vec = STRVEC_INIT;
-
-@@ t/unit-tests/strvec.c: static void t_push(void)
- strvec_clear(&vec);
- }
-
--static void t_pushf(void)
++{
++ struct strvec vec = STRVEC_INIT;
++
++ strvec_push(&vec, "foo");
++ check_strvec(&vec, "foo", NULL);
++
++ strvec_push(&vec, "bar");
++ check_strvec(&vec, "foo", "bar", NULL);
++
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushf(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushf(&vec, "foo: %d", 1);
-@@ t/unit-tests/strvec.c: static void t_pushf(void)
- strvec_clear(&vec);
- }
-
--static void t_pushl(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushf(&vec, "foo: %d", 1);
++ check_strvec(&vec, "foo: 1", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushl(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_pushl(void)
- strvec_clear(&vec);
- }
-
--static void t_pushv(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushv(void)
- {
- const char *strings[] = {
- "foo", "bar", "baz", NULL,
-@@ t/unit-tests/strvec.c: static void t_pushv(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_at_head(void)
++{
++ const char *strings[] = {
++ "foo", "bar", "baz", NULL,
++ };
++ struct strvec vec = STRVEC_INIT;
++
++ strvec_pushv(&vec, strings);
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_at_head(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_at_head(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_at_tail(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 0, "replaced");
++ check_strvec(&vec, "replaced", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_at_tail(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_at_tail(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_in_between(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 2, "replaced");
++ check_strvec(&vec, "foo", "bar", "replaced", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_in_between(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_in_between(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_with_substring(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 1, "replaced");
++ check_strvec(&vec, "foo", "replaced", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_with_substring(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_with_substring(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_at_head(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", NULL);
++ strvec_replace(&vec, 0, vec.v[0] + 1);
++ check_strvec(&vec, "oo", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_at_head(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_at_head(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_at_tail(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 0);
++ check_strvec(&vec, "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_at_tail(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_at_tail(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_in_between(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 2);
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_in_between(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_in_between(void)
- strvec_clear(&vec);
- }
-
--static void t_pop_empty_array(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 1);
++ check_strvec(&vec, "foo", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pop_empty_array(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pop(&vec);
-@@ t/unit-tests/strvec.c: static void t_pop_empty_array(void)
- strvec_clear(&vec);
- }
-
--static void t_pop_non_empty_array(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pop(&vec);
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pop_non_empty_array(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_pop_non_empty_array(void)
- strvec_clear(&vec);
- }
-
--static void t_split_empty_string(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_pop(&vec);
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_empty_string(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "");
-@@ t/unit-tests/strvec.c: static void t_split_empty_string(void)
- strvec_clear(&vec);
- }
-
--static void t_split_single_item(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "");
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_single_item(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo");
-@@ t/unit-tests/strvec.c: static void t_split_single_item(void)
- strvec_clear(&vec);
- }
-
--static void t_split_multiple_items(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo");
++ check_strvec(&vec, "foo", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_multiple_items(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo bar baz");
-@@ t/unit-tests/strvec.c: static void t_split_multiple_items(void)
- strvec_clear(&vec);
- }
-
--static void t_split_whitespace_only(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo bar baz");
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_whitespace_only(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, " \t\n");
-@@ t/unit-tests/strvec.c: static void t_split_whitespace_only(void)
- strvec_clear(&vec);
- }
-
--static void t_split_multiple_consecutive_whitespaces(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, " \t\n");
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_multiple_consecutive_whitespaces(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo\n\t bar");
-@@ t/unit-tests/strvec.c: static void t_split_multiple_consecutive_whitespaces(void)
- strvec_clear(&vec);
- }
-
--static void t_detach(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo\n\t bar");
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__detach(void)
- {
- struct strvec vec = STRVEC_INIT;
- const char **detached;
-@@ t/unit-tests/strvec.c: static void t_detach(void)
- strvec_push(&vec, "foo");
-
- detached = strvec_detach(&vec);
-- check_str(detached[0], "foo");
-- check_pointer_eq(detached[1], NULL);
++{
++ struct strvec vec = STRVEC_INIT;
++ const char **detached;
++
++ strvec_push(&vec, "foo");
++
++ detached = strvec_detach(&vec);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
-
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
-
- free((char *) detached[0]);
- free(detached);
- }
++
++ free((char *) detached[0]);
++ free(detached);
++}
+
+ ## t/unit-tests/t-strvec.c (deleted) ##
+@@
+-#include "test-lib.h"
+-#include "strbuf.h"
+-#include "strvec.h"
+-
+-#define check_strvec(vec, ...) \
+- do { \
+- const char *expect[] = { __VA_ARGS__ }; \
+- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
+- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
+- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
+- check_uint((vec)->nr, <=, (vec)->alloc)) { \
+- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
+- if (!check_str((vec)->v[i], expect[i])) { \
+- test_msg(" i: %"PRIuMAX, \
+- (uintmax_t)i); \
+- break; \
+- } \
+- } \
+- } \
+- } while (0)
-
-int cmd_main(int argc, const char **argv)
-{
-- TEST(t_static_init(), "static initialization");
-- TEST(t_dynamic_init(), "dynamic initialization");
-- TEST(t_clear(), "clear");
-- TEST(t_push(), "push");
-- TEST(t_pushf(), "pushf");
-- TEST(t_pushl(), "pushl");
-- TEST(t_pushv(), "pushv");
-- TEST(t_replace_at_head(), "replace at head");
-- TEST(t_replace_in_between(), "replace in between");
-- TEST(t_replace_at_tail(), "replace at tail");
-- TEST(t_replace_with_substring(), "replace with substring");
-- TEST(t_remove_at_head(), "remove at head");
-- TEST(t_remove_in_between(), "remove in between");
-- TEST(t_remove_at_tail(), "remove at tail");
-- TEST(t_pop_empty_array(), "pop with empty array");
-- TEST(t_pop_non_empty_array(), "pop with non-empty array");
-- TEST(t_split_empty_string(), "split empty string");
-- TEST(t_split_single_item(), "split single item");
-- TEST(t_split_multiple_items(), "split multiple items");
-- TEST(t_split_whitespace_only(), "split whitespace only");
-- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
-- TEST(t_detach(), "detach");
+- if_test ("static initialization") {
+- struct strvec vec = STRVEC_INIT;
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("dynamic initialization") {
+- struct strvec vec;
+- strvec_init(&vec);
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("clear") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_push(&vec, "foo");
+- strvec_clear(&vec);
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("push") {
+- struct strvec vec = STRVEC_INIT;
+-
+- strvec_push(&vec, "foo");
+- check_strvec(&vec, "foo", NULL);
+-
+- strvec_push(&vec, "bar");
+- check_strvec(&vec, "foo", "bar", NULL);
+-
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushf") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushf(&vec, "foo: %d", 1);
+- check_strvec(&vec, "foo: 1", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushl") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushv") {
+- const char *strings[] = {
+- "foo", "bar", "baz", NULL,
+- };
+- struct strvec vec = STRVEC_INIT;
+-
+- strvec_pushv(&vec, strings);
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+-
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace at head") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 0, "replaced");
+- check_strvec(&vec, "replaced", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace at tail") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 2, "replaced");
+- check_strvec(&vec, "foo", "bar", "replaced", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace in between") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 1, "replaced");
+- check_strvec(&vec, "foo", "replaced", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace with substring") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", NULL);
+- strvec_replace(&vec, 0, vec.v[0] + 1);
+- check_strvec(&vec, "oo", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove at head") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 0);
+- check_strvec(&vec, "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove at tail") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 2);
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove in between") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 1);
+- check_strvec(&vec, "foo", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pop with empty array") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pop(&vec);
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pop with non-empty array") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_pop(&vec);
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split empty string") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "");
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split single item") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo");
+- check_strvec(&vec, "foo", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split multiple items") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo bar baz");
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split whitespace only") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, " \t\n");
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split multiple consecutive whitespaces") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo\n\t bar");
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("detach") {
+- struct strvec vec = STRVEC_INIT;
+- const char **detached;
+-
+- strvec_push(&vec, "foo");
+-
+- detached = strvec_detach(&vec);
+- check_str(detached[0], "foo");
+- check_pointer_eq(detached[1], NULL);
+-
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+-
+- free((char *) detached[0]);
+- free(detached);
+- }
+-
- return test_done();
-}
-
- ## t/unit-tests/unit-test.c ##
-@@ t/unit-tests/unit-test.c: int cmd_main(int argc, const char **argv)
- int ret;
-
- /* Append the "-t" flag such that the tests generate TAP output. */
-- ALLOC_ARRAY(argv_copy, argc + 2);
-+ ALLOC_ARRAY(argv_copy, argc + 1);
- COPY_ARRAY(argv_copy, argv, argc);
- argv_copy[argc++] = "-t";
-- argv_copy[argc] = NULL;
-
- ret = clar_test(argc, (char **) argv_copy);
-
9: ca09d19fd51 ! 12: 1ac2e48a7f2 t/unit-tests: convert ctype tests to use clar
@@ Commit message
t/unit-tests: convert ctype tests to use clar
Convert the ctype tests to use the new clar unit testing framework.
+ Introduce a new function `cl_failf()` that allows us to print a
+ formatted error message, which we can use to point out which of the
+ characters was classified incorrectly. This results in output like this
+ on failure:
+
+ # start of suite 1: ctype
+ ok 1 - ctype::isspace
+ not ok 2 - ctype::isdigit
+ ---
+ reason: |
+ Test failed.
+ 0x61 is classified incorrectly
+ at:
+ file: 't/unit-tests/ctype.c'
+ line: 38
+ function: 'test_ctype__isdigit'
+ ---
+ ok 3 - ctype::isalpha
+ ok 4 - ctype::isalnum
+ ok 5 - ctype::is_glob_special
+ ok 6 - ctype::is_regex_special
+ ok 7 - ctype::is_pathspec_magic
+ ok 8 - ctype::isascii
+ ok 9 - ctype::islower
+ ok 10 - ctype::isupper
+ ok 11 - ctype::iscntrl
+ ok 12 - ctype::ispunct
+ ok 13 - ctype::isxdigit
+ ok 14 - ctype::isprint
Signed-off-by: Patrick Steinhardt <[email protected]>
@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
- UNIT_TEST_PROGRAMS += t-mem-pool
+ UNIT_TEST_PROGRAMS += t-hashmap
## t/unit-tests/t-ctype.c => t/unit-tests/ctype.c ##
@@
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
-- int skip = test__run_begin(); \
-- if (!skip) { \
+- if_test (#class " works") { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
-- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
-+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
++ if (class(i) != !!memchr(string, i, len)) \
++ cl_failf("0x%02x is classified incorrectly", i); \
+ cl_assert(!class(EOF)); \
} while (0)
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
+
+ ## t/unit-tests/unit-test.h ##
+@@
+ #include "git-compat-util.h"
+ #include "clar/clar.h"
+ #include "clar-decls.h"
++#include "strbuf.h"
++
++#define cl_failf(fmt, ...) do { \
++ char *desc = xstrfmt(fmt, __VA_ARGS__); \
++ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
++ free(desc); \
++} while (0) -: ----------- > 13: 131036c398e clar: add CMake support
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 1/13 t: do not pass GIT_TEST_OPTS to unit tests with prove
When using the prove target, we append GIT_TEST_OPTS to the arguments that we execute each of the tests with. This doesn't only include the intended test scripts, but also ends up passing the arguments to our unit tests. This is unintentional though as they do not even know to interpret those arguments, and is inconsistent with how we execute unit tests without prove.
This isn't much of an issue because our current set of unit tests mostly ignore their arguments anyway. With the introduction of clar-based unit tests this is about to become an issue though, as these do parse their command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an environment variable. Like this, we can conditionally forward it to our test scripts, only.
--- t/Makefile | 3 ++- t/run-test.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)
t/Makefile +2 -1
@@ -68,7 +68,8 @@ failed: test -z "$$failed" || $(MAKE) $$failed prove: pre-clean check-chainlint $(TEST_LINT) - @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS) + @echo "*** prove (shell & unit tests) ***" + @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) $(MAKE) clean-except-prove-cache $(T):
t/run-test.sh +1 -1
@@ -10,7 +10,7 @@ case "$1" in echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set" exit 1 fi - exec "${TEST_SHELL_PATH}" "$@" + exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS} ;; *) exec "$@"
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 2/13 t: import the clar unit testing framework
In reply to Patrick SteinhardtImport the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
--- Documentation/technical/unit-tests.txt | 2 + Makefile | 4 +- t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 522 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 153 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ 20 files changed, 2943 insertions(+), 1 deletion(-) create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml create mode 100644 t/unit-tests/clar/COPYING create mode 100644 t/unit-tests/clar/README.md create mode 100644 t/unit-tests/clar/clar.c create mode 100644 t/unit-tests/clar/clar.h create mode 100644 t/unit-tests/clar/clar/fixtures.h create mode 100644 t/unit-tests/clar/clar/fs.h create mode 100644 t/unit-tests/clar/clar/print.h create mode 100644 t/unit-tests/clar/clar/sandbox.h create mode 100644 t/unit-tests/clar/clar/summary.h create mode 100755 t/unit-tests/clar/generate.py create mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644 t/unit-tests/clar/test/clar_test.h create mode 100644 t/unit-tests/clar/test/main.c create mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644 t/unit-tests/clar/test/sample.c
Documentation/technical/unit-tests.txt +2 -0
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this. :criterion: https://github.com/Snaipe/Criterion[Criterion] :c-tap: https://github.com/rra/c-tap-harness/[C TAP] :check: https://libcheck.github.io/check/[Check] +:clar: https://github.com/clar-test/clar[Clar] [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"] |=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800 {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33 {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973 +{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192 |===== === Additional framework candidates
Makefile +3 -1
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/% THIRD_PARTY_SOURCES += compat/regex/% THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1dc/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o .PHONY: sparse sparse: $(SP_OBJ) -EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% +EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/% ifndef OPENSSL_SHA1 EXCEPT_HDRS += sha1/openssl.h endif
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
@@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + + runs-on: ${{ matrix.os }} + + steps: + - name: Check out + uses: actions/checkout@v2 + - name: Build + run: | + cd test + make
t/unit-tests/clar/COPYING new file +15 -0
@@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2011-2015 Vicent Marti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
t/unit-tests/clar/README.md new file +329 -0
329 lines not on this page · raw message
t/unit-tests/clar/clar.c new file +842 -0
842 lines not on this page · raw message
t/unit-tests/clar/clar.h new file +173 -0
173 lines not on this page · raw message
t/unit-tests/clar/clar/fixtures.h new file +50 -0
50 lines not on this page · raw message
t/unit-tests/clar/clar/fs.h new file +522 -0
522 lines not on this page · raw message
t/unit-tests/clar/clar/print.h new file +211 -0
211 lines not on this page · raw message
t/unit-tests/clar/clar/sandbox.h new file +153 -0
153 lines not on this page · raw message
t/unit-tests/clar/clar/summary.h new file +143 -0
143 lines not on this page · raw message
t/unit-tests/clar/generate.py new file +266 -0
266 lines not on this page · raw message
t/unit-tests/clar/test/.gitignore new file +4 -0
@@ -0,0 +1,4 @@ +clar.suite +.clarcache +clar_test +*.o
t/unit-tests/clar/test/Makefile new file +39 -0
@@ -0,0 +1,39 @@ +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +# +# Set up the path to the clar sources and to the fixtures directory +# +# The fixture path needs to be an absolute path so it can be used +# even after we have chdir'ed into the test directory while testing. +# +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE))) +CLAR_PATH := $(dir $(TEST_DIRECTORY)) +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/ + +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\" + +.PHONY: clean + +# list the objects that go into our test +objects = main.o sample.o + +# build the test executable itself +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects) + +# test object files depend on clar macros +$(objects) : $(CLAR_PATH)clar.h + +# build the clar.suite file of test metadata +clar.suite: + python "$(CLAR_PATH)generate.py" . + +# remove all generated files +clean: + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
t/unit-tests/clar/test/clar_test.h new file +16 -0
@@ -0,0 +1,16 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ +#ifndef __CLAR_TEST__ +#define __CLAR_TEST__ + +/* Import the standard clar helper functions */ +#include "clar.h" + +/* Your custom shared includes / defines here */ +extern int global_test_counter; + +#endif
t/unit-tests/clar/test/main.c new file +40 -0
@@ -0,0 +1,40 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ + +#include "clar_test.h" + +/* +* Sample main() for clar tests. +* +* You should write your own main routine for clar tests that does specific +* setup and teardown as necessary for your application. The only required +* line is the call to `clar_test(argc, argv)`, which will execute the test +* suite. If you want to check the return value of the test application, +* your main() should return the same value returned by clar_test(). +*/ + +int global_test_counter =0 ; + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ +int ret; + +/* Your custom initialization here */ + global_test_counter =0 ; + +/* Run the test suite */ + ret =clar_test (argc, argv); + +/* Your custom cleanup here */ +cl_assert_equal_i (8 , global_test_counter); + +return ret; +}
t/unit-tests/clar/test/main.c.sample new file +27 -0
@@ -0,0 +1,27 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ + +#include "clar_test.h" + +/* + * Minimal main() for clar tests. + * + * Modify this with any application specific setup or teardown that you need. + * The only required line is the call to `clar_test(argc, argv)`, which will + * execute the test suite. If you want to check the return value of the test + * application, main() should return the same value returned by clar_test(). + */ + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + /* Run the test suite */ + return clar_test(argc, argv); +}
t/unit-tests/clar/test/resources/test/file new file +1 -0
@@ -0,0 +1 @@ +File
t/unit-tests/clar/test/sample.c new file +84 -0
84 lines not on this page · raw message
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
Re: [PATCH v6 02/13] t: import the clar unit testing framework
Hi Patrick
Quoted 3 lines · Patrick Steinhardt
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
I would be good to explain why we're doing this so we have a record in the project history. A summary of the advantages and disadvantages of using clar vs our current unit test framework would be helpful as well.
t/unit-tests/clar/clar.c
Quoted 9 lines
t/unit-tests/clar/clar.c +6
-0+static void +clar_parse_args (int argc,char **argv) +{ +case 'q' : + _clar.report_errors_only =1 ; +break ;
I think this option is incompatible with TAP output as when TAP output is selected the first error is printed as part of clar_print_ontest() and clar_print_errors() is a no-op. We should error out if it is given with '-t'
t/unit-tests/clar/clar.c line 676
Quoted 9 lines
t/unit-tests/clar/clar.c +9
-0+void clar__fail ( +const char *file, +const char *function, +size_t line, +const char *error_msg, +const char *description, +int should_abort) +{ +struct clar_error *error =calloc (1 ,sizeof (struct clar_error ));
clar seems to take an inconsistent approach to memory allocation errors. Here "error" is de-referenced without checking if it is NULL and yet in other places there are checks for NULL after calling calloc() or strdup().
Quoted 17 lines
t/unit-tests/clar/clar.c +17
-0+void clar__assert_equal ( +const char *file, +const char *function, +size_t line, +const char *err, +int should_abort, +const char *fmt, + ...) +{ +va_list args; +char buf[4096 ]; +int is_equal =1 ; + +va_start (args, fmt); + +if (!strcmp ("%s" , fmt)) { + const char *s1 = va_arg(args, const char *);
t/unit-tests/clar/clar.c line 742
Quoted 10 lines
t/unit-tests/clar/clar.c +10
-0+ const char *s2 = va_arg(args, const char *); + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp (s1, s2); + +if (!is_equal) { +if (s1 && s2) { +int pos; +for (pos =0 ; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) +/* find differing byte offset */ ; +p_snprintf (buf,sizeof (buf),"'%s' != '%s' (at byte %d)" , + s1, s2, pos);
s1 and s2 are passed to snprintf without checking if they are NULL. As 'snprintf(buf, sizeof(buf), "%s", NULL)' is undefined we should check for NULL here. The use of variadic arguments means there is no type checking so if one is checking the contents of an strbuf and writes
cl_assert_equal_s(buf, "expect");
instead of
cl_assert_equal_s(buf->buf, "expect");
the compiler does not warn you. This is a regression compared to check_str(). We can address this by having cl_assert_equal_s() wrap a function that takes two strings. Another regression is that if the string contains control characters they are printed verbatim.
t/unit-tests/clar/clar.h line 137
Quoted 3 lines
t/unit-tests/clar/clar.h +1
-0+#define cl_assert_equal_i (i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
The (int) casts here mean that we'll silently truncate longer arguments and suppress any compiler warnings about comparing incompatible types.
Quoted 18 lines
t/unit-tests/clar/clar/print.h +16
-0+static void clar_print_tap_ontest (const char *suite_name,const char *test_name,int test_number,enum cl_test_status status) +{ +const struct clar_error *error = _clar.last_report->errors; + + (void )test_name; + (void )test_number; + +switch (status) { +case CL_TEST_OK : +printf ("ok %d - %s::%s\n" , test_number, suite_name, test_name); +break ; +case CL_TEST_FAILURE : +printf ("not ok %d - %s::%s\n" , test_number, suite_name, test_name); + +printf (" ---\n" ); +printf (" reason: |\n" );
t/unit-tests/clar/clar/print.h line 141
Quoted 10 lines
t/unit-tests/clar/clar/print.h +10
-0+printf (" %s\n" , error->error_msg); + +if (error->description) +printf (" %s\n" , error->description); + +printf (" at:\n" ); +printf (" file: '" );print_escaped (error->file);printf ("'\n" ); +printf (" line: %" PRIuZ"\n" , error->line_number); +printf (" function: '%s'\n" , error->function); +printf (" ---\n" );
If a test calls cl_warning() only the message from the first call to that function is printed and any message from later calls to cl_warning() or clar__fail() is suppressed when TAP output is selected.
Best Wishes
Phillip
Re: [PATCH v6 02/13] t: import the clar unit testing framework
Quoted 10 lines · Phillip Wood
Hi Patrick
Quoted 3 lines · Patrick Steinhardt
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
I would be good to explain why we're doing this so we have a record in the project history. A summary of the advantages and disadvantages of using clar vs our current unit test framework would be helpful as well.
Will do.
Quoted 5 lines
Quoted 5 lines
t/unit-tests/clar/clar.c +3
-0+static void +clar_parse_args (int argc,char **argv) +{
Quoted 8 lines
t/unit-tests/clar/clar.c line 517
Quoted 4 lines
[...]
t/unit-tests/clar/clar.c +3
-0+case 'q' : + _clar.report_errors_only =1 ; +break ;I think this option is incompatible with TAP output as when TAP output is selected the first error is printed as part of
clar_print_ontest()andclar_print_errors()is a no-op. We should error out if it is given with '-t'
Yup. I'll address this by implementing our own option parsing.
The remainder of your concerns relate to the clar implementation itself, and as such I won't yet address them. I'll work with upstream to get these fixed after this series lands though, as outlined in [1].
[1]: <ZsSTOczuGhT5zwoV@tanuki>
Patrick
PATCH v6 3/13 t/clar: fix compatibility with NonStop
In reply to Patrick SteinhardtThe NonStop platform does not have mkdtemp() available, which we rely on in build_sandbox_path(). Fix this issue by using mktemp() and mkdir() instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
--- t/unit-tests/clar/clar/sandbox.h | 6 ++++++ 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)if (_mktemp (_clar_path) ==NULL )return -1 ; +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#elif defined(__TANDEM) +if (mktemp (_clar_path) ==NULL ) +return -1 ; +if (mkdir (_clar_path,0700 ) !=0 )return -1 ;#elif defined(_WIN32)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 4/13 clar: avoid compile error with mingw-w64
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When using mingw-w64 to compile the code, and using _stat(), it is necessary to use struct _stat, too, and not struct stat (as the latter is incompatible with the "dashed" version because it is limited to 32-bit time types for backwards compatibility).
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -68,7 +68,7 @@# define PRIxZ "Ix"# endif -# if defined(_MSC_VER) || defined(__MINGW32__) +# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))typedef struct stat STAT_T ;# else typedef struct _stat STAT_T ;
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 5/13 clar(win32): avoid compile error due to unused `fs_copy()`
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When CLAR_FIXTURE_PATH is unset, the fs_copy() function seems not to be used. But it is declared as static, and GCC does not like that, complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the MAYBE_UNUSED pattern from Git's git-compat-util.h. However, this is a GCC-only construct that is not understood by Visual C. Besides, clar does not use that pattern at all.
Instead, let's use the ((void)SYMBOL); pattern that clar already uses elsewhere; This avoids the compile error by sorta kinda make the function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to figure out that this function is unused even though there are recursive calls between fs_copy() and fs_copydir_helper(); Earlier GCC versions do not detect that, and therefore the issue has been hidden from the regular Linux CI builds (where GCC 14.x is not yet used). That is the reason why this change is only made in the Windows-specific portion of t/unit-tests/clar/clar/fs.h.
--- t/unit-tests/clar/clar/fs.h | 2 ++ 1 file changed, 2 insertions(+)
t/unit-tests/clar/clar/fs.h +2 -0
@@ -297,6 +297,8 @@ cl_fs_cleanup(void) {#ifdef CLAR_FIXTURE_PATH fs_rm (fixture_path (_clar_path,"*" )); +#else + ((void )fs_copy);/* unused */ #endif }
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 6/13 clar: stop including `shellapi.h` unnecessarily
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
The shellapi.h header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have SHFileOperation() declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed that call, and therefore that #include <shellapi.h> is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons of header files, including shellapi.h.
So let's remove it.
Note: Since the windows.h header would include shellapi.h anyway, we also define WIN32_LEAN_AND_MEAN to avoid this and similar other unnecessary includes before including windows.h.
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -19,9 +19,9 @@#include <sys/stat.h> #ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN # include <windows.h> # include <io.h> -# include <shellapi.h> # include <direct.h> # define _MAIN_CC __cdecl
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 7/13 Makefile: fix sparse dependency on GENERATED_H
In reply to Patrick SteinhardtThe "check" Makefile target is essentially an alias around the "sparse" target. The one difference though is that it will tell users to instead run the "test" target in case they do not have sparse(1) installed, as chances are high that they wanted to execute the test suite rather than doing semantic checks.
But even though the "check" target ultimately just ends up executing make sparse, it still depends on our generated headers. This does not make any sense though: they are irrelevant for the "test" target advice, and if these headers are required for the "sparse" target they must be declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as concurrent builds may then end up generating the headers and running sparse concurrently. Instead, we make them a dependency of the specific objects. While that is overly broad, it does ensure correct ordering. The alternative, specifying which file depends on what generated header explicitly, feels rather unmaintainable.
--- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) -$(SP_OBJ): %.sp: %.c %.o +$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ -Wsparse-error \ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style: git clang-format --style file --diff --extensions c,h .PHONY: check -check: $(GENERATED_H) +check: @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 8/13 Makefile: make hdr-check depend on generated headers
In reply to Patrick SteinhardtThe "hdr-check" Makefile target compiles each of our headers as a standalone code unit to ensure that they are not missing any type declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework, which will have the effect that some headers start depending on generated ones. While we could declare that dependency explicitly, it does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects depend on all of our generated headers. While again overly broad, it is easy to maintain and generating headers is not an expensive thing to do anyway.
--- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc) @echo '#include "git-compat-util.h"' >$@ @echo '#include "$<"' >>$@ -$(HCO): %.hco: %.hcc FORCE +$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE $(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $< .PHONY: hdr-check $(HCO)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 9/13 Makefile: do not use sparse on third-party sources
In reply to Patrick SteinhardtWe have several third-party sources in our codebase that we have imported from upstream projects. These sources are mostly excluded from our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
--- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) check-sha1:: t/helper/test-tool$X t/helper/test-sha1.sh -SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) +SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS))) +SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC)) $(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 10/13 Makefile: wire up the clar unit testing framework
In reply to Patrick SteinhardtWire up the clar unit testing framework by introducing a new "unit-tests" executable. In contrast to the existing framework, this will result in a single executable for all test suites. The ability to pick specific tests to execute is retained via functionality built into the clar itself.
Note that we need to be a bit careful about how we need to invalidate our Makefile rules. While we obviously have to regenerate the clar suite when our test suites change, we also have to invalidate it in case any of the test suites gets removed. We do so by using our typical pattern of creating a GIT-TEST-SUITES file that gets updated whenever the set of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test functions are neither static, nor do they have external declarations. This is because they are getting parsed via "generate.py", which then creates the external generations that get populated into an array. These declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes" errors from our compiler for each of these test functions. To fix those errors, we extract the extern declarations from "clar.suite" and put them into a standalone header that then gets included by each of our unit tests. This gets rid of compiler warnings for every function which has been extracted by "generate.py". More importantly though, it does _not_ get rid of warnings in case a function really isn't being used by anything. Thus, it would cause a compiler error if a function name was mistyped and thus not picked up by "generate.py".
--- .gitignore | 1 + Makefile | 36 +++++++++++++++++++++--- t/Makefile | 1 + t/unit-tests/.gitignore | 2 ++ t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++ t/unit-tests/unit-test.c | 18 ++++++++++++ t/unit-tests/unit-test.h | 3 ++ 7 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 t/unit-tests/clar-generate.awk create mode 100644 t/unit-tests/unit-test.c create mode 100644 t/unit-tests/unit-test.h
.gitignore +1 -0
@@ -9,6 +9,7 @@ /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-SPATCH-DEFINES +/GIT-TEST-SUITES /GIT-USER-AGENT /GIT-VERSION-FILE /bin-wrappers/
Makefile +32 -4
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a GENERATED_H += command-list.h GENERATED_H += config-list.h GENERATED_H += hook-list.h +GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h +GENERATED_H += $(UNIT_TEST_DIR)/clar.suite .PHONY: generated-hdrs generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) -all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) +all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \ GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \ - $(UNIT_TEST_PROGS) $(MOFILES) + $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES) $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \ SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean: clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) + $(RM) GIT-TEST-SUITES $(RM) po/git.pot po/git-core.pot $(RM) git.res $(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) +GIT-TEST-SUITES: FORCE + @FLAGS='$(UNIT_TESTS_SUITES)'; \ + if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \ + echo >&2 " * new test suites"; \ + echo "$$FLAGS" >GIT-TEST-SUITES; \ + fi + +$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES + $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \ + sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \ + done >$@ +$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h + $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite +$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h +$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) +$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS + $(call mkdir_p_parent_template) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + .PHONY: build-unit-tests unit-tests -build-unit-tests: $(UNIT_TEST_PROGS) -unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X +build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) +unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests
t/Makefile +1 -0
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) +UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X) UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS)) UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
t/unit-tests/.gitignore +2 -0
@@ -1 +1,3 @@ /bin +/clar.suite +/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0
50 lines not on this page · raw message
t/unit-tests/unit-test.c new file +18 -0
@@ -0,0 +1,18 @@ +#include "unit-test.h" + +int cmd_main (int argc,const char **argv) +{ +const char **argv_copy; +int ret; + +/* Append the "-t" flag such that the tests generate TAP output. */ +ALLOC_ARRAY (argv_copy, argc +2 ); +COPY_ARRAY (argv_copy, argv, argc); + argv_copy[argc++] ="-t" ; + argv_copy[argc] =NULL ; + + ret =clar_test (argc, (char **) argv_copy); + +free (argv_copy); +return ret; +}
t/unit-tests/unit-test.h new file +3 -0
@@ -0,0 +1,3 @@ +#include "git-compat-util.h" +#include "clar/clar.h" +#include "clar-decls.h"
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
PATCH v6 11/13 t/unit-tests: convert strvec tests to use clar
In reply to Patrick SteinhardtConvert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22The binary also supports some parameters that allow us to run only a subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [options] Options:
-sname Run only the suite with name (can go to individual test name)
-iname Include the suite with name
-xname Exclude the suite with name
-v Increase verbosity (show suite names)
-q Only report tests that had an error
-Q Quit as soon as a test fails
-t Display results in tap format
-l Print suite names
-r[filename] Write summary file (to the optional filename)
Furthermore, running make unit-tests runs the binary along with all the other unit tests we have.
--- Makefile | 2 +- t/unit-tests/strvec.c | 222 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/t-strvec.c | 211 -------------------------------------- 3 files changed, 223 insertions(+), 212 deletions(-) create mode 100644 t/unit-tests/strvec.c delete mode 100644 t/unit-tests/t-strvec.c
Makefile +1 -1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record UNIT_TEST_PROGRAMS += t-reftable-tree UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strcmp-offset -UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-trailer UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
t/unit-tests/strvec.c new file +222 -0
222 lines not on this page · raw message
t/unit-tests/t-strvec.c deleted +0 -211
211 lines not on this page · raw message
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
Hi Patrick
Quoted 6 lines · Patrick Steinhardt
Convert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary, it generates TAP output:
It would be interesting to see a comparison between the current framework and clar of the output from a failing test - the TAP output for passing tests is pretty much the same regardless of the framework used.
Quoted 7 lines
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
[...] The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:Quoted 8 lines
$ ./t/unit-tests/bin/unit-tests -h Usage: ./t/unit-tests/bin/unit-tests [options]Options:
-sname Run only the suite withname(can go to individual test name)
-iname Include the suite withname
-xname Exclude the suite withname
-v Increase verbosity (show suite names)
The output above seems to include the suite name - are we running the tests with '-v' from our Makefile?
Quoted 1 line
-q Only report tests that had an error
This option is incompatible with TAP output. As we force TAP output we should find a way to stop displaying this help.
Quoted 2 lines
-Q Quit as soon as a test fails
-t Display results in tap format
We force TAP output by adding '-t' to argv in main() so this line is not very helpful
t/unit-tests/strvec.c line 8
Quoted 9 lines
-l Print suite names
-r[filename] Write summary file (to the optional filename)t/unit-tests/strvec.c +4
-0+#define check_strvec (vec, ...) \ + do { \ + const char *expect[] = { __VA_ARGS__ }; \ + cl_assert(ARRAY_SIZE(expect) > 0); \
As there are a lot occurrences of ARRAY_SIZE(expect) it is probably worth adding
size_t expect_len = ARRAY_SIZE(expect);
above.
t/unit-tests/strvec.c line 11
Quoted 3 lines
t/unit-tests/strvec.c +3
-0+cl_assert_equal_p (expect[ARRAY_SIZE (expect) -1 ],NULL ); \ +cl_assert_equal_i ((vec)->nr,ARRAY_SIZE (expect) -1 ); \ +cl_assert ((vec)->nr <= (vec)->alloc); \
The conversion here loses the values of nr and alloc which is a shame as they would be useful when debugging a test failure.
t/unit-tests/strvec.c line 13
Quoted 2 lines
t/unit-tests/strvec.c +2
-0+for (size_t i =0 ; i <ARRAY_SIZE (expect); i++) \ +cl_assert_equal_s ((vec)->v[i], expect[i]); \
The original test also printed the array index of the failing check. As the elements of the test vectors all seem to be unique that is less of a worry than if we had tests with repeating elements.
t/unit-tests/strvec.c line 18
Quoted 5 lines
t/unit-tests/strvec.c +5
-0+ }while (0 ) + +void test_strvec__init(void) +{ +struct strvec vec =STRVEC_INIT ;
If we're rewriting the tests perhaps we can take the opportunity to add a blank line to each one after the variable declarations in accordance with our coding guidelines.
It might be a good opportunity to show the set-up and tear-down facilities in clar as well instead of repeating the initialization in each test.
Best Wishes
Phillip
Quoted 421 lines
t/unit-tests/strvec.c +204
-0+cl_assert_equal_p (vec.v, empty_strvec); +cl_assert_equal_i (vec.nr,0 ); +cl_assert_equal_i (vec.alloc,0 ); +} + +void test_strvec__dynamic_init (void ) +{ +struct strvec vec; +strvec_init (&vec); +cl_assert_equal_p (vec.v, empty_strvec); +cl_assert_equal_i (vec.nr,0 ); +cl_assert_equal_i (vec.alloc,0 ); +} + +void test_strvec__clear (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_push (&vec,"foo" ); +strvec_clear (&vec); +cl_assert_equal_p (vec.v, empty_strvec); +cl_assert_equal_i (vec.nr,0 ); +cl_assert_equal_i (vec.alloc,0 ); +} + +void test_strvec__push (void ) +{ +struct strvec vec =STRVEC_INIT ; + +strvec_push (&vec,"foo" ); +check_strvec (&vec,"foo" ,NULL ); + +strvec_push (&vec,"bar" ); +check_strvec (&vec,"foo" ,"bar" ,NULL ); + +strvec_clear (&vec); +} + +void test_strvec__pushf (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushf (&vec,"foo: %d" ,1 ); +check_strvec (&vec,"foo: 1" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__pushl (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__pushv (void ) +{ +const char *strings[] = { +"foo" ,"bar" ,"baz" ,NULL , + }; +struct strvec vec =STRVEC_INIT ; + +strvec_pushv (&vec, strings); +check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); + +strvec_clear (&vec); +} + +void test_strvec__replace_at_head (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_replace (&vec,0 ,"replaced" ); +check_strvec (&vec,"replaced" ,"bar" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__replace_at_tail (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_replace (&vec,2 ,"replaced" ); +check_strvec (&vec,"foo" ,"bar" ,"replaced" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__replace_in_between (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_replace (&vec,1 ,"replaced" ); +check_strvec (&vec,"foo" ,"replaced" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__replace_with_substring (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,NULL ); +strvec_replace (&vec,0 , vec.v[0 ] +1 ); +check_strvec (&vec,"oo" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__remove_at_head (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_remove (&vec,0 ); +check_strvec (&vec,"bar" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__remove_at_tail (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_remove (&vec,2 ); +check_strvec (&vec,"foo" ,"bar" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__remove_in_between (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_remove (&vec,1 ); +check_strvec (&vec,"foo" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__pop_empty_array (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pop (&vec); +check_strvec (&vec,NULL ); +strvec_clear (&vec); +} + +void test_strvec__pop_non_empty_array (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_pop (&vec); +check_strvec (&vec,"foo" ,"bar" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__split_empty_string (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_split (&vec,"" ); +check_strvec (&vec,NULL ); +strvec_clear (&vec); +} + +void test_strvec__split_single_item (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_split (&vec,"foo" ); +check_strvec (&vec,"foo" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__split_multiple_items (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_split (&vec,"foo bar baz" ); +check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__split_whitespace_only (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_split (&vec," \t\n" ); +check_strvec (&vec,NULL ); +strvec_clear (&vec); +} + +void test_strvec__split_multiple_consecutive_whitespaces (void ) +{ +struct strvec vec =STRVEC_INIT ; +strvec_split (&vec,"foo\n\t bar" ); +check_strvec (&vec,"foo" ,"bar" ,NULL ); +strvec_clear (&vec); +} + +void test_strvec__detach (void ) +{ +struct strvec vec =STRVEC_INIT ; +const char **detached; + +strvec_push (&vec,"foo" ); + + detached =strvec_detach (&vec); +cl_assert_equal_s (detached[0 ],"foo" ); +cl_assert_equal_p (detached[1 ],NULL ); + +cl_assert_equal_p (vec.v, empty_strvec); +cl_assert_equal_i (vec.nr,0 ); +cl_assert_equal_i (vec.alloc,0 ); + +free ((char *) detached[0 ]); +free (detached); +}t/unit-tests/t-strvec.c deleted +0
-211@@ -1,211 +0,0 @@ -#include "test-lib.h" -#include "strbuf.h" -#include "strvec.h" - -#define check_strvec (vec, ...) \ - do { \ - const char *expect[] = { __VA_ARGS__ }; \ - if (check_uint(ARRAY_SIZE(expect), >, 0) && \ - check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \ - check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \ - check_uint((vec)->nr, <=, (vec)->alloc)) { \ - for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \ - if (!check_str((vec)->v[i], expect[i])) { \ - test_msg(" i: %"PRIuMAX, \ - (uintmax_t)i); \ - break; \ - } \ - } \ - } \ - } while (0) - -int cmd_main (int argc,const char **argv) -{ - if_test ("static initialization") { -struct strvec vec =STRVEC_INIT ; -check_pointer_eq (vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); - } - - if_test ("dynamic initialization") { -struct strvec vec; -strvec_init (&vec); -check_pointer_eq (vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); - } - - if_test ("clear") { -struct strvec vec =STRVEC_INIT ; -strvec_push (&vec,"foo" ); -strvec_clear (&vec); -check_pointer_eq (vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); - } - - if_test ("push") { -struct strvec vec =STRVEC_INIT ; - -strvec_push (&vec,"foo" ); -check_strvec (&vec,"foo" ,NULL ); - -strvec_push (&vec,"bar" ); -check_strvec (&vec,"foo" ,"bar" ,NULL ); - -strvec_clear (&vec); - } - - if_test ("pushf") { -struct strvec vec =STRVEC_INIT ; -strvec_pushf (&vec,"foo: %d" ,1 ); -check_strvec (&vec,"foo: 1" ,NULL ); -strvec_clear (&vec); - } - - if_test ("pushl") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("pushv") { -const char *strings[] = { -"foo" ,"bar" ,"baz" ,NULL , - }; -struct strvec vec =STRVEC_INIT ; - -strvec_pushv (&vec, strings); -check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); - -strvec_clear (&vec); - } - - if_test ("replace at head") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_replace (&vec,0 ,"replaced" ); -check_strvec (&vec,"replaced" ,"bar" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("replace at tail") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_replace (&vec,2 ,"replaced" ); -check_strvec (&vec,"foo" ,"bar" ,"replaced" ,NULL ); -strvec_clear (&vec); - } - - if_test ("replace in between") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_replace (&vec,1 ,"replaced" ); -check_strvec (&vec,"foo" ,"replaced" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("replace with substring") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,NULL ); -strvec_replace (&vec,0 , vec.v[0 ] +1 ); -check_strvec (&vec,"oo" ,NULL ); -strvec_clear (&vec); - } - - if_test ("remove at head") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_remove (&vec,0 ); -check_strvec (&vec,"bar" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("remove at tail") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_remove (&vec,2 ); -check_strvec (&vec,"foo" ,"bar" ,NULL ); -strvec_clear (&vec); - } - - if_test ("remove in between") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_remove (&vec,1 ); -check_strvec (&vec,"foo" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("pop with empty array") { -struct strvec vec =STRVEC_INIT ; -strvec_pop (&vec); -check_strvec (&vec,NULL ); -strvec_clear (&vec); - } - - if_test ("pop with non-empty array") { -struct strvec vec =STRVEC_INIT ; -strvec_pushl (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_pop (&vec); -check_strvec (&vec,"foo" ,"bar" ,NULL ); -strvec_clear (&vec); - } - - if_test ("split empty string") { -struct strvec vec =STRVEC_INIT ; -strvec_split (&vec,"" ); -check_strvec (&vec,NULL ); -strvec_clear (&vec); - } - - if_test ("split single item") { -struct strvec vec =STRVEC_INIT ; -strvec_split (&vec,"foo" ); -check_strvec (&vec,"foo" ,NULL ); -strvec_clear (&vec); - } - - if_test ("split multiple items") { -struct strvec vec =STRVEC_INIT ; -strvec_split (&vec,"foo bar baz" ); -check_strvec (&vec,"foo" ,"bar" ,"baz" ,NULL ); -strvec_clear (&vec); - } - - if_test ("split whitespace only") { -struct strvec vec =STRVEC_INIT ; -strvec_split (&vec," \t\n" ); -check_strvec (&vec,NULL ); -strvec_clear (&vec); - } - - if_test ("split multiple consecutive whitespaces") { -struct strvec vec =STRVEC_INIT ; -strvec_split (&vec,"foo\n\t bar" ); -check_strvec (&vec,"foo" ,"bar" ,NULL ); -strvec_clear (&vec); - } - - if_test ("detach") { -struct strvec vec =STRVEC_INIT ; -const char **detached; - -strvec_push (&vec,"foo" ); - - detached =strvec_detach (&vec); -check_str (detached[0 ],"foo" ); -check_pointer_eq (detached[1 ],NULL ); - -check_pointer_eq (vec.v, empty_strvec); - check_uint(vec.nr, ==, 0); - check_uint(vec.alloc, ==, 0); - -free ((char *) detached[0 ]); -free (detached); - } - -return test_done (); -}
Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
Quoted 7 lines · Phillip Wood
Hi Patrick
Quoted 4 lines · Patrick Steinhardt
Convert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
Quoted 6 lines
Quoted 2 lines
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary, it generates TAP output:
It would be interesting to see a comparison between the current framework and clar of the output from a failing test - the TAP output for passing tests is pretty much the same regardless of the framework used.
Will do.
Quoted 11 lines
Quoted 11 lines
# ./t/unit-tests/bin/unit-tests TAP version 13 # start of suite 1: strvec ok 1 - strvec::init [...] The binary also supports some parameters that allow us to run only a subset of unit tests or alter the output:$ ./t/unit-tests/bin/unit-tests -h Usage: ./t/unit-tests/bin/unit-tests [options]
Quoted 8 lines
Quoted 5 lines
Options:
-sname Run only the suite withname(can go to individual test name)
-iname Include the suite withname
-xname Exclude the suite withname
-v Increase verbosity (show suite names)The output above seems to include the suite name - are we running the tests with '-v' from our
Makefile?
The -v switch is actually doing nothing when generating TAP output.
Quoted 10 lines
Quoted 1 line
-q Only report tests that had an error
This option is incompatible with TAP output. As we force TAP output we should find a way to stop displaying this help.
Quoted 2 lines
-Q Quit as soon as a test fails
-t Display results in tap formatWe force TAP output by adding '-t' to argv in
main()so this line is not very helpful
True indeed. This is the default argument parsing and output from clar, so it's nothing that we can change. That being said, I guess the best way to address this is to use our own option parsing here instead of using whatever clar provides, and then we can also print our own usage.
Will amend accordingly.
Quoted 13 lines
t/unit-tests/strvec.c line 8
Quoted 9 lines
-l Print suite names
-r[filename] Write summary file (to the optional filename)t/unit-tests/strvec.c +4
-0+#define check_strvec (vec, ...) \ + do { \ + const char *expect[] = { __VA_ARGS__ }; \ + cl_assert(ARRAY_SIZE(expect) > 0); \As there are a lot occurrences of
ARRAY_SIZE(expect)it is probably worth adding
Quoted 3 lines
size_t expect_len = ARRAY_SIZE(expect);above.
Can do.
Quoted 6 lines
t/unit-tests/strvec.c line 11
Quoted 3 lines
t/unit-tests/strvec.c +3
-0+cl_assert_equal_p (expect[ARRAY_SIZE (expect) -1 ],NULL ); \ +cl_assert_equal_i ((vec)->nr,ARRAY_SIZE (expect) -1 ); \ +cl_assert ((vec)->nr <= (vec)->alloc); \The conversion here loses the values of nr and alloc which is a shame as they would be useful when debugging a test failure.
This is something I'd address in the future, once we have macros that can do relative comparisons.
Quoted 7 lines
t/unit-tests/strvec.c line 13
Quoted 2 lines
t/unit-tests/strvec.c +2
-0+for (size_t i =0 ; i <ARRAY_SIZE (expect); i++) \ +cl_assert_equal_s ((vec)->v[i], expect[i]); \The original test also printed the array index of the failing check. As the elements of the test vectors all seem to be unique that is less of a worry than if we had tests with repeating elements.
Quoted 9 lines
t/unit-tests/strvec.c line 18
Quoted 5 lines
t/unit-tests/strvec.c +5
-0+ }while (0 ) + +void test_strvec__init(void) +{ +struct strvec vec =STRVEC_INIT ;If we're rewriting the tests perhaps we can take the opportunity to add a blank line to each one after the variable declarations in accordance with our coding guidelines.
Can do.
Quoted 2 lines
It might be a good opportunity to show the set-up and tear-down facilities in clar as well instead of repeating the initialization in each test.
I don't think it's a good fit here, as setup and teardown would hit the system under test. I rather think they should be used in cases where you e.g. always have to setup a repository for your tests.
Patrick
Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
Hi Patrick
Quoted 10 lines · Patrick Steinhardt
Quoted 5 lines · Phillip Wood
Hi Patrick
On 20/08/2024 15:02, Patrick Steinhardt wrote:
It might be a good opportunity to show the set-up and tear-down facilities in clar as well instead of repeating the initialization in each test.I don't think it's a good fit here, as setup and teardown would hit the system under test. I rather think they should be used in cases where you e.g. always have to setup a repository for your tests.
I'm not sure I follow. I was suggesting we define test_strvec__initialize() to initialize a global strvec which the tests use and is then freed by test_strvec__cleanup() like the tests/adding.c example the clar's README.md. That would allow use to remove the setup and teardown from each test. As I understand it clar's setup/cleanup functionality is usable without setting up a sandbox directory for each test.
I'll take a look at v7 in the next few days - I suspect we're getting to the point where it's ready to be merged.
Best Wishes
Phillip
Quoted 1 line
Patrick
Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
Quoted 14 lines · On Tue, Sep 03, 2024 at 10:48:01AM +0100, [email protected] wrote:
Hi Patrick
Quoted 10 lines · Patrick Steinhardt
Quoted 5 lines · Phillip Wood
Hi Patrick
On 20/08/2024 15:02, Patrick Steinhardt wrote:
It might be a good opportunity to show the set-up and tear-down facilities in clar as well instead of repeating the initialization in each test.I don't think it's a good fit here, as setup and teardown would hit the system under test. I rather think they should be used in cases where you e.g. always have to setup a repository for your tests.
Quoted 6 lines
I'm not sure I follow. I was suggesting we define
test_strvec__initialize()to initialize a global strvec which the tests use and is then freed bytest_strvec__cleanup()like thetests/adding.cexample the clar's README.md. That would allow use to remove the setup and teardown from each test. As I understand it clar's setup/cleanup functionality is usable without setting up a sandbox directory for each test.
What I'm saying is that strvec_init() itself is part of the system under test, so evicting that into a __initialize() function doesn't quite make sense to me. If there was for example a bug somewhere in the strvec code we might bring the global struct strvec into a state that is completely unusable, and thus all subsequent tests would fail. We could of course work around that by always zeroing out the struct, but because of that I just don't think it's a good fit.
I rather see the usefulness of __initialize() in setting up auxiliary data structures that are a dependency of the system under test, but which are not the system under test itself.
Quoted 2 lines
I'll take a look at v7 in the next few days - I suspect we're getting to the point where it's ready to be merged.
Thanks!
Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
Quoted 18 lines · Patrick Steinhardt
Quoted 10 lines · On Tue, Sep 03, 2024 at 10:48:01AM +0100, [email protected] wrote:
Quoted 8 lines · Patrick Steinhardt
Quoted 3 lines · Phillip Wood
On 20/08/2024 15:02, Patrick Steinhardt wrote:
It might be a good opportunity to show the set-up and tear-down facilities in clar as well instead of repeating the initialization in each test.I don't think it's a good fit here, as setup and teardown would hit the system under test. I rather think they should be used in cases where you e.g. always have to setup a repository for your tests.
Quoted 6 lines
I'm not sure I follow. I was suggesting we define
test_strvec__initialize()to initialize a global strvec which the tests use and is then freed bytest_strvec__cleanup()like thetests/adding.cexample the clar's README.md. That would allow use to remove the setup and teardown from each test. As I understand it clar's setup/cleanup functionality is usable without setting up a sandbox directory for each test.
Quoted 2 lines
What I'm saying is that
strvec_init()itself is part of the system under test,
Oh, I see - I'd misunderstood what you meant by "system under test"
Quoted 6 lines
so evicting that into a
__initialize()function doesn't quite make sense to me. If there was for example a bug somewhere in the strvec code we might bring the globalstruct strvecinto a state that is completely unusable, and thus all subsequent tests would fail. We could of course work around that by always zeroing out the struct, but because of that I just don't think it's a good fit.
Isn't the point of strvec_init() to ensure that the struct strvec passed to it is usable? If it does not do that then having all the tests fail is good as it tells us there is a bug. I was hoping that __initialize() would be a useful replacement for the
TEST(setup(test_fn()), "description")
pattern in some of our existing tests. I find hoisting the common initialization and cleanup out of each tests makes them easier to review as I can review the setup code once and not worry about it when reviewing each test. It also avoids a lot of repetition when writing tests and keeps the test bodies concise.
Quoted 3 lines
I rather see the usefulness of
__initialize()in setting up auxiliary data structures that are a dependency of the system under test, but which are not the system under test itself.
That's certainly a good use for them
Best Wishes
Phillip
Quoted 4 lines
Quoted 2 lines
I'll take a look at v7 in the next few days - I suspect we're getting to the point where it's ready to be merged.
Thanks!
PATCH v6 12/13 t/unit-tests: convert ctype tests to use clar
In reply to Patrick SteinhardtConvert the ctype tests to use the new clar unit testing framework. Introduce a new function cl_failf() that allows us to print a formatted error message, which we can use to point out which of the characters was classified incorrectly. This results in output like this on failure:
# start of suite 1: ctype
ok 1 - ctype::isspace
not ok 2 - ctype::isdigit
---
reason: |
Test failed.
0x61 is classified incorrectly
at:
file: 't/unit-tests/ctype.c'
line: 38
function: 'test_ctype__isdigit'
---
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 70 ++++++++++++++++++++++++-----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 67 insertions(+), 12 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (70%)Makefile +1 -1
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += ctype UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o -UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash UNIT_TEST_PROGRAMS += t-hashmap
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +59 -11
96 lines not on this page · raw message
t/unit-tests/unit-test.h +7 -0
@@ -1,3 +1,10 @@#include "git-compat-util.h" #include "clar/clar.h" #include "clar-decls.h" +#include "strbuf.h" + +#define cl_failf (fmt, ...) do { \ + char *desc = xstrfmt(fmt, __VA_ARGS__); \ + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ + free(desc); \ +} while (0)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
Re: [PATCH v6 12/13] t/unit-tests: convert ctype tests to use clar
Hi Patrick
Quoted 2 lines · Patrick Steinhardt
Convert the ctype tests to use the new clar unit testing framework. Introduce a new function
cl_failf()
This is a nice addition which somewhat mitigates the lack of an equivalent to test_msg() that adds addition context messages to test failures.
Quoted 11 lines
that allows us to print a
formatted error message, which we can use to point out which of the characters was classified incorrectly. This results in output like this on failure:# start of suite 1: ctype ok 1 - ctype::isspace not ok 2 - ctype::isdigit --- reason: | Test failed.
"Test failed." is not the reason for the test failure
Quoted 6 lines
0x61 is classified incorrectly
at:
file: 't/unit-tests/ctype.c'
line: 38
function: 'test_ctype__isdigit'
---This is rather verbose compared to the current framework
# check "isdigit(i) == !!memchr("123456789", i, len)" failed at t/unit-tests/t-ctype.c:36
# left: 1
# right: 0
# i: 0x30
not ok 2 - isdigit worksThe current tests also shows which characters are expected to return true and distinguishes between the two possible failure modes which are (a) misclassification and (b) returning a non-zero integer other than '1' as "true". The new test output does not allow the person running the test to distinguish between these two failure modes.
t/unit-tests/unit-test.h line 7
Quoted 12 lines
t/unit-tests/unit-test.h +4
-0@@ -1,3 +1,10 @@#include "git-compat-util.h" #include "clar/clar.h" #include "clar-decls.h" +#include "strbuf.h" + +#define cl_failf (fmt, ...) do { \ + char *desc = xstrfmt(fmt, __VA_ARGS__); \
In our current framework we avoid relying on the strbuf api and functions like xstrfmt() that use it as we want to be able to test strbuf.c with the framework. We'd be better with a macro wrapping a new function that uses a stack allocated buffer and p_snprintf() like clar__assert_equal(). That would allow us to upstream this change as well.
t/unit-tests/unit-test.h line 9
Quoted 2 lines
t/unit-tests/unit-test.h +2
-0+clar__fail (__FILE__, __func__, __LINE__,"Test failed." , desc,1 ); \ +free (desc); \
This is leaked on failure due to the use of longjmp.
Best Wishes
Phillip
Quoted 1 line
t/unit-tests/unit-test.h +1
-0+}while (0 )
Re: [PATCH v6 12/13] t/unit-tests: convert ctype tests to use clar
Quoted 14 lines · Phillip Wood
Hi Patrick
Quoted 2 lines · Patrick Steinhardt
Convert the ctype tests to use the new clar unit testing framework. Introduce a new function
cl_failf()This is a nice addition which somewhat mitigates the lack of an equivalent to
test_msg()that adds addition context messages to test failures.Quoted 5 lines
that allows us to print a
formatted error message, which we can use to point out which of the characters was classified incorrectly. This results in output like this on failure:
Quoted 8 lines
Quoted 6 lines
# start of suite 1: ctype ok 1 - ctype::isspace not ok 2 - ctype::isdigit --- reason: | Test failed."Test failed." is not the reason for the test failure
The clar expects two strings, one "general" description and the details. I agree that it's a bit shoddy to have this as part of the reason, it really should be a separate "field". But it allows us to distinguish e.g. between test failures and tests which emitted a warning, only.
Quoted 8 lines
Quoted 6 lines
0x61 is classified incorrectly at: file: 't/unit-tests/ctype.c' line: 38 function: 'test_ctype__isdigit' ---This is rather verbose compared to the current framework
I guess this is a matter of taste. I actually prefer the more verbose style.
Quoted 7 lines
# check "isdigit(i) == !!memchr("123456789", i, len)" failed at
t/unit-tests/t-ctype.c:36
# left: 1
# right: 0
# i: 0x30
not ok 2 - isdigit worksQuoted 5 lines
The current tests also shows which characters are expected to return true and distinguishes between the two possible failure modes which are (a) misclassification and (b) returning a non-zero integer other than '1' as "true". The new test output does not allow the person running the test to distinguish between these two failure modes.
This one is true though. Will amend.
Quoted 10 lines
Quoted 10 lines
t/unit-tests/unit-test.h +2
-0@@ -1,3 +1,10 @@#include "git-compat-util.h" #include "clar/clar.h" #include "clar-decls.h" +#include "strbuf.h" +
Quoted 8 lines
t/unit-tests/unit-test.h line 7
Quoted 2 lines
t/unit-tests/unit-test.h +2
-0+#define cl_failf (fmt, ...) do { \ + char *desc = xstrfmt(fmt, __VA_ARGS__); \In our current framework we avoid relying on the strbuf api and functions like
xstrfmt()that use it as we want to be able to teststrbuf.cwith the framework. We'd be better with a macro wrapping a new function that uses a stack allocated buffer andp_snprintf()likeclar__assert_equal(). That would allow us to upstream this change as well.
I don't think it's all that important to avoid a low-level API like xstrfmt(). But the second argument makes sense to me indeed.
Quoted 4 lines
t/unit-tests/unit-test.h line 9
Quoted 2 lines
t/unit-tests/unit-test.h +2
-0+clar__fail (__FILE__, __func__, __LINE__,"Test failed." , desc,1 ); \ +free (desc); \This is leaked on failure due to the use of longjmp.
As discussed elsewhere I don't think this leak matters all that much. But it's getting fixed with your proposal, so that's that.
Patrick
PATCH v6 13/13 clar: add CMake support
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
Now that we're using clar as powerful test framework, we have to adjust the Visual C build (read: the CMake definition) to be able to handle that, too.
--- contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
contrib/buildsystems/CMakeLists.txt +53 -0
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS}) endif() endforeach() +parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "") + +set(clar_decls "") +set(clar_cbs "") +set(clar_cbs_count 0) +set(clar_suites "static struct clar_suite _clar_suites[] = {\n") +list(LENGTH unit_tests_SUITES clar_suites_count) +foreach(suite ${unit_tests_SUITES}) + file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls + REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$") + + list(LENGTH decls decls_count) + string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls}) + string(JOIN "" cbs ${cbs}) + list(TRANSFORM decls PREPEND "extern ") + string(JOIN ";\n" decls ${decls}) + + string(APPEND clar_decls "${decls};\n") + string(APPEND clar_cbs + "static const struct clar_func _clar_cb_${suite}[] = {\n" + ${cbs} + "};\n") + string(APPEND clar_suites + " {\n" + " \"${suite}\",\n" + " { NULL, NULL },\n" + " { NULL, NULL },\n" + " _clar_cb_${suite}, ${decls_count}, 1\n" + " },\n") + math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}") +endforeach() +string(APPEND clar_suites + "};\n" + "static const size_t _clar_suite_count = ${clar_suites_count};\n" + "static const size_t _clar_callback_count = ${clar_cbs_count};\n") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}") + +list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/") +list(TRANSFORM unit_tests_SUITES APPEND ".c") +add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c") +target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests") +add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c") +target_link_libraries(unit-tests unit-tests-lib common-main) +set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +if(MSVC) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +endif() + #test-tool parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
Quoted 4 lines · Patrick Steinhardt
Hi,
this is another version of my patch series that introduces the clar testing framework for our C unit tests.
I've left some comments on the code, most of them are points I made in the last round that received no response. My main concern is that the assertions offered by clar are not as convinent as the check_* macros.
What's the plan for converting our current tests if this gets merged? If we were to add wrappers that provide check_int() etc. that would greatly simplify the conversion. I think it would offer a more ergonomic api for writing new tests than the verbose and non-typesafe cl_assert_equal_i() and friends.
Best Wishes
Phillip
Quoted 896 lines
Changes compared to v5:
- Rebased on top of
masterto fix some merge conflicts. This seriesis now built on bb9c16bd4f (The sixth batch, 2024-08-19).- Introduce
cl_failf()as a nice way to print formatted error messages.- NULL-terminate the argv array again and move the changes into the correct patch.
- Most of the changes were done by Dscho to port clar over to Windows, including CMake build instructions to make this usable with MSVC. I plan to upstream these changes when this series lands in Git.
- Introduce CMake support, also by Dscho.
Thanks for all the feedback, and thanks to Dscho for helping out with porting this to Windows.
GitHub pipeline: https://github.com/git/git/actions/runs/10472008177/
GitLab pipeline: https://gitlab.com/gitlab-org/git/-/pipelines/1419946971Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unusedfs_copy()
clar: stop includingshellapi.hunnecessarily
clar: add CMake supportPatrick Steinhardt (9):
t: do not passGIT_TEST_OPTSto unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency onGENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar.gitignore | 1 + Documentation/technical/unit-tests.txt | 2 + Makefile | 53 +- contrib/buildsystems/CMakeLists.txt | 53 ++ t/Makefile | 4 +- t/run-test.sh | 2 +- t/unit-tests/.gitignore | 2 + t/unit-tests/clar-generate.awk | 50 ++ t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 524 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 159 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ t/unit-tests/{t-ctype.c => ctype.c} | 70 +- t/unit-tests/strvec.c | 222 ++++++ t/unit-tests/t-strvec.c | 211 ------ t/unit-tests/unit-test.c | 18 + t/unit-tests/unit-test.h | 10 + 31 files changed, 3409 insertions(+), 235 deletions(-)create mode 100644
t/unit-tests/clar-generate.awkcreate mode 100644t/unit-tests/clar/.github/workflows/ci.ymlcreate mode 100644 t/unit-tests/clar/COPYING
create mode 100644t/unit-tests/clar/README.mdcreate mode 100644t/unit-tests/clar/clar.c
create mode 100644t/unit-tests/clar/clar.h
create mode 100644t/unit-tests/clar/clar/fixtures.hcreate mode 100644t/unit-tests/clar/clar/fs.hcreate mode 100644t/unit-tests/clar/clar/print.hcreate mode 100644t/unit-tests/clar/clar/sandbox.hcreate mode 100644t/unit-tests/clar/clar/summary.hcreate mode 100755t/unit-tests/clar/generate.pycreate mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644t/unit-tests/clar/test/clar_test.hcreate mode 100644t/unit-tests/clar/test/main.ccreate mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644t/unit-tests/clar/test/sample.crenamet/unit-tests/{t-ctype.c=>ctype.c} (70%) create mode 100644t/unit-tests/strvec.c
delete mode 100644t/unit-tests/t-strvec.c
create mode 100644t/unit-tests/unit-test.c
create mode 100644t/unit-tests/unit-test.hRange-diff against v5:
1: 832dc0496fb = 1: e48a6461137 t: do not pass GIT_TEST_OPTS to unit tests with prove 2: 36906079330 = 2: 1710e9f9ff7 t: import the clar unit testing framework 3: db53673294e = 3: 5c21aa87aa2 t/clar: fix compatibility with NonStop -: ----------- > 4: 06d2bce0d82 clar: avoid compile error with mingw-w64 -: ----------- > 5: f88b3421a09 clar(win32): avoid compile error due to unused `fs_copy()` -: ----------- > 6: 5fb4c55be33 clar: stop including `shellapi.h` unnecessarily 4: b6199c88dd7 = 7: e0dcbd5ca83 Makefile: fix sparse dependency on GENERATED_H 5: 06364b2b722 = 8: 77a03f8df70 Makefile: make hdr-check depend on generated headers 6: 88ea94ce16c = 9: c91dd7327e3 Makefile: do not use sparse on third-party sources 7: 05bcb5bef6c = 10: 115c15aa9ae Makefile: wire up the clar unit testing framework 8: 8f56b4d6264 ! 11: b3b8df04872 t/unit-tests: convert strvec tests to use clar @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/% UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o -@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged - UNIT_TEST_PROGRAMS += t-reftable-record +@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-record + UNIT_TEST_PROGRAMS += t-reftable-tree UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strcmp-offset -UNIT_TEST_PROGRAMS += t-strvec @@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS)) - ## t/unit-tests/t-strvec.c => t/unit-tests/strvec.c ## + ## t/unit-tests/strvec.c (new) ## @@ --#include "test-lib.h" +#include "unit-test.h" - #include "strbuf.h" - #include "strvec.h" - - #define check_strvec(vec, ...) \ - do { \ - const char *expect[] = { __VA_ARGS__ }; \ -- if (check_uint(ARRAY_SIZE(expect), >, 0) && \ -- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \ -- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \ -- check_uint((vec)->nr, <=, (vec)->alloc)) { \ -- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \ -- if (!check_str((vec)->v[i], expect[i])) { \ -- test_msg(" i: %"PRIuMAX, \ -- (uintmax_t)i); \ -- break; \ -- } \ -- } \ -- } \ ++#include "strbuf.h" ++#include "strvec.h" ++ ++#define check_strvec(vec, ...) \ ++ do { \ ++ const char *expect[] = { __VA_ARGS__ }; \ + cl_assert(ARRAY_SIZE(expect) > 0); \ + cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \ + cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \ + cl_assert((vec)->nr <= (vec)->alloc); \ + for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \ + cl_assert_equal_s((vec)->v[i], expect[i]); \ - } while (0) - --static void t_static_init(void) ++ } while (0) ++ +void test_strvec__init(void) - { - struct strvec vec = STRVEC_INIT; -- check_pointer_eq(vec.v, empty_strvec); -- check_uint(vec.nr, ==, 0); -- check_uint(vec.alloc, ==, 0); ++{ ++ struct strvec vec = STRVEC_INIT; + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); + cl_assert_equal_i(vec.alloc, 0); - } - --static void t_dynamic_init(void) ++} ++ +void test_strvec__dynamic_init(void) - { - struct strvec vec; - strvec_init(&vec); -- check_pointer_eq(vec.v, empty_strvec); -- check_uint(vec.nr, ==, 0); -- check_uint(vec.alloc, ==, 0); ++{ ++ struct strvec vec; ++ strvec_init(&vec); + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); + cl_assert_equal_i(vec.alloc, 0); - } - --static void t_clear(void) ++} ++ +void test_strvec__clear(void) - { - struct strvec vec = STRVEC_INIT; - strvec_push(&vec, "foo"); - strvec_clear(&vec); -- check_pointer_eq(vec.v, empty_strvec); -- check_uint(vec.nr, ==, 0); -- check_uint(vec.alloc, ==, 0); ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_push(&vec, "foo"); ++ strvec_clear(&vec); + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); + cl_assert_equal_i(vec.alloc, 0); - } - --static void t_push(void) ++} ++ +void test_strvec__push(void) - { - struct strvec vec = STRVEC_INIT; - -@@ t/unit-tests/strvec.c: static void t_push(void) - strvec_clear(&vec); - } - --static void t_pushf(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ ++ strvec_push(&vec, "foo"); ++ check_strvec(&vec, "foo", NULL); ++ ++ strvec_push(&vec, "bar"); ++ check_strvec(&vec, "foo", "bar", NULL); ++ ++ strvec_clear(&vec); ++} ++ +void test_strvec__pushf(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushf(&vec, "foo: %d", 1); -@@ t/unit-tests/strvec.c: static void t_pushf(void) - strvec_clear(&vec); - } - --static void t_pushl(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushf(&vec, "foo: %d", 1); ++ check_strvec(&vec, "foo: 1", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__pushl(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_pushl(void) - strvec_clear(&vec); - } - --static void t_pushv(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ check_strvec(&vec, "foo", "bar", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__pushv(void) - { - const char *strings[] = { - "foo", "bar", "baz", NULL, -@@ t/unit-tests/strvec.c: static void t_pushv(void) - strvec_clear(&vec); - } - --static void t_replace_at_head(void) ++{ ++ const char *strings[] = { ++ "foo", "bar", "baz", NULL, ++ }; ++ struct strvec vec = STRVEC_INIT; ++ ++ strvec_pushv(&vec, strings); ++ check_strvec(&vec, "foo", "bar", "baz", NULL); ++ ++ strvec_clear(&vec); ++} ++ +void test_strvec__replace_at_head(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_replace_at_head(void) - strvec_clear(&vec); - } - --static void t_replace_at_tail(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_replace(&vec, 0, "replaced"); ++ check_strvec(&vec, "replaced", "bar", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__replace_at_tail(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_replace_at_tail(void) - strvec_clear(&vec); - } - --static void t_replace_in_between(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_replace(&vec, 2, "replaced"); ++ check_strvec(&vec, "foo", "bar", "replaced", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__replace_in_between(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_replace_in_between(void) - strvec_clear(&vec); - } - --static void t_replace_with_substring(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_replace(&vec, 1, "replaced"); ++ check_strvec(&vec, "foo", "replaced", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__replace_with_substring(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", NULL); -@@ t/unit-tests/strvec.c: static void t_replace_with_substring(void) - strvec_clear(&vec); - } - --static void t_remove_at_head(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", NULL); ++ strvec_replace(&vec, 0, vec.v[0] + 1); ++ check_strvec(&vec, "oo", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__remove_at_head(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_remove_at_head(void) - strvec_clear(&vec); - } - --static void t_remove_at_tail(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_remove(&vec, 0); ++ check_strvec(&vec, "bar", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__remove_at_tail(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_remove_at_tail(void) - strvec_clear(&vec); - } - --static void t_remove_in_between(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_remove(&vec, 2); ++ check_strvec(&vec, "foo", "bar", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__remove_in_between(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_remove_in_between(void) - strvec_clear(&vec); - } - --static void t_pop_empty_array(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_remove(&vec, 1); ++ check_strvec(&vec, "foo", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__pop_empty_array(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pop(&vec); -@@ t/unit-tests/strvec.c: static void t_pop_empty_array(void) - strvec_clear(&vec); - } - --static void t_pop_non_empty_array(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pop(&vec); ++ check_strvec(&vec, NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__pop_non_empty_array(void) - { - struct strvec vec = STRVEC_INIT; - strvec_pushl(&vec, "foo", "bar", "baz", NULL); -@@ t/unit-tests/strvec.c: static void t_pop_non_empty_array(void) - strvec_clear(&vec); - } - --static void t_split_empty_string(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL); ++ strvec_pop(&vec); ++ check_strvec(&vec, "foo", "bar", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__split_empty_string(void) - { - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, ""); -@@ t/unit-tests/strvec.c: static void t_split_empty_string(void) - strvec_clear(&vec); - } - --static void t_split_single_item(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_split(&vec, ""); ++ check_strvec(&vec, NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__split_single_item(void) - { - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo"); -@@ t/unit-tests/strvec.c: static void t_split_single_item(void) - strvec_clear(&vec); - } - --static void t_split_multiple_items(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_split(&vec, "foo"); ++ check_strvec(&vec, "foo", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__split_multiple_items(void) - { - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo bar baz"); -@@ t/unit-tests/strvec.c: static void t_split_multiple_items(void) - strvec_clear(&vec); - } - --static void t_split_whitespace_only(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_split(&vec, "foo bar baz"); ++ check_strvec(&vec, "foo", "bar", "baz", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__split_whitespace_only(void) - { - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, " \t\n"); -@@ t/unit-tests/strvec.c: static void t_split_whitespace_only(void) - strvec_clear(&vec); - } - --static void t_split_multiple_consecutive_whitespaces(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_split(&vec, " \t\n"); ++ check_strvec(&vec, NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__split_multiple_consecutive_whitespaces(void) - { - struct strvec vec = STRVEC_INIT; - strvec_split(&vec, "foo\n\t bar"); -@@ t/unit-tests/strvec.c: static void t_split_multiple_consecutive_whitespaces(void) - strvec_clear(&vec); - } - --static void t_detach(void) ++{ ++ struct strvec vec = STRVEC_INIT; ++ strvec_split(&vec, "foo\n\t bar"); ++ check_strvec(&vec, "foo", "bar", NULL); ++ strvec_clear(&vec); ++} ++ +void test_strvec__detach(void) - { - struct strvec vec = STRVEC_INIT; - const char **detached; -@@ t/unit-tests/strvec.c: static void t_detach(void) - strvec_push(&vec, "foo"); - - detached = strvec_detach(&vec); -- check_str(detached[0], "foo"); -- check_pointer_eq(detached[1], NULL); ++{ ++ struct strvec vec = STRVEC_INIT; ++ const char **detached; ++ ++ strvec_push(&vec, "foo"); ++ ++ detached = strvec_detach(&vec); + cl_assert_equal_s(detached[0], "foo"); + cl_assert_equal_p(detached[1], NULL); - -- check_pointer_eq(vec.v, empty_strvec); -- check_uint(vec.nr, ==, 0); -- check_uint(vec.alloc, ==, 0); ++ + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); + cl_assert_equal_i(vec.alloc, 0); - - free((char *) detached[0]); - free(detached); - } ++ ++ free((char *) detached[0]); ++ free(detached); ++} + + ## t/unit-tests/t-strvec.c (deleted) ## +@@ +-#include "test-lib.h" +-#include "strbuf.h" +-#include "strvec.h" +- +-#define check_strvec(vec, ...) \ +- do { \ +- const char *expect[] = { __VA_ARGS__ }; \ +- if (check_uint(ARRAY_SIZE(expect), >, 0) && \ +- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \ +- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \ +- check_uint((vec)->nr, <=, (vec)->alloc)) { \ +- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \ +- if (!check_str((vec)->v[i], expect[i])) { \ +- test_msg(" i: %"PRIuMAX, \ +- (uintmax_t)i); \ +- break; \ +- } \ +- } \ +- } \ +- } while (0) - -int cmd_main(int argc, const char **argv) -{ -- TEST(t_static_init(), "static initialization"); -- TEST(t_dynamic_init(), "dynamic initialization"); -- TEST(t_clear(), "clear"); -- TEST(t_push(), "push"); -- TEST(t_pushf(), "pushf"); -- TEST(t_pushl(), "pushl"); -- TEST(t_pushv(), "pushv"); -- TEST(t_replace_at_head(), "replace at head"); -- TEST(t_replace_in_between(), "replace in between"); -- TEST(t_replace_at_tail(), "replace at tail"); -- TEST(t_replace_with_substring(), "replace with substring"); -- TEST(t_remove_at_head(), "remove at head"); -- TEST(t_remove_in_between(), "remove in between"); -- TEST(t_remove_at_tail(), "remove at tail"); -- TEST(t_pop_empty_array(), "pop with empty array"); -- TEST(t_pop_non_empty_array(), "pop with non-empty array"); -- TEST(t_split_empty_string(), "split empty string"); -- TEST(t_split_single_item(), "split single item"); -- TEST(t_split_multiple_items(), "split multiple items"); -- TEST(t_split_whitespace_only(), "split whitespace only"); -- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces"); -- TEST(t_detach(), "detach"); +- if_test ("static initialization") { +- struct strvec vec = STRVEC_INIT; +- check_pointer_eq(vec.v, empty_strvec); +- check_uint(vec.nr, ==, 0); +- check_uint(vec.alloc, ==, 0); +- } +- +- if_test ("dynamic initialization") { +- struct strvec vec; +- strvec_init(&vec); +- check_pointer_eq(vec.v, empty_strvec); +- check_uint(vec.nr, ==, 0); +- check_uint(vec.alloc, ==, 0); +- } +- +- if_test ("clear") { +- struct strvec vec = STRVEC_INIT; +- strvec_push(&vec, "foo"); +- strvec_clear(&vec); +- check_pointer_eq(vec.v, empty_strvec); +- check_uint(vec.nr, ==, 0); +- check_uint(vec.alloc, ==, 0); +- } +- +- if_test ("push") { +- struct strvec vec = STRVEC_INIT; +- +- strvec_push(&vec, "foo"); +- check_strvec(&vec, "foo", NULL); +- +- strvec_push(&vec, "bar"); +- check_strvec(&vec, "foo", "bar", NULL); +- +- strvec_clear(&vec); +- } +- +- if_test ("pushf") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushf(&vec, "foo: %d", 1); +- check_strvec(&vec, "foo: 1", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("pushl") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- check_strvec(&vec, "foo", "bar", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("pushv") { +- const char *strings[] = { +- "foo", "bar", "baz", NULL, +- }; +- struct strvec vec = STRVEC_INIT; +- +- strvec_pushv(&vec, strings); +- check_strvec(&vec, "foo", "bar", "baz", NULL); +- +- strvec_clear(&vec); +- } +- +- if_test ("replace at head") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_replace(&vec, 0, "replaced"); +- check_strvec(&vec, "replaced", "bar", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("replace at tail") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_replace(&vec, 2, "replaced"); +- check_strvec(&vec, "foo", "bar", "replaced", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("replace in between") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_replace(&vec, 1, "replaced"); +- check_strvec(&vec, "foo", "replaced", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("replace with substring") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", NULL); +- strvec_replace(&vec, 0, vec.v[0] + 1); +- check_strvec(&vec, "oo", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("remove at head") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_remove(&vec, 0); +- check_strvec(&vec, "bar", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("remove at tail") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_remove(&vec, 2); +- check_strvec(&vec, "foo", "bar", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("remove in between") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_remove(&vec, 1); +- check_strvec(&vec, "foo", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("pop with empty array") { +- struct strvec vec = STRVEC_INIT; +- strvec_pop(&vec); +- check_strvec(&vec, NULL); +- strvec_clear(&vec); +- } +- +- if_test ("pop with non-empty array") { +- struct strvec vec = STRVEC_INIT; +- strvec_pushl(&vec, "foo", "bar", "baz", NULL); +- strvec_pop(&vec); +- check_strvec(&vec, "foo", "bar", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("split empty string") { +- struct strvec vec = STRVEC_INIT; +- strvec_split(&vec, ""); +- check_strvec(&vec, NULL); +- strvec_clear(&vec); +- } +- +- if_test ("split single item") { +- struct strvec vec = STRVEC_INIT; +- strvec_split(&vec, "foo"); +- check_strvec(&vec, "foo", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("split multiple items") { +- struct strvec vec = STRVEC_INIT; +- strvec_split(&vec, "foo bar baz"); +- check_strvec(&vec, "foo", "bar", "baz", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("split whitespace only") { +- struct strvec vec = STRVEC_INIT; +- strvec_split(&vec, " \t\n"); +- check_strvec(&vec, NULL); +- strvec_clear(&vec); +- } +- +- if_test ("split multiple consecutive whitespaces") { +- struct strvec vec = STRVEC_INIT; +- strvec_split(&vec, "foo\n\t bar"); +- check_strvec(&vec, "foo", "bar", NULL); +- strvec_clear(&vec); +- } +- +- if_test ("detach") { +- struct strvec vec = STRVEC_INIT; +- const char **detached; +- +- strvec_push(&vec, "foo"); +- +- detached = strvec_detach(&vec); +- check_str(detached[0], "foo"); +- check_pointer_eq(detached[1], NULL); +- +- check_pointer_eq(vec.v, empty_strvec); +- check_uint(vec.nr, ==, 0); +- check_uint(vec.alloc, ==, 0); +- +- free((char *) detached[0]); +- free(detached); +- } +- - return test_done(); -} - - ## t/unit-tests/unit-test.c ## -@@ t/unit-tests/unit-test.c: int cmd_main(int argc, const char **argv) - int ret; - - /* Append the "-t" flag such that the tests generate TAP output. */ -- ALLOC_ARRAY(argv_copy, argc + 2); -+ ALLOC_ARRAY(argv_copy, argc + 1); - COPY_ARRAY(argv_copy, argv, argc); - argv_copy[argc++] = "-t"; -- argv_copy[argc] = NULL; - - ret = clar_test(argc, (char **) argv_copy); - 9: ca09d19fd51 ! 12: 1ac2e48a7f2 t/unit-tests: convert ctype tests to use clar @@ Commit message t/unit-tests: convert ctype tests to use clar Convert the ctype tests to use the new clar unit testing framework. + Introduce a new function `cl_failf()` that allows us to print a + formatted error message, which we can use to point out which of the + characters was classified incorrectly. This results in output like this + on failure: + + # start of suite 1: ctype + ok 1 - ctype::isspace + not ok 2 - ctype::isdigit + --- + reason: | + Test failed. + 0x61 is classified incorrectly + at: + file: 't/unit-tests/ctype.c' + line: 38 + function: 'test_ctype__isdigit' + --- + ok 3 - ctype::isalpha + ok 4 - ctype::isalnum + ok 5 - ctype::is_glob_special + ok 6 - ctype::is_regex_special + ok 7 - ctype::is_pathspec_magic + ok 8 - ctype::isascii + ok 9 - ctype::islower + ok 10 - ctype::isupper + ok 11 - ctype::iscntrl + ok 12 - ctype::ispunct + ok 13 - ctype::isxdigit + ok 14 - ctype::isprint Signed-off-by: Patrick Steinhardt <[email protected]> @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/% -UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash - UNIT_TEST_PROGRAMS += t-mem-pool + UNIT_TEST_PROGRAMS += t-hashmap ## t/unit-tests/t-ctype.c => t/unit-tests/ctype.c ## @@ @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c size_t len = ARRAY_SIZE(string) - 1 + \ BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \ BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \ -- int skip = test__run_begin(); \ -- if (!skip) { \ +- if_test (#class " works") { \ - for (int i = 0; i < 256; i++) { \ - if (!check_int(class(i), ==, !!memchr(string, i, len)))\ - test_msg(" i: 0x%02x", i); \ - } \ - check(!class(EOF)); \ - } \ -- test__run_end(!skip, TEST_LOCATION(), #class " works"); \ + for (int i = 0; i < 256; i++) \ -+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \ ++ if (class(i) != !!memchr(string, i, len)) \ ++ cl_failf("0x%02x is classified incorrectly", i); \ + cl_assert(!class(EOF)); \ } while (0) @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c +{ + TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); } + + ## t/unit-tests/unit-test.h ## +@@ + #include "git-compat-util.h" + #include "clar/clar.h" + #include "clar-decls.h" ++#include "strbuf.h" ++ ++#define cl_failf(fmt, ...) do { \ ++ char *desc = xstrfmt(fmt, __VA_ARGS__); \ ++ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ ++ free(desc); \ ++} while (0)-: ----------- > 13:
131036c398eclar: add CMake support
Quoted 9 lines · Phillip Wood
Quoted 4 lines · Patrick Steinhardt
Hi,
this is another version of my patch series that introduces the clar testing framework for our C unit tests.
I've left some comments on the code, most of them are points I made in the last round that received no response. My main concern is that the assertions offered by clar are not as convinent as the check_* macros.
Did you have a look at my reply at <ZsSTOczuGhT5zwoV@tanuki>, where I responded to these concerns?
In summary: I'm aware that this is still a rough edge. I'd be happy to follow up on this and improve usability of the assertions, but doing it likely is a bit more involved, mostly because I want to upstream all changes in this context. So I'd rather want to land a basic version first, and then I'd iterate and improve asserts.
Quoted 5 lines
What's the plan for converting our current tests if this gets merged? If we were to add wrappers that provide
check_int()etc. that would greatly simplify the conversion. I think it would offer a more ergonomic api for writing new tests than the verbose and non-typesafecl_assert_equal_i()and friends.
My plan would first be to let things cook for a bit while I sort out the rough spots upstream. Once done and once we are sufficiently sure that this is the direction to go I'm happy to do the conversion myself.
Whether we want to have wrappers... I dunno, I don't think the names are all that bad. They have a clear namespace and say rather directly what they are doing, which I value more than briefness (to a certain extent, of course). The type safety is another topic though, and something I will aim to address.
Thanks!
Patrick
Hi Patrick
Quoted 7 lines · Patrick Steinhardt
Quoted 6 lines · Phillip Wood
Quoted 4 lines · Patrick Steinhardt
Hi,
this is another version of my patch series that introduces the clar testing framework for our C unit tests.
Quoted 6 lines
Quoted 3 lines
I've left some comments on the code, most of them are points I made in the last round that received no response. My main concern is that the assertions offered by clar are not as convinent as the check_* macros.
Did you have a look at my reply at <ZsSTOczuGhT5zwoV@tanuki>, where I responded to these concerns?
Oh sorry I'd completely missed that mail (it came in while I was off line and I failed to notice it).
Quoted 5 lines
In summary: I'm aware that this is still a rough edge. I'd be happy to follow up on this and improve usability of the assertions, but doing it likely is a bit more involved, mostly because I want to upstream all changes in this context. So I'd rather want to land a basic version first, and then I'd iterate and improve asserts.
That sounds reasonable
Quoted 6 lines
Quoted 5 lines
What's the plan for converting our current tests if this gets merged? If we were to add wrappers that provide
check_int()etc. that would greatly simplify the conversion. I think it would offer a more ergonomic api for writing new tests than the verbose and non-typesafecl_assert_equal_i()and friends.
Quoted 8 lines
My plan would first be to let things cook for a bit while I sort out the rough spots upstream. Once done and once we are sufficiently sure that this is the direction to go I'm happy to do the conversion myself.
Whether we want to have wrappers... I dunno, I don't think the names are all that bad. They have a clear namespace and say rather directly what they are doing, which I value more than briefness (to a certain extent, of course).
One could argue the check_* are namespaced by "check". I find writing unit tests in C is pretty tedious and having to type cl_assert_equal_? just adds to that.
Quoted 2 lines
The type safety is another topic though, and something I will aim to address.
Thanks, it sounds like we're more or less on the same page
Best Wishes
Phillip
Quoted 3 lines
Thanks!
Patrick
v7 cover Introduce clar testing framework
In reply to Patrick SteinhardtHi,
this is the seventh version fo my patch series that introduces the clar unit testing framework.
Changes compared to v6:
- Add some explanations for why we introduce clar in the first place to the second commit message.
- Split out implementation of the test driver, that is the main function, into a separate commit.
- Reimplement argument parsing ourselves. This allows more flexibility and gets rid of some options that do not make sense for us.
- Add an empty line between declarations and code.
- Improve the test messages in ctype tests to also mention our expectations.
- Adapt cl_failf() to not use xstrfmt(), but snprintf() with a static buffer instead.
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unused fs_copy()
clar: stop including shellapi.h unnecessarily
clar: add CMake support
Patrick Steinhardt (10):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: implement test driver t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/strvec.c | 241 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 45 ++
t/unit-tests/unit-test.h | 10 +
31 files changed, 3457 insertions(+), 234 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.hRange-diff against v6:
1: e48a6461137 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove 2: 1710e9f9ff7 ! 2: 55a9b46e65f t: import the clar unit testing framework
@@ Metadata
## Commit message ##
t: import the clar unit testing framework
+ Our unit testing framework is a homegrown solution. While it supports
+ most of our needs, it is likely that the volume of unit tests will grow
+ quite a bit in the future such that we can exercise low-level subsystems
+ directly. This surfaces several shortcomings that the current solution
+ has:
+
+ - There is no way to run only one specific tests. While some of our
+ unit tests wire this up manually, others don't. In general, it
+ requires quite a bit of boilerplate to get this set up correctly.
+
+ - Failures do not cause a test to stop execution directly. Instead,
+ the test author needs to return manually whenever an assertion
+ fails. This is rather verbose and is not done correctly in most of
+ our unit tests.
+
+ - Wiring up a new testcase requires both implementing the test
+ function and calling it in the respective test suite's main
+ function, which is creating code duplication.
+
+ We can of course fix all of these issues ourselves, but that feels
+ rather pointless when there are already so many unit testing frameworks
+ out there that have those features.
+
+ We line out some requirements for any unit testing framework in
+ "Documentation/technical/unit-tests.txt". The "clar" unit testing
+ framework, which isn't listed in that table yet, ticks many of the
+ boxes:
+
+ - It is licensed under ISC, which is compatible.
+
+ - It is easily vendorable because it is rather tiny at around 1200
+ lines of code.
+
+ - It is easily hackable due to the same reason.
+
+ - It has TAP support.
+
+ - It has skippable tests.
+
+ - It preprocesses test files in order to extract test functions, which
+ then get wired up automatically.
+
+ While it's not perfect, the fact that clar originates from the libgit2
+ project means that it should be rather easy for us to collaborate with
+ upstream to plug any gaps.
+
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
3: 5c21aa87aa2 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
4: 06d2bce0d82 = 4: 658a601c541 clar: avoid compile error with mingw-w64
5: f88b3421a09 = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
6: 5fb4c55be33 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
7: e0dcbd5ca83 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
8: 77a03f8df70 = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
9: c91dd7327e3 = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
10: 115c15aa9ae ! 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
@@ Commit message
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
+ The test driver "unit-test.c" is an empty stub for now. It will get
+ implemented in the next commit.
+
Signed-off-by: Patrick Steinhardt <[email protected]>
## .gitignore ##
@@ t/unit-tests/unit-test.c (new)
@@
+#include "unit-test.h"
+
-+int cmd_main(int argc, const char **argv)
++int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
-+ const char **argv_copy;
-+ int ret;
-+
-+ /* Append the "-t" flag such that the tests generate TAP output. */
-+ ALLOC_ARRAY(argv_copy, argc + 2);
-+ COPY_ARRAY(argv_copy, argv, argc);
-+ argv_copy[argc++] = "-t";
-+ argv_copy[argc] = NULL;
-+
-+ ret = clar_test(argc, (char **) argv_copy);
-+
-+ free(argv_copy);
-+ return ret;
++ return 0;
+}
## t/unit-tests/unit-test.h (new) ##
-: ----------- > 11: 8bd5b3e2b29 t/unit-tests: implement test driver
11: b3b8df04872 ! 12: 3c3b9eacdfb t/unit-tests: convert strvec tests to use clar
@@ Commit message
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
- running that binary, it generates TAP output:
+ running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
@@ Commit message
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
- ok 4 - strvec::push
- ok 5 - strvec::pushft_pushf
+ not ok 4 - strvec::push
+ ---
+ reason: |
+ String mismatch: (&vec)->v[i] != expect[i]
+ 'foo' != 'fo' (at byte 2)
+ at:
+ file: 't/unit-tests/strvec.c'
+ line: 48
+ function: 'test_strvec__push'
+ ---
+ ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
@@ t/unit-tests/strvec.c (new)
+#define check_strvec(vec, ...) \
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
-+ cl_assert(ARRAY_SIZE(expect) > 0); \
-+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
-+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
++ size_t expect_len = ARRAY_SIZE(expect); \
++ cl_assert(expect_len > 0); \
++ cl_assert_equal_p(expect[expect_len - 1], NULL); \
++ cl_assert_equal_i((vec)->nr, expect_len - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
-+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
++ for (size_t i = 0; i < expect_len; i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
+
+void test_strvec__init(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__dynamic_init(void)
+{
+ struct strvec vec;
++
+ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__clear(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_push(&vec, "foo");
+ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pushf(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushf(&vec, "foo: %d", 1);
+ check_strvec(&vec, "foo: 1", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pushl(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 0, "replaced");
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 1, "replaced");
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_with_substring(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", NULL);
+ strvec_replace(&vec, 0, vec.v[0] + 1);
+ check_strvec(&vec, "oo", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 0);
+ check_strvec(&vec, "bar", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 2);
+ check_strvec(&vec, "foo", "bar", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 1);
+ check_strvec(&vec, "foo", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pop_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pop(&vec);
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pop_non_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_pop(&vec);
+ check_strvec(&vec, "foo", "bar", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_empty_string(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_single_item(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_multiple_items(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo bar baz");
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_whitespace_only(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, " \t\n");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo\n\t bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
12: 1ac2e48a7f2 ! 13: c8360db2f86 t/unit-tests: convert ctype tests to use clar
@@ Commit message
on failure:
# start of suite 1: ctype
- ok 1 - ctype::isspace
- not ok 2 - ctype::isdigit
+ not ok 1 - ctype::isspace
---
reason: |
Test failed.
- 0x61 is classified incorrectly
+ 0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
- line: 38
- function: 'test_ctype__isdigit'
+ line: 36
+ function: 'test_ctype__isspace'
---
+ ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
-- } \
-+ for (int i = 0; i < 256; i++) \
-+ if (class(i) != !!memchr(string, i, len)) \
-+ cl_failf("0x%02x is classified incorrectly", i); \
++ for (int i = 0; i < 256; i++) { \
++ int actual = class(i), expect = !!memchr(string, i, len); \
++ if (actual != expect) \
++ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
++ i, expect, actual); \
+ } \
+ cl_assert(!class(EOF)); \
} while (0)
@@ t/unit-tests/unit-test.h
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
-+ char *desc = xstrfmt(fmt, __VA_ARGS__); \
++ char desc[4096]; \
++ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
-+ free(desc); \
+} while (0)13: 131036c398e = 14: d51c146cd9d clar: add CMake support
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 1/14 t: do not pass GIT_TEST_OPTS to unit tests with prove
When using the prove target, we append GIT_TEST_OPTS to the arguments that we execute each of the tests with. This doesn't only include the intended test scripts, but also ends up passing the arguments to our unit tests. This is unintentional though as they do not even know to interpret those arguments, and is inconsistent with how we execute unit tests without prove.
This isn't much of an issue because our current set of unit tests mostly ignore their arguments anyway. With the introduction of clar-based unit tests this is about to become an issue though, as these do parse their command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an environment variable. Like this, we can conditionally forward it to our test scripts, only.
--- t/Makefile | 3 ++- t/run-test.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)
t/Makefile +2 -1
@@ -68,7 +68,8 @@ failed: test -z "$$failed" || $(MAKE) $$failed prove: pre-clean check-chainlint $(TEST_LINT) - @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS) + @echo "*** prove (shell & unit tests) ***" + @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) $(MAKE) clean-except-prove-cache $(T):
t/run-test.sh +1 -1
@@ -10,7 +10,7 @@ case "$1" in echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set" exit 1 fi - exec "${TEST_SHELL_PATH}" "$@" + exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS} ;; *) exec "$@"
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 2/14 t: import the clar unit testing framework
In reply to Patrick SteinhardtOur unit testing framework is a homegrown solution. While it supports most of our needs, it is likely that the volume of unit tests will grow quite a bit in the future such that we can exercise low-level subsystems directly. This surfaces several shortcomings that the current solution has:
- There is no way to run only one specific tests. While some of our unit tests wire this up manually, others don't. In general, it requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead, the test author needs to return manually whenever an assertion fails. This is rather verbose and is not done correctly in most of our unit tests.
- Wiring up a new testcase requires both implementing the test function and calling it in the respective test suite's main function, which is creating code duplication.
We can of course fix all of these issues ourselves, but that feels rather pointless when there are already so many unit testing frameworks out there that have those features.
We line out some requirements for any unit testing framework in "Documentation/technical/unit-tests.txt". The "clar" unit testing framework, which isn't listed in that table yet, ticks many of the boxes:
- It is licensed under ISC, which is compatible.
- It is easily vendorable because it is rather tiny at around 1200 lines of code.
- It is easily hackable due to the same reason.
- It has TAP support.
- It has skippable tests.
- It preprocesses test files in order to extract test functions, which then get wired up automatically.
While it's not perfect, the fact that clar originates from the libgit2 project means that it should be rather easy for us to collaborate with upstream to plug any gaps.
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
--- Documentation/technical/unit-tests.txt | 2 + Makefile | 4 +- t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 522 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 153 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ 20 files changed, 2943 insertions(+), 1 deletion(-) create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml create mode 100644 t/unit-tests/clar/COPYING create mode 100644 t/unit-tests/clar/README.md create mode 100644 t/unit-tests/clar/clar.c create mode 100644 t/unit-tests/clar/clar.h create mode 100644 t/unit-tests/clar/clar/fixtures.h create mode 100644 t/unit-tests/clar/clar/fs.h create mode 100644 t/unit-tests/clar/clar/print.h create mode 100644 t/unit-tests/clar/clar/sandbox.h create mode 100644 t/unit-tests/clar/clar/summary.h create mode 100755 t/unit-tests/clar/generate.py create mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644 t/unit-tests/clar/test/clar_test.h create mode 100644 t/unit-tests/clar/test/main.c create mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644 t/unit-tests/clar/test/sample.c
Documentation/technical/unit-tests.txt +2 -0
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this. :criterion: https://github.com/Snaipe/Criterion[Criterion] :c-tap: https://github.com/rra/c-tap-harness/[C TAP] :check: https://libcheck.github.io/check/[Check] +:clar: https://github.com/clar-test/clar[Clar] [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"] |=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800 {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33 {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973 +{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192 |===== === Additional framework candidates
Makefile +3 -1
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/% THIRD_PARTY_SOURCES += compat/regex/% THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1dc/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o .PHONY: sparse sparse: $(SP_OBJ) -EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% +EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/% ifndef OPENSSL_SHA1 EXCEPT_HDRS += sha1/openssl.h endif
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
@@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + + runs-on: ${{ matrix.os }} + + steps: + - name: Check out + uses: actions/checkout@v2 + - name: Build + run: | + cd test + make
t/unit-tests/clar/COPYING new file +15 -0
@@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2011-2015 Vicent Marti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
t/unit-tests/clar/README.md new file +329 -0
329 lines not on this page · raw message
t/unit-tests/clar/clar.c new file +842 -0
842 lines not on this page · raw message
t/unit-tests/clar/clar.h new file +173 -0
173 lines not on this page · raw message
t/unit-tests/clar/clar/fixtures.h new file +50 -0
50 lines not on this page · raw message
t/unit-tests/clar/clar/fs.h new file +522 -0
522 lines not on this page · raw message
t/unit-tests/clar/clar/print.h new file +211 -0
211 lines not on this page · raw message
t/unit-tests/clar/clar/sandbox.h new file +153 -0
153 lines not on this page · raw message
t/unit-tests/clar/clar/summary.h new file +143 -0
143 lines not on this page · raw message
t/unit-tests/clar/generate.py new file +266 -0
266 lines not on this page · raw message
t/unit-tests/clar/test/.gitignore new file +4 -0
@@ -0,0 +1,4 @@ +clar.suite +.clarcache +clar_test +*.o
t/unit-tests/clar/test/Makefile new file +39 -0
@@ -0,0 +1,39 @@ +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +# +# Set up the path to the clar sources and to the fixtures directory +# +# The fixture path needs to be an absolute path so it can be used +# even after we have chdir'ed into the test directory while testing. +# +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE))) +CLAR_PATH := $(dir $(TEST_DIRECTORY)) +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/ + +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\" + +.PHONY: clean + +# list the objects that go into our test +objects = main.o sample.o + +# build the test executable itself +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects) + +# test object files depend on clar macros +$(objects) : $(CLAR_PATH)clar.h + +# build the clar.suite file of test metadata +clar.suite: + python "$(CLAR_PATH)generate.py" . + +# remove all generated files +clean: + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
t/unit-tests/clar/test/clar_test.h new file +16 -0
@@ -0,0 +1,16 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ +#ifndef __CLAR_TEST__ +#define __CLAR_TEST__ + +/* Import the standard clar helper functions */ +#include "clar.h" + +/* Your custom shared includes / defines here */ +extern int global_test_counter; + +#endif
t/unit-tests/clar/test/main.c new file +40 -0
@@ -0,0 +1,40 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ + +#include "clar_test.h" + +/* +* Sample main() for clar tests. +* +* You should write your own main routine for clar tests that does specific +* setup and teardown as necessary for your application. The only required +* line is the call to `clar_test(argc, argv)`, which will execute the test +* suite. If you want to check the return value of the test application, +* your main() should return the same value returned by clar_test(). +*/ + +int global_test_counter =0 ; + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ +int ret; + +/* Your custom initialization here */ + global_test_counter =0 ; + +/* Run the test suite */ + ret =clar_test (argc, argv); + +/* Your custom cleanup here */ +cl_assert_equal_i (8 , global_test_counter); + +return ret; +}
t/unit-tests/clar/test/main.c.sample new file +27 -0
@@ -0,0 +1,27 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ + +#include "clar_test.h" + +/* + * Minimal main() for clar tests. + * + * Modify this with any application specific setup or teardown that you need. + * The only required line is the call to `clar_test(argc, argv)`, which will + * execute the test suite. If you want to check the return value of the test + * application, main() should return the same value returned by clar_test(). + */ + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + /* Run the test suite */ + return clar_test(argc, argv); +}
t/unit-tests/clar/test/resources/test/file new file +1 -0
@@ -0,0 +1 @@ +File
t/unit-tests/clar/test/sample.c new file +84 -0
84 lines not on this page · raw message
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
Re: [PATCH v7 02/14] t: import the clar unit testing framework
On Tue, Sep 3, 2024 at 5:15 AM Patrick Steinhardt <[email protected]> wrote:>
Quoted 19 lines
Our unit testing framework is a homegrown solution. While it supports most of our needs, it is likely that the volume of unit tests will grow quite a bit in the future such that we can exercise low-level subsystems directly. This surfaces several shortcomings that the current solution has:
- There is no way to run only one specific tests. While some of our unit tests wire this up manually, others don't. In general, it requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead, the test author needs to return manually whenever an assertion fails. This is rather verbose and is not done correctly in most of our unit tests.
- Wiring up a new testcase requires both implementing the test function and calling it in the respective test suite's main function, which is creating code duplication.
Quoted 5 lines
We can of course fix all of these issues ourselves, but that feels rather pointless when there are already so many unit testing frameworks out there that have those features.
We line out some requirements for any unit testing framework in
Perhaps you meant s/line out/outline/ ?
(Not worth a reroll.)
Quoted 27 lines
"
Documentation/technical/unit-tests.txt". The "clar" unit testing framework, which isn't listed in that table yet, ticks many of the boxes:- It is licensed under ISC, which is compatible.
- It is easily vendorable because it is rather tiny at around 1200 lines of code.
- It is easily hackable due to the same reason.
- It has TAP support.
- It has skippable tests.
- It preprocesses test files in order to extract test functions, which then get wired up automatically.
While it's not perfect, the fact that clar originates from the libgit2 project means that it should be rather easy for us to collaborate with upstream to plug any gaps.
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
Signed-off-by Patrick Steinhardt <[email protected]>
Re: [PATCH v7 02/14] t: import the clar unit testing framework
Quoted 24 lines · Eric Sunshine
On Tue, Sep 3, 2024 at 5:15 AM Patrick Steinhardt <[email protected]> wrote:>
Quoted 23 lines
Our unit testing framework is a homegrown solution. While it supports most of our needs, it is likely that the volume of unit tests will grow quite a bit in the future such that we can exercise low-level subsystems directly. This surfaces several shortcomings that the current solution has:
- There is no way to run only one specific tests. While some of our unit tests wire this up manually, others don't. In general, it requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead, the test author needs to return manually whenever an assertion fails. This is rather verbose and is not done correctly in most of our unit tests.
- Wiring up a new testcase requires both implementing the test function and calling it in the respective test suite's main function, which is creating code duplication.
We can of course fix all of these issues ourselves, but that feels rather pointless when there are already so many unit testing frameworks out there that have those features.
Quoted 5 lines
Quoted 1 line
We line out some requirements for any unit testing framework in
Perhaps you meant s/line out/outline/ ?
(Not worth a reroll.)
Hum. Yeah, this is German grammar leaking into English. Anyway, TIL what "to line out" means :) Thanks!
Patrick
PATCH v7 3/14 t/clar: fix compatibility with NonStop
In reply to Patrick SteinhardtThe NonStop platform does not have mkdtemp() available, which we rely on in build_sandbox_path(). Fix this issue by using mktemp() and mkdir() instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
--- t/unit-tests/clar/clar/sandbox.h | 6 ++++++ 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)if (_mktemp (_clar_path) ==NULL )return -1 ; +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#elif defined(__TANDEM) +if (mktemp (_clar_path) ==NULL ) +return -1 ; +if (mkdir (_clar_path,0700 ) !=0 )return -1 ;#elif defined(_WIN32)
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 4/14 clar: avoid compile error with mingw-w64
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When using mingw-w64 to compile the code, and using _stat(), it is necessary to use struct _stat, too, and not struct stat (as the latter is incompatible with the "dashed" version because it is limited to 32-bit time types for backwards compatibility).
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -68,7 +68,7 @@# define PRIxZ "Ix"# endif -# if defined(_MSC_VER) || defined(__MINGW32__) +# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))typedef struct stat STAT_T ;# else typedef struct _stat STAT_T ;
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 5/14 clar(win32): avoid compile error due to unused `fs_copy()`
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When CLAR_FIXTURE_PATH is unset, the fs_copy() function seems not to be used. But it is declared as static, and GCC does not like that, complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the MAYBE_UNUSED pattern from Git's git-compat-util.h. However, this is a GCC-only construct that is not understood by Visual C. Besides, clar does not use that pattern at all.
Instead, let's use the ((void)SYMBOL); pattern that clar already uses elsewhere; This avoids the compile error by sorta kinda make the function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to figure out that this function is unused even though there are recursive calls between fs_copy() and fs_copydir_helper(); Earlier GCC versions do not detect that, and therefore the issue has been hidden from the regular Linux CI builds (where GCC 14.x is not yet used). That is the reason why this change is only made in the Windows-specific portion of t/unit-tests/clar/clar/fs.h.
--- t/unit-tests/clar/clar/fs.h | 2 ++ 1 file changed, 2 insertions(+)
t/unit-tests/clar/clar/fs.h +2 -0
@@ -297,6 +297,8 @@ cl_fs_cleanup(void) {#ifdef CLAR_FIXTURE_PATH fs_rm (fixture_path (_clar_path,"*" )); +#else + ((void )fs_copy);/* unused */ #endif }
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 6/14 clar: stop including `shellapi.h` unnecessarily
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
The shellapi.h header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have SHFileOperation() declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed that call, and therefore that #include <shellapi.h> is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons of header files, including shellapi.h.
So let's remove it.
Note: Since the windows.h header would include shellapi.h anyway, we also define WIN32_LEAN_AND_MEAN to avoid this and similar other unnecessary includes before including windows.h.
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -19,9 +19,9 @@#include <sys/stat.h> #ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN # include <windows.h> # include <io.h> -# include <shellapi.h> # include <direct.h> # define _MAIN_CC __cdecl
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 7/14 Makefile: fix sparse dependency on GENERATED_H
In reply to Patrick SteinhardtThe "check" Makefile target is essentially an alias around the "sparse" target. The one difference though is that it will tell users to instead run the "test" target in case they do not have sparse(1) installed, as chances are high that they wanted to execute the test suite rather than doing semantic checks.
But even though the "check" target ultimately just ends up executing make sparse, it still depends on our generated headers. This does not make any sense though: they are irrelevant for the "test" target advice, and if these headers are required for the "sparse" target they must be declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as concurrent builds may then end up generating the headers and running sparse concurrently. Instead, we make them a dependency of the specific objects. While that is overly broad, it does ensure correct ordering. The alternative, specifying which file depends on what generated header explicitly, feels rather unmaintainable.
--- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) -$(SP_OBJ): %.sp: %.c %.o +$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ -Wsparse-error \ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style: git clang-format --style file --diff --extensions c,h .PHONY: check -check: $(GENERATED_H) +check: @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 8/14 Makefile: make hdr-check depend on generated headers
In reply to Patrick SteinhardtThe "hdr-check" Makefile target compiles each of our headers as a standalone code unit to ensure that they are not missing any type declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework, which will have the effect that some headers start depending on generated ones. While we could declare that dependency explicitly, it does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects depend on all of our generated headers. While again overly broad, it is easy to maintain and generating headers is not an expensive thing to do anyway.
--- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc) @echo '#include "git-compat-util.h"' >$@ @echo '#include "$<"' >>$@ -$(HCO): %.hco: %.hcc FORCE +$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE $(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $< .PHONY: hdr-check $(HCO)
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 9/14 Makefile: do not use sparse on third-party sources
In reply to Patrick SteinhardtWe have several third-party sources in our codebase that we have imported from upstream projects. These sources are mostly excluded from our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
--- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) check-sha1:: t/helper/test-tool$X t/helper/test-sha1.sh -SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) +SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS))) +SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC)) $(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 10/14 Makefile: wire up the clar unit testing framework
In reply to Patrick SteinhardtWire up the clar unit testing framework by introducing a new "unit-tests" executable. In contrast to the existing framework, this will result in a single executable for all test suites. The ability to pick specific tests to execute is retained via functionality built into the clar itself.
Note that we need to be a bit careful about how we need to invalidate our Makefile rules. While we obviously have to regenerate the clar suite when our test suites change, we also have to invalidate it in case any of the test suites gets removed. We do so by using our typical pattern of creating a GIT-TEST-SUITES file that gets updated whenever the set of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test functions are neither static, nor do they have external declarations. This is because they are getting parsed via "generate.py", which then creates the external generations that get populated into an array. These declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes" errors from our compiler for each of these test functions. To fix those errors, we extract the extern declarations from "clar.suite" and put them into a standalone header that then gets included by each of our unit tests. This gets rid of compiler warnings for every function which has been extracted by "generate.py". More importantly though, it does _not_ get rid of warnings in case a function really isn't being used by anything. Thus, it would cause a compiler error if a function name was mistyped and thus not picked up by "generate.py".
The test driver "unit-test.c" is an empty stub for now. It will get implemented in the next commit.
--- .gitignore | 1 + Makefile | 36 +++++++++++++++++++++--- t/Makefile | 1 + t/unit-tests/.gitignore | 2 ++ t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++ t/unit-tests/unit-test.c | 6 ++++ t/unit-tests/unit-test.h | 3 ++ 7 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 t/unit-tests/clar-generate.awk create mode 100644 t/unit-tests/unit-test.c create mode 100644 t/unit-tests/unit-test.h
.gitignore +1 -0
@@ -9,6 +9,7 @@ /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-SPATCH-DEFINES +/GIT-TEST-SUITES /GIT-USER-AGENT /GIT-VERSION-FILE /bin-wrappers/
Makefile +32 -4
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a GENERATED_H += command-list.h GENERATED_H += config-list.h GENERATED_H += hook-list.h +GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h +GENERATED_H += $(UNIT_TEST_DIR)/clar.suite .PHONY: generated-hdrs generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) -all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) +all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \ GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \ - $(UNIT_TEST_PROGS) $(MOFILES) + $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES) $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \ SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean: clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) + $(RM) GIT-TEST-SUITES $(RM) po/git.pot po/git-core.pot $(RM) git.res $(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) +GIT-TEST-SUITES: FORCE + @FLAGS='$(UNIT_TESTS_SUITES)'; \ + if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \ + echo >&2 " * new test suites"; \ + echo "$$FLAGS" >GIT-TEST-SUITES; \ + fi + +$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES + $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \ + sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \ + done >$@ +$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h + $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite +$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h +$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) +$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS + $(call mkdir_p_parent_template) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + .PHONY: build-unit-tests unit-tests -build-unit-tests: $(UNIT_TEST_PROGS) -unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X +build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) +unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests
t/Makefile +1 -0
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) +UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X) UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS)) UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
t/unit-tests/.gitignore +2 -0
@@ -1 +1,3 @@ /bin +/clar.suite +/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0
50 lines not on this page · raw message
t/unit-tests/unit-test.c new file +6 -0
@@ -0,0 +1,6 @@ +#include "unit-test.h" + +int cmd_main(int argc UNUSED, const char **argv UNUSED) +{ +return 0 ; +}
t/unit-tests/unit-test.h new file +3 -0
@@ -0,0 +1,3 @@ +#include "git-compat-util.h" +#include "clar/clar.h" +#include "clar-decls.h"
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 11/14 t/unit-tests: implement test driver
In reply to Patrick SteinhardtThe test driver in "unit-test.c" is responsible for setting up our unit tests and eventually running them. As such, it is also responsible for parsing the command line arguments.
The clar unit testing framework provides function clar_test() that parses command line arguments and then executes the tests for us. In theory that would already be sufficient. We have the special requirement to always generate TAP-formatted output though, so we'd have to always pass the "-t" argument to clar. Furthermore, some of the options exposed by clar are ineffective when "-t" is used, but they would still be shown when the user passes the "-h" parameter to have the clar show its usage.
Implement our own option handling instead of using the one provided by clar, which gives us greater flexibility in how exactly we set things up.
We would ideally not use any "normal" code of ours for this such that the unit testing framework doesn't depend on it working correctly. But it is somewhat dubious whether we really want to reimplement all of the option parsing. So for now, let's be pragmatic and reuse it until we find a good reason in the future why we'd really want to avoid it.
--- t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-)
t/unit-tests/unit-test.c +41 -2
@@ -1,6 +1,45 @@#include "unit-test.h" +#include "parse-options.h" +#include "string-list.h" +#include "strvec.h" -int cmd_main(int argc UNUSED, const char **argv UNUSED) +static const char *const unit_test_usage[] = { +N_ ("unit-test [<options>]" ), +NULL , +}; + +int cmd_main (int argc,const char **argv) { -return 0 ; +struct string_list run_args =STRING_LIST_INIT_NODUP ; +struct string_list exclude_args =STRING_LIST_INIT_NODUP ; +int immediate =0 ; +struct option options[] = { +OPT_BOOL ('i' ,"--immediate" , &immediate, +N_ ("immediately exit upon the first failed test" )), +OPT_STRING_LIST ('r' ,"run" , &run_args,N_ ("name" ), +N_ ("run only test suite or individual test <name>" )), +OPT_STRING_LIST ('x' ,"exclude" , &exclude_args,N_ ("name" ), +N_ ("exclude test suite <name>" )), +OPT_END (), + }; +struct strvec args =STRVEC_INIT ; +int ret; + + argc =parse_options (argc, argv,NULL , options, + unit_test_usage,PARSE_OPT_KEEP_ARGV0 ); +if (argc >1 ) +usagef (_ ("extra command line parameter '%s'" ), argv[0 ]); + +strvec_push (&args, argv[0 ]); +strvec_push (&args,"-t" ); +for (size_t i =0 ; i < run_args.nr; i++) +strvec_pushf (&args,"-s%s" , run_args.items[i].string); +for (size_t i =0 ; i < exclude_args.nr; i++) +strvec_pushf (&args,"-x%s" , exclude_args.items[i].string); + + ret =clar_test (args.nr, (char **) args.v); + +string_list_clear (&run_args,0 ); +strvec_clear (&args); +return ret; }
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
Re: [PATCH v7 11/14] t/unit-tests: implement test driver
Hi Patrick
Quoted 12 lines · Patrick Steinhardt
The test driver in "
unit-test.c" is responsible for setting up our unit tests and eventually running them. As such, it is also responsible for parsing the command line arguments.The clar unit testing framework provides function
clar_test()that parses command line arguments and then executes the tests for us. In theory that would already be sufficient. We have the special requirement to always generate TAP-formatted output though, so we'd have to always pass the "-t" argument to clar. Furthermore, some of the options exposed by clar are ineffective when "-t" is used, but they would still be shown when the user passes the "-h" parameter to have the clar show its usage.
Quoted 3 lines
Implement our own option handling instead of using the one provided by clar, which gives us greater flexibility in how exactly we set things up.
That makes sense
Quoted 5 lines
We would ideally not use any "normal" code of ours for this such that the unit testing framework doesn't depend on it working correctly. But it is somewhat dubious whether we really want to reimplement all of the option parsing. So for now, let's be pragmatic and reuse it until we find a good reason in the future why we'd really want to avoid it.
I think that's fine for now. Using parse_options() gives a much nicer user experience than clar_test() as it supports long options and has more flexible support for option arguments. I'd expect the code that implements "struct string_list" and "struct strvec" to be pretty stable so its probably safe to rely on those.
Given there's only a couple of options it wouldn't be too bad to implement the parsing ourselves if we have to in the future. We might need to do that to support the libification work as I suspect we wont want to link tests for other libraries against libgit.a.
Quoted 20 lines
Signed-off-by Patrick Steinhardt <[email protected]>--- t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-)t/unit-tests/unit-test.c +7
-1@@ -1,6 +1,45 @@#include "unit-test.h" +#include "parse-options.h" +#include "string-list.h" +#include "strvec.h" -int cmd_main(int argc UNUSED, const char **argv UNUSED) +static const char *const unit_test_usage[] = { +N_ ("unit-test [<options>]" ), +NULL , +};
t/unit-tests/unit-test.c line 18
Quoted 10 lines
t/unit-tests/unit-test.c +8
-1+ +int cmd_main (int argc,const char **argv) { -return 0 ; +struct string_list run_args =STRING_LIST_INIT_NODUP ; +struct string_list exclude_args =STRING_LIST_INIT_NODUP ; +int immediate =0 ; +struct option options[] = { +OPT_BOOL ('i' ,"--immediate" , &immediate, +N_ ("immediately exit upon the first failed test" )),
This is unused. If we want to to behave like the "--immediate" option of our integration tests that's hard to implement by wrapping clar_test() which requires "-i<suite>". The simplest thing would be to just drop it for now. Otherwise as the most likely use for "-i" is manually testing some tests in a suite we could require "--run" with "-i". Then we would have one or more suite names which we can append to "-i" when passing it to clar_test(). Alternatively we could include "clar.suite" and wade through all the test suite names ourselves to construct a suitable list of "-i" options to pass to clar_test() but that would probably mean we have to parse the excludes as well which makes it all a bit of a faff.
t/unit-tests/unit-test.c line 20
Quoted 2 lines
t/unit-tests/unit-test.c +2
-0+OPT_STRING_LIST ('r' ,"run" , &run_args,N_ ("name" ), + N_("run only test suite or individual test <name>")),
It's nice that this option name now matches our integration tests. It would be helpful to show the syntax for "name" (I think it expects <suite>[::<test>]) but I failed to come up with a concise description to add to the help here.
Best Wishes
Phillip
Quoted 25 lines
t/unit-tests/unit-test.c +24
-0+OPT_STRING_LIST ('x' ,"exclude" , &exclude_args,N_ ("name" ), +N_ ("exclude test suite <name>" )), + OPT_END(), + }; +struct strvec args =STRVEC_INIT ; +int ret; + + argc =parse_options (argc, argv,NULL , options, + unit_test_usage,PARSE_OPT_KEEP_ARGV0 ); +if (argc >1 ) +usagef (_ ("extra command line parameter '%s'" ), argv[0 ]); + +strvec_push (&args, argv[0 ]); +strvec_push (&args,"-t" ); +for (size_t i =0 ; i < run_args.nr; i++) +strvec_pushf (&args,"-s%s" , run_args.items[i].string); +for (size_t i =0 ; i < exclude_args.nr; i++) +strvec_pushf (&args,"-x%s" , exclude_args.items[i].string); + + ret =clar_test (args.nr, (char **) args.v); + +string_list_clear (&run_args,0 ); +strvec_clear (&args); +return ret; }
Re: [PATCH v7 11/14] t/unit-tests: implement test driver
Quoted 70 lines · Phillip Wood
Hi Patrick
Quoted 12 lines · Patrick Steinhardt
The test driver in "
unit-test.c" is responsible for setting up our unit tests and eventually running them. As such, it is also responsible for parsing the command line arguments.The clar unit testing framework provides function
clar_test()that parses command line arguments and then executes the tests for us. In theory that would already be sufficient. We have the special requirement to always generate TAP-formatted output though, so we'd have to always pass the "-t" argument to clar. Furthermore, some of the options exposed by clar are ineffective when "-t" is used, but they would still be shown when the user passes the "-h" parameter to have the clar show its usage.Quoted 3 lines
Implement our own option handling instead of using the one provided by clar, which gives us greater flexibility in how exactly we set things up.
That makes sense
Quoted 5 lines
We would ideally not use any "normal" code of ours for this such that the unit testing framework doesn't depend on it working correctly. But it is somewhat dubious whether we really want to reimplement all of the option parsing. So for now, let's be pragmatic and reuse it until we find a good reason in the future why we'd really want to avoid it.
I think that's fine for now. Using
parse_options()gives a much nicer user experience thanclar_test()as it supports long options and has more flexible support for option arguments. I'd expect the code that implements "structstring_list" and "struct strvec" to be pretty stable so its probably safe to rely on those.Given there's only a couple of options it wouldn't be too bad to implement the parsing ourselves if we have to in the future. We might need to do that to support the libification work as I suspect we wont want to link tests for other libraries against libgit.a.
Quoted 19 lines
Signed-off-by Patrick Steinhardt <[email protected]>--- t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-)t/unit-tests/unit-test.c +7
-1@@ -1,6 +1,45 @@#include "unit-test.h" +#include "parse-options.h" +#include "string-list.h" +#include "strvec.h" -int cmd_main(int argc UNUSED, const char **argv UNUSED) +static const char *const unit_test_usage[] = { +N_ ("unit-test [<options>]" ), +NULL , +};t/unit-tests/unit-test.c line 17
Quoted 10 lines
t/unit-tests/unit-test.c +8
-1+ +int cmd_main (int argc,const char **argv) { -return 0 ; +struct string_list run_args =STRING_LIST_INIT_NODUP ; +struct string_list exclude_args =STRING_LIST_INIT_NODUP ; +int immediate =0 ; +struct option options[] = { +OPT_BOOL ('i' ,"--immediate" , &immediate, +N_ ("immediately exit upon the first failed test" )),This is unused. If we want to to behave like the "--immediate" option of our integration tests that's hard to implement by wrapping
clar_test()which
Quoted 8 lines
requires "-i<suite>". The simplest thing would be to just drop it for now. Otherwise as the most likely use for "-i" is manually testing some tests in a suite we could require "--run" with "-i". Then we would have one or more suite names which we can append to "-i" when passing it to
clar_test(). Alternatively we could include "clar.suite" and wade through all the test suite names ourselves to construct a suitable list of "-i" options to pass toclar_test()but that would probably mean we have to parse the excludes as well which makes it all a bit of a faff.
Oh, that's a plain oversight on my side. It is easy to wire up given that the clar already supports it via "-Q". Also made me notice that I wrote "--immediate" instead of "immediate".
Quoted 7 lines
t/unit-tests/unit-test.c line 20
Quoted 2 lines
t/unit-tests/unit-test.c +2
-0+OPT_STRING_LIST ('r' ,"run" , &run_args,N_ ("name" ), + N_("run only test suite or individual test <name>")),It's nice that this option name now matches our integration tests. It would be helpful to show the syntax for "name" (I think it expects <suite>[::<test>]) but I failed to come up with a concise description to add to the help here.
Isn't <suite[::test]> concise enough? I certainly like it.
Patrick
Re: [PATCH v7 11/14] t/unit-tests: implement test driver
Hi Patrick
Quoted 4 lines · Patrick Steinhardt
On Wed, Sep 04, 2024 at 02:35:20PM +0100, Phillip Wood wrote:
Oh, that's a plain oversight on my side. It is easy to wire up given that the clar already supports it via "-Q". Also made me notice that I wrote "--immediate" instead of "immediate".
That's handy, I'd missed clar's "-Q" option.
Quoted 9 lines
Quoted 7 lines
t/unit-tests/unit-test.c line 20
Quoted 2 lines
t/unit-tests/unit-test.c +2
-0+OPT_STRING_LIST ('r' ,"run" , &run_args,N_ ("name" ), + N_("run only test suite or individual test <name>")),It's nice that this option name now matches our integration tests. It would be helpful to show the syntax for "name" (I think it expects <suite>[::<test>]) but I failed to come up with a concise description to add to the help here.
Isn't
<suite[::test]>concise enough? I certainly like it.
Sounds good
Phillip
Quoted 1 line
Patrick
PATCH v7 12/14 t/unit-tests: convert strvec tests to use clar
In reply to Patrick SteinhardtConvert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
not ok 4 - strvec::push
---
reason: |
String mismatch: (&vec)->v[i] != expect[i]
'foo' != 'fo' (at byte 2)
at:
file: 't/unit-tests/strvec.c'
line: 48
function: 'test_strvec__push'
---
ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22The binary also supports some parameters that allow us to run only a subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [options] Options:
-sname Run only the suite with name (can go to individual test name)
-iname Include the suite with name
-xname Exclude the suite with name
-v Increase verbosity (show suite names)
-q Only report tests that had an error
-Q Quit as soon as a test fails
-t Display results in tap format
-l Print suite names
-r[filename] Write summary file (to the optional filename)
Furthermore, running make unit-tests runs the binary along with all the other unit tests we have.
--- Makefile | 2 +- t/unit-tests/strvec.c | 241 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/t-strvec.c | 211 ----------------------------------- 3 files changed, 242 insertions(+), 212 deletions(-) create mode 100644 t/unit-tests/strvec.c delete mode 100644 t/unit-tests/t-strvec.c
Makefile +1 -1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record UNIT_TEST_PROGRAMS += t-reftable-tree UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strcmp-offset -UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-trailer UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
t/unit-tests/strvec.c new file +241 -0
241 lines not on this page · raw message
t/unit-tests/t-strvec.c deleted +0 -211
211 lines not on this page · raw message
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 13/14 t/unit-tests: convert ctype tests to use clar
In reply to Patrick SteinhardtConvert the ctype tests to use the new clar unit testing framework. Introduce a new function cl_failf() that allows us to print a formatted error message, which we can use to point out which of the characters was classified incorrectly. This results in output like this on failure:
# start of suite 1: ctype
not ok 1 - ctype::isspace
---
reason: |
Test failed.
0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
line: 36
function: 'test_ctype__isspace'
---
ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++++----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 69 insertions(+), 11 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)Makefile +1 -1
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += ctype UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o -UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash UNIT_TEST_PROGRAMS += t-hashmap
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +61 -10
98 lines not on this page · raw message
t/unit-tests/unit-test.h +7 -0
@@ -1,3 +1,10 @@#include "git-compat-util.h" #include "clar/clar.h" #include "clar-decls.h" +#include "strbuf.h" + +#define cl_failf (fmt, ...) do { \ + char desc[4096]; \ + snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \ + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ +} while (0)
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
PATCH v7 14/14 clar: add CMake support
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
Now that we're using clar as powerful test framework, we have to adjust the Visual C build (read: the CMake definition) to be able to handle that, too.
--- contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
contrib/buildsystems/CMakeLists.txt +53 -0
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS}) endif() endforeach() +parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "") + +set(clar_decls "") +set(clar_cbs "") +set(clar_cbs_count 0) +set(clar_suites "static struct clar_suite _clar_suites[] = {\n") +list(LENGTH unit_tests_SUITES clar_suites_count) +foreach(suite ${unit_tests_SUITES}) + file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls + REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$") + + list(LENGTH decls decls_count) + string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls}) + string(JOIN "" cbs ${cbs}) + list(TRANSFORM decls PREPEND "extern ") + string(JOIN ";\n" decls ${decls}) + + string(APPEND clar_decls "${decls};\n") + string(APPEND clar_cbs + "static const struct clar_func _clar_cb_${suite}[] = {\n" + ${cbs} + "};\n") + string(APPEND clar_suites + " {\n" + " \"${suite}\",\n" + " { NULL, NULL },\n" + " { NULL, NULL },\n" + " _clar_cb_${suite}, ${decls_count}, 1\n" + " },\n") + math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}") +endforeach() +string(APPEND clar_suites + "};\n" + "static const size_t _clar_suite_count = ${clar_suites_count};\n" + "static const size_t _clar_callback_count = ${clar_cbs_count};\n") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}") + +list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/") +list(TRANSFORM unit_tests_SUITES APPEND ".c") +add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c") +target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests") +add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c") +target_link_libraries(unit-tests unit-tests-lib common-main) +set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +if(MSVC) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +endif() + #test-tool parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
Signature · 1 line
2.46.0.421.g159f2d50e7.dirty
Hi Patrick
Quoted 16 lines · Patrick Steinhardt
Hi,
this is the seventh version fo my patch series that introduces the clar unit testing framework.
Changes compared to v6:
- Add some explanations for why we introduce clar in the first place to the second commit message.
- Split out implementation of the test driver, that is the main function, into a separate commit.
- Reimplement argument parsing ourselves. This allows more flexibility and gets rid of some options that do not make sense for us.
Quoted 7 lines
- Add an empty line between declarations and code.
- Improve the test messages in ctype tests to also mention our expectations.
- Adapt
cl_failf()to not usexstrfmt(), butsnprintf()with a static buffer instead.
I've left some comments on patch 11 which is new. Apart from that range-diff looks fine to me.
Thanks for working on this
Phillip
Quoted 420 lines
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unusedfs_copy()
clar: stop includingshellapi.hunnecessarily
clar: add CMake supportPatrick Steinhardt (10):
t: do not passGIT_TEST_OPTSto unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency onGENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: implement test driver t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar.gitignore | 1 + Documentation/technical/unit-tests.txt | 2 + Makefile | 53 +- contrib/buildsystems/CMakeLists.txt | 53 ++ t/Makefile | 4 +- t/run-test.sh | 2 +- t/unit-tests/.gitignore | 2 + t/unit-tests/clar-generate.awk | 50 ++ t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 524 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 159 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ t/unit-tests/{t-ctype.c => ctype.c} | 71 +- t/unit-tests/strvec.c | 241 ++++++ t/unit-tests/t-strvec.c | 211 ------ t/unit-tests/unit-test.c | 45 ++ t/unit-tests/unit-test.h | 10 + 31 files changed, 3457 insertions(+), 234 deletions(-)create mode 100644
t/unit-tests/clar-generate.awkcreate mode 100644t/unit-tests/clar/.github/workflows/ci.ymlcreate mode 100644 t/unit-tests/clar/COPYING
create mode 100644t/unit-tests/clar/README.mdcreate mode 100644t/unit-tests/clar/clar.c
create mode 100644t/unit-tests/clar/clar.h
create mode 100644t/unit-tests/clar/clar/fixtures.hcreate mode 100644t/unit-tests/clar/clar/fs.hcreate mode 100644t/unit-tests/clar/clar/print.hcreate mode 100644t/unit-tests/clar/clar/sandbox.hcreate mode 100644t/unit-tests/clar/clar/summary.hcreate mode 100755t/unit-tests/clar/generate.pycreate mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644t/unit-tests/clar/test/clar_test.hcreate mode 100644t/unit-tests/clar/test/main.ccreate mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644t/unit-tests/clar/test/sample.crenamet/unit-tests/{t-ctype.c=>ctype.c} (68%) create mode 100644t/unit-tests/strvec.c
delete mode 100644t/unit-tests/t-strvec.c
create mode 100644t/unit-tests/unit-test.c
create mode 100644t/unit-tests/unit-test.hRange-diff against v6:
1: e48a6461137 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove 2: 1710e9f9ff7 ! 2: 55a9b46e65f t: import the clar unit testing framework@@ Metadata
## Commit message ##
t: import the clar unit testing framework+ Our unit testing framework is a homegrown solution. While it supports
+ most of our needs, it is likely that the volume of unit tests will grow
+ quite a bit in the future such that we can exercise low-level subsystems
+ directly. This surfaces several shortcomings that the current solution
+ has:
+
+ - There is no way to run only one specific tests. While some of our
+ unit tests wire this up manually, others don't. In general, it
+ requires quite a bit of boilerplate to get this set up correctly.
+
+ - Failures do not cause a test to stop execution directly. Instead,
+ the test author needs to return manually whenever an assertion
+ fails. This is rather verbose and is not done correctly in most of
+ our unit tests.
+
+ - Wiring up a new testcase requires both implementing the test
+ function and calling it in the respective test suite's main
+ function, which is creating code duplication.
+
+ We can of course fix all of these issues ourselves, but that feels
+ rather pointless when there are already so many unit testing frameworks
+ out there that have those features.
+
+ We line out some requirements for any unit testing framework in
+ "Documentation/technical/unit-tests.txt". The "clar" unit testing
+ framework, which isn't listed in that table yet, ticks many of the
+ boxes:
+
+ - It is licensed under ISC, which is compatible.
+
+ - It is easily vendorable because it is rather tiny at around 1200
+ lines of code.
+
+ - It is easily hackable due to the same reason.
+
+ - It has TAP support.
+
+ - It has skippable tests.
+
+ - It preprocesses test files in order to extract test functions, which
+ then get wired up automatically.
+
+ While it's not perfect, the fact that clar originates from the libgit2
+ project means that it should be rather easy for us to collaborate with
+ upstream to plug any gaps.
+
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.3: 5c21aa87aa2 = 3: f24401f0a87 t/clar: fix compatibility with NonStop 4: 06d2bce0d82 = 4: 658a601c541 clar: avoid compile error with mingw-w64 5: f88b3421a09 = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()` 6: 5fb4c55be33 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily 7: e0dcbd5ca83 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H 8: 77a03f8df70 = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers 9: c91dd7327e3 = 9: 7d0f494850a Makefile: do not use sparse on third-party sources 10: 115c15aa9ae ! 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework @@ Commit message anything. Thus, it would cause a compiler error if a function name was mistyped and thus not picked up by "generate.py". + The test driver "unit-test.c" is an empty stub for now. It will get + implemented in the next commit. + Signed-off-by: Patrick Steinhardt <[email protected]> ## .gitignore ## @@ t/unit-tests/unit-test.c (new) @@ +#include "unit-test.h" + -+int cmd_main(int argc, const char **argv) ++int cmd_main(int argc UNUSED, const char **argv UNUSED) +{ -+ const char **argv_copy; -+ int ret; -+ -+ /* Append the "-t" flag such that the tests generate TAP output. */ -+ ALLOC_ARRAY(argv_copy, argc + 2); -+ COPY_ARRAY(argv_copy, argv, argc); -+ argv_copy[argc++] = "-t"; -+ argv_copy[argc] = NULL; -+ -+ ret = clar_test(argc, (char **) argv_copy); -+ -+ free(argv_copy); -+ return ret; ++ return 0; +} ## t/unit-tests/unit-test.h (new) ## -: ----------- > 11: 8bd5b3e2b29 t/unit-tests: implement test driver 11: b3b8df04872 ! 12: 3c3b9eacdfb t/unit-tests: convert strvec tests to use clar @@ Commit message clar-based tests looks like. The tests are part of the "t/unit-tests/bin/unit-tests" binary. When - running that binary, it generates TAP output: + running that binary with an injected error, it generates TAP output: # ./t/unit-tests/bin/unit-tests TAP version 13 @@ Commit message ok 1 - strvec::init ok 2 - strvec::dynamic_init ok 3 - strvec::clear - ok 4 - strvec::push - ok 5 - strvec::pushft_pushf + not ok 4 - strvec::push + --- + reason: | + String mismatch: (&vec)->v[i] != expect[i] + 'foo' != 'fo' (at byte 2) + at: + file: 't/unit-tests/strvec.c' + line: 48 + function: 'test_strvec__push' + --- + ok 5 - strvec::pushf ok 6 - strvec::pushl ok 7 - strvec::pushv ok 8 - strvec::replace_at_head @@ t/unit-tests/strvec.c (new) +#define check_strvec(vec, ...) \ + do { \ + const char *expect[] = { __VA_ARGS__ }; \ -+ cl_assert(ARRAY_SIZE(expect) > 0); \ -+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \ -+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \ ++ size_t expect_len = ARRAY_SIZE(expect); \ ++ cl_assert(expect_len > 0); \ ++ cl_assert_equal_p(expect[expect_len - 1], NULL); \ ++ cl_assert_equal_i((vec)->nr, expect_len - 1); \ + cl_assert((vec)->nr <= (vec)->alloc); \ -+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \ ++ for (size_t i = 0; i < expect_len; i++) \ + cl_assert_equal_s((vec)->v[i], expect[i]); \ + } while (0) + +void test_strvec__init(void) +{ + struct strvec vec = STRVEC_INIT; ++ + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); + cl_assert_equal_i(vec.alloc, 0); @@ t/unit-tests/strvec.c (new) +void test_strvec__dynamic_init(void) +{ + struct strvec vec; ++ + strvec_init(&vec); + cl_assert_equal_p(vec.v, empty_strvec); + cl_assert_equal_i(vec.nr, 0); @@ t/unit-tests/strvec.c (new) +void test_strvec__clear(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_push(&vec, "foo"); + strvec_clear(&vec); + cl_assert_equal_p(vec.v, empty_strvec); @@ t/unit-tests/strvec.c (new) +void test_strvec__pushf(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushf(&vec, "foo: %d", 1); + check_strvec(&vec, "foo: 1", NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__pushl(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__replace_at_head(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 0, "replaced"); + check_strvec(&vec, "replaced", "bar", "baz", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__replace_in_between(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_replace(&vec, 1, "replaced"); + check_strvec(&vec, "foo", "replaced", "baz", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__replace_with_substring(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", NULL); + strvec_replace(&vec, 0, vec.v[0] + 1); + check_strvec(&vec, "oo", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__remove_at_head(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 0); + check_strvec(&vec, "bar", "baz", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__remove_at_tail(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 2); + check_strvec(&vec, "foo", "bar", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__remove_in_between(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_remove(&vec, 1); + check_strvec(&vec, "foo", "baz", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__pop_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pop(&vec); + check_strvec(&vec, NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__pop_non_empty_array(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_pushl(&vec, "foo", "bar", "baz", NULL); + strvec_pop(&vec); + check_strvec(&vec, "foo", "bar", NULL); @@ t/unit-tests/strvec.c (new) +void test_strvec__split_empty_string(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_split(&vec, ""); + check_strvec(&vec, NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__split_single_item(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_split(&vec, "foo"); + check_strvec(&vec, "foo", NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__split_multiple_items(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_split(&vec, "foo bar baz"); + check_strvec(&vec, "foo", "bar", "baz", NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__split_whitespace_only(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_split(&vec, " \t\n"); + check_strvec(&vec, NULL); + strvec_clear(&vec); @@ t/unit-tests/strvec.c (new) +void test_strvec__split_multiple_consecutive_whitespaces(void) +{ + struct strvec vec = STRVEC_INIT; ++ + strvec_split(&vec, "foo\n\t bar"); + check_strvec(&vec, "foo", "bar", NULL); + strvec_clear(&vec); 12: 1ac2e48a7f2 ! 13: c8360db2f86 t/unit-tests: convert ctype tests to use clar @@ Commit message on failure: # start of suite 1: ctype - ok 1 - ctype::isspace - not ok 2 - ctype::isdigit + not ok 1 - ctype::isspace --- reason: | Test failed. - 0x61 is classified incorrectly + 0x0d is classified incorrectly: expected 0, got 1 at: file: 't/unit-tests/ctype.c' - line: 38 - function: 'test_ctype__isdigit' + line: 36 + function: 'test_ctype__isspace' --- + ok 2 - ctype::isdigit ok 3 - ctype::isalpha ok 4 - ctype::isalnum ok 5 - ctype::is_glob_special @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c - test_msg(" i: 0x%02x", i); \ - } \ - check(!class(EOF)); \ -- } \ -+ for (int i = 0; i < 256; i++) \ -+ if (class(i) != !!memchr(string, i, len)) \ -+ cl_failf("0x%02x is classified incorrectly", i); \ ++ for (int i = 0; i < 256; i++) { \ ++ int actual = class(i), expect = !!memchr(string, i, len); \ ++ if (actual != expect) \ ++ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \ ++ i, expect, actual); \ + } \ + cl_assert(!class(EOF)); \ } while (0) @@ t/unit-tests/unit-test.h +#include "strbuf.h" + +#define cl_failf(fmt, ...) do { \ -+ char *desc = xstrfmt(fmt, __VA_ARGS__); \ ++ char desc[4096]; \ ++ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \ + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ -+ free(desc); \ +} while (0)13:
131036c398e= 14:d51c146cd9dclar: add CMake support
Quoted 27 lines · Phillip Wood
Hi Patrick
Quoted 16 lines · Patrick Steinhardt
Hi,
this is the seventh version fo my patch series that introduces the clar unit testing framework.
Changes compared to v6:
- Add some explanations for why we introduce clar in the first place to the second commit message.
- Split out implementation of the test driver, that is the main function, into a separate commit.
- Reimplement argument parsing ourselves. This allows more flexibility and gets rid of some options that do not make sense for us.
Quoted 7 lines
- Add an empty line between declarations and code.
- Improve the test messages in ctype tests to also mention our expectations.
- Adapt
cl_failf()to not usexstrfmt(), butsnprintf()with a static buffer instead.
Quoted 6 lines
I've left some comments on patch 11 which is new. Apart from that range-diff looks fine to me.
Thanks for working on this
Phillip
Thanks for all your reviews on this series, highly appreciated!
Patrick
v8 cover Introduce clar testing framework
In reply to Patrick SteinhardtHi,
this is the 8th version of my patch series that introduces the clar testing framework.
Changes compared to v7:
- Properly wire up the "--immediate" flag.
- Give a hint for the syntax to run only specific suites or tests for the "--run" and "--exclude" options.
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unused fs_copy()
clar: stop including shellapi.h unnecessarily
clar: add CMake support
Patrick Steinhardt (10):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: implement test driver t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/strvec.c | 241 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 47 ++
t/unit-tests/unit-test.h | 10 +
31 files changed, 3459 insertions(+), 234 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.hRange-diff against v7:
1: b67f10ec0b0 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 55a9b46e65f = 2: 55a9b46e65f t: import the clar unit testing framework
3: f24401f0a87 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
4: 658a601c541 = 4: 658a601c541 clar: avoid compile error with mingw-w64
5: 0b8a6ac5fed = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
6: c50e7a0ea68 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
7: b8f3f16dd27 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
8: 3d3fe443b9a = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
9: 7d0f494850a = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
10: 9c74c5ae019 = 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
11: 8bd5b3e2b29 ! 11: 81d932bfa33 t/unit-tests: implement test driver
@@ t/unit-tests/unit-test.c
+ struct string_list exclude_args = STRING_LIST_INIT_NODUP;
+ int immediate = 0;
+ struct option options[] = {
-+ OPT_BOOL('i', "--immediate", &immediate,
++ OPT_BOOL('i', "immediate", &immediate,
+ N_("immediately exit upon the first failed test")),
-+ OPT_STRING_LIST('r', "run", &run_args, N_("name"),
-+ N_("run only test suite or individual test <name>")),
-+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"),
-+ N_("exclude test suite <name>")),
++ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"),
++ N_("run only test suite or individual test <suite[::test]>")),
++ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"),
++ N_("exclude test suite <suite>")),
+ OPT_END(),
+ };
+ struct strvec args = STRVEC_INIT;
@@ t/unit-tests/unit-test.c
+
+ strvec_push(&args, argv[0]);
+ strvec_push(&args, "-t");
++ if (immediate)
++ strvec_push(&args, "-Q");
+ for (size_t i = 0; i < run_args.nr; i++)
+ strvec_pushf(&args, "-s%s", run_args.items[i].string);
+ for (size_t i = 0; i < exclude_args.nr; i++)
12: 3c3b9eacdfb = 12: 604303e31aa t/unit-tests: convert strvec tests to use clar
13: c8360db2f86 = 13: ba05b9f1eef t/unit-tests: convert ctype tests to use clar
14: d51c146cd9d = 14: 8441d29daa8 clar: add CMake supportSignature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 1/14 t: do not pass GIT_TEST_OPTS to unit tests with prove
When using the prove target, we append GIT_TEST_OPTS to the arguments that we execute each of the tests with. This doesn't only include the intended test scripts, but also ends up passing the arguments to our unit tests. This is unintentional though as they do not even know to interpret those arguments, and is inconsistent with how we execute unit tests without prove.
This isn't much of an issue because our current set of unit tests mostly ignore their arguments anyway. With the introduction of clar-based unit tests this is about to become an issue though, as these do parse their command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an environment variable. Like this, we can conditionally forward it to our test scripts, only.
--- t/Makefile | 3 ++- t/run-test.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)
t/Makefile +2 -1
@@ -68,7 +68,8 @@ failed: test -z "$$failed" || $(MAKE) $$failed prove: pre-clean check-chainlint $(TEST_LINT) - @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS) + @echo "*** prove (shell & unit tests) ***" + @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) $(MAKE) clean-except-prove-cache $(T):
t/run-test.sh +1 -1
@@ -10,7 +10,7 @@ case "$1" in echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set" exit 1 fi - exec "${TEST_SHELL_PATH}" "$@" + exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS} ;; *) exec "$@"
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 2/14 t: import the clar unit testing framework
In reply to Patrick SteinhardtOur unit testing framework is a homegrown solution. While it supports most of our needs, it is likely that the volume of unit tests will grow quite a bit in the future such that we can exercise low-level subsystems directly. This surfaces several shortcomings that the current solution has:
- There is no way to run only one specific tests. While some of our unit tests wire this up manually, others don't. In general, it requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead, the test author needs to return manually whenever an assertion fails. This is rather verbose and is not done correctly in most of our unit tests.
- Wiring up a new testcase requires both implementing the test function and calling it in the respective test suite's main function, which is creating code duplication.
We can of course fix all of these issues ourselves, but that feels rather pointless when there are already so many unit testing frameworks out there that have those features.
We line out some requirements for any unit testing framework in "Documentation/technical/unit-tests.txt". The "clar" unit testing framework, which isn't listed in that table yet, ticks many of the boxes:
- It is licensed under ISC, which is compatible.
- It is easily vendorable because it is rather tiny at around 1200 lines of code.
- It is easily hackable due to the same reason.
- It has TAP support.
- It has skippable tests.
- It preprocesses test files in order to extract test functions, which then get wired up automatically.
While it's not perfect, the fact that clar originates from the libgit2 project means that it should be rather easy for us to collaborate with upstream to plug any gaps.
Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits.
--- Documentation/technical/unit-tests.txt | 2 + Makefile | 4 +- t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 522 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 153 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ 20 files changed, 2943 insertions(+), 1 deletion(-) create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml create mode 100644 t/unit-tests/clar/COPYING create mode 100644 t/unit-tests/clar/README.md create mode 100644 t/unit-tests/clar/clar.c create mode 100644 t/unit-tests/clar/clar.h create mode 100644 t/unit-tests/clar/clar/fixtures.h create mode 100644 t/unit-tests/clar/clar/fs.h create mode 100644 t/unit-tests/clar/clar/print.h create mode 100644 t/unit-tests/clar/clar/sandbox.h create mode 100644 t/unit-tests/clar/clar/summary.h create mode 100755 t/unit-tests/clar/generate.py create mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644 t/unit-tests/clar/test/clar_test.h create mode 100644 t/unit-tests/clar/test/main.c create mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644 t/unit-tests/clar/test/sample.c
Documentation/technical/unit-tests.txt +2 -0
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this. :criterion: https://github.com/Snaipe/Criterion[Criterion] :c-tap: https://github.com/rra/c-tap-harness/[C TAP] :check: https://libcheck.github.io/check/[Check] +:clar: https://github.com/clar-test/clar[Clar] [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"] |=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800 {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33 {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973 +{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192 |===== === Additional framework candidates
Makefile +3 -1
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/% THIRD_PARTY_SOURCES += compat/regex/% THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1dc/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o .PHONY: sparse sparse: $(SP_OBJ) -EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% +EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/% ifndef OPENSSL_SHA1 EXCEPT_HDRS += sha1/openssl.h endif
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
@@ -0,0 +1,23 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + + runs-on: ${{ matrix.os }} + + steps: + - name: Check out + uses: actions/checkout@v2 + - name: Build + run: | + cd test + make
t/unit-tests/clar/COPYING new file +15 -0
@@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2011-2015 Vicent Marti + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
t/unit-tests/clar/README.md new file +329 -0
329 lines not on this page · raw message
t/unit-tests/clar/clar.c new file +842 -0
842 lines not on this page · raw message
t/unit-tests/clar/clar.h new file +173 -0
173 lines not on this page · raw message
t/unit-tests/clar/clar/fixtures.h new file +50 -0
50 lines not on this page · raw message
t/unit-tests/clar/clar/fs.h new file +522 -0
522 lines not on this page · raw message
t/unit-tests/clar/clar/print.h new file +211 -0
211 lines not on this page · raw message
t/unit-tests/clar/clar/sandbox.h new file +153 -0
153 lines not on this page · raw message
t/unit-tests/clar/clar/summary.h new file +143 -0
143 lines not on this page · raw message
t/unit-tests/clar/generate.py new file +266 -0
266 lines not on this page · raw message
t/unit-tests/clar/test/.gitignore new file +4 -0
@@ -0,0 +1,4 @@ +clar.suite +.clarcache +clar_test +*.o
t/unit-tests/clar/test/Makefile new file +39 -0
@@ -0,0 +1,39 @@ +# +# Copyright (c) Vicent Marti. All rights reserved. +# +# This file is part of clar, distributed under the ISC license. +# For full terms see the included COPYING file. +# + +# +# Set up the path to the clar sources and to the fixtures directory +# +# The fixture path needs to be an absolute path so it can be used +# even after we have chdir'ed into the test directory while testing. +# +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)) +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE))) +CLAR_PATH := $(dir $(TEST_DIRECTORY)) +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/ + +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\" + +.PHONY: clean + +# list the objects that go into our test +objects = main.o sample.o + +# build the test executable itself +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects) + +# test object files depend on clar macros +$(objects) : $(CLAR_PATH)clar.h + +# build the clar.suite file of test metadata +clar.suite: + python "$(CLAR_PATH)generate.py" . + +# remove all generated files +clean: + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
t/unit-tests/clar/test/clar_test.h new file +16 -0
@@ -0,0 +1,16 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ +#ifndef __CLAR_TEST__ +#define __CLAR_TEST__ + +/* Import the standard clar helper functions */ +#include "clar.h" + +/* Your custom shared includes / defines here */ +extern int global_test_counter; + +#endif
t/unit-tests/clar/test/main.c new file +40 -0
@@ -0,0 +1,40 @@ +/* +* Copyright (c) Vicent Marti. All rights reserved. +* +* This file is part of clar, distributed under the ISC license. +* For full terms see the included COPYING file. +*/ + +#include "clar_test.h" + +/* +* Sample main() for clar tests. +* +* You should write your own main routine for clar tests that does specific +* setup and teardown as necessary for your application. The only required +* line is the call to `clar_test(argc, argv)`, which will execute the test +* suite. If you want to check the return value of the test application, +* your main() should return the same value returned by clar_test(). +*/ + +int global_test_counter =0 ; + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ +int ret; + +/* Your custom initialization here */ + global_test_counter =0 ; + +/* Run the test suite */ + ret =clar_test (argc, argv); + +/* Your custom cleanup here */ +cl_assert_equal_i (8 , global_test_counter); + +return ret; +}
t/unit-tests/clar/test/main.c.sample new file +27 -0
@@ -0,0 +1,27 @@ +/* + * Copyright (c) Vicent Marti. All rights reserved. + * + * This file is part of clar, distributed under the ISC license. + * For full terms see the included COPYING file. + */ + +#include "clar_test.h" + +/* + * Minimal main() for clar tests. + * + * Modify this with any application specific setup or teardown that you need. + * The only required line is the call to `clar_test(argc, argv)`, which will + * execute the test suite. If you want to check the return value of the test + * application, main() should return the same value returned by clar_test(). + */ + +#ifdef _WIN32 +int __cdecl main(int argc, char *argv[]) +#else +int main(int argc, char *argv[]) +#endif +{ + /* Run the test suite */ + return clar_test(argc, argv); +}
t/unit-tests/clar/test/resources/test/file new file +1 -0
@@ -0,0 +1 @@ +File
t/unit-tests/clar/test/sample.c new file +84 -0
84 lines not on this page · raw message
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 3/14 t/clar: fix compatibility with NonStop
In reply to Patrick SteinhardtThe NonStop platform does not have mkdtemp() available, which we rely on in build_sandbox_path(). Fix this issue by using mktemp() and mkdir() instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
--- t/unit-tests/clar/clar/sandbox.h | 6 ++++++ 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)if (_mktemp (_clar_path) ==NULL )return -1 ; +if (mkdir (_clar_path,0700 ) !=0 ) +return -1 ; +#elif defined(__TANDEM) +if (mktemp (_clar_path) ==NULL ) +return -1 ; +if (mkdir (_clar_path,0700 ) !=0 )return -1 ;#elif defined(_WIN32)
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 4/14 clar: avoid compile error with mingw-w64
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When using mingw-w64 to compile the code, and using _stat(), it is necessary to use struct _stat, too, and not struct stat (as the latter is incompatible with the "dashed" version because it is limited to 32-bit time types for backwards compatibility).
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -68,7 +68,7 @@# define PRIxZ "Ix"# endif -# if defined(_MSC_VER) || defined(__MINGW32__) +# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))typedef struct stat STAT_T ;# else typedef struct _stat STAT_T ;
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 5/14 clar(win32): avoid compile error due to unused `fs_copy()`
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
When CLAR_FIXTURE_PATH is unset, the fs_copy() function seems not to be used. But it is declared as static, and GCC does not like that, complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the MAYBE_UNUSED pattern from Git's git-compat-util.h. However, this is a GCC-only construct that is not understood by Visual C. Besides, clar does not use that pattern at all.
Instead, let's use the ((void)SYMBOL); pattern that clar already uses elsewhere; This avoids the compile error by sorta kinda make the function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to figure out that this function is unused even though there are recursive calls between fs_copy() and fs_copydir_helper(); Earlier GCC versions do not detect that, and therefore the issue has been hidden from the regular Linux CI builds (where GCC 14.x is not yet used). That is the reason why this change is only made in the Windows-specific portion of t/unit-tests/clar/clar/fs.h.
--- t/unit-tests/clar/clar/fs.h | 2 ++ 1 file changed, 2 insertions(+)
t/unit-tests/clar/clar/fs.h +2 -0
@@ -297,6 +297,8 @@ cl_fs_cleanup(void) {#ifdef CLAR_FIXTURE_PATH fs_rm (fixture_path (_clar_path,"*" )); +#else + ((void )fs_copy);/* unused */ #endif }
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 6/14 clar: stop including `shellapi.h` unnecessarily
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
The shellapi.h header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have SHFileOperation() declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed that call, and therefore that #include <shellapi.h> is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons of header files, including shellapi.h.
So let's remove it.
Note: Since the windows.h header would include shellapi.h anyway, we also define WIN32_LEAN_AND_MEAN to avoid this and similar other unnecessary includes before including windows.h.
--- t/unit-tests/clar/clar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
@@ -19,9 +19,9 @@#include <sys/stat.h> #ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN # include <windows.h> # include <io.h> -# include <shellapi.h> # include <direct.h> # define _MAIN_CC __cdecl
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 7/14 Makefile: fix sparse dependency on GENERATED_H
In reply to Patrick SteinhardtThe "check" Makefile target is essentially an alias around the "sparse" target. The one difference though is that it will tell users to instead run the "test" target in case they do not have sparse(1) installed, as chances are high that they wanted to execute the test suite rather than doing semantic checks.
But even though the "check" target ultimately just ends up executing make sparse, it still depends on our generated headers. This does not make any sense though: they are irrelevant for the "test" target advice, and if these headers are required for the "sparse" target they must be declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as concurrent builds may then end up generating the headers and running sparse concurrently. Instead, we make them a dependency of the specific objects. While that is overly broad, it does ensure correct ordering. The alternative, specifying which file depends on what generated header explicitly, feels rather unmaintainable.
--- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) -$(SP_OBJ): %.sp: %.c %.o +$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \ -Wsparse-error \ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style: git clang-format --style file --diff --extensions c,h .PHONY: check -check: $(GENERATED_H) +check: @if sparse; \ then \ echo >&2 "Use 'make sparse' instead"; \
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 8/14 Makefile: make hdr-check depend on generated headers
In reply to Patrick SteinhardtThe "hdr-check" Makefile target compiles each of our headers as a standalone code unit to ensure that they are not missing any type declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework, which will have the effect that some headers start depending on generated ones. While we could declare that dependency explicitly, it does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects depend on all of our generated headers. While again overly broad, it is easy to maintain and generating headers is not an expensive thing to do anyway.
--- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc) @echo '#include "git-compat-util.h"' >$@ @echo '#include "$<"' >>$@ -$(HCO): %.hco: %.hcc FORCE +$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE $(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $< .PHONY: hdr-check $(HCO)
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 9/14 Makefile: do not use sparse on third-party sources
In reply to Patrick SteinhardtWe have several third-party sources in our codebase that we have imported from upstream projects. These sources are mostly excluded from our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
--- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB) check-sha1:: t/helper/test-tool$X t/helper/test-sha1.sh -SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS)) +SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS))) +SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC)) $(SP_OBJ): %.sp: %.c %.o $(GENERATED_H) $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 10/14 Makefile: wire up the clar unit testing framework
In reply to Patrick SteinhardtWire up the clar unit testing framework by introducing a new "unit-tests" executable. In contrast to the existing framework, this will result in a single executable for all test suites. The ability to pick specific tests to execute is retained via functionality built into the clar itself.
Note that we need to be a bit careful about how we need to invalidate our Makefile rules. While we obviously have to regenerate the clar suite when our test suites change, we also have to invalidate it in case any of the test suites gets removed. We do so by using our typical pattern of creating a GIT-TEST-SUITES file that gets updated whenever the set of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test functions are neither static, nor do they have external declarations. This is because they are getting parsed via "generate.py", which then creates the external generations that get populated into an array. These declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes" errors from our compiler for each of these test functions. To fix those errors, we extract the extern declarations from "clar.suite" and put them into a standalone header that then gets included by each of our unit tests. This gets rid of compiler warnings for every function which has been extracted by "generate.py". More importantly though, it does _not_ get rid of warnings in case a function really isn't being used by anything. Thus, it would cause a compiler error if a function name was mistyped and thus not picked up by "generate.py".
The test driver "unit-test.c" is an empty stub for now. It will get implemented in the next commit.
--- .gitignore | 1 + Makefile | 36 +++++++++++++++++++++--- t/Makefile | 1 + t/unit-tests/.gitignore | 2 ++ t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++ t/unit-tests/unit-test.c | 6 ++++ t/unit-tests/unit-test.h | 3 ++ 7 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 t/unit-tests/clar-generate.awk create mode 100644 t/unit-tests/unit-test.c create mode 100644 t/unit-tests/unit-test.h
.gitignore +1 -0
@@ -9,6 +9,7 @@ /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-SPATCH-DEFINES +/GIT-TEST-SUITES /GIT-USER-AGENT /GIT-VERSION-FILE /bin-wrappers/
Makefile +32 -4
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a GENERATED_H += command-list.h GENERATED_H += config-list.h GENERATED_H += hook-list.h +GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h +GENERATED_H += $(UNIT_TEST_DIR)/clar.suite .PHONY: generated-hdrs generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X)) -all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) +all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) bin-wrappers/%: wrap-for-bin.sh $(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \ GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \ - $(UNIT_TEST_PROGS) $(MOFILES) + $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES) $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \ SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean: clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) + $(RM) GIT-TEST-SUITES $(RM) po/git.pot po/git-core.pot $(RM) git.res $(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) +GIT-TEST-SUITES: FORCE + @FLAGS='$(UNIT_TESTS_SUITES)'; \ + if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \ + echo >&2 " * new test suites"; \ + echo "$$FLAGS" >GIT-TEST-SUITES; \ + fi + +$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES + $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \ + sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \ + done >$@ +$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h + $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite +$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h +$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) +$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS + $(call mkdir_p_parent_template) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + .PHONY: build-unit-tests unit-tests -build-unit-tests: $(UNIT_TEST_PROGS) -unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X +build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) +unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests
t/Makefile +1 -0
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) +UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X) UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS)) UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
t/unit-tests/.gitignore +2 -0
@@ -1 +1,3 @@ /bin +/clar.suite +/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0
50 lines not on this page · raw message
t/unit-tests/unit-test.c new file +6 -0
@@ -0,0 +1,6 @@ +#include "unit-test.h" + +int cmd_main(int argc UNUSED, const char **argv UNUSED) +{ +return 0 ; +}
t/unit-tests/unit-test.h new file +3 -0
@@ -0,0 +1,3 @@ +#include "git-compat-util.h" +#include "clar/clar.h" +#include "clar-decls.h"
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
Re: [PATCH v8 10/14] Makefile: wire up the clar unit testing framework
Makefile OBJECTS += $(XDIFF_OBJS) · line 2724
Quoted 13 lines · Patrick Steinhardt
Makefile +6
-0+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS)
What is the longer term direction we envision? Do we aim to retire the UNIT_TEST_* and end up with only the clar based tests?
At least until that happens, can we have "UNIT_TESTS" -> "CLAR_TEST" or something that makes it less confusing? Every time I see merge conflicts around this area in the Makefile, I get puzzled and wonder which one is which.
Thanks.
Re: [PATCH v8 10/14] Makefile: wire up the clar unit testing framework
Quoted 18 lines · Junio C Hamano
Makefile OBJECTS += $(XDIFF_OBJS) · line 2724
Quoted 13 lines · Patrick Steinhardt
Makefile +6
-0+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o + UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS) OBJECTS += $(FUZZ_OBJS) OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS) OBJECTS += $(UNIT_TEST_OBJS) +OBJECTS += $(UNIT_TESTS_OBJS)What is the longer term direction we envision? Do we aim to retire the UNIT_TEST_* and end up with only the clar based tests?
Yeah, that's my plan. I first want to let the new clar-based tests cook a bit to shake out any issues with it, and address issues with clar that were raised during review upstream. But once that's done and we see that it works alright for our purpose I'd aim to convert all test suites to use the clar such that we ultimately end up with a single unit testing framework, only.
Quoted 4 lines
At least until that happens, can we have "
UNIT_TESTS" -> "CLAR_TEST" or something that makes it less confusing? Every time I see merge conflicts around this area in theMakefile, I get puzzled and wonder which one is which.
These are indeed quite similarly named, so I see where the confusion comes from. I'll send a patch in a bit to rename them.
Patrick
PATCH v8 11/14 t/unit-tests: implement test driver
In reply to Patrick SteinhardtThe test driver in "unit-test.c" is responsible for setting up our unit tests and eventually running them. As such, it is also responsible for parsing the command line arguments.
The clar unit testing framework provides function clar_test() that parses command line arguments and then executes the tests for us. In theory that would already be sufficient. We have the special requirement to always generate TAP-formatted output though, so we'd have to always pass the "-t" argument to clar. Furthermore, some of the options exposed by clar are ineffective when "-t" is used, but they would still be shown when the user passes the "-h" parameter to have the clar show its usage.
Implement our own option handling instead of using the one provided by clar, which gives us greater flexibility in how exactly we set things up.
We would ideally not use any "normal" code of ours for this such that the unit testing framework doesn't depend on it working correctly. But it is somewhat dubious whether we really want to reimplement all of the option parsing. So for now, let's be pragmatic and reuse it until we find a good reason in the future why we'd really want to avoid it.
--- t/unit-tests/unit-test.c | 45 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-)
t/unit-tests/unit-test.c +43 -2
@@ -1,6 +1,47 @@#include "unit-test.h" +#include "parse-options.h" +#include "string-list.h" +#include "strvec.h" -int cmd_main(int argc UNUSED, const char **argv UNUSED) +static const char *const unit_test_usage[] = { +N_ ("unit-test [<options>]" ), +NULL , +}; + +int cmd_main (int argc,const char **argv) { -return 0 ; +struct string_list run_args =STRING_LIST_INIT_NODUP ; +struct string_list exclude_args =STRING_LIST_INIT_NODUP ; +int immediate =0 ; +struct option options[] = { +OPT_BOOL ('i' ,"immediate" , &immediate, +N_ ("immediately exit upon the first failed test" )), +OPT_STRING_LIST ('r' ,"run" , &run_args,N_ ("suite[::test]" ), +N_ ("run only test suite or individual test <suite[::test]>" )), +OPT_STRING_LIST ('x' ,"exclude" , &exclude_args,N_ ("suite" ), +N_ ("exclude test suite <suite>" )), +OPT_END (), + }; +struct strvec args =STRVEC_INIT ; +int ret; + + argc =parse_options (argc, argv,NULL , options, + unit_test_usage,PARSE_OPT_KEEP_ARGV0 ); +if (argc >1 ) +usagef (_ ("extra command line parameter '%s'" ), argv[0 ]); + +strvec_push (&args, argv[0 ]); +strvec_push (&args,"-t" ); +if (immediate) +strvec_push (&args,"-Q" ); +for (size_t i =0 ; i < run_args.nr; i++) +strvec_pushf (&args,"-s%s" , run_args.items[i].string); +for (size_t i =0 ; i < exclude_args.nr; i++) +strvec_pushf (&args,"-x%s" , exclude_args.items[i].string); + + ret =clar_test (args.nr, (char **) args.v); + +string_list_clear (&run_args,0 ); +strvec_clear (&args); +return ret; }
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 12/14 t/unit-tests: convert strvec tests to use clar
In reply to Patrick SteinhardtConvert the strvec tests to use the new clar unit testing framework. This is a first test balloon that demonstrates how the testing infra for clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
not ok 4 - strvec::push
---
reason: |
String mismatch: (&vec)->v[i] != expect[i]
'foo' != 'fo' (at byte 2)
at:
file: 't/unit-tests/strvec.c'
line: 48
function: 'test_strvec__push'
---
ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22The binary also supports some parameters that allow us to run only a subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [options] Options:
-sname Run only the suite with name (can go to individual test name)
-iname Include the suite with name
-xname Exclude the suite with name
-v Increase verbosity (show suite names)
-q Only report tests that had an error
-Q Quit as soon as a test fails
-t Display results in tap format
-l Print suite names
-r[filename] Write summary file (to the optional filename)
Furthermore, running make unit-tests runs the binary along with all the other unit tests we have.
--- Makefile | 2 +- t/unit-tests/strvec.c | 241 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/t-strvec.c | 211 ----------------------------------- 3 files changed, 242 insertions(+), 212 deletions(-) create mode 100644 t/unit-tests/strvec.c delete mode 100644 t/unit-tests/t-strvec.c
Makefile +1 -1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record UNIT_TEST_PROGRAMS += t-reftable-tree UNIT_TEST_PROGRAMS += t-strbuf UNIT_TEST_PROGRAMS += t-strcmp-offset -UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-trailer UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS)) UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
t/unit-tests/strvec.c new file +241 -0
241 lines not on this page · raw message
t/unit-tests/t-strvec.c deleted +0 -211
211 lines not on this page · raw message
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 13/14 t/unit-tests: convert ctype tests to use clar
In reply to Patrick SteinhardtConvert the ctype tests to use the new clar unit testing framework. Introduce a new function cl_failf() that allows us to print a formatted error message, which we can use to point out which of the characters was classified incorrectly. This results in output like this on failure:
# start of suite 1: ctype
not ok 1 - ctype::isspace
---
reason: |
Test failed.
0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
line: 36
function: 'test_ctype__isspace'
---
ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++++----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 69 insertions(+), 11 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)Makefile +1 -1
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% +UNIT_TESTS_SUITES += ctype UNIT_TESTS_SUITES += strvec UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES)) UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o -UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-example-decorate UNIT_TEST_PROGRAMS += t-hash UNIT_TEST_PROGRAMS += t-hashmap
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +61 -10
98 lines not on this page · raw message
t/unit-tests/unit-test.h +7 -0
@@ -1,3 +1,10 @@#include "git-compat-util.h" #include "clar/clar.h" #include "clar-decls.h" +#include "strbuf.h" + +#define cl_failf (fmt, ...) do { \ + char desc[4096]; \ + snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \ + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ +} while (0)
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
PATCH v8 14/14 clar: add CMake support
In reply to Patrick SteinhardtFrom: Johannes Schindelin <[email protected]>
Now that we're using clar as powerful test framework, we have to adjust the Visual C build (read: the CMake definition) to be able to handle that, too.
--- contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+)
contrib/buildsystems/CMakeLists.txt +53 -0
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS}) endif() endforeach() +parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "") + +set(clar_decls "") +set(clar_cbs "") +set(clar_cbs_count 0) +set(clar_suites "static struct clar_suite _clar_suites[] = {\n") +list(LENGTH unit_tests_SUITES clar_suites_count) +foreach(suite ${unit_tests_SUITES}) + file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls + REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$") + + list(LENGTH decls decls_count) + string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls}) + string(JOIN "" cbs ${cbs}) + list(TRANSFORM decls PREPEND "extern ") + string(JOIN ";\n" decls ${decls}) + + string(APPEND clar_decls "${decls};\n") + string(APPEND clar_cbs + "static const struct clar_func _clar_cb_${suite}[] = {\n" + ${cbs} + "};\n") + string(APPEND clar_suites + " {\n" + " \"${suite}\",\n" + " { NULL, NULL },\n" + " { NULL, NULL },\n" + " _clar_cb_${suite}, ${decls_count}, 1\n" + " },\n") + math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}") +endforeach() +string(APPEND clar_suites + "};\n" + "static const size_t _clar_suite_count = ${clar_suites_count};\n" + "static const size_t _clar_callback_count = ${clar_cbs_count};\n") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}") +file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}") + +list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/") +list(TRANSFORM unit_tests_SUITES APPEND ".c") +add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c") +target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests") +add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c") +target_link_libraries(unit-tests unit-tests-lib common-main) +set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +if(MSVC) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + set_target_properties(unit-tests + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin) +endif() + #test-tool parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
Signature · 1 line
2.46.0.519.g2e7b89e038.dirty
Hi Patrick
Quoted 7 lines · Patrick Steinhardt
Changes compared to v7:
- Properly wire up the "--immediate" flag.
- Give a hint for the syntax to run only specific suites or tests for the "--run" and "--exclude" options.
As far as I'm concerned this version looks ready to be merged to next
Thanks
Phillip
Quoted 121 lines
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64 clar(win32): avoid compile error due to unusedfs_copy()
clar: stop includingshellapi.hunnecessarily
clar: add CMake supportPatrick Steinhardt (10):
t: do not passGIT_TEST_OPTSto unit tests with prove
t: import the clar unit testing framework t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency onGENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework t/unit-tests: implement test driver t/unit-tests: convert strvec tests to use clar t/unit-tests: convert ctype tests to use clar.gitignore | 1 + Documentation/technical/unit-tests.txt | 2 + Makefile | 53 +- contrib/buildsystems/CMakeLists.txt | 53 ++ t/Makefile | 4 +- t/run-test.sh | 2 +- t/unit-tests/.gitignore | 2 + t/unit-tests/clar-generate.awk | 50 ++ t/unit-tests/clar/.github/workflows/ci.yml | 23 + t/unit-tests/clar/COPYING | 15 + t/unit-tests/clar/README.md | 329 ++++++++ t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++ t/unit-tests/clar/clar.h | 173 +++++ t/unit-tests/clar/clar/fixtures.h | 50 ++ t/unit-tests/clar/clar/fs.h | 524 +++++++++++++ t/unit-tests/clar/clar/print.h | 211 ++++++ t/unit-tests/clar/clar/sandbox.h | 159 ++++ t/unit-tests/clar/clar/summary.h | 143 ++++ t/unit-tests/clar/generate.py | 266 +++++++ t/unit-tests/clar/test/.gitignore | 4 + t/unit-tests/clar/test/Makefile | 39 + t/unit-tests/clar/test/clar_test.h | 16 + t/unit-tests/clar/test/main.c | 40 + t/unit-tests/clar/test/main.c.sample | 27 + t/unit-tests/clar/test/resources/test/file | 1 + t/unit-tests/clar/test/sample.c | 84 ++ t/unit-tests/{t-ctype.c => ctype.c} | 71 +- t/unit-tests/strvec.c | 241 ++++++ t/unit-tests/t-strvec.c | 211 ------ t/unit-tests/unit-test.c | 47 ++ t/unit-tests/unit-test.h | 10 + 31 files changed, 3459 insertions(+), 234 deletions(-)create mode 100644
t/unit-tests/clar-generate.awkcreate mode 100644t/unit-tests/clar/.github/workflows/ci.ymlcreate mode 100644 t/unit-tests/clar/COPYING
create mode 100644t/unit-tests/clar/README.mdcreate mode 100644t/unit-tests/clar/clar.c
create mode 100644t/unit-tests/clar/clar.h
create mode 100644t/unit-tests/clar/clar/fixtures.hcreate mode 100644t/unit-tests/clar/clar/fs.hcreate mode 100644t/unit-tests/clar/clar/print.hcreate mode 100644t/unit-tests/clar/clar/sandbox.hcreate mode 100644t/unit-tests/clar/clar/summary.hcreate mode 100755t/unit-tests/clar/generate.pycreate mode 100644 t/unit-tests/clar/test/.gitignore create mode 100644 t/unit-tests/clar/test/Makefile create mode 100644t/unit-tests/clar/test/clar_test.hcreate mode 100644t/unit-tests/clar/test/main.ccreate mode 100644 t/unit-tests/clar/test/main.c.sample create mode 100644 t/unit-tests/clar/test/resources/test/file create mode 100644t/unit-tests/clar/test/sample.crenamet/unit-tests/{t-ctype.c=>ctype.c} (68%) create mode 100644t/unit-tests/strvec.c
delete mode 100644t/unit-tests/t-strvec.c
create mode 100644t/unit-tests/unit-test.c
create mode 100644t/unit-tests/unit-test.hRange-diff against v7:
1: b67f10ec0b0 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove 2: 55a9b46e65f = 2: 55a9b46e65f t: import the clar unit testing framework 3: f24401f0a87 = 3: f24401f0a87 t/clar: fix compatibility with NonStop 4: 658a601c541 = 4: 658a601c541 clar: avoid compile error with mingw-w64 5: 0b8a6ac5fed = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()` 6: c50e7a0ea68 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily 7: b8f3f16dd27 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H 8: 3d3fe443b9a = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers 9: 7d0f494850a = 9: 7d0f494850a Makefile: do not use sparse on third-party sources 10: 9c74c5ae019 = 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework 11: 8bd5b3e2b29 ! 11: 81d932bfa33 t/unit-tests: implement test driver @@ t/unit-tests/unit-test.c + struct string_list exclude_args = STRING_LIST_INIT_NODUP; + int immediate = 0; + struct option options[] = { -+ OPT_BOOL('i', "--immediate", &immediate, ++ OPT_BOOL('i', "immediate", &immediate, + N_("immediately exit upon the first failed test")), -+ OPT_STRING_LIST('r', "run", &run_args, N_("name"), -+ N_("run only test suite or individual test <name>")), -+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"), -+ N_("exclude test suite <name>")), ++ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"), ++ N_("run only test suite or individual test <suite[::test]>")), ++ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"), ++ N_("exclude test suite <suite>")), + OPT_END(), + }; + struct strvec args = STRVEC_INIT; @@ t/unit-tests/unit-test.c + + strvec_push(&args, argv[0]); + strvec_push(&args, "-t"); ++ if (immediate) ++ strvec_push(&args, "-Q"); + for (size_t i = 0; i < run_args.nr; i++) + strvec_pushf(&args, "-s%s", run_args.items[i].string); + for (size_t i = 0; i < exclude_args.nr; i++) 12: 3c3b9eacdfb = 12: 604303e31aa t/unit-tests: convert strvec tests to use clar 13: c8360db2f86 = 13: ba05b9f1eef t/unit-tests: convert ctype tests to use clar 14: d51c146cd9d = 14: 8441d29daa8 clar: add CMake support