Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c45251a

Browse files
committed
SF patch #1397960: When mixing file-iteration and
readline/readlines/read/readinto, loudly break by raising ValueError, rather than silently deliver data out of order or hitting EOF prematurely. Probably not a bugfix candidate, even though it affects no 'working' code.
1 parent f5b3e36 commit c45251a

2 files changed

Lines changed: 150 additions & 3 deletions

File tree

Lib/test/test_file.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from array import array
44
from weakref import proxy
55

6-
from test.test_support import verify, TESTFN, TestFailed
6+
from test.test_support import verify, TESTFN, TestFailed, findfile
77
from UserList import UserList
88

99
# verify weak references
@@ -228,3 +228,113 @@ def bug801631():
228228
bug801631()
229229
finally:
230230
os.unlink(TESTFN)
231+
232+
# Test the complex interaction when mixing file-iteration and the various
233+
# read* methods. Ostensibly, the mixture could just be tested to work
234+
# when it should work according to the Python language, instead of fail
235+
# when it should fail according to the current CPython implementation.
236+
# People don't always program Python the way they should, though, and the
237+
# implemenation might change in subtle ways, so we explicitly test for
238+
# errors, too; the test will just have to be updated when the
239+
# implementation changes.
240+
dataoffset = 16384
241+
filler = "ham\n"
242+
assert not dataoffset % len(filler), \
243+
"dataoffset must be multiple of len(filler)"
244+
nchunks = dataoffset // len(filler)
245+
testlines = [
246+
"spam, spam and eggs\n",
247+
"eggs, spam, ham and spam\n",
248+
"saussages, spam, spam and eggs\n",
249+
"spam, ham, spam and eggs\n",
250+
"spam, spam, spam, spam, spam, ham, spam\n",
251+
"wonderful spaaaaaam.\n"
252+
]
253+
methods = [("readline", ()), ("read", ()), ("readlines", ()),
254+
("readinto", (array("c", " "*100),))]
255+
256+
try:
257+
# Prepare the testfile
258+
bag = open(TESTFN, "w")
259+
bag.write(filler * nchunks)
260+
bag.writelines(testlines)
261+
bag.close()
262+
# Test for appropriate errors mixing read* and iteration
263+
for methodname, args in methods:
264+
f = open(TESTFN)
265+
if f.next() != filler:
266+
raise TestFailed, "Broken testfile"
267+
meth = getattr(f, methodname)
268+
try:
269+
meth(*args)
270+
except ValueError:
271+
pass
272+
else:
273+
raise TestFailed("%s%r after next() didn't raise ValueError" %
274+
(methodname, args))
275+
f.close()
276+
277+
# Test to see if harmless (by accident) mixing of read* and iteration
278+
# still works. This depends on the size of the internal iteration
279+
# buffer (currently 8192,) but we can test it in a flexible manner.
280+
# Each line in the bag o' ham is 4 bytes ("h", "a", "m", "\n"), so
281+
# 4096 lines of that should get us exactly on the buffer boundary for
282+
# any power-of-2 buffersize between 4 and 16384 (inclusive).
283+
f = open(TESTFN)
284+
for i in range(nchunks):
285+
f.next()
286+
testline = testlines.pop(0)
287+
try:
288+
line = f.readline()
289+
except ValueError:
290+
raise TestFailed("readline() after next() with supposedly empty "
291+
"iteration-buffer failed anyway")
292+
if line != testline:
293+
raise TestFailed("readline() after next() with empty buffer "
294+
"failed. Got %r, expected %r" % (line, testline))
295+
testline = testlines.pop(0)
296+
buf = array("c", "\x00" * len(testline))
297+
try:
298+
f.readinto(buf)
299+
except ValueError:
300+
raise TestFailed("readinto() after next() with supposedly empty "
301+
"iteration-buffer failed anyway")
302+
line = buf.tostring()
303+
if line != testline:
304+
raise TestFailed("readinto() after next() with empty buffer "
305+
"failed. Got %r, expected %r" % (line, testline))
306+
307+
testline = testlines.pop(0)
308+
try:
309+
line = f.read(len(testline))
310+
except ValueError:
311+
raise TestFailed("read() after next() with supposedly empty "
312+
"iteration-buffer failed anyway")
313+
if line != testline:
314+
raise TestFailed("read() after next() with empty buffer "
315+
"failed. Got %r, expected %r" % (line, testline))
316+
try:
317+
lines = f.readlines()
318+
except ValueError:
319+
raise TestFailed("readlines() after next() with supposedly empty "
320+
"iteration-buffer failed anyway")
321+
if lines != testlines:
322+
raise TestFailed("readlines() after next() with empty buffer "
323+
"failed. Got %r, expected %r" % (line, testline))
324+
# Reading after iteration hit EOF shouldn't hurt either
325+
f = open(TESTFN)
326+
for line in f:
327+
pass
328+
try:
329+
f.readline()
330+
f.readinto(buf)
331+
f.read()
332+
f.readlines()
333+
except ValueError:
334+
raise TestFailed("read* failed after next() consumed file")
335+
finally:
336+
# Bare 'except' so as not to mask errors in the test
337+
try:
338+
os.unlink(TESTFN)
339+
except:
340+
pass

