Hi, Sergey! Thanks for the patch. Please, consider my suggestion: the debugger-aware «constants» are defined in one block per debugger. diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py index 30a2c478..f4414f4d 100644 --- a/test/tarantool-debugger-tests/debug-extension-tests.py +++ b/test/tarantool-debugger-tests/debug-extension-tests.py @@ -19,9 +19,6 @@ LLDB = 'lldb' in DEBUGGER EXTENSION = EXTENSION_PATH + '/luajit_dbg.py' TIMEOUT = 10 -# Don't run any initialization scripts. -RUN_CMD_FILE = [] - if LLDB: RUN_CMD_FILE = [ '--batch', @@ -30,15 +27,15 @@ if LLDB: '--source-quietly', '--source' ] +    INFERIOR_ARGS = '--' +    PROCESS_RUN = 'process launch' +    LOAD_EXTENSION = 'command script import {ext}'.format(ext=EXTENSION) 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) +    INFERIOR_ARGS = '--args' +    PROCESS_RUN = 'r' +    LOAD_EXTENSION = 'source {ext}'.format(ext=EXTENSION) -- Best regards, Evgeniy Temirgaleev > > From: Sergey Kaplun > To: Mikhail Elhimov , Sergey Bronnikov >, Evgeniy Temirgaleev > Cc: tarantool-patches@dev.tarantool.org, Sergey Kaplun > > Date: Tuesday, May 19, 2026 3:40 PM +03:00 > 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 >