Made test_file pass. This meant adding support for read(-1) and read()

to even the most basic file object (I also added readall() which may
be a better API).  Also, not all the tests requiring specific failure
modes could be saved.  And there were the usual str/bytes issues.
I made sure test_io.py still passes (io.py is now most thoroughly
tested by combining test_file.py and test_io.py).
This commit is contained in:
Guido van Rossum 2007-07-10 06:54:34 +00:00
parent e8432ac42f
commit 7165cb1a48
3 changed files with 158 additions and 99 deletions

View File

@ -101,7 +101,9 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None):
updating = "+" in modes
text = "t" in modes
binary = "b" in modes
if "U" in modes and not (reading or writing or appending):
if "U" in modes:
if writing or appending:
raise ValueError("can't use U and writing mode at once")
reading = True
if text and binary:
raise ValueError("can't have text and binary mode at once")
@ -296,7 +298,7 @@ class IOBase:
"""
return False
### Readline ###
### Readline[s] and writelines ###
def readline(self, limit: int = -1) -> bytes:
"""For backwards compatibility, a (slowish) readline()."""
@ -324,6 +326,31 @@ class IOBase:
break
return res
def __iter__(self):
return self
def __next__(self):
line = self.readline()
if not line:
raise StopIteration
return line
def readlines(self, hint=None):
if hint is None:
return list(self)
n = 0
lines = []
for line in self:
lines.append(line)
n += len(line)
if n >= hint:
break
return lines
def writelines(self, lines):
for line in lines:
self.write(line)
class RawIOBase(IOBase):
@ -340,17 +367,31 @@ class RawIOBase(IOBase):
recursion in case a subclass doesn't implement either.)
"""
def read(self, n: int) -> bytes:
def read(self, n: int = -1) -> bytes:
"""read(n: int) -> bytes. Read and return up to n bytes.
Returns an empty bytes array on EOF, or None if the object is
set not to block and has no data to read.
"""
if n is None:
n = -1
if n < 0:
return self.readall()
b = bytes(n.__index__())
n = self.readinto(b)
del b[n:]
return b
def readall(self):
"""readall() -> bytes. Read until EOF, using multiple read() call."""
res = bytes()
while True:
data = self.read(DEFAULT_BUFFER_SIZE)
if not data:
break
res += data
return res
def readinto(self, b: bytes) -> int:
"""readinto(b: bytes) -> int. Read up to len(b) bytes into b.
@ -494,7 +535,13 @@ class BufferedIOBase(IOBase):
# XXX This ought to work with anything that supports the buffer API
data = self.read(len(b))
n = len(data)
b[:n] = data
try:
b[:n] = data
except TypeError as err:
import array
if not isinstance(b, array.array):
raise err
b[:n] = array.array('b', data)
return n
def write(self, b: bytes) -> int:
@ -530,6 +577,8 @@ class _BufferedIOMixin(BufferedIOBase):
return self.raw.tell()
def truncate(self, pos=None):
if pos is None:
pos = self.tell()
return self.raw.truncate(pos)
### Flush and close ###
@ -731,6 +780,9 @@ class BufferedWriter(_BufferedIOMixin):
def write(self, b):
if not isinstance(b, bytes):
if hasattr(b, "__index__"):
raise TypeError("Can't write object of type %s" %
type(b).__name__)
b = bytes(b)
# XXX we can implement some more tricks to try and avoid partial writes
if len(self._write_buf) > self.buffer_size:
@ -924,42 +976,11 @@ class TextIOBase(IOBase):
"""
self._unsupported("readline")
def __iter__(self) -> "TextIOBase": # That's a forward reference
"""__iter__() -> Iterator. Return line iterator (actually just self).
"""
return self
def __next__(self) -> str:
"""Same as readline() except raises StopIteration on immediate EOF."""
line = self.readline()
if not line:
raise StopIteration
return line
@property
def encoding(self):
"""Subclasses should override."""
return None
# The following are provided for backwards compatibility
def readlines(self, hint=None):
if hint is None:
return list(self)
n = 0
lines = []
while not lines or n < hint:
line = self.readline()
if not line:
break
lines.append(line)
n += len(line)
return lines
def writelines(self, lines):
for line in lines:
self.write(line)
class TextIOWrapper(TextIOBase):

View File