Objects/fileobject.c

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,17 @@ err_closed(void)
344344
return NULL;
345345
}
346346

347+
/* Refuse regular file I/O if there's data in the iteration-buffer.
348+
* Mixing them would cause data to arrive out of order, as the read*
349+
* methods don't use the iteration buffer. */
350+
static PyObject *
351+
err_iterbuffered(void)
352+
{
353+
PyErr_SetString(PyExc_ValueError,
354+
"Mixing iteration and read methods would lose data");
355+
return NULL;
356+
}
357+
347358
static void drop_readahead(PyFileObject *);
348359

349360
/* Methods */
@@ -795,6 +806,11 @@ file_read(PyFileObject *f, PyObject *args)
795806

796807
if (f->f_fp == NULL)
797808
return err_closed();
809+
/* refuse to mix with f.next() */
810+
if (f->f_buf != NULL &&
811+
(f->f_bufend - f->f_bufptr) > 0 &&
812+
f->f_buf[0] != '\0')
813+
return err_iterbuffered();
798814
if (!PyArg_ParseTuple(args, "|l:read", &bytesrequested))
799815
return NULL;
800816
if (bytesrequested < 0)
@@ -858,6 +874,11 @@ file_readinto(PyFileObject *f, PyObject *args)
858874

859875
if (f->f_fp == NULL)
860876
return err_closed();
877+
/* refuse to mix with f.next() */
878+
if (f->f_buf != NULL &&
879+
(f->f_bufend - f->f_bufptr) > 0 &&
880+
f->f_buf[0] != '\0')
881+
return err_iterbuffered();
861882
if (!PyArg_ParseTuple(args, "w#", &ptr, &ntodo))
862883
return NULL;
863884
ndone = 0;
@@ -1211,9 +1232,15 @@ PyFile_GetLine(PyObject *f, int n)
12111232
}
12121233

12131234
if (PyFile_Check(f)) {
1214-
if (((PyFileObject*)f)->f_fp == NULL)
1235+
PyFileObject *fo = (PyFileObject *)f;
1236+
if (fo->f_fp == NULL)
12151237
return err_closed();
1216-
result = get_line((PyFileObject *)f, n);
1238+
/* refuse to mix with f.next() */
1239+
if (fo->f_buf != NULL &&
1240+
(fo->f_bufend - fo->f_bufptr) > 0 &&
1241+
fo->f_buf[0] != '\0')
1242+
return err_iterbuffered();
1243+
result = get_line(fo, n);
12171244
}
12181245
else {
12191246
PyObject *reader;
@@ -1296,6 +1323,11 @@ file_readline(PyFileObject *f, PyObject *args)
12961323

12971324
if (f->f_fp == NULL)
12981325
return err_closed();
1326+
/* refuse to mix with f.next() */
1327+
if (f->f_buf != NULL &&
1328+
(f->f_bufend - f->f_bufptr) > 0 &&
1329+
f->f_buf[0] != '\0')
1330+
return err_iterbuffered();
12991331
if (!PyArg_ParseTuple(args, "|i:readline", &n))
13001332
return NULL;
13011333
if (n == 0)
@@ -1324,6 +1356,11 @@ file_readlines(PyFileObject *f, PyObject *args)
13241356

13251357
if (f->f_fp == NULL)
13261358
return err_closed();
1359+
/* refuse to mix with f.next() */
1360+
if (f->f_buf != NULL &&
1361+
(f->f_bufend - f->f_bufptr) > 0 &&
1362+
f->f_buf[0] != '\0')
1363+
return err_iterbuffered();
13271364
if (!PyArg_ParseTuple(args, "|l:readlines", &sizehint))
13281365
return NULL;
13291366
if ((list = PyList_New(0)) == NULL)

0 commit comments

Comments
 (0)