From: Mikhail Elhimov via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Sergey Kaplun <skaplun@tarantool.org>,
Sergey Bronnikov <sergeyb@tarantool.org>,
Evgeniy Temirgaleev <e.temirgaleev@tarantool.org>
Cc: tarantool-patches@dev.tarantool.org
Subject: [Tarantool-patches] [PATCH luajit 1/4] dbg: clean up and unification
Date: Fri, 13 Nov 2026 02:42:22 +0300 [thread overview]
Message-ID: <0a9a648552eb3a3bbaa9639e0d567ae008cb0e14.1794522533.git.m.elhimov@vk.team> (raw)
In-Reply-To: <cover.1794522533.git.m.elhimov@vk.team>
* unified `dbg.eval` approach to simplify error handling (exception in
case of any failure)
* removed trailing newline in all the dumpers to unify composing of the
complex output
* newly introduced `dbg.print` is used whenever trailing newline is
needed to make code more compact and clean
* moved handling of a missing command arg to command level (all the
dump-like functions now expect concrete value)
* unified handling of command-level error (introduced special exception
for LLDB that works similar to `gdb.GdbError`, i.e. doesn't cause
traceback to be printed)
* in `gdb.eval()` dropped check of the value returned by
`gdb.parse_and_eval()` as it would also fail for an expression like
`'0'` (looks like a kind of experimental code that was left by mistake)
* `lldb.eval()` is adjusted to return object of the same type as
`lldb.cast()` method returns (this improves consistency)
* renamed `dbg.eval` argument to reflect its meaning (it is
an expression rather than a command)
---
src/luajit_dbg.py | 178 +++++++++--------
.../debug-extension-tests.py | 182 +++++++++++++-----
2 files changed, 234 insertions(+), 126 deletions(-)
diff --git a/src/luajit_dbg.py b/src/luajit_dbg.py
index 80057a4e..77abf8c6 100644
--- a/src/luajit_dbg.py
+++ b/src/luajit_dbg.py
@@ -110,6 +110,10 @@ class Debugger(object):
self.write('{} command initialized\n'.format(name))
self.write('LuaJIT debug extension is successfully loaded\n')
+ def print(self, msg):
+ '''Print the message with the new line.'''
+ self.write(msg + '\n')
+
@abc.abstractmethod
def cast(self, typestr, val):
'''Cast the value to the required C type.'''
@@ -146,8 +150,8 @@ class Debugger(object):
pass
@abc.abstractmethod
- def eval(self, command):
- '''Parse and evaluate the given debugger command.'''
+ def eval(self, expr):
+ '''Parse and evaluate the given debugger expression.'''
pass
@abc.abstractmethod
@@ -268,14 +272,8 @@ class _GDBDebugger(Debugger):
else:
return None
- def eval(self, command):
- if not command:
- return None
-
- ret = gdb.parse_and_eval(command)
- if not ret:
- raise gdb.GdbError('table argument empty')
- return ret
+ def eval(self, expr):
+ return gdb.parse_and_eval(expr)
def detect_arch(self):
if hasattr(self, 'arch'):
@@ -670,15 +668,26 @@ class _LLDBDebugger(Debugger):
else:
return None
- def eval(self, command):
- if not command:
- return None
-
+ def eval(self, expr):
process = self.target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
- ret = frame.EvaluateExpression(command)
- return ret
+ val = frame.EvaluateExpression(expr)
+ # LLDB doesn't throw an exception if it fails to
+ # evaluate an expression. In order to simplify handling
+ # of `Debugger.eval()` it is convenient to make it work
+ # similar to GDB, i.e. throw an exception in case of
+ # failure.
+ # Be aware that SBValue.IsValid() is necessary, but not
+ # sufficient as it only indicates that the value object
+ # does contain the relevant data (including the potential
+ # error), so it is necessary to check the `error` property
+ # as well.
+ if not val.IsValid():
+ raise self.error('{}: invalid expression'.format(expr))
+ if val.error.fail:
+ raise self.error(val.error)
+ return lldb.value(val)
def detect_arch(self):
if hasattr(self, 'arch'):
@@ -694,8 +703,13 @@ class _LLDBDebugger(Debugger):
def write(self, msg):
sys.stdout.write(msg)
+ class DbgError(Exception):
+ '''Raised to indicate an error during command execution.
+ Doesn't cause a traceback to be printed (like gdb.GdbError for GDB).'''
+ pass
+
def error(self, errmsg):
- return Exception(errmsg)
+ return self.DbgError(errmsg)
def check_libluajit(self):
# TODO: Implement postpone loading for LLDB too.
@@ -738,12 +752,27 @@ class _LLDBDebugger(Debugger):
def __call__(ljbase, debugger, args, exe_ctx, result):
try:
ljbase.execute(args)
- except Exception as e:
+ except _LLDBDebugger.DbgError as e:
msg = 'Failed to execute command `{}`: {}'.format(
ljbase.name,
e
)
result.SetError(msg)
+ # `result` is an output argument of LLDB command
+ # handler. If error message is set, LLDB displays
+ # it in session. But such a message is missing in
+ # output when the same problem occurs in batch
+ # mode, so in order to be able to catch this kind
+ # of error in batch mode it is needed to put it
+ # into stderr explicitly.
+ # Also to avoid duplication of the same error
+ # message in interactive mode, it should be done
+ # in batch mode only. There is no official way to
+ # check if LLDB is in the batch mode so one of the
+ # recommended workarounds is used below that
+ # checks if tty is not used for standard input.
+ if not sys.stdin.isatty():
+ result.PutError(lldb.SBFile.Create(sys.stderr, True))
@abc.abstractmethod
def execute(ljbase, args):
@@ -2399,7 +2428,7 @@ def dump_proto(proto):
startbc = proto_bc(proto)
func_loc = proto_loc(proto)
# Location has the following format: '{chunk}:{firstline}'.
- dump = '{func_loc}-{lastline}\n'.format(
+ dump = '{func_loc}-{lastline}'.format(
func_loc=func_loc,
lastline=proto['firstline'] + proto['numline'],
)
@@ -2408,9 +2437,9 @@ def dump_proto(proto):
return '=> ' + str(npc_from + delta).zfill(4)
for bcnum in range(0, int(proto['sizebc'])):
- dump += (str(bcnum).zfill(4) + ' ' + dump_bc(
+ dump += ('\n' + str(bcnum).zfill(4) + ' ' + dump_bc(
startbc[bcnum], jmp_format=jmp_format, jmp_ctx=bcnum, proto=proto,
- ) + '\n')
+ ))
return dump
@@ -2421,9 +2450,9 @@ def dump_func(func):
pt = funcproto(func)
return dump_proto(pt)
elif ffid == 1:
- return 'C function @ {}\n'.format(strx64(func['f']))
+ return 'C function @ {}'.format(strx64(func['f']))
else:
- return 'fast function #{}\n'.format(int(ffid))
+ return 'fast function #{}'.format(int(ffid))
# FFI dumpers.
@@ -2740,7 +2769,7 @@ def dump_irins(irins, trace=None):
if op1 != -1:
args = dump_call_args(trace, int(op1))
- return '{flags} {type} {name:6} [{mode:2}] {f}({args}) {ct}\n'.format(
+ return '{flags} {type} {name:6} [{mode:2}] {f}({args}) {ct}'.format(
flags=flags,
name=irname,
mode=ir_mode(irop),
@@ -2792,7 +2821,7 @@ def dump_irins(irins, trace=None):
else:
typename = IRTYPES[irt_type(irt)]
- return '{flags} {type} {name:6} [{mode:2}] {left:<9s} {right}\n'.format(
+ return '{flags} {type} {name:6} [{mode:2}] {left:<9s} {right}'.format(
flags=flags,
name=irname,
mode=ir_mode(irop),
@@ -2857,18 +2886,19 @@ def dump_regsp(irins, ins_number):
def dump_trace(trace, flags):
- dump = 'Trace {num} start\n\tproto: {start_pt}\n\tBC: {start_bc}\n'.format(
+ dump = 'Trace {num} start\n\tproto: {start_pt}\n\tBC: {start_bc}'.format(
num=trace['traceno'],
start_pt=gcref(trace['startpt']),
start_bc=mref('BCIns *', trace['startpc']),
)
nins = trace['nins'] - REF_BIAS
- dump += '---- TRACE IR\n'
+ dump += '\n---- TRACE IR'
nsnap = 0
snap = trace['snap'][nsnap]
snapref = snap['ref']
for irnum in range(1, nins):
+ dump += '\n'
irref = REF_BIAS + irnum
if 's' in flags and irref >= snapref and nsnap < trace['nsnap']:
dump += '.... '
@@ -2894,27 +2924,32 @@ def dump_tref(tref):
)
-def dump_jslots(coroutine):
- lstate = L(None)
- g = G(lstate or coroutine)
- j = J(g)
-
- dump = ''
- maxslot = j['baseslot'] + j['maxslot']
+def dump_jslots(J):
+ dump = []
+ maxslot = J['baseslot'] + J['maxslot']
first_base_slot = 1 + LJ_FR2
for n in reversed(range(first_base_slot, maxslot)):
- tref = j['slot'][n]
+ tref = J['slot'][n]
ref = tref_ref(tref)
address = dbg.address(tref)
- dump += '{addr} {nslot:04d} {base:1s} {tref}{const}\n'.format(
+ dump.append('{addr} {nslot:04d} {base:1s} {tref}{const}'.format(
addr=address,
- base='B' if address == j['base'] else ' ',
+ base='B' if address == J['base'] else ' ',
nslot=n,
tref=dump_tref(tref),
- const=' ' + dump_irk(j['cur'], ref - REF_BIAS)
- if ref != 0 and ref < REF_BIAS else ''
- )
- return dump
+ const=' ' + dump_irk(J['cur'], ref - REF_BIAS)
+ if ref != 0 and ref < REF_BIAS else ''
+ ))
+ return '\n'.join(dump)
+
+
+# Extension commands helpers. ####################################
+
+
+def cast_command_arg(typestr, arg):
+ if not arg:
+ raise dbg.error('Argument is required')
+ return dbg.cast(typestr, dbg.eval(arg))
# Extension commands. ############################################
@@ -2931,8 +2966,8 @@ compile-time flag to inspect if LuaJIT is built in dual-number mode.
'''
def execute(self, arg):
- dbg.write(
- 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}\n'
+ dbg.print(
+ 'LJ_64: {LJ_64}, LJ_GC64: {LJ_GC64}, LJ_DUALNUM: {LJ_DUALNUM}'
.format(
LJ_64=LJ_64,
LJ_GC64=LJ_GC64,
@@ -2964,9 +2999,7 @@ https://github.com/tarantool/tarantool/wiki/LuaJIT-Bytecodes.
'''
def execute(self, arg):
- dbg.write('{}\n'.format(
- dump_bc(dbg.cast('BCIns *', dbg.eval(arg))[0])
- ))
+ dbg.print(dump_bc(cast_command_arg('BCIns *', arg)[0]))
class LJDumpCType(dbg.LJBase):
@@ -2978,9 +3011,7 @@ and dumps the ID and the name for this C data type.
'''
def execute(self, arg):
- dbg.write('{}\n'.format(
- dump_ctype(dbg.cast('CType *', dbg.eval(arg)))
- ))
+ dbg.print(dump_ctype(cast_command_arg('CType *', arg)))
class LJDumpFunc(dbg.LJBase):
@@ -3005,7 +3036,7 @@ function:
'''
def execute(self, arg):
- dbg.write('{}'.format(dump_func(dbg.cast('GCfuncC *', dbg.eval(arg)))))
+ dbg.print(dump_func(cast_command_arg('GCfuncC *', arg)))
class LJGC(dbg.LJBase):
@@ -3029,7 +3060,7 @@ The command requires no args and dumps current GC stats:
def execute(self, arg):
g = G(L(None))
- dbg.write('GC stats: {state}\n{stats}\n'.format(
+ dbg.print('GC stats: {state}\n{stats}'.format(
state=gc_state(g),
stats=dump_gc(g)
))
@@ -3060,8 +3091,7 @@ error message occurs.
'''
def execute(self, arg):
- gcobj = dbg.cast('GCobj *', dbg.eval(arg))
- dbg.write('{}\n'.format(dump_gcobj(gcobj)))
+ dbg.print(dump_gcobj(cast_command_arg('GCobj *', arg)))
class LJDumpIR(dbg.LJBase):
@@ -3078,7 +3108,7 @@ https://github.com/tarantool/tarantool/wiki/LuaJIT-SSA-IR.
'''
def execute(self, arg):
- dbg.write('{}'.format(dump_irins(dbg.cast('IRIns *', dbg.eval(arg)))))
+ dbg.print(dump_irins(cast_command_arg('IRIns *', arg)))
class LJDumpJSlots(dbg.LJBase):
@@ -3095,9 +3125,8 @@ when there is no coroutine to be inspected in the debugged frame.
'''
def execute(self, arg):
- dbg.write('{}'.format(
- dump_jslots(dbg.cast('lua_State *', dbg.eval(arg)))
- ))
+ lstate = L(dbg.eval(arg) if arg else None)
+ dbg.print(dump_jslots(J(G(lstate))))
class LJDumpProto(dbg.LJBase):
@@ -3122,9 +3151,7 @@ function:
'''
def execute(self, arg):
- dbg.write('{}'.format(
- dump_proto(dbg.cast('GCproto *', dbg.eval(arg)))
- ))
+ dbg.print(dump_proto(cast_command_arg('GCproto *', arg)))
class LJDumpStack(dbg.LJBase):
@@ -3161,7 +3188,8 @@ If L is omitted, the main coroutine is used.
'''
def execute(self, arg):
- dbg.write('{}\n'.format(dump_stack(L(dbg.eval(arg)))))
+ lstate = L(dbg.eval(arg) if arg else None)
+ dbg.print(dump_stack(lstate))
class LJState(dbg.LJBase):
@@ -3175,13 +3203,13 @@ The command requires no args and dumps current VM and GC states:
def execute(self, arg):
g = G(L(None))
- dbg.write('{}\n'.format('\n'.join(
+ dbg.print('\n'.join(
map(lambda t: '{} state: {}'.format(*t), {
'VM': vm_state(g),
'GC': gc_state(g),
'JIT': jit_state(g),
}.items())
- )))
+ ))
class LJDumpString(dbg.LJBase):
@@ -3196,8 +3224,8 @@ is replaced with the corresponding error when decoding fails.
'''
def execute(self, arg):
- string = dbg.cast('GCstr *', dbg.eval(arg))
- dbg.write("String: {body} [{len} bytes] with hash {hash}\n".format(
+ string = cast_command_arg('GCstr *', arg)
+ dbg.print("String: {body} [{len} bytes] with hash {hash}".format(
body=strdata(string),
hash=strx64(string['hash']),
len=string['len'],
@@ -3217,7 +3245,7 @@ The command receives a GCtab address and dumps the table contents:
'''
def execute(self, arg):
- t = dbg.cast('GCtab *', dbg.eval(arg))
+ t = cast_command_arg('GCtab *', arg)
array = mref('TValue *', t['array'])
nodes = mref('struct Node *', t['node'])
mt = gcval(t['metatable'])
@@ -3227,22 +3255,22 @@ The command receives a GCtab address and dumps the table contents:
}
if mt != 0:
- dbg.write('Metatable detected: {}\n'.format(strx64(mt)))
+ dbg.print('Metatable detected: {}'.format(strx64(mt)))
- dbg.write('Array part: {} slots\n'.format(capacity['apart']))
+ dbg.print('Array part: {} slots'.format(capacity['apart']))
for i in range(capacity['apart']):
slot = array + i
- dbg.write('{ptr}: [{index}]: {value}\n'.format(
+ dbg.print('{ptr}: [{index}]: {value}'.format(
ptr=slot,
index=i,
value=dump_tvalue(slot)
))
- dbg.write('Hash part: {} nodes\n'.format(capacity['hpart']))
+ dbg.print('Hash part: {} nodes'.format(capacity['hpart']))
# See hmask comment in lj_obj.h
for i in range(capacity['hpart']):
node = nodes + i
- dbg.write('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}\n'.format(
+ dbg.print('{ptr}: {{ {key} }} => {{ {val} }}; next = {n}'.format(
ptr=node,
key=dump_tvalue(node['key']),
val=dump_tvalue(node['val']),
@@ -3280,8 +3308,7 @@ error message occurs.
'''
def execute(self, arg):
- tv = dbg.cast('TValue *', dbg.eval(arg))
- dbg.write('{}\n'.format(dump_tvalue(tv)))
+ dbg.print(dump_tvalue(cast_command_arg('TValue *', arg)))
class LJDumpTrace(dbg.LJBase):
@@ -3300,10 +3327,7 @@ Trace may be preceded with /FLAGS:
def execute(self, arg):
arg, flags = dbg.extract_flags(arg, 'rs')
- dbg.write('{}'.format(dump_trace(
- dbg.cast('GCtrace *', dbg.eval(arg)),
- flags
- )))
+ dbg.print(dump_trace(cast_command_arg('GCtrace *', arg), flags))
def load(event=None):
diff --git a/test/tarantool-debugger-tests/debug-extension-tests.py b/test/tarantool-debugger-tests/debug-extension-tests.py
index ab44121c..fe8c5028 100644
--- a/test/tarantool-debugger-tests/debug-extension-tests.py
+++ b/test/tarantool-debugger-tests/debug-extension-tests.py
@@ -19,30 +19,6 @@ LLDB = 'lldb' in DEBUGGER
EXTENSION = EXTENSION_PATH + '/luajit_dbg.py'
TIMEOUT = 10
-if LLDB:
- INFERIOR_ARGS = '--'
- LOAD_EXTENSION = 'command script import ' + EXTENSION
- PROCESS_RUN = (
- # Prevent errors in case when running tests in Docker.
- 'settings set target.disable-aslr false\n'
- 'process launch'
- )
- # Don't run any initialization scripts.
- RUN_CMD_FILE = [
- '--batch',
- '--no-lldbinit',
- '--no-use-colors',
- '--source-quietly',
- '--source'
- ]
-else:
- # GDB.
- INFERIOR_ARGS = '--args'
- LOAD_EXTENSION = 'source ' + EXTENSION
- PROCESS_RUN = 'run'
- # Don't run any initialization scripts.
- RUN_CMD_FILE = ['--batch', '--nx', '--quiet', '--command']
-
TEST_VERBOSE = os.getenv('DEBUGGER_TEST_VERBOSE', default=False)
RX_ADDR = r'0x[a-f0-9]+'
@@ -60,7 +36,7 @@ def persist(data):
return tmp
-def execute_process(cmd, timeout=TIMEOUT):
+def execute_process_raw(cmd, timeout=TIMEOUT):
if LEGACY:
# XXX: The Python 2.7 version of `subprocess.Popen`
# doesn't have a timeout option, so the required
@@ -68,12 +44,12 @@ def execute_process(cmd, timeout=TIMEOUT):
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
# 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 = Timer(timeout, process.kill)
timer.start()
stdout, _ = process.communicate()
timer.cancel()
@@ -87,16 +63,71 @@ def execute_process(cmd, timeout=TIMEOUT):
process = subprocess.run(
cmd,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
# This prevents sending of SIGSTTOU to the test when
# running by `make'. Stdin is unused anyway.
stdin=subprocess.DEVNULL,
universal_newlines=True,
- timeout=TIMEOUT
+ timeout=timeout,
)
return process.stdout
+if LLDB:
+ INFERIOR_ARGS = '--'
+ LOAD_EXTENSION = 'command script import ' + EXTENSION
+ PROCESS_RUN = (
+ # Prevent errors in case when running tests in Docker.
+ 'settings set target.disable-aslr false\n'
+ 'process launch'
+ )
+ # Don't run any initialization scripts.
+ RUN_CMD_FILE = [
+ '--batch',
+ '--no-lldbinit',
+ '--no-use-colors',
+ '--source-quietly',
+ '--source'
+ ]
+ SET_EXEC = 'target create ' + LUAJIT_BINARY
+ SET_RUN_ARGS = 'settings set target.run-args'
+
+ def execute_process(cmd, timeout=TIMEOUT):
+ # While executing extension commands LLDB might produce
+ # errors that cannot be caught in extension. The internal
+ # parser does some processing in background and might
+ # emit an error message into stderr asynchronously. Also
+ # the internal parser has a kind of fallback mechanism
+ # that might resolve the problem, but is not able to
+ # "cancel" the error message which might fail tests in
+ # this case.
+ #
+ # List of regex that specifies lines to be wiped out from
+ # the LLDB output. In case additional cleaning is needed,
+ # just append this list with the correponding regex (use
+ # as strict regex as possible in order to avoid accidental
+ # removing of relevant data).
+ RX_LINES_TO_REMOVE = [
+ # This error occurs while parsing `union FPRCBArg` (it
+ # looks like either offset of `f`` field or size of
+ # `union FPRCBArg` is identified incorrectly).
+ r".*DW_TAG_member 'f' refers to type \w+ which extends beyond.*",
+ ]
+ raw_out = execute_process_raw(cmd, timeout)
+ return re.sub(r'^({})\n'.format('|'.join(RX_LINES_TO_REMOVE)),
+ '', raw_out, flags=re.M)
+
+else:
+ # GDB.
+ INFERIOR_ARGS = '--args'
+ LOAD_EXTENSION = 'source ' + EXTENSION
+ PROCESS_RUN = 'run'
+ # Don't run any initialization scripts.
+ RUN_CMD_FILE = ['--batch', '--nx', '--quiet', '--command']
+ SET_EXEC = 'file ' + LUAJIT_BINARY
+ SET_RUN_ARGS = 'set args'
+ execute_process = execute_process_raw
+
IS_DUALNUM = execute_process([
LUAJIT_BINARY, '-e', "print(require('ffi').abi('dualnum'))"
]).strip() == 'true'
@@ -129,42 +160,38 @@ else:
class TestCaseBase(unittest.TestCase):
@classmethod
- def construct_cmds(cls):
- return '\n'.join([
+ def construct_cmds(cls, deferred, *run_args):
+ cmds = [
+ SET_EXEC,
+ SET_RUN_ARGS + ' ' + ' '.join(run_args),
'b {loc}'.format(loc=cls.location),
PROCESS_RUN,
'n',
- LOAD_EXTENSION,
cls.extension_cmds.strip(),
- 'q',
- ])
+ ]
+ cmds.insert(0 if deferred else 1, LOAD_EXTENSION)
+ return '\n'.join(cmds)
- @classmethod
- def setUpClass(cls):
- cmd_file = persist(cls.construct_cmds())
- script_file = persist(cls.lua_script)
+ def check(self, deferred=False):
+ script_file = persist(self.lua_script)
+ cmd_file = persist(self.construct_cmds(deferred, script_file.name))
process_cmd = [
DEBUGGER,
*RUN_CMD_FILE,
cmd_file.name,
- INFERIOR_ARGS,
- LUAJIT_BINARY,
- script_file.name,
]
if TEST_VERBOSE:
- print('# Test name: {}'.format(cls.__name__))
+ print('# Test name: {}'.format(self.__class__.__name__))
print('# Test command: {}'.format(' '.join(process_cmd)))
- cls.output = execute_process(process_cmd)
+ output = execute_process(process_cmd)
if TEST_VERBOSE:
- print('# Command output:\n{}'.format(cls.output))
+ print('# Command output:\n{}'.format(output))
cmd_file.close()
script_file.close()
-
- def check(self):
if LEGACY:
- self.assertRegexpMatches(self.output, self.pattern.strip())
+ self.assertRegexpMatches(output, self.pattern.strip())
else:
- self.assertRegex(self.output, self.pattern.strip())
+ self.assertRegex(output, self.pattern.strip())
# Test that the emitted debug information supports macro
@@ -1051,8 +1078,65 @@ class TestLJCTypeBase(TestCaseBase):
pattern = r'\[\d+\] <int>'
-for test_cls in TestCaseBase.__subclasses__():
+class TestMissingArgBase(TestCaseBase):
+ location = 'lj_cf_print'
+ lua_script = 'print(1)'
+ pattern = r'Argument is required\n'
+
+ @classmethod
+ def setUpClass(cls):
+ cls.extension_cmds = '{}\n'.format(cls.cmd_name)
+ super(TestMissingArgBase, cls).setUpClass()
+
+
+class TestMissingArgLJFunc(TestMissingArgBase):
+ cmd_name = 'lj-func'
+
+
+class TestMissingArgLJTValue(TestMissingArgBase):
+ cmd_name = 'lj-tv'
+
+
+class TestMissingArgLJBC(TestMissingArgBase):
+ cmd_name = 'lj-bc'
+
+
+class TestMissingArgLJStr(TestMissingArgBase):
+ cmd_name = 'lj-str'
+
+
+class TestMissingArgLJTable(TestMissingArgBase):
+ cmd_name = 'lj-tab'
+
+
+class TestMissingArgLJProto(TestMissingArgBase):
+ cmd_name = 'lj-proto'
+
+
+class TestMissingArgLJGCO(TestMissingArgBase):
+ cmd_name = 'lj-gco'
+
+
+class TestMissingArgLJCType(TestMissingArgBase):
+ cmd_name = 'lj-ctype'
+
+
+def get_leaf_subclasses(cls):
+ subclasses = cls.__subclasses__()
+ if not subclasses:
+ yield cls
+ else:
+ for sub in subclasses:
+ for leaf in get_leaf_subclasses(sub):
+ yield leaf
+
+
+for test_cls in get_leaf_subclasses(TestCaseBase):
test_cls.test = lambda self: self.check()
+ test_cls.test_with_deferred_loading = \
+ unittest.skipIf(LLDB, "not supported for LLDB")(
+ lambda self: self.check(True)
+ )
if __name__ == '__main__':
unittest.main(verbosity=2)
--
2.55.0
next prev parent reply other threads:[~2026-11-12 23:43 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 23:42 [Tarantool-patches] [PATCH luajit 0/4] dbg: avoid hardcoded enums Mikhail Elhimov via Tarantool-patches
2026-11-12 23:42 ` Mikhail Elhimov via Tarantool-patches [this message]
2026-11-12 23:42 ` [Tarantool-patches] [PATCH luajit 2/4] " Mikhail Elhimov via Tarantool-patches
2026-11-12 23:42 ` [Tarantool-patches] [PATCH luajit 3/4] dbg: display fast function name along with ffid Mikhail Elhimov via Tarantool-patches
2026-11-12 23:42 ` [Tarantool-patches] [PATCH luajit 4/4] dbg: remove wrong GC state Mikhail Elhimov via Tarantool-patches
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0a9a648552eb3a3bbaa9639e0d567ae008cb0e14.1794522533.git.m.elhimov@vk.team \
--to=tarantool-patches@dev.tarantool.org \
--cc=e.temirgaleev@tarantool.org \
--cc=m.elhimov@vk.team \
--cc=sergeyb@tarantool.org \
--cc=skaplun@tarantool.org \
--subject='Re: [Tarantool-patches] [PATCH luajit 1/4] dbg: clean up and unification' \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox