Commit 8742977b authored by Guido van Rossum's avatar Guido van Rossum

truncate() returns the new size and position.

write() returns the number of bytes/characters written/buffered.
FileIO.close() calls self.flush().
Implement readinto() for buffered readers.
Tests th check all these.
Test proper behavior of __enter__/__exit__.
parent 34d69e57
...@@ -177,10 +177,11 @@ class IOBase: ...@@ -177,10 +177,11 @@ class IOBase:
"""tell() -> int. Return current stream position.""" """tell() -> int. Return current stream position."""
return self.seek(0, 1) return self.seek(0, 1)
def truncate(self, pos: int = None) -> None: def truncate(self, pos: int = None) -> int:
"""truncate(size: int = None) -> None. Truncate file to size bytes. """truncate(size: int = None) -> int. Truncate file to size bytes.
Size defaults to the current IO position as reported by tell(). Size defaults to the current IO position as reported by tell().
Returns the new size.
""" """
self._unsupported("truncate") self._unsupported("truncate")
...@@ -329,6 +330,10 @@ class FileIO(_fileio._FileIO, RawIOBase): ...@@ -329,6 +330,10 @@ class FileIO(_fileio._FileIO, RawIOBase):
would be hard to do since _fileio.c is written in C). would be hard to do since _fileio.c is written in C).
""" """
def close(self):
_fileio._FileIO.close(self)
RawIOBase.close(self)
class SocketIO(RawIOBase): class SocketIO(RawIOBase):
...@@ -413,7 +418,10 @@ class BufferedIOBase(IOBase): ...@@ -413,7 +418,10 @@ class BufferedIOBase(IOBase):
Raises BlockingIOError if the underlying raw stream has no Raises BlockingIOError if the underlying raw stream has no
data at the moment. data at the moment.
""" """
self._unsupported("readinto") data = self.read(len(b))
n = len(data)
b[:n] = data
return n
def write(self, b: bytes) -> int: def write(self, b: bytes) -> int:
"""write(b: bytes) -> int. Write the given buffer to the IO stream. """write(b: bytes) -> int. Write the given buffer to the IO stream.
...@@ -448,7 +456,7 @@ class _BufferedIOMixin(BufferedIOBase): ...@@ -448,7 +456,7 @@ class _BufferedIOMixin(BufferedIOBase):
return self.raw.tell() return self.raw.tell()
def truncate(self, pos=None): def truncate(self, pos=None):
self.raw.truncate(pos) return self.raw.truncate(pos)
### Flush and close ### ### Flush and close ###
...@@ -503,12 +511,6 @@ class _MemoryIOMixin(BufferedIOBase): ...@@ -503,12 +511,6 @@ class _MemoryIOMixin(BufferedIOBase):
self._pos = newpos self._pos = newpos
return b return b
def readinto(self, b):
tmp = self.read(len(b))
n = len(tmp)
b[:n] = tmp
return n
def write(self, b): def write(self, b):
n = len(b) n = len(b)
newpos = self._pos + n newpos = self._pos + n
...@@ -536,6 +538,7 @@ class _MemoryIOMixin(BufferedIOBase): ...@@ -536,6 +538,7 @@ class _MemoryIOMixin(BufferedIOBase):
else: else:
self._pos = max(0, pos) self._pos = max(0, pos)
del self._buffer[pos:] del self._buffer[pos:]
return pos
def readable(self): def readable(self):
return True return True
...@@ -652,7 +655,6 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -652,7 +655,6 @@ class BufferedWriter(_BufferedIOMixin):
def write(self, b): def write(self, b):
# XXX we can implement some more tricks to try and avoid partial writes # XXX we can implement some more tricks to try and avoid partial writes
##assert issubclass(type(b), bytes)
if len(self._write_buf) > self.buffer_size: if len(self._write_buf) > self.buffer_size:
# We're full, so let's pre-flush the buffer # We're full, so let's pre-flush the buffer
try: try:
...@@ -672,6 +674,7 @@ class BufferedWriter(_BufferedIOMixin): ...@@ -672,6 +674,7 @@ class BufferedWriter(_BufferedIOMixin):
overage = len(self._write_buf) - self.max_buffer_size overage = len(self._write_buf) - self.max_buffer_size
self._write_buf = self._write_buf[:self.max_buffer_size] self._write_buf = self._write_buf[:self.max_buffer_size]
raise BlockingIOError(e.errno, e.strerror, overage) raise BlockingIOError(e.errno, e.strerror, overage)
return len(b)
def flush(self): def flush(self):
written = 0 written = 0
......
...@@ -79,17 +79,19 @@ class IOTest(unittest.TestCase): ...@@ -79,17 +79,19 @@ class IOTest(unittest.TestCase):
test_support.unlink(test_support.TESTFN) test_support.unlink(test_support.TESTFN)
def write_ops(self, f): def write_ops(self, f):
f.write(b"blah.") self.assertEqual(f.write(b"blah."), 5)
f.seek(0) self.assertEqual(f.seek(0), 0)
f.write(b"Hello.") self.assertEqual(f.write(b"Hello."), 6)
self.assertEqual(f.tell(), 6) self.assertEqual(f.tell(), 6)
f.seek(-1, 1) self.assertEqual(f.seek(-1, 1), 5)
self.assertEqual(f.tell(), 5) self.assertEqual(f.tell(), 5)
f.write(" world\n\n\n") self.assertEqual(f.write(" world\n\n\n"), 9)
f.seek(0) self.assertEqual(f.seek(0), 0)
f.write("h") self.assertEqual(f.write("h"), 1)
f.seek(-2, 2) self.assertEqual(f.seek(-1, 2), 13)
f.truncate() self.assertEqual(f.tell(), 13)
self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12)
LARGE = 2**31 LARGE = 2**31
...@@ -101,10 +103,10 @@ class IOTest(unittest.TestCase): ...@@ -101,10 +103,10 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.write(b"xxx"), 3) self.assertEqual(f.write(b"xxx"), 3)
self.assertEqual(f.tell(), self.LARGE + 3) self.assertEqual(f.tell(), self.LARGE + 3)
self.assertEqual(f.seek(-1, 1), self.LARGE + 2) self.assertEqual(f.seek(-1, 1), self.LARGE + 2)
f.truncate() self.assertEqual(f.truncate(), self.LARGE + 2)
self.assertEqual(f.tell(), self.LARGE + 2) self.assertEqual(f.tell(), self.LARGE + 2)
self.assertEqual(f.seek(0, 2), self.LARGE + 2) self.assertEqual(f.seek(0, 2), self.LARGE + 2)
f.truncate(self.LARGE + 1) self.assertEqual(f.truncate(self.LARGE + 1), self.LARGE + 1)
self.assertEqual(f.tell(), self.LARGE + 1) self.assertEqual(f.tell(), self.LARGE + 1)
self.assertEqual(f.seek(0, 2), self.LARGE + 1) self.assertEqual(f.seek(0, 2), self.LARGE + 1)
self.assertEqual(f.seek(-1, 2), self.LARGE) self.assertEqual(f.seek(-1, 2), self.LARGE)
...@@ -142,6 +144,20 @@ class IOTest(unittest.TestCase): ...@@ -142,6 +144,20 @@ class IOTest(unittest.TestCase):
self.read_ops(f) self.read_ops(f)
f.close() f.close()
def test_buffered_file_io(self):
f = io.open(test_support.TESTFN, "wb")
self.assertEqual(f.readable(), False)
self.assertEqual(f.writable(), True)
self.assertEqual(f.seekable(), True)
self.write_ops(f)
f.close()
f = io.open(test_support.TESTFN, "rb")
self.assertEqual(f.readable(), True)
self.assertEqual(f.writable(), False)
self.assertEqual(f.seekable(), True)
self.read_ops(f)
f.close()
def test_raw_bytes_io(self): def test_raw_bytes_io(self):
f = io.BytesIO() f = io.BytesIO()
self.write_ops(f) self.write_ops(f)
...@@ -163,9 +179,52 @@ class IOTest(unittest.TestCase): ...@@ -163,9 +179,52 @@ class IOTest(unittest.TestCase):
print("Use 'regrtest.py -u largefile test_io' to run it.", print("Use 'regrtest.py -u largefile test_io' to run it.",
file=sys.stderr) file=sys.stderr)
return return
f = io.open(test_support.TESTFN, "w+b", buffering=0) f = io.open(test_support.TESTFN, "w+b", 0)
self.large_file_ops(f) self.large_file_ops(f)
f.close() f.close()
f = io.open(test_support.TESTFN, "w+b")
self.large_file_ops(f)
f.close()
def test_with_open(self):
for bufsize in (0, 1, 100):
f = None
with open(test_support.TESTFN, "wb", bufsize) as f:
f.write("xxx")
self.assertEqual(f.closed, True)
f = None
try:
with open(test_support.TESTFN, "wb", bufsize) as f:
1/0
except ZeroDivisionError:
self.assertEqual(f.closed, True)
else:
self.fail("1/0 didn't raise an exception")
def test_destructor(self):
record = []
class MyFileIO(io.FileIO):
def __del__(self):
record.append(1)
io.FileIO.__del__(self)
def close(self):
record.append(2)
io.FileIO.close(self)
def flush(self):
record.append(3)
io.FileIO.flush(self)
f = MyFileIO(test_support.TESTFN, "w")
f.write("xxx")
del f
self.assertEqual(record, [1, 2, 3])
def test_close_flushes(self):
f = io.open(test_support.TESTFN, "wb")
f.write("xxx")
f.close()
f = io.open(test_support.TESTFN, "rb")
self.assertEqual(f.read(), b"xxx")
f.close()
class MemorySeekTestMixin: class MemorySeekTestMixin:
......
...@@ -530,6 +530,9 @@ fileio_truncate(PyFileIOObject *self, PyObject *args) ...@@ -530,6 +530,9 @@ fileio_truncate(PyFileIOObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "|O", &posobj)) if (!PyArg_ParseTuple(args, "|O", &posobj))
return NULL; return NULL;
if (posobj == Py_None)
posobj = NULL;
if (posobj == NULL) if (posobj == NULL)
whence = 1; whence = 1;
else else
...@@ -545,19 +548,22 @@ fileio_truncate(PyFileIOObject *self, PyObject *args) ...@@ -545,19 +548,22 @@ fileio_truncate(PyFileIOObject *self, PyObject *args)
pos = PyLong_Check(posobj) ? pos = PyLong_Check(posobj) ?
PyLong_AsLongLong(posobj) : PyInt_AsLong(posobj); PyLong_AsLongLong(posobj) : PyInt_AsLong(posobj);
#endif #endif
Py_DECREF(posobj); if (PyErr_Occurred()) {
if (PyErr_Occurred()) Py_DECREF(posobj);
return NULL; return NULL;
}
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
pos = ftruncate(fd, pos); pos = ftruncate(fd, pos);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
if (errno < 0) if (pos < 0) {
Py_DECREF(posobj);
PyErr_SetFromErrno(PyExc_IOError); PyErr_SetFromErrno(PyExc_IOError);
}
Py_RETURN_NONE; return posobj;
} }
static char * static char *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment