Hi, Sergey,
thanks for the patch! Please see my comments.
Sergey
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
s/TODO:/TODO/+ return() +endif() + +if(CMAKE_VERSION VERSION_LESS "3.12") + # TODO:Can remove this after upgrading to CMake >= 3.12.
it is not dummy+ 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")
+ 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)