]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-111495: Add PyFile tests (#129449)
authorVictor Stinner <vstinner@python.org>
Thu, 30 Jan 2025 17:05:32 +0000 (18:05 +0100)
committerGitHub <noreply@github.com>
Thu, 30 Jan 2025 17:05:32 +0000 (18:05 +0100)
Add tests for the following functions in test_capi.test_file:

* PyFile_FromFd()
* PyFile_GetLine()
* PyFile_NewStdPrinter()
* PyFile_WriteObject()
* PyFile_WriteString()
* PyObject_AsFileDescriptor()

Add Modules/_testlimitedcapi/file.c file.

Remove test_embed.StdPrinterTests which became redundant.

Lib/test/test_capi/test_file.py
Lib/test/test_embed.py
Modules/Setup.stdlib.in
Modules/_testcapi/clinic/file.c.h
Modules/_testcapi/file.c
Modules/_testlimitedcapi.c
Modules/_testlimitedcapi/clinic/file.c.h [new file with mode: 0644]
Modules/_testlimitedcapi/file.c [new file with mode: 0644]
Modules/_testlimitedcapi/parts.h
PCbuild/_testlimitedcapi.vcxproj
PCbuild/_testlimitedcapi.vcxproj.filters

index a67a5121c4588bf46be1bee2f42d131a43562246..b57677569928614cba704a7d33dff28c13e23300 100644 (file)
+import io
 import os
 import unittest
+import warnings
 from test import support
-from test.support import import_helper, os_helper
+from test.support import import_helper, os_helper, warnings_helper
 
-_testcapi = import_helper.import_module('_testcapi')
 
+_testcapi = import_helper.import_module('_testcapi')
+_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+_io = import_helper.import_module('_io')
 NULL = None
+STDOUT_FD = 1
+
+with open(__file__, 'rb') as fp:
+    FIRST_LINE = next(fp).decode()
+FIRST_LINE_NORM = FIRST_LINE.rstrip() + '\n'
 
 
 class CAPIFileTest(unittest.TestCase):
+    def test_pyfile_fromfd(self):
+        # Test PyFile_FromFd() which is a thin wrapper to _io.open()
+        pyfile_fromfd = _testlimitedcapi.pyfile_fromfd
+        filename = __file__
+        with open(filename, "rb") as fp:
+            fd = fp.fileno()
+
+            # FileIO
+            fp.seek(0)
+            obj = pyfile_fromfd(fd, filename, "rb", 0, NULL, NULL, NULL, 0)
+            try:
+                self.assertIsInstance(obj, _io.FileIO)
+                self.assertEqual(obj.readline(), FIRST_LINE.encode())
+            finally:
+                obj.close()
+
+            # BufferedReader
+            fp.seek(0)
+            obj = pyfile_fromfd(fd, filename, "rb", 1024, NULL, NULL, NULL, 0)
+            try:
+                self.assertIsInstance(obj, _io.BufferedReader)
+                self.assertEqual(obj.readline(), FIRST_LINE.encode())
+            finally:
+                obj.close()
+
+            # TextIOWrapper
+            fp.seek(0)
+            obj = pyfile_fromfd(fd, filename, "r", 1,
+                                "utf-8", "replace", NULL, 0)
+            try:
+                self.assertIsInstance(obj, _io.TextIOWrapper)
+                self.assertEqual(obj.encoding, "utf-8")
+                self.assertEqual(obj.errors, "replace")
+                self.assertEqual(obj.readline(), FIRST_LINE_NORM)
+            finally:
+                obj.close()
+
+    def test_pyfile_getline(self):
+        # Test PyFile_GetLine(file, n): call file.readline()
+        # and strip "\n" suffix if n < 0.
+        pyfile_getline = _testlimitedcapi.pyfile_getline
+
+        # Test Unicode
+        with open(__file__, "r") as fp:
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, -1),
+                             FIRST_LINE_NORM.rstrip('\n'))
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, 0),
+                             FIRST_LINE_NORM)
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, 6),
+                             FIRST_LINE_NORM[:6])
+
+        # Test bytes
+        with open(__file__, "rb") as fp:
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, -1),
+                             FIRST_LINE.rstrip('\n').encode())
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, 0),
+                             FIRST_LINE.encode())
+            fp.seek(0)
+            self.assertEqual(pyfile_getline(fp, 6),
+                             FIRST_LINE.encode()[:6])
+
+    def test_pyfile_writestring(self):
+        # Test PyFile_WriteString(str, file): call file.write(str)
+        writestr = _testlimitedcapi.pyfile_writestring
+
+        with io.StringIO() as fp:
+            self.assertEqual(writestr("a\xe9\u20ac\U0010FFFF".encode(), fp), 0)
+            with self.assertRaises(UnicodeDecodeError):
+                writestr(b"\xff", fp)
+            with self.assertRaises(UnicodeDecodeError):
+                writestr("\udc80".encode("utf-8", "surrogatepass"), fp)
+
+            text = fp.getvalue()
+            self.assertEqual(text, "a\xe9\u20ac\U0010FFFF")
+
+        with self.assertRaises(SystemError):
+            writestr(b"abc", NULL)
+
+    def test_pyfile_writeobject(self):
+        # Test PyFile_WriteObject(obj, file, flags):
+        # - Call file.write(str(obj)) if flags equals Py_PRINT_RAW.
+        # - Call file.write(repr(obj)) otherwise.
+        writeobject = _testlimitedcapi.pyfile_writeobject
+        Py_PRINT_RAW = 1
+
+        with io.StringIO() as fp:
+            # Test flags=Py_PRINT_RAW
+            self.assertEqual(writeobject("raw", fp, Py_PRINT_RAW), 0)
+            writeobject(NULL, fp, Py_PRINT_RAW)
+
+            # Test flags=0
+            self.assertEqual(writeobject("repr", fp, 0), 0)
+            writeobject(NULL, fp, 0)
+
+            text = fp.getvalue()
+            self.assertEqual(text, "raw<NULL>'repr'<NULL>")
+
+        # invalid file type
+        for invalid_file in (123, "abc", object()):
+            with self.subTest(file=invalid_file):
+                with self.assertRaises(AttributeError):
+                    writeobject("abc", invalid_file, Py_PRINT_RAW)
+
+        with self.assertRaises(TypeError):
+            writeobject("abc", NULL, 0)
+
+    def test_pyobject_asfiledescriptor(self):
+        # Test PyObject_AsFileDescriptor(obj):
+        # - Return obj if obj is an integer.
+        # - Return obj.fileno() otherwise.
+        # File descriptor must be >= 0.
+        asfd = _testlimitedcapi.pyobject_asfiledescriptor
+
+        self.assertEqual(asfd(123), 123)
+        self.assertEqual(asfd(0), 0)
+
+        with open(__file__, "rb") as fp:
+            self.assertEqual(asfd(fp), fp.fileno())
+
+        # bool emits RuntimeWarning
+        msg = r"bool is used as a file descriptor"
+        with warnings_helper.check_warnings((msg, RuntimeWarning)):
+            self.assertEqual(asfd(True), 1)
+
+        class FakeFile:
+            def __init__(self, fd):
+                self.fd = fd
+            def fileno(self):
+                return self.fd
+
+        # file descriptor must be positive
+        with self.assertRaises(ValueError):
+            asfd(-1)
+        with self.assertRaises(ValueError):
+            asfd(FakeFile(-1))
+
+        # fileno() result must be an integer
+        with self.assertRaises(TypeError):
+            asfd(FakeFile("text"))
+
+        # unsupported types
+        for obj in ("string", ["list"], object()):
+            with self.subTest(obj=obj):
+                with self.assertRaises(TypeError):
+                    asfd(obj)
+
+        # CRASHES asfd(NULL)
+
+    def test_pyfile_newstdprinter(self):
+        # Test PyFile_NewStdPrinter()
+        pyfile_newstdprinter = _testcapi.pyfile_newstdprinter
+
+        file = pyfile_newstdprinter(STDOUT_FD)
+        self.assertEqual(file.closed, False)
+        self.assertIsNone(file.encoding)
+        self.assertEqual(file.mode, "w")
+
+        self.assertEqual(file.fileno(), STDOUT_FD)
+        self.assertEqual(file.isatty(), os.isatty(STDOUT_FD))
+
+        # flush() is a no-op
+        self.assertIsNone(file.flush())
+
+        # close() is a no-op
+        self.assertIsNone(file.close())
+        self.assertEqual(file.closed, False)
+
+        support.check_disallow_instantiation(self, type(file))
+
+    def test_pyfile_newstdprinter_write(self):
+        # Test the write() method of PyFile_NewStdPrinter()
+        pyfile_newstdprinter = _testcapi.pyfile_newstdprinter
+
+        filename = os_helper.TESTFN
+        self.addCleanup(os_helper.unlink, filename)
+
+        try:
+            old_stdout = os.dup(STDOUT_FD)
+        except OSError as exc:
+            # os.dup(STDOUT_FD) is not supported on WASI
+            self.skipTest(f"os.dup() failed with {exc!r}")
+
+        try:
+            with open(filename, "wb") as fp:
+                # PyFile_NewStdPrinter() only accepts fileno(stdout)
+                # or fileno(stderr) file descriptor.
+                fd = fp.fileno()
+                os.dup2(fd, STDOUT_FD)
+
+                file = pyfile_newstdprinter(STDOUT_FD)
+                self.assertEqual(file.write("text"), 4)
+                # The surrogate character is encoded with
+                # the "surrogateescape" error handler
+                self.assertEqual(file.write("[\udc80]"), 8)
+        finally:
+            os.dup2(old_stdout, STDOUT_FD)
+            os.close(old_stdout)
+
+        with open(filename, "r") as fp:
+            self.assertEqual(fp.read(), "text[\\udc80]")
+
     def test_py_fopen(self):
         # Test Py_fopen() and Py_fclose()
