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 386076ECCC; Wed, 20 May 2026 16:38:58 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 386076ECCC DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1779284338; bh=5W5G50k18dGv6bdBagi2q/bSaxtd8MSMAK7M+XKF4Qs=; h=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc: From; b=l638Rw8qitD5Ewpj+9aqNG0VNIiau2RNE+xw83RgMl7+43ju9TQBC+X+Hz784Alda 91vY4D7mm+9g1mpsAjhIfvlCnNT+hF9TzK1O12MbYLkFw2HkNIl/MGdPN6GxDU96Zu 4bMBmvilF094Ont2qPe8lg1CY41jzBzupPN5rzq0= Received: from send126.i.mail.ru (send126.i.mail.ru [89.221.237.221]) (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 D75BD6ECCC for ; Wed, 20 May 2026 16:38:56 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org D75BD6ECCC Received: by exim-smtp-8466d5ddfc-zbcjt with esmtpa (envelope-from ) id 1wPh8R-00000000Xpu-1q2o; Wed, 20 May 2026 16:38:56 +0300 Content-Type: multipart/alternative; boundary="------------Vc5kJg1GEdOrwPQ11Phq8Y0l" Message-ID: <3703735e-192b-4e3f-8842-2cff7a61e725@tarantool.org> Date: Wed, 20 May 2026 16:38:54 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird To: Sergey Kaplun References: <20260519123913.178775-1-skaplun@tarantool.org> <20260519123913.178775-2-skaplun@tarantool.org> Content-Language: en-US In-Reply-To: <20260519123913.178775-2-skaplun@tarantool.org> X-Mailru-Src: smtp X-4EC0790: 10 X-7564579A: 646B95376F6C166E X-77F55803: 4F1203BC0FB41BD98DC58EE55F2C5C375ED019E89806C63C68B913310D02212300894C459B0CD1B9339A0BEF1F3E8CF77A388D641E84891FAA4905A3B33FC80F80D6B8F74B06CF2F6C831481B6BD6C10 X-7FA49CB5: FF5795518A3D127A4AD6D5ED66289B5278DA827A17800CE79FF7180C05A1FF7CEA1F7E6F0F101C67BD4B6F7A4D31EC0BCC500DACC3FED6E28638F802B75D45FF8AA50765F7900637F88016AB904663428638F802B75D45FF914D58D5BE9E6BC1A93B80C6DEB9DEE97C6FB206A91F05B2450227111B893C902E070BE324C7D3C4C0190C160425A1CEF6B57BC7E64490618DEB871D839B73339E8FC8737B5C2249D082881546D93491CC7F00164DA146DAFE8445B8C89999729449624AB7ADAF37F6B57BC7E64490611E7FA7ABCAF51C92176DF2183F8FC7C0ECC8AC47CD0EDEFF8941B15DA834481F9449624AB7ADAF372E808ACE2090B5E14AD6D5ED66289B5259CC434672EE63711DD303D21008E298D5E8D9A59859A8B6B372FE9A2E580EFC725E5C173C3A84C3271F372ED763686635872C767BF85DA2F004C90652538430E4A6367B16DE6309 X-C1DE0DAB: 0D63561A33F958A535020FF35D26BAE85002B1117B3ED696CD37BC8DD3631A109E040399BDE4761E823CB91A9FED034534781492E4B8EEAD1ED1D7C9C0E78937BDAD6C7F3747799A X-C8649E89: 1C3962B70DF3F0ADB58128AB1E6D661A716CD42B3DD1D34CAB70F9BE574AE9C625B6776AC983F447FC0B9F89525902EE6F57B2FD27647F25E66C117BDB76D6597D51827D8BD60A94821F56A6D8A22FE6A032C498F710821180C67D289AF89774686B489405E63487B8341EE9D5BE9A0A750562063548561AB5BC87267C95936BB70DA6C5DF6B0F2AC7CEAA0681F5848F4C41F94D744909CE2512F26BEC029E55448553D2254B8D95CD72808BE417F3B9E0E7457915DAA85F X-D57D3AED: 3ZO7eAau8CL7WIMRKs4sN3D3tLDjz0dLbV79QFUyzQ2Ujvy7cMT6pYYqY16iZVKkSc3dCLJ7zSJH7+u4VD18S7Vl4ZUrpaVfd2+vE6kuoey4m4VkSEu53w8ahmwBjZKM/YPHZyZHvz5uv+WouB9+ObcCpyrx6l7KImUglyhkEat/+ysWwi0gdhEs0JGjl6ggRWTy1haxBpVdbIX1nthFXMZebaIdHP2ghjoIc/363UZI6Kf1ptIMVax5M0mESuWmKJKtQO9LD60= X-DA7885C5: A550D1DF5AD37DD0F255D290C0D534F9D858F6EE711772AC404959BB7A709F389D9F3487583F1FB65B1A4C17EAA7BC4BEF2421ABFA55128DAF83EF9164C44C7E X-Mailru-Sender: 689FA8AB762F7393520AF17B8A65FDE28AF602122D86BFA9CC6ACF283053BA2B3B39329502D6FD8CEF86D5F70DA33880E41E8EF7A07863ECB274557F927329BE2DDF8182D28ACDB545BD1C3CC395C826B4A721A3011E896F X-Mras: Ok Subject: Re: [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 Bronnikov via Tarantool-patches Reply-To: Sergey Bronnikov Cc: tarantool-patches@dev.tarantool.org Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" This is a multi-part message in MIME format. --------------Vc5kJg1GEdOrwPQ11Phq8Y0l Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit Hi, Sergey, thanks for the patch! Please see my comments. Sergey On 5/19/26 15:39, Sergey Kaplun wrote: > From: Maxim Kokryashkin It looks like the patch was made by Maxim K. and you took it as is, but it is not so. The latest version of this patch is here [1] and there several changes introduced by you. I suppose these changes should be described and you should to somehow say that you changed the patch a little (for example using Git trailer "Co-authored-by:"). Otherwise, we are following bad practices, let's not be like those people we usually joke about. The full diff is below: --- dbg/debug-extension-tests.py 2026-05-20 16:02:11.854999756 +0300 +++ test/tarantool-debugger-tests/debug-extension-tests.py   2026-05-20 16:02:43.124696812 +0300 @@ -1,4 +1,6 @@ -# This file provides tests for LuaJIT debug extensions for lldb and gdb. +# This file provides tests for LuaJIT debug extensions for lldb +# and gdb. +  import os  import re  import subprocess @@ -11,12 +13,27 @@  LEGACY = re.match(r'^2\.', sys.version)  LUAJIT_BINARY = os.environ['LUAJIT_TEST_BINARY'] -EXTENSION = os.environ['DEBUGGER_EXTENSION_PATH'] +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 -RUN_CMD_FILE = '-s' if LLDB else '-x' +# 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 = ( @@ -24,6 +41,11 @@  ).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) @@ -33,32 +55,39 @@  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) +        # 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. +        # 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, capture_output=True, timeout=TIMEOUT) -        return process.stdout.decode('ascii') - - -def filter_debugger_output(output): -    descriptor = '(lldb)' if LLDB else '(gdb)' -    return ''.join( -        filter( -            lambda line: not line.startswith(descriptor), -            output.splitlines(True), -        ), -    ) +        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): @@ -79,13 +108,13 @@          script_file = persist(cls.lua_script)          process_cmd = [              DEBUGGER, -            RUN_CMD_FILE, +            *RUN_CMD_FILE,              cmd_file.name,              INFERIOR_ARGS,              LUAJIT_BINARY,              script_file.name,          ] -        cls.output = filter_debugger_output(execute_process(process_cmd)) +        cls.output = execute_process(process_cmd)          cmd_file.close()          script_file.close() @@ -101,14 +130,14 @@      location = 'lj_cf_print'      lua_script = 'print(1)'      pattern = ( -        'lj-tv command intialized\n' -        'lj-state command intialized\n' -        'lj-arch command intialized\n' -        'lj-gc command intialized\n' -        'lj-str command intialized\n' -        'lj-tab command intialized\n' -        'lj-stack command intialized\n' -        'LuaJIT debug extension is successfully loaded\n' +        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'      ) @@ -117,9 +146,9 @@      location = 'lj_cf_print'      lua_script = 'print(1)'      pattern = ( -        'LJ_64: (True|False), ' -        'LJ_GC64: (True|False), ' -        'LJ_DUALNUM: (True|False)' +        r'LJ_64: (True|False), ' +        r'LJ_GC64: (True|False), ' +        r'LJ_DUALNUM: (True|False)'      ) @@ -128,9 +157,9 @@      location = 'lj_cf_print'      lua_script = 'print(1)'      pattern = ( -        'VM state: [A-Z]+\n' -        'GC state: [A-Z]+\n' -        'JIT state: [A-Z]+\n' +        r'VM state: [A-Z]+\n' +        r'GC state: [A-Z]+\n' +        r'JIT state: [A-Z]+\n'      ) @@ -139,19 +168,19 @@      location = 'lj_cf_print'      lua_script = 'print(1)'      pattern = ( -        'GC stats: [A-Z]+\n' -        '\ttotal: \d+\n' -        '\tthreshold: \d+\n' -        '\tdebt: \d+\n' -        '\testimate: \d+\n' -        '\tstepmul: \d+\n' -        '\tpause: \d+\n' -        '\tsweepstr: \d+/\d+\n' -        '\troot: \d+ objects\n' -        '\tgray: \d+ objects\n' -        '\tgrayagain: \d+ objects\n' -        '\tweak: \d+ objects\n' -        '\tmmudata: \d+ objects\n' +        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'      ) @@ -160,17 +189,15 @@      location = 'lj_cf_print'      lua_script = 'print(1)'      pattern = ( -        '-+ Red zone:\s+\d+ slots -+\n' -        '(0x[a-zA-Z0-9]+\s+\[(S|\s)(B|\s)(T|\s)(M|\s)\] VALUE: nil\n?)*\n' -        '-+ Stack:\s+\d+ slots -+\n' -        '(0x[A-Za-z0-9]+(:0x[A-Za-z0-9]+)?\s+' -        '\[(S|\s)(B|\s)(T|\s)(M|\s)\].*\n?)+\n' +        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' -    lua_script = 'print(1)'      extension_cmds = (          'lj-tv L->base\n'          'lj-tv L->base + 1\n' @@ -205,46 +232,55 @@      )      pattern = ( -        'nil\n' -        'false\n' -        'true\n' -        'string \"hello\" @ 0x[a-zA-Z0-9]+\n' -        'table @ 0x[a-zA-Z0-9]+ \(asize: \d+, hmask: 0x[a-zA-Z0-9]+\)\n' -        '(number|integer) .*1.*\n' -        'number 1.1\d+\n' -        'thread @ 0x[a-zA-Z0-9]+\n' -        'cdata @ 0x[a-zA-Z0-9]+\n' -        'Lua function @ 0x[a-zA-Z0-9]+, [0-9]+ upvalues, .+:[0-9]+\n' -        'fast function #[0-9]+\n' -        'C function @ 0x[a-zA-Z0-9]+\n' +        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 = 'lj-str fname' +    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 = 'String: .* \[\d+ bytes\] with hash 0x[a-zA-Z0-9]+' +    pattern = r'String: .* \[\d+ bytes\] with hash ' + RX_HASH  class TestLJTab(TestCaseBase): -    extension_cmds = 'lj-tab t' +    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 = ( -        'Array part: 3 slots\n' -        '0x[a-zA-Z0-9]+: \[0\]: nil\n' -        '0x[a-zA-Z0-9]+: \[1\]: .+ 1\n' -        '0x[a-zA-Z0-9]+: \[2\]: nil\n' -        'Hash part: 2 nodes\n' -        '0x[a-zA-Z0-9]+: { string "a" @ 0x[a-zA-Z0-9]+ } => ' -        '{ .+ 1 }; next = 0x0\n' -        '0x[a-zA-Z0-9]+: { nil } => { nil }; next = 0x0\n' +        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() -if __name__ == '__main__': +# FIXME: skip for LLDB since most commands are not working anyway. +if __name__ == '__main__' and not LLDB:      unittest.main(verbosity=2) [1]: https://lists.tarantool.org/tarantool-patches/cover.1712182830.git.m.kokryashkin@tarantool.org/ > 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. I don't get why we cannot run debugger tests together with other tests. dbg extension must work with all LuaJIT configurations that we test in CI, so I suppose we should run these (dbg) tests with all other regression tests. For example, the proposed GHA workflows doesn't cover DUALNUM build, сan I be sure that the extension will work with DUALNUM build? Seems no. > > [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 see the comment above > 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" > + ) it is not dummy, it doesn't exist at all: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cd build make tarantool-debugger-tests make[3]: *** No rule to make target 'src/luajit', needed by 'test/tarantool-debugger-tests/CMakeFiles/tarantool-debugger-tests-deps'. Stop. > + 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" > + ) the same as above, it is not dummy > + return() > +endif() > + > +if(CMAKE_VERSION VERSION_LESS "3.12") > + #TODO:Can remove this after upgrading to CMake >= 3.12. s/TODO:/TODO/ > + find_package(PythonInterp) > + if(NOT PYTHONINTERP_FOUND) > + message(WARNING "`python` is not found, tarantool-debugger-tests is dummy") it is not dummy > + return() > + endif() > +else() > + find_package(Python COMPONENTS Interpreter) > + if(NOT PYTHON_FOUND) > + message(WARNING "`python` is not found, tarantool-debugger-tests is dummy") it is not 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() Duplicate code for GDB and LLDB. May be use a macro for this? > 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'-+ Redzone:\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) --------------Vc5kJg1GEdOrwPQ11Phq8Y0l Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit

Hi, Sergey,

thanks for the patch! Please see my comments.

Sergey

On 5/19/26 15:39, Sergey Kaplun wrote:
From: Maxim Kokryashkin <m.kokryashkin@tarantool.org>

It looks like the patch was made by Maxim K. and you took it as is, but it is not so.

The latest version of this patch is here [1] and there several changes introduced by you.

I suppose these changes should be described and you should to somehow

say that you changed the patch a little (for example using Git trailer "Co-authored-by:"). 

Otherwise, we are following bad practices, let's not be like those people we usually joke about.

The full diff is below:

--- dbg/debug-extension-tests.py    2026-05-20 16:02:11.854999756 +0300
+++ test/tarantool-debugger-tests/debug-extension-tests.py    2026-05-20 16:02:43.124696812 +0300
@@ -1,4 +1,6 @@
-# This file provides tests for LuaJIT debug extensions for lldb and gdb.
+# This file provides tests for LuaJIT debug extensions for lldb
+# and gdb.
+
 import os
 import re
 import subprocess
@@ -11,12 +13,27 @@
 LEGACY = re.match(r'^2\.', sys.version)
 
 LUAJIT_BINARY = os.environ['LUAJIT_TEST_BINARY']
-EXTENSION = os.environ['DEBUGGER_EXTENSION_PATH']
+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
 
-RUN_CMD_FILE = '-s' if LLDB else '-x'
+# 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 = (
@@ -24,6 +41,11 @@
 ).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)
@@ -33,32 +55,39 @@
 
 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)
+        # 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.
+        # 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, capture_output=True, timeout=TIMEOUT)
-        return process.stdout.decode('ascii')
-
-
-def filter_debugger_output(output):
-    descriptor = '(lldb)' if LLDB else '(gdb)'
-    return ''.join(
-        filter(
-            lambda line: not line.startswith(descriptor),
-            output.splitlines(True),
-        ),
-    )
+        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):
@@ -79,13 +108,13 @@
         script_file = persist(cls.lua_script)
         process_cmd = [
             DEBUGGER,
-            RUN_CMD_FILE,
+            *RUN_CMD_FILE,
             cmd_file.name,
             INFERIOR_ARGS,
             LUAJIT_BINARY,
             script_file.name,
         ]
-        cls.output = filter_debugger_output(execute_process(process_cmd))
+        cls.output = execute_process(process_cmd)
         cmd_file.close()
         script_file.close()
 
@@ -101,14 +130,14 @@
     location = 'lj_cf_print'
     lua_script = 'print(1)'
     pattern = (
-        'lj-tv command intialized\n'
-        'lj-state command intialized\n'
-        'lj-arch command intialized\n'
-        'lj-gc command intialized\n'
-        'lj-str command intialized\n'
-        'lj-tab command intialized\n'
-        'lj-stack command intialized\n'
-        'LuaJIT debug extension is successfully loaded\n'
+        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'
     )
 
 
@@ -117,9 +146,9 @@
     location = 'lj_cf_print'
     lua_script = 'print(1)'
     pattern = (
-        'LJ_64: (True|False), '
-        'LJ_GC64: (True|False), '
-        'LJ_DUALNUM: (True|False)'
+        r'LJ_64: (True|False), '
+        r'LJ_GC64: (True|False), '
+        r'LJ_DUALNUM: (True|False)'
     )
 
 
@@ -128,9 +157,9 @@
     location = 'lj_cf_print'
     lua_script = 'print(1)'
     pattern = (
-        'VM state: [A-Z]+\n'
-        'GC state: [A-Z]+\n'
-        'JIT state: [A-Z]+\n'
+        r'VM state: [A-Z]+\n'
+        r'GC state: [A-Z]+\n'
+        r'JIT state: [A-Z]+\n'
     )
 
 
@@ -139,19 +168,19 @@
     location = 'lj_cf_print'
     lua_script = 'print(1)'
     pattern = (
-        'GC stats: [A-Z]+\n'
-        '\ttotal: \d+\n'
-        '\tthreshold: \d+\n'
-        '\tdebt: \d+\n'
-        '\testimate: \d+\n'
-        '\tstepmul: \d+\n'
-        '\tpause: \d+\n'
-        '\tsweepstr: \d+/\d+\n'
-        '\troot: \d+ objects\n'
-        '\tgray: \d+ objects\n'
-        '\tgrayagain: \d+ objects\n'
-        '\tweak: \d+ objects\n'
-        '\tmmudata: \d+ objects\n'
+        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'
     )
 
 
