Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests
@ 2023-06-05 10:41 Sergey Kaplun via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables Sergey Kaplun via Tarantool-patches
                   ` (5 more replies)
  0 siblings, 6 replies; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

The whole idea of the patch-set introduce module for LuaJIT C tests. It
also, can be used for unit tests.
* The first patch is the prerequisite for the patch-set. It fixes
  LD_LIBRARY_PATH definition.
* The 2nd and 3d patches provides an API and helper for writing the tests.
* The last 3 patches rewrite existing tests that should be written in C in
  the proper way.

Branch: https://github.com/tarantool/luajit/tree/skaplun/gh-noticket-tarantool-c-tests
PR: https://github.com/tarantool/tarantool/pull/8444
Related Issue:
* https://github.com/tarantool/tarantool/issues/7900
* https://github.com/tarantool/tarantool/issues/781

Changes in v2:
1) use

| int _test_run_group(const char *group_name, const struct test_unit tests[],
| 		    size_t n_tests, void *test_state);

instead of

| int _test_run_group(const char *group_name, const struct test_unit *tests,
| 		    size_t n_tests, void *test_state);

2) `skip()` `skip_all()` and `todo()` helpers now return values to be
return to runner.
i.e. change usage from
| if (cond)
| 	skip("NIY");
to
| if (cond)
| 	return skip("NIY");

`bail_out()` helper still just exits with error code, which corresponding
its standard specification.

But now some parts of the code start to look "alya cringe":
| return todo("Need to replace backtrace with libunwind first");
| lua_State *L = test_state;
| utils_get_aux_lfunc(L);
| (void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF);
| (void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_FLUSH);
| check_profile_func(L);
| (void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON);
| return TEST_EXIT_SUCCESS;

(Yes, we want to use unconditional `todo()`).
So I commented the similar code, helper `check_profile_func()`, etc.
with `#if 0`.

3) In 4th patch use subtest group for snap-related tests to skip all at
once with disabled JIT.

4) lj-49-bad-lightuserdata is moved in C tests, too.

Changes in the v3:

1) Now helper `utils_load_aux_script()` from <utils.h> accepts the
   filename of the Lua script to load (so, now define for test contains
   the directory instead of the test filename).
2) Add README.md for the 3rd commit with an example of usage.
3) Add unit tests (ok, skip, todo) for <test.c> lib.
4) Move test builds to the 2nd commit to launch unit test
   (<unit-tap.test.c>), updating here <.gitignore> too.
5) Fix typos.

Sergey Kaplun (6):
  test: fix setting of {DY}LD_LIBRARY_PATH variables
  test: introduce module for C tests
  test: introduce utils.h helper for C tests
  test: rewrite misclib-getmetrics-capi test in C
  test: rewrite misclib-sysprof-capi test in C
  test: rewrite lj-49-bad-lightuserdata test in C

 .gitignore                                    |   1 +
 src/CMakeLists.txt                            |   2 +
 test/CMakeLists.txt                           |   2 +
 test/tarantool-c-tests/CMakeLists.txt         |  69 ++++
 test/tarantool-c-tests/README.md              |  36 ++
 .../lj-49-bad-lightuserdata.test.c}           |  47 +--
 .../misclib-getmetrics-capi-script.lua}       |  83 ++---
 .../misclib-getmetrics-capi.test.c            | 343 ++++++++++++++++++
 .../misclib-sysprof-capi-script.lua           |  35 ++
 .../misclib-sysprof-capi.test.c               | 325 +++++++++++++++++
 test/tarantool-c-tests/test.c                 | 251 +++++++++++++
 test/tarantool-c-tests/test.h                 | 217 +++++++++++
 test/tarantool-c-tests/unit-tap.test.c        |  31 ++
 test/tarantool-c-tests/utils.h                |  75 ++++
 test/tarantool-tests/CMakeLists.txt           |  12 +-
 .../lj-49-bad-lightuserdata.test.lua          |  11 -
 .../lj-49-bad-lightuserdata/CMakeLists.txt    |   1 -
 .../misclib-getmetrics-capi/CMakeLists.txt    |   1 -
 .../misclib-getmetrics-capi/testgetmetrics.c  | 270 --------------
 .../misclib-sysprof-capi.test.lua             |  54 ---
 .../misclib-sysprof-capi/CMakeLists.txt       |   1 -
 .../misclib-sysprof-capi/testsysprof.c        | 260 -------------
 22 files changed, 1457 insertions(+), 670 deletions(-)
 create mode 100644 test/tarantool-c-tests/CMakeLists.txt
 create mode 100644 test/tarantool-c-tests/README.md
 rename test/{tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c => tarantool-c-tests/lj-49-bad-lightuserdata.test.c} (55%)
 rename test/{tarantool-tests/misclib-getmetrics-capi.test.lua => tarantool-c-tests/misclib-getmetrics-capi-script.lua} (68%)
 create mode 100644 test/tarantool-c-tests/misclib-getmetrics-capi.test.c
 create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi-script.lua
 create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi.test.c
 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
 create mode 100644 test/tarantool-c-tests/utils.h
 delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
 delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
 delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
 delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi.test.lua
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/testsysprof.c

-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-05 14:21   ` Sergey Bronnikov via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests Sergey Kaplun via Tarantool-patches
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

When we set `LUA_TEST_ENV_MORE` variable to be used in the additional
env command for run testing if `"` is used to wrap the `LD_LIBRARY_PATH`
value the content of this environment variable is literally
`"/abs/path1:/abs/path2:...:"`. So, the first entry is treated as the
relative path starting with `"`. In that case if we need the library to
be loaded via FFI for this particular test, that loading fails with the
error "cannot open shared object file", since the path to it is
incorrect.

This patch removes `"` wrapping for the aforementioned variables.
---
 test/tarantool-tests/CMakeLists.txt | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
index a428d009..38d6ae49 100644
--- a/test/tarantool-tests/CMakeLists.txt
+++ b/test/tarantool-tests/CMakeLists.txt
@@ -102,6 +102,11 @@ endif()
 # loaded modules on MacOS instead of shared libraries as it is
 # done on Linux and BSD, another environment variable should be
 # used to guide <ffi.load> while searching the extension.
+# XXX: Be noticed that we shouldn't use `"` here to wrap
+# the variable's content. If we do this, the variable value will
+# contain `"` at the beginning and the end, so this `"` at the
+# beginning will be treated as the directory for the first entry
+# (the last subdirectory added).
 if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
   # XXX: Apple tries their best to "protect their users from
   # malware". As a result SIP (see the link[1] below) has been
@@ -122,9 +127,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
   #
   # [1]: https://support.apple.com/en-us/HT204899
   # [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
