Skip to content

Commit 48f08fe

Browse files
authored
[3.12] gh-111495: Add PyFile tests (#129449) (#129477) (#129501)
[3.13] gh-111495: Add PyFile tests (#129449) (#129477) gh-111495: Add PyFile tests (#129449) Add tests for the following functions in test_capi.test_file: * PyFile_FromFd() * PyFile_GetLine() * PyFile_NewStdPrinter() * PyFile_WriteObject() * PyFile_WriteString() * PyObject_AsFileDescriptor() Remove test_embed.StdPrinterTests which became redundant. (cherry picked from commit 4ca9fc0) (cherry picked from commit 9a59a51)
1 parent d2f551d commit 48f08fe

File tree

4 files changed

+471
-56
lines changed

4 files changed

+471
-56
lines changed

Lib/test/test_capi/test_file.py

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import io
2+
import os
3+
import unittest
4+
import warnings
5+
from test import support
6+
from test.support import import_helper, os_helper, warnings_helper
7+
8+
9+
_testcapi = import_helper.import_module('_testcapi')
10+
_io = import_helper.import_module('_io')
11+
NULL = None
12+
STDOUT_FD = 1
13+
14+
with open(__file__, 'rb') as fp:
15+
FIRST_LINE = next(fp).decode()
16+
FIRST_LINE_NORM = FIRST_LINE.rstrip() + '\n'
17+
18+
19+
class CAPIFileTest(unittest.TestCase):
20+
def test_pyfile_fromfd(self):
21+
# Test PyFile_FromFd() which is a thin wrapper to _io.open()
22+
pyfile_fromfd = _testcapi.pyfile_fromfd
23+
filename = __file__
24+
with open(filename, "rb") as fp:
25+
fd = fp.fileno()
26+
27+
# FileIO
28+
fp.seek(0)
29+
obj = pyfile_fromfd(fd, filename, "rb", 0, NULL, NULL, NULL, 0)
30+
try:
31+
self.assertIsInstance(obj, _io.FileIO)
32+
self.assertEqual(obj.readline(), FIRST_LINE.encode())
33+
finally:
34+
obj.close()
35+
36+
# BufferedReader
37+
fp.seek(0)
38+
obj = pyfile_fromfd(fd, filename, "rb", 1024, NULL, NULL, NULL, 0)
39+
try:
40+
self.assertIsInstance(obj, _io.BufferedReader)
41+
self.assertEqual(obj.readline(), FIRST_LINE.encode())
42+
finally:
43+
obj.close()
44+
45+
# TextIOWrapper
46+
fp.seek(0)
47+
obj = pyfile_fromfd(fd, filename, "r", 1,
48+
"utf-8", "replace", NULL, 0)
49+
try:
50+
self.assertIsInstance(obj, _io.TextIOWrapper)
51+
self.assertEqual(obj.encoding, "utf-8")
52+
self.assertEqual(obj.errors, "replace")
53+
self.assertEqual(obj.readline(), FIRST_LINE_NORM)
54+
finally:
55+
obj.close()
56+
57+
def test_pyfile_getline(self):
58+
# Test PyFile_GetLine(file, n): call file.readline()
59+
# and strip "\n" suffix if n < 0.
60+
pyfile_getline = _testcapi.pyfile_getline
61+
62+
# Test Unicode
63+
with open(__file__, "r") as fp:
64+
fp.seek(0)
65+
self.assertEqual(pyfile_getline(fp, -1),
66+
FIRST_LINE_NORM.rstrip('\n'))
67+
fp.seek(0)
68+
self.assertEqual(pyfile_getline(fp, 0),
69+
FIRST_LINE_NORM)
70+
fp.seek(0)
71+
self.assertEqual(pyfile_getline(fp, 6),
72+
FIRST_LINE_NORM[:6])
73+
74+
# Test bytes
75+
with open(__file__, "rb") as fp:
76+
fp.seek(0)
77+
self.assertEqual(pyfile_getline(fp, -1),
78+
FIRST_LINE.rstrip('\n').encode())
79+
fp.seek(0)
80+
self.assertEqual(pyfile_getline(fp, 0),
81+
FIRST_LINE.encode())
82+
fp.seek(0)
83+
self.assertEqual(pyfile_getline(fp, 6),
84+
FIRST_LINE.encode()[:6])
85+
86+
def test_pyfile_writestring(self):
87+
# Test PyFile_WriteString(str, file): call file.write(str)
88+
writestr = _testcapi.pyfile_writestring
89+
90+
with io.StringIO() as fp:
91+
self.assertEqual(writestr("a\xe9\u20ac\U0010FFFF".encode(), fp), 0)
92+
with self.assertRaises(UnicodeDecodeError):
93+
writestr(b"\xff", fp)
94+
with self.assertRaises(UnicodeDecodeError):
95+
writestr("\udc80".encode("utf-8", "surrogatepass"), fp)
96+
97+
text = fp.getvalue()
98+
self.assertEqual(text, "a\xe9\u20ac\U0010FFFF")
99+
100+
with self.assertRaises(SystemError):
101+
writestr(b"abc", NULL)
102+
103+
def test_pyfile_writeobject(self):
104+
# Test PyFile_WriteObject(obj, file, flags):
105+
# - Call file.write(str(obj)) if flags equals Py_PRINT_RAW.
106+
# - Call file.write(repr(obj)) otherwise.
107+
writeobject = _testcapi.pyfile_writeobject
108+
Py_PRINT_RAW = 1
109+
110+
with io.StringIO() as fp:
111+
# Test flags=Py_PRINT_RAW
112+
self.assertEqual(writeobject("raw", fp, Py_PRINT_RAW), 0)
113+
writeobject(NULL, fp, Py_PRINT_RAW)
114+
115+
# Test flags=0
116+
self.assertEqual(writeobject("repr", fp, 0), 0)
117+
writeobject(NULL, fp, 0)
118+
119+
text = fp.getvalue()
120+
self.assertEqual(text, "raw<NULL>'repr'<NULL>")
121+
122+
# invalid file type
123+
for invalid_file in (123, "abc", object()):
124+
with self.subTest(file=invalid_file):
125+
with self.assertRaises(AttributeError):
126+
writeobject("abc", invalid_file, Py_PRINT_RAW)
127+
128+
with self.assertRaises(TypeError):
129+
writeobject("abc", NULL, 0)
130+
131+
def test_pyobject_asfiledescriptor(self):
132+
# Test PyObject_AsFileDescriptor(obj):
133+
# - Return obj if obj is an integer.
134+
# - Return obj.fileno() otherwise.
135+
# File descriptor must be >= 0.
136+
asfd = _testcapi.pyobject_asfiledescriptor
137+
138+
self.assertEqual(asfd(123), 123)
139+
self.assertEqual(asfd(0), 0)
140+
141+
with open(__file__, "rb") as fp:
142+
self.assertEqual(asfd(fp), fp.fileno())
143+
144+
self.assertEqual(asfd(False), 0)
145+
self.assertEqual(asfd(True), 1)
146+
147+
class FakeFile:
148+
def __init__(self, fd):
149+
self.fd = fd
150+
def fileno(self):
151+
return self.fd
152+
153+
# file descriptor must be positive
154+
with self.assertRaises(ValueError):
155+
asfd(-1)
156+
with self.assertRaises(ValueError):
157+
asfd(FakeFile(-1))
158+
159+
# fileno() result must be an integer
160+
with self.assertRaises(TypeError):
161+
asfd(FakeFile("text"))
162+
163+
# unsupported types
164+
for obj in ("string", ["list"], object()):
165+
with self.subTest(obj=obj):
166+
with self.assertRaises(TypeError):
167+
asfd(obj)
168+
169+
# CRASHES asfd(NULL)
170+
171+
def test_pyfile_newstdprinter(self):
172+
# Test PyFile_NewStdPrinter()
173+
pyfile_newstdprinter = _testcapi.pyfile_newstdprinter
174+
175+
file = pyfile_newstdprinter(STDOUT_FD)
176+
self.assertEqual(file.closed, False)
177+
self.assertIsNone(file.encoding)
178+
self.assertEqual(file.mode, "w")
179+
180+
self.assertEqual(file.fileno(), STDOUT_FD)
181+
self.assertEqual(file.isatty(), os.isatty(STDOUT_FD))
182+
183+
# flush() is a no-op
184+
self.assertIsNone(file.flush())
185+
186+
# close() is a no-op
187+
self.assertIsNone(file.close())
188+
self.assertEqual(file.closed, False)
189+
190+
support.check_disallow_instantiation(self, type(file))
191+
192+
def test_pyfile_newstdprinter_write(self):
193+
# Test the write() method of PyFile_NewStdPrinter()
194+
pyfile_newstdprinter = _testcapi.pyfile_newstdprinter
195+
196+
filename = os_helper.TESTFN
197+
self.addCleanup(os_helper.unlink, filename)
198+
199+
try:
200+
old_stdout = os.dup(STDOUT_FD)
201+
except OSError as exc:
202+
# os.dup(STDOUT_FD) is not supported on WASI
203+
self.skipTest(f"os.dup() failed with {exc!r}")
204+
205+
try:
206+
with open(filename, "wb") as fp:
207+
# PyFile_NewStdPrinter() only accepts fileno(stdout)
208+
# or fileno(stderr) file descriptor.
209+
fd = fp.fileno()
210+
os.dup2(fd, STDOUT_FD)
211+
212+
file = pyfile_newstdprinter(STDOUT_FD)
213+
self.assertEqual(file.write("text"), 4)
214+
# The surrogate character is encoded with
215+
# the "surrogateescape" error handler
216+
self.assertEqual(file.write("[\udc80]"), 8)
217+
finally:
218+
os.dup2(old_stdout, STDOUT_FD)
219+
os.close(old_stdout)
220+
221+
with open(filename, "r") as fp:
222+
self.assertEqual(fp.read(), "text[\\udc80]")
223+
224+
# TODO: Test Py_UniversalNewlineFgets()
225+
226+
# PyFile_SetOpenCodeHook() and PyFile_OpenCode() are tested by
227+
# test_embed.test_open_code_hook()
228+
229+
230+
if __name__ == "__main__":
231+
unittest.main()

Lib/test/test_embed.py

-51
Original file line numberDiff line numberDiff line change
@@ -1866,56 +1866,5 @@ def test_no_memleak(self):
18661866
self.assertEqual(blocks, 0, out)
18671867

18681868

1869-
class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
1870-
# Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
1871-
# "Set up a preliminary stderr printer until we have enough
1872-
# infrastructure for the io module in place."
1873-
1874-
STDOUT_FD = 1
1875-
1876-
def create_printer(self, fd):
1877-
ctypes = import_helper.import_module('ctypes')
1878-
PyFile_NewStdPrinter = ctypes.pythonapi.PyFile_NewStdPrinter
1879-
PyFile_NewStdPrinter.argtypes = (ctypes.c_int,)
1880-
PyFile_NewStdPrinter.restype = ctypes.py_object
1881-
return PyFile_NewStdPrinter(fd)
1882-
1883-
def test_write(self):
1884-
message = "unicode:\xe9-\u20ac-\udc80!\n"
1885-
1886-
stdout_fd = self.STDOUT_FD
1887-
stdout_fd_copy = os.dup(stdout_fd)
1888-
self.addCleanup(os.close, stdout_fd_copy)
1889-
1890-
rfd, wfd = os.pipe()
1891-
self.addCleanup(os.close, rfd)
1892-
self.addCleanup(os.close, wfd)
1893-
try:
1894-
# PyFile_NewStdPrinter() only accepts fileno(stdout)
1895-
# or fileno(stderr) file descriptor.
1896-
os.dup2(wfd, stdout_fd)
1897-
1898-
printer = self.create_printer(stdout_fd)
1899-
printer.write(message)
1900-
finally:
1901-
os.dup2(stdout_fd_copy, stdout_fd)
1902-
1903-
data = os.read(rfd, 100)
1904-
self.assertEqual(data, message.encode('utf8', 'backslashreplace'))
1905-
1906-
def test_methods(self):
1907-
fd = self.STDOUT_FD
1908-
printer = self.create_printer(fd)
1909-
self.assertEqual(printer.fileno(), fd)
1910-
self.assertEqual(printer.isatty(), os.isatty(fd))
1911-
printer.flush() # noop
1912-
printer.close() # noop
1913-
1914-
def test_disallow_instantiation(self):
1915-
fd = self.STDOUT_FD
1916-
printer = self.create_printer(fd)
1917-
support.check_disallow_instantiation(self, type(printer))
1918-
1919-
19201869
if __name__ == "__main__":
19211870
unittest.main()

0 commit comments

Comments
 (0)