Skip to content

gh-111495: Add PyFile_* CAPI tests #111709

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 93 additions & 56 deletions Lib/test/test_capi/test_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import unittest
import io
import os
import unittest

from test.support import import_helper, os_helper

Expand All @@ -20,65 +19,77 @@ class TestPyFile_FromFd(_TempFileMixin, unittest.TestCase):
# `_io.open` which is fully tested in `test_io`.

def test_file_from_fd(self):
from_fd = _testcapi.file_from_fd
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = _testcapi.file_from_fd(
f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", "\n", 0,
)
self.assertIsInstance(file_obj, io.TextIOWrapper)
file_obj = from_fd(f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", "\n", 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)
self.assertEqual(file_obj.name, f.fileno())

def test_name_null(self):
from_fd = _testcapi.file_from_fd
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = _testcapi.file_from_fd(
f.fileno(), NULL, "w",
1, "utf-8", "strict", "\n", 0,
)
self.assertIsInstance(file_obj, io.TextIOWrapper)
file_obj = from_fd(f.fileno(), NULL, "w",
1, "utf-8", "strict", "\n", 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)
self.assertEqual(file_obj.name, f.fileno())

def test_name_invalid_utf(self):
def test_name_invalid_utf(self): # TODO: use bytes
from_fd = _testcapi.file_from_fd
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = _testcapi.file_from_fd(
f.fileno(), "abc\xe9", "w",
1, "utf-8", "strict", "\n", 0,
)
file_obj = from_fd(f.fileno(), "abc\xe9", "w",
1, "utf-8", "strict", "\n", 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)

def test_mode_as_null(self):
from_fd = _testcapi.file_from_fd
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
self.assertRaisesRegex(
with self.assertRaisesRegex(
TypeError,
r"open\(\) argument 'mode' must be str, not None",
_testcapi.file_from_fd,
f.fileno(), "abc\xe9", NULL,
1, "utf-8", "strict", "\n", 0,
)
):
from_fd(f.fileno(), "abc\xe9", NULL,
1, "utf-8", "strict", "\n", 0)

def test_string_args_as_null(self):
for arg_pos in (4, 5, 6):
with self.subTest(arg_pos=arg_pos):
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
args = [
f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", "\n", 0,
]
args[arg_pos] = NULL
file_obj = _testcapi.file_from_fd(*args)
self.assertIsInstance(file_obj, io.TextIOWrapper)
from_fd = _testcapi.file_from_fd

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = from_fd(f.fileno(), os_helper.TESTFN, "w",
1, NULL, "strict", "\n", 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)
self.assertEqual(file_obj.encoding, "UTF-8")

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = from_fd(f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", NULL, "\n", 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)
self.assertEqual(file_obj.errors, "strict")

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
file_obj = from_fd(f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", NULL, 0)
self.assertIsInstance(file_obj, io.TextIOWrapper)
self.assertIsNone(file_obj.newlines)

def test_string_args_as_invalid_utf(self):
for arg_pos in (4, 5, 6):
with self.subTest(arg_pos=arg_pos):
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
args = [
f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", "\n", 0,
]
args[arg_pos] = "\xc3\x28" # invalid utf string
self.assertRaises(
(ValueError, LookupError),
_testcapi.file_from_fd,
*args,
)
from_fd = _testcapi.file_from_fd
invalid_utf = "\xc3\x28" # TODO: use bytes

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
with self.assertRaises((ValueError, LookupError)):
from_fd(f.fileno(), os_helper.TESTFN, "w",
1, invalid_utf, "strict", "\n", 0)

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
with self.assertRaises((ValueError, LookupError)):
from_fd(f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", invalid_utf, "\n", 0)

with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
with self.assertRaises((ValueError, LookupError)):
from_fd(f.fileno(), os_helper.TESTFN, "w",
1, "utf-8", "strict", invalid_utf, 0)


class TestPyFile_GetLine(_TempFileMixin, unittest.TestCase):
Expand Down Expand Up @@ -109,8 +120,8 @@ def assertGetLine(self, first_line, eof=False):

def test_file_empty_line(self):
first_line = ""
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
f.writelines([first_line])
with open(os_helper.TESTFN, "w", encoding="utf-8"):
pass
self.assertGetLine(first_line, eof=True)

def test_file_single_unicode_line(self):
Expand All @@ -122,7 +133,7 @@ def test_file_single_unicode_line(self):
self.assertGetLine(first_line)

def test_file_single_unicode_line_invalid_utf(self):
first_line = "\xc3\x28\n"
first_line = "\xc3\x28\n" # TODO: use bytes
with open(os_helper.TESTFN, "w", encoding="utf-8") as f:
f.writelines([first_line])
self.assertGetLine(first_line)
Expand Down Expand Up @@ -153,9 +164,7 @@ def test_file_get_multiple_lines(self):
def test_file_get_line_from_file_like(self):
first_line = "text with юникод 统一码\n"
second_line = "second line\n"
contents = io.StringIO()
contents.writelines([first_line, second_line])
contents.seek(0)
contents = io.StringIO(f"{first_line}{second_line}")
self.assertEqual(self.get_line(contents, 0), first_line)
self.assertEqual(self.get_line(contents, 0), second_line)

Expand Down Expand Up @@ -220,6 +229,23 @@ def __repr__(self):
"<str>",
)

def test_file_write_custom_obj_raises(self):
class ReprRaises:
def __repr__(self):
raise ValueError("repr raised")

with self.assertRaisesRegex(ValueError, "repr raised"):
self.write_and_return(ReprRaises())
with self.assertRaisesRegex(ValueError, "repr raised"):
self.write_and_return(ReprRaises(), flags=_testcapi.Py_PRINT_RAW)

class StrRaises:
def __str__(self):
raise ValueError("str raised")

with self.assertRaisesRegex(ValueError, "str raised"):
self.write_and_return(StrRaises(), flags=_testcapi.Py_PRINT_RAW)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear what is the difference between these tests if it raises in any case. You should either define __str__ and __repr__ that do not raise in corresponding classes and test both classes with and without Py_PRINT_RAW, or just make both __str__ and __repr__ in the same class raising different exceptions and test that writing with and without Py_PRINT_RAW gives different errors. The former option will duplicate other tests, so I suggest the later way.

Oh, and you do not need to use write_and_return here.

def test_file_write_null(self):
self.assertEqual(self.write_and_return(NULL), "<NULL>")

Expand All @@ -243,11 +269,21 @@ def test_file_write_to_ascii_file(self):
)

def test_file_write_invalid(self):
self.assertRaises(TypeError, self.write, object(), io.BytesIO(), 0)
self.assertRaises(AttributeError, self.write, object(), object(), 0)
self.assertRaises(TypeError, self.write, object(), NULL, 0)
self.assertRaises(AttributeError, self.write, NULL, object(), 0)
self.assertRaises(TypeError, self.write, NULL, NULL, 0)
wr = self.write
self.assertRaises(TypeError, wr, object(), io.BytesIO(), 0)
self.assertRaises(AttributeError, wr, object(), object(), 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a string instead of object(). It will be clearer what you write and why this fails.

self.assertRaises(TypeError, wr, object(), NULL, 0)
self.assertRaises(AttributeError, wr, NULL, object(), 0)
self.assertRaises(TypeError, wr, NULL, NULL, 0)

def test_file_write_invalid_print_raw(self):
wr = self.write
raw = _testcapi.Py_PRINT_RAW
self.assertRaises(TypeError, wr, object(), io.BytesIO(), raw)
self.assertRaises(AttributeError, wr, object(), object(), raw)
self.assertRaises(TypeError, wr, object(), NULL, raw)
self.assertRaises(AttributeError, wr, NULL, object(), raw)
self.assertRaises(TypeError, wr, NULL, NULL, raw)


class TestPyFile_WriteString(unittest.TestCase):
Expand All @@ -264,6 +300,7 @@ def test_file_write_string(self):
self.write_and_return("text with юникод 统一码"),
"text with юникод 统一码",
)
# TODO: use real invalid utf8 via bytes
self.assertEqual(self.write_and_return("\xc3\x28"), "\xc3\x28")

def test_invalid_write(self):
Expand Down
Loading