From db8ae5f8216a555853c499e5e8a7ce3fde024462 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Sat, 17 Feb 2024 19:01:44 +1300 Subject: [PATCH] Make breakpoint.run test run against LLDB as well as GDB --- src/test/breakpoint.py | 13 ++--- src/test/breakpoint.run | 2 +- src/test/util.py | 106 +++++++++++++++++++++++++++------------- src/test/util.sh | 43 ++++++++++++++-- 4 files changed, 119 insertions(+), 45 deletions(-) diff --git a/src/test/breakpoint.py b/src/test/breakpoint.py index d9e636c231f..55a96303602 100644 --- a/src/test/breakpoint.py +++ b/src/test/breakpoint.py @@ -1,15 +1,12 @@ from util import * -send_gdb('b C') -expect_gdb('Breakpoint 1') - -send_gdb('c') +breakpoint_at('C', 1) +cont() expect_rr('calling C') +expect_breakpoint_stop(1) -expect_gdb('Breakpoint 1(.*) C') - -send_gdb('bt') -expect_gdb('#0[^C]+C[^#]+#1[^B]+B[^#]+#2[^A]+A[^#]+#3[^m]+main') +backtrace() +expect_debugger('#0[^C]+C[^#]+#1[^B]+B[^#]+#2[^A]+A[^#]+#3.+main') ok() diff --git a/src/test/breakpoint.run b/src/test/breakpoint.run index 0e9b81df833..3eadb5a637f 100644 --- a/src/test/breakpoint.run +++ b/src/test/breakpoint.run @@ -1,2 +1,2 @@ source `dirname $0`/util.sh -debug_test_gdb_only +debug_test diff --git a/src/test/util.py b/src/test/util.py index 7b6c5d08b32..da9b7d862ea 100644 --- a/src/test/util.py +++ b/src/test/util.py @@ -1,18 +1,21 @@ import pexpect, re, signal, sys, time -__all__ = [ 'expect_gdb', 'send_gdb','expect_rr', 'expect_list', - 'restart_replay', 'interrupt_gdb', 'ok', - 'failed', 'iterlines_both', 'last_match', 'get_exe_arch', - 'get_gdb_version' ] +__all__ = [ 'expect_rr', 'expect_list', 'expect_debugger', + 'restart_replay', 'interrupt_gdb', 'expect_gdb', 'send_gdb', + 'ok', 'failed', 'iterlines_both', 'last_match', 'get_exe_arch', + 'get_gdb_version', + 'breakpoint_at', 'cont', 'backtrace', + 'expect_breakpoint_stop' ] # Don't use python timeout. Use test-monitor timeout instead. TIMEOUT_SEC = 10000 # The debugger and rr are part of the same process tree, so they share # stdin/stdout. child = None +debugger_type = 'GDB' # Public API -def expect_gdb(what): +def expect_debugger(what): expect(child, what) def expect_list(pats): @@ -28,11 +31,11 @@ def failed(why, e=None): clean_up() sys.exit(1) -def interrupt_gdb(): +def interrupt_debugger(): try: child.kill(signal.SIGINT) except Exception as e: - failed('interrupting gdb', e) + failed('interrupting debugger', e) expect_gdb('stopped.') def iterlines_both(): @@ -41,6 +44,22 @@ def iterlines_both(): def last_match(): return child.match +def expect_gdb(what): + assert debugger_type == 'GDB' + expect_debugger(what) + +def interrupt_gdb(): + assert debugger_type == 'GDB' + interrupt_debugger() + +def send_gdb(what): + assert debugger_type == 'GDB' + send(child, "%s\n"%what) + +def send_lldb(what): + assert debugger_type == 'LLDB' + send(child, "%s\n"%what) + # Restarts and continues execution def restart_replay(event=0): if event: @@ -56,14 +75,46 @@ def restart_replay(event=0): expect_rr('stopped') send_gdb('c') -def send_gdb(what): - send(child, "%s\n"%what) +def breakpoint_at(location, expected_number): + send_debugger('b %s'%location, 'b %s'%location) + expect_debugger('Breakpoint %d' % expected_number) + +def cont(): + send_debugger('continue', 'continue') + +def backtrace(): + send_debugger('bt', 'thread backtrace') + +def expect_breakpoint_stop(number): + if debugger_type == 'GDB': + expect_debugger("Breakpoint %d"%number) + else: + expect_debugger("stop reason = breakpoint %d"%number) + +def send_debugger(gdb_cmd, lldb_cmd): + if debugger_type == 'GDB': + send_gdb(gdb_cmd) + else: + send_lldb(lldb_cmd) def ok(): - send_gdb('q') - send_gdb('y') + send_debugger('quit', 'quit') + send_debugger('y', 'y') clean_up() +def get_exe_arch(): + send_gdb('show architecture') + expect_gdb(r'The target architecture is set (automatically|to "auto") \(currently "?([0-9a-z:-]+)"?\)\.?') + global child + return child.match.group(2) + +def get_gdb_version(): + '''Return the gdb version''' + send_gdb('python print(gdb.VERSION)') + expect_gdb(r'(\d+.\d+)') + global child + return float(child.match.group(1)) + # Internal helpers def clean_up(): global child @@ -88,25 +139,6 @@ def expect(prog, what): except Exception as e: failed('expecting "%s"'% (what), e) -def get_exe_arch(): - send_gdb('show architecture') - expect_gdb(r'The target architecture is set (automatically|to "auto") \(currently "?([0-9a-z:-]+)"?\)\.?') - global child - return child.match.group(2) - -def get_rr_cmd(): - '''Return the command that should be used to invoke rr, as the tuple - (executable, array-of-args)''' - rrargs = sys.argv[1:] - return (rrargs[0], rrargs[1:]) - -def get_gdb_version(): - '''Return the gdb version''' - send_gdb('python print(gdb.VERSION)') - expect_gdb(r'(\d+.\d+)') - global child - return float(child.match.group(1)) - def send(prog, what): try: prog.send(what) @@ -115,11 +147,19 @@ def send(prog, what): def set_up(): global child + global debugger_type + args = sys.argv[1:] + log_file = 'gdb_rr.log' + if args[0] == '--lldb': + debugger_type = 'LLDB' + args = args[1:] + ['-d', 'lldb', '-o', '--no-use-colors'] + log_file = 'lldb_rr.log' try: - child = pexpect.spawn(*get_rr_cmd(), codec_errors='ignore', timeout=TIMEOUT_SEC, encoding='utf-8', logfile=open('gdb_rr.log', 'w')) + child = pexpect.spawn(args[0], args[1:], codec_errors='ignore', + timeout=TIMEOUT_SEC, encoding='utf-8', logfile=open(log_file, 'w')) child.delaybeforesend = 0 - expect_gdb(r'\(rr\)') + expect_debugger(r'\(rr\)') except Exception as e: - failed('initializing rr and gdb', e) + failed('initializing rr and debugger', e) set_up() diff --git a/src/test/util.sh b/src/test/util.sh index 5dc5e1c29d1..623126c6964 100644 --- a/src/test/util.sh +++ b/src/test/util.sh @@ -336,6 +336,28 @@ function do_ps { psflags=$1 $RR_EXE $GLOBAL_OPTIONS ps $psflags } +# debug_lldb_only [replay-args] +# +# Load the "expect" script to drive replay of the recording of |exe|. +# Only LLDB is tested. +function debug_lldb_only { expectscript=$1; replayargs=$2 + _RR_TRACE_DIR="$workdir" test-monitor $TIMEOUT debug.err \ + python3 $TESTDIR/$expectscript.py --lldb \ + $RR_EXE $GLOBAL_OPTIONS replay -o-S -o$TESTDIR/test_setup.lldb $replayargs + if [[ $? == 0 ]]; then + passed_msg lldb + else + failed "debug script failed (lldb)" + echo "--------------------------------------------------" + echo "lldb_rr.log:" + cat lldb_rr.log + echo "--------------------------------------------------" + echo "debug.err:" + cat debug.err + echo "--------------------------------------------------" + fi +} + # debug_gdb_only [replay-args] # # Load the "expect" script to drive replay of the recording of |exe|. @@ -345,9 +367,9 @@ function debug_gdb_only { expectscript=$1; replayargs=$2 python3 $TESTDIR/$expectscript.py \ $RR_EXE $GLOBAL_OPTIONS replay -o-n -o-ix -o$TESTDIR/test_setup.gdb $replayargs if [[ $? == 0 ]]; then - passed + passed_msg gdb else - failed "debug script failed" + failed "debug script failed (lldb)" echo "--------------------------------------------------" echo "gdb_rr.log:" cat gdb_rr.log @@ -367,6 +389,10 @@ function passed { echo "Test '$TESTNAME' PASSED" } +function passed_msg { msg=$1 + echo "Test '$TESTNAME' PASSED ($msg)" +} + function just_check_replay_err { if [[ $(cat replay.err) != "" ]]; then failed ": error during replay:" @@ -470,11 +496,22 @@ function compare_test { token=$1; replayflags=$2; check $token } -# debug_test_gdb_only +# debug_test # # Record the test name passed to |util.sh|, then replay the recording # using the "expect" script $test-name.py, which is responsible for # computing test pass/fail. +function debug_test { + record $TESTNAME + debug_gdb_only $TEST_PREFIX$TESTNAME_NO_BITNESS + debug_lldb_only $TEST_PREFIX$TESTNAME_NO_BITNESS +} + +# debug_test_gdb_only +# +# Record the test name passed to |util.sh|, then replay the recording +# using the "expect" script $test-name.py, which is responsible for +# computing test pass/fail. Only GDB is tested. function debug_test_gdb_only { record $TESTNAME debug_gdb_only $TEST_PREFIX$TESTNAME_NO_BITNESS