+        py_fopen = _testcapi.py_fopen
 
         with open(__file__, "rb") as fp:
             source = fp.read()
 
         for filename in (__file__, os.fsencode(__file__)):
             with self.subTest(filename=filename):
-                data = _testcapi.py_fopen(filename, "rb")
+                data = py_fopen(filename, "rb")
                 self.assertEqual(data, source[:256])
 
-                data = _testcapi.py_fopen(os_helper.FakePath(filename), "rb")
+                data = py_fopen(os_helper.FakePath(filename), "rb")
                 self.assertEqual(data, source[:256])
 
         filenames = [
@@ -43,41 +259,46 @@ class CAPIFileTest(unittest.TestCase):
                     filename = None
                     continue
                 try:
-                    data = _testcapi.py_fopen(filename, "rb")
+                    data = py_fopen(filename, "rb")
                     self.assertEqual(data, source[:256])
                 finally:
                     os_helper.unlink(filename)
 
         # embedded null character/byte in the filename
         with self.assertRaises(ValueError):
-            _testcapi.py_fopen("a\x00b", "rb")
+            py_fopen("a\x00b", "rb")
         with self.assertRaises(ValueError):
-            _testcapi.py_fopen(b"a\x00b", "rb")
+            py_fopen(b"a\x00b", "rb")
 
         # non-ASCII mode failing with "Invalid argument"
         with self.assertRaises(OSError):
-            _testcapi.py_fopen(__file__, b"\xc2\x80")
+            py_fopen(__file__, b"\xc2\x80")
         with self.assertRaises(OSError):
             # \x98 is invalid in cp1250, cp1251, cp1257
             # \x9d is invalid in cp1252-cp1255, cp1258
-            _testcapi.py_fopen(__file__, b"\xc2\x98\xc2\x9d")
+            py_fopen(__file__, b"\xc2\x98\xc2\x9d")
         # UnicodeDecodeError can come from the audit hook code
         with self.assertRaises((UnicodeDecodeError, OSError)):
-            _testcapi.py_fopen(__file__, b"\x98\x9d")
+            py_fopen(__file__, b"\x98\x9d")
 
         # invalid filename type
         for invalid_type in (123, object()):
             with self.subTest(filename=invalid_type):
                 with self.assertRaises(TypeError):
-                    _testcapi.py_fopen(invalid_type, "rb")
+                    py_fopen(invalid_type, "rb")
 
         if support.MS_WINDOWS:
             with self.assertRaises(OSError):
                 # On Windows, the file mode is limited to 10 characters
-                _testcapi.py_fopen(__file__, "rt+, ccs=UTF-8")
+                py_fopen(__file__, "rt+, ccs=UTF-8")
+
+        # CRASHES py_fopen(NULL, 'rb')
+        # CRASHES py_fopen(__file__, NULL)
+
+    # TODO: Test Py_UniversalNewlineFgets()
 
-        # CRASHES _testcapi.py_fopen(NULL, 'rb')
-        # CRASHES _testcapi.py_fopen(__file__, NULL)
+    # PyFile_SetOpenCodeHook() and PyFile_OpenCode() are tested by
+    # test_embed.test_open_code_hook()
 
 
 if __name__ == "__main__":
index f41c45ec4d9cddad502c3470d302fc96e0118c83..72221379a00051b669ee2eaecdbca1919d0bd815 100644 (file)
@@ -1985,56 +1985,5 @@ class MiscTests(EmbeddingTestsMixin, unittest.TestCase):
         self.assertIn("unique-python-message", out)
 
 
-class StdPrinterTests(EmbeddingTestsMixin, unittest.TestCase):
-    # Test PyStdPrinter_Type which is used by _PySys_SetPreliminaryStderr():
-    #   "Set up a preliminary stderr printer until we have enough
-    #    infrastructure for the io module in place."
-
-    STDOUT_FD = 1
-
-    def create_printer(self, fd):
-        ctypes = import_helper.import_module('ctypes')
-        PyFile_NewStdPrinter = ctypes.pythonapi.PyFile_NewStdPrinter
-        PyFile_NewStdPrinter.argtypes = (ctypes.c_int,)
-        PyFile_NewStdPrinter.restype = ctypes.py_object
-        return PyFile_NewStdPrinter(fd)
-
-    def test_write(self):
-        message = "unicode:\xe9-\u20ac-\udc80!\n"
-
-        stdout_fd = self.STDOUT_FD
-        stdout_fd_copy = os.dup(stdout_fd)
-        self.addCleanup(os.close, stdout_fd_copy)
-
-        rfd, wfd = os.pipe()
-        self.addCleanup(os.close, rfd)
-        self.addCleanup(os.close, wfd)
-        try:
-            # PyFile_NewStdPrinter() only accepts fileno(stdout)
-            # or fileno(stderr) file descriptor.
-            os.dup2(wfd, stdout_fd)
-
-            printer = self.create_printer(stdout_fd)
-            printer.write(message)
-        finally:
-            os.dup2(stdout_fd_copy, stdout_fd)
-
-        data = os.read(rfd, 100)
-        self.assertEqual(data, message.encode('utf8', 'backslashreplace'))
-
-    def test_methods(self):
-        fd = self.STDOUT_FD
-        printer = self.create_printer(fd)
-        self.assertEqual(printer.fileno(), fd)
-        self.assertEqual(printer.isatty(), os.isatty(fd))
-        printer.flush()  # noop
-        printer.close()  # noop
-
-    def test_disallow_instantiation(self):
-        fd = self.STDOUT_FD
-        printer = self.create_printer(fd)
-        support.check_disallow_instantiation(self, type(printer))
-
-
 if __name__ == "__main__":
     unittest.main()
index 563bbc1bda6223cc352030051a65e6b716ebb10b..b31bf2734e4fd10987d5dac0120fcf58d2b356eb 100644 (file)
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
 @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
 @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
 
index fddbf48071bd3b8e097fa0c4a274b737bcc555f7..6efb6b473534433febc5a0e2129da285eb88bc35 100644 (file)
@@ -4,6 +4,33 @@ preserve
 
 #include "pycore_modsupport.h"    // _PyArg_CheckPositional()
 
+PyDoc_STRVAR(_testcapi_pyfile_newstdprinter__doc__,
+"pyfile_newstdprinter($module, fd, /)\n"
+"--\n"
+"\n");
+
+#define _TESTCAPI_PYFILE_NEWSTDPRINTER_METHODDEF    \
+    {"pyfile_newstdprinter", (PyCFunction)_testcapi_pyfile_newstdprinter, METH_O, _testcapi_pyfile_newstdprinter__doc__},
+
+static PyObject *
+_testcapi_pyfile_newstdprinter_impl(PyObject *module, int fd);
+
+static PyObject *
+_testcapi_pyfile_newstdprinter(PyObject *module, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    int fd;
+
+    fd = PyLong_AsInt(arg);
+    if (fd == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = _testcapi_pyfile_newstdprinter_impl(module, fd);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(_testcapi_py_fopen__doc__,
 "py_fopen($module, path, mode, /)\n"
 "--\n"
@@ -34,4 +61,4 @@ _testcapi_py_fopen(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=c4dc92400306c3eb input=a9049054013a1b77]*/
+/*[clinic end generated code: output=e943bbd7f181d079 input=a9049054013a1b77]*/
index d15173fc7959e5eb15cf474f167ba435f2efa388..060e0f50598d7e8c0ac7a1dd66d5363a6e8f9e95 100644 (file)
@@ -5,11 +5,29 @@
 #include "util.h"
 #include "clinic/file.c.h"
 
+
 /*[clinic input]
 module _testcapi
 [clinic start generated code]*/
 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
 
+
+/*[clinic input]
+_testcapi.pyfile_newstdprinter
+
+    fd: int
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+_testcapi_pyfile_newstdprinter_impl(PyObject *module, int fd)
+/*[clinic end generated code: output=8a2d1c57b6892db3 input=442f1824142262ea]*/
+{
+    return PyFile_NewStdPrinter(fd);
+}
+
+
 /*[clinic input]
 _testcapi.py_fopen
 
@@ -38,7 +56,9 @@ _testcapi_py_fopen_impl(PyObject *module, PyObject *path, const char *mode,
     return PyBytes_FromStringAndSize(buffer, size);
 }
 
+
 static PyMethodDef test_methods[] = {
+    _TESTCAPI_PYFILE_NEWSTDPRINTER_METHODDEF
     _TESTCAPI_PY_FOPEN_METHODDEF
     {NULL},
 };
@@ -46,9 +66,5 @@ static PyMethodDef test_methods[] = {
 int
 _PyTestCapi_Init_File(PyObject *m)
 {
-    if (PyModule_AddFunctions(m, test_methods) < 0){
-        return -1;
-    }
-
-    return 0;
+    return PyModule_AddFunctions(m, test_methods);
 }
index 82dac1c999470f846594570e69ac7feab6c866d0..4dae99ec92a08501091d39cb97568f49d834f3b0 100644 (file)
@@ -89,5 +89,8 @@ PyInit__testlimitedcapi(void)
     if (_PyTestLimitedCAPI_Init_Version(mod) < 0) {
         return NULL;
     }
+    if (_PyTestLimitedCAPI_Init_File(mod) < 0) {
+        return NULL;
+    }
     return mod;
 }
diff --git a/Modules/_testlimitedcapi/clinic/file.c.h b/Modules/_testlimitedcapi/clinic/file.c.h
new file mode 100644 (file)
index 0000000..663619e
--- /dev/null
@@ -0,0 +1,81 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+PyDoc_STRVAR(_testcapi_pyfile_getline__doc__,
+"pyfile_getline($module, file, n, /)\n"
+"--\n"
+"\n");
+
+#define _TESTCAPI_PYFILE_GETLINE_METHODDEF    \
+    {"pyfile_getline", (PyCFunction)(void(*)(void))_testcapi_pyfile_getline, METH_FASTCALL, _testcapi_pyfile_getline__doc__},
+
+static PyObject *
+_testcapi_pyfile_getline_impl(PyObject *module, PyObject *file, int n);
+
+static PyObject *
+_testcapi_pyfile_getline(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+    PyObject *return_value = NULL;
+    PyObject *file;
+    int n;
+
+    if (nargs != 2) {
+        PyErr_Format(PyExc_TypeError, "pyfile_getline expected 2 arguments, got %zd", nargs);
+        goto exit;
+    }
+    file = args[0];
+    n = PyLong_AsInt(args[1]);
+    if (n == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = _testcapi_pyfile_getline_impl(module, file, n);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_testcapi_pyfile_writeobject__doc__,
+"pyfile_writeobject($module, obj, file, flags, /)\n"
+"--\n"
+"\n");
+
+#define _TESTCAPI_PYFILE_WRITEOBJECT_METHODDEF    \
+    {"pyfile_writeobject", (PyCFunction)(void(*)(void))_testcapi_pyfile_writeobject, METH_FASTCALL, _testcapi_pyfile_writeobject__doc__},
+
+static PyObject *
+_testcapi_pyfile_writeobject_impl(PyObject *module, PyObject *obj,
+                                  PyObject *file, int flags);
+
+static PyObject *
+_testcapi_pyfile_writeobject(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+    PyObject *return_value = NULL;
+    PyObject *obj;
+    PyObject *file;
+    int flags;
+
+    if (nargs != 3) {
+        PyErr_Format(PyExc_TypeError, "pyfile_writeobject expected 3 arguments, got %zd", nargs);
+        goto exit;
+    }
+    obj = args[0];
+    file = args[1];
+    flags = PyLong_AsInt(args[2]);
+    if (flags == -1 && PyErr_Occurred()) {
+        goto exit;
+    }
+    return_value = _testcapi_pyfile_writeobject_impl(module, obj, file, flags);
+
+exit:
+    return return_value;
+}
+
+PyDoc_STRVAR(_testcapi_pyobject_asfiledescriptor__doc__,
+"pyobject_asfiledescriptor($module, obj, /)\n"
+"--\n"
+"\n");
+
+#define _TESTCAPI_PYOBJECT_ASFILEDESCRIPTOR_METHODDEF    \
+    {"pyobject_asfiledescriptor", (PyCFunction)_testcapi_pyobject_asfiledescriptor, METH_O, _testcapi_pyobject_asfiledescriptor__doc__},
+/*[clinic end generated code: output=ea572aaaa01aec7b input=a9049054013a1b77]*/
diff --git a/Modules/_testlimitedcapi/file.c b/Modules/_testlimitedcapi/file.c
new file mode 100644 (file)
index 0000000..e082e3c
--- /dev/null
@@ -0,0 +1,128 @@
+#include "pyconfig.h"   // Py_GIL_DISABLED
+#ifndef Py_GIL_DISABLED
+   // Need limited C API 3.13 for PyLong_AsInt()
+#  define Py_LIMITED_API 0x030d0000
+#endif
+
+#include "parts.h"
+#include "util.h"
+#include "clinic/file.c.h"
+
+
+/*[clinic input]
+module _testcapi
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
+
+
+static PyObject *
+pyfile_fromfd(PyObject *module, PyObject *args)
+{
+    int fd;
+    const char *name;
+    Py_ssize_t size;
+    const char *mode;
+    int buffering;
+    const char *encoding;
+    const char *errors;
+    const char *newline;
+    int closefd;
+    if (!PyArg_ParseTuple(args,
+                          "iz#z#"
+                          "iz#z#"
+                          "z#i",
+                          &fd, &name, &size, &mode, &size,
+                          &buffering, &encoding, &size, &errors, &size,
+                          &newline, &size, &closefd)) {
+        return NULL;
+    }
+
+    return PyFile_FromFd(fd, name, mode, buffering,
+                         encoding, errors, newline, closefd);
+}
+
+
+/*[clinic input]
+_testcapi.pyfile_getline
+
+    file: object
+    n: int
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+_testcapi_pyfile_getline_impl(PyObject *module, PyObject *file, int n)
+/*[clinic end generated code: output=137fde2774563266 input=df26686148b3657e]*/
+{
+    return PyFile_GetLine(file, n);
+}
+
+
+/*[clinic input]
+_testcapi.pyfile_writeobject
+
+    obj: object
+    file: object
+    flags: int
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+_testcapi_pyfile_writeobject_impl(PyObject *module, PyObject *obj,
+                                  PyObject *file, int flags)
+/*[clinic end generated code: output=ebb4d802e3db489c input=64a34a3e75b9935a]*/
+{
+    NULLABLE(obj);
+    NULLABLE(file);
+    RETURN_INT(PyFile_WriteObject(obj, file, flags));
+}
+
+
+static PyObject *
+pyfile_writestring(PyObject *module, PyObject *args)
+{
+    const char *str;
+    Py_ssize_t size;
+    PyObject *file;
+    if (!PyArg_ParseTuple(args, "z#O", &str, &size, &file)) {
+        return NULL;
+    }
+    NULLABLE(file);
+
+    RETURN_INT(PyFile_WriteString(str, file));
+}
+
+
+/*[clinic input]
+_testcapi.pyobject_asfiledescriptor
+
+    obj: object
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+_testcapi_pyobject_asfiledescriptor(PyObject *module, PyObject *obj)
+/*[clinic end generated code: output=2d640c6a1970c721 input=45fa1171d62b18d7]*/
+{
+    NULLABLE(obj);
+    RETURN_INT(PyObject_AsFileDescriptor(obj));
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"pyfile_fromfd", pyfile_fromfd, METH_VARARGS},
+    _TESTCAPI_PYFILE_GETLINE_METHODDEF
+    _TESTCAPI_PYFILE_WRITEOBJECT_METHODDEF
+    {"pyfile_writestring", pyfile_writestring, METH_VARARGS},
+    _TESTCAPI_PYOBJECT_ASFILEDESCRIPTOR_METHODDEF
+    {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_File(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
index 9efcd8dcb71e5b3bdb9df2d7418484537f28f5cb..60f6f03011a65c457fd015ac85c7abf16ded8857 100644 (file)
@@ -42,5 +42,6 @@ int _PyTestLimitedCAPI_Init_Tuple(PyObject *module);
 int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
 int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
 int _PyTestLimitedCAPI_Init_Version(PyObject *module);
+int _PyTestLimitedCAPI_Init_File(PyObject *module);
 
 #endif // Py_TESTLIMITEDCAPI_PARTS_H
index 87abff5249309848c924d78548f5881db9c16e45..36c41fc9824fdaf89df57759701e36115ef980fe 100644 (file)
     <ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
index a975a508506905c7b1579e9998338a74f4923e9f..62ecb2f70ffa2ddc9e130eeecb96eaf09ff35560 100644 (file)
@@ -30,6 +30,7 @@
     <ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
+    <ClCompile Include="..\Modules\_testlimitedcapi\file.c" />
     <ClCompile Include="..\Modules\_testlimitedcapi.c" />
   </ItemGroup>
   <ItemGroup>