提交 37681d8f 编写于 作者: L Lucas Meneghel Rodrigues

Merge pull request #234 from clebergnu/gdb_basic_stdout

GDB stdout support
...@@ -19,6 +19,7 @@ Module that provides communication with GDB via its GDB/MI interpreter ...@@ -19,6 +19,7 @@ Module that provides communication with GDB via its GDB/MI interpreter
import os import os
import time import time
import fcntl import fcntl
import tempfile
import subprocess import subprocess
from avocado.utils import network from avocado.utils import network
...@@ -451,12 +452,18 @@ class GDBServer(object): ...@@ -451,12 +452,18 @@ class GDBServer(object):
def __init__(self): def __init__(self):
self.port = network.find_free_port(*self.PORT_RANGE) self.port = network.find_free_port(*self.PORT_RANGE)
prefix = 'avocado_gdbserver_%s_' % self.port
_, self.stdout_path = tempfile.mkstemp(prefix=prefix + 'stdout_')
self.stdout = open(self.stdout_path, 'w')
_, self.stderr_path = tempfile.mkstemp(prefix=prefix + 'stderr_')
self.stderr = open(self.stderr_path, 'w')
args = self.ARGS[:] args = self.ARGS[:]
args.append(":%s" % self.port) args.append(":%s" % self.port)
self.process = subprocess.Popen(args, self.process = subprocess.Popen(args,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=self.stdout,
stderr=subprocess.PIPE, stderr=self.stderr,
close_fds=True) close_fds=True)
def exit(self, force=True): def exit(self, force=True):
...@@ -488,3 +495,5 @@ class GDBServer(object): ...@@ -488,3 +495,5 @@ class GDBServer(object):
temp_client.process.kill() temp_client.process.kill()
temp_client.process.wait() temp_client.process.wait()
self.process.wait() self.process.wait()
self.stdout.close()
self.stderr.close()
...@@ -452,7 +452,27 @@ class GDBSubProcess(object): ...@@ -452,7 +452,27 @@ class GDBSubProcess(object):
Runs a subprocess inside the GNU Debugger Runs a subprocess inside the GNU Debugger
''' '''
def __init__(self, cmd, verbose=True, record_stream_files=False): def __init__(self, cmd, verbose=True, allow_output_check='all'):
"""
Creates the subprocess object, stdout/err, reader threads and locks.
:param cmd: Command line to run.
:type cmd: str
:param verbose: Whether to log the command run and stdout/stderr.
Currently unused and provided for compatibility only.
:type verbose: bool
:param allow_output_check: Whether to log the command stream outputs
(stdout and stderr) in the test stream
files. Valid values: 'stdout', for
allowing only standard output, 'stderr',
to allow only standard error, 'all',
to allow both standard output and error
(default), and 'none', to allow
none to be recorded. Currently unused and
provided for compatibility only.
:type allow_output_check: str
"""
self.cmd = cmd self.cmd = cmd
self.args = shlex.split(cmd) self.args = shlex.split(cmd)
...@@ -658,10 +678,31 @@ class GDBSubProcess(object): ...@@ -658,10 +678,31 @@ class GDBSubProcess(object):
self.gdb.set_break(b, ignore_error=True) self.gdb.set_break(b, ignore_error=True)
result = self.gdb.run(self.args[1:]) result = self.gdb.run(self.args[1:])
# Collect gdbserver stdout and stderr file information for debugging
# based on its process ID and stream (stdout or stderr)
current_test = runtime.CURRENT_TEST
if current_test is not None:
stdout_name = 'gdbserver.%s.stdout' % self.gdb_server.process.pid
stdout_path = os.path.join(current_test.logdir, stdout_name)
stderr_name = 'gdbserver.%s.stderr' % self.gdb_server.process.pid
stderr_path = os.path.join(current_test.logdir, stderr_name)
while True: while True:
r = self.wait_for_exit() r = self.wait_for_exit()
if r: if r:
self.gdb.disconnect() self.gdb.disconnect()
# Now collect the gdbserver stdout and stderr file themselves
# and populate the CommandResult stdout and stderr
if current_test is not None:
if os.path.exists(self.gdb_server.stdout_path):
shutil.copy(self.gdb_server.stdout_path, stdout_path)
self.result.stdout = open(stdout_path, 'r').read()
if os.path.exists(self.gdb_server.stderr_path):
shutil.copy(self.gdb_server.stderr_path, stderr_path)
self.result.stderr = open(stderr_path, 'r').read()
self.gdb_server.exit() self.gdb_server.exit()
return self.result return self.result
......
...@@ -15,18 +15,48 @@ avocado man page. ...@@ -15,18 +15,48 @@ avocado man page.
Caveats Caveats
------- -------
Currently there is one big caveat when running binaries inside GDB: there's Currently, when using the Avocado GDB plugin, that is, when using the
no way to perform input/output with the process `STDIN`, `STDOUT` and `STDERR`. `--gdb-run-bin` option, there are some caveats you should be aware of:
There are a couple of reasons for that: * It is not currently compatible with Avocado's `--output-check-record` feature
* There's no way to perform proper input to the process, that is, manipulate its `STDIN`
* The process `STDERR` content is mixed with the content generated by `gdbserver` on its
own `STDERR` (because they are in fact, the same thing)
* The process that runs inside GDB has, by default, the same controlling `tty` of the `gdb` process that avocado *initially* runs. When avocado reaches a given breakpoint, it pauses the tests and allows the user to run another `gdb` process. This second `gdb` process is still connected to the same running process by means of a separate `gdbserver`. At this point, the process is still using the original `tty`. But, you can still depend on the process `STDOUT`, as exemplified by this fictional
test::
* Even when using a single `tty`, there's no reliable way of separating multiple streams of data, say from `gdb` and from your application, or even `STDOUT` and `STDERR` streams from either one. from avocado import test
from avocado.utils import process
The complete resolution to this caveat suggests the creating of a Pseudo `tty` class hello_output_test(test.Test):
for the process running inside GDB, so that the process is the only entity reading
and writing to that `tty`. This will be addressed in future avocado versions. def action(self):
result = process.run("/path/to/hello", ignore_status=True)
self.assertIn("hello\n", result.stdout)
If run under GDB or not, `result.stdout` behavior and content is expected to be the same.
Reasons for the caveats
~~~~~~~~~~~~~~~~~~~~~~~
There are a two basic reasons for the mentioned caveats:
* The architecture of Avocado's GDB feature
* GDB's own behavior and limitations
When using the Avocado GDB plugin, that is, `--gdb-run-bin`, avocado runs a `gdbserver` instance
transparently and controls it by means of a `gdb` process. When a given event happens, say a
breakpoint is reached, it disconnects its own `gdb` from the server, and allows the user to use
a standard `gdb` to connect to the `gdbserver`. This provides a natural and seamless user experience.
But, `gdbserver` has some limitations at this point, including:
* Not being able to set a controlling `tty`
* Not separating its own `STDERR` content from the application being run
These limitations are being addressed both on Avocado and GDB, and will be resolved in future avocado
versions.
Workaround Workaround
~~~~~~~~~~ ~~~~~~~~~~
......
...@@ -356,6 +356,42 @@ class gdbtest(test.Test): ...@@ -356,6 +356,42 @@ class gdbtest(test.Test):
result = process.run(cmd, ignore_status=True) result = process.run(cmd, ignore_status=True)
self.assertEquals(result.exit_status, exp) self.assertEquals(result.exit_status, exp)
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 = open(s.stderr_path, 'r').readlines()
listening_line = "Listening on port %s\n" % 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 = open(s.stdout_path, 'r').readlines()
self.assertIn("return 99\n", stdout_lines)
def test_interactive_stdout(self):
"""
Tests avocado's GDB plugin features
If GDB command line options are given, `--gdb-run-bin=return99` for
this particular test, the test will stop at binary main() function.
"""
self.log.info('Testing GDB interactivity')
result = process.run(self.return99_binary_path, ignore_status=True)
self.assertIn("return 99\n", result.stdout)
def action(self): def action(self):
""" """
Execute tests Execute tests
...@@ -377,3 +413,6 @@ class gdbtest(test.Test): ...@@ -377,3 +413,6 @@ class gdbtest(test.Test):
self.test_interactive() self.test_interactive()
self.test_interactive_args() self.test_interactive_args()
self.test_exit_status() self.test_exit_status()
self.test_server_stderr()
self.test_server_stdout()
self.test_interactive_stdout()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册