forked from LuaJIT/LuaJIT
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add tests for debugging extensions
This patch adds tests for LuaJIT debugging extensions for lldb and gdb.
- Loading branch information
1 parent
d1046af
commit f90741e
Showing
4 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
add_custom_target(LuaJIT-gdb-extension-tests | ||
DEPENDS ${LUAJIT_TEST_BINARY} | ||
) | ||
|
||
add_custom_target(LuaJIT-lldb-extension-tests | ||
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() | ||
|
||
# FIXME: Use the `PythonInterp` cmake package for now. Should be | ||
# removed after the CMake minimal version bump. | ||
cmake_policy(SET CMP0148 OLD) | ||
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(GDB_TEST_ENV ${DEBUGGER_TEST_ENV} | ||
"DEBUGGER_COMMAND=${GDB}" | ||
) | ||
add_custom_command(TARGET LuaJIT-gdb-extension-tests | ||
COMMENT "Running debug extension tests with gdb" | ||
COMMAND | ||
${GDB_TEST_ENV} ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} | ||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} | ||
) | ||
else() | ||
message(WARNING "`gdb' is not found, so LuaJIT-gdb-extension-tests is dummy") | ||
endif() | ||
|
||
find_program(LLDB lldb) | ||
if(LLDB) | ||
set(LLDB_TEST_ENV ${DEBUGGER_TEST_ENV} | ||
"DEBUGGER_COMMAND=${LLDB}" | ||
) | ||
add_custom_command(TARGET LuaJIT-lldb-extension-tests | ||
COMMENT "Running debug extension tests with lldb" | ||
COMMAND | ||
${LLDB_TEST_ENV} ${PYTHON_EXECUTABLE} ${TEST_SCRIPT_PATH} | ||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} | ||
) | ||
else() | ||
message(WARNING "`lldb' is not found, so LuaJIT-gdb-extension-tests is dummy") | ||
endif() |
251 changes: 251 additions & 0 deletions
251
test/LuaJIT-debug-extensions-tests/debug-extension-tests.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
# 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__': | ||
# XXX: Verbosity level 2 is the most verbose. | ||
unittest.main(verbosity=2) |