-  list(APPEND LUA_TEST_ENV_MORE DYLD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
+  list(APPEND LUA_TEST_ENV_MORE DYLD_LIBRARY_PATH=${LD_LIBRARY_PATH})
 else()
-  list(APPEND LUA_TEST_ENV_MORE LD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
+  list(APPEND LUA_TEST_ENV_MORE LD_LIBRARY_PATH=${LD_LIBRARY_PATH})
 endif()
 
 # LUA_CPATH and LD_LIBRARY_PATH variables and also TESTLIBS list
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-05 15:08   ` Sergey Bronnikov via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper " Sergey Kaplun via Tarantool-patches
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

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 <test.c> 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),
| 	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.

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 <limits.h>
+#include <setjmp.h>
+#include <stdarg.h>
+/* Need for `strchr()` in diagnostic parsing. */
+#include <string.h>
+
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+
+/*
+ * 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)
+
+#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);
+}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper for C tests
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables Sergey Kaplun via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-05 15:11   ` Sergey Bronnikov via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C Sergey Kaplun via Tarantool-patches
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

This header contains generic init and close functions for tests and
helpers for loading auxiliary Lua script with functions to run inside a
C test.  See more information in the <README.md> file.

It will be properly used in the next commit.

Part of tarantool/tarantool#7900
---
 test/tarantool-c-tests/CMakeLists.txt |  3 ++
 test/tarantool-c-tests/README.md      | 36 +++++++++++++
 test/tarantool-c-tests/utils.h        | 75 +++++++++++++++++++++++++++
 3 files changed, 114 insertions(+)
 create mode 100644 test/tarantool-c-tests/README.md
 create mode 100644 test/tarantool-c-tests/utils.h

diff --git a/test/tarantool-c-tests/CMakeLists.txt b/test/tarantool-c-tests/CMakeLists.txt
index da128457..17255345 100644
--- a/test/tarantool-c-tests/CMakeLists.txt
+++ b/test/tarantool-c-tests/CMakeLists.txt
@@ -26,6 +26,9 @@ set_target_properties(libtest PROPERTIES
 # lj_arch.h in compiled test are consistent with the LuaJIT library
 # to link.
 AppendFlags(TESTS_C_FLAGS ${TARGET_C_FLAGS})
+# Use directory for absolute path to the Lua script helper. So,
+# test binary can be run from any directory.
+AppendFlags(TESTS_C_FLAGS "-D__LJ_TEST_DIR__='\"${CMAKE_CURRENT_SOURCE_DIR}\"'")
 
 set(CTEST_SRC_SUFFIX ".test.c")
 file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/*${CTEST_SRC_SUFFIX}")
diff --git a/test/tarantool-c-tests/README.md b/test/tarantool-c-tests/README.md
new file mode 100644
index 00000000..102ec151
--- /dev/null
+++ b/test/tarantool-c-tests/README.md
@@ -0,0 +1,36 @@
+This directory contains C tests for Tarantool's fork of LuaJIT.
+
+They should test C API, some unit functionality, etc..
+
+The group of unit tests is declared like the following:
+```c
+void *t_state = NULL;
+const struct test_unit tgroup[] = {
+      test_unit_new(test_base),
+      test_unit_new(test_simple),
+};
+return test_run_group(tgroup, t_state);
+```
+
+Sometimes we need to test C API for modules, that show some Lua metrics (like
+`luaM_metrics` or sysprof). In these cases, the required Lua script should be
+named like the following: `<ctestname-script.lua>` and contains a table with a
+bunch of Lua functions named the same as the unit C test, that uses this
+function.
+
+```lua
+local M = {}
+M.base = function()
+  -- ...
+end
+
+M.test_simple = function()
+  -- ...
+end
+
+return M
+```
+
+The script is loaded via `utils_load_aux_script(L, script_name)`. It loads the file
+and place the table with functions at the top of Lua stack. Each function is get
+from the table via `utils_get_aux_lfunc(L)` helper in the corresponding test.
diff --git a/test/tarantool-c-tests/utils.h b/test/tarantool-c-tests/utils.h
new file mode 100644
index 00000000..714aa61a
--- /dev/null
+++ b/test/tarantool-c-tests/utils.h
@@ -0,0 +1,75 @@
+#ifndef TARANTOOL_LUAJIT_TEST_UTILS_H
+#define TARANTOOL_LUAJIT_TEST_UTILS_H
+
+#include <limits.h>
+#include <string.h>
+
+#include "lauxlib.h"
+#include "lua.h"
+#include "luajit.h"
+#include "lualib.h"
+
+#include "test.h"
+
+#define UTILS_UNUSED __attribute__((unused))
+
+/* Generic init for our tests. */
+static lua_State *utils_lua_init(void)
+{
+	lua_State *L = luaL_newstate();
+	if (!L)
+		bail_out("Can't init Lua state");
+	/* Stop collector during library initialization. */
+	lua_gc(L, LUA_GCSTOP, 0);
+	luaL_openlibs(L);
+	lua_gc(L, LUA_GCRESTART, -1);
+	return L;
+}
+
+/* Generic close for our tests. */
+static void utils_lua_close(lua_State *L)
+{
+	lua_close(L);
+}
+
+/*
+ * Load the pair to the test file <filename-script.lua>.
+ * Each file should return the table with functions (named the
+ * same as the corresponding unit tests) to call in unit tests.
+ * Script file name is given as the second argument.
+ * Push the table with those functions on the Lua stack.
+ */
+UTILS_UNUSED static void utils_load_aux_script(lua_State *L, const char *file)
+{
+	/*
+	 * Format script name.
+	 * `__LJ_TEST_DIR__` is set via CMake.
+	 */
+	char script[PATH_MAX] = __LJ_TEST_DIR__;
+	char *script_name = script + sizeof(__LJ_TEST_DIR__) - 1;
+	/* Replace '\0' with '/'. */
+	*script_name++ = '/';
+	/* Append script filename. */
+	strcpy(script_name, file);
+
+	if (luaL_dofile(L, script) != LUA_OK) {
+		test_comment("Can't load %s: '%s'", script,
+			     lua_tostring(L, -1));
+		bail_out("Can't load auxiliary script");
+	}
+
+	if (!lua_istable(L, -1))
+		bail_out("Returned value from script is not a table");
+}
+
+/*
+ * Accept a table on top of the Lua stack which containing the
+ * function named as the unit test we currently executing.
+ */
+#define utils_get_aux_lfunc(L) do {					\
+	lua_getfield((L), -1, __func__);				\
+	if (!lua_isfunction((L), -1))					\
+		bail_out("Can't get auxiliary test function");		\
+} while (0)
+
+#endif /* TARANTOOL_LUAJIT_TEST_UTILS_H */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
                   ` (2 preceding siblings ...)
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper " Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-07 16:25   ` Sergey Bronnikov via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi " Sergey Kaplun via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata " Sergey Kaplun via Tarantool-patches
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

This patch rewrites the aforementioned test with the usage libtest,
recently introduced. Since we still stand in need of a Lua helper script
for generation of traces, the original test file is reworked as a
standalone script, which returns the table with helper functions. Each
helper function is named the same as the test where it will be used. Now
single quotes are used according to our Lua code style.

In C part all asserts from glibc are replaced with the corresponding
assert_{cond} from the libtest. Now tests return `TEST_EXIT_SUCCESS` at
the finish. Also, the stack check for the amount of return results from
the helper function is slightly changed, since there is one more stack
slot in use (table with these functions). `snap_restores()` C function
duplicates 4 times for each subtest. Common helper
`check_snap_restores()` is used for each of them. Each error throwing is
replaced with `bail_out()` call.

NB: `lua_pop()` to clear the Lua stack after a call should be done before
any possible assertion, which would exit from the test leaving the stack
uncleaned.

All skipconds use macros defined in <lj_arch.h>, so it is included in
the test. As far as this test initializes the LuaJIT VM manually, there
is no need to check `jit.status()` result; checking `LJ_HASJIT` is
enough. Now, only JIT-related tests are skipped, when compiled without
JIT. Nevertheless, all tests are skipped for *BSD arches.

Also, this patch sets the new CMake variable named `LUAJIT_LIBRARY`
equal to `LUAJIT_LIB` in `PARENT_SCOPE` to be used in tarantool-c-tests
linking.

Part of tarantool/tarantool#7900
---
 src/CMakeLists.txt                            |   2 +
 .../misclib-getmetrics-capi-script.lua}       |  83 ++---
 .../misclib-getmetrics-capi.test.c            | 343 ++++++++++++++++++
 test/tarantool-tests/CMakeLists.txt           |   1 -
 .../misclib-getmetrics-capi/CMakeLists.txt    |   1 -
 .../misclib-getmetrics-capi/testgetmetrics.c  | 270 --------------
 6 files changed, 383 insertions(+), 317 deletions(-)
 rename test/{tarantool-tests/misclib-getmetrics-capi.test.lua => tarantool-c-tests/misclib-getmetrics-capi-script.lua} (68%)
 create mode 100644 test/tarantool-c-tests/misclib-getmetrics-capi.test.c
 delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
 delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index dffc0a4d..fed4c38d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -325,6 +325,8 @@ if(NOT BUILDMODE STREQUAL "dynamic")
   set(LUAJIT_BIN luajit_static)
   set(LUAJIT_LIB libluajit_static)
 endif()
+# Need for the test linking, so the PARENT_SCOPE option is used.
+set(LUAJIT_LIBRARY ${LUAJIT_LIB} PARENT_SCOPE)
 set(LIBLUAJIT_DEPS ${LIBLUAJIT_STATIC_DEPS} ${LIBLUAJIT_SHARED_DEPS})
 
 add_executable(${LUAJIT_BIN} EXCLUDE_FROM_ALL ${CLI_SOURCES})
diff --git a/test/tarantool-tests/misclib-getmetrics-capi.test.lua b/test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
similarity index 68%
rename from test/tarantool-tests/misclib-getmetrics-capi.test.lua
rename to test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
index 654e5545..2f0ee5cf 100644
--- a/test/tarantool-tests/misclib-getmetrics-capi.test.lua
+++ b/test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
@@ -1,32 +1,26 @@
-local tap = require('tap')
-local test = tap.test("clib-misc-getmetrics"):skipcond({
-  ['Test requires JIT enabled'] = not jit.status(),
-  ['Disabled on *BSD due to #4819'] = jit.os == 'BSD',
-})
+local ffi = require('ffi')
 
-test:plan(11)
+-- Auxiliary script to provide Lua functions to be used in tests
+-- for `getmetrics()` C API inside the test
+-- <misclib-getmetrics-capi.test.c>.
+local M = {}
 
-local path = arg[0]:gsub('%.test%.lua', '')
-local suffix = package.cpath:match('?.(%a+);')
-package.cpath = ('%s/?.%s;'):format(path, suffix)..package.cpath
+-- XXX: Max nins is limited by max IRRef, that equals to
+-- REF_DROP - REF_BIAS. Unfortunately, these constants are not
+-- provided to Lua space, so we ought to make some math:
+-- * REF_DROP = 0xffff
+-- * REF_BIAS = 0x8000
+-- See <src/lj_ir.h> for details.
+local MAXNINS =  0xffff - 0x8000
 
-local MAXNINS = require('utils').const.maxnins
 local jit_opt_default = {
     3, -- level
-    "hotloop=56",
-    "hotexit=10",
-    "minstitch=0",
+    'hotloop=56',
+    'hotexit=10',
+    'minstitch=0',
 }
 
-local testgetmetrics = require("testgetmetrics")
-
-test:ok(testgetmetrics.base())
-test:ok(testgetmetrics.gc_allocated_freed())
-test:ok(testgetmetrics.gc_steps())
-
-test:ok(testgetmetrics.objcount(function(iterations)
-    local ffi = require("ffi")
-
+M.objcount = function(iterations)
     jit.opt.start(0)
 
     local placeholder = {
@@ -51,35 +45,34 @@ test:ok(testgetmetrics.objcount(function(iterations)
 
     for _ = 1, iterations do
         -- Check counting of VLA/VLS/aligned cdata.
-        table.insert(placeholder.cdata, ffi.new("char[?]", 4))
+        table.insert(placeholder.cdata, ffi.new('char[?]', 4))
     end
 
     for _ = 1, iterations do
         -- Check counting of non-VLA/VLS/aligned cdata.
-        table.insert(placeholder.cdata, ffi.new("uint64_t", _))
+        table.insert(placeholder.cdata, ffi.new('uint64_t', _))
     end
 
     placeholder = nil -- luacheck: no unused
     -- Restore default jit settings.
     jit.opt.start(unpack(jit_opt_default))
-end))
+end
 
-test:ok(testgetmetrics.objcount_cdata_decrement(function()
+M.objcount_cdata_decrement = function()
     -- gc_cdatanum decrement test.
     -- See https://github.com/tarantool/tarantool/issues/5820.
-    local ffi = require("ffi")
     local function nop() end
-    ffi.gc(ffi.cast("void *", 0), nop)
+    ffi.gc(ffi.cast('void *', 0), nop)
     -- Does not collect the cdata, but resurrects the object and
     -- removes LJ_GC_CDATA_FIN flag.
     collectgarbage()
     -- Collects the cdata.
     collectgarbage()
-end))
+end
 
 -- Compiled loop with a direct exit to the interpreter.
-test:ok(testgetmetrics.snap_restores(function()
-    jit.opt.start(0, "hotloop=1")
+M.snap_restores_direct_exit = function()
+    jit.opt.start(0, 'hotloop=1')
 
     local sum = 0
     for i = 1, 20 do
@@ -91,11 +84,11 @@ test:ok(testgetmetrics.snap_restores(function()
 
     -- A single snapshot restoration happened on loop finish.
     return 1
-end))
+end
 
 -- Compiled loop with a side exit which does not get compiled.
-test:ok(testgetmetrics.snap_restores(function()
-    jit.opt.start(0, "hotloop=1", "hotexit=2", ("minstitch=%d"):format(MAXNINS))
+M.snap_restores_side_exit_not_compiled = function()
+    jit.opt.start(0, 'hotloop=1', 'hotexit=2', ('minstitch=%d'):format(MAXNINS))
 
     local function foo(i)
         -- math.fmod is not yet compiled!
@@ -112,13 +105,13 @@ test:ok(testgetmetrics.snap_restores(function()
 
     -- Side exits from the root trace could not get compiled.
     return 5
-end))
+end
 
 -- Compiled loop with a side exit which gets compiled.
-test:ok(testgetmetrics.snap_restores(function()
+M.snap_restores_side_exit_compiled = function()
     -- Optimization level is important here as `loop` optimization
     -- may unroll the loop body and insert +1 side exit.
-    jit.opt.start(0, "hotloop=1", "hotexit=5")
+    jit.opt.start(0, 'hotloop=1', 'hotexit=5')
 
     local function foo(i)
         return i <= 10 and i or tostring(i)
@@ -136,13 +129,13 @@ test:ok(testgetmetrics.snap_restores(function()
     -- and compiled
     -- 1 side exit on loop end
     return 6
-end))
+end
 
 -- Compiled scalar trace with a direct exit to the interpreter.
-test:ok(testgetmetrics.snap_restores(function()
+M.snap_restores_direct_exit_scalar = function()
     -- For calls it will be 2 * hotloop (see lj_dispatch.{c,h}
     -- and hotcall@vm_*.dasc).
-    jit.opt.start(3, "hotloop=2", "hotexit=3")
+    jit.opt.start(3, 'hotloop=2', 'hotexit=3')
 
     local function foo(i)
         return i <= 15 and i or tostring(i)
@@ -167,15 +160,15 @@ test:ok(testgetmetrics.snap_restores(function()
     jit.opt.start(unpack(jit_opt_default))
 
     return 2
-end))
+end
 
-test:ok(testgetmetrics.strhash())
-
-test:ok(testgetmetrics.tracenum_base(function()
+M.tracenum_base = function()
     local sum = 0
     for i = 1, 200 do
         sum = sum + i
     end
     -- Compiled only 1 loop as new trace.
     return 1
-end))
+end
+
+return M
diff --git a/test/tarantool-c-tests/misclib-getmetrics-capi.test.c b/test/tarantool-c-tests/misclib-getmetrics-capi.test.c
new file mode 100644
index 00000000..724b551a
--- /dev/null
+++ b/test/tarantool-c-tests/misclib-getmetrics-capi.test.c
@@ -0,0 +1,343 @@
+#include "lua.h"
+#include "luajit.h"
+#include "lauxlib.h"
+#include "lmisclib.h"
+
+#include "test.h"
+#include "utils.h"
+
+/* Need for skipcond for BSD and JIT. */
+#include "lj_arch.h"
+
+static int base(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Metrics metrics;
+	luaM_metrics(L, &metrics);
+
+	/*
+	 * Just check structure format, not values that fields
+	 * contain.
+	 */
+	(void)metrics.strhash_hit;
+	(void)metrics.strhash_miss;
+
+	(void)metrics.gc_strnum;
+	(void)metrics.gc_tabnum;
+	(void)metrics.gc_udatanum;
+	(void)metrics.gc_cdatanum;
+
+	(void)metrics.gc_total;
+	(void)metrics.gc_freed;
+	(void)metrics.gc_allocated;
+
+	(void)metrics.gc_steps_pause;
+	(void)metrics.gc_steps_propagate;
+	(void)metrics.gc_steps_atomic;
+	(void)metrics.gc_steps_sweepstring;
+	(void)metrics.gc_steps_sweep;
+	(void)metrics.gc_steps_finalize;
+
+	(void)metrics.jit_snap_restore;
+	(void)metrics.jit_trace_abort;
+	(void)metrics.jit_mcode_size;
+	(void)metrics.jit_trace_num;
+
+	return TEST_EXIT_SUCCESS;
+}
+
+static int gc_allocated_freed(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Metrics oldm, newm;
+	/* Force up garbage collect all dead objects. */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+
+	luaM_metrics(L, &oldm);
+	/* Simple garbage generation. */
+	if (luaL_dostring(L, "local i = 0 for j = 1, 10 do i = i + j end"))
+		bail_out("failed to translate Lua code snippet");
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	luaM_metrics(L, &newm);
+	assert_true(newm.gc_allocated - oldm.gc_allocated > 0);
+	assert_true(newm.gc_freed - oldm.gc_freed > 0);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+static int gc_steps(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Metrics oldm, newm;
+	/*
+	 * Some garbage has already happened before the next line,
+	 * i.e. during frontend processing Lua test chunk.
+	 * Let's put a full garbage collection cycle on top
+	 * of that, and confirm that non-null values are reported
+	 * (we are not yet interested in actual numbers):
+	 */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+
+	luaM_metrics(L, &oldm);
+	assert_true(oldm.gc_steps_pause > 0);
+	assert_true(oldm.gc_steps_propagate > 0);
+	assert_true(oldm.gc_steps_atomic > 0);
+	assert_true(oldm.gc_steps_sweepstring > 0);
+	assert_true(oldm.gc_steps_sweep > 0);
+	/* Nothing to finalize, skipped. */
+	assert_true(oldm.gc_steps_finalize == 0);
+
+	/*
+	 * As long as we don't create new Lua objects
+	 * consequent call should return the same values:
+	 */
+	luaM_metrics(L, &newm);
+	assert_sizet_equal(newm.gc_steps_pause, oldm.gc_steps_pause);
+	assert_sizet_equal(newm.gc_steps_propagate, oldm.gc_steps_propagate);
+	assert_sizet_equal(newm.gc_steps_atomic, oldm.gc_steps_atomic);
+	assert_sizet_equal(newm.gc_steps_sweepstring,
+			   oldm.gc_steps_sweepstring);
+	assert_sizet_equal(newm.gc_steps_sweep, oldm.gc_steps_sweep);
+	/* Nothing to finalize, skipped. */
+	assert_true(newm.gc_steps_finalize == 0);
+	oldm = newm;
+
+	/*
+	 * Now the last phase: run full GC once and make sure that
+	 * everything is being reported as expected:
+	 */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	luaM_metrics(L, &newm);
+	assert_true(newm.gc_steps_pause - oldm.gc_steps_pause == 1);
+	assert_true(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1);
+	assert_true(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1);
+	assert_true(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1);
+	assert_true(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1);
+	/* Nothing to finalize, skipped. */
+	assert_true(newm.gc_steps_finalize == 0);
+	oldm = newm;
+
+	/*
+	 * Now let's run three GC cycles to ensure that
+	 * increment was not a lucky coincidence.
+	 */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	luaM_metrics(L, &newm);
+	assert_true(newm.gc_steps_pause - oldm.gc_steps_pause == 3);
+	assert_true(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3);
+	assert_true(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3);
+	assert_true(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3);
+	assert_true(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3);
+	/* Nothing to finalize, skipped. */
+	assert_true(newm.gc_steps_finalize == 0);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+static int objcount(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Metrics oldm, newm;
+	if (!LJ_HASJIT)
+		return skip("Test requires JIT enabled");
+
+	utils_get_aux_lfunc(L);
+
+	/* Force up garbage collect all dead objects. */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+
+	luaM_metrics(L, &oldm);
+	/* Generate garbage. Argument is iterations amount. */
+	lua_pushnumber(L, 1000);
+	lua_call(L, 1, 0);
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	luaM_metrics(L, &newm);
+	assert_sizet_equal(newm.gc_strnum, oldm.gc_strnum);
+	assert_sizet_equal(newm.gc_tabnum, oldm.gc_tabnum);
+	assert_sizet_equal(newm.gc_udatanum, oldm.gc_udatanum);
+	assert_sizet_equal(newm.gc_cdatanum, oldm.gc_cdatanum);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+static int objcount_cdata_decrement(void *test_state)
+{
+	lua_State *L = test_state;
+	/*
+	 * cdata decrement test.
+	 * See https://github.com/tarantool/tarantool/issues/5820.
+	 */
+	struct luam_Metrics oldm, newm;
+	utils_get_aux_lfunc(L);
+
+	/* Force up garbage collect all dead objects. */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+
+	luaM_metrics(L, &oldm);
+	/*
+	 * The function generates and collects cdata with
+	 * LJ_GC_CDATA_FIN flag.
+	 */
+	lua_call(L, 0, 0);
+	luaM_metrics(L, &newm);
+	assert_sizet_equal(newm.gc_cdatanum, oldm.gc_cdatanum);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+/*
+ * Get function to call to generate the corresponding snapshot
+ * restores on top of the Lua stack. Function returns the amount
+ * of snapshot restorations expected.
+ * Clear stack after call.
+ */
+static void check_snap_restores(lua_State *L)
+{
+	struct luam_Metrics oldm, newm;
+	luaM_metrics(L, &oldm);
+	/* Generate snapshots. */
+	lua_call(L, 0, 1);
+	int n = lua_gettop(L);
+	/*
+	 * The first value is the table with functions,
+	 * the second is number of snapshot restores.
+	 */
+	if (n != 2 || !lua_isnumber(L, -1))
+		bail_out("incorrect return value: 1 number is required");
+	size_t snap_restores = lua_tonumber(L, -1);
+	luaM_metrics(L, &newm);
+	/*
+	 * Remove `snap_restores` from stack.
+	 * Must be done before potiential assert and exit from
+	 * the test.
+	 */
+	lua_pop(L, 1);
+	assert_true(newm.jit_snap_restore - oldm.jit_snap_restore
+		    == snap_restores);
+}
+
+static int snap_restores_direct_exit(void *test_state)
+{
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	check_snap_restores(L);
+	return TEST_EXIT_SUCCESS;
+}
+
+static int snap_restores_direct_exit_scalar(void *test_state)
+{
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	check_snap_restores(L);
+	return TEST_EXIT_SUCCESS;
+}
+
+static int snap_restores_side_exit_compiled(void *test_state)
+{
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	check_snap_restores(L);
+	return TEST_EXIT_SUCCESS;
+}
+
+static int snap_restores_side_exit_not_compiled(void *test_state)
+{
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	check_snap_restores(L);
+	return TEST_EXIT_SUCCESS;
+}
+
+static int snap_restores_group(void *test_state)
+{
+	if (!LJ_HASJIT)
+		return skip("Test requires JIT enabled");
+	const struct test_unit tgroup[] = {
+		test_unit_new(snap_restores_direct_exit),
+		test_unit_new(snap_restores_direct_exit_scalar),
+		test_unit_new(snap_restores_side_exit_compiled),
+		test_unit_new(snap_restores_side_exit_not_compiled)
+	};
+	return test_run_group(tgroup, test_state);
+}
+
+static int strhash(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Metrics oldm, newm;
+	lua_pushstring(L, "strhash_hit");
+	luaM_metrics(L, &oldm);
+	lua_pushstring(L, "strhash_hit");
+	lua_pushstring(L, "new_str");
+	luaM_metrics(L, &newm);
+	/* Remove pushed strings. */
+	lua_pop(L, 3);
+	assert_true(newm.strhash_hit - oldm.strhash_hit == 1);
+	assert_true(newm.strhash_miss - oldm.strhash_miss == 1);
+	return TEST_EXIT_SUCCESS;
+}
+
+static int tracenum_base(void *test_state)
+{
+	lua_State *L = test_state;
+	if (!LJ_HASJIT)
+		return skip("Test requires JIT enabled");
+	struct luam_Metrics metrics;
+	utils_get_aux_lfunc(L);
+
+	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
+	/* Force up garbage collect all dead objects. */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+
+	luaM_metrics(L, &metrics);
+	assert_true(metrics.jit_trace_num == 0);
+
+	/* Generate traces. */
+	lua_call(L, 0, 1);
+	int n = lua_gettop(L);
+	/*
+	 * The first value is the table with functions,
+	 * the second is the amount of traces.
+	 */
+	if (n != 2 || !lua_isnumber(L, -1))
+		bail_out("incorrect return value: 1 number is required");
+	size_t jit_trace_num = lua_tonumber(L, -1);
+	luaM_metrics(L, &metrics);
+	/* Remove `jit_trace_num` from Lua stack. */
+	lua_pop(L, 1);
+
+	assert_sizet_equal(metrics.jit_trace_num, jit_trace_num);
+
+	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
+	/* Force up garbage collect all dead objects. */
+	lua_gc(L, LUA_GCCOLLECT, 0);
+	luaM_metrics(L, &metrics);
+	assert_true(metrics.jit_trace_num == 0);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+int main(void)
+{
+	if (LUAJIT_OS == LUAJIT_OS_BSD)
+		return skip_all("Disabled on *BSD due to #4819");
+
+	lua_State *L = utils_lua_init();
+
+	utils_load_aux_script(L, "misclib-getmetrics-capi-script.lua");
+	const struct test_unit tgroup[] = {
+		test_unit_new(base),
+		test_unit_new(gc_allocated_freed),
+		test_unit_new(gc_steps),
+		test_unit_new(objcount),
+		test_unit_new(objcount_cdata_decrement),
+		test_unit_new(snap_restores_group),
+		test_unit_new(strhash),
+		test_unit_new(tracenum_base)
+	};
+	const int test_result = test_run_group(tgroup, L);
+	utils_lua_close(L);
+	return test_result;
+}
diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
index 38d6ae49..b4ce39d3 100644
--- a/test/tarantool-tests/CMakeLists.txt
+++ b/test/tarantool-tests/CMakeLists.txt
@@ -66,7 +66,6 @@ add_subdirectory(lj-416-xor-before-jcc)
 add_subdirectory(lj-601-fix-gc-finderrfunc)
 add_subdirectory(lj-727-lightuserdata-itern)
 add_subdirectory(lj-flush-on-trace)
-add_subdirectory(misclib-getmetrics-capi)
 add_subdirectory(misclib-sysprof-capi)
 
 # The part of the memory profiler toolchain is located in tools
diff --git a/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt b/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
deleted file mode 100644
index 60eb5bbb..00000000
--- a/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-BuildTestCLib(testgetmetrics testgetmetrics.c)
diff --git a/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c b/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
deleted file mode 100644
index 67776338..00000000
--- a/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
+++ /dev/null
@@ -1,270 +0,0 @@
-#include <lua.h>
-#include <luajit.h>
-#include <lauxlib.h>
-
-#include <lmisclib.h>
-
-#undef NDEBUG
-#include <assert.h>
-
-static int base(lua_State *L)
-{
-	struct luam_Metrics metrics;
-	luaM_metrics(L, &metrics);
-
-	/* Just check structure format, not values that fields contain. */
-	(void)metrics.strhash_hit;
-	(void)metrics.strhash_miss;
-
-	(void)metrics.gc_strnum;
-	(void)metrics.gc_tabnum;
-	(void)metrics.gc_udatanum;
-	(void)metrics.gc_cdatanum;
-
-	(void)metrics.gc_total;
-	(void)metrics.gc_freed;
-	(void)metrics.gc_allocated;
-
-	(void)metrics.gc_steps_pause;
-	(void)metrics.gc_steps_propagate;
-	(void)metrics.gc_steps_atomic;
-	(void)metrics.gc_steps_sweepstring;
-	(void)metrics.gc_steps_sweep;
-	(void)metrics.gc_steps_finalize;
-
-	(void)metrics.jit_snap_restore;
-	(void)metrics.jit_trace_abort;
-	(void)metrics.jit_mcode_size;
-	(void)metrics.jit_trace_num;
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int gc_allocated_freed(lua_State *L)
-{
-	struct luam_Metrics oldm, newm;
-	/* Force up garbage collect all dead objects. */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-
-	luaM_metrics(L, &oldm);
-	/* Simple garbage generation. */
-	if (luaL_dostring(L, "local i = 0 for j = 1, 10 do i = i + j end"))
-		luaL_error(L, "failed to translate Lua code snippet");
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	luaM_metrics(L, &newm);
-	assert(newm.gc_allocated - oldm.gc_allocated > 0);
-	assert(newm.gc_freed - oldm.gc_freed > 0);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int gc_steps(lua_State *L)
-{
-	struct luam_Metrics oldm, newm;
-	/*
-	 * Some garbage has already happened before the next line,
-	 * i.e. during frontend processing Lua test chunk.
-	 * Let's put a full garbage collection cycle on top
-	 * of that, and confirm that non-null values are reported
-	 * (we are not yet interested in actual numbers):
-	 */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-
-	luaM_metrics(L, &oldm);
-	assert(oldm.gc_steps_pause > 0);
-	assert(oldm.gc_steps_propagate > 0);
-	assert(oldm.gc_steps_atomic > 0);
-	assert(oldm.gc_steps_sweepstring > 0);
-	assert(oldm.gc_steps_sweep > 0);
-	/* Nothing to finalize, skipped. */
-	assert(oldm.gc_steps_finalize == 0);
-
-	/*
-	 * As long as we don't create new Lua objects
-	 * consequent call should return the same values:
-	 */
-	luaM_metrics(L, &newm);
-	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 0);
-	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate == 0);
-	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 0);
-	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring == 0);
-	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep == 0);
-	/* Nothing to finalize, skipped. */
-	assert(newm.gc_steps_finalize == 0);
-	oldm = newm;
-
-	/*
-	 * Now the last phase: run full GC once and make sure that
-	 * everything is being reported as expected:
-	 */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	luaM_metrics(L, &newm);
-	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 1);
-	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1);
-	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1);
-	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1);
-	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1);
-	/* Nothing to finalize, skipped. */
-	assert(newm.gc_steps_finalize == 0);
-	oldm = newm;
-
-	/*
-	 * Now let's run three GC cycles to ensure that
-	 * increment was not a lucky coincidence.
-	 */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	luaM_metrics(L, &newm);
-	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 3);
-	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3);
-	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3);
-	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3);
-	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3);
-	/* Nothing to finalize, skipped. */
-	assert(newm.gc_steps_finalize == 0);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int objcount(lua_State *L)
-{
-	struct luam_Metrics oldm, newm;
-	int n = lua_gettop(L);
-	if (n != 1 || !lua_isfunction(L, 1))
-		luaL_error(L, "incorrect argument: 1 function is required");
-
-	/* Force up garbage collect all dead objects. */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-
-	luaM_metrics(L, &oldm);
-	/* Generate garbage. Argument is iterations amount. */
-	lua_pushnumber(L, 1000);
-	lua_call(L, 1, 0);
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	luaM_metrics(L, &newm);
-	assert(newm.gc_strnum - oldm.gc_strnum == 0);
-	assert(newm.gc_tabnum - oldm.gc_tabnum == 0);
-	assert(newm.gc_udatanum - oldm.gc_udatanum == 0);
-	assert(newm.gc_cdatanum - oldm.gc_cdatanum == 0);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int objcount_cdata_decrement(lua_State *L)
-{
-	/*
-	 * cdata decrement test.
-	 * See https://github.com/tarantool/tarantool/issues/5820.
-	 */
-	struct luam_Metrics oldm, newm;
-	int n = lua_gettop(L);
-	if (n != 1 || !lua_isfunction(L, 1))
-		luaL_error(L, "incorrect argument: 1 function is required");
-
-	/* Force up garbage collect all dead objects. */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-
-	luaM_metrics(L, &oldm);
-	/*
-	 * The function generates and collects cdata with
-	 * LJ_GC_CDATA_FIN flag.
-	 */
-	lua_call(L, 0, 0);
-	luaM_metrics(L, &newm);
-	assert(newm.gc_cdatanum - oldm.gc_cdatanum == 0);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int snap_restores(lua_State *L)
-{
-	struct luam_Metrics oldm, newm;
-	int n = lua_gettop(L);
-	if (n != 1 || !lua_isfunction(L, 1))
-		luaL_error(L, "incorrect arguments: 1 function is required");
-
-	luaM_metrics(L, &oldm);
-	/* Generate snapshots. */
-	lua_call(L, 0, 1);
-	n = lua_gettop(L);
-	if (n != 1 || !lua_isnumber(L, 1))
-		luaL_error(L, "incorrect return value: 1 number is required");
-	size_t snap_restores = lua_tonumber(L, 1);
-	luaM_metrics(L, &newm);
-	assert(newm.jit_snap_restore - oldm.jit_snap_restore == snap_restores);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int strhash(lua_State *L)
-{
-	struct luam_Metrics oldm, newm;
-	lua_pushstring(L, "strhash_hit");
-	luaM_metrics(L, &oldm);
-	lua_pushstring(L, "strhash_hit");
-	lua_pushstring(L, "new_str");
-	luaM_metrics(L, &newm);
-	assert(newm.strhash_hit - oldm.strhash_hit == 1);
-	assert(newm.strhash_miss - oldm.strhash_miss == 1);
-	lua_pop(L, 3);
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int tracenum_base(lua_State *L)
-{
-	struct luam_Metrics metrics;
-	int n = lua_gettop(L);
-	if (n != 1 || !lua_isfunction(L, 1))
-		luaL_error(L, "incorrect arguments: 1 function is required");
-
-	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
-	/* Force up garbage collect all dead objects. */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-
-	luaM_metrics(L, &metrics);
-	assert(metrics.jit_trace_num == 0);
-
-	/* Generate traces. */
-	lua_call(L, 0, 1);
-	n = lua_gettop(L);
-	if (n != 1 || !lua_isnumber(L, 1))
-		luaL_error(L, "incorrect return value: 1 number is required");
-	size_t jit_trace_num = lua_tonumber(L, 1);
-	luaM_metrics(L, &metrics);
-	assert(metrics.jit_trace_num == jit_trace_num);
-
-	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
-	/* Force up garbage collect all dead objects. */
-	lua_gc(L, LUA_GCCOLLECT, 0);
-	luaM_metrics(L, &metrics);
-	assert(metrics.jit_trace_num == 0);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static const struct luaL_Reg testgetmetrics[] = {
-	{"base", base},
-	{"gc_allocated_freed", gc_allocated_freed},
-	{"gc_steps", gc_steps},
-	{"objcount", objcount},
-	{"objcount_cdata_decrement", objcount_cdata_decrement},
-	{"snap_restores", snap_restores},
-	{"strhash", strhash},
-	{"tracenum_base", tracenum_base},
-	{NULL, NULL}
-};
-
-LUA_API int luaopen_testgetmetrics(lua_State *L)
-{
-	luaL_register(L, "testgetmetrics", testgetmetrics);
-	return 1;
-}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi test in C
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
                   ` (3 preceding siblings ...)
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-07 16:18   ` Sergey Bronnikov via Tarantool-patches
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata " Sergey Kaplun via Tarantool-patches
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

This patch rewrites the aforementioned test with the usage libtest
recently introduced. The approach is similar to the previous patch.
`c_payload()` function to profile is now set up globally on script
loading. The payload for enabled and disabled JIT is the same. As far as
for sysprof testing is necessary to replace backtrace with libunwind
first, the payload tests are marked with `todo()`.

Nevertheless, glibc `assert()` still necessary to check the correctness
of the profile writer, so it is still used there.

Relates to tarantool/tarantool#781
Part of tarantool/tarantool#7900
---
 .../misclib-sysprof-capi-script.lua           |  35 ++
 .../misclib-sysprof-capi.test.c               | 325 ++++++++++++++++++
 test/tarantool-tests/CMakeLists.txt           |   1 -
 .../misclib-sysprof-capi.test.lua             |  54 ---
 .../misclib-sysprof-capi/CMakeLists.txt       |   1 -
 .../misclib-sysprof-capi/testsysprof.c        | 260 --------------
 6 files changed, 360 insertions(+), 316 deletions(-)
 create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi-script.lua
 create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi.test.c
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi.test.lua
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
 delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/testsysprof.c

diff --git a/test/tarantool-c-tests/misclib-sysprof-capi-script.lua b/test/tarantool-c-tests/misclib-sysprof-capi-script.lua
new file mode 100644
index 00000000..dd8387db
--- /dev/null
+++ b/test/tarantool-c-tests/misclib-sysprof-capi-script.lua
@@ -0,0 +1,35 @@
+local M = {}
+
+-- luacheck: no global
+assert(c_payload, 'c_payload global function should be set via script loader')
+
+local function lua_payload(n)
+  if n <= 1 then
+    return n
+  end
+  return lua_payload(n - 1) + lua_payload(n - 2)
+end
+
+local function payload()
+  local n_iterations = 500000
+
+  local co = coroutine.create(function()
+    for i = 1, n_iterations do
+      if i % 2 == 0 then
+        c_payload(10)
+      else
+        lua_payload(10)
+      end
+      coroutine.yield()
+    end
+  end)
+
+  for _ = 1, n_iterations do
+    coroutine.resume(co)
+  end
+end
+
+M.profile_func_jiton = payload
+M.profile_func_jitoff = payload
+
+return M
diff --git a/test/tarantool-c-tests/misclib-sysprof-capi.test.c b/test/tarantool-c-tests/misclib-sysprof-capi.test.c
new file mode 100644
index 00000000..4a7a0d46
--- /dev/null
+++ b/test/tarantool-c-tests/misclib-sysprof-capi.test.c
@@ -0,0 +1,325 @@
+#include "lauxlib.h"
+#include "lmisclib.h"
+#include "lua.h"
+#include "luajit.h"
+
+#include "test.h"
+#include "utils.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+/* XXX: Still need normal assert inside writer functions. */
+#undef NDEBUG
+#include <assert.h>
+
+/* Need for skipcond for OS and ARCH. */
+#include "lj_arch.h"
+
+#define UNUSED(x) ((void)(x))
+
+/* --- utils -------------------------------------------------- */
+
+#define SYSPROF_INTERVAL_DEFAULT 100
+
+/*
+ * Yep, 8Mb. Tuned in order not to bother the platform with too
+ * often flushes.
+ */
+#define STREAM_BUFFER_SIZE (8 * 1024 * 1024)
+
+
+/* --- C Payload ---------------------------------------------- */
+
+static double fib(double n)
+{
+	if (n <= 1)
+		return n;
+	return fib(n - 1) + fib(n - 2);
+}
+
+static int c_payload(lua_State *L)
+{
+	fib(luaL_checknumber(L, 1));
+	lua_pushboolean(L, 1);
+	return 1;
+}
+/* --- sysprof C API tests ------------------------------------ */
+
+static int base(void *test_state)
+{
+	UNUSED(test_state);
+	struct luam_Sysprof_Options opt = {};
+	struct luam_Sysprof_Counters cnt = {};
+
+	(void)opt.interval;
+	(void)opt.mode;
+	(void)opt.ctx;
+	(void)opt.buf;
+	(void)opt.len;
+
+	luaM_sysprof_report(&cnt);
+
+	(void)cnt.samples;
+	(void)cnt.vmst_interp;
+	(void)cnt.vmst_lfunc;
+	(void)cnt.vmst_ffunc;
+	(void)cnt.vmst_cfunc;
+	(void)cnt.vmst_gc;
+	(void)cnt.vmst_exit;
+	(void)cnt.vmst_record;
+	(void)cnt.vmst_opt;
+	(void)cnt.vmst_asm;
+	(void)cnt.vmst_trace;
+
+	return TEST_EXIT_SUCCESS;
+}
+
+static int validation(void *test_state)
+{
+	lua_State *L = test_state;
+	struct luam_Sysprof_Options opt = {};
+	int status = PROFILE_SUCCESS;
+
+	/* Unknown mode. */
+	opt.mode = 0x40;
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_ERRUSE);
+
+	/* Buffer not configured. */
+	opt.mode = LUAM_SYSPROF_CALLGRAPH;
+	opt.buf = NULL;
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_ERRUSE);
+
+	/* Bad interval. */
+	opt.mode = LUAM_SYSPROF_DEFAULT;
+	opt.interval = 0;
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_ERRUSE);
+
+	/* Check if profiling started. */
+	opt.mode = LUAM_SYSPROF_DEFAULT;
+	opt.interval = SYSPROF_INTERVAL_DEFAULT;
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_SUCCESS);
+
+	/* Already running. */
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_ERRRUN);
+
+	/* Profiler stopping. */
+	status = luaM_sysprof_stop(L);
+	assert_true(status == PROFILE_SUCCESS);
+
+	/* Stopping profiler which is not running. */
+	status = luaM_sysprof_stop(L);
+	assert_true(status == PROFILE_ERRRUN);
+
+	return TEST_EXIT_SUCCESS;
+}
+
+/*
+ * FIXME: The following two tests are disabled because sometimes
+ * `backtrace` dynamically loads a platform-specific unwinder,
+ * which is not signal-safe.
+ */
+
+#if 0
+/*
+ * Structure given as ctx to sysprof writer and on_stop callback.
+ */
+struct sysprof_ctx {
+	/* Output file descriptor for data. */
+	int fd;
+	/* Buffer for data. */
+	uint8_t buf[STREAM_BUFFER_SIZE];
+};
+
+/*
+ * Default buffer writer function.
+ * Just call fwrite to the corresponding FILE.
+ */
+static size_t buffer_writer_default(const void **buf_addr, size_t len,
+				    void *opt)
+{
+	struct sysprof_ctx *ctx = opt;
+	int fd = ctx->fd;
+	const void * const buf_start = *buf_addr;
+	const void *data = *buf_addr;
+	size_t write_total = 0;
+
+	assert(len <= STREAM_BUFFER_SIZE);
+
+	for (;;) {
+		const size_t written = write(fd, data, len - write_total);
+
+		if (written == 0) {
+			/* Re-tries write in case of EINTR. */
+			if (errno != EINTR) {
+				/*
+				 * Will be freed as whole chunk
+				 * later.
+				 */
+				*buf_addr = NULL;
+				return write_total;
+			}
+			errno = 0;
+			continue;
+		}
+
+		write_total += written;
+		assert(write_total <= len);
+
+		if (write_total == len)
+			break;
+
+		data = (uint8_t *)data + (ptrdiff_t)written;
+	}
+
+	*buf_addr = buf_start;
+	return write_total;
+}
+
+/*
+ * Default on stop callback. Just close the corresponding stream.
+ */
+static int on_stop_cb_default(void *opt, uint8_t *buf)
+{
+	UNUSED(buf);
+	struct sysprof_ctx *ctx = opt;
+	int fd = ctx->fd;
+	free(ctx);
+	return close(fd);
+}
+
+static int stream_init(struct luam_Sysprof_Options *opt)
+{
+	struct sysprof_ctx *ctx = calloc(1, sizeof(struct sysprof_ctx));
+	if (NULL == ctx)
+		return PROFILE_ERRIO;
+
+	ctx->fd = open("/dev/null", O_WRONLY | O_CREAT, 0644);
+	if (-1 == ctx->fd) {
+		free(ctx);
+		return PROFILE_ERRIO;
+	}
+
+	opt->ctx = ctx;
+	opt->buf = ctx->buf;
+	opt->len = STREAM_BUFFER_SIZE;
+
+	return PROFILE_SUCCESS;
+}
+
+/* Get function to profile on top, call it. */
+static int check_profile_func(lua_State *L)
+{
+	struct luam_Sysprof_Options opt = {};
+	struct luam_Sysprof_Counters cnt = {};
+	int status = PROFILE_ERRUSE;
+	/*
+	 * Since all the other modes functionality is the
+	 * subset of CALLGRAPH mode, run this mode to test
+	 * the profiler's behavior.
+	 */
+	opt.mode = LUAM_SYSPROF_CALLGRAPH;
+	opt.interval = SYSPROF_INTERVAL_DEFAULT;
+	stream_init(&opt);
+
+	/*
+	 * XXX: Payload function on top will not be removed if any
+	 * of those assertions fail. So, the next call to the
+	 * `utils_get_aux_lfunc()` will fail. It's OK, since
+	 * we are already in trouble, just keep it in mind.
+	 */
+	assert_true(luaM_sysprof_set_writer(buffer_writer_default)
+		    == PROFILE_SUCCESS);
+	assert_true(luaM_sysprof_set_on_stop(on_stop_cb_default)
+		    == PROFILE_SUCCESS);
+	assert_true(luaM_sysprof_set_backtracer(NULL) == PROFILE_SUCCESS);
+
+	status = luaM_sysprof_start(L, &opt);
+	assert_true(status == PROFILE_SUCCESS);
+
+	/* Run payload. */
+	if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
+		test_comment("error running payload: %s", lua_tostring(L, -1));
+		bail_out("error running sysprof test payload");
+	}
+
+	status = luaM_sysprof_stop(L);
+	assert_true(status == PROFILE_SUCCESS);
+
+	status = luaM_sysprof_report(&cnt);
+	assert_true(status == PROFILE_SUCCESS);
+
+	assert_true(cnt.samples > 1);
+	assert_true(cnt.samples == cnt.vmst_asm +
+			cnt.vmst_cfunc +
+			cnt.vmst_exit +
+			cnt.vmst_ffunc +
+			cnt.vmst_gc +
+			cnt.vmst_interp +
+			cnt.vmst_lfunc +
+			cnt.vmst_opt +
+			cnt.vmst_record +
+			cnt.vmst_trace);
+
+	return TEST_EXIT_SUCCESS;
+}
+#endif
+
+static int profile_func_jitoff(void *test_state)
+{
+	UNUSED(test_state);
+	return todo("Need to replace backtrace with libunwind first");
+#if 0
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF);
+	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_FLUSH);
+	check_profile_func(L);
+	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON);
+	return TEST_EXIT_SUCCESS;
+#endif
+}
+
+static int profile_func_jiton(void *test_state)
+{
+	UNUSED(test_state);
+	return todo("Need to replace backtrace with libunwind first");
+#if 0
+	lua_State *L = test_state;
+	utils_get_aux_lfunc(L);
+	check_profile_func(L);
+	return TEST_EXIT_SUCCESS;
+#endif
+}
+
+int main(void)
+{
+	if (LUAJIT_OS != LUAJIT_OS_LINUX)
+		return skip_all("Sysprof is implemented for Linux only");
+	if (LUAJIT_TARGET != LUAJIT_ARCH_X86
+	    && LUAJIT_TARGET != LUAJIT_ARCH_X64)
+		return skip_all("Sysprof is implemented for x86_64 only");
+
+	lua_State *L = utils_lua_init();
+
+	lua_pushcfunction(L, c_payload);
+	lua_setfield(L, LUA_GLOBALSINDEX, "c_payload");
+	utils_load_aux_script(L, "misclib-sysprof-capi-script.lua");
+
+	const struct test_unit tgroup[] = {
+		test_unit_new(base),
+		test_unit_new(validation),
+		test_unit_new(profile_func_jitoff),
+		test_unit_new(profile_func_jiton)
+	};
+	const int test_result = test_run_group(tgroup, L);
+	utils_lua_close(L);
+	return test_result;
+}
diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
index b4ce39d3..b1c7207f 100644
--- a/test/tarantool-tests/CMakeLists.txt
+++ b/test/tarantool-tests/CMakeLists.txt
@@ -66,7 +66,6 @@ add_subdirectory(lj-416-xor-before-jcc)
 add_subdirectory(lj-601-fix-gc-finderrfunc)
 add_subdirectory(lj-727-lightuserdata-itern)
 add_subdirectory(lj-flush-on-trace)
-add_subdirectory(misclib-sysprof-capi)
 
 # The part of the memory profiler toolchain is located in tools
 # directory, jit, profiler, and bytecode toolchains are located
diff --git a/test/tarantool-tests/misclib-sysprof-capi.test.lua b/test/tarantool-tests/misclib-sysprof-capi.test.lua
deleted file mode 100644
index 4395bce3..00000000
--- a/test/tarantool-tests/misclib-sysprof-capi.test.lua
+++ /dev/null
@@ -1,54 +0,0 @@
-local tap = require("tap")
-local test = tap.test("clib-misc-sysprof"):skipcond({
-  ["Sysprof is implemented for x86_64 only"] = jit.arch ~= "x86" and
-                                               jit.arch ~= "x64",
-  ["Sysprof is implemented for Linux only"] = jit.os ~= "Linux",
-})
-
-test:plan(2)
-
-local testsysprof = require("testsysprof")
-
-jit.off()
-
-test:ok(testsysprof.base())
-test:ok(testsysprof.validation())
-
--- FIXME: The following two tests are disabled because sometimes
--- `backtrace` dynamically loads a platform-specific unwinder, which is
--- not signal-safe.
---[[
-local function lua_payload(n)
-  if n <= 1 then
-    return n
-  end
-  return lua_payload(n - 1) + lua_payload(n - 2)
-end
-
-local function payload()
-  local n_iterations = 500000
-
-  local co = coroutine.create(function ()
-    for i = 1, n_iterations do
-      if i % 2 == 0 then
-        testsysprof.c_payload(10)
-      else
-        lua_payload(10)
-      end
-      coroutine.yield()
-    end
-  end)
-
-  for _ = 1, n_iterations do
-    coroutine.resume(co)
-  end
-end
-
-test:ok(testsysprof.profile_func(payload))
-
-jit.on()
-jit.flush()
-
-test:ok(testsysprof.profile_func(payload))
---]]
-os.exit(test:check() and 0 or 1)
diff --git a/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt b/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
deleted file mode 100644
index d9fb1a1a..00000000
--- a/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-BuildTestCLib(testsysprof testsysprof.c)
diff --git a/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c b/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c
deleted file mode 100644
index d7a3e355..00000000
--- a/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c
+++ /dev/null
@@ -1,260 +0,0 @@
-#include <lua.h>
-#include <luajit.h>
-#include <lauxlib.h>
-
-#include <lmisclib.h>
-
-#include <fcntl.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef NDEBUG
-#include <assert.h>
-
-#define UNUSED(x) ((void)(x))
-
-/* --- utils -------------------------------------------------------------- */
-
-#define SYSPROF_INTERVAL_DEFAULT 100
-
-/*
- ** Yep, 8Mb. Tuned in order not to bother the platform with too often flushes.
- */
-#define STREAM_BUFFER_SIZE (8 * 1024 * 1024)
-
-/* Structure given as ctx to sysprof writer and on_stop callback. */
-struct sysprof_ctx {
-	/* Output file descriptor for data. */
-	int fd;
-	/* Buffer for data. */
-	uint8_t buf[STREAM_BUFFER_SIZE];
-};
-
-/*
- ** Default buffer writer function.
- ** Just call fwrite to the corresponding FILE.
- */
-static size_t buffer_writer_default(const void **buf_addr, size_t len,
-		void *opt)
-{
-	struct sysprof_ctx *ctx = opt;
-	int fd = ctx->fd;
-	const void * const buf_start = *buf_addr;
-	const void *data = *buf_addr;
-	size_t write_total = 0;
-
-	assert(len <= STREAM_BUFFER_SIZE);
-
-	for (;;) {
-		const size_t written = write(fd, data, len - write_total);
-
-		if (written == 0) {
-			/* Re-tries write in case of EINTR. */
-			if (errno != EINTR) {
-				/* Will be freed as whole chunk later. */
-				*buf_addr = NULL;
-				return write_total;
-			}
-			errno = 0;
-			continue;
-		}
-
-		write_total += written;
-		assert(write_total <= len);
-
-		if (write_total == len)
-			break;
-
-		data = (uint8_t *)data + (ptrdiff_t)written;
-	}
-
-	*buf_addr = buf_start;
-	return write_total;
-}
-
-/* Default on stop callback. Just close the corresponding stream. */
-static int on_stop_cb_default(void *opt, uint8_t *buf)
-{
-	UNUSED(buf);
-	struct sysprof_ctx *ctx = opt;
-	int fd = ctx->fd;
-	free(ctx);
-	return close(fd);
-}
-
-static int stream_init(struct luam_Sysprof_Options *opt)
-{
-	struct sysprof_ctx *ctx = calloc(1, sizeof(struct sysprof_ctx));
-	if (NULL == ctx)
-		return PROFILE_ERRIO;
-
-	ctx->fd = open("/dev/null", O_WRONLY | O_CREAT, 0644);
-	if (-1 == ctx->fd) {
-		free(ctx);
-		return PROFILE_ERRIO;
-	}
-
-	opt->ctx = ctx;
-	opt->buf = ctx->buf;
-	opt->len = STREAM_BUFFER_SIZE;
-
-	return PROFILE_SUCCESS;
-}
-
-/* --- Payload ------------------------------------------------------------ */
-
-static double fib(double n)
-{
-	if (n <= 1)
-		return n;
-	return fib(n - 1) + fib(n - 2);
-}
-
-static int c_payload(lua_State *L)
-{
-	fib(luaL_checknumber(L, 1));
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-/* --- sysprof C API tests ------------------------------------------------ */
-
-static int base(lua_State *L)
-{
-	struct luam_Sysprof_Options opt = {};
-	struct luam_Sysprof_Counters cnt = {};
-
-	(void)opt.interval;
-	(void)opt.mode;
-	(void)opt.ctx;
-	(void)opt.buf;
-	(void)opt.len;
-
-	luaM_sysprof_report(&cnt);
-
-	(void)cnt.samples;
-	(void)cnt.vmst_interp;
-	(void)cnt.vmst_lfunc;
-	(void)cnt.vmst_ffunc;
-	(void)cnt.vmst_cfunc;
-	(void)cnt.vmst_gc;
-	(void)cnt.vmst_exit;
-	(void)cnt.vmst_record;
-	(void)cnt.vmst_opt;
-	(void)cnt.vmst_asm;
-	(void)cnt.vmst_trace;
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int validation(lua_State *L)
-{
-	struct luam_Sysprof_Options opt = {};
-	int status = PROFILE_SUCCESS;
-
-	/* Unknown mode. */
-	opt.mode = 0x40;
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_ERRUSE == status);
-
-	/* Buffer not configured. */
-	opt.mode = LUAM_SYSPROF_CALLGRAPH;
-	opt.buf = NULL;
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_ERRUSE == status);
-
-	/* Bad interval. */
-	opt.mode = LUAM_SYSPROF_DEFAULT;
-	opt.interval = 0;
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_ERRUSE == status);
-
-	/* Check if profiling started. */
-	opt.mode = LUAM_SYSPROF_DEFAULT;
-	opt.interval = SYSPROF_INTERVAL_DEFAULT;
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_SUCCESS == status);
-
-	/* Already running. */
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_ERRRUN == status);
-
-	/* Profiler stopping. */
-	status = luaM_sysprof_stop(L);
-	assert(PROFILE_SUCCESS == status);
-
-	/* Stopping profiler which is not running. */
-	status = luaM_sysprof_stop(L);
-	assert(PROFILE_ERRRUN == status);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static int profile_func(lua_State *L)
-{
-	struct luam_Sysprof_Options opt = {};
-	struct luam_Sysprof_Counters cnt = {};
-	int status = PROFILE_ERRUSE;
-
-	int n = lua_gettop(L);
-	if (n != 1 || !lua_isfunction(L, 1))
-		luaL_error(L, "incorrect argument: 1 function is required");
-
-	/*
-	 ** Since all the other modes functionality is the
-	 ** subset of CALLGRAPH mode, run this mode to test
-	 ** the profiler's behavior.
-	 */
-	opt.mode = LUAM_SYSPROF_CALLGRAPH;
-	opt.interval = SYSPROF_INTERVAL_DEFAULT;
-	stream_init(&opt);
-
-	assert(luaM_sysprof_set_writer(buffer_writer_default) == PROFILE_SUCCESS);
-	assert(luaM_sysprof_set_on_stop(on_stop_cb_default) == PROFILE_SUCCESS);
-	assert(luaM_sysprof_set_backtracer(NULL) == PROFILE_SUCCESS);
-
-	status = luaM_sysprof_start(L, &opt);
-	assert(PROFILE_SUCCESS == status);
-
-	/* Run payload. */
-	if (lua_pcall(L, 0, 0, 0) != LUA_OK)
-		luaL_error(L, "error running payload: %s", lua_tostring(L, -1));
-
-	status = luaM_sysprof_stop(L);
-	assert(PROFILE_SUCCESS == status);
-
-	status = luaM_sysprof_report(&cnt);
-	assert(PROFILE_SUCCESS == status);
-
-	assert(cnt.samples > 1);
-	assert(cnt.samples == cnt.vmst_asm +
-			cnt.vmst_cfunc +
-			cnt.vmst_exit +
-			cnt.vmst_ffunc +
-			cnt.vmst_gc +
-			cnt.vmst_interp +
-			cnt.vmst_lfunc +
-			cnt.vmst_opt +
-			cnt.vmst_record +
-			cnt.vmst_trace);
-
-	lua_pushboolean(L, 1);
-	return 1;
-}
-
-static const struct luaL_Reg testsysprof[] = {
-	{"base", base},
-	{"c_payload", c_payload},
-	{"profile_func", profile_func},
-	{"validation", validation},
-	{NULL, NULL}
-};
-
-LUA_API int luaopen_testsysprof(lua_State *L)
-{
-	luaL_register(L, "testsysprof", testsysprof);
-	return 1;
-}
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata test in C
  2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
                   ` (4 preceding siblings ...)
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi " Sergey Kaplun via Tarantool-patches
@ 2023-06-05 10:41 ` Sergey Kaplun via Tarantool-patches
  2023-06-07 16:14   ` Sergey Bronnikov via Tarantool-patches
  5 siblings, 1 reply; 14+ messages in thread
From: Sergey Kaplun via Tarantool-patches @ 2023-06-05 10:41 UTC (permalink / raw)
  To: Igor Munkin, Maxim Kokryashkin, Sergey Bronnikov; +Cc: tarantool-patches

This patch rewrites the aforementioned test with the usage libtest
recently introduced. The approach is similar to the previous patch.

Nevertheless, glibc `assert()` is used to check the correctness
of the `mmap()` usage.

Part of tarantool/tarantool#7900
---
 .../lj-49-bad-lightuserdata.test.c}           | 47 ++++++++++---------
 test/tarantool-tests/CMakeLists.txt           |  1 -
 .../lj-49-bad-lightuserdata.test.lua          | 11 -----
 .../lj-49-bad-lightuserdata/CMakeLists.txt    |  1 -
 4 files changed, 25 insertions(+), 35 deletions(-)
 rename test/{tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c => tarantool-c-tests/lj-49-bad-lightuserdata.test.c} (55%)
 delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
 delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt

diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c b/test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
similarity index 55%
rename from test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c
rename to test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
index 1b909fc6..a9cc4763 100644
--- a/test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c
+++ b/test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
@@ -1,16 +1,21 @@
-#include <lua.h>
-#include <lauxlib.h>
+#include "lua.h"
+#include "lauxlib.h"
 
 #include <sys/mman.h>
 #include <unistd.h>
 
-#undef NDEBUG
-#include <assert.h>
+#include "test.h"
+#include "utils.h"
 
 #define START ((void *)-1)
 
-static int crafted_ptr(lua_State *L)
+/* XXX: Still need normal assert to validate mmap correctness. */
+#undef NDEBUG
+#include <assert.h>
+
+static int crafted_ptr(void *test_state)
 {
+	lua_State *L = test_state;
 	/*
 	 * We know that for arm64 at least 48 bits are available.
 	 * So emulate manually push of lightuseradata within
@@ -18,15 +23,15 @@ static int crafted_ptr(lua_State *L)
 	 */
 	void *longptr = (void *)(1llu << 48);
 	lua_pushlightuserdata(L, longptr);
-	assert(longptr == lua_topointer(L, -1));
+	assert_ptr_equal(longptr, lua_topointer(L, -1));
 	/* Clear our stack. */
 	lua_pop(L, 0);
-	lua_pushboolean(L, 1);
-	return 1;
+	return TEST_EXIT_SUCCESS;
 }
 
-static int mmaped_ptr(lua_State *L)
+static int mmaped_ptr(void *test_state)
 {
+	lua_State *L = test_state;
 	/*
 	 * If start mapping address is not NULL, then the kernel
 	 * takes it as a hint about where to place the mapping, so
@@ -38,24 +43,22 @@ static int mmaped_ptr(lua_State *L)
 			    -1, 0);
 	if (mmaped != MAP_FAILED) {
 		lua_pushlightuserdata(L, mmaped);
-		assert(mmaped == lua_topointer(L, -1));
+		assert_ptr_equal(mmaped, lua_topointer(L, -1));
 		assert(munmap(mmaped, pagesize) == 0);
 	}
 	/* Clear our stack. */
 	lua_pop(L, 0);
-	lua_pushboolean(L, 1);
-	return 1;
+	return TEST_EXIT_SUCCESS;
 }
 
-static const struct luaL_Reg testlightuserdata[] = {
-	{"crafted_ptr", crafted_ptr},
-	{"mmaped_ptr", mmaped_ptr},
-	{NULL, NULL}
-};
-
-LUA_API int luaopen_testlightuserdata(lua_State *L)
+int main(void)
 {
-	luaL_register(L, "testlightuserdata", testlightuserdata);
-	return 1;
+	lua_State *L = utils_lua_init();
+	const struct test_unit tgroup[] = {
+		test_unit_new(crafted_ptr),
+		test_unit_new(mmaped_ptr)
+	};
+	const int test_result = test_run_group(tgroup, L);
+	utils_lua_close(L);
+	return test_result;
 }
-
diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
index b1c7207f..527905b6 100644
--- a/test/tarantool-tests/CMakeLists.txt
+++ b/test/tarantool-tests/CMakeLists.txt
@@ -61,7 +61,6 @@ add_subdirectory(gh-5813-resolving-of-c-symbols/gnuhash)
 add_subdirectory(gh-5813-resolving-of-c-symbols/stripped)
 add_subdirectory(gh-6098-fix-side-exit-patching-on-arm64)
 add_subdirectory(gh-6189-cur_L)
-add_subdirectory(lj-49-bad-lightuserdata)
 add_subdirectory(lj-416-xor-before-jcc)
 add_subdirectory(lj-601-fix-gc-finderrfunc)
 add_subdirectory(lj-727-lightuserdata-itern)
diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua b/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
deleted file mode 100644
index 94a743c7..00000000
--- a/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-local tap = require('tap')
-
-local test = tap.test('lj-49-bad-lightuserdata')
-test:plan(2)
-
-local testlightuserdata = require('testlightuserdata')
-
-test:ok(testlightuserdata.crafted_ptr())
-test:ok(testlightuserdata.mmaped_ptr())
-
-os.exit(test:check() and 0 or 1)
diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt b/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
deleted file mode 100644
index ec6bb62c..00000000
--- a/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
+++ /dev/null
@@ -1 +0,0 @@
-BuildTestCLib(testlightuserdata testlightuserdata.c)
-- 
2.34.1


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables Sergey Kaplun via Tarantool-patches
@ 2023-06-05 14:21   ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-05 14:21 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches

Sergey,

thanks for patches and fixes!

LGTM with a minor comment below.

On 6/5/23 13:41, Sergey Kaplun wrote:
> When we set `LUA_TEST_ENV_MORE` variable to be used in the additional
> env command for run testing if `"` is used to wrap the `LD_LIBRARY_PATH`
> value the content of this environment variable is literally
> `"/abs/path1:/abs/path2:...:"`. So, the first entry is treated as the
> relative path starting with `"`. In that case if we need the library to
> be loaded via FFI for this particular test, that loading fails with the
> error "cannot open shared object file", since the path to it is
> incorrect.
>
> This patch removes `"` wrapping for the aforementioned variables.

Nit: I would also add a couple of words about how it is related to a 
whole patchset.

Not it is not obvious.

As I got it right at least a test 
"test/tarantool-tests/lj-flush-on-trace.test.lua" fails

because it uses "DYLD_LIBRARY_PATH".


> ---
>   test/tarantool-tests/CMakeLists.txt | 9 +++++++--
>   1 file changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
> index a428d009..38d6ae49 100644
> --- a/test/tarantool-tests/CMakeLists.txt
> +++ b/test/tarantool-tests/CMakeLists.txt
> @@ -102,6 +102,11 @@ endif()
>   # loaded modules on MacOS instead of shared libraries as it is
>   # done on Linux and BSD, another environment variable should be
>   # used to guide <ffi.load> while searching the extension.
> +# XXX: Be noticed that we shouldn't use `"` here to wrap
> +# the variable's content. If we do this, the variable value will
> +# contain `"` at the beginning and the end, so this `"` at the
> +# beginning will be treated as the directory for the first entry
> +# (the last subdirectory added).
>   if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
>     # XXX: Apple tries their best to "protect their users from
>     # malware". As a result SIP (see the link[1] below) has been
> @@ -122,9 +127,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
>     #
>     # [1]: https://support.apple.com/en-us/HT204899
>     # [2]: https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/RuntimeProtections/RuntimeProtections.html
> -  list(APPEND LUA_TEST_ENV_MORE DYLD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
> +  list(APPEND LUA_TEST_ENV_MORE DYLD_LIBRARY_PATH=${LD_LIBRARY_PATH})
>   else()
> -  list(APPEND LUA_TEST_ENV_MORE LD_LIBRARY_PATH="${LD_LIBRARY_PATH}")
> +  list(APPEND LUA_TEST_ENV_MORE LD_LIBRARY_PATH=${LD_LIBRARY_PATH})
>   endif()
>   
>   # LUA_CPATH and LD_LIBRARY_PATH variables and also TESTLIBS list

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests Sergey Kaplun via Tarantool-patches
@ 2023-06-05 15:08   ` Sergey Bronnikov via Tarantool-patches
  2023-06-07 15:51     ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 1 reply; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-05 15:08 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: 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 <test.c> 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(<reason>), bail_out() with 
bail_out(<reason>), todo() with todo(<reason>)

and skip() with skip(<reason>).

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 <limits.h>
> +#include <setjmp.h>
> +#include <stdarg.h>
> +/* Need for `strchr()` in diagnostic parsing. */
> +#include <string.h>
> +
> +/*
> + * 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 <stdio.h>
> +#include <stdlib.h>
> +
> +/*
> + * 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);
> +}

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper for C tests
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper " Sergey Kaplun via Tarantool-patches
@ 2023-06-05 15:11   ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-05 15:11 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches

Sergey,


thanks for patches and fixes.


LGTM with a couple of minor comments below


On 6/5/23 13:41, Sergey Kaplun wrote:
> This header contains generic init and close functions for tests and
> helpers for loading auxiliary Lua script with functions to run inside a
> C test.  See more information in the <README.md> file.
>
> It will be properly used in the next commit.
>
> Part of tarantool/tarantool#7900
> ---
>   test/tarantool-c-tests/CMakeLists.txt |  3 ++
>   test/tarantool-c-tests/README.md      | 36 +++++++++++++
>   test/tarantool-c-tests/utils.h        | 75 +++++++++++++++++++++++++++
>   3 files changed, 114 insertions(+)
>   create mode 100644 test/tarantool-c-tests/README.md
>   create mode 100644 test/tarantool-c-tests/utils.h
>
> diff --git a/test/tarantool-c-tests/CMakeLists.txt b/test/tarantool-c-tests/CMakeLists.txt
> index da128457..17255345 100644
> --- a/test/tarantool-c-tests/CMakeLists.txt
> +++ b/test/tarantool-c-tests/CMakeLists.txt
> @@ -26,6 +26,9 @@ set_target_properties(libtest PROPERTIES
>   # lj_arch.h in compiled test are consistent with the LuaJIT library
>   # to link.
>   AppendFlags(TESTS_C_FLAGS ${TARGET_C_FLAGS})
> +# Use directory for absolute path to the Lua script helper. So,
> +# test binary can be run from any directory.
> +AppendFlags(TESTS_C_FLAGS "-D__LJ_TEST_DIR__='\"${CMAKE_CURRENT_SOURCE_DIR}\"'")
>   
>   set(CTEST_SRC_SUFFIX ".test.c")
>   file(GLOB tests "${CMAKE_CURRENT_SOURCE_DIR}/*${CTEST_SRC_SUFFIX}")
> diff --git a/test/tarantool-c-tests/README.md b/test/tarantool-c-tests/README.md
> new file mode 100644
> index 00000000..102ec151
> --- /dev/null
> +++ b/test/tarantool-c-tests/README.md
> @@ -0,0 +1,36 @@
> +This directory contains C tests for Tarantool's fork of LuaJIT.
> +
> +They should test C API, some unit functionality, etc..
> +
> +The group of unit tests is declared like the following:
> +```c
> +void *t_state = NULL;
> +const struct test_unit tgroup[] = {
> +      test_unit_new(test_base),
> +      test_unit_new(test_simple),
> +};
> +return test_run_group(tgroup, t_state);
> +```
> +
> +Sometimes we need to test C API for modules, that show some Lua metrics (like
> +`luaM_metrics` or sysprof). In these cases, the required Lua script should be
> +named like the following: `<ctestname-script.lua>` and contains a table with a
> +bunch of Lua functions named the same as the unit C test, that uses this
> +function.
> +
> +```lua
> +local M = {}
> +M.base = function()
> +  -- ...
> +end
> +
> +M.test_simple = function()
> +  -- ...
> +end
> +
> +return M
> +```
> +
> +The script is loaded via `utils_load_aux_script(L, script_name)`. It loads the file
> +and place the table with functions at the top of Lua stack. Each function is get
> +from the table via `utils_get_aux_lfunc(L)` helper in the corresponding test.
> diff --git a/test/tarantool-c-tests/utils.h b/test/tarantool-c-tests/utils.h
> new file mode 100644
> index 00000000..714aa61a
> --- /dev/null
> +++ b/test/tarantool-c-tests/utils.h
> @@ -0,0 +1,75 @@
> +#ifndef TARANTOOL_LUAJIT_TEST_UTILS_H
> +#define TARANTOOL_LUAJIT_TEST_UTILS_H
> +
> +#include <limits.h>
> +#include <string.h>
> +
> +#include "lauxlib.h"
> +#include "lua.h"
> +#include "luajit.h"
> +#include "lualib.h"
> +
> +#include "test.h"
> +
> +#define UTILS_UNUSED __attribute__((unused))
> +
> +/* Generic init for our tests. */
> +static lua_State *utils_lua_init(void)
> +{
> +	lua_State *L = luaL_newstate();
> +	if (!L)
> +		bail_out("Can't init Lua state");
> +	/* Stop collector during library initialization. */

Message would be more helpful with explanation *why* GC should be stopped.


> +	lua_gc(L, LUA_GCSTOP, 0);
> +	luaL_openlibs(L);
> +	lua_gc(L, LUA_GCRESTART, -1);
> +	return L;
> +}
> +
> +/* Generic close for our tests. */
> +static void utils_lua_close(lua_State *L)
> +{
> +	lua_close(L);
> +}
> +
> +/*
> + * Load the pair to the test file <filename-script.lua>.

Nit: replace filename-script with file (same as in argument name, this 
allows easily match them

on reading

> + * Each file should return the table with functions (named the
> + * same as the corresponding unit tests) to call in unit tests.
> + * Script file name is given as the second argument.
> + * Push the table with those functions on the Lua stack.
> + */
> +UTILS_UNUSED static void utils_load_aux_script(lua_State *L, const char *file)
> +{
> +	/*
> +	 * Format script name.
> +	 * `__LJ_TEST_DIR__` is set via CMake.
> +	 */
> +	char script[PATH_MAX] = __LJ_TEST_DIR__;
> +	char *script_name = script + sizeof(__LJ_TEST_DIR__) - 1;
> +	/* Replace '\0' with '/'. */
> +	*script_name++ = '/';
> +	/* Append script filename. */
> +	strcpy(script_name, file);
> +
> +	if (luaL_dofile(L, script) != LUA_OK) {
> +		test_comment("Can't load %s: '%s'", script,
> +			     lua_tostring(L, -1));
> +		bail_out("Can't load auxiliary script");
> +	}
> +
> +	if (!lua_istable(L, -1))
> +		bail_out("Returned value from script is not a table");
> +}
> +
> +/*
> + * Accept a table on top of the Lua stack which containing the
> + * function named as the unit test we currently executing.
> + */
> +#define utils_get_aux_lfunc(L) do {					\
> +	lua_getfield((L), -1, __func__);				\
> +	if (!lua_isfunction((L), -1))					\
> +		bail_out("Can't get auxiliary test function");		\
> +} while (0)
> +
> +#endif /* TARANTOOL_LUAJIT_TEST_UTILS_H */

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests
  2023-06-05 15:08   ` Sergey Bronnikov via Tarantool-patches
@ 2023-06-07 15:51     ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-07 15:51 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches


On 6/5/23 18:08, Sergey Bronnikov via Tarantool-patches wrote:
> Sergey,
>
> thanks for patches and fixes. See my comments below.
>
> On 6/5/23 13:41, Sergey Kaplun wrote: 
<snipped>
>> 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").
>
>
there is a typo. I mean "we have *no* opposite function with destructor"

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata test in C
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata " Sergey Kaplun via Tarantool-patches
@ 2023-06-07 16:14   ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-07 16:14 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches

Sergey,

thanks for a third version of the patch!

LGTM in general, however I have a minor comment, see below.


On 6/5/23 13:41, Sergey Kaplun wrote:
> This patch rewrites the aforementioned test with the usage libtest
> recently introduced. The approach is similar to the previous patch.
>
> Nevertheless, glibc `assert()` is used to check the correctness
> of the `mmap()` usage.
>
> Part of tarantool/tarantool#7900
> ---
>   .../lj-49-bad-lightuserdata.test.c}           | 47 ++++++++++---------
>   test/tarantool-tests/CMakeLists.txt           |  1 -
>   .../lj-49-bad-lightuserdata.test.lua          | 11 -----
>   .../lj-49-bad-lightuserdata/CMakeLists.txt    |  1 -
>   4 files changed, 25 insertions(+), 35 deletions(-)
>   rename test/{tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c => tarantool-c-tests/lj-49-bad-lightuserdata.test.c} (55%)
>   delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
>   delete mode 100644 test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
>
> diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c b/test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
> similarity index 55%
> rename from test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c
> rename to test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
> index 1b909fc6..a9cc4763 100644
> --- a/test/tarantool-tests/lj-49-bad-lightuserdata/testlightuserdata.c
> +++ b/test/tarantool-c-tests/lj-49-bad-lightuserdata.test.c
> @@ -1,16 +1,21 @@
> -#include <lua.h>
> -#include <lauxlib.h>
> +#include "lua.h"
> +#include "lauxlib.h"
>   
>   #include <sys/mman.h>
>   #include <unistd.h>
>   
> -#undef NDEBUG
> -#include <assert.h>
> +#include "test.h"
> +#include "utils.h"
>   
>   #define START ((void *)-1)
>   
> -static int crafted_ptr(lua_State *L)
> +/* XXX: Still need normal assert to validate mmap correctness. */
> +#undef NDEBUG
> +#include <assert.h>
> +
> +static int crafted_ptr(void *test_state)

