Skip to content

Commit b4be619

Browse files
committed
gh-161275: Fix TextIOWrapper.truncate() with no argument
truncate() delegated to the buffer without an explicit position, so the stream was resized to wherever the buffer had read ahead to rather than to the current position of the text stream. _pyio passes tell() through and already behaves correctly, so this only changes the C implementation.
1 parent 7e3290d commit b4be619

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

‎Lib/test/test_io/test_textio.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,28 @@ def multi_line_test(self, f, enc):
577577
rlines.append((pos, line))
578578
self.assertEqual(rlines, wlines)
579579

580+
def test_truncate_after_read(self):
581+
# gh-161275: truncate() without an argument resizes the stream to the
582+
# current position. The buffer may have read ahead of the text
583+
# stream, so the buffer's own position must not be used.
584+
with self.open(os_helper.TESTFN, "w", encoding="utf-8") as f:
585+
f.write("abcde")
586+
with self.open(os_helper.TESTFN, "r+", encoding="utf-8") as f:
587+
self.assertEqual(f.read(2), "ab")
588+
self.assertEqual(f.tell(), 2)
589+
self.assertEqual(f.truncate(), 2)
590+
self.assertEqual(f.tell(), 2)
591+
with self.open(os_helper.TESTFN, "r", encoding="utf-8") as f:
592+
self.assertEqual(f.read(), "ab")
593+
594+
# Also with a file larger than the read-ahead chunk.
595+
with self.open(os_helper.TESTFN, "w", encoding="utf-8") as f:
596+
f.write("0123456789" * 2000)
597+
with self.open(os_helper.TESTFN, "r+", encoding="utf-8") as f:
598+
self.assertEqual(f.read(10), "0123456789")
599+
self.assertEqual(f.truncate(), 10)
600+
self.assertEqual(os.path.getsize(os_helper.TESTFN), 10)
601+
580602
def test_telling(self):
581603
f = self.open(os_helper.TESTFN, "w+", encoding="utf-8")
582604
p0 = f.tell()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:meth:`io.TextIOWrapper.truncate` with no argument resized the stream to the
2+
position of the underlying buffer, which may have read ahead, instead of the
3+
current position of the text stream. It now agrees with the pure Python
4+
implementation and with its documented behaviour.

‎Modules/_io/textio.c‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3112,6 +3112,20 @@ _io_TextIOWrapper_truncate_impl(textio *self, PyObject *pos)
31123112
return NULL;
31133113
}
31143114

3115+
if (pos == Py_None) {
3116+
/* The buffer may have read ahead of this stream's position, so the
3117+
buffer's own position must not be used here. Resize to the
3118+
position reported by tell(), as _pyio already does. */
3119+
PyObject *cookie = _io_TextIOWrapper_tell_impl(self);
3120+
if (cookie == NULL) {
3121+
return NULL;
3122+
}
3123+
PyObject *res = buffer_callmethod_onearg(self, &_Py_ID(truncate),
3124+
cookie);
3125+
Py_DECREF(cookie);
3126+
return res;
3127+
}
3128+
31153129
return buffer_callmethod_onearg(self, &_Py_ID(truncate), pos);
31163130
}
31173131

0 commit comments

Comments
 (0)