11import os
22import signal
33import sys
4+ import textwrap
45import unittest
56import warnings
67from unittest import mock
1213from test import support
1314from test .support import os_helper
1415
15- if sys .platform != 'win32' :
16+
17+ MS_WINDOWS = (sys .platform == 'win32' )
18+ if MS_WINDOWS :
19+ import msvcrt
20+ else :
1621 from asyncio import unix_events
1722
23+
1824if support .check_sanitizer (address = True ):
1925 raise unittest .SkipTest ("Exposes ASAN flakiness in GitHub CI" )
2026
@@ -270,26 +276,43 @@ async def send_signal(proc):
270276 finally :
271277 signal .signal (signal .SIGHUP , old_handler )
272278
273- def prepare_broken_pipe_test (self ):
279+ def test_stdin_broken_pipe (self ):
274280 # buffer large enough to feed the whole pipe buffer
275281 large_data = b'x' * support .PIPE_MAX_SIZE
276282
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+
277303 # the program ends before the stdin can be fed
278304 proc = self .loop .run_until_complete (
279305 asyncio .create_subprocess_exec (
280- sys .executable , '-c' , 'pass' ,
306+ sys .executable , '-c' , code ,
281307 stdin = subprocess .PIPE ,
308+ ** kwargs
282309 )
283310 )
284311
285- return (proc , large_data )
286-
287- def test_stdin_broken_pipe (self ):
288- proc , large_data = self .prepare_broken_pipe_test ()
289-
290312 async def write_stdin (proc , data ):
291- await asyncio .sleep (0.5 )
292313 proc .stdin .write (data )
314+ # Only exit the child process once the write buffer is filled
315+ os .write (wfd , b'go' )
293316 await proc .stdin .drain ()
294317
295318 coro = write_stdin (proc , large_data )
@@ -300,7 +323,16 @@ async def write_stdin(proc, data):
300323 self .loop .run_until_complete (proc .wait ())
301324
302325 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+ )
304336
305337 # communicate() must ignore BrokenPipeError when feeding stdin
306338 self .loop .set_exception_handler (lambda loop , msg : None )
0 commit comments