X Tutup
Skip to content

Commit fa3f421

Browse files
CPython Developersyouknowone
authored andcommitted
Update test_winconsoleio from v3.14.3
1 parent 8e06707 commit fa3f421

File tree

4 files changed

+261
-6
lines changed

4 files changed

+261
-6
lines changed

.cspell.dict/cpython.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ baseinfo
1212
basetype
1313
binop
1414
boolop
15+
BUFMAX
1516
BUILDSTDLIB
1617
bxor
1718
byteswap
@@ -33,6 +34,8 @@ cmpop
3334
codedepth
3435
constevaluator
3536
CODEUNIT
37+
CONIN
38+
CONOUT
3639
CONVFUNC
3740
convparam
3841
copyslot
@@ -105,6 +108,7 @@ metavars
105108
miscompiles
106109
mult
107110
multibytecodec
111+
nameobj
108112
nameop
109113
nconsts
110114
newargs
@@ -160,6 +164,7 @@ SETREF
160164
setresult
161165
setslice
162166
SLOTDEFINED
167+
SMALLBUF
163168
SOABI
164169
SSLEOF
165170
stackdepth
@@ -174,6 +179,7 @@ subscr
174179
sval
175180
swappedbytes
176181
templatelib
182+
testconsole
177183
ticketer
178184
tmptype
179185
tok_oldval
@@ -199,6 +205,7 @@ wbits
199205
weakreflist
200206
weakrefobject
201207
webpki
208+
winconsoleio
202209
withitem
203210
withs
204211
xstat

Lib/test/test_fileio.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,6 @@ class PyAutoFileTests(AutoFileTests, unittest.TestCase):
508508
FileIO = _pyio.FileIO
509509
modulename = '_pyio'
510510

511-
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; _blksize mismatch on Windows')
512511
def testBlksize(self):
513512
return super().testBlksize()
514513

Lib/test/test_winconsoleio.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
'''Tests for WindowsConsoleIO
2+
'''
3+
4+
import io
5+
import os
6+
import sys
7+
import tempfile
8+
import unittest
9+
from test.support import os_helper, requires_resource
10+
11+
if sys.platform != 'win32':
12+
raise unittest.SkipTest("test only relevant on win32")
13+
14+
from _testconsole import write_input
15+
16+
ConIO = io._WindowsConsoleIO
17+
18+
class WindowsConsoleIOTests(unittest.TestCase):
19+
def test_abc(self):
20+
self.assertIsSubclass(ConIO, io.RawIOBase)
21+
self.assertNotIsSubclass(ConIO, io.BufferedIOBase)
22+
self.assertNotIsSubclass(ConIO, io.TextIOBase)
23+
24+
def test_open_fd(self):
25+
self.assertRaisesRegex(ValueError,
26+
"negative file descriptor", ConIO, -1)
27+
28+
with tempfile.TemporaryFile() as tmpfile:
29+
fd = tmpfile.fileno()
30+
# Windows 10: "Cannot open non-console file"
31+
# Earlier: "Cannot open console output buffer for reading"
32+
self.assertRaisesRegex(ValueError,
33+
"Cannot open (console|non-console file)", ConIO, fd)
34+
35+
try:
36+
f = ConIO(0)
37+
except ValueError:
38+
# cannot open console because it's not a real console
39+
pass
40+
else:
41+
self.assertTrue(f.readable())
42+
self.assertFalse(f.writable())
43+
self.assertEqual(0, f.fileno())
44+
f.close() # multiple close should not crash
45+
f.close()
46+
with self.assertWarns(RuntimeWarning):
47+
with ConIO(False):
48+
pass
49+
50+
try:
51+
f = ConIO(1, 'w')
52+
except ValueError:
53+
# cannot open console because it's not a real console
54+
pass
55+
else:
56+
self.assertFalse(f.readable())
57+
self.assertTrue(f.writable())
58+
self.assertEqual(1, f.fileno())
59+
f.close()
60+
f.close()
61+
with self.assertWarns(RuntimeWarning):
62+
with ConIO(False):
63+
pass
64+
65+
try:
66+
f = ConIO(2, 'w')
67+
except ValueError:
68+
# cannot open console because it's not a real console
69+
pass
70+
else:
71+
self.assertFalse(f.readable())
72+
self.assertTrue(f.writable())
73+
self.assertEqual(2, f.fileno())
74+
f.close()
75+
f.close()
76+
77+
def test_open_name(self):
78+
self.assertRaises(ValueError, ConIO, sys.executable)
79+
80+
f = ConIO("CON")
81+
self.assertTrue(f.readable())
82+
self.assertFalse(f.writable())
83+
self.assertIsNotNone(f.fileno())
84+
f.close() # multiple close should not crash
85+
f.close()
86+
87+
f = ConIO('CONIN$')
88+
self.assertTrue(f.readable())
89+
self.assertFalse(f.writable())
90+
self.assertIsNotNone(f.fileno())
91+
f.close()
92+
f.close()
93+
94+
f = ConIO('CONOUT$', 'w')
95+
self.assertFalse(f.readable())
96+
self.assertTrue(f.writable())
97+
self.assertIsNotNone(f.fileno())
98+
f.close()
99+
f.close()
100+
101+
# bpo-45354: Windows 11 changed MS-DOS device name handling
102+
if sys.getwindowsversion()[:3] < (10, 0, 22000):
103+
f = open('C:/con', 'rb', buffering=0)
104+
self.assertIsInstance(f, ConIO)
105+
f.close()
106+
107+
def test_subclass_repr(self):
108+
class TestSubclass(ConIO):
109+
pass
110+
111+
f = TestSubclass("CON")
112+
with f:
113+
self.assertIn(TestSubclass.__name__, repr(f))
114+
115+
self.assertIn(TestSubclass.__name__, repr(f))
116+
117+
@unittest.skipIf(sys.getwindowsversion()[:2] <= (6, 1),
118+
"test does not work on Windows 7 and earlier")
119+
def test_conin_conout_names(self):
120+
f = open(r'\\.\conin$', 'rb', buffering=0)
121+
self.assertIsInstance(f, ConIO)
122+
f.close()
123+
124+
f = open('//?/conout$', 'wb', buffering=0)
125+
self.assertIsInstance(f, ConIO)
126+
f.close()
127+
128+
def test_conout_path(self):
129+
temp_path = tempfile.mkdtemp()
130+
self.addCleanup(os_helper.rmtree, temp_path)
131+
132+
conout_path = os.path.join(temp_path, 'CONOUT$')
133+
134+
with open(conout_path, 'wb', buffering=0) as f:
135+
# bpo-45354: Windows 11 changed MS-DOS device name handling
136+
if (6, 1) < sys.getwindowsversion()[:3] < (10, 0, 22000):
137+
self.assertIsInstance(f, ConIO)
138+
else:
139+
self.assertNotIsInstance(f, ConIO)
140+
141+
def test_write_empty_data(self):
142+
with ConIO('CONOUT$', 'w') as f:
143+
self.assertEqual(f.write(b''), 0)
144+
145+
@requires_resource('console')
146+
def test_write(self):
147+
testcases = []
148+
with ConIO('CONOUT$', 'w') as f:
149+
for a in [
150+
b'',
151+
b'abc',
152+
b'\xc2\xa7\xe2\x98\x83\xf0\x9f\x90\x8d',
153+
b'\xff'*10,
154+
]:
155+
for b in b'\xc2\xa7', b'\xe2\x98\x83', b'\xf0\x9f\x90\x8d':
156+
testcases.append(a + b)
157+
for i in range(1, len(b)):
158+
data = a + b[:i]
159+
testcases.append(data + b'z')
160+
testcases.append(data + b'\xff')
161+
# incomplete multibyte sequence
162+
with self.subTest(data=data):
163+
self.assertEqual(f.write(data), len(a))
164+
for data in testcases:
165+
with self.subTest(data=data):
166+
self.assertEqual(f.write(data), len(data))
167+
168+
def assertStdinRoundTrip(self, text):
169+
stdin = open('CONIN$', 'r')
170+
old_stdin = sys.stdin
171+
try:
172+
sys.stdin = stdin
173+
write_input(
174+
stdin.buffer.raw,
175+
(text + '\r\n').encode('utf-16-le', 'surrogatepass')
176+
)
177+
actual = input()
178+
finally:
179+
sys.stdin = old_stdin
180+
self.assertEqual(actual, text)
181+
182+
@requires_resource('console')
183+
def test_input(self):
184+
# ASCII
185+
self.assertStdinRoundTrip('abc123')
186+
# Non-ASCII
187+
self.assertStdinRoundTrip('ϼўТλФЙ')
188+
# Combining characters
189+
self.assertStdinRoundTrip('A͏B ﬖ̳AA̝')
190+
191+
# bpo-38325
192+
@unittest.skipIf(True, "Handling Non-BMP characters is broken")
193+
def test_input_nonbmp(self):
194+
# Non-BMP
195+
self.assertStdinRoundTrip('\U00100000\U0010ffff\U0010fffd')
196+
197+
@requires_resource('console')
198+
def test_partial_reads(self):
199+
# Test that reading less than 1 full character works when stdin
200+
# contains multibyte UTF-8 sequences
201+
source = 'ϼўТλФЙ\r\n'.encode('utf-16-le')
202+
expected = 'ϼўТλФЙ\r\n'.encode('utf-8')
203+
for read_count in range(1, 16):
204+
with open('CONIN$', 'rb', buffering=0) as stdin:
205+
write_input(stdin, source)
206+
207+
actual = b''
208+
while not actual.endswith(b'\n'):
209+
b = stdin.read(read_count)
210+
actual += b
211+
212+
self.assertEqual(actual, expected, 'stdin.read({})'.format(read_count))
213+
214+
# bpo-38325
215+
@unittest.skipIf(True, "Handling Non-BMP characters is broken")
216+
def test_partial_surrogate_reads(self):
217+
# Test that reading less than 1 full character works when stdin
218+
# contains surrogate pairs that cannot be decoded to UTF-8 without
219+
# reading an extra character.
220+
source = '\U00101FFF\U00101001\r\n'.encode('utf-16-le')
221+
expected = '\U00101FFF\U00101001\r\n'.encode('utf-8')
222+
for read_count in range(1, 16):
223+
with open('CONIN$', 'rb', buffering=0) as stdin:
224+
write_input(stdin, source)
225+
226+
actual = b''
227+
while not actual.endswith(b'\n'):
228+
b = stdin.read(read_count)
229+
actual += b
230+
231+
self.assertEqual(actual, expected, 'stdin.read({})'.format(read_count))
232+
233+
@requires_resource('console')
234+
def test_ctrl_z(self):
235+
with open('CONIN$', 'rb', buffering=0) as stdin:
236+
source = '\xC4\x1A\r\n'.encode('utf-16-le')
237+
expected = '\xC4'.encode('utf-8')
238+
write_input(stdin, source)
239+
a, b = stdin.read(1), stdin.readall()
240+
self.assertEqual(expected[0:1], a)
241+
self.assertEqual(expected[1:], b)
242+
243+
if __name__ == "__main__":
244+
unittest.main()

