Revert "Retired: Fails to install: https://pagure.io/releng/issue/12969"
This reverts commit e350a6d823.
Signed-off-by: Fedora Release Engineering <releng@fedoraproject.org>
347 lines
11 KiB
Python
Executable file
347 lines
11 KiB
Python
Executable file
import os
|
|
|
|
from avocado import Test
|
|
from avocado.utils import gdb, genio, process
|
|
|
|
|
|
class GdbTest(Test):
|
|
|
|
"""
|
|
Execute the gdb test
|
|
|
|
:avocado: tags=requires_c_compiler
|
|
"""
|
|
|
|
VALID_CMDS = ["-list-target-features",
|
|
"-break-info",
|
|
"-break-list",
|
|
"-thread-info",
|
|
"-stack-info-frame",
|
|
"-stack-info-depth"]
|
|
|
|
INVALID_CMDS = ["-foobar",
|
|
"-magic8ball",
|
|
"-find-me-the-bug",
|
|
"-auto-debug-it"]
|
|
|
|
def setUp(self):
|
|
self.return99_binary_path = os.path.join(self.teststmpdir, 'return99')
|
|
if not os.path.exists(self.return99_binary_path):
|
|
return99_source_path = self.get_data('return99.c')
|
|
if return99_source_path is None:
|
|
self.cancel('Test is missing data file "return99.c"')
|
|
process.system(f'gcc -O0 -g {return99_source_path} -o {self.return99_binary_path}')
|
|
|
|
self.segfault_binary_path = os.path.join(self.teststmpdir, 'segfault')
|
|
if not os.path.exists(self.segfault_binary_path):
|
|
segfault_source_path = self.get_data('segfault.c')
|
|
if segfault_source_path is None:
|
|
self.cancel('Test is missing data file "segfault.c"')
|
|
process.system(f'gcc -O0 -g {segfault_source_path} -o {self.segfault_binary_path}')
|
|
|
|
@staticmethod
|
|
def is_process_alive(process): # pylint: disable=W0621
|
|
"""
|
|
Checks if a process is still alive
|
|
|
|
:param process: a :class:`subprocess.POpen` instance
|
|
:type process: :class:`subprocess.POpen`
|
|
:returns: True or False
|
|
:rtype: bool
|
|
"""
|
|
return process.poll() is None
|
|
|
|
def test_start_exit(self):
|
|
"""
|
|
Tests execution of multiple GDB instances without any blocking or race
|
|
"""
|
|
self.log.info("Testing execution of multiple GDB instances")
|
|
process_count = 10
|
|
gdb_instances = []
|
|
for i in range(0, process_count):
|
|
gdb_instances.append(gdb.GDB())
|
|
|
|
for i in range(0, process_count):
|
|
self.assertEqual(gdb_instances[i].exit(), 0)
|
|
|
|
def test_existing_commands_raw(self):
|
|
"""
|
|
Tests the GDB response to commands that exist and to those that do not
|
|
"""
|
|
g = gdb.GDB()
|
|
self.log.info("Testing existing (valid) GDB commands using raw commands")
|
|
for cmd in self.VALID_CMDS:
|
|
info_cmd = f"-info-gdb-mi-command {cmd[1:]}"
|
|
r = g.cmd(info_cmd)
|
|
self.assertEqual(r.result.result.command.exists, 'true')
|
|
|
|
self.log.info("Testing non-existing (invalid) GDB commands using raw "
|
|
"commands")
|
|
for cmd in self.INVALID_CMDS:
|
|
info_cmd = f"-info-gdb-mi-command {cmd[1:]}"
|
|
r = g.cmd(info_cmd)
|
|
self.assertEqual(r.result.result.command.exists, 'false')
|
|
|
|
def test_existing_commands(self):
|
|
g = gdb.GDB()
|
|
|
|
self.log.info("Testing existing (valid) GDB commands using utility "
|
|
"methods")
|
|
for cmd in self.VALID_CMDS:
|
|
self.assertTrue(g.cmd_exists(cmd))
|
|
g.cmd(cmd)
|
|
|
|
self.log.info("Testing non-existing (invalid) GDB commands using "
|
|
"utility methods")
|
|
for cmd in self.INVALID_CMDS:
|
|
self.assertFalse(g.cmd_exists(cmd))
|
|
|
|
def test_load_set_breakpoint_run_exit_raw(self):
|
|
"""
|
|
Test a common GDB cycle using raw commands: load, set break, run, exit
|
|
"""
|
|
self.log.info("Testing that GDB loads a file and sets a breakpoint")
|
|
g = gdb.GDB()
|
|
|
|
file_cmd = f"-file-exec-and-symbols {self.return99_binary_path}"
|
|
r = g.cmd(file_cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
break_cmd = "-break-insert 5"
|
|
r = g.cmd(break_cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
self.assertEqual(r.result.result.bkpt.number, '1')
|
|
self.assertEqual(r.result.result.bkpt.enabled, 'y')
|
|
|
|
break_del_cmd = "-break-delete 1"
|
|
r = g.cmd(break_del_cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
run_cmd = "-exec-run"
|
|
r = g.cmd(run_cmd)
|
|
self.assertEqual(r.result.class_, 'running')
|
|
|
|
g.cmd("-gdb-exit")
|
|
self.assertEqual(g.process.wait(), 0)
|
|
|
|
def test_load_set_breakpoint_run_exit(self):
|
|
"""
|
|
Test a common GDB cycle: load, set break, delete break, run, exit
|
|
"""
|
|
self.log.info("Testing a common GDB cycle")
|
|
g = gdb.GDB()
|
|
g.set_file(self.return99_binary_path)
|
|
g.set_break("5")
|
|
g.del_break(1)
|
|
g.run()
|
|
g.exit()
|
|
|
|
def test_generate_core(self):
|
|
"""
|
|
Load a file that will cause a segfault and produce a core dump
|
|
"""
|
|
self.log.info("Testing that a core dump will be generated")
|
|
|
|
g = gdb.GDB()
|
|
file_cmd = f"-file-exec-and-symbols {self.segfault_binary_path}"
|
|
r = g.cmd(file_cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
run_cmd = "-exec-run"
|
|
r = g.cmd(run_cmd)
|
|
self.assertEqual(r.result.class_, 'running')
|
|
|
|
other_messages = g.read_until_break()
|
|
core_path = None
|
|
for msg in other_messages:
|
|
parsed_msg = gdb.parse_mi(msg.decode())
|
|
if (hasattr(parsed_msg, 'class_') and
|
|
(parsed_msg.class_ == 'stopped') and
|
|
(parsed_msg.result.signal_name == 'SIGSEGV')):
|
|
core_path = f"{self.segfault_binary_path}.core"
|
|
gcore_cmd = f'gcore {core_path}'
|
|
gcore_cmd = gdb.encode_mi_cli(gcore_cmd)
|
|
r = g.cmd(gcore_cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
self.assertTrue(os.path.exists(core_path))
|
|
g.exit()
|
|
|
|
def test_set_multiple_break(self):
|
|
"""
|
|
Tests that multiple breakpoints do not interfere with each other
|
|
"""
|
|
self.log.info("Testing setting multiple breakpoints")
|
|
g = gdb.GDB()
|
|
g.set_file(self.return99_binary_path)
|
|
g.set_break('empty')
|
|
g.set_break('7')
|
|
g.exit()
|
|
|
|
def test_disconnect_raw(self):
|
|
"""
|
|
Connect/disconnect repeatedly from a remote debugger using raw commands
|
|
"""
|
|
self.log.info("Testing connecting and disconnecting repeatedly using "
|
|
"raw commands")
|
|
s = gdb.GDBServer()
|
|
g = gdb.GDB()
|
|
|
|
# Do 100 cycle of target (kind of connects) and disconnects
|
|
for _ in range(0, 100):
|
|
cmd = f'-target-select extended-remote :{s.port}'
|
|
r = g.cmd(cmd)
|
|
self.assertEqual(r.result.class_, 'connected')
|
|
r = g.cmd('-target-disconnect')
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
# manual server shutdown
|
|
cmd = f'-target-select extended-remote :{s.port}'
|
|
r = g.cmd(cmd)
|
|
self.assertEqual(r.result.class_, 'connected')
|
|
r = g.cli_cmd('monitor exit')
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
g.exit()
|
|
s.exit()
|
|
|
|
def test_disconnect(self):
|
|
"""
|
|
Connect/disconnect repeatedly from a remote debugger using utilities
|
|
"""
|
|
self.log.info("Testing connecting and disconnecting repeatedly")
|
|
s = gdb.GDBServer()
|
|
g = gdb.GDB()
|
|
|
|
for _ in range(0, 100):
|
|
r = g.connect(s.port)
|
|
self.assertEqual(r.result.class_, 'connected')
|
|
r = g.disconnect()
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
g.exit()
|
|
s.exit()
|
|
|
|
def test_remote_exec(self):
|
|
"""
|
|
Tests execution on a remote target
|
|
"""
|
|
self.log.info("Testing execution on a remote target")
|
|
hit_breakpoint = False
|
|
|
|
s = gdb.GDBServer()
|
|
g = gdb.GDB()
|
|
|
|
cmd = f'-file-exec-and-symbols {self.return99_binary_path}'
|
|
r = g.cmd(cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
cmd = f'set remote exec-file {self.return99_binary_path}'
|
|
r = g.cmd(cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
cmd = f"-break-insert {'main'}"
|
|
r = g.cmd(cmd)
|
|
self.assertEqual(r.result.class_, 'done')
|
|
|
|
r = g.cmd('-exec-run')
|
|
|
|
other_messages = g.read_until_break()
|
|
for msg in other_messages:
|
|
parsed_msg = gdb.parse_mi(msg.decode())
|
|
if (hasattr(parsed_msg, 'class_') and
|
|
parsed_msg.class_ == 'stopped' and
|
|
parsed_msg.result.reason == 'breakpoint-hit'):
|
|
hit_breakpoint = True
|
|
|
|
self.assertTrue(hit_breakpoint)
|
|
g.exit()
|
|
s.exit()
|
|
|
|
def test_stream_messages(self):
|
|
"""
|
|
Tests if the expected response appears in the result stream messages
|
|
"""
|
|
self.log.info("Testing that messages appears in the result stream")
|
|
g = gdb.GDB()
|
|
r = g.cmd("-gdb-version")
|
|
self.assertIn("GNU GPL version", r.get_stream_messages_text())
|
|
|
|
def test_connect_multiple_clients(self):
|
|
"""
|
|
Tests two or more connections to the same server raise an exception
|
|
"""
|
|
self.log.info("Testing that multiple clients cannot connect at once")
|
|
s = gdb.GDBServer()
|
|
c1 = gdb.GDB()
|
|
c1.connect(s.port)
|
|
c2 = gdb.GDB()
|
|
with self.assertRaises(gdb.UnexpectedResponseError):
|
|
c2.connect(s.port)
|
|
s.exit()
|
|
|
|
def test_server_exit(self):
|
|
"""
|
|
Tests that the server is shutdown by using a monitor exit command
|
|
"""
|
|
self.log.info("Testing that a single server exits cleanly")
|
|
s = gdb.GDBServer()
|
|
s.exit()
|
|
self.assertFalse(self.is_process_alive(s.process))
|
|
|
|
def test_multiple_servers(self):
|
|
"""
|
|
Tests multiple server instances without any blocking or race condition
|
|
"""
|
|
self.log.info("Testing execution of multiple GDB server instances")
|
|
process_count = 10
|
|
server_instances = []
|
|
for i in range(0, process_count):
|
|
s = gdb.GDBServer()
|
|
c = gdb.GDB()
|
|
c.connect(s.port)
|
|
c.cmd('show-version')
|
|
c.disconnect()
|
|
server_instances.append(s)
|
|
|
|
for i in range(0, process_count):
|
|
self.assertTrue(self.is_process_alive(server_instances[i].process))
|
|
server_instances[i].exit()
|
|
self.assertFalse(self.is_process_alive(server_instances[i].process))
|
|
|
|
def test_server_stderr(self):
|
|
self.log.info('Testing server stderr collection')
|
|
s = gdb.GDBServer()
|
|
s.exit()
|
|
self.assertTrue(os.path.exists(s.stderr_path))
|
|
|
|
stderr_lines = genio.read_all_lines(s.stderr_path)
|
|
listening_line = f"Listening on port {s.port}"
|
|
self.assertIn(listening_line, stderr_lines)
|
|
|
|
def test_server_stdout(self):
|
|
self.log.info('Testing server stdout/stderr collection')
|
|
s = gdb.GDBServer()
|
|
c = gdb.GDB()
|
|
c.connect(s.port)
|
|
c.set_file(self.return99_binary_path)
|
|
c.run()
|
|
s.exit()
|
|
|
|
self.assertTrue(os.path.exists(s.stdout_path))
|
|
self.assertTrue(os.path.exists(s.stderr_path))
|
|
|
|
stdout_lines = genio.read_all_lines(s.stdout_path)
|
|
self.assertIn("return 99", stdout_lines)
|
|
|
|
@staticmethod
|
|
def test_remote():
|
|
"""
|
|
Tests GDBRemote interaction with a GDBServer
|
|
"""
|
|
s = gdb.GDBServer()
|
|
r = gdb.GDBRemote('127.0.0.1', s.port)
|
|
r.connect()
|
|
r.cmd(b"qSupported")
|
|
r.cmd(b"qfThreadInfo")
|
|
s.exit()
|