Nit: I would rename test_state to something like lua_state here and in 
mmaped_ptr.

Feel free to ignore.

>   {
> +	lua_State *L = test_state;
>   	/*
>   	 * We know that for arm64 at least 48 bits are available.
>   	 * So emulate manually push of lightuseradata within
> @@ -18,15 +23,15 @@ static int crafted_ptr(lua_State *L)
>   	 */
>   	void *longptr = (void *)(1llu << 48);
>   	lua_pushlightuserdata(L, longptr);
> -	assert(longptr == lua_topointer(L, -1));
> +	assert_ptr_equal(longptr, lua_topointer(L, -1));
>   	/* Clear our stack. */
>   	lua_pop(L, 0);
> -	lua_pushboolean(L, 1);
> -	return 1;
> +	return TEST_EXIT_SUCCESS;
>   }
>   
> -static int mmaped_ptr(lua_State *L)
> +static int mmaped_ptr(void *test_state)
>   {
> +	lua_State *L = test_state;
>   	/*
>   	 * If start mapping address is not NULL, then the kernel
>   	 * takes it as a hint about where to place the mapping, so
> @@ -38,24 +43,22 @@ static int mmaped_ptr(lua_State *L)
>   			    -1, 0);
>   	if (mmaped != MAP_FAILED) {
>   		lua_pushlightuserdata(L, mmaped);
> -		assert(mmaped == lua_topointer(L, -1));
> +		assert_ptr_equal(mmaped, lua_topointer(L, -1));
>   		assert(munmap(mmaped, pagesize) == 0);
>   	}
>   	/* Clear our stack. */
>   	lua_pop(L, 0);
> -	lua_pushboolean(L, 1);
> -	return 1;
> +	return TEST_EXIT_SUCCESS;
>   }
>   
> -static const struct luaL_Reg testlightuserdata[] = {
> -	{"crafted_ptr", crafted_ptr},
> -	{"mmaped_ptr", mmaped_ptr},
> -	{NULL, NULL}
> -};
> -
> -LUA_API int luaopen_testlightuserdata(lua_State *L)
> +int main(void)
>   {
> -	luaL_register(L, "testlightuserdata", testlightuserdata);
> -	return 1;
> +	lua_State *L = utils_lua_init();
> +	const struct test_unit tgroup[] = {
> +		test_unit_new(crafted_ptr),
> +		test_unit_new(mmaped_ptr)
> +	};
> +	const int test_result = test_run_group(tgroup, L);
> +	utils_lua_close(L);
> +	return test_result;
>   }
> -
> diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
> index b1c7207f..527905b6 100644
> --- a/test/tarantool-tests/CMakeLists.txt
> +++ b/test/tarantool-tests/CMakeLists.txt
> @@ -61,7 +61,6 @@ add_subdirectory(gh-5813-resolving-of-c-symbols/gnuhash)
>   add_subdirectory(gh-5813-resolving-of-c-symbols/stripped)
>   add_subdirectory(gh-6098-fix-side-exit-patching-on-arm64)
>   add_subdirectory(gh-6189-cur_L)
> -add_subdirectory(lj-49-bad-lightuserdata)
>   add_subdirectory(lj-416-xor-before-jcc)
>   add_subdirectory(lj-601-fix-gc-finderrfunc)
>   add_subdirectory(lj-727-lightuserdata-itern)
> diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua b/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
> deleted file mode 100644
> index 94a743c7..00000000
> --- a/test/tarantool-tests/lj-49-bad-lightuserdata.test.lua
> +++ /dev/null
> @@ -1,11 +0,0 @@
> -local tap = require('tap')
> -
> -local test = tap.test('lj-49-bad-lightuserdata')
> -test:plan(2)
> -
> -local testlightuserdata = require('testlightuserdata')
> -
> -test:ok(testlightuserdata.crafted_ptr())
> -test:ok(testlightuserdata.mmaped_ptr())
> -
> -os.exit(test:check() and 0 or 1)
> diff --git a/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt b/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
> deleted file mode 100644
> index ec6bb62c..00000000
> --- a/test/tarantool-tests/lj-49-bad-lightuserdata/CMakeLists.txt
> +++ /dev/null
> @@ -1 +0,0 @@
> -BuildTestCLib(testlightuserdata testlightuserdata.c)

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi test in C
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi " Sergey Kaplun via Tarantool-patches
@ 2023-06-07 16:18   ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-07 16:18 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches

Sergey,


thanks for the patch! LGTM


On 6/5/23 13:41, Sergey Kaplun wrote:
> This patch rewrites the aforementioned test with the usage libtest
> recently introduced. The approach is similar to the previous patch.
> `c_payload()` function to profile is now set up globally on script
> loading. The payload for enabled and disabled JIT is the same. As far as
> for sysprof testing is necessary to replace backtrace with libunwind
> first, the payload tests are marked with `todo()`.
>
> Nevertheless, glibc `assert()` still necessary to check the correctness
> of the profile writer, so it is still used there.
>
> Relates to tarantool/tarantool#781
> Part of tarantool/tarantool#7900
> ---
>   .../misclib-sysprof-capi-script.lua           |  35 ++
>   .../misclib-sysprof-capi.test.c               | 325 ++++++++++++++++++
>   test/tarantool-tests/CMakeLists.txt           |   1 -
>   .../misclib-sysprof-capi.test.lua             |  54 ---
>   .../misclib-sysprof-capi/CMakeLists.txt       |   1 -
>   .../misclib-sysprof-capi/testsysprof.c        | 260 --------------
>   6 files changed, 360 insertions(+), 316 deletions(-)
>   create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi-script.lua
>   create mode 100644 test/tarantool-c-tests/misclib-sysprof-capi.test.c
>   delete mode 100644 test/tarantool-tests/misclib-sysprof-capi.test.lua
>   delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
>   delete mode 100644 test/tarantool-tests/misclib-sysprof-capi/testsysprof.c
>
> diff --git a/test/tarantool-c-tests/misclib-sysprof-capi-script.lua b/test/tarantool-c-tests/misclib-sysprof-capi-script.lua
> new file mode 100644
> index 00000000..dd8387db
> --- /dev/null
> +++ b/test/tarantool-c-tests/misclib-sysprof-capi-script.lua
> @@ -0,0 +1,35 @@
> +local M = {}
> +
> +-- luacheck: no global
> +assert(c_payload, 'c_payload global function should be set via script loader')
> +
> +local function lua_payload(n)
> +  if n <= 1 then
> +    return n
> +  end
> +  return lua_payload(n - 1) + lua_payload(n - 2)
> +end
> +
> +local function payload()
> +  local n_iterations = 500000
> +
> +  local co = coroutine.create(function()
> +    for i = 1, n_iterations do
> +      if i % 2 == 0 then
> +        c_payload(10)
> +      else
> +        lua_payload(10)
> +      end
> +      coroutine.yield()
> +    end
> +  end)
> +
> +  for _ = 1, n_iterations do
> +    coroutine.resume(co)
> +  end
> +end
> +
> +M.profile_func_jiton = payload
> +M.profile_func_jitoff = payload
> +
> +return M
> diff --git a/test/tarantool-c-tests/misclib-sysprof-capi.test.c b/test/tarantool-c-tests/misclib-sysprof-capi.test.c
> new file mode 100644
> index 00000000..4a7a0d46
> --- /dev/null
> +++ b/test/tarantool-c-tests/misclib-sysprof-capi.test.c
> @@ -0,0 +1,325 @@
> +#include "lauxlib.h"
> +#include "lmisclib.h"
> +#include "lua.h"
> +#include "luajit.h"
> +
> +#include "test.h"
> +#include "utils.h"
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +
> +/* XXX: Still need normal assert inside writer functions. */
> +#undef NDEBUG
> +#include <assert.h>
> +
> +/* Need for skipcond for OS and ARCH. */
> +#include "lj_arch.h"
> +
> +#define UNUSED(x) ((void)(x))
> +
> +/* --- utils -------------------------------------------------- */
> +
> +#define SYSPROF_INTERVAL_DEFAULT 100
> +
> +/*
> + * Yep, 8Mb. Tuned in order not to bother the platform with too
> + * often flushes.
> + */
> +#define STREAM_BUFFER_SIZE (8 * 1024 * 1024)
> +
> +
> +/* --- C Payload ---------------------------------------------- */
> +
> +static double fib(double n)
> +{
> +	if (n <= 1)
> +		return n;
> +	return fib(n - 1) + fib(n - 2);
> +}
> +
> +static int c_payload(lua_State *L)
> +{
> +	fib(luaL_checknumber(L, 1));
> +	lua_pushboolean(L, 1);
> +	return 1;
> +}
> +/* --- sysprof C API tests ------------------------------------ */
> +
> +static int base(void *test_state)
> +{
> +	UNUSED(test_state);
> +	struct luam_Sysprof_Options opt = {};
> +	struct luam_Sysprof_Counters cnt = {};
> +
> +	(void)opt.interval;
> +	(void)opt.mode;
> +	(void)opt.ctx;
> +	(void)opt.buf;
> +	(void)opt.len;
> +
> +	luaM_sysprof_report(&cnt);
> +
> +	(void)cnt.samples;
> +	(void)cnt.vmst_interp;
> +	(void)cnt.vmst_lfunc;
> +	(void)cnt.vmst_ffunc;
> +	(void)cnt.vmst_cfunc;
> +	(void)cnt.vmst_gc;
> +	(void)cnt.vmst_exit;
> +	(void)cnt.vmst_record;
> +	(void)cnt.vmst_opt;
> +	(void)cnt.vmst_asm;
> +	(void)cnt.vmst_trace;
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int validation(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Sysprof_Options opt = {};
> +	int status = PROFILE_SUCCESS;
> +
> +	/* Unknown mode. */
> +	opt.mode = 0x40;
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_ERRUSE);
> +
> +	/* Buffer not configured. */
> +	opt.mode = LUAM_SYSPROF_CALLGRAPH;
> +	opt.buf = NULL;
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_ERRUSE);
> +
> +	/* Bad interval. */
> +	opt.mode = LUAM_SYSPROF_DEFAULT;
> +	opt.interval = 0;
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_ERRUSE);
> +
> +	/* Check if profiling started. */
> +	opt.mode = LUAM_SYSPROF_DEFAULT;
> +	opt.interval = SYSPROF_INTERVAL_DEFAULT;
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_SUCCESS);
> +
> +	/* Already running. */
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_ERRRUN);
> +
> +	/* Profiler stopping. */
> +	status = luaM_sysprof_stop(L);
> +	assert_true(status == PROFILE_SUCCESS);
> +
> +	/* Stopping profiler which is not running. */
> +	status = luaM_sysprof_stop(L);
> +	assert_true(status == PROFILE_ERRRUN);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +/*
> + * FIXME: The following two tests are disabled because sometimes
> + * `backtrace` dynamically loads a platform-specific unwinder,
> + * which is not signal-safe.
> + */
> +
> +#if 0
> +/*
> + * Structure given as ctx to sysprof writer and on_stop callback.
> + */
> +struct sysprof_ctx {
> +	/* Output file descriptor for data. */
> +	int fd;
> +	/* Buffer for data. */
> +	uint8_t buf[STREAM_BUFFER_SIZE];
> +};
> +
> +/*
> + * Default buffer writer function.
> + * Just call fwrite to the corresponding FILE.
> + */
> +static size_t buffer_writer_default(const void **buf_addr, size_t len,
> +				    void *opt)
> +{
> +	struct sysprof_ctx *ctx = opt;
> +	int fd = ctx->fd;
> +	const void * const buf_start = *buf_addr;
> +	const void *data = *buf_addr;
> +	size_t write_total = 0;
> +
> +	assert(len <= STREAM_BUFFER_SIZE);
> +
> +	for (;;) {
> +		const size_t written = write(fd, data, len - write_total);
> +
> +		if (written == 0) {
> +			/* Re-tries write in case of EINTR. */
> +			if (errno != EINTR) {
> +				/*
> +				 * Will be freed as whole chunk
> +				 * later.
> +				 */
> +				*buf_addr = NULL;
> +				return write_total;
> +			}
> +			errno = 0;
> +			continue;
> +		}
> +
> +		write_total += written;
> +		assert(write_total <= len);
> +
> +		if (write_total == len)
> +			break;
> +
> +		data = (uint8_t *)data + (ptrdiff_t)written;
> +	}
> +
> +	*buf_addr = buf_start;
> +	return write_total;
> +}
> +
> +/*
> + * Default on stop callback. Just close the corresponding stream.
> + */
> +static int on_stop_cb_default(void *opt, uint8_t *buf)
> +{
> +	UNUSED(buf);
> +	struct sysprof_ctx *ctx = opt;
> +	int fd = ctx->fd;
> +	free(ctx);
> +	return close(fd);
> +}
> +
> +static int stream_init(struct luam_Sysprof_Options *opt)
> +{
> +	struct sysprof_ctx *ctx = calloc(1, sizeof(struct sysprof_ctx));
> +	if (NULL == ctx)
> +		return PROFILE_ERRIO;
> +
> +	ctx->fd = open("/dev/null", O_WRONLY | O_CREAT, 0644);
> +	if (-1 == ctx->fd) {
> +		free(ctx);
> +		return PROFILE_ERRIO;
> +	}
> +
> +	opt->ctx = ctx;
> +	opt->buf = ctx->buf;
> +	opt->len = STREAM_BUFFER_SIZE;
> +
> +	return PROFILE_SUCCESS;
> +}
> +
> +/* Get function to profile on top, call it. */
> +static int check_profile_func(lua_State *L)
> +{
> +	struct luam_Sysprof_Options opt = {};
> +	struct luam_Sysprof_Counters cnt = {};
> +	int status = PROFILE_ERRUSE;
> +	/*
> +	 * Since all the other modes functionality is the
> +	 * subset of CALLGRAPH mode, run this mode to test
> +	 * the profiler's behavior.
> +	 */
> +	opt.mode = LUAM_SYSPROF_CALLGRAPH;
> +	opt.interval = SYSPROF_INTERVAL_DEFAULT;
> +	stream_init(&opt);
> +
> +	/*
> +	 * XXX: Payload function on top will not be removed if any
> +	 * of those assertions fail. So, the next call to the
> +	 * `utils_get_aux_lfunc()` will fail. It's OK, since
> +	 * we are already in trouble, just keep it in mind.
> +	 */
> +	assert_true(luaM_sysprof_set_writer(buffer_writer_default)
> +		    == PROFILE_SUCCESS);
> +	assert_true(luaM_sysprof_set_on_stop(on_stop_cb_default)
> +		    == PROFILE_SUCCESS);
> +	assert_true(luaM_sysprof_set_backtracer(NULL) == PROFILE_SUCCESS);
> +
> +	status = luaM_sysprof_start(L, &opt);
> +	assert_true(status == PROFILE_SUCCESS);
> +
> +	/* Run payload. */
> +	if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
> +		test_comment("error running payload: %s", lua_tostring(L, -1));
> +		bail_out("error running sysprof test payload");
> +	}
> +
> +	status = luaM_sysprof_stop(L);
> +	assert_true(status == PROFILE_SUCCESS);
> +
> +	status = luaM_sysprof_report(&cnt);
> +	assert_true(status == PROFILE_SUCCESS);
> +
> +	assert_true(cnt.samples > 1);
> +	assert_true(cnt.samples == cnt.vmst_asm +
> +			cnt.vmst_cfunc +
> +			cnt.vmst_exit +
> +			cnt.vmst_ffunc +
> +			cnt.vmst_gc +
> +			cnt.vmst_interp +
> +			cnt.vmst_lfunc +
> +			cnt.vmst_opt +
> +			cnt.vmst_record +
> +			cnt.vmst_trace);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +#endif
> +
> +static int profile_func_jitoff(void *test_state)
> +{
> +	UNUSED(test_state);
> +	return todo("Need to replace backtrace with libunwind first");
> +#if 0
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF);
> +	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_FLUSH);
> +	check_profile_func(L);
> +	(void)luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_ON);
> +	return TEST_EXIT_SUCCESS;
> +#endif
> +}
> +
> +static int profile_func_jiton(void *test_state)
> +{
> +	UNUSED(test_state);
> +	return todo("Need to replace backtrace with libunwind first");
> +#if 0
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	check_profile_func(L);
> +	return TEST_EXIT_SUCCESS;
> +#endif
> +}
> +
> +int main(void)
> +{
> +	if (LUAJIT_OS != LUAJIT_OS_LINUX)
> +		return skip_all("Sysprof is implemented for Linux only");
> +	if (LUAJIT_TARGET != LUAJIT_ARCH_X86
> +	    && LUAJIT_TARGET != LUAJIT_ARCH_X64)
> +		return skip_all("Sysprof is implemented for x86_64 only");
> +
> +	lua_State *L = utils_lua_init();
> +
> +	lua_pushcfunction(L, c_payload);
> +	lua_setfield(L, LUA_GLOBALSINDEX, "c_payload");
> +	utils_load_aux_script(L, "misclib-sysprof-capi-script.lua");
> +
> +	const struct test_unit tgroup[] = {
> +		test_unit_new(base),
> +		test_unit_new(validation),
> +		test_unit_new(profile_func_jitoff),
> +		test_unit_new(profile_func_jiton)
> +	};
> +	const int test_result = test_run_group(tgroup, L);
> +	utils_lua_close(L);
> +	return test_result;
> +}
> diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
> index b4ce39d3..b1c7207f 100644
> --- a/test/tarantool-tests/CMakeLists.txt
> +++ b/test/tarantool-tests/CMakeLists.txt
> @@ -66,7 +66,6 @@ add_subdirectory(lj-416-xor-before-jcc)
>   add_subdirectory(lj-601-fix-gc-finderrfunc)
>   add_subdirectory(lj-727-lightuserdata-itern)
>   add_subdirectory(lj-flush-on-trace)
> -add_subdirectory(misclib-sysprof-capi)
>   
>   # The part of the memory profiler toolchain is located in tools
>   # directory, jit, profiler, and bytecode toolchains are located
> diff --git a/test/tarantool-tests/misclib-sysprof-capi.test.lua b/test/tarantool-tests/misclib-sysprof-capi.test.lua
> deleted file mode 100644
> index 4395bce3..00000000
> --- a/test/tarantool-tests/misclib-sysprof-capi.test.lua
> +++ /dev/null
> @@ -1,54 +0,0 @@
> -local tap = require("tap")
> -local test = tap.test("clib-misc-sysprof"):skipcond({
> -  ["Sysprof is implemented for x86_64 only"] = jit.arch ~= "x86" and
> -                                               jit.arch ~= "x64",
> -  ["Sysprof is implemented for Linux only"] = jit.os ~= "Linux",
> -})
> -
> -test:plan(2)
> -
> -local testsysprof = require("testsysprof")
> -
> -jit.off()
> -
> -test:ok(testsysprof.base())
> -test:ok(testsysprof.validation())
> -
> --- FIXME: The following two tests are disabled because sometimes
> --- `backtrace` dynamically loads a platform-specific unwinder, which is
> --- not signal-safe.
> ---[[
> -local function lua_payload(n)
> -  if n <= 1 then
> -    return n
> -  end
> -  return lua_payload(n - 1) + lua_payload(n - 2)
> -end
> -
> -local function payload()
> -  local n_iterations = 500000
> -
> -  local co = coroutine.create(function ()
> -    for i = 1, n_iterations do
> -      if i % 2 == 0 then
> -        testsysprof.c_payload(10)
> -      else
> -        lua_payload(10)
> -      end
> -      coroutine.yield()
> -    end
> -  end)
> -
> -  for _ = 1, n_iterations do
> -    coroutine.resume(co)
> -  end
> -end
> -
> -test:ok(testsysprof.profile_func(payload))
> -
> -jit.on()
> -jit.flush()
> -
> -test:ok(testsysprof.profile_func(payload))
> ---]]
> -os.exit(test:check() and 0 or 1)
> diff --git a/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt b/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
> deleted file mode 100644
> index d9fb1a1a..00000000
> --- a/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt
> +++ /dev/null
> @@ -1 +0,0 @@
> -BuildTestCLib(testsysprof testsysprof.c)
> diff --git a/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c b/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c
> deleted file mode 100644
> index d7a3e355..00000000
> --- a/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c
> +++ /dev/null
> @@ -1,260 +0,0 @@
> -#include <lua.h>
> -#include <luajit.h>
> -#include <lauxlib.h>
> -
> -#include <lmisclib.h>
> -
> -#include <fcntl.h>
> -#include <stdlib.h>
> -#include <unistd.h>
> -#include <errno.h>
> -
> -#undef NDEBUG
> -#include <assert.h>
> -
> -#define UNUSED(x) ((void)(x))
> -
> -/* --- utils -------------------------------------------------------------- */
> -
> -#define SYSPROF_INTERVAL_DEFAULT 100
> -
> -/*
> - ** Yep, 8Mb. Tuned in order not to bother the platform with too often flushes.
> - */
> -#define STREAM_BUFFER_SIZE (8 * 1024 * 1024)
> -
> -/* Structure given as ctx to sysprof writer and on_stop callback. */
> -struct sysprof_ctx {
> -	/* Output file descriptor for data. */
> -	int fd;
> -	/* Buffer for data. */
> -	uint8_t buf[STREAM_BUFFER_SIZE];
> -};
> -
> -/*
> - ** Default buffer writer function.
> - ** Just call fwrite to the corresponding FILE.
> - */
> -static size_t buffer_writer_default(const void **buf_addr, size_t len,
> -		void *opt)
> -{
> -	struct sysprof_ctx *ctx = opt;
> -	int fd = ctx->fd;
> -	const void * const buf_start = *buf_addr;
> -	const void *data = *buf_addr;
> -	size_t write_total = 0;
> -
> -	assert(len <= STREAM_BUFFER_SIZE);
> -
> -	for (;;) {
> -		const size_t written = write(fd, data, len - write_total);
> -
> -		if (written == 0) {
> -			/* Re-tries write in case of EINTR. */
> -			if (errno != EINTR) {
> -				/* Will be freed as whole chunk later. */
> -				*buf_addr = NULL;
> -				return write_total;
> -			}
> -			errno = 0;
> -			continue;
> -		}
> -
> -		write_total += written;
> -		assert(write_total <= len);
> -
> -		if (write_total == len)
> -			break;
> -
> -		data = (uint8_t *)data + (ptrdiff_t)written;
> -	}
> -
> -	*buf_addr = buf_start;
> -	return write_total;
> -}
> -
> -/* Default on stop callback. Just close the corresponding stream. */
> -static int on_stop_cb_default(void *opt, uint8_t *buf)
> -{
> -	UNUSED(buf);
> -	struct sysprof_ctx *ctx = opt;
> -	int fd = ctx->fd;
> -	free(ctx);
> -	return close(fd);
> -}
> -
> -static int stream_init(struct luam_Sysprof_Options *opt)
> -{
> -	struct sysprof_ctx *ctx = calloc(1, sizeof(struct sysprof_ctx));
> -	if (NULL == ctx)
> -		return PROFILE_ERRIO;
> -
> -	ctx->fd = open("/dev/null", O_WRONLY | O_CREAT, 0644);
> -	if (-1 == ctx->fd) {
> -		free(ctx);
> -		return PROFILE_ERRIO;
> -	}
> -
> -	opt->ctx = ctx;
> -	opt->buf = ctx->buf;
> -	opt->len = STREAM_BUFFER_SIZE;
> -
> -	return PROFILE_SUCCESS;
> -}
> -
> -/* --- Payload ------------------------------------------------------------ */
> -
> -static double fib(double n)
> -{
> -	if (n <= 1)
> -		return n;
> -	return fib(n - 1) + fib(n - 2);
> -}
> -
> -static int c_payload(lua_State *L)
> -{
> -	fib(luaL_checknumber(L, 1));
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -/* --- sysprof C API tests ------------------------------------------------ */
> -
> -static int base(lua_State *L)
> -{
> -	struct luam_Sysprof_Options opt = {};
> -	struct luam_Sysprof_Counters cnt = {};
> -
> -	(void)opt.interval;
> -	(void)opt.mode;
> -	(void)opt.ctx;
> -	(void)opt.buf;
> -	(void)opt.len;
> -
> -	luaM_sysprof_report(&cnt);
> -
> -	(void)cnt.samples;
> -	(void)cnt.vmst_interp;
> -	(void)cnt.vmst_lfunc;
> -	(void)cnt.vmst_ffunc;
> -	(void)cnt.vmst_cfunc;
> -	(void)cnt.vmst_gc;
> -	(void)cnt.vmst_exit;
> -	(void)cnt.vmst_record;
> -	(void)cnt.vmst_opt;
> -	(void)cnt.vmst_asm;
> -	(void)cnt.vmst_trace;
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int validation(lua_State *L)
> -{
> -	struct luam_Sysprof_Options opt = {};
> -	int status = PROFILE_SUCCESS;
> -
> -	/* Unknown mode. */
> -	opt.mode = 0x40;
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_ERRUSE == status);
> -
> -	/* Buffer not configured. */
> -	opt.mode = LUAM_SYSPROF_CALLGRAPH;
> -	opt.buf = NULL;
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_ERRUSE == status);
> -
> -	/* Bad interval. */
> -	opt.mode = LUAM_SYSPROF_DEFAULT;
> -	opt.interval = 0;
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_ERRUSE == status);
> -
> -	/* Check if profiling started. */
> -	opt.mode = LUAM_SYSPROF_DEFAULT;
> -	opt.interval = SYSPROF_INTERVAL_DEFAULT;
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_SUCCESS == status);
> -
> -	/* Already running. */
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_ERRRUN == status);
> -
> -	/* Profiler stopping. */
> -	status = luaM_sysprof_stop(L);
> -	assert(PROFILE_SUCCESS == status);
> -
> -	/* Stopping profiler which is not running. */
> -	status = luaM_sysprof_stop(L);
> -	assert(PROFILE_ERRRUN == status);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int profile_func(lua_State *L)
> -{
> -	struct luam_Sysprof_Options opt = {};
> -	struct luam_Sysprof_Counters cnt = {};
> -	int status = PROFILE_ERRUSE;
> -
> -	int n = lua_gettop(L);
> -	if (n != 1 || !lua_isfunction(L, 1))
> -		luaL_error(L, "incorrect argument: 1 function is required");
> -
> -	/*
> -	 ** Since all the other modes functionality is the
> -	 ** subset of CALLGRAPH mode, run this mode to test
> -	 ** the profiler's behavior.
> -	 */
> -	opt.mode = LUAM_SYSPROF_CALLGRAPH;
> -	opt.interval = SYSPROF_INTERVAL_DEFAULT;
> -	stream_init(&opt);
> -
> -	assert(luaM_sysprof_set_writer(buffer_writer_default) == PROFILE_SUCCESS);
> -	assert(luaM_sysprof_set_on_stop(on_stop_cb_default) == PROFILE_SUCCESS);
> -	assert(luaM_sysprof_set_backtracer(NULL) == PROFILE_SUCCESS);
> -
> -	status = luaM_sysprof_start(L, &opt);
> -	assert(PROFILE_SUCCESS == status);
> -
> -	/* Run payload. */
> -	if (lua_pcall(L, 0, 0, 0) != LUA_OK)
> -		luaL_error(L, "error running payload: %s", lua_tostring(L, -1));
> -
> -	status = luaM_sysprof_stop(L);
> -	assert(PROFILE_SUCCESS == status);
> -
> -	status = luaM_sysprof_report(&cnt);
> -	assert(PROFILE_SUCCESS == status);
> -
> -	assert(cnt.samples > 1);
> -	assert(cnt.samples == cnt.vmst_asm +
> -			cnt.vmst_cfunc +
> -			cnt.vmst_exit +
> -			cnt.vmst_ffunc +
> -			cnt.vmst_gc +
> -			cnt.vmst_interp +
> -			cnt.vmst_lfunc +
> -			cnt.vmst_opt +
> -			cnt.vmst_record +
> -			cnt.vmst_trace);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static const struct luaL_Reg testsysprof[] = {
> -	{"base", base},
> -	{"c_payload", c_payload},
> -	{"profile_func", profile_func},
> -	{"validation", validation},
> -	{NULL, NULL}
> -};
> -
> -LUA_API int luaopen_testsysprof(lua_State *L)
> -{
> -	luaL_register(L, "testsysprof", testsysprof);
> -	return 1;
> -}

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C
  2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C Sergey Kaplun via Tarantool-patches
@ 2023-06-07 16:25   ` Sergey Bronnikov via Tarantool-patches
  0 siblings, 0 replies; 14+ messages in thread
From: Sergey Bronnikov via Tarantool-patches @ 2023-06-07 16:25 UTC (permalink / raw)
  To: Sergey Kaplun, Igor Munkin, Maxim Kokryashkin; +Cc: tarantool-patches

Hi, Sergey!

LGTM


On 6/5/23 13:41, Sergey Kaplun wrote:
> This patch rewrites the aforementioned test with the usage libtest,
> recently introduced. Since we still stand in need of a Lua helper script
> for generation of traces, the original test file is reworked as a
> standalone script, which returns the table with helper functions. Each
> helper function is named the same as the test where it will be used. Now
> single quotes are used according to our Lua code style.
>
> In C part all asserts from glibc are replaced with the corresponding
> assert_{cond} from the libtest. Now tests return `TEST_EXIT_SUCCESS` at
> the finish. Also, the stack check for the amount of return results from
> the helper function is slightly changed, since there is one more stack
> slot in use (table with these functions). `snap_restores()` C function
> duplicates 4 times for each subtest. Common helper
> `check_snap_restores()` is used for each of them. Each error throwing is
> replaced with `bail_out()` call.
>
> NB: `lua_pop()` to clear the Lua stack after a call should be done before
> any possible assertion, which would exit from the test leaving the stack
> uncleaned.
>
> All skipconds use macros defined in <lj_arch.h>, so it is included in
> the test. As far as this test initializes the LuaJIT VM manually, there
> is no need to check `jit.status()` result; checking `LJ_HASJIT` is
> enough. Now, only JIT-related tests are skipped, when compiled without
> JIT. Nevertheless, all tests are skipped for *BSD arches.
>
> Also, this patch sets the new CMake variable named `LUAJIT_LIBRARY`
> equal to `LUAJIT_LIB` in `PARENT_SCOPE` to be used in tarantool-c-tests
> linking.
>
> Part of tarantool/tarantool#7900
> ---
>   src/CMakeLists.txt                            |   2 +
>   .../misclib-getmetrics-capi-script.lua}       |  83 ++---
>   .../misclib-getmetrics-capi.test.c            | 343 ++++++++++++++++++
>   test/tarantool-tests/CMakeLists.txt           |   1 -
>   .../misclib-getmetrics-capi/CMakeLists.txt    |   1 -
>   .../misclib-getmetrics-capi/testgetmetrics.c  | 270 --------------
>   6 files changed, 383 insertions(+), 317 deletions(-)
>   rename test/{tarantool-tests/misclib-getmetrics-capi.test.lua => tarantool-c-tests/misclib-getmetrics-capi-script.lua} (68%)
>   create mode 100644 test/tarantool-c-tests/misclib-getmetrics-capi.test.c
>   delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
>   delete mode 100644 test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
>
> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
> index dffc0a4d..fed4c38d 100644
> --- a/src/CMakeLists.txt
> +++ b/src/CMakeLists.txt
> @@ -325,6 +325,8 @@ if(NOT BUILDMODE STREQUAL "dynamic")
>     set(LUAJIT_BIN luajit_static)
>     set(LUAJIT_LIB libluajit_static)
>   endif()
> +# Need for the test linking, so the PARENT_SCOPE option is used.
> +set(LUAJIT_LIBRARY ${LUAJIT_LIB} PARENT_SCOPE)
>   set(LIBLUAJIT_DEPS ${LIBLUAJIT_STATIC_DEPS} ${LIBLUAJIT_SHARED_DEPS})
>   
>   add_executable(${LUAJIT_BIN} EXCLUDE_FROM_ALL ${CLI_SOURCES})
> diff --git a/test/tarantool-tests/misclib-getmetrics-capi.test.lua b/test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
> similarity index 68%
> rename from test/tarantool-tests/misclib-getmetrics-capi.test.lua
> rename to test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
> index 654e5545..2f0ee5cf 100644
> --- a/test/tarantool-tests/misclib-getmetrics-capi.test.lua
> +++ b/test/tarantool-c-tests/misclib-getmetrics-capi-script.lua
> @@ -1,32 +1,26 @@
> -local tap = require('tap')
> -local test = tap.test("clib-misc-getmetrics"):skipcond({
> -  ['Test requires JIT enabled'] = not jit.status(),
> -  ['Disabled on *BSD due to #4819'] = jit.os == 'BSD',
> -})
> +local ffi = require('ffi')
>   
> -test:plan(11)
> +-- Auxiliary script to provide Lua functions to be used in tests
> +-- for `getmetrics()` C API inside the test
> +-- <misclib-getmetrics-capi.test.c>.
> +local M = {}
>   
> -local path = arg[0]:gsub('%.test%.lua', '')
> -local suffix = package.cpath:match('?.(%a+);')
> -package.cpath = ('%s/?.%s;'):format(path, suffix)..package.cpath
> +-- XXX: Max nins is limited by max IRRef, that equals to
> +-- REF_DROP - REF_BIAS. Unfortunately, these constants are not
> +-- provided to Lua space, so we ought to make some math:
> +-- * REF_DROP = 0xffff
> +-- * REF_BIAS = 0x8000
> +-- See <src/lj_ir.h> for details.
> +local MAXNINS =  0xffff - 0x8000
>   
> -local MAXNINS = require('utils').const.maxnins
>   local jit_opt_default = {
>       3, -- level
> -    "hotloop=56",
> -    "hotexit=10",
> -    "minstitch=0",
> +    'hotloop=56',
> +    'hotexit=10',
> +    'minstitch=0',
>   }
>   
> -local testgetmetrics = require("testgetmetrics")
> -
> -test:ok(testgetmetrics.base())
> -test:ok(testgetmetrics.gc_allocated_freed())
> -test:ok(testgetmetrics.gc_steps())
> -
> -test:ok(testgetmetrics.objcount(function(iterations)
> -    local ffi = require("ffi")
> -
> +M.objcount = function(iterations)
>       jit.opt.start(0)
>   
>       local placeholder = {
> @@ -51,35 +45,34 @@ test:ok(testgetmetrics.objcount(function(iterations)
>   
>       for _ = 1, iterations do
>           -- Check counting of VLA/VLS/aligned cdata.
> -        table.insert(placeholder.cdata, ffi.new("char[?]", 4))
> +        table.insert(placeholder.cdata, ffi.new('char[?]', 4))
>       end
>   
>       for _ = 1, iterations do
>           -- Check counting of non-VLA/VLS/aligned cdata.
> -        table.insert(placeholder.cdata, ffi.new("uint64_t", _))
> +        table.insert(placeholder.cdata, ffi.new('uint64_t', _))
>       end
>   
>       placeholder = nil -- luacheck: no unused
>       -- Restore default jit settings.
>       jit.opt.start(unpack(jit_opt_default))
> -end))
> +end
>   
> -test:ok(testgetmetrics.objcount_cdata_decrement(function()
> +M.objcount_cdata_decrement = function()
>       -- gc_cdatanum decrement test.
>       -- See https://github.com/tarantool/tarantool/issues/5820.
> -    local ffi = require("ffi")
>       local function nop() end
> -    ffi.gc(ffi.cast("void *", 0), nop)
> +    ffi.gc(ffi.cast('void *', 0), nop)
>       -- Does not collect the cdata, but resurrects the object and
>       -- removes LJ_GC_CDATA_FIN flag.
>       collectgarbage()
>       -- Collects the cdata.
>       collectgarbage()
> -end))
> +end
>   
>   -- Compiled loop with a direct exit to the interpreter.
> -test:ok(testgetmetrics.snap_restores(function()
> -    jit.opt.start(0, "hotloop=1")
> +M.snap_restores_direct_exit = function()
> +    jit.opt.start(0, 'hotloop=1')
>   
>       local sum = 0
>       for i = 1, 20 do
> @@ -91,11 +84,11 @@ test:ok(testgetmetrics.snap_restores(function()
>   
>       -- A single snapshot restoration happened on loop finish.
>       return 1
> -end))
> +end
>   
>   -- Compiled loop with a side exit which does not get compiled.
> -test:ok(testgetmetrics.snap_restores(function()
> -    jit.opt.start(0, "hotloop=1", "hotexit=2", ("minstitch=%d"):format(MAXNINS))
> +M.snap_restores_side_exit_not_compiled = function()
> +    jit.opt.start(0, 'hotloop=1', 'hotexit=2', ('minstitch=%d'):format(MAXNINS))
>   
>       local function foo(i)
>           -- math.fmod is not yet compiled!
> @@ -112,13 +105,13 @@ test:ok(testgetmetrics.snap_restores(function()
>   
>       -- Side exits from the root trace could not get compiled.
>       return 5
> -end))
> +end
>   
>   -- Compiled loop with a side exit which gets compiled.
> -test:ok(testgetmetrics.snap_restores(function()
> +M.snap_restores_side_exit_compiled = function()
>       -- Optimization level is important here as `loop` optimization
>       -- may unroll the loop body and insert +1 side exit.
> -    jit.opt.start(0, "hotloop=1", "hotexit=5")
> +    jit.opt.start(0, 'hotloop=1', 'hotexit=5')
>   
>       local function foo(i)
>           return i <= 10 and i or tostring(i)
> @@ -136,13 +129,13 @@ test:ok(testgetmetrics.snap_restores(function()
>       -- and compiled
>       -- 1 side exit on loop end
>       return 6
> -end))
> +end
>   
>   -- Compiled scalar trace with a direct exit to the interpreter.
> -test:ok(testgetmetrics.snap_restores(function()
> +M.snap_restores_direct_exit_scalar = function()
>       -- For calls it will be 2 * hotloop (see lj_dispatch.{c,h}
>       -- and hotcall@vm_*.dasc).
> -    jit.opt.start(3, "hotloop=2", "hotexit=3")
> +    jit.opt.start(3, 'hotloop=2', 'hotexit=3')
>   
>       local function foo(i)
>           return i <= 15 and i or tostring(i)
> @@ -167,15 +160,15 @@ test:ok(testgetmetrics.snap_restores(function()
>       jit.opt.start(unpack(jit_opt_default))
>   
>       return 2
> -end))
> +end
>   
> -test:ok(testgetmetrics.strhash())
> -
> -test:ok(testgetmetrics.tracenum_base(function()
> +M.tracenum_base = function()
>       local sum = 0
>       for i = 1, 200 do
>           sum = sum + i
>       end
>       -- Compiled only 1 loop as new trace.
>       return 1
> -end))
> +end
> +
> +return M
> diff --git a/test/tarantool-c-tests/misclib-getmetrics-capi.test.c b/test/tarantool-c-tests/misclib-getmetrics-capi.test.c
> new file mode 100644
> index 00000000..724b551a
> --- /dev/null
> +++ b/test/tarantool-c-tests/misclib-getmetrics-capi.test.c
> @@ -0,0 +1,343 @@
> +#include "lua.h"
> +#include "luajit.h"
> +#include "lauxlib.h"
> +#include "lmisclib.h"
> +
> +#include "test.h"
> +#include "utils.h"
> +
> +/* Need for skipcond for BSD and JIT. */
> +#include "lj_arch.h"
> +
> +static int base(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Metrics metrics;
> +	luaM_metrics(L, &metrics);
> +
> +	/*
> +	 * Just check structure format, not values that fields
> +	 * contain.
> +	 */
> +	(void)metrics.strhash_hit;
> +	(void)metrics.strhash_miss;
> +
> +	(void)metrics.gc_strnum;
> +	(void)metrics.gc_tabnum;
> +	(void)metrics.gc_udatanum;
> +	(void)metrics.gc_cdatanum;
> +
> +	(void)metrics.gc_total;
> +	(void)metrics.gc_freed;
> +	(void)metrics.gc_allocated;
> +
> +	(void)metrics.gc_steps_pause;
> +	(void)metrics.gc_steps_propagate;
> +	(void)metrics.gc_steps_atomic;
> +	(void)metrics.gc_steps_sweepstring;
> +	(void)metrics.gc_steps_sweep;
> +	(void)metrics.gc_steps_finalize;
> +
> +	(void)metrics.jit_snap_restore;
> +	(void)metrics.jit_trace_abort;
> +	(void)metrics.jit_mcode_size;
> +	(void)metrics.jit_trace_num;
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int gc_allocated_freed(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Metrics oldm, newm;
> +	/* Force up garbage collect all dead objects. */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +
> +	luaM_metrics(L, &oldm);
> +	/* Simple garbage generation. */
> +	if (luaL_dostring(L, "local i = 0 for j = 1, 10 do i = i + j end"))
> +		bail_out("failed to translate Lua code snippet");
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	luaM_metrics(L, &newm);
> +	assert_true(newm.gc_allocated - oldm.gc_allocated > 0);
> +	assert_true(newm.gc_freed - oldm.gc_freed > 0);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int gc_steps(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Metrics oldm, newm;
> +	/*
> +	 * Some garbage has already happened before the next line,
> +	 * i.e. during frontend processing Lua test chunk.
> +	 * Let's put a full garbage collection cycle on top
> +	 * of that, and confirm that non-null values are reported
> +	 * (we are not yet interested in actual numbers):
> +	 */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +
> +	luaM_metrics(L, &oldm);
> +	assert_true(oldm.gc_steps_pause > 0);
> +	assert_true(oldm.gc_steps_propagate > 0);
> +	assert_true(oldm.gc_steps_atomic > 0);
> +	assert_true(oldm.gc_steps_sweepstring > 0);
> +	assert_true(oldm.gc_steps_sweep > 0);
> +	/* Nothing to finalize, skipped. */
> +	assert_true(oldm.gc_steps_finalize == 0);
> +
> +	/*
> +	 * As long as we don't create new Lua objects
> +	 * consequent call should return the same values:
> +	 */
> +	luaM_metrics(L, &newm);
> +	assert_sizet_equal(newm.gc_steps_pause, oldm.gc_steps_pause);
> +	assert_sizet_equal(newm.gc_steps_propagate, oldm.gc_steps_propagate);
> +	assert_sizet_equal(newm.gc_steps_atomic, oldm.gc_steps_atomic);
> +	assert_sizet_equal(newm.gc_steps_sweepstring,
> +			   oldm.gc_steps_sweepstring);
> +	assert_sizet_equal(newm.gc_steps_sweep, oldm.gc_steps_sweep);
> +	/* Nothing to finalize, skipped. */
> +	assert_true(newm.gc_steps_finalize == 0);
> +	oldm = newm;
> +
> +	/*
> +	 * Now the last phase: run full GC once and make sure that
> +	 * everything is being reported as expected:
> +	 */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	luaM_metrics(L, &newm);
> +	assert_true(newm.gc_steps_pause - oldm.gc_steps_pause == 1);
> +	assert_true(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1);
> +	assert_true(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1);
> +	assert_true(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1);
> +	assert_true(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1);
> +	/* Nothing to finalize, skipped. */
> +	assert_true(newm.gc_steps_finalize == 0);
> +	oldm = newm;
> +
> +	/*
> +	 * Now let's run three GC cycles to ensure that
> +	 * increment was not a lucky coincidence.
> +	 */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	luaM_metrics(L, &newm);
> +	assert_true(newm.gc_steps_pause - oldm.gc_steps_pause == 3);
> +	assert_true(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3);
> +	assert_true(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3);
> +	assert_true(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3);
> +	assert_true(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3);
> +	/* Nothing to finalize, skipped. */
> +	assert_true(newm.gc_steps_finalize == 0);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int objcount(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Metrics oldm, newm;
> +	if (!LJ_HASJIT)
> +		return skip("Test requires JIT enabled");
> +
> +	utils_get_aux_lfunc(L);
> +
> +	/* Force up garbage collect all dead objects. */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +
> +	luaM_metrics(L, &oldm);
> +	/* Generate garbage. Argument is iterations amount. */
> +	lua_pushnumber(L, 1000);
> +	lua_call(L, 1, 0);
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	luaM_metrics(L, &newm);
> +	assert_sizet_equal(newm.gc_strnum, oldm.gc_strnum);
> +	assert_sizet_equal(newm.gc_tabnum, oldm.gc_tabnum);
> +	assert_sizet_equal(newm.gc_udatanum, oldm.gc_udatanum);
> +	assert_sizet_equal(newm.gc_cdatanum, oldm.gc_cdatanum);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int objcount_cdata_decrement(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	/*
> +	 * cdata decrement test.
> +	 * See https://github.com/tarantool/tarantool/issues/5820.
> +	 */
> +	struct luam_Metrics oldm, newm;
> +	utils_get_aux_lfunc(L);
> +
> +	/* Force up garbage collect all dead objects. */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +
> +	luaM_metrics(L, &oldm);
> +	/*
> +	 * The function generates and collects cdata with
> +	 * LJ_GC_CDATA_FIN flag.
> +	 */
> +	lua_call(L, 0, 0);
> +	luaM_metrics(L, &newm);
> +	assert_sizet_equal(newm.gc_cdatanum, oldm.gc_cdatanum);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +/*
> + * Get function to call to generate the corresponding snapshot
> + * restores on top of the Lua stack. Function returns the amount
> + * of snapshot restorations expected.
> + * Clear stack after call.
> + */
> +static void check_snap_restores(lua_State *L)
> +{
> +	struct luam_Metrics oldm, newm;
> +	luaM_metrics(L, &oldm);
> +	/* Generate snapshots. */
> +	lua_call(L, 0, 1);
> +	int n = lua_gettop(L);
> +	/*
> +	 * The first value is the table with functions,
> +	 * the second is number of snapshot restores.
> +	 */
> +	if (n != 2 || !lua_isnumber(L, -1))
> +		bail_out("incorrect return value: 1 number is required");
> +	size_t snap_restores = lua_tonumber(L, -1);
> +	luaM_metrics(L, &newm);
> +	/*
> +	 * Remove `snap_restores` from stack.
> +	 * Must be done before potiential assert and exit from
> +	 * the test.
> +	 */
> +	lua_pop(L, 1);
> +	assert_true(newm.jit_snap_restore - oldm.jit_snap_restore
> +		    == snap_restores);
> +}
> +
> +static int snap_restores_direct_exit(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	check_snap_restores(L);
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int snap_restores_direct_exit_scalar(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	check_snap_restores(L);
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int snap_restores_side_exit_compiled(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	check_snap_restores(L);
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int snap_restores_side_exit_not_compiled(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	utils_get_aux_lfunc(L);
> +	check_snap_restores(L);
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int snap_restores_group(void *test_state)
> +{
> +	if (!LJ_HASJIT)
> +		return skip("Test requires JIT enabled");
> +	const struct test_unit tgroup[] = {
> +		test_unit_new(snap_restores_direct_exit),
> +		test_unit_new(snap_restores_direct_exit_scalar),
> +		test_unit_new(snap_restores_side_exit_compiled),
> +		test_unit_new(snap_restores_side_exit_not_compiled)
> +	};
> +	return test_run_group(tgroup, test_state);
> +}
> +
> +static int strhash(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	struct luam_Metrics oldm, newm;
> +	lua_pushstring(L, "strhash_hit");
> +	luaM_metrics(L, &oldm);
> +	lua_pushstring(L, "strhash_hit");
> +	lua_pushstring(L, "new_str");
> +	luaM_metrics(L, &newm);
> +	/* Remove pushed strings. */
> +	lua_pop(L, 3);
> +	assert_true(newm.strhash_hit - oldm.strhash_hit == 1);
> +	assert_true(newm.strhash_miss - oldm.strhash_miss == 1);
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +static int tracenum_base(void *test_state)
> +{
> +	lua_State *L = test_state;
> +	if (!LJ_HASJIT)
> +		return skip("Test requires JIT enabled");
> +	struct luam_Metrics metrics;
> +	utils_get_aux_lfunc(L);
> +
> +	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
> +	/* Force up garbage collect all dead objects. */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +
> +	luaM_metrics(L, &metrics);
> +	assert_true(metrics.jit_trace_num == 0);
> +
> +	/* Generate traces. */
> +	lua_call(L, 0, 1);
> +	int n = lua_gettop(L);
> +	/*
> +	 * The first value is the table with functions,
> +	 * the second is the amount of traces.
> +	 */
> +	if (n != 2 || !lua_isnumber(L, -1))
> +		bail_out("incorrect return value: 1 number is required");
> +	size_t jit_trace_num = lua_tonumber(L, -1);
> +	luaM_metrics(L, &metrics);
> +	/* Remove `jit_trace_num` from Lua stack. */
> +	lua_pop(L, 1);
> +
> +	assert_sizet_equal(metrics.jit_trace_num, jit_trace_num);
> +
> +	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
> +	/* Force up garbage collect all dead objects. */
> +	lua_gc(L, LUA_GCCOLLECT, 0);
> +	luaM_metrics(L, &metrics);
> +	assert_true(metrics.jit_trace_num == 0);
> +
> +	return TEST_EXIT_SUCCESS;
> +}
> +
> +int main(void)
> +{
> +	if (LUAJIT_OS == LUAJIT_OS_BSD)
> +		return skip_all("Disabled on *BSD due to #4819");
> +
> +	lua_State *L = utils_lua_init();
> +
> +	utils_load_aux_script(L, "misclib-getmetrics-capi-script.lua");
> +	const struct test_unit tgroup[] = {
> +		test_unit_new(base),
> +		test_unit_new(gc_allocated_freed),
> +		test_unit_new(gc_steps),
> +		test_unit_new(objcount),
> +		test_unit_new(objcount_cdata_decrement),
> +		test_unit_new(snap_restores_group),
> +		test_unit_new(strhash),
> +		test_unit_new(tracenum_base)
> +	};
> +	const int test_result = test_run_group(tgroup, L);
> +	utils_lua_close(L);
> +	return test_result;
> +}
> diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt
> index 38d6ae49..b4ce39d3 100644
> --- a/test/tarantool-tests/CMakeLists.txt
> +++ b/test/tarantool-tests/CMakeLists.txt
> @@ -66,7 +66,6 @@ add_subdirectory(lj-416-xor-before-jcc)
>   add_subdirectory(lj-601-fix-gc-finderrfunc)
>   add_subdirectory(lj-727-lightuserdata-itern)
>   add_subdirectory(lj-flush-on-trace)
> -add_subdirectory(misclib-getmetrics-capi)
>   add_subdirectory(misclib-sysprof-capi)
>   
>   # The part of the memory profiler toolchain is located in tools
> diff --git a/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt b/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
> deleted file mode 100644
> index 60eb5bbb..00000000
> --- a/test/tarantool-tests/misclib-getmetrics-capi/CMakeLists.txt
> +++ /dev/null
> @@ -1 +0,0 @@
> -BuildTestCLib(testgetmetrics testgetmetrics.c)
> diff --git a/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c b/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
> deleted file mode 100644
> index 67776338..00000000
> --- a/test/tarantool-tests/misclib-getmetrics-capi/testgetmetrics.c
> +++ /dev/null
> @@ -1,270 +0,0 @@
> -#include <lua.h>
> -#include <luajit.h>
> -#include <lauxlib.h>
> -
> -#include <lmisclib.h>
> -
> -#undef NDEBUG
> -#include <assert.h>
> -
> -static int base(lua_State *L)
> -{
> -	struct luam_Metrics metrics;
> -	luaM_metrics(L, &metrics);
> -
> -	/* Just check structure format, not values that fields contain. */
> -	(void)metrics.strhash_hit;
> -	(void)metrics.strhash_miss;
> -
> -	(void)metrics.gc_strnum;
> -	(void)metrics.gc_tabnum;
> -	(void)metrics.gc_udatanum;
> -	(void)metrics.gc_cdatanum;
> -
> -	(void)metrics.gc_total;
> -	(void)metrics.gc_freed;
> -	(void)metrics.gc_allocated;
> -
> -	(void)metrics.gc_steps_pause;
> -	(void)metrics.gc_steps_propagate;
> -	(void)metrics.gc_steps_atomic;
> -	(void)metrics.gc_steps_sweepstring;
> -	(void)metrics.gc_steps_sweep;
> -	(void)metrics.gc_steps_finalize;
> -
> -	(void)metrics.jit_snap_restore;
> -	(void)metrics.jit_trace_abort;
> -	(void)metrics.jit_mcode_size;
> -	(void)metrics.jit_trace_num;
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int gc_allocated_freed(lua_State *L)
> -{
> -	struct luam_Metrics oldm, newm;
> -	/* Force up garbage collect all dead objects. */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -
> -	luaM_metrics(L, &oldm);
> -	/* Simple garbage generation. */
> -	if (luaL_dostring(L, "local i = 0 for j = 1, 10 do i = i + j end"))
> -		luaL_error(L, "failed to translate Lua code snippet");
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_allocated - oldm.gc_allocated > 0);
> -	assert(newm.gc_freed - oldm.gc_freed > 0);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int gc_steps(lua_State *L)
> -{
> -	struct luam_Metrics oldm, newm;
> -	/*
> -	 * Some garbage has already happened before the next line,
> -	 * i.e. during frontend processing Lua test chunk.
> -	 * Let's put a full garbage collection cycle on top
> -	 * of that, and confirm that non-null values are reported
> -	 * (we are not yet interested in actual numbers):
> -	 */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -
> -	luaM_metrics(L, &oldm);
> -	assert(oldm.gc_steps_pause > 0);
> -	assert(oldm.gc_steps_propagate > 0);
> -	assert(oldm.gc_steps_atomic > 0);
> -	assert(oldm.gc_steps_sweepstring > 0);
> -	assert(oldm.gc_steps_sweep > 0);
> -	/* Nothing to finalize, skipped. */
> -	assert(oldm.gc_steps_finalize == 0);
> -
> -	/*
> -	 * As long as we don't create new Lua objects
> -	 * consequent call should return the same values:
> -	 */
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 0);
> -	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate == 0);
> -	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 0);
> -	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring == 0);
> -	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep == 0);
> -	/* Nothing to finalize, skipped. */
> -	assert(newm.gc_steps_finalize == 0);
> -	oldm = newm;
> -
> -	/*
> -	 * Now the last phase: run full GC once and make sure that
> -	 * everything is being reported as expected:
> -	 */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 1);
> -	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1);
> -	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1);
> -	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1);
> -	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1);
> -	/* Nothing to finalize, skipped. */
> -	assert(newm.gc_steps_finalize == 0);
> -	oldm = newm;
> -
> -	/*
> -	 * Now let's run three GC cycles to ensure that
> -	 * increment was not a lucky coincidence.
> -	 */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_steps_pause - oldm.gc_steps_pause == 3);
> -	assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3);
> -	assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3);
> -	assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3);
> -	assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3);
> -	/* Nothing to finalize, skipped. */
> -	assert(newm.gc_steps_finalize == 0);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int objcount(lua_State *L)
> -{
> -	struct luam_Metrics oldm, newm;
> -	int n = lua_gettop(L);
> -	if (n != 1 || !lua_isfunction(L, 1))
> -		luaL_error(L, "incorrect argument: 1 function is required");
> -
> -	/* Force up garbage collect all dead objects. */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -
> -	luaM_metrics(L, &oldm);
> -	/* Generate garbage. Argument is iterations amount. */
> -	lua_pushnumber(L, 1000);
> -	lua_call(L, 1, 0);
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_strnum - oldm.gc_strnum == 0);
> -	assert(newm.gc_tabnum - oldm.gc_tabnum == 0);
> -	assert(newm.gc_udatanum - oldm.gc_udatanum == 0);
> -	assert(newm.gc_cdatanum - oldm.gc_cdatanum == 0);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int objcount_cdata_decrement(lua_State *L)
> -{
> -	/*
> -	 * cdata decrement test.
> -	 * See https://github.com/tarantool/tarantool/issues/5820.
> -	 */
> -	struct luam_Metrics oldm, newm;
> -	int n = lua_gettop(L);
> -	if (n != 1 || !lua_isfunction(L, 1))
> -		luaL_error(L, "incorrect argument: 1 function is required");
> -
> -	/* Force up garbage collect all dead objects. */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -
> -	luaM_metrics(L, &oldm);
> -	/*
> -	 * The function generates and collects cdata with
> -	 * LJ_GC_CDATA_FIN flag.
> -	 */
> -	lua_call(L, 0, 0);
> -	luaM_metrics(L, &newm);
> -	assert(newm.gc_cdatanum - oldm.gc_cdatanum == 0);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int snap_restores(lua_State *L)
> -{
> -	struct luam_Metrics oldm, newm;
> -	int n = lua_gettop(L);
> -	if (n != 1 || !lua_isfunction(L, 1))
> -		luaL_error(L, "incorrect arguments: 1 function is required");
> -
> -	luaM_metrics(L, &oldm);
> -	/* Generate snapshots. */
> -	lua_call(L, 0, 1);
> -	n = lua_gettop(L);
> -	if (n != 1 || !lua_isnumber(L, 1))
> -		luaL_error(L, "incorrect return value: 1 number is required");
> -	size_t snap_restores = lua_tonumber(L, 1);
> -	luaM_metrics(L, &newm);
> -	assert(newm.jit_snap_restore - oldm.jit_snap_restore == snap_restores);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int strhash(lua_State *L)
> -{
> -	struct luam_Metrics oldm, newm;
> -	lua_pushstring(L, "strhash_hit");
> -	luaM_metrics(L, &oldm);
> -	lua_pushstring(L, "strhash_hit");
> -	lua_pushstring(L, "new_str");
> -	luaM_metrics(L, &newm);
> -	assert(newm.strhash_hit - oldm.strhash_hit == 1);
> -	assert(newm.strhash_miss - oldm.strhash_miss == 1);
> -	lua_pop(L, 3);
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static int tracenum_base(lua_State *L)
> -{
> -	struct luam_Metrics metrics;
> -	int n = lua_gettop(L);
> -	if (n != 1 || !lua_isfunction(L, 1))
> -		luaL_error(L, "incorrect arguments: 1 function is required");
> -
> -	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
> -	/* Force up garbage collect all dead objects. */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -
> -	luaM_metrics(L, &metrics);
> -	assert(metrics.jit_trace_num == 0);
> -
> -	/* Generate traces. */
> -	lua_call(L, 0, 1);
> -	n = lua_gettop(L);
> -	if (n != 1 || !lua_isnumber(L, 1))
> -		luaL_error(L, "incorrect return value: 1 number is required");
> -	size_t jit_trace_num = lua_tonumber(L, 1);
> -	luaM_metrics(L, &metrics);
> -	assert(metrics.jit_trace_num == jit_trace_num);
> -
> -	luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
> -	/* Force up garbage collect all dead objects. */
> -	lua_gc(L, LUA_GCCOLLECT, 0);
> -	luaM_metrics(L, &metrics);
> -	assert(metrics.jit_trace_num == 0);
> -
> -	lua_pushboolean(L, 1);
> -	return 1;
> -}
> -
> -static const struct luaL_Reg testgetmetrics[] = {
> -	{"base", base},
> -	{"gc_allocated_freed", gc_allocated_freed},
> -	{"gc_steps", gc_steps},
> -	{"objcount", objcount},
> -	{"objcount_cdata_decrement", objcount_cdata_decrement},
> -	{"snap_restores", snap_restores},
> -	{"strhash", strhash},
> -	{"tracenum_base", tracenum_base},
> -	{NULL, NULL}
> -};
> -
> -LUA_API int luaopen_testgetmetrics(lua_State *L)
> -{
> -	luaL_register(L, "testgetmetrics", testgetmetrics);
> -	return 1;
> -}

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2023-06-07 16:25 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-05 10:41 [Tarantool-patches] [PATCH v3 luajit 0/6] Reworking C tests Sergey Kaplun via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 1/6] test: fix setting of {DY}LD_LIBRARY_PATH variables Sergey Kaplun via Tarantool-patches
2023-06-05 14:21   ` Sergey Bronnikov via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 2/6] test: introduce module for C tests Sergey Kaplun via Tarantool-patches
2023-06-05 15:08   ` Sergey Bronnikov via Tarantool-patches
2023-06-07 15:51     ` Sergey Bronnikov via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 3/6] test: introduce utils.h helper " Sergey Kaplun via Tarantool-patches
2023-06-05 15:11   ` Sergey Bronnikov via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 4/6] test: rewrite misclib-getmetrics-capi test in C Sergey Kaplun via Tarantool-patches
2023-06-07 16:25   ` Sergey Bronnikov via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 5/6] test: rewrite misclib-sysprof-capi " Sergey Kaplun via Tarantool-patches
2023-06-07 16:18   ` Sergey Bronnikov via Tarantool-patches
2023-06-05 10:41 ` [Tarantool-patches] [PATCH v3 luajit 6/6] test: rewrite lj-49-bad-lightuserdata " Sergey Kaplun via Tarantool-patches
2023-06-07 16:14   ` Sergey Bronnikov via Tarantool-patches

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox