From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 14AAD111A004; Tue, 19 May 2026 15:40:31 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 14AAD111A004 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1779194431; bh=7O68/V0qhZCmG0RdkvNl2GodiIOLzRE5aljTr0YNxzE=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=hY2XQY8P6BtQtBCo7VL5USg5n5eith6O2hX/3cdHW0pluCdxY7zwxDSlwllJTTJMa zeeGvmnRQaxsIyx06qKw0KPBbHMLXyFNYGpPpLJNBOvZ7h29k6gIGbtsxyNhjVQy20 3Et2+z7lPUIXaKzM3Xj4mnvvjE4JQXl1otUAdOMQ= Received: from send151.i.mail.ru (send151.i.mail.ru [89.221.237.246]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 1D1071711D84 for ; Tue, 19 May 2026 15:40:00 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 1D1071711D84 Received: by exim-smtp-8466d5ddfc-zbcjt with esmtpa (envelope-from ) id 1wPJjq-00000000Fgc-3qzV; Tue, 19 May 2026 15:39:59 +0300 To: Mikhail Elhimov , Sergey Bronnikov , Evgeniy Temirgaleev Date: Tue, 19 May 2026 15:39:08 +0300 Message-ID: <20260519123913.178775-2-skaplun@tarantool.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260519123913.178775-1-skaplun@tarantool.org> References: <20260519123913.178775-1-skaplun@tarantool.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Mailru-Src: smtp X-618D5548: A9EA38CDAD7F3561411046492FDDF80644A8F7931E96C4B210B0A4238973836AB2B36B5C8FB518A2 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD98DC58EE55F2C5C374A7150980F231CEB4594521187ABD14F00894C459B0CD1B9A9EA38CDAD7F3561411046492FDDF806CB1F5537C56E1ED410B0A4238973836A09DF98A5C5F6EC85 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE72407438AC6002944EA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637AC83A81C8FD4AD23D82A6BABE6F325AC2E85FA5F3EDFCBAA7353EFBB55337566BAD382DE51689651EE0983EFA1057A78E6297AC5CE0D9CFD2A06C1CB094850EA389733CBF5DBD5E913377AFFFEAFD269176DF2183F8FC7C0ECC8AC47CD0EDEFF8941B15DA834481FCF19DD082D7633A0EF3E4896CB9E6436389733CBF5DBD5E9D5E8D9A59859A8B65FF0BFC5AEE34BE6CC7F00164DA146DA6F5DAA56C3B73B237318B6A418E8EAB86D1867E19FE14079C09775C1D3CA48CF17B107DEF921CE791DD303D21008E298D5E8D9A59859A8B6D082881546D9349175ECD9A6C639B01B78DA827A17800CE71FE705C1FB16C834731C566533BA786AA5CC5B56E945C8DA X-87b9d050: 1 X-C1DE0DAB: 0D63561A33F958A51DE6AB0E2ABBA7935002B1117B3ED696FFAEC515FC23D4675B6221DB6D7A72AD823CB91A9FED034534781492E4B8EEADC0A73878EBD0941BC79554A2A72441328621D336A7BC284946AD531847A6065A535571D14F44ED41 X-C8649E89: 1C3962B70DF3F0ADB58128AB1E6D661A716CD42B3DD1D34C77DD89D51EBB774225B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D6590EDDE8513BEF529C94DD0B1C9233D11E1A7AB5213AA710B6B2290E3EB99D15BC45B8AA78983D4555B8341EE9D5BE9A0A5D79196A75D1BDDC4B43B2A6B51D093C6070FDA3D3468D059350E1BD188BD56E4C41F94D744909CECFA6C6B0C050A61A8CAF69B82BA93681CD72808BE417F3B9E0E7457915DAA85F X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVax5M0mESuWmhV4QYLDnWj0= X-DA7885C5: 5A5AF583B520AFECF255D290C0D534F954E41C758B1A2780FF0172837B87EF8B2813DE7160DC1BFD5B1A4C17EAA7BC4BEF2421ABFA55128DAF83EF9164C44C7E X-Mailru-Sender: 689FA8AB762F7393520AF17B8A65FDE260B018529FDEBE53228D337503C33EE5735B1744EA728255E49D44BB4BD9522A059A1ED8796F048DB274557F927329BE89D5A3BC2B10C37545BD1C3CC395C826B4A721A3011E896F X-Mras: Ok Subject: [Tarantool-patches] [PATCH v2 luajit 1/6] test: introduce tests for debugging extensions X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Sergey Kaplun via Tarantool-patches Reply-To: Sergey Kaplun Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" From: Maxim Kokryashkin This patch adds tests for LuaJIT debugging extensions for lldb and gdb. The tests are written in Python's unittest framework [1]. Most of the tests are failed for the lldb due to outdated extension sources and overcomplicated hard-coded C structures fields introspection. Hence, tests for LLDB are disabled since they are failing anyway. The tarantool-debugger-tests target is introduced. This target is included in the LuaJIT-check-all target but not in the LuaJIT-test target to avoid it running for all LuaJIT builds by default in CI. [1]: https://docs.python.org/3/library/unittest.html --- test/CMakeLists.txt | 7 + test/tarantool-debugger-tests/CMakeLists.txt | 93 ++++++ .../debug-extension-tests.py | 286 ++++++++++++++++++ 3 files changed, 386 insertions(+) create mode 100644 test/tarantool-debugger-tests/CMakeLists.txt create mode 100644 test/tarantool-debugger-tests/debug-extension-tests.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f48afa25..26b15892 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -175,6 +175,7 @@ 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-debugger-tests) add_subdirectory(tarantool-tests) # Each testsuite has its own CMake target, but combining these @@ -186,6 +187,9 @@ add_subdirectory(tarantool-tests) # command that runs all generated CMake tests. add_custom_target(${PROJECT_NAME}-test COMMAND ${CMAKE_CTEST_COMMAND} ${CTEST_FLAGS} + # Omit this target in LuaJIT-test since we don't want to set + # up and run debuggers for every build. + --label-exclude tarantool-debugger-tests DEPENDS tarantool-c-tests-deps tarantool-tests-deps lua-Harness-tests-deps @@ -195,5 +199,8 @@ add_custom_target(${PROJECT_NAME}-test add_custom_target(${PROJECT_NAME}-check-all DEPENDS ${PROJECT_NAME}-test + # Omit this target in LuaJIT-test since we don't want to + # set up and run debuggers for every build. + tarantool-debugger-tests ${PROJECT_NAME}-lint ) diff --git a/test/tarantool-debugger-tests/CMakeLists.txt b/test/tarantool-debugger-tests/CMakeLists.txt new file mode 100644 index 00000000..7fd0debc --- /dev/null +++ b/test/tarantool-debugger-tests/CMakeLists.txt @@ -0,0 +1,93 @@ +set(TEST_SUITE_NAME "tarantool-debugger-tests") + +# XXX: The call produces both test and target +# as a side effect. +add_test_suite_target(tarantool-debugger-tests + LABELS ${TEST_SUITE_NAME} + DEPENDS ${LUAJIT_TEST_BINARY} +) + +# Debug info is required for testing of extensions. +if(NOT (CMAKE_BUILD_TYPE MATCHES Debug)) + message(WARNING + "Not a DEBUG build, tarantool-debugger-tests is dummy" + ) + return() +endif() + +# MacOS asks for permission to debug a process even when the +# machine is set into development mode. To solve the issue, +# it is required to add relevant users to the `_developer` user +# group in macOS. Disabled for now. +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND DEFINED ENV{CI}) + message(WARNING + "Interactive debugging is unavailable for macOS CI builds," + " tarantool-debugger-tests is dummy" + ) + return() +endif() + +if(CMAKE_VERSION VERSION_LESS "3.12") + # TODO:Can remove this after upgrading to CMake >= 3.12. + find_package(PythonInterp) + if(NOT PYTHONINTERP_FOUND) + message(WARNING "`python` is not found, tarantool-debugger-tests is dummy") + return() + endif() +else() + find_package(Python COMPONENTS Interpreter) + if(NOT PYTHON_FOUND) + message(WARNING "`python` is not found, tarantool-debugger-tests is dummy") + return() + endif() + set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}") +endif() + +set(DEBUGGER_TEST_ENV + "LUAJIT_TEST_BINARY=${LUAJIT_TEST_BINARY}" + # Suppresses __pycache__ generation. + "PYTHONDONTWRITEBYTECODE=1" + "DEBUGGER_EXTENSION_PATH=${PROJECT_SOURCE_DIR}/src" +) + +set(TEST_SCRIPT_PATH + ${CMAKE_CURRENT_SOURCE_DIR}/debug-extension-tests.py +) + +find_program(GDB gdb) +if(GDB) + set(test_title "test/${TEST_SUITE_NAME}/gdb") + set(GDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${GDB}") + add_test(NAME "${test_title}" + COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties("${test_title}" PROPERTIES + ENVIRONMENT "${GDB_TEST_ENV}" + LABELS ${TEST_SUITE_NAME} + DEPENDS tarantool-debugger-tests-deps + ) +else() + message(WARNING + "`gdb' is not found, so tarantool-debugger-tests/gdb is omitted" + ) +endif() + +find_program(LLDB lldb) +if(LLDB) + set(test_title "test/${TEST_SUITE_NAME}/lldb") + set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${LLDB}") + add_test(NAME "${test_title}" + COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties("${test_title}" PROPERTIES + ENVIRONMENT "${LLDB_TEST_ENV}" + LABELS ${TEST_SUITE_NAME} + DEPENDS tarantool-debugger-tests-deps + ) +else() + message(WARNING + "`lldb' is not found, so tarantool-debugger-tests/lldb is omitted" + ) +endif() diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py new file mode 100644 index 00000000..6094c535 --- /dev/null +++ b/test/tarantool-debugger-tests/debug-extension-tests.py @@ -0,0 +1,286 @@ +# This file provides tests for LuaJIT debug extensions for lldb +# and gdb. + +import os +import re +import subprocess +import sys +import tempfile +import unittest + +from threading import Timer + +LEGACY = re.match(r'^2\.', sys.version) + +LUAJIT_BINARY = os.environ['LUAJIT_TEST_BINARY'] +EXTENSION_PATH = os.environ['DEBUGGER_EXTENSION_PATH'] +DEBUGGER = os.environ['DEBUGGER_COMMAND'] +LLDB = 'lldb' in DEBUGGER +EXTENSION = EXTENSION_PATH + ('/luajit_lldb.py' if LLDB else '/luajit-gdb.py') +TIMEOUT = 10 + +# Don't run any initialization scripts. +RUN_CMD_FILE = [] + +if LLDB: + RUN_CMD_FILE = [ + '--batch', + '--no-lldbinit', + '--no-use-colors', + '--source-quietly', + '--source' + ] +else: + # GDB. + RUN_CMD_FILE = ['--batch', '--nx', '--quiet', '--command'] + +INFERIOR_ARGS = '--' if LLDB else '--args' +PROCESS_RUN = 'process launch' if LLDB else 'r' +LOAD_EXTENSION = ( + 'command script import {ext}' if LLDB else 'source {ext}' +).format(ext=EXTENSION) + + +RX_ADDR = r'0x[a-f0-9]+' +RX_HASH = RX_ADDR # The same pattern for hexademic values. +RX_FRAME = r'\[(S|\s)(B|\s)(T|\s)(M|\s)\]' + + +def persist(data): + tmp = tempfile.NamedTemporaryFile(mode='w') + tmp.write(data) + tmp.flush() + return tmp + + +def execute_process(cmd, timeout=TIMEOUT): + if LEGACY: + # XXX: The Python 2.7 version of `subprocess.Popen` + # doesn't have a timeout option, so the required + # functionality was implemented via `threading.Timer`. + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + # This prevents sending of SIGSTTOU to the test when + # running by `make'. Stdin is unused anyway. + stdin=subprocess.DEVNULL + ) + timer = Timer(TIMEOUT, process.kill) + timer.start() + stdout, _ = process.communicate() + timer.cancel() + + # XXX: If the timeout is exceeded and the process is + # killed by the timer, then the return code is non-zero, + # and we are going to blow up. + assert process.returncode == 0 + return stdout.decode('ascii') + else: + process = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + # This prevents sending of SIGSTTOU to the test when + # running by `make'. Stdin is unused anyway. + stdin=subprocess.DEVNULL, + universal_newlines=True, + timeout=TIMEOUT + ) + return process.stdout + + +class TestCaseBase(unittest.TestCase): + @classmethod + def construct_cmds(cls): + return '\n'.join([ + 'b {loc}'.format(loc=cls.location), + PROCESS_RUN, + 'n', + LOAD_EXTENSION, + cls.extension_cmds.strip(), + 'q', + ]) + + @classmethod + def setUpClass(cls): + cmd_file = persist(cls.construct_cmds()) + script_file = persist(cls.lua_script) + process_cmd = [ + DEBUGGER, + *RUN_CMD_FILE, + cmd_file.name, + INFERIOR_ARGS, + LUAJIT_BINARY, + script_file.name, + ] + cls.output = execute_process(process_cmd) + cmd_file.close() + script_file.close() + + def check(self): + if LEGACY: + self.assertRegexpMatches(self.output, self.pattern.strip()) + else: + self.assertRegex(self.output, self.pattern.strip()) + + +class TestLoad(TestCaseBase): + extension_cmds = '' + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = ( + r'lj-arch command initialized\n' + r'lj-tv command initialized\n' + r'lj-str command initialized\n' + r'lj-tab command initialized\n' + r'lj-stack command initialized\n' + r'lj-state command initialized\n' + r'lj-gc command initialized\n' + r'.*is successfully loaded' + ) + + +class TestLJArch(TestCaseBase): + extension_cmds = 'lj-arch' + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = ( + r'LJ_64: (True|False), ' + r'LJ_GC64: (True|False), ' + r'LJ_DUALNUM: (True|False)' + ) + + +class TestLJState(TestCaseBase): + extension_cmds = 'lj-state' + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = ( + r'VM state: [A-Z]+\n' + r'GC state: [A-Z]+\n' + r'JIT state: [A-Z]+\n' + ) + + +class TestLJGC(TestCaseBase): + extension_cmds = 'lj-gc' + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = ( + r'GC stats: [A-Z]+\n' + r'\ttotal: \d+\n' + r'\tthreshold: \d+\n' + r'\tdebt: \d+\n' + r'\testimate: \d+\n' + r'\tstepmul: \d+\n' + r'\tpause: \d+\n' + r'\tsweepstr: \d+/\d+\n' + r'\troot: \d+ objects\n' + r'\tgray: \d+ objects\n' + r'\tgrayagain: \d+ objects\n' + r'\tweak: \d+ objects\n' + r'\tmmudata: \d+ objects\n' + ) + + +class TestLJStack(TestCaseBase): + extension_cmds = 'lj-stack' + location = 'lj_cf_print' + lua_script = 'print(1)' + pattern = ( + r'-+ Red zone:\s+\d+ slots -+\n' + r'(' + RX_ADDR + r'\s+' + RX_FRAME + r' VALUE: nil\n?)*\n' + r'-+ Stack:\s+\d+ slots -+\n' + r'(' + RX_ADDR + r'(:' + RX_ADDR + r')?\s+' + RX_FRAME + r'.*\n?)+\n' + ) + + +class TestLJTV(TestCaseBase): + location = 'lj_cf_print' + extension_cmds = ( + 'lj-tv L->base\n' + 'lj-tv L->base + 1\n' + 'lj-tv L->base + 2\n' + 'lj-tv L->base + 3\n' + 'lj-tv L->base + 4\n' + 'lj-tv L->base + 5\n' + 'lj-tv L->base + 6\n' + 'lj-tv L->base + 7\n' + 'lj-tv L->base + 8\n' + 'lj-tv L->base + 9\n' + 'lj-tv L->base + 10\n' + 'lj-tv L->base + 11\n' + ) + + lua_script = ( + 'local ffi = require("ffi")\n' + 'print(\n' + ' nil,\n' + ' false,\n' + ' true,\n' + ' "hello",\n' + ' {1},\n' + ' 1,\n' + ' 1.1,\n' + ' coroutine.create(function() end),\n' + ' ffi.new("int*"),\n' + ' function() end,\n' + ' print,\n' + ' require\n' + ')\n' + ) + + pattern = ( + r'nil\n' + r'false\n' + r'true\n' + r'string \"hello\" @ ' + RX_ADDR + r'\n' + r'table @ ' + RX_ADDR + r' \(asize: \d+, hmask: ' + RX_HASH + r'\)\n' + r'(number|integer) .*1.*\n' + r'number 1.1\d+\n' + r'thread @ ' + RX_ADDR + r'\n' + r'cdata @ ' + RX_ADDR + r'\n' + r'Lua function @ ' + RX_ADDR + r', [0-9]+ upvalues, .+:[0-9]+\n' + r'fast function #[0-9]+\n' + r'C function @ ' + RX_ADDR + r'\n' + ) + + +class TestLJStr(TestCaseBase): + extension_cmds = ( + # XXX: Get the value to the stack slot for the variable. + 'n\n' + 'lj-str fname\n' + ) + location = 'lj_cf_dofile' + lua_script = 'pcall(dofile("name"))' + pattern = r'String: .* \[\d+ bytes\] with hash ' + RX_HASH + + +class TestLJTab(TestCaseBase): + extension_cmds = ( + # XXX: Get the value to the stack slot for the variable. + 'n\n' + 'lj-tab t\n' + ) + location = 'lj_cf_unpack' + lua_script = 'unpack({1; a = 1})' + pattern = ( + r'Array part: 3 slots\n' + + RX_ADDR + r': \[0\]: nil\n' + + RX_ADDR + r': \[1\]: .+ 1\n' + + RX_ADDR + r': \[2\]: nil\n' + + r'Hash part: 2 nodes\n' + + RX_ADDR + r': { string "a" @ ' + RX_ADDR + r' } => ' + + r'{ .+ 1 }; next = 0x0\n' + + RX_ADDR + r': { nil } => { nil }; next = 0x0\n' + ) + + +for test_cls in TestCaseBase.__subclasses__(): + test_cls.test = lambda self: self.check() + +# FIXME: skip for LLDB since most commands are not working anyway. +if __name__ == '__main__' and not LLDB: + unittest.main(verbosity=2) -- 2.53.0