From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id C41884A1535; Mon, 5 Jun 2023 18:08:33 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org C41884A1535 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1685977713; bh=rjft0/5xh+9K3w05RuEIZVgHFtwyOOtsgC9GyMNquvo=; h=Date:To:Cc:References:In-Reply-To:Subject:List-Id: List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe: From:Reply-To:From; b=fGuIHX+OedHUqRMCDivhucC8aJpj2m1FtJojIE4v+wYwiztoV8g912Iebty8VNQ50 h0+ZhU2mbXh0KWIXkM/K46Ls6PhaTZH24mBlSKhN6KDfHsJ8bwsfqixXjiy3fJ/wR8 WmYxCGRHCKuGjWwAa86PPCgTTiDOoAvYwJuy2eHI= Received: from smtp59.i.mail.ru (smtp59.i.mail.ru [95.163.41.97]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 9C9B5454541 for ; Mon, 5 Jun 2023 18:08:32 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 9C9B5454541 Received: by smtp59.i.mail.ru with esmtpa (envelope-from ) id 1q6Bot-000BYZ-EC; Mon, 05 Jun 2023 18:08:32 +0300 Message-ID: <1fcbd8cc-44ab-484f-1cd5-715701282049@tarantool.org> Date: Mon, 5 Jun 2023 18:08:30 +0300 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.11.0 Content-Language: en-US To: Sergey Kaplun , Igor Munkin , Maxim Kokryashkin Cc: tarantool-patches@dev.tarantool.org References: <97194cc5150bc632c5fd566c9f34033413af1518.1685613304.git.skaplun@tarantool.org> In-Reply-To: <97194cc5150bc632c5fd566c9f34033413af1518.1685613304.git.skaplun@tarantool.org> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD988F25420CCA9469A21EDFAB76D1A971DC4DCEC6961D2C287182A05F5380850402E78BDF94E58EDEB24219D5E7DE0070BAF30808169CF1581F7C08F5BF61A86FD X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE7F2393C4755A27B53EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637CDAF09D3682101918638F802B75D45FF36EB9D2243A4F8B5A6FCA7DBDB1FC311F39EFFDF887939037866D6147AF826D83E37A8AACD5532F25FECDF7F2DBFBBC5117882F44604297287769387670735204B6963042765DA4BCB629EEF1311BF91D2E47CDBA5A96583BA9C0B312567BB2376E601842F6C81A19E625A9149C048EECCD848CCB6FE560CF41620B44FB51B7DD8FC6C240DEA7642DBF02ECDB25306B2B78CF848AE20165D0A6AB1C7CE11FEE36089696B24BB1D19C0837EA9F3D19764C4224003CC836476E2F48590F00D11D6E2021AF6380DFAD1A18204E546F3947CB11811A4A51E3B096D1867E19FE1407959CC434672EE6371089D37D7C0E48F6C8AA50765F7900637BC468E7E89D8C5D6EFF80C71ABB335746BA297DBC24807EABDAD6C7F3747799A X-C1DE0DAB: 0D63561A33F958A50AFDC31F0D85D0385AE3544BB0E0A72292EACD022BA348B8F87CCE6106E1FC07E67D4AC08A07B9B0CE135D2742255B35CB5012B2E24CD356 X-C8649E89: 1C3962B70DF3F0ADE00A9FD3E00BEEDF3FED46C3ACD6F73ED3581295AF09D3DF87807E0823442EA2ED31085941D9CD0AF7F820E7B07EA4CF40987D5975DBA6D0F72E64F60E6DA15AB57B2E4E4716EC34767A6EBA7350974B0070534DE2CB11AA588A46A5EBCE8E37BA8B2B27EF6193CBBE2A137004F45D85A74DFFEFA5DC0E7F02C26D483E81D6BE0DBAE6F56676BC7117BB6831D7356A2DEC5B5AD62611EEC62B5AFB4261A09AF0 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojxchphH+YLHw/Ba7gtlvPfQ== X-Mailru-Sender: 11C2EC085EDE56FAC07928AF2646A7691D01319308BCD9D224219D5E7DE0070BC59A7EFB72EB47B2EBA65886582A37BD66FEC6BF5C9C28D98A98C1125256619760D574B6FC815AB872D6B4FCE48DF648AE208404248635DF X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" Sergey, thanks for patches and fixes. See my comments below. On 6/5/23 13:41, Sergey Kaplun wrote: > We need an instrument to write tests in plain C for LuaJIT, to be able: > * easily test LuaC API > * test patches without usage of plain Lua > * write unit tests > * startup LuaJIT with custom memory allocator, to test some GC issues > * maybe, in future, use custom hashing function to test a behavior > of LuaJIT tables and so on. > > The module serves to achieve these goals without too fancy > features. > > It's functionality inspired by CMocka API [1], but only TAP14 [2] > protocol is supported (Version of TAP set to 13 to be compatible with > old TAP13 harnesses). > > The group of unit tests is declared like the following: > > | void *t_state = NULL; > | const struct test_unit tgroup[] = { > | test_unit_new(test_base), As I proposed in discussion verbally, I suggest to remove postfix "_new" because we have opposite function with destructor ("test_unit_delete"). > | test_unit_new(test_subtest), > | }; > | return test_run_group(tgroup, t_state); > > `test_run_group()` runs the whole group of tests, returns > `TEST_EXIT_SUCCESS` or `TEST_EXIT_FAILURE`. > > If a similar group is declared inside unit test, this group will be > considered as a subtest. > > This library provides an API similar to glibc (3) `assert()` to use > inside unit tests. `assert_[true,false]()` are useful for condition > checks and `assert_{type}_[not_,]_equal()` are useful for value > comparisons. If some assertion fails diagnostic is set, all test > considered as failing and finished via `longjmp()`, so these assertions > can be used inside custom subroutines. > > Also, this module provides ability to skip one test or all tests, mark > test as todo, bail out all tests. Just use `return skip()`, `skip_all()` > or `todo()` for early return. They should be used only in the test body > to make skipping clear. `skip_all()` may be used both for the parent > test and for a subtest. `bail_out()` prints an error message and exits > the process. Nit: replace skip_all() with skip_all(), bail_out() with bail_out(), todo() with todo() and skip() with skip(). I would describe these functions in a test/tarantool-c-tests/README.md too. BTW, why README.md was added in commit "test: introduce utils.h helper for C tests" and not in this patch? I believe this patch, where test lib added, is more appropriate place for API documentation. > > As a part of this commit, tarantool-c-tests directory is created with > the corresponding CMakeLists.txt file to build this test library. > Tests to be rewritten in C with this library in the next commit and > placed as unit tests are: > * lj-49-bad-lightuserdata.test.lua > * misclib-getmetrics-capi.test.lua > * misclib-sysprof-capi.test.lua > > For now the tarantool-c-tests target just build the test library without > new tests to run. > > The library itself is tested via some primitive tests for `ok` case, > `skip` and `todo` directives. The TAP13 format is tested via prove, that > we are using for running our tests. TAP14 format is compatible with > TAP13, so there are no other tests required. > > Also, .c_test suffix is added to the <.gitignore>. > > [1]: https://github.com/clibs/cmocka > [2]: https://testanything.org/tap-version-14-specification.html > > Part of tarantool/tarantool#7900 > --- > .gitignore | 1 + > test/CMakeLists.txt | 2 + > test/tarantool-c-tests/CMakeLists.txt | 66 +++++++ > test/tarantool-c-tests/test.c | 251 +++++++++++++++++++++++++ > test/tarantool-c-tests/test.h | 217 +++++++++++++++++++++ > test/tarantool-c-tests/unit-tap.test.c | 31 +++ > 6 files changed, 568 insertions(+) > create mode 100644 test/tarantool-c-tests/CMakeLists.txt > create mode 100644 test/tarantool-c-tests/test.c > create mode 100644 test/tarantool-c-tests/test.h > create mode 100644 test/tarantool-c-tests/unit-tap.test.c > > diff --git a/.gitignore b/.gitignore > index b7908aee..dc5ea5fc 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -24,3 +24,4 @@ install_manifest.txt > luajit-parse-memprof > luajit-parse-sysprof > luajit.pc > +*.c_test > diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt > index a8262b12..47296a22 100644 > --- a/test/CMakeLists.txt > +++ b/test/CMakeLists.txt > @@ -48,12 +48,14 @@ separate_arguments(LUAJIT_TEST_COMMAND) > add_subdirectory(LuaJIT-tests) > add_subdirectory(PUC-Rio-Lua-5.1-tests) > add_subdirectory(lua-Harness-tests) > +add_subdirectory(tarantool-c-tests) > add_subdirectory(tarantool-tests) > > add_custom_target(${PROJECT_NAME}-test DEPENDS > LuaJIT-tests > PUC-Rio-Lua-5.1-tests > lua-Harness-tests > + tarantool-c-tests > tarantool-tests > ) > > diff --git a/test/tarantool-c-tests/CMakeLists.txt b/test/tarantool-c-tests/CMakeLists.txt > new file mode 100644 > index 00000000..da128457 > --- /dev/null > +++ b/test/tarantool-c-tests/CMakeLists.txt > @@ -0,0 +1,66 @@ > +find_program(PROVE prove) > +if(NOT PROVE) > + message(WARNING "`prove' is not found, so tarantool-c-tests target is not generated") > + return() > +endif() > + > +set(C_TEST_SUFFIX .c_test) > +set(C_TEST_FLAGS --failures --shuffle) > + > +if(CMAKE_VERBOSE_MAKEFILE) > + list(APPEND C_TEST_FLAGS --verbose) > +endif() > + > +# Build libtest. > + > +set(TEST_LIB_NAME "test") > +add_library(libtest STATIC EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/test.c) > +target_include_directories(libtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) > +set_target_properties(libtest PROPERTIES > + COMPILE_FLAGS "-Wall -Wextra" > + OUTPUT_NAME "${TEST_LIB_NAME}" > + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" > +) > + > +# TARGET_C_FLAGS is required here to be sure that headers like > +# lj_arch.h in compiled test are consistent with the LuaJIT library > +# to link. > +AppendFlags(TESTS_C_FLAGS ${TARGET_C_FLAGS}) > + > +set(CTEST_SRC_SUFFIX ".test.c") > +file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/*${CTEST_SRC_SUFFIX}") > +foreach(test_source ${tests}) > + # Get test name without suffix. Needed to set OUTPUT_NAME. > + get_filename_component(exe ${test_source} NAME_WE) > + add_executable(${exe} EXCLUDE_FROM_ALL ${test_source}) > + target_include_directories(${exe} PRIVATE > + ${CMAKE_CURRENT_SOURCE_DIR} > + ${LUAJIT_SOURCE_DIR} > + ) > + set_target_properties(${exe} PROPERTIES > + COMPILE_FLAGS "${TESTS_C_FLAGS}" > + OUTPUT_NAME "${exe}${C_TEST_SUFFIX}" > + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" > + ) > + target_link_libraries(${exe} libtest ${LUAJIT_LIBRARY}) > + LIST(APPEND TESTS_COMPILED ${exe}) > +endforeach() > + > +add_custom_target(tarantool-c-tests > + DEPENDS libluajit libtest ${TESTS_COMPILED} > +) > + > +add_custom_command(TARGET tarantool-c-tests > + COMMENT "Running Tarantool C tests" > + COMMAND > + ${PROVE} > + ${CMAKE_CURRENT_BINARY_DIR} > + --ext ${C_TEST_SUFFIX} > + --jobs ${CMAKE_BUILD_PARALLEL_LEVEL} > + # Report any TAP parse errors, if any, since test module is > + # maintained by us. > + --parse > + ${C_TEST_FLAGS} > + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} > +) > + > diff --git a/test/tarantool-c-tests/test.c b/test/tarantool-c-tests/test.c > new file mode 100644 > index 00000000..74cba3a3 > --- /dev/null > +++ b/test/tarantool-c-tests/test.c > @@ -0,0 +1,251 @@ > +#include "test.h" > + > +/* > + * Test module, based on TAP 14 specification [1]. > + * [1]: https://testanything.org/tap-version-14-specification.html > + */ > + > +/* Need for `PATH_MAX` in diagnostic definition. */ > +#include > +#include > +#include > +/* Need for `strchr()` in diagnostic parsing. */ > +#include > + > +/* > + * Test level: 0 for the parent test, >0 for any subtests. > + */ > +static int level = -1; > + > +/* > + * The last diagnostic data to be used in the YAML Diagnostic > + * block. > + * > + * Contains filename, line number and failed expression or assert > + * name and "got" and "expected" fields. All entries are separated > + * by \n. > + * The longest field is filename here, so PATH_MAX * 3 as > + * the diagnostic string length should be enough. > + * > + * The first \0 means the end of diagnostic data. > + * > + * As far as `strchr()` searches until \0, all previous entries > + * are suppressed by the last one. If the first byte is \0 -- > + * diagnostic is empty. > + */ > +#define TEST_DIAG_DATA_MAX (PATH_MAX * 3) > +char test_diag_buf[TEST_DIAG_DATA_MAX] = {0}; > + > +const char *skip_reason = NULL; > +const char *todo_reason = NULL; > + > +/* Indent for the TAP. 4 spaces is default for subtest. */ > +static void indent(void) > +{ > + int i; > + for (i = 0; i < level; i++) > + printf(" "); > +} > + > +void test_message(const char *fmt, ...) > +{ > + va_list ap; > + indent(); > + va_start(ap, fmt); > + vprintf(fmt, ap); > + printf("\n"); > + va_end(ap); > +} > + > +static void test_print_tap_version(void) > +{ > + /* > + * Since several TAP13 parsers in popular usage treat > + * a repeated Version declaration as an error, even if the > + * Version is indented, Subtests _should not_ include a > + * Version, if TAP13 Harness compatibility is > + * desirable [1]. > + */ > + if (level == 0) > + test_message("TAP version %d", TAP_VERSION); > +} > + > +static void test_start_comment(const char *t_name) > +{ > + if (level > -1) > + /* > + * Inform about starting subtest, easier for > + * humans to read. > + * Subtest with a name must be terminated by a > + * Test Point with a matching Description [1]. > + */ > + test_comment("Subtest: %s", t_name); > +} > + > +void _test_print_skip_all(const char *group_name, const char *reason) > +{ > + test_start_comment(group_name); > + /* > + * XXX: This test isn't started yet, so set indent level > + * manually. > + */ > + level++; > + test_print_tap_version(); > + /* > + * XXX: `SKIP_DIRECTIVE` is not necessary here according > + * to the TAP14 specification [1], but some harnesses may > + * fail to parse the output without it. > + */ > + test_message("1..0" SKIP_DIRECTIVE "%s", reason); > + level--; > +} > + > +/* Just inform TAP parser how many tests we want to run. */ > +static void test_plan(size_t planned) > +{ > + test_message("1..%lu", planned); > +} > + > +/* Human-readable output how many tests/subtests are failed. */ > +static void test_finish(size_t planned, size_t failed) > +{ > + const char *t_type = level == 0 ? "tests" : "subtests"; > + if (failed > 0) > + test_comment("Failed %lu %s out of %lu", > + failed, t_type, planned); > + fflush(stdout); > +} > + > +void test_set_skip_reason(const char *reason) > +{ > + skip_reason = reason; > +} > + > +void test_set_todo_reason(const char *reason) > +{ > + todo_reason = reason; > +} > + > +void test_save_diag_data(const char *fmt, ...) > +{ > + va_list ap; > + va_start(ap, fmt); > + vsnprintf(test_diag_buf, TEST_DIAG_DATA_MAX, fmt, ap); > + va_end(ap); > +} > + > +static void test_clear_diag_data(void) > +{ > + /* > + * Limit buffer with zero byte to show that there is no > + * any entry. > + */ > + test_diag_buf[0] = '\0'; > +} > + > +static int test_diagnostic_is_set(void) > +{ > + return test_diag_buf[0] != '\0'; > +} > + > +/* > + * Parse the last diagnostic data entry and print it in YAML > + * format with the corresponding additional half-indent in TAP > + * (2 spaces). > + * Clear diagnostic message to be sure that it's printed once. > + * XXX: \n separators are changed to \0 during parsing and > + * printing output for convenience in usage. > + */ > +static void test_diagnostic(void) > +{ > + test_message(" ---"); > + char *ent = test_diag_buf; > + char *ent_end = NULL; > + while ((ent_end = strchr(ent, '\n')) != NULL) { > + char *next_ent = ent_end + 1; > + /* > + * Limit string with with the zero byte for > + * formatted output. Anyway, don't need this \n > + * anymore. > + */ > + *ent_end = '\0'; > + test_message(" %s", ent); > + ent = next_ent; > + } > + test_message(" ..."); > + test_clear_diag_data(); > +} > + > +static jmp_buf test_run_env; > + > +TEST_NORET void _test_exit(int status) > +{ > + longjmp(test_run_env, status); > +} > + > +static int test_run(const struct test_unit *test, size_t test_number, > + void *test_state) > +{ > + int status = TEST_EXIT_SUCCESS; > + /* > + * Run unit test. Diagnostic in case of failure setup by > + * helpers assert macros defined in the header. > + */ > + int jmp_status; > + if ((jmp_status = setjmp(test_run_env)) == 0) { > + if (test->f(test_state) != TEST_EXIT_SUCCESS) > + status = TEST_EXIT_FAILURE; > + } else { > + status = jmp_status - TEST_JMP_STATUS_SHIFT; > + } > + const char *result = status == TEST_EXIT_SUCCESS ? "ok" : "not ok"; > + > + /* > + * Format suffix of the test message for SKIP or TODO > + * directives. > + */ > +#define SUFFIX_SZ 1024 > + char suffix[SUFFIX_SZ] = {0}; > + if (skip_reason) { > + snprintf(suffix, SUFFIX_SZ, SKIP_DIRECTIVE "%s", skip_reason); > + skip_reason = NULL; > + } else if (todo_reason) { > + /* Prevent count this test as failed. */ > + status = TEST_EXIT_SUCCESS; > + snprintf(suffix, SUFFIX_SZ, TODO_DIRECTIVE "%s", todo_reason); > + todo_reason = NULL; > + } > +#undef SUFFIX_SZ > + > + test_message("%s %lu - %s%s", result, test_number, test->name, > + suffix); > + > + if (status && test_diagnostic_is_set()) > + test_diagnostic(); > + return status; > +} > + > +int _test_run_group(const char *group_name, const struct test_unit tests[], > + size_t n_tests, void *test_state) > +{ > + test_start_comment(group_name); > + > + level++; > + test_print_tap_version(); > + > + test_plan(n_tests); > + > + size_t n_failed = 0; > + > + size_t i; > + for (i = 0; i < n_tests; i++) { > + size_t test_number = i + 1; > + /* Return 1 on failure, 0 on success. */ > + n_failed += test_run(&tests[i], test_number, test_state); > + } > + > + test_finish(n_tests, n_failed); > + > + level--; > + return n_failed > 0 ? TEST_EXIT_FAILURE : TEST_EXIT_SUCCESS; > +} > diff --git a/test/tarantool-c-tests/test.h b/test/tarantool-c-tests/test.h > new file mode 100644 > index 00000000..047f01a5 > --- /dev/null > +++ b/test/tarantool-c-tests/test.h > @@ -0,0 +1,217 @@ > +#ifndef TARANTOOL_LUAJIT_TEST_H > +#define TARANTOOL_LUAJIT_TEST_H > + > +#include > +#include > + > +/* > + * Test module, based on TAP 14 specification [1]. > + * [1]: https://testanything.org/tap-version-14-specification.html > + * Version 13 is set for better compatibility on old machines. > + * > + * TODO: > + * * Helpers assert macros: > + * - assert_uint_equal if needed > + * - assert_uint_not_equal if needed > + * - assert_str_equal if needed > + * - assert_str_not_equal if needed > + * - assert_memory_equal if needed > + * - assert_memory_not_equal if needed > + * * Pragmas. > + */ > + > +#define TAP_VERSION 13 > + > +#define TEST_EXIT_SUCCESS 0 > +#define TEST_EXIT_FAILURE 1 > + > +#define TEST_JMP_STATUS_SHIFT 2 > +#define TEST_LJMP_EXIT_SUCCESS (TEST_EXIT_SUCCESS + TEST_JMP_STATUS_SHIFT) > +#define TEST_LJMP_EXIT_FAILURE (TEST_EXIT_FAILURE + TEST_JMP_STATUS_SHIFT) > + > +#define TEST_NORET __attribute__((noreturn)) > + > +typedef int (*test_func)(void *test_state); > +struct test_unit { > + const char *name; > + test_func f; > +}; > + > +/* API declaration. */ > + > +/* > + * Print formatted message with the corresponding indent. > + * If you want to leave a comment, use `test_comment()` instead. > + */ > +void test_message(const char *fmt, ...); > + > +/* Need for `skip_all()`, please, don't use it. */ > +void _test_print_skip_all(const char *group_name, const char *reason); > +/* End test via `longjmp()`, please, don't use it. */ > +TEST_NORET void _test_exit(int status); > + > +void test_set_skip_reason(const char *reason); > +void test_set_todo_reason(const char *reason); > +/* > + * Save formatted diagnostic data. Each entry separated with \n. > + */ > +void test_save_diag_data(const char *fmt, ...); > + > +/* Internal, it is better to use `test_run_group()` instead. */ > +int _test_run_group(const char *group_name, const struct test_unit tests[], > + size_t n_tests, void *test_state); > + > +/* Initialize `test_unit` structure. */ > +#define test_unit_new(f) {#f, f} > + > +#define lengthof(arr) (sizeof(arr) / sizeof((arr)[0])) > + > +/* > + * __func__ is the name for a test group, "main" for the parent > + * test. > + */ > +#define test_run_group(t_arr, t_state) \ > + _test_run_group(__func__, t_arr, lengthof(t_arr), t_state) > + > +#define SKIP_DIRECTIVE " # SKIP " > +#define TODO_DIRECTIVE " # TODO " > + > +#define skip_all(reason) ({ \ > + _test_print_skip_all(__func__, reason); \ > + TEST_EXIT_SUCCESS; \ > +}) > + > +static inline int skip(const char *reason) > +{ > + test_set_skip_reason(reason); > + return TEST_EXIT_SUCCESS; > +} > + > +static inline int todo(const char *reason) > +{ > + test_set_todo_reason(reason); > + return TEST_EXIT_FAILURE; > +} > + > +#define bail_out(reason) do { \ > + /* \ > + * For backwards compatibility with TAP13 Harnesses, \ > + * Producers _should_ emit a "Bail out!" line at the root \ > + * indentation level whenever a Subtest bails out [1]. \ > + */ \ > + printf("Bail out! %s\n", reason); \ > + exit(TEST_EXIT_FAILURE); \ > +} while (0) > + > +/* `fmt` should always be a format string here. */ > +#define test_comment(fmt, ...) test_message("# " fmt, __VA_ARGS__) > + > +/* > + * This is a set of useful assert macros like the standard C > + * libary's assert(3) macro. > + * > + * On an assertion failure an assert macro will save the > + * diagnostic to the special buffer, to be reported via YAML > + * Diagnostic block and finish a test function with > + * `return TEST_EXIT_FAILURE`. > + * > + * Due to limitations of the C language `assert_true()` and > + * `assert_false()` macros can only display the expression that > + * caused the assertion failure. Type specific assert macros, > + * `assert_{type}_equal()` and `assert_{type}_not_equal()`, save > + * the data that caused the assertion failure which increases data > + * visibility aiding debugging of failing test cases. > + */ > + > +#define LOCATION_FMT "location:\t%s:%d\n" > +#define ASSERT_NAME_FMT(name) "failed_assertion:\t" #name "\n" > +#define ASSERT_EQUAL_FMT(name_type, type_fmt) \ > + LOCATION_FMT \ > + ASSERT_NAME_FMT(assert_ ## name_type ## _equal) \ > + "got: " type_fmt "\n" \ > + "expected: " type_fmt "\n" > + > +#define ASSERT_NOT_EQUAL_FMT(type_fmt) \ > + LOCATION_FMT \ > + ASSERT_NAME_FMT(assert_ ## name_type ## _not_equal) \ > + "got: " type_fmt "\n" \ > + "unexpected: " type_fmt "\n" > + > +#define assert_true(cond) do { \ > + if (!(cond)) { \ > + test_save_diag_data(LOCATION_FMT \ > + "condition_failed:\t'" #cond "'\n", \ > + __FILE__, __LINE__); \ > + _test_exit(TEST_LJMP_EXIT_FAILURE); \ > + } \ > +} while (0) > + > +#define assert_false(cond) assert_true(!(cond)) > + > +#define assert_general(cond, fmt, ...) do { \ > + if (!(cond)) { \ > + test_save_diag_data(fmt, __VA_ARGS__); \ > + _test_exit(TEST_LJMP_EXIT_FAILURE); \ > + } \ > +} while (0) > + > +#define assert_ptr_equal(got, expected) do { \ > + assert_general((got) == (expected), \ > + ASSERT_EQUAL_FMT(ptr, "%p"), \ > + __FILE__, __LINE__, (got), (expected) \ > + ); \ > +} while (0) > + > +#define assert_ptr_not_equal(got, unexpected) do { \ > + assert_general((got) != (unexpected), \ > + ASSERT_NOT_EQUAL_FMT(ptr, "%p"), \ > + __FILE__, __LINE__, (got), (unexpected) \ > + ); \ > +} while (0) > + > + > +#define assert_int_equal(got, expected) do { \ > + assert_general((got) == (expected), \ > + ASSERT_EQUAL_FMT(int, "%d"), \ > + __FILE__, __LINE__, (got), (expected) \ > + ); \ > +} while (0) > + > +#define assert_int_not_equal(got, unexpected) do { \ > + assert_general((got) != (unexpected), \ > + ASSERT_NOT_EQUAL_FMT(int, "%d"), \ > + __FILE__, __LINE__, (got), (unexpected) \ > + ); \ > +} while (0) > + > +#define assert_sizet_equal(got, expected) do { \ > + assert_general((got) == (expected), \ > + ASSERT_EQUAL_FMT(sizet, "%lu"), \ > + __FILE__, __LINE__, (got), (expected) \ > + ); \ > +} while (0) > + > +#define assert_sizet_not_equal(got, unexpected) do { \ > + assert_general((got) != (unexpected), \ > + ASSERT_NOT_EQUAL_FMT(sizet, "%lu"), \ > + __FILE__, __LINE__, (got), (unexpected) \ > + ); \ > +} while (0) > + > +/* Check that doubles are __exactly__ the same. */ > +#define assert_double_equal(got, expected) do { \ > + assert_general((got) == (expected), \ > + ASSERT_EQUAL_FMT(double, "%lf"), \ > + __FILE__, __LINE__, (got), (expected) \ > + ); \ > +} while (0) > + > +/* Check that doubles are not __exactly__ the same. */ > +#define assert_double_not_equal(got, unexpected) do { \ > + assert_general((got) != (unexpected), \ > + ASSERT_NOT_EQUAL_FMT(double, "%lf"), \ > + __FILE__, __LINE__, (got), (unexpected) \ > + ); \ > +} while (0) I propose to describe all types of assertions in the test/tarantool-c-tests/README.md. > + > +#endif /* TARANTOOL_LUAJIT_TEST_H */ > diff --git a/test/tarantool-c-tests/unit-tap.test.c b/test/tarantool-c-tests/unit-tap.test.c > new file mode 100644 > index 00000000..27dc84ee > --- /dev/null > +++ b/test/tarantool-c-tests/unit-tap.test.c > @@ -0,0 +1,31 @@ > +#include "test.h" > + > +#define UNUSED(x) ((void)(x)) > + > +static int test_ok(void *test_state) > +{ > + UNUSED(test_state); > + return TEST_EXIT_SUCCESS; > +} > + > +static int test_skip(void *test_state) > +{ > + UNUSED(test_state); > + return skip("test skip"); > +} > + > +static int test_todo(void *test_state) > +{ > + UNUSED(test_state); > + return todo("test todo"); > +} > + > +int main(void) > +{ > + const struct test_unit tgroup[] = { > + test_unit_new(test_ok), > + test_unit_new(test_skip), > + test_unit_new(test_todo) > + }; > + return test_run_group(tgroup, NULL); > +}