first_stage_test: Add tests for closed STDIN/STDOUT

Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
pull/1395/head
Marc Hartmayer 1 month ago
parent 492bd2fa1f
commit e0cc3e8cbd

@ -1,6 +1,7 @@
import errno
import operator
import os
import pty
import mitogen.core
import mitogen.parent
@ -142,6 +143,28 @@ class DummyConnectionEOFRead(mitogen.parent.Connection):
return proc
class DummyConnectionClosedStdout(mitogen.parent.Connection):
"""Dummy closed stdout connection"""
create_child = staticmethod(create_child_using_pipes)
name_prefix = "dummy_closed_stdout"
#: Dictionary of extra kwargs passed to :attr:`create_child`.
create_child_args = {"blocking": True,
"preexec_fn": lambda: os.close(pty.STDOUT_FILENO)}
class DummyConnectionClosedStdin(mitogen.parent.Connection):
"""Dummy closed stdin connection"""
create_child = staticmethod(create_child_using_pipes)
name_prefix = "dummy_closed_stdin"
#: Dictionary of extra kwargs passed to :attr:`create_child`.
create_child_args = {"blocking": True,
"preexec_fn": lambda: os.close(pty.STDIN_FILENO)}
class DummyConnectionEndlessBlockingRead(mitogen.parent.Connection):
"""Dummy connection that triggers a non-returning read(STDIN) call in the
first_stage.
@ -200,6 +223,24 @@ class ConnectionTest(testlib.RouterMixin, testlib.TestCase):
ctx = self.router._connect(DummyConnectionBlocking, connect_timeout=0.5)
self.assertEqual(3, ctx.call(operator.add, 1, 2))
def test_closed_communication_channel(self):
"""Test that first stage does not work with a closed STDIN or STDOUT
The first stage of the boot command should bail out as soon as it
detects that STDIN or STDOUT is closed/unavailable and this results in
disconnected streams and that is mapped by the broker to an
:class:`mitogen.parent.EofError`.
"""
with testlib.LogCapturer() as _:
e = self.assertRaises(mitogen.parent.EofError,
self.router._connect, DummyConnectionClosedStdout, connect_timeout=0.5)
self.assertIn("Bad file descriptor", str(e))
e = self.assertRaises(mitogen.parent.EofError,
self.router._connect, DummyConnectionClosedStdin, connect_timeout=0.5)
self.assertIn("Bad file descriptor", str(e))
def test_broker_connect_eof_error(self):
"""Test that broker takes care about EOF errors in the first stage
@ -365,3 +406,87 @@ class CommandLineTest(testlib.RouterMixin, testlib.TestCase):
finally:
proc.stdout.close()
proc.stderr.close()
def test_closed_stdin(self):
"""This test closes STDIN of the child process.
1. The child process detects that STDIN is unavailable
2. The child process terminates early with an OSError exception, and
reports the issue via exception printed on STDERR.
3. The parent process correctly identifies this condition.
"""
options = mitogen.parent.Options(max_message_size=123)
conn = mitogen.parent.Connection(options, self.router)
conn.context = mitogen.core.Context(None, 123)
proc = testlib.subprocess.Popen(
args=conn.get_boot_command(),
stdout=testlib.subprocess.PIPE,
stderr=testlib.subprocess.PIPE,
preexec_fn=lambda: os.close(pty.STDIN_FILENO),
close_fds=True,
)
try:
stdout, stderr = proc.communicate(timeout=1)
except testlib.subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=3)
self.fail("Closed STDIN situation was not recognized")
self.assertEqual(1, proc.returncode)
self.assertEqual(stdout, b"")
self.assertIn(
b("Bad file descriptor"),
stderr,
)
def test_closed_stdout(self):
"""Test that first stage bails out if STDOUT is closed
This test closes STDOUT of the child process.
1. The child process detects that STDOUT is unavailable
2. The child process terminates early with an OSError exception, and
reports the issue via exception printed on STDERR.
3. The parent process correctly identifies this condition.
"""
options = mitogen.parent.Options(max_message_size=123)
conn = mitogen.parent.Connection(options, self.router)
conn.context = mitogen.core.Context(None, 123)
stdout_r, stdout_w = mitogen.core.pipe()
mitogen.core.set_cloexec(stdout_r.fileno())
stderr_r, stderr_w = mitogen.core.pipe()
mitogen.core.set_cloexec(stderr_r.fileno())
try:
proc = testlib.subprocess.Popen(
args=conn.get_boot_command(),
stdout=stdout_w,
stderr=stderr_w,
preexec_fn=lambda: os.close(pty.STDOUT_FILENO),
)
except Exception:
stdout_r.close()
stdout_w.close()
stderr_w.close()
stderr_r.close()
raise
stdout_w.close()
stderr_w.close()
try:
returncode = proc.wait(timeout=1)
except testlib.subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=3)
self.fail("Closed STDOUT situation was not detected")
else:
stderr = stderr_r.read()
finally:
stderr_r.close()
stdout_r.close()
self.assertEqual(1, returncode)
self.assertIn(
b("Bad file descriptor"),
stderr,
)

Loading…
Cancel
Save