crates/vm/src/stdlib/io.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ fn file_closed(file: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
2929
file.get_attr("closed", vm)?.try_to_bool(vm)
3030
}
3131

32+
const DEFAULT_BUFFER_SIZE: usize = 128 * 1024;
33+
3234
/// iobase_finalize in Modules/_io/iobase.c
3335
fn iobase_finalize(zelf: &PyObject, vm: &VirtualMachine) {
3436
// If `closed` doesn't exist or can't be evaluated as bool, then the
@@ -244,7 +246,7 @@ mod _io {
244246
}
245247

246248
#[pyattr]
247-
const DEFAULT_BUFFER_SIZE: usize = 8 * 1024;
249+
const DEFAULT_BUFFER_SIZE: usize = super::DEFAULT_BUFFER_SIZE;
248250

249251
pub(super) fn seekfrom(
250252
vm: &VirtualMachine,
@@ -5252,7 +5254,7 @@ mod fileio {
52525254
closefd: AtomicCell::new(true),
52535255
mode: AtomicCell::new(Mode::empty()),
52545256
seekable: AtomicCell::new(None),
5255-
blksize: AtomicCell::new(8 * 1024), // DEFAULT_BUFFER_SIZE
5257+
blksize: AtomicCell::new(super::DEFAULT_BUFFER_SIZE as _),
52565258
finalizing: AtomicCell::new(false),
52575259
}
52585260
}
@@ -5828,7 +5830,10 @@ mod winconsoleio {
58285830
let Ok(name) = path_or_fd.str(vm) else {
58295831
return '\0';
58305832
};
5831-
let name_str = name.as_str();
5833+
let Some(name_str) = name.to_str() else {
5834+
// Surrogate strings can't be console device names
5835+
return '\0';
5836+
};
58325837

58335838
if name_str.eq_ignore_ascii_case("CONIN$") {
58345839
return 'r';
@@ -5928,7 +5933,7 @@ mod winconsoleio {
59285933
writable: AtomicCell::new(false),
59295934
closefd: AtomicCell::new(false),
59305935
finalizing: AtomicCell::new(false),
5931-
blksize: AtomicCell::new(8 * 1024),
5936+
blksize: AtomicCell::new(super::DEFAULT_BUFFER_SIZE as _),
59325937
buf: PyMutex::new([0u8; SMALLBUF]),
59335938
}
59345939
}
@@ -6131,7 +6136,7 @@ mod winconsoleio {
61316136
return Err(vm.new_value_error("Cannot open console output buffer for reading"));
61326137
}
61336138

6134-
zelf.blksize.store(8 * 1024);
6139+
zelf.blksize.store(super::DEFAULT_BUFFER_SIZE as _);
61356140
*zelf.buf.lock() = [0u8; SMALLBUF];
61366141

61376142
zelf.as_object().set_attr("name", nameobj, vm)?;

0 commit comments

Comments
 (0)
X Tutup