From baa977e4f510b4c1fe1e9c4bc39cd47927f5d7bf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 17 Jan 2025 16:16:59 +0000 Subject: [PATCH] invoke: preserve line numbers during helper methods When invoking a helper function (or "auxiliary method", in the documentation), we lose the file/function/line information of the current call point. This means that we attribute failures to the helper function instead of the actual test. Provide a `cl_invoke` function that will preserve the current state. This allows callers to use the simpler "clar style" as documented, but get the correct failure location. --- README.md | 30 +++++++++++++++++------------- clar.c | 29 ++++++++++++++++++++++++++--- clar.h | 21 +++++++++++++++++++++ test/selftest.c | 18 +++++++++--------- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7e70993..4159598 100644 --- a/README.md +++ b/README.md @@ -250,11 +250,16 @@ suite. - `cl_fixture(const char *)`: Gets the full path to a fixture file. -Please do note that these methods are *always* available whilst running a -test, even when calling auxiliary/static functions inside the same file. +### Auxiliary / helper functions -It's strongly encouraged to perform test assertions in auxiliary methods, -instead of returning error values. This is considered good Clar style. +The clar API is always available while running a test, even when calling +"auxiliary" (helper) functions. + +You're encouraged to perform test assertions in those auxiliary +methods, instead of returning error values. This is considered good +Clar style. _However_, when you do this, you need to call `cl_invoke` +to preserve the current state; this ensures that failures are reported +as coming from the actual test, instead of the auxiliary method. Style Example: @@ -309,20 +314,19 @@ static void check_string(const char *str) void test_example__a_test_with_auxiliary_methods(void) { - check_string("foo"); - check_string("bar"); + cl_invoke(check_string("foo")); + cl_invoke(check_string("bar")); } ~~~~ About Clar ========== -Clar has been written from scratch by [Vicent Martí](https://github.com/vmg), -to replace the old testing framework in [libgit2][libgit2]. - -Do you know what languages are *in* on the SF startup scene? Node.js *and* -Latin. Follow [@vmg](https://www.twitter.com/vmg) on Twitter to -receive more lessons on word etymology. You can be hip too. - +Clar was originally written by [Vicent Martí](https://github.com/vmg), +to replace the old testing framework in [libgit2][libgit2]. It is +currently maintained by [Edward Thomson](https://github.com/ethomson), +and used by the [libgit2][libgit2] and [git][git] projects, amongst +others. [libgit2]: https://github.com/libgit2/libgit2 +[git]: https://github.com/git/git diff --git a/clar.c b/clar.c index 481713d..7f61c25 100644 --- a/clar.c +++ b/clar.c @@ -164,6 +164,10 @@ static struct { struct clar_report *reports; struct clar_report *last_report; + const char *invoke_file; + const char *invoke_func; + size_t invoke_line; + void (*local_cleanup)(void *); void *local_cleanup_payload; @@ -328,6 +332,8 @@ clar_run_test( if (_clar.local_cleanup != NULL) _clar.local_cleanup(_clar.local_cleanup_payload); + clar__clear_invokepoint(); + if (cleanup->ptr != NULL) cleanup->ptr(); @@ -703,9 +709,9 @@ void clar__fail( _clar.last_report->last_error = error; - error->file = file; - error->function = function; - error->line_number = line; + error->file = _clar.invoke_file ? _clar.invoke_file : file; + error->function = _clar.invoke_func ? _clar.invoke_func : function; + error->line_number = _clar.invoke_line ? _clar.invoke_line : line; error->error_msg = error_msg; if (description != NULL && @@ -859,6 +865,23 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque) _clar.local_cleanup_payload = opaque; } +void clar__set_invokepoint( + const char *file, + const char *func, + size_t line) +{ + _clar.invoke_file = file; + _clar.invoke_func = func; + _clar.invoke_line = line; +} + +void clar__clear_invokepoint(void) +{ + _clar.invoke_file = NULL; + _clar.invoke_func = NULL; + _clar.invoke_line = 0; +} + #include "clar/sandbox.h" #include "clar/fixtures.h" #include "clar/fs.h" diff --git a/clar.h b/clar.h index 99b1904..b24a598 100644 --- a/clar.h +++ b/clar.h @@ -93,6 +93,20 @@ void cl_fixture_cleanup(const char *fixture_name); const char *cl_fixture_basename(const char *fixture_name); #endif +/** + * Invoke a helper function, which itself will use `cl_assert` + * constructs. This will preserve the stack information of the + * current call point, so that function name and line number + * information is shown from the line of the test, instead of + * the helper function. + */ +#define cl_invoke(expr) \ + do { \ + clar__set_invokepoint(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE); \ + expr; \ + clar__clear_invokepoint(); \ + } while(0) + /** * Assertion macros with explicit error message */ @@ -180,4 +194,11 @@ void clar__assert_equal( const char *fmt, ...); +void clar__set_invokepoint( + const char *file, + const char *func, + size_t line); + +void clar__clear_invokepoint(void); + #endif diff --git a/test/selftest.c b/test/selftest.c index c74214c..8154fa1 100644 --- a/test/selftest.c +++ b/test/selftest.c @@ -239,43 +239,43 @@ static void run(const char *expected_output_file, int expected_error_code, ...) void test_selftest__help(void) { - run("help", 1, "-h", NULL); + cl_invoke(run("help", 1, "-h", NULL)); } void test_selftest__without_arguments(void) { - run("without_arguments", 8, NULL); + cl_invoke(run("without_arguments", 8, NULL)); } void test_selftest__specific_test(void) { - run("specific_test", 1, "-sselftest::suite::bool", NULL); + cl_invoke(run("specific_test", 1, "-sselftest::suite::bool", NULL)); } void test_selftest__stop_on_failure(void) { - run("stop_on_failure", 1, "-Q", NULL); + cl_invoke(run("stop_on_failure", 1, "-Q", NULL)); } void test_selftest__quiet(void) { - run("quiet", 8, "-q", NULL); + cl_invoke(run("quiet", 8, "-q", NULL)); } void test_selftest__tap(void) { - run("tap", 8, "-t", NULL); + cl_invoke(run("tap", 8, "-t", NULL)); } void test_selftest__suite_names(void) { - run("suite_names", 0, "-l", NULL); + cl_invoke(run("suite_names", 0, "-l", NULL)); } void test_selftest__summary_without_filename(void) { struct stat st; - run("summary_without_filename", 8, "-r", NULL); + cl_invoke(run("summary_without_filename", 8, "-r", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("summary.xml", &st)); } @@ -283,7 +283,7 @@ void test_selftest__summary_without_filename(void) void test_selftest__summary_with_filename(void) { struct stat st; - run("summary_with_filename", 8, "-rdifferent.xml", NULL); + cl_invoke(run("summary_with_filename", 8, "-rdifferent.xml", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("different.xml", &st)); }