Skip to content

Commit f1abb42

Browse files
committed
first_stage_test: Add more tests
+ test_stdin_non_blocking + test_stdin_blocking Signed-off-by: Marc Hartmayer <[email protected]>
1 parent ead4a6d commit f1abb42

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed

tests/first_stage_test.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,134 @@
1+
import fcntl
2+
import functools
3+
import operator
4+
15
import mitogen.core
26
import mitogen.parent
37
from mitogen.core import b
48

59
import testlib
610

711

12+
def own_create_child(args, blocking, pipe_size=None, preexec_fn=None, pass_stderr=True):
13+
"""
14+
Create a child process whose stdin/stdout is connected to a pipe.
15+
16+
:param list args:
17+
Program argument vector.
18+
:param bool blocking:
19+
If :data:`True`, the pipes use blocking IO, otherwise non-blocking.
20+
:param int pipe_size:
21+
If not :data:`None`, use the values as the pipe size.
22+
:param function preexec_fn:
23+
If not :data:`None`, a function to run within the post-fork child
24+
before executing the target program.
25+
:returns:
26+
:class:`PopenProcess` instance.
27+
"""
28+
parent_rfp, child_wfp = mitogen.core.pipe(blocking=blocking)
29+
child_rfp, parent_wfp = mitogen.core.pipe(blocking=blocking)
30+
stderr_r, stderr = mitogen.core.pipe(blocking=blocking)
31+
mitogen.core.set_cloexec(stderr_r.fileno())
32+
if pipe_size is not None:
33+
fcntl.fcntl(parent_rfp.fileno(), fcntl.F_SETPIPE_SZ, pipe_size)
34+
fcntl.fcntl(child_rfp.fileno(), fcntl.F_SETPIPE_SZ, pipe_size)
35+
fcntl.fcntl(stderr_r.fileno(), fcntl.F_SETPIPE_SZ, pipe_size)
36+
assert fcntl.fcntl(parent_rfp.fileno(), fcntl.F_GETPIPE_SZ) == pipe_size
37+
assert fcntl.fcntl(child_rfp.fileno(), fcntl.F_GETPIPE_SZ) == pipe_size
38+
assert fcntl.fcntl(stderr_r.fileno(), fcntl.F_GETPIPE_SZ) == pipe_size
39+
40+
try:
41+
proc = testlib.subprocess.Popen(
42+
args=args,
43+
stdin=child_rfp,
44+
stdout=child_wfp,
45+
stderr=stderr,
46+
preexec_fn=preexec_fn,
47+
)
48+
except Exception:
49+
child_rfp.close()
50+
child_wfp.close()
51+
parent_rfp.close()
52+
parent_wfp.close()
53+
stderr_r.close()
54+
stderr.close()
55+
raise
56+
57+
child_rfp.close()
58+
child_wfp.close()
59+
stderr.close()
60+
# Only used to create a specific test scenario!
61+
if not pass_stderr:
62+
stderr_r.close()
63+
stderr_r = None
64+
return mitogen.parent.PopenProcess(
65+
proc=proc,
66+
stdin=parent_wfp,
67+
stdout=parent_rfp,
68+
stderr=stderr_r,
69+
)
70+
71+
72+
class DummyConnectionBlocking(mitogen.parent.Connection):
73+
"""Dummy blocking IO connection"""
74+
75+
pipe_size = 4096 if getattr(fcntl, "F_SETPIPE_SZ", None) else None
76+
create_child = staticmethod(
77+
functools.partial(own_create_child, blocking=True, pipe_size=pipe_size)
78+
)
79+
name_prefix = "dummy_blocking"
80+
81+
82+
class DummyConnectionNonBlocking(mitogen.parent.Connection):
83+
"""Dummy non-blocking IO connection"""
84+
85+
pipe_size = 4096 if getattr(fcntl, "F_SETPIPE_SZ", None) else None
86+
create_child = staticmethod(
87+
functools.partial(own_create_child, blocking=False, pipe_size=pipe_size)
88+
)
89+
name_prefix = "dummy_non_blocking"
90+
91+
92+
class ConnectionTest(testlib.RouterMixin, testlib.TestCase):
93+
def test_stdin_non_blocking(self):
94+
"""Test that first stage works with non-blocking STDIN
95+
96+
The boot command should read the preamble from STDIN, write all ECO
97+
markers to STDOUT, and then execute the preamble.
98+
99+
This test writes the complete preamble to non-blocking STDIN.
100+
101+
1. Fork child reads from non-blocking STDIN
102+
2. Fork child writes all data as expected by the protocol.
103+
3. A context call works as expected.
104+
105+
"""
106+
log = testlib.LogCapturer()
107+
log.start()
108+
ctx = self.router._connect(DummyConnectionNonBlocking, connect_timeout=0.5)
109+
self.assertEqual(3, ctx.call(operator.add, 1, 2))
110+
logs = log.stop()
111+
112+
def test_stdin_blocking(self):
113+
"""Test that first stage works with blocking STDIN
114+
115+
The boot command should read the preamble from STDIN, write all ECO
116+
markers to STDOUT, and then execute the preamble.
117+
118+
This test writes the complete preamble to blocking STDIN.
119+
120+
1. Fork child reads from blocking STDIN
121+
2. Fork child writes all data as expected by the protocol.
122+
3. A context call works as expected.
123+
124+
"""
125+
log = testlib.LogCapturer()
126+
log.start()
127+
ctx = self.router._connect(DummyConnectionBlocking, connect_timeout=0.5)
128+
self.assertEqual(3, ctx.call(operator.add, 1, 2))
129+
logs = log.stop()
130+
131+
8132
class CommandLineTest(testlib.RouterMixin, testlib.TestCase):
9133
# Ensure this version of Python produces a command line that is sufficient
10134
# to bootstrap this version of Python.

0 commit comments

Comments
 (0)