1
1
import os
2
2
import signal
3
3
import sys
4
+ import textwrap
4
5
import unittest
5
6
import warnings
6
7
from unittest import mock
12
13
from test import support
13
14
from test .support import os_helper
14
15
15
- if sys .platform != 'win32' :
16
+
17
+ MS_WINDOWS = (sys .platform == 'win32' )
18
+ if MS_WINDOWS :
19
+ import msvcrt
20
+ else :
16
21
from asyncio import unix_events
17
22
23
+
18
24
if support .check_sanitizer (address = True ):
19
25
raise unittest .SkipTest ("Exposes ASAN flakiness in GitHub CI" )
20
26
@@ -270,26 +276,43 @@ async def send_signal(proc):
270
276
finally :
271
277
signal .signal (signal .SIGHUP , old_handler )
272
278
273
- def prepare_broken_pipe_test (self ):
279
+ def test_stdin_broken_pipe (self ):
274
280
# buffer large enough to feed the whole pipe buffer
275
281
large_data = b'x' * support .PIPE_MAX_SIZE
276
282
283
+ rfd , wfd = os .pipe ()
284
+ self .addCleanup (os .close , rfd )
285
+ self .addCleanup (os .close , wfd )
286
+ if MS_WINDOWS :
287
+ handle = msvcrt .get_osfhandle (rfd )
288
+ os .set_handle_inheritable (handle , True )
289
+ code = textwrap .dedent (f'''
290
+ import os, msvcrt
291
+ handle = { handle }
292
+ fd = msvcrt.open_osfhandle(handle, os.O_RDONLY)
293
+ os.read(fd, 1)
294
+ ''' )
295
+ from subprocess import STARTUPINFO
296
+ startupinfo = STARTUPINFO ()
297
+ startupinfo .lpAttributeList = {"handle_list" : [handle ]}
298
+ kwargs = dict (startupinfo = startupinfo )
299
+ else :
300
+ code = f'import os; fd = { rfd } ; os.read(fd, 1)'
301
+ kwargs = dict (pass_fds = (rfd ,))
302
+
277
303
# the program ends before the stdin can be fed
278
304
proc = self .loop .run_until_complete (
279
305
asyncio .create_subprocess_exec (
280
- sys .executable , '-c' , 'pass' ,
306
+ sys .executable , '-c' , code ,
281
307
stdin = subprocess .PIPE ,
308
+ ** kwargs
282
309
)
283
310
)
284
311
285
- return (proc , large_data )
286
-
287
- def test_stdin_broken_pipe (self ):
288
- proc , large_data = self .prepare_broken_pipe_test ()
289
-
290
312
async def write_stdin (proc , data ):
291
- await asyncio .sleep (0.5 )
292
313
proc .stdin .write (data )
314
+ # Only exit the child process once the write buffer is filled
315
+ os .write (wfd , b'go' )
293
316
await proc .stdin .drain ()
294
317
295
318
coro = write_stdin (proc , large_data )
@@ -300,7 +323,16 @@ async def write_stdin(proc, data):
300
323
self .loop .run_until_complete (proc .wait ())
301
324
302
325
def test_communicate_ignore_broken_pipe (self ):
303
- proc , large_data = self .prepare_broken_pipe_test ()
326
+ # buffer large enough to feed the whole pipe buffer
327
+ large_data = b'x' * support .PIPE_MAX_SIZE
328
+
329
+ # the program ends before the stdin can be fed
330
+ proc = self .loop .run_until_complete (
331
+ asyncio .create_subprocess_exec (
332
+ sys .executable , '-c' , 'pass' ,
333
+ stdin = subprocess .PIPE ,
334
+ )
335
+ )
304
336
305
337
# communicate() must ignore BrokenPipeError when feeding stdin
306
338
self .loop .set_exception_handler (lambda loop , msg : None )
0 commit comments