diff --git a/Lib/test/test_io/test_textio.py b/Lib/test/test_io/test_textio.py index a18386d68569ae9..ff4a6742401069f 100644 --- a/Lib/test/test_io/test_textio.py +++ b/Lib/test/test_io/test_textio.py @@ -577,6 +577,28 @@ def multi_line_test(self, f, enc): rlines.append((pos, line)) self.assertEqual(rlines, wlines) + def test_truncate_after_read(self): + # gh-161275: truncate() without an argument resizes the stream to the + # current position. The buffer may have read ahead of the text + # stream, so the buffer's own position must not be used. + with self.open(os_helper.TESTFN, "w", encoding="utf-8") as f: + f.write("abcde") + with self.open(os_helper.TESTFN, "r+", encoding="utf-8") as f: + self.assertEqual(f.read(2), "ab") + self.assertEqual(f.tell(), 2) + self.assertEqual(f.truncate(), 2) + self.assertEqual(f.tell(), 2) + with self.open(os_helper.TESTFN, "r", encoding="utf-8") as f: + self.assertEqual(f.read(), "ab") + + # Also with a file larger than the read-ahead chunk. + with self.open(os_helper.TESTFN, "w", encoding="utf-8") as f: + f.write("0123456789" * 2000) + with self.open(os_helper.TESTFN, "r+", encoding="utf-8") as f: + self.assertEqual(f.read(10), "0123456789") + self.assertEqual(f.truncate(), 10) + self.assertEqual(os.path.getsize(os_helper.TESTFN), 10) + def test_telling(self): f = self.open(os_helper.TESTFN, "w+", encoding="utf-8") p0 = f.tell() diff --git a/Misc/NEWS.d/next/Library/2026-11-04-09-40-00.gh-issue-161275.Rt4Xw8.rst b/Misc/NEWS.d/next/Library/2026-11-04-09-40-00.gh-issue-161275.Rt4Xw8.rst new file mode 100644 index 000000000000000..c8f61417443e36b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-04-09-40-00.gh-issue-161275.Rt4Xw8.rst @@ -0,0 +1,3 @@ +:meth:`~io.IOBase.truncate` on an :class:`io.TextIOWrapper` with no argument +resized the stream to the position of the underlying buffer, which may have +read ahead, instead of to the current position of the text stream. diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 4ef5251d451a4cd..b02318190468759 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -3112,6 +3112,20 @@ _io_TextIOWrapper_truncate_impl(textio *self, PyObject *pos) return NULL; } + if (pos == Py_None) { + /* The buffer may have read ahead of this stream's position, so the + buffer's own position must not be used here. Resize to the + position reported by tell(), as _pyio already does. */ + PyObject *cookie = _io_TextIOWrapper_tell_impl(self); + if (cookie == NULL) { + return NULL; + } + PyObject *res = buffer_callmethod_onearg(self, &_Py_ID(truncate), + cookie); + Py_DECREF(cookie); + return res; + } + return buffer_callmethod_onearg(self, &_Py_ID(truncate), pos); }