<HTML><BODY><div>Hi!</div><div>Thanks for the patch!</div><div>Please consider my comments below.</div><div> </div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;">Среда, 15 марта 2023, 19:14 +03:00 от Sergey Kaplun <skaplun@tarantool.org>:<br> <div id=""><div class="js-helper js-readmsg-msg"><div><div id="style_16788968911966947015_BODY">We need an instrument to write tests in plain C for LuaJIT, to be able:<br>* easily test LuaC API<br>* test patches without usage plain Lua</div></div></div></div></blockquote><div>Typo: s/usage/usage of/</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>* write unit tests<br>* startup LuaJIT with custom memory allocator, to test some GC issues<br>* maybe, in future, use custom hashing function to test a behavior<br>  of LuaJIT tables<br>and so on.<br><br>The <test.c> module serves to achieve these goals without too fancy<br>features.<br><br>It's functionality inspired by cmoka API [1], but only TAP14 [2]<br>protocol is supported (Version of TAP set to 13 to be compatible with<br>old TAP13 harnesses).<br><br>The group of unit tests is declared like the following:<br><br>| void *t_state = NULL;<br>| const struct test_unit tgroup[] = {<br>| test_unit_new(test_base),<br>| test_unit_new(test_subtest),<br>| };<br>| return test_run_group(tgroup, t_state);<br><br>`test_run_group()` runs the whole group of tests, returns<br>`TEST_EXIT_SUCCESS` or `TEST_EXIT_FAILURE`.<br><br>If a similar group is declared inside unit test, this group will be<br>considered as a subtest.<br><br>This library provides an API similar to glibc (3) `assert()` to use<br>inside unit tests. `assert_[true,false]()` are useful for condition<br>checks and `assert_{type}_[not_,]_equal()` are useful for value<br>comparisons. If some assertion fails diagnostic is set, all test<br>considered as failing and finished via `longjmp()`, so these assertions<br>can be used inside custom subroutines.<br><br>Also, this module provides ability to skip one test or all tests, mark<br>test as todo, bail out all tests. `skip()`, `skip_all()` and `todo()`<br>macros are implemented via an early return to be used only in the test<br>body to make skipping clear. `skip_all()` may be used both for the<br>parent test and for a subtest.<br><br>As a part of this commit, tarantool-c-tests directory is created with<br>the corresponding CMakeLists.txt file to build this test library.<br>Tests to be rewritten in C with this library in the next commit and<br>placed as unit tests are:<br>* misclib-getmetrics-capi.test.lua<br>* misclib-sysprof-capi.test.lua<br><br>For now the tarantool-c-tests target just build the test library without<br>new tests to run.<br><br>[1]: <a href="https://github.com/clibs/cmocka" target="_blank">https://github.com/clibs/cmocka</a><br>[2]: <a href="https://testanything.org/tap-version-14-specification.html" target="_blank">https://testanything.org/tap-version-14-specification.html</a><br><br>Part of tarantool/tarantool#7900<br>---<br><br>I left some notes about this test module and I'll be happy to read your<br>thoughts about them.<br><br>* Should we cast to `(void *)` in `assert_ptr_[not_]equal()`? Or it will<br>  be better to notice user about bad type comparisons?</div></div></div></div></blockquote><div>I believe we should not cast it to the (void *) and notice user. It is the C<br>language, so if we can prevent some potentially incorrect behavior in<br>compile time, we should do that.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>* How often should we flush stdout?</div></div></div></div></blockquote><div>It seems to be often enough for now. I believe, it is better<br>to get used to that module and add additional `flush()`’es<br>a little bit down the road, if we’ll need that.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>* Obviously we can use `_test_run_group(__func__, NULL, 0, NULL)` with</div></div></div></div></blockquote><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>  `test_set_skip_reason()` set to implement `skip_all()` functionality.<br>  Nevertheless, I decided to reimpliment it's logic separately to be<br>  more easily maintained in the future.</div></div></div></div></blockquote><div>If we’ll change the signature to the one I proposed below, the <br>`_test_run_group(__func__, NULL, 0, NULL)` approach is not<br>possible anymore, so there is nothing wrong with reimplementation.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div><br><br> test/CMakeLists.txt | 2 +<br> test/tarantool-c-tests/CMakeLists.txt | 43 +++++<br> test/tarantool-c-tests/test.c | 251 ++++++++++++++++++++++++++<br> test/tarantool-c-tests/test.h | 251 ++++++++++++++++++++++++++<br> 4 files changed, 547 insertions(+)<br> create mode 100644 test/tarantool-c-tests/CMakeLists.txt<br> create mode 100644 test/tarantool-c-tests/test.c<br> create mode 100644 test/tarantool-c-tests/test.h<br><br>diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt<br>index a8262b12..47296a22 100644<br>--- a/test/CMakeLists.txt<br>+++ b/test/CMakeLists.txt<br>@@ -48,12 +48,14 @@ separate_arguments(LUAJIT_TEST_COMMAND)<br> add_subdirectory(LuaJIT-tests)<br> add_subdirectory(PUC-Rio-Lua-5.1-tests)<br> add_subdirectory(lua-Harness-tests)<br>+add_subdirectory(tarantool-c-tests)<br> add_subdirectory(tarantool-tests)<br> <br> add_custom_target(${PROJECT_NAME}-test DEPENDS<br>   LuaJIT-tests<br>   PUC-Rio-Lua-5.1-tests<br>   lua-Harness-tests<br>+ tarantool-c-tests<br>   tarantool-tests<br> )<br> <br>diff --git a/test/tarantool-c-tests/CMakeLists.txt b/test/tarantool-c-tests/CMakeLists.txt<br>new file mode 100644<br>index 00000000..5ebea441<br>--- /dev/null<br>+++ b/test/tarantool-c-tests/CMakeLists.txt<br>@@ -0,0 +1,43 @@<br>+find_program(PROVE prove)<br>+if(NOT PROVE)<br>+ message(WARNING "`prove' is not found, so tarantool-c-tests target is not generated")<br>+ return()<br>+endif()</div></div></div></div></blockquote><div>There is the same check in the test/tarantool-tests/CMakeLists.txt. Maybe</div><div>we should move it to the higher-level CMake so the lower-level CMakeLists</div><div>inherit it.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+<br>+set(C_TEST_SUFFIX .c_test)<br>+set(C_TEST_FLAGS --failures --shuffle)<br>+<br>+if(CMAKE_VERBOSE_MAKEFILE)<br>+ list(APPEND C_TEST_FLAGS --verbose)<br>+endif()<br>+<br>+# Build libtest.<br>+<br>+set(TEST_LIB_NAME "test")<br>+add_library(libtest STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/test.c)<br>+target_include_directories(libtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})<br>+set_target_properties(libtest PROPERTIES<br>+ COMPILE_FLAGS "-Wall -Wextra"<br>+ OUTPUT_NAME "${TEST_LIB_NAME}"<br>+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"<br>+)<br>+<br>+# XXX: For now, just build libtest. The tests to be depended on<br>+# will be added at the next commit.</div></div></div></div></blockquote><div>Typo: s/at the next/in the next/</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+add_custom_target(tarantool-c-tests<br>+ DEPENDS libluajit libtest<br>+)<br>+<br>+# XXX: For now, run 0 tests. Just verify that libtest was build.</div></div></div></div></blockquote><div>Typo: s/was build/was built/</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+add_custom_command(TARGET tarantool-c-tests<br>+ COMMENT "Running Tarantool C tests"<br>+ COMMAND<br>+ ${PROVE}<br>+ ${CMAKE_CURRENT_BINARY_DIR}<br>+ --ext ${C_TEST_SUFFIX}<br>+ --jobs ${CMAKE_BUILD_PARALLEL_LEVEL}<br>+ ${C_TEST_FLAGS}<br>+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}<br>+)<br>+<br>+# vim: ft=cmake expandtab shiftwidth=2: tabstop=2:</div></div></div></div></blockquote><div>That change is not necessary.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>diff --git a/test/tarantool-c-tests/test.c b/test/tarantool-c-tests/test.c<br>new file mode 100644<br>index 00000000..dc63cf3f<br>--- /dev/null<br>+++ b/test/tarantool-c-tests/test.c<br>@@ -0,0 +1,251 @@<br>+#include "test.h"<br>+<br>+/*<br>+ * Test module, based on TAP 14 specification [1].<br>+ * [1]: <a href="https://testanything.org/tap-version-14-specification.html" target="_blank">https://testanything.org/tap-version-14-specification.html</a><br>+ */<br>+<br>+/* Need for `PATH_MAX` in diagnostic definition. */<br>+#include <limits.h><br>+#include <setjmp.h><br>+#include <stdarg.h><br>+/* Need for `strchr()` in diagnostic parsing. */</div></div></div></div></blockquote><div>`strchr()` is not safe, despite the fact it searches till `\0`.</div><div>We should at least replace it with `memchr()`, which has</div><div>the explicit constraint for buffer length.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+#include <string.h><br>+<br>+/*<br>+ * Test level: 0 for the parent test, >0 for any subtests.<br>+ */<br>+static int level = -1;<br>+<br>+/*<br>+ * The last diagnostic data to be used in the YAML Diagnostic<br>+ * block.<br>+ *<br>+ * Contains filename, line number and failed expression or assert<br>+ * name and "got" and "expected" fields. All entries are separated<br>+ * by \n.<br>+ * The longest field is filename here, so PATH_MAX * 3 as<br>+ * the diagnostic string length should be enough.<br>+ *<br>+ * The first \0 means the end of diagnostic data.<br>+ *<br>+ * As far as `strchr()` searches until \0, all previous entries<br>+ * are suppressed by the last one. If the first byte is \0 --<br>+ * diagnostic is empty.<br>+ */<br>+#define TEST_DIAG_DATA_MAX (PATH_MAX * 3)<br>+char test_diag_buf[TEST_DIAG_DATA_MAX] = {0};<br>+<br>+const char *skip_reason = NULL;<br>+const char *todo_reason = NULL;<br>+<br>+/* Indent for the TAP. 4 spaces is default for subtest. */<br>+static void indent(void)<br>+{<br>+ int i;<br>+ for (i = 0; i < level; i++)<br>+ printf(" ");<br>+}<br>+<br>+void test_message(const char *fmt, ...)<br>+{<br>+ va_list ap;<br>+ indent();<br>+ va_start(ap, fmt);<br>+ vprintf(fmt, ap);<br>+ printf("\n");<br>+ va_end(ap);<br>+}<br>+<br>+static void test_print_tap_version(void)<br>+{<br>+ /*<br>+ * Since several TAP13 parsers in popular usage treat<br>+ * a repeated Version declaration as an error, even if the<br>+ * Version is indented, Subtests _should not_ include a<br>+ * Version, if TAP13 Harness compatibility is<br>+ * desirable [1].<br>+ */<br>+ if (level == 0)<br>+ test_message("TAP version %d", TAP_VERSION);<br>+}<br>+<br>+static void test_start_comment(const char *t_name)<br>+{<br>+ if (level > -1)<br>+ /*<br>+ * Inform about starting subtest, easier for<br>+ * humans to read.<br>+ * Subtest with a name must be terminated by a<br>+ * Test Point with a matching Description [1].<br>+ */<br>+ test_comment("Subtest: %s", t_name);<br>+}<br>+<br>+void _test_print_skip_all(const char *group_name, const char *reason)<br>+{<br>+ test_start_comment(group_name);<br>+ /*<br>+ * XXX: This test isn't started yet, so set indent level<br>+ * manually.<br>+ */<br>+ level++;<br>+ test_print_tap_version();<br>+ /*<br>+ * XXX: `SKIP_DIRECTIVE` is not necessary here according<br>+ * to the TAP14 specification [1], but some harnesses may<br>+ * fail to parse the output without it.<br>+ */<br>+ test_message("1..0" SKIP_DIRECTIVE "%s", reason);<br>+ level--;<br>+}<br>+<br>+/* Just inform TAP parser how many tests we want to run. */<br>+static void test_plan(size_t planned)<br>+{<br>+ test_message("1..%lu", planned);<br>+}<br>+<br>+/* Human-readable output how many tests/subtests are failed. */<br>+static void test_finish(size_t planned, size_t failed)<br>+{<br>+ const char *t_type = level == 0 ? "tests" : "subtests";<br>+ if (failed > 0)<br>+ test_comment("Looks like you failed %lu %s out of %lu",<br>+ failed, t_type, planned);</div></div></div></div></blockquote><div>Side note: «Looks like» is a bit misleading, it seems like we are<br>not sure whether the tests failed or not. I propose to rephrase it<br>in a more strict fashion: «Failed %lu out of %lu».</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+ fflush(stdout);<br>+}<br>+<br>+void test_set_skip_reason(const char *reason)<br>+{<br>+ skip_reason = reason;<br>+}<br>+<br>+void test_set_todo_reason(const char *reason)<br>+{<br>+ todo_reason = reason;<br>+}<br>+<br>+void test_save_diag_data(const char *fmt, ...)<br>+{<br>+ va_list ap;<br>+ va_start(ap, fmt);<br>+ vsnprintf(test_diag_buf, TEST_DIAG_DATA_MAX, fmt, ap);<br>+ va_end(ap);<br>+}<br>+<br>+static void test_clear_diag_data(void)<br>+{<br>+ /*<br>+ * Limit buffer with zero byte to show that there is no<br>+ * any entry.<br>+ */<br>+ test_diag_buf[0] = '\0';<br>+}<br>+<br>+static int test_diagnostic_is_set(void)<br>+{<br>+ return test_diag_buf[0] != '\0';<br>+}<br>+<br>+/*<br>+ * Parse the last diagnostic data entry and print it in YAML<br>+ * format with the corresponding additional half-indent in TAP<br>+ * (2 spaces).<br>+ * Clear diagnostic message to be sure that it's printed once.<br>+ * XXX: \n separators are changed to \0 during parsing and<br>+ * printing output for convenience in usage.<br>+ */<br>+static void test_diagnostic(void)<br>+{<br>+ test_message(" ---");<br>+ char *ent = test_diag_buf;<br>+ char *ent_end = NULL;<br>+ while ((ent_end = strchr(ent, '\n')) != NULL) {<br>+ char *next_ent = ent_end + 1;<br>+ /*<br>+ * Limit string with with the zero byte for<br>+ * formatted output. Anyway, don't need this \n<br>+ * anymore.<br>+ */<br>+ *ent_end = '\0';<br>+ test_message(" %s", ent);<br>+ ent = next_ent;<br>+ }<br>+ test_message(" ...");<br>+ test_clear_diag_data();<br>+}<br>+<br>+static jmp_buf test_run_env;<br>+<br>+TEST_NORET void _test_exit(int status)<br>+{<br>+ longjmp(test_run_env, status);<br>+}<br>+<br>+static int test_run(const struct test_unit *test, size_t test_number,<br>+ void *test_state)<br>+{<br>+ int status = TEST_EXIT_SUCCESS;<br>+ /*<br>+ * Run unit test. Diagnostic in case of failure setup by<br>+ * helpers assert macros defined in the header.<br>+ */<br>+ int jmp_status;<br>+ if ((jmp_status = setjmp(test_run_env)) == 0) {<br>+ if (test->f(test_state) != TEST_EXIT_SUCCESS)<br>+ status = TEST_EXIT_FAILURE;<br>+ } else {<br>+ status = jmp_status - TEST_JMP_STATUS_SHIFT;<br>+ }<br>+ const char *result = status == TEST_EXIT_SUCCESS ? "ok" : "not ok";<br>+<br>+ /*<br>+ * Format suffix of the test message for SKIP or TODO<br>+ * directives.<br>+ */<br>+#define SUFFIX_SZ 1024<br>+ char suffix[SUFFIX_SZ] = {0};<br>+ if (skip_reason) {<br>+ snprintf(suffix, SUFFIX_SZ, SKIP_DIRECTIVE "%s", skip_reason);<br>+ skip_reason = NULL;<br>+ } else if (todo_reason) {<br>+ /* Prevent count this test as failed. */<br>+ status = TEST_EXIT_SUCCESS;<br>+ snprintf(suffix, SUFFIX_SZ, TODO_DIRECTIVE "%s", todo_reason);<br>+ todo_reason = NULL;<br>+ }<br>+#undef SUFFIX_SZ<br>+<br>+ test_message("%s %lu - %s%s", result, test_number, test->name,<br>+ suffix);<br>+<br>+ if (status && test_diagnostic_is_set())<br>+ test_diagnostic();<br>+ return status;<br>+}<br>+<br>+int _test_run_group(const char *group_name, const struct test_unit *tests,<br>+ size_t n_tests, void *test_state)</div></div></div></div></blockquote><div>Strictly saying, the <type>* and <type>[] are different, and since that testing<br>facility is dependent on the `sizeof` behavior on <type>[], I think that argument<br>type should be changed to `const struct test_unit[]`. </div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+{<br>+ test_start_comment(group_name);<br>+<br>+ level++;<br>+ test_print_tap_version();<br>+<br>+ test_plan(n_tests);<br>+<br>+ size_t n_failed = 0;<br>+<br>+ size_t i;<br>+ for (i = 0; i < n_tests; i++) {<br>+ size_t test_number = i + 1;<br>+ /* Return 1 on failure, 0 on success. */<br>+ n_failed += test_run(&tests[i], test_number, test_state);<br>+ }<br>+<br>+ test_finish(n_tests, n_failed);<br>+<br>+ level--;<br>+ return n_failed > 0 ? TEST_EXIT_FAILURE : TEST_EXIT_SUCCESS;<br>+}<br>diff --git a/test/tarantool-c-tests/test.h b/test/tarantool-c-tests/test.h<br>new file mode 100644<br>index 00000000..695c5b4d<br>--- /dev/null<br>+++ b/test/tarantool-c-tests/test.h<br>@@ -0,0 +1,251 @@<br>+#ifndef TEST_H<br>+#define TEST_H<br>+<br>+#include <stdio.h><br>+#include <stdlib.h><br>+<br>+/*<br>+ * Test module, based on TAP 14 specification [1].<br>+ * [1]: <a href="https://testanything.org/tap-version-14-specification.html" target="_blank">https://testanything.org/tap-version-14-specification.html</a><br>+ * Version 13 is set for better compatibility on old machines.<br>+ *<br>+ * TODO:<br>+ * * Helpers assert macros:<br>+ * - assert_uint_equal if needed<br>+ * - assert_uint_not_equal if needed<br>+ * - assert_str_equal if needed<br>+ * - assert_str_not_equal if needed<br>+ * - assert_memory_equal if needed<br>+ * - assert_memory_not_equal if needed<br>+ * * Pragmas.<br>+ */<br>+<br>+#define TAP_VERSION 13<br>+<br>+#define TEST_EXIT_SUCCESS 0<br>+#define TEST_EXIT_FAILURE 1<br>+<br>+#define TEST_JMP_STATUS_SHIFT 2<br>+#define TEST_LJMP_EXIT_SUCCESS (TEST_EXIT_SUCCESS + TEST_JMP_STATUS_SHIFT)<br>+#define TEST_LJMP_EXIT_FAILURE (TEST_EXIT_FAILURE + TEST_JMP_STATUS_SHIFT)<br>+<br>+#define TEST_NORET __attribute__((noreturn))<br>+<br>+typedef int (*test_func)(void *test_state);<br>+struct test_unit {<br>+ const char *name;<br>+ test_func f;<br>+};<br>+<br>+/* Initialize `test_unit` structure. */<br>+#define test_unit_new(f) {#f, f}<br>+<br>+#define lengthof(arr) (sizeof(arr) / sizeof((arr)[0]))</div></div></div></div></blockquote><div>See the comment with my concerns about the <type> and <type>[]<br>above.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+<br>+/*<br>+ * __func__ is the name for a test group, "main" for the parent<br>+ * test.<br>+ */<br>+#define test_run_group(t_arr, t_state) \<br>+ _test_run_group(__func__, t_arr, lengthof(t_arr), t_state)</div></div></div></div></blockquote><div>Is there any reason for it to be a macro and not a function wrapper?<br>I believe it is better to use the functions when possible, since they are<br>easier to support and debug.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+<br>+#define SKIP_DIRECTIVE " # SKIP "<br>+#define TODO_DIRECTIVE " # TODO "<br>+<br>+/*<br>+ * XXX: May be implemented as well via<br>+ * `_test_run_group(__func, NULL, 0, NULL)` and<br>+ * `test_set_skip_reason` with additional changes in the former.<br>+ * But the current approach is easier to maintain, as far as we<br>+ * don't want to interfere different entities.<br>+ */<br>+#define skip_all(reason) do { \<br>+ _test_print_skip_all(__func__, reason); \<br>+ return TEST_EXIT_SUCCESS; \<br>+} while (0)</div></div></div></div></blockquote><div>Again, I propose to replace it with a conventional function to<br>make the return point explicit in the test implementation, so it<br>looks like `return skip_all(<reason>)`, instead of just `skip_all`.<br>Same for the skipcond and todo facilities below.</div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+<br>+#define skip(reason) do { \<br>+ test_set_skip_reason(reason); \<br>+ return TEST_EXIT_SUCCESS; \<br>+} while (0)</div></div></div></div></blockquote><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;"><div><div class="js-helper js-readmsg-msg"><div><div>+<br>+#define todo(reason) do { \<br>+ test_set_todo_reason(reason); \<br>+ return TEST_EXIT_FAILURE; \<br>+} while (0)<br>+<br>+#define bail_out(reason) do { \<br>+ /* \<br>+ * For backwards compatibility with TAP13 Harnesses, \<br>+ * Producers _should_ emit a "Bail out!" line at the root \<br>+ * indentation level whenever a Subtest bails out [1]. \<br>+ */ \<br>+ printf("Bail out! %s\n", reason); \<br>+ exit(TEST_EXIT_FAILURE); \<br>+} while (0)<br>+<br>+/* `fmt` should always be a format string here. */<br>+#define test_comment(fmt, ...) test_message("# " fmt, __VA_ARGS__)<br>+<br>+/*<br>+ * This is a set of useful assert macros like the standard C<br>+ * libary's assert(3) macro.<br>+ *<br>+ * On an assertion failure an assert macro will save the<br>+ * diagnostic to the special buffer, to be reported via YAML<br>+ * Diagnostic block and finish a test function with<br>+ * `return TEST_EXIT_FAILURE`.<br>+ *<br>+ * Due to limitations of the C language `assert_true()` and<br>+ * `assert_false()` macros can only display the expression that<br>+ * caused the assertion failure. Type specific assert macros,<br>+ * `assert_{type}_equal()` and `assert_{type}_not_equal()`, save<br>+ * the data that caused the assertion failure which increases data<br>+ * visibility aiding debugging of failing test cases.<br>+ */<br>+<br>+#define LOCATION_FMT "location:\t%s:%d\n"<br>+#define ASSERT_NAME_FMT(name) "failed_assertion:\t" #name "\n"<br>+<br>+#define assert_true(cond) do { \<br>+ if (!(cond)) { \<br>+ test_save_diag_data(LOCATION_FMT \<br>+ "condition_failed:\t'" #cond "'\n", \<br>+ __FILE__, __LINE__); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_false(cond) assert_true(!(cond))<br>+<br>+#define assert_ptr_equal(got, expected) do { \<br>+ if ((got) != (expected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_ptr_equal) \<br>+ "got: %p\n" \<br>+ "expected: %p\n", \<br>+ __FILE__, __LINE__, (got), (expected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_ptr_not_equal(got, unexpected) do { \<br>+ if ((got) == (unexpected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_ptr_not_equal) \<br>+ "got: %p\n" \<br>+ "unexpected: %p\n", \<br>+ __FILE__, __LINE__, (got), (unexpected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_int_equal(got, expected) do { \<br>+ if ((got) != (expected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_int_equal) \<br>+ "got: %d\n" \<br>+ "expected: %d\n", \<br>+ __FILE__, __LINE__, (got), (expected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_int_not_equal(got, unexpected) do { \<br>+ if ((got) == (unexpected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_int_not_equal) \<br>+ "got: %d\n" \<br>+ "unexpected: %d\n", \<br>+ __FILE__, __LINE__, (got), (unexpected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_sizet_equal(got, expected) do { \<br>+ if ((got) != (expected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_sizet_equal) \<br>+ "got: %lu\n" \<br>+ "expected: %lu\n", \<br>+ __FILE__, __LINE__, (got), (expected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+#define assert_sizet_not_equal(got, unexpected) do { \<br>+ if ((got) == (unexpected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_sizet_not_equal) \<br>+ "got: %lu\n" \<br>+ "unexpected: %lu\n", \<br>+ __FILE__, __LINE__, (got), (unexpected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+/* Check that doubles are __exactly__ the same. */<br>+#define assert_double_equal(got, expected) do { \<br>+ if ((got) != (expected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_double_equal) \<br>+ "got: %lf\n" \<br>+ "expected: %lf\n", \<br>+ __FILE__, __LINE__, (got), (expected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+/* Check that doubles are not __exactly__ the same. */<br>+#define assert_double_not_equal(got, unexpected) do { \<br>+ if ((got) == (unexpected)) { \<br>+ test_save_diag_data( \<br>+ LOCATION_FMT \<br>+ ASSERT_NAME_FMT(assert_double_not_equal) \<br>+ "got: %lf\n" \<br>+ "unexpected: %lf\n", \<br>+ __FILE__, __LINE__, (got), (unexpected) \<br>+ ); \<br>+ _test_exit(TEST_LJMP_EXIT_FAILURE); \<br>+ } \<br>+} while (0)<br>+<br>+/* API declaration. */<br>+<br>+/*<br>+ * Print formatted message with the corresponding indent.<br>+ * If you want to leave a comment, use `test_comment()` instead.<br>+ */<br>+void test_message(const char *fmt, ...);<br>+<br>+/* Need for `skip_all()`, please, don't use it. */<br>+void _test_print_skip_all(const char *group_name, const char *reason);<br>+/* End test via `longjmp()`, please, don't use it. */<br>+TEST_NORET void _test_exit(int status);<br>+<br>+void test_set_skip_reason(const char *reason);<br>+void test_set_todo_reason(const char *reason);<br>+/*<br>+ * Save formatted diagnostic data. Each entry separated with \n.<br>+ */<br>+void test_save_diag_data(const char *fmt, ...);<br>+<br>+/* Internal, it is better to use `test_run_group()` instead. */<br>+int _test_run_group(const char *group_name, const struct test_unit *tests,<br>+ size_t n_tests, void *test_state);<br>+<br>+#endif /* TEST_H */<br>--<br>2.34.1</div></div></div></div></blockquote><div><div>--<br>Best regards,</div><div>Maxim Kokryashkin</div></div><div> </div></BODY></HTML>