[Tarantool-patches] [PATCH luajit v6 2/2] test: add tests for debugging extensions
Sergey Bronnikov
sergeyb at tarantool.org
Thu Apr 4 13:27:19 MSK 2024
Hi, Max
thanks for the patch. See my comments below:
On 4/4/24 01:21, Maxim Kokryashkin wrote:
> From: Maksim Kokryashkin <max.kokryashkin at gmail.com>
>
> This patch adds tests for LuaJIT debugging
> extensions for lldb and gdb.
> ---
> .flake8rc | 4 +
> test/CMakeLists.txt | 1 +
> .../CMakeLists.txt | 80 ++++++
> .../debug-extension-tests.py | 250 ++++++++++++++++++
> 4 files changed, 335 insertions(+)
> create mode 100644 test/LuaJIT-debug-extensions-tests/CMakeLists.txt
> create mode 100644 test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
>
> diff --git a/.flake8rc b/.flake8rc
> index 13e6178f..6766ed41 100644
> --- a/.flake8rc
> +++ b/.flake8rc
> @@ -3,3 +3,7 @@ extend-ignore =
> # XXX: Suppress F821, since we have autogenerated names for
> # 'ptr' type complements in luajit_lldb.py.
> F821
> +per-file-ignores =
> + # XXX: Flake considers regexp special characters to be
> + # escape sequences.
> + test/LuaJIT-debug-extensions-tests/debug-extension-tests.py:W605
> diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
> index 19726f5a..a3b48939 100644
> --- a/test/CMakeLists.txt
> +++ b/test/CMakeLists.txt
> @@ -148,6 +148,7 @@ add_subdirectory(PUC-Rio-Lua-5.1-tests)
> add_subdirectory(lua-Harness-tests)
> add_subdirectory(tarantool-c-tests)
> add_subdirectory(tarantool-tests)
> +add_subdirectory(LuaJIT-debug-extensions-tests)
>
> # Each testsuite has its own CMake target, but combining these
> # target into a single one is not desired, because each target
> diff --git a/test/LuaJIT-debug-extensions-tests/CMakeLists.txt b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
> new file mode 100644
> index 00000000..9ac626ec
> --- /dev/null
> +++ b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
> @@ -0,0 +1,80 @@
> +SET(TEST_SUITE_NAME "LuaJIT-dbg-extension-tests")
> +add_test_suite_target(LuaJIT-dbg-extension-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, LuaJIT-lldb-extension-tests and "
> + "LuaJIT-gdb-extension-tests are 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,"
> + "LuaJIT-lldb-extension-tests is dummy"
> + )
> + return()
> +endif()
> +
> +find_package(PythonInterp)
> +if(NOT PYTHONINTERP_FOUND)
> + message(WARNING
> + "`python` is not found, LuaJIT-lldb-extension-tests and "
> + "LuaJIT-gdb-extension-tests are dummy"
> + )
> + return()
> +endif()
> +
> +set(DEBUGGER_TEST_ENV
> + "LUAJIT_TEST_BINARY=${LUAJIT_TEST_BINARY}"
> + # Suppresses __pycache__ generation.
> + "PYTHONDONTWRITEBYTECODE=1"
> + "DEBUGGER_EXTENSION_PATH=${PROJECT_SOURCE_DIR}/src/luajit_dbg.py"
> +)
> +
> +set(TEST_SCRIPT_PATH
> + ${PROJECT_SOURCE_DIR}/test/LuaJIT-debug-extensions-tests/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 LuaJIT-dbg-extension-tests-deps
> + )
> +else()
> + message(WARNING "`gdb' is not found, so LuaJIT-gdb-extension-tests is dummy")
> +endif()
> +
> +find_program(LLDB lldb)
> +if(LLDB)
> + set(test_title "test/${TEST_SUITE_NAME}/lldb")
I would use a real path as a test title like we do in other testsuites
(PUC Rio Lua and LuaJIT tests are exception in this rule).
For these test we have two flavors, so I suggest to create symlinks:
~/sources/MRG/tarantool/third_party/luajit/test/LuaJIT-debug-extensions-tests$
ls -la
total 20
drwxrwxr-x 2 sergeyb sergeyb 4096 Apr 4 13:15 .
drwxrwxr-x 10 sergeyb sergeyb 4096 Apr 4 12:44 ..
-rw-rw-r-- 1 sergeyb sergeyb 2451 Apr 4 12:44 CMakeLists.txt
-rw-rw-r-- 1 sergeyb sergeyb 6787 Apr 4 12:44 debug-extension-tests.py
lrwxrwxrwx 1 sergeyb sergeyb 24 Apr 4 13:15
gdb-debug-extension-tests.py -> debug-extension-tests.py
lrwxrwxrwx 1 sergeyb sergeyb 24 Apr 4 13:15
lldb-debug-extension-tests.py -> debug-extension-tests.py
And generate CMake tests for these files:
--- a/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
+++ b/test/LuaJIT-debug-extensions-tests/CMakeLists.txt
@@ -41,14 +41,11 @@ set(DEBUGGER_TEST_ENV
"DEBUGGER_EXTENSION_PATH=${PROJECT_SOURCE_DIR}/src/luajit_dbg.py"
)
-set(TEST_SCRIPT_PATH
-
${PROJECT_SOURCE_DIR}/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
-)
-
find_program(GDB gdb)
if(GDB)
- set(test_title "test/${TEST_SUITE_NAME}/gdb")
+ set(test_title "test/${TEST_SUITE_NAME}/gdb-debug-extension-tests.py")
set(GDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${GDB}")
+ set(TEST_SCRIPT_PATH
${CMAKE_CURRENT_SOURCE_DIR}/gdb-debug-extension-tests.py)
add_test(NAME "${test_title}"
COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
@@ -64,8 +61,9 @@ endif()
find_program(LLDB lldb)
if(LLDB)
- set(test_title "test/${TEST_SUITE_NAME}/lldb")
+ set(test_title "test/${TEST_SUITE_NAME}/lldb-debug-extension-tests.py")
set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${LLDB}")
+ set(TEST_SCRIPT_PATH
${CMAKE_CURRENT_SOURCE_DIR}/lldb-debug-extension-tests.py)
add_test(NAME "test/${TEST_SUITE_NAME}/lldb"
COMMAND ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
In CTest test titles looks as a real file names:
$ ctest -L LuaJIT-dbg-extension-tests
Test project
/home/sergeyb/sources/MRG/tarantool/third_party/luajit/build/gc64
Start 212: LuaJIT-dbg-extension-tests-deps
1/2 Test #212: LuaJIT-dbg-extension-tests-deps
................................ Passed 0.01 sec
Start 213: test/LuaJIT-dbg-extension-tests/gdb-debug-extension-tests.py
2/2 Test #213:
test/LuaJIT-dbg-extension-tests/gdb-debug-extension-tests.py ...
Passed 2.11 sec
100% tests passed, 0 tests failed out of 2
Label Time Summary:
LuaJIT-dbg-extension-tests = 2.12 sec*proc (2 tests)
Total Test time (real) = 2.14 sec
What do you think?
> + set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} "DEBUGGER_COMMAND=${LLDB}")
> + add_test(NAME "test/${TEST_SUITE_NAME}/lldb"
> + 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 LuaJIT-dbg-extension-tests-deps
> + )
> +else()
> + message(WARNING "`lldb' is not found, so LuaJIT-lldb-extension-tests is dummy")
> +endif()
> diff --git a/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py b/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
> new file mode 100644
> index 00000000..6ef87473
> --- /dev/null
> +++ b/test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
> @@ -0,0 +1,250 @@
> +# 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 = os.environ['DEBUGGER_EXTENSION_PATH']
> +DEBUGGER = os.environ['DEBUGGER_COMMAND']
> +LLDB = 'lldb' in DEBUGGER
> +TIMEOUT = 10
> +
> +RUN_CMD_FILE = '-s' if LLDB else '-x'
> +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)
> +
> +
> +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)
> + 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, 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),
> + ),
> + )
> +
> +
> +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 = filter_debugger_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 = (
> + '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'
> + )
> +
> +
> +class TestLJArch(TestCaseBase):
> + extension_cmds = 'lj-arch'
> + location = 'lj_cf_print'
> + lua_script = 'print(1)'
> + pattern = (
> + 'LJ_64: (True|False), '
> + 'LJ_GC64: (True|False), '
> + 'LJ_DUALNUM: (True|False)'
> + )
> +
> +
> +class TestLJState(TestCaseBase):
> + extension_cmds = 'lj-state'
> + location = 'lj_cf_print'
> + lua_script = 'print(1)'
> + pattern = (
> + 'VM state: [A-Z]+\n'
> + 'GC state: [A-Z]+\n'
> + 'JIT state: [A-Z]+\n'
> + )
> +
> +
> +class TestLJGC(TestCaseBase):
> + extension_cmds = 'lj-gc'
> + 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'
> + )
> +
> +
> +class TestLJStack(TestCaseBase):
> + extension_cmds = 'lj-stack'
> + 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'
> + )
> +
> +
> +class TestLJTV(TestCaseBase):
> + location = 'lj_cf_print'
> + lua_script = 'print(1)'
> + 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 = (
> + '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'
> + )
> +
> +
> +class TestLJStr(TestCaseBase):
> + extension_cmds = 'lj-str fname'
> + location = 'lj_cf_dofile'
> + lua_script = 'pcall(dofile("name"))'
> + pattern = 'String: .* \[\d+ bytes\] with hash 0x[a-zA-Z0-9]+'
> +
> +
> +class TestLJTab(TestCaseBase):
> + extension_cmds = 'lj-tab t'
> + 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'
> + )
> +
> +
> +for test_cls in TestCaseBase.__subclasses__():
> + test_cls.test = lambda self: self.check()
> +
> +if __name__ == '__main__':
> + unittest.main(verbosity=2)
More information about the Tarantool-patches
mailing list