@@ -160,17 +189,15 @@
     location = 'lj_cf_print'
     lua_script = 'print(1)'
     pattern = (
-        '-+ Red zone:\s+\d+ slots -+\n'
-        '(0x[a-zA-Z0-9]+\s+\[(S|\s)(B|\s)(T|\s)(M|\s)\] VALUE: nil\n?)*\n'
-        '-+ Stack:\s+\d+ slots -+\n'
-        '(0x[A-Za-z0-9]+(:0x[A-Za-z0-9]+)?\s+'
-        '\[(S|\s)(B|\s)(T|\s)(M|\s)\].*\n?)+\n'
+        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'
-    lua_script = 'print(1)'
     extension_cmds = (
         'lj-tv L->base\n'
         'lj-tv L->base + 1\n'
@@ -205,46 +232,55 @@
     )
 
     pattern = (
-        'nil\n'
-        'false\n'
-        'true\n'
-        'string \"hello\" @ 0x[a-zA-Z0-9]+\n'
-        'table @ 0x[a-zA-Z0-9]+ \(asize: \d+, hmask: 0x[a-zA-Z0-9]+\)\n'
-        '(number|integer) .*1.*\n'
-        'number 1.1\d+\n'
-        'thread @ 0x[a-zA-Z0-9]+\n'
-        'cdata @ 0x[a-zA-Z0-9]+\n'
-        'Lua function @ 0x[a-zA-Z0-9]+, [0-9]+ upvalues, .+:[0-9]+\n'
-        'fast function #[0-9]+\n'
-        'C function @ 0x[a-zA-Z0-9]+\n'
+        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 = 'lj-str fname'
+    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 = 'String: .* \[\d+ bytes\] with hash 0x[a-zA-Z0-9]+'
+    pattern = r'String: .* \[\d+ bytes\] with hash ' + RX_HASH
 
 
 class TestLJTab(TestCaseBase):
-    extension_cmds = 'lj-tab t'
+    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 = (
-        'Array part: 3 slots\n'
-        '0x[a-zA-Z0-9]+: \[0\]: nil\n'
-        '0x[a-zA-Z0-9]+: \[1\]: .+ 1\n'
-        '0x[a-zA-Z0-9]+: \[2\]: nil\n'
-        'Hash part: 2 nodes\n'
-        '0x[a-zA-Z0-9]+: { string "a" @ 0x[a-zA-Z0-9]+ } => '
-        '{ .+ 1 }; next = 0x0\n'
-        '0x[a-zA-Z0-9]+: { nil } => { nil }; next = 0x0\n'
+        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()
 
-if __name__ == '__main__':
+# FIXME: skip for LLDB since most commands are not working anyway.
+if __name__ == '__main__' and not LLDB:
     unittest.main(verbosity=2)

[1]: https://lists.tarantool.org/tarantool-patches/cover.1712182830.git.m.kokryashkin@tarantool.org/

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.

I don't get why we cannot run debugger tests together with other tests.

dbg extension must work with all LuaJIT configurations that we test in CI,

so I suppose we should run these (dbg) tests with all other regression tests.

For example, the proposed GHA workflows doesn't cover DUALNUM build,

сan I be sure that the extension will work with DUALNUM build? Seems no.


[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
see the comment above
   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
+# <tarantool-debugger-tests-deps> 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"
+  )

it is not dummy, it doesn't exist at all:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

cd build

make tarantool-debugger-tests
make[3]: *** No rule to make target 'src/luajit', needed by 'test/tarantool-debugger-tests/CMakeFiles/tarantool-debugger-tests-deps'.  Stop.

+  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"
+  )
the same as above, it is not dummy
+  return()
+endif()
+
+if(CMAKE_VERSION VERSION_LESS "3.12")
+  # TODO:Can remove this after upgrading to CMake >= 3.12.
s/TODO:/TODO/
+  find_package(PythonInterp)
+  if(NOT PYTHONINTERP_FOUND)
+    message(WARNING "`python` is not found, tarantool-debugger-tests is dummy")
it is not dummy
+    return()
+  endif()
+else()
+  find_package(Python COMPONENTS Interpreter)
+  if(NOT PYTHON_FOUND)
+    message(WARNING "`python` is not found, tarantool-debugger-tests is dummy")
it is not 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()
Duplicate code for GDB and LLDB. May be use a macro for this?
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)
--------------Vc5kJg1GEdOrwPQ11Phq8Y0l--