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 E670A4AC1B8; Wed, 14 Jun 2023 11:47:54 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org E670A4AC1B8 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1686732475; bh=JoMitnjLDvZA1kCEpwi/fF7cpvg7lX/NB0Xs4iM6rFo=; 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=CDM8qbc3btDO5t+88dUw0p725LyhxxIqiWRunNE1x4suZH8FZExKZ+fMFcykgzk8u 6xi1TBm4h5ujcFQa7G94uzBeLz5okvWpzB0JVhH8tEGHCeUSMVPYTo9fdMtfO38GRU BzUj6kT85+SC0jwiyG8estsOZDxOY6j8uFG5wWJ4= Received: from smtp32.i.mail.ru (smtp32.i.mail.ru [95.163.41.73]) (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 928584AC1B8 for ; Wed, 14 Jun 2023 11:47:53 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 928584AC1B8 Received: by smtp32.i.mail.ru with esmtpa (envelope-from ) id 1q9MAS-00BbVp-B5; Wed, 14 Jun 2023 11:47:53 +0300 Message-ID: <81bf4eef-17bc-ebb8-4a3a-5913059c4494@tarantool.org> Date: Wed, 14 Jun 2023 11:47:51 +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: In-Reply-To: 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: 4F1203BC0FB41BD95D99986233CC4DDC0CFA96C1FC16B4C08BF6D14A4A27CEF2182A05F538085040160518234E0FC316BABDB0AC7A5C14BD0BB9025CDA66873F53900C971C0DFA5E X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE749E89BD568380EECC2099A533E45F2D0395957E7521B51C2CFCAF695D4D8E9FCEA1F7E6F0F101C6778DA827A17800CE72E0BB8D059315229EA1F7E6F0F101C6723150C8DA25C47586E58E00D9D99D84E1BDDB23E98D2D38BE5CCB53A13BC8DBAF4C70671B6265EC70A703DB4884DC805CC7F00164DA146DAFE8445B8C89999728AA50765F79006370D9A29B7FD16D1239FA2833FD35BB23D2EF20D2F80756B5F868A13BD56FB6657A471835C12D1D977725E5C173C3A84C327E9584FBD6BDD31117882F4460429728AD0CFFFB425014E868A13BD56FB6657D81D268191BDAD3DC09775C1D3CA48CFE94F0B04D026D522BA3038C0950A5D36C8A9BA7A39EFB766EC990983EF5C0329BA3038C0950A5D36D5E8D9A59859A8B63770467DA350783E76E601842F6C81A1F004C906525384303E02D724532EE2C3F43C7A68FF6260569E8FC8737B5C2249EC8D19AE6D49635B68655334FD4449CB9ECD01F8117BC8BEAAAE862A0553A39223F8577A6DFFEA7CAA44A86D94E7BBB043847C11F186F3C59DAA53EE0834AAEE X-C1DE0DAB: 0D63561A33F958A537EB0A4F67441C16777D43E2650C41D07663DAD53A083D8AF87CCE6106E1FC07E67D4AC08A07B9B0CE135D2742255B35CB5012B2E24CD356 X-C8649E89: 1C3962B70DF3F0ADE00A9FD3E00BEEDF3FED46C3ACD6F73ED3581295AF09D3DF87807E0823442EA2ED31085941D9CD0AF7F820E7B07EA4CF43099DB1E8C77D9E6D2F67935DA4E482C1CABC08942AC1727AE4943DD50025A5E125D5F9813FCCC5513AFAF93E604BF59917CA2EABF56FB8AD6969A483D1CFD1A74DFFEFA5DC0E7F02C26D483E81D6BE0DBAE6F56676BC7117BB6831D7356A2DEC5B5AD62611EEC62B5AFB4261A09AF0 X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu530nj6fImhcD4MUrOEAnl0W826KZ9Q+tr5ycPtXkTV4k65bRjmOUUP8cvGozZ33TWg5HZplvhhXbhDGzqmQDTd6OAevLeAnq3Ra9uf7zvY2zzsIhlcp/Y7m53TZgf2aB4JOg4gkr2biojdUHSxnroE51ecmQnQM1X/Q== X-Mailru-Sender: 11C2EC085EDE56FAC07928AF2646A76981BAFBEF8B153FE8BABDB0AC7A5C14BD60497F7EF99AAC8FEBA65886582A37BD66FEC6BF5C9C28D98A98C1125256619760D574B6FC815AB872D6B4FCE48DF648AE208404248635DF X-Mras: Ok Subject: Re: [Tarantool-patches] [PATCH v4 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" Thanks for your fixes, Sergey! LGTM now. On 6/10/23 11:03, 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_def(test_base), > | test_unit_def(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("reason")`, > `skip_all("reason")` or `todo("reason")` for early return. They should > be used only in the test body to make skipping clear. > `skip_all("reason")` may be used both for the parent test and for a > subtest. `bail_out("reason")` prints an error message and exits the > process. > > 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/README.md | 46 +++++ > test/tarantool-c-tests/test.c | 251 +++++++++++++++++++++++++ > test/tarantool-c-tests/test.h | 217 +++++++++++++++++++++ > test/tarantool-c-tests/unit-tap.test.c | 31 +++ > 7 files changed, 614 insertions(+) > create mode 100644 test/tarantool-c-tests/CMakeLists.txt > create mode 100644 test/tarantool-c-tests/README.md > 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/README.md b/test/tarantool-c-tests/README.md > new file mode 100644 > index 00000000..04ddb729 > --- /dev/null > +++ b/test/tarantool-c-tests/README.md > @@ -0,0 +1,46 @@ > +# Tarantool C tests > +This directory contains C tests for Tarantool's fork of LuaJIT. > + > +They should test C API, some unit functionality, etc.. > + > +## How to start > + > +The group of unit tests is declared like the following: > +```c > +void *t_state = NULL; > +const struct test_unit tgroup[] = { > + test_unit_def(test_base), > + test_unit_def(test_simple), > +}; > +return test_run_group(tgroup, t_state); > +``` > + > +Each of the unit tests has the following definition: > + > +```c > +static int test_base(void *test_state) > +``` > + > +## Assertions > + > +The following assertions are defined in to be used instead default > +glibc `assert()`: > +* `assert_true(cond)` -- check that condition `cond` is true. > +* `assert_false(cond)` -- check that condition `cond` is false. > +* `assert_ptr{_not}_equal(a, b)` -- check that pointer `a` is {not} equal to > + the `b`. > +* `assert_int{_not}_equal(a, b)` -- check that `int` variable `a` is {not} > + equal to the `b`. > +* `assert_sizet{_not}_equal(a, b)` -- check that `size_t` variable `a` is {not} > + equal to the `b`. > +* `assert_double{_not}_equal(a, b)` -- check that two doubles are {not} > + **exactly** equal. > + > +## Directives > + > +The following directives are supported to stop unit test or group of tests > +earlier: > +* `skip("reason")` -- skip the current test. > +* `skip_all("reason")` -- skip the current group of tests. > +* `todo("reason")` -- skip the current test marking as TODO. > +* `bail_out("reason")` -- exit the entire process due to some emergency. > 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..8b14c705 > --- /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_def(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) > + > +#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..ca0709ca > --- /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_def(test_ok), > + test_unit_def(test_skip), > + test_unit_def(test_todo) > + }; > + return test_run_group(tgroup, NULL); > +}