@@ -956,6 +956,48 @@ def test_communicate(self):
956956 self .assertEqual (stdout , b"banana" )
957957 self .assertEqual (stderr , b"pineapple" )
958958
959+ def test_communicate_memoryview_input (self ):
960+ # Test memoryview input with byte elements
961+ test_data = b"Hello, memoryview!"
962+ mv = memoryview (test_data )
963+ p = subprocess .Popen ([sys .executable , "-c" ,
964+ 'import sys; sys.stdout.write(sys.stdin.read())' ],
965+ stdin = subprocess .PIPE ,
966+ stdout = subprocess .PIPE )
967+ self .addCleanup (p .stdout .close )
968+ self .addCleanup (p .stdin .close )
969+ (stdout , stderr ) = p .communicate (mv )
970+ self .assertEqual (stdout , test_data )
971+ self .assertIsNone (stderr )
972+
973+ def test_communicate_memoryview_input_nonbyte (self ):
974+ # Test memoryview input with non-byte elements (e.g., int32)
975+ # This tests the fix for gh-134453 where non-byte memoryviews
976+ # had incorrect length tracking on POSIX
977+ import array
978+ # Create an array of 32-bit integers that's large enough to trigger
979+ # the chunked writing behavior (> PIPE_BUF)
980+ pipe_buf = getattr (select , 'PIPE_BUF' , 512 )
981+ # Each 'i' element is 4 bytes, so we need more than pipe_buf/4 elements
982+ # Add some extra to ensure we exceed the buffer size
983+ num_elements = pipe_buf + 1
984+ test_array = array .array ('i' , [0x64306f66 for _ in range (num_elements )])
985+ expected_bytes = test_array .tobytes ()
986+ mv = memoryview (test_array )
987+
988+ p = subprocess .Popen ([sys .executable , "-c" ,
989+ 'import sys; '
990+ 'data = sys.stdin.buffer.read(); '
991+ 'sys.stdout.buffer.write(data)' ],
992+ stdin = subprocess .PIPE ,
993+ stdout = subprocess .PIPE )
994+ self .addCleanup (p .stdout .close )
995+ self .addCleanup (p .stdin .close )
996+ (stdout , stderr ) = p .communicate (mv )
997+ self .assertEqual (stdout , expected_bytes ,
998+ msg = f"{ len (stdout )= } =? { len (expected_bytes )= } " )
999+ self .assertIsNone (stderr )
1000+
9591001 def test_communicate_timeout (self ):
9601002 p = subprocess .Popen ([sys .executable , "-c" ,
9611003 'import sys,os,time;'
@@ -1698,6 +1740,40 @@ def test_wait_negative_timeout(self):
16981740
16991741 self .assertEqual (proc .wait (), 0 )
17001742
1743+ def test_post_timeout_communicate_sends_input (self ):
1744+ """GH-141473 regression test; the stdin pipe must close"""
1745+ with subprocess .Popen (
1746+ [sys .executable , "-uc" , """\
1747+ import sys
1748+ while c := sys.stdin.read(512):
1749+ sys.stdout.write(c)
1750+ print()
1751+ """ ],
1752+ stdin = subprocess .PIPE ,
1753+ stdout = subprocess .PIPE ,
1754+ stderr = subprocess .PIPE ,
1755+ text = True ,
1756+ ) as proc :
1757+ try :
1758+ data = f"spam{ '#' * 4096 } beans"
1759+ proc .communicate (
1760+ input = data ,
1761+ timeout = 0 ,
1762+ )
1763+ except subprocess .TimeoutExpired as exc :
1764+ pass
1765+ # Prior to the bugfix, this would hang as the stdin
1766+ # pipe to the child had not been closed.
1767+ try :
1768+ stdout , stderr = proc .communicate (timeout = 15 )
1769+ except subprocess .TimeoutExpired as exc :
1770+ self .fail ("communicate() hung waiting on child process that should have seen its stdin pipe close and exit" )
1771+ self .assertEqual (
1772+ proc .returncode , 0 ,
1773+ msg = f"STDERR:\n { stderr } \n STDOUT:\n { stdout } " )
1774+ self .assertTrue (stdout .startswith ("spam" ), msg = stdout )
1775+ self .assertIn ("beans" , stdout )
1776+
17011777
17021778class RunFuncTestCase (BaseTestCase ):
17031779 def run_python (self , code , ** kwargs ):
0 commit comments