@ -34,34 +34,31 @@ class AutoFileTests(unittest.TestCase):
f.mode # ditto
f.closed # ditto
# verify the others aren't
for attr in 'name', 'mode', 'closed':
self.assertRaises((AttributeError, TypeError), setattr, f, attr, 'oops')
def testReadinto(self):
# verify readinto
self.f.write('12')
self.f.close()
a = array('c', 'x'*10)
a = array('b', b'x'*10)
self.f = open(TESTFN, 'rb')
n = self.f.readinto(a)
self.assertEquals('12', a.tostring()[:n])
self.assertEquals(b'12', a.tostring()[:n])
def testReadinto_text(self):
# verify readinto refuses text files
a = array('c', 'x'*10)
a = array('b', b'x'*10)
self.f.close()
self.f = open(TESTFN, 'r')
self.assertRaises(TypeError, self.f.readinto, a)
if hasattr(self.f, "readinto"):
self.assertRaises(TypeError, self.f.readinto, a)
def testWritelinesUserList(self):
# verify writelines with instance sequence
l = UserList(['1', '2'])
l = UserList([b'1', b'2'])
self.f.writelines(l)
self.f.close()
self.f = open(TESTFN, 'rb')
buf = self.f.read()
self.assertEquals(buf, '12')
self.assertEquals(buf, b'12')
def testWritelinesIntegers(self):
# verify writelines with integers
@ -80,17 +77,14 @@ class AutoFileTests(unittest.TestCase):
self.assertRaises(TypeError, self.f.writelines,
[NonString(), NonString()])
def testRepr(self):
# verify repr works
self.assert_(repr(self.f).startswith("<open file '" + TESTFN))
def testErrors(self):
f = self.f
self.assertEquals(f.name, TESTFN)
self.assert_(not f.isatty())
self.assert_(not f.closed)
self.assertRaises(TypeError, f.readinto, "")
if hasattr(f, "readinto"):
self.assertRaises((IOError, TypeError), f.readinto, "")
f.close()
self.assert_(f.closed)
@ -105,11 +99,11 @@ class AutoFileTests(unittest.TestCase):
self.f.__exit__(None, None, None)
self.assert_(self.f.closed)
for methodname in methods:
method = getattr(self.f, methodname)
# should raise on closed file
self.assertRaises(ValueError, method)
self.assertRaises(ValueError, self.f.writelines, [])
## for methodname in methods:
## method = getattr(self.f, methodname)
## # should raise on closed file
## self.assertRaises(ValueError, method)
## self.assertRaises(ValueError, self.f.writelines, [])
# file is closed, __exit__ shouldn't do anything
self.assertEquals(self.f.__exit__(None, None, None), None)
@ -136,19 +130,12 @@ class OtherFileTests(unittest.TestCase):
def testStdin(self):
# This causes the interpreter to exit on OSF1 v5.1.
if sys.platform != 'osf1V5':
self.assertRaises(IOError, sys.stdin.seek, -1)
self.assertRaises(ValueError, sys.stdin.seek, -1)
else:
print((
' Skipping sys.stdin.seek(-1), it may crash the interpreter.'
' Test manually.'), file=sys.__stdout__)
self.assertRaises(IOError, sys.stdin.truncate)
def testUnicodeOpen(self):
# verify repr works for unicode too
f = open(str(TESTFN), "w")
self.assert_(repr(f).startswith("<open file u'" + TESTFN))
f.close()
os.unlink(TESTFN)
self.assertRaises(ValueError, sys.stdin.truncate)
def testBadModeArgument(self):
# verify that we get a sensible error message for bad mode argument
@ -171,12 +158,12 @@ class OtherFileTests(unittest.TestCase):
# misbehaviour especially with repeated close() calls
for s in (-1, 0, 1, 512):
try:
f = open(TESTFN, 'w', s)
f.write(str(s))
f = open(TESTFN, 'wb', s)
f.write(str(s).encode("ascii"))
f.close()
f.close()
f = open(TESTFN, 'r', s)
d = int(f.read())
f = open(TESTFN, 'rb', s)
d = int(f.read().decode("ascii"))
f.close()
f.close()
except IOError as msg:
@ -190,12 +177,12 @@ class OtherFileTests(unittest.TestCase):
# SF bug <http://www.python.org/sf/801631>
# "file.truncate fault on windows"
f = open(TESTFN, 'wb')
f.write('12345678901') # 11 bytes
f.write(b'12345678901') # 11 bytes
f.close()
f = open(TESTFN,'rb+')
data = f.read(5)
if data != '12345':
if data != b'12345':
self.fail("Read on file opened for update failed %r" % data)
if f.tell() != 5:
self.fail("File pos after read wrong %d" % f.tell())
@ -216,28 +203,22 @@ class OtherFileTests(unittest.TestCase):
def testIteration(self):
# Test the complex interaction when mixing file-iteration and the
# various read* methods. Ostensibly, the mixture could just be tested
# to work when it should work according to the Python language,
# instead of fail when it should fail according to the current CPython
# implementation. People don't always program Python the way they
# should, though, and the implemenation might change in subtle ways,
# so we explicitly test for errors, too; the test will just have to
# be updated when the implementation changes.
# various read* methods.
dataoffset = 16384
filler = "ham\n"
assert not dataoffset % len(filler), \
"dataoffset must be multiple of len(filler)"
nchunks = dataoffset // len(filler)
testlines = [
"spam, spam and eggs\n",
"eggs, spam, ham and spam\n",
"saussages, spam, spam and eggs\n",
"spam, ham, spam and eggs\n",
"spam, spam, spam, spam, spam, ham, spam\n",
"wonderful spaaaaaam.\n"
b"spam, spam and eggs\n",
b"eggs, spam, ham and spam\n",
b"saussages, spam, spam and eggs\n",
b"spam, ham, spam and eggs\n",
b"spam, spam, spam, spam, spam, ham, spam\n",
b"wonderful spaaaaaam.\n"
]
methods = [("readline", ()), ("read", ()), ("readlines", ()),
("readinto", (array("c", " "*100),))]
("readinto", (array("b", b" "*100),))]
try:
# Prepare the testfile
@ -251,13 +232,7 @@ class OtherFileTests(unittest.TestCase):
if next(f) != filler:
self.fail, "Broken testfile"
meth = getattr(f, methodname)
try:
meth(*args)
except ValueError:
pass
else:
self.fail("%s%r after next() didn't raise ValueError" %
(methodname, args))
meth(*args) # This simply shouldn't fail
f.close()
# Test to see if harmless (by accident) mixing of read* and
@ -280,7 +255,7 @@ class OtherFileTests(unittest.TestCase):
self.fail("readline() after next() with empty buffer "
"failed. Got %r, expected %r" % (line, testline))
testline = testlines.pop(0)
buf = array("c", "\x00" * len(testline))
buf = array("b", b"\x00" * len(testline))
try:
f.readinto(buf)
except ValueError:

View File

@ -365,11 +365,69 @@ fileio_readinto(PyFileIOObject *self, PyObject *args)
return PyInt_FromLong(n);
}
#define DEFAULT_BUFFER_SIZE (8*1024)
static PyObject *
fileio_readall(PyFileIOObject *self)
{
PyObject *result;
Py_ssize_t total = 0;
int n;
result = PyBytes_FromStringAndSize(NULL, DEFAULT_BUFFER_SIZE);
if (result == NULL)
return NULL;
while (1) {
Py_ssize_t newsize = total + DEFAULT_BUFFER_SIZE;
if (PyBytes_GET_SIZE(result) < newsize) {
if (PyBytes_Resize(result, newsize) < 0) {
if (total == 0) {
Py_DECREF(result);
return NULL;
}
PyErr_Clear();
break;
}
}
Py_BEGIN_ALLOW_THREADS
errno = 0;
n = read(self->fd,
PyBytes_AS_STRING(result) + total,
newsize - total);
Py_END_ALLOW_THREADS
if (n == 0)
break;
if (n < 0) {
if (total > 0)
break;
if (errno == EAGAIN) {
Py_DECREF(result);
Py_RETURN_NONE;
}
Py_DECREF(result);
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
total += n;
}
if (PyBytes_GET_SIZE(result) > total) {
if (PyBytes_Resize(result, total) < 0) {
/* This should never happen, but just in case */
Py_DECREF(result);
return NULL;
}
}
return result;
}
static PyObject *
fileio_read(PyFileIOObject *self, PyObject *args)
{
char *ptr;
Py_ssize_t n, size;
Py_ssize_t n;
Py_ssize_t size = -1;
PyObject *bytes;
if (self->fd < 0)
@ -377,13 +435,11 @@ fileio_read(PyFileIOObject *self, PyObject *args)
if (!self->readable)
return err_mode("reading");
if (!PyArg_ParseTuple(args, "i", &size))
if (!PyArg_ParseTuple(args, "|i", &size))
return NULL;
if (size < 0) {
PyErr_SetString(PyExc_ValueError,
"negative read count");
return NULL;
return fileio_readall(self);
}
bytes = PyBytes_FromStringAndSize(NULL, size);
@ -624,8 +680,14 @@ PyDoc_STRVAR(read_doc,
"read(size: int) -> bytes. read at most size bytes, returned as bytes.\n"
"\n"
"Only makes one system call, so less data may be returned than requested\n"
"In non-blocking mode, returns None if no data is available. On\n"
"end-of-file, returns 0.");
"In non-blocking mode, returns None if no data is available.\n"
"On end-of-file, returns ''.");
PyDoc_STRVAR(readall_doc,
"readall() -> bytes. read all data from the file, returned as bytes.\n"
"\n"
"In non-blocking mode, returns as much as is immediately available,\n"
"or None if no data is available. On end-of-file, returns ''.");
PyDoc_STRVAR(write_doc,
"write(b: bytes) -> int. Write bytes b to file, return number written.\n"
@ -680,6 +742,7 @@ PyDoc_STRVAR(writable_doc,
static PyMethodDef fileio_methods[] = {
{"read", (PyCFunction)fileio_read, METH_VARARGS, read_doc},
{"readall", (PyCFunction)fileio_readall, METH_NOARGS, readall_doc},
{"readinto", (PyCFunction)fileio_readinto, METH_VARARGS, readinto_doc},
{"write", (PyCFunction)fileio_write, METH_VARARGS, write_doc},
{"seek", (PyCFunction)fileio_seek, METH_VARARGS, seek_doc},