Merged revisions 77895-77896 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/branches/py3k

................
  r77895 | antoine.pitrou | 2010-01-31 23:47:27 +0100 (dim., 31 janv. 2010) | 12 lines

  Merged revisions 77890 via svnmerge from
  svn+ssh://pythondev@svn.python.org/python/trunk

  ........
    r77890 | antoine.pitrou | 2010-01-31 23:26:04 +0100 (dim., 31 janv. 2010) | 7 lines

    - Issue #6939: Fix file I/O objects in the `io` module to keep the original
      file position when calling `truncate()`.  It would previously change the
      file position to the given argument, which goes against the tradition of
      ftruncate() and other truncation APIs.  Patch by Pascal Chambon.
  ........
................
  r77896 | antoine.pitrou | 2010-02-01 00:12:29 +0100 (lun., 01 févr. 2010) | 3 lines

  r77895 broke doctest.
................
This commit is contained in:
Antoine Pitrou 2010-01-31 23:20:26 +00:00
parent 1665a8d931
commit 66f9fea782
13 changed files with 99 additions and 48 deletions

View File

@ -851,7 +851,7 @@ class BytesIO(BufferedIOBase):
elif pos < 0: elif pos < 0:
raise ValueError("negative truncate position %r" % (pos,)) raise ValueError("negative truncate position %r" % (pos,))
del self._buffer[pos:] del self._buffer[pos:]
return self.seek(pos) return pos
def readable(self): def readable(self):
return True return True
@ -1210,8 +1210,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
if pos is None: if pos is None:
pos = self.tell() pos = self.tell()
# Use seek to flush the read buffer. # Use seek to flush the read buffer.
self.seek(pos) return BufferedWriter.truncate(self, pos)
return BufferedWriter.truncate(self)
def read(self, n=None): def read(self, n=None):
if n is None: if n is None:
@ -1712,8 +1711,7 @@ class TextIOWrapper(TextIOBase):
self.flush() self.flush()
if pos is None: if pos is None:
pos = self.tell() pos = self.tell()
self.seek(pos) return self.buffer.truncate(pos)
return self.buffer.truncate()
def detach(self): def detach(self):
if self.buffer is None: if self.buffer is None:

View File

@ -247,7 +247,8 @@ class _SpoofOut(StringIO):
return result return result
def truncate(self, size=None): def truncate(self, size=None):
StringIO.truncate(self, size) self.seek(size)
StringIO.truncate(self)
# Worst-case linear-time ellipsis matching. # Worst-case linear-time ellipsis matching.
def _ellipsis_match(want, got): def _ellipsis_match(want, got):

View File

@ -330,6 +330,17 @@ class OtherFileTests(unittest.TestCase):
f.close() f.close()
self.fail("no error for invalid mode: %s" % bad_mode) self.fail("no error for invalid mode: %s" % bad_mode)
def testTruncate(self):
f = _FileIO(TESTFN, 'w')
f.write(bytes(bytearray(range(10))))
self.assertEqual(f.tell(), 10)
f.truncate(5)
self.assertEqual(f.tell(), 10)
self.assertEqual(f.seek(0, os.SEEK_END), 5)
f.truncate(15)
self.assertEqual(f.tell(), 5)
self.assertEqual(f.seek(0, os.SEEK_END), 15)
def testTruncateOnWindows(self): def testTruncateOnWindows(self):
def bug801631(): def bug801631():
# SF bug <http://www.python.org/sf/801631> # SF bug <http://www.python.org/sf/801631>

View File

@ -228,6 +228,11 @@ class IOTest(unittest.TestCase):
support.unlink(support.TESTFN) support.unlink(support.TESTFN)
def write_ops(self, f): def write_ops(self, f):
self.assertEqual(f.write(b"blah."), 5)
f.truncate(0)
self.assertEqual(f.tell(), 5)
f.seek(0)
self.assertEqual(f.write(b"blah."), 5) self.assertEqual(f.write(b"blah."), 5)
self.assertEqual(f.seek(0), 0) self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(b"Hello."), 6) self.assertEqual(f.write(b"Hello."), 6)
@ -239,8 +244,9 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.write(b"h"), 1) self.assertEqual(f.write(b"h"), 1)
self.assertEqual(f.seek(-1, 2), 13) self.assertEqual(f.seek(-1, 2), 13)
self.assertEqual(f.tell(), 13) self.assertEqual(f.tell(), 13)
self.assertEqual(f.truncate(12), 12) self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12) self.assertEqual(f.tell(), 13)
self.assertRaises(TypeError, f.seek, 0.0) self.assertRaises(TypeError, f.seek, 0.0)
def read_ops(self, f, buffered=False): def read_ops(self, f, buffered=False):
@ -285,7 +291,7 @@ class IOTest(unittest.TestCase):
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)
self.assertEqual(f.truncate(self.LARGE + 1), 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 + 2)
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)
self.assertEqual(f.read(2), b"x") self.assertEqual(f.read(2), b"x")
@ -980,7 +986,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
bufio = self.tp(raw, 8) bufio = self.tp(raw, 8)
bufio.write(b"abcdef") bufio.write(b"abcdef")
self.assertEqual(bufio.truncate(3), 3) self.assertEqual(bufio.truncate(3), 3)
self.assertEqual(bufio.tell(), 3) self.assertEqual(bufio.tell(), 6)
with self.open(support.TESTFN, "rb", buffering=0) as f: with self.open(support.TESTFN, "rb", buffering=0) as f:
self.assertEqual(f.read(), b"abc") self.assertEqual(f.read(), b"abc")
@ -1366,6 +1372,14 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
self.assertEqual(s, self.assertEqual(s,
b"A" + b"B" * overwrite_size + b"A" * (9 - overwrite_size)) b"A" + b"B" * overwrite_size + b"A" * (9 - overwrite_size))
def test_truncate_after_read_or_write(self):
raw = self.BytesIO(b"A" * 10)
bufio = self.tp(raw, 100)
self.assertEqual(bufio.read(2), b"AA") # the read buffer gets filled
self.assertEqual(bufio.truncate(), 2)
self.assertEqual(bufio.write(b"BB"), 2) # the write buffer increases
self.assertEqual(bufio.truncate(), 4)
def test_misbehaved_io(self): def test_misbehaved_io(self):
BufferedReaderTest.test_misbehaved_io(self) BufferedReaderTest.test_misbehaved_io(self)
BufferedWriterTest.test_misbehaved_io(self) BufferedWriterTest.test_misbehaved_io(self)

View File

@ -122,14 +122,14 @@ class LargeFileTest(unittest.TestCase):
newsize -= 1 newsize -= 1
f.seek(42) f.seek(42)
f.truncate(newsize) f.truncate(newsize)
self.assertEqual(f.tell(), newsize) # else wasn't truncated self.assertEqual(f.tell(), 42)
f.seek(0, 2) f.seek(0, 2)
self.assertEqual(f.tell(), newsize) self.assertEqual(f.tell(), newsize)
# XXX truncate(larger than true size) is ill-defined # XXX truncate(larger than true size) is ill-defined
# across platform; cut it waaaaay back # across platform; cut it waaaaay back
f.seek(0) f.seek(0)
f.truncate(1) f.truncate(1)
self.assertEqual(f.tell(), 1) # else pointer moved self.assertEqual(f.tell(), 0) # else pointer moved
f.seek(0) f.seek(0)
self.assertEqual(len(f.read()), 1) # else wasn't truncated self.assertEqual(len(f.read()), 1) # else wasn't truncated

View File

@ -72,7 +72,7 @@ class MemoryTestMixin:
self.assertEqual(f.seek(0), 0) self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(t("h")), 1) self.assertEqual(f.write(t("h")), 1)
self.assertEqual(f.truncate(12), 12) self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12) self.assertEqual(f.tell(), 1)
def test_write(self): def test_write(self):
buf = self.buftype("hello world\n") buf = self.buftype("hello world\n")
@ -120,7 +120,8 @@ class MemoryTestMixin:
self.assertEqual(memio.getvalue(), buf[:6]) self.assertEqual(memio.getvalue(), buf[:6])
self.assertEqual(memio.truncate(4), 4) self.assertEqual(memio.truncate(4), 4)
self.assertEqual(memio.getvalue(), buf[:4]) self.assertEqual(memio.getvalue(), buf[:4])
self.assertEqual(memio.tell(), 4) self.assertEqual(memio.tell(), 6)
memio.seek(0, 2)
memio.write(buf) memio.write(buf)
self.assertEqual(memio.getvalue(), buf[:4] + buf) self.assertEqual(memio.getvalue(), buf[:4] + buf)
pos = memio.tell() pos = memio.tell()

View File

@ -118,6 +118,7 @@ Terry Carroll
Donn Cave Donn Cave
Per Cederqvist Per Cederqvist
Octavian Cerna Octavian Cerna
Pascal Chambon
Hye-Shik Chang Hye-Shik Chang
Jeffrey Chang Jeffrey Chang
Mitch Chapman Mitch Chapman

View File

@ -76,6 +76,11 @@ Core and Builtins
Library Library
------- -------
- Issue #6939: Fix file I/O objects in the `io` module to keep the original
file position when calling `truncate()`. It would previously change the
file position to the given argument, which goes against the tradition of
ftruncate() and other truncation APIs. Patch by Pascal Chambon.
- Issue #7792: Registering non-classes to ABCs raised an obscure error. - Issue #7792: Registering non-classes to ABCs raised an obscure error.
- Issue #7785: Don't accept bytes in FileIO.write(). - Issue #7785: Don't accept bytes in FileIO.write().

View File

@ -412,7 +412,7 @@ PyDoc_STRVAR(truncate_doc,
"truncate([size]) -> int. Truncate the file to at most size bytes.\n" "truncate([size]) -> int. Truncate the file to at most size bytes.\n"
"\n" "\n"
"Size defaults to the current file position, as returned by tell().\n" "Size defaults to the current file position, as returned by tell().\n"
"Returns the new size. Imply an absolute seek to the position size."); "The current file position is unchanged. Returns the new size.\n");
static PyObject * static PyObject *
bytesio_truncate(bytesio *self, PyObject *args) bytesio_truncate(bytesio *self, PyObject *args)
@ -451,7 +451,6 @@ bytesio_truncate(bytesio *self, PyObject *args)
if (resize_buffer(self, size) < 0) if (resize_buffer(self, size) < 0)
return NULL; return NULL;
} }
self->pos = size;
return PyLong_FromSsize_t(size); return PyLong_FromSsize_t(size);
} }

View File

@ -761,8 +761,10 @@ fileio_tell(fileio *self, PyObject *args)
static PyObject * static PyObject *
fileio_truncate(fileio *self, PyObject *args) fileio_truncate(fileio *self, PyObject *args)
{ {
PyObject *posobj = NULL; PyObject *posobj = NULL; /* the new size wanted by the user */
#ifndef MS_WINDOWS
Py_off_t pos; Py_off_t pos;
#endif
int ret; int ret;
int fd; int fd;
@ -782,53 +784,81 @@ fileio_truncate(fileio *self, PyObject *args)
return NULL; return NULL;
} }
else { else {
/* Move to the position to be truncated. */ Py_INCREF(posobj);
posobj = portable_lseek(fd, posobj, 0);
} }
if (posobj == NULL)
return NULL;
#if defined(HAVE_LARGEFILE_SUPPORT)
pos = PyLong_AsLongLong(posobj);
#else
pos = PyLong_AsLong(posobj);
#endif
if (pos == -1 && PyErr_Occurred())
return NULL;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
/* MS _chsize doesn't work if newsize doesn't fit in 32 bits, /* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
so don't even try using it. */ so don't even try using it. */
{ {
PyObject *oldposobj, *tempposobj;
HANDLE hFile; HANDLE hFile;
/* we save the file pointer position */
oldposobj = portable_lseek(fd, NULL, 1);
if (oldposobj == NULL) {
Py_DECREF(posobj);
return NULL;
}
/* we then move to the truncation position */
tempposobj = portable_lseek(fd, posobj, 0);
if (tempposobj == NULL) {
Py_DECREF(oldposobj);
Py_DECREF(posobj);
return NULL;
}
Py_DECREF(tempposobj);
/* Truncate. Note that this may grow the file! */ /* Truncate. Note that this may grow the file! */
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
hFile = (HANDLE)_get_osfhandle(fd); hFile = (HANDLE)_get_osfhandle(fd);
ret = hFile == (HANDLE)-1; ret = hFile == (HANDLE)-1; /* testing for INVALID_HANDLE value */
if (ret == 0) { if (ret == 0) {
ret = SetEndOfFile(hFile) == 0; ret = SetEndOfFile(hFile) == 0;
if (ret) if (ret)
errno = EACCES; errno = EACCES;
} }
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
/* we restore the file pointer position in any case */
tempposobj = portable_lseek(fd, oldposobj, 0);
Py_DECREF(oldposobj);
if (tempposobj == NULL) {
Py_DECREF(posobj);
return NULL;
}
Py_DECREF(tempposobj);
} }
#else #else
#if defined(HAVE_LARGEFILE_SUPPORT)
pos = PyLong_AsLongLong(posobj);
#else
pos = PyLong_AsLong(posobj);
#endif
if (PyErr_Occurred()){
Py_DECREF(posobj);
return NULL;
}
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
ret = ftruncate(fd, pos); ret = ftruncate(fd, pos);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
#endif /* !MS_WINDOWS */ #endif /* !MS_WINDOWS */
if (ret != 0) { if (ret != 0) {
Py_DECREF(posobj);
PyErr_SetFromErrno(PyExc_IOError); PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
return posobj; return posobj;
} }
#endif #endif /* HAVE_FTRUNCATE */
static char * static char *
mode_string(fileio *self) mode_string(fileio *self)

View File

@ -102,8 +102,8 @@ iobase_tell(PyObject *self, PyObject *args)
PyDoc_STRVAR(iobase_truncate_doc, PyDoc_STRVAR(iobase_truncate_doc,
"Truncate file to size bytes.\n" "Truncate file to size bytes.\n"
"\n" "\n"
"Size defaults to the current IO position as reported by tell(). Return\n" "File pointer is left unchanged. Size defaults to the current IO\n"
"the new size."); "position as reported by tell(). Returns the new size.");
static PyObject * static PyObject *
iobase_truncate(PyObject *self, PyObject *args) iobase_truncate(PyObject *self, PyObject *args)

View File

@ -350,7 +350,7 @@ PyDoc_STRVAR(stringio_truncate_doc,
"Truncate size to pos.\n" "Truncate size to pos.\n"
"\n" "\n"
"The pos argument defaults to the current file position, as\n" "The pos argument defaults to the current file position, as\n"
"returned by tell(). Imply an absolute seek to pos.\n" "returned by tell(). The current file position is unchanged.\n"
"Returns the new absolute position.\n"); "Returns the new absolute position.\n");
static PyObject * static PyObject *
@ -390,7 +390,6 @@ stringio_truncate(stringio *self, PyObject *args)
return NULL; return NULL;
self->string_size = size; self->string_size = size;
} }
self->pos = size;
return PyLong_FromSsize_t(size); return PyLong_FromSsize_t(size);
} }

View File

@ -2318,15 +2318,7 @@ textiowrapper_truncate(textio *self, PyObject *args)
return NULL; return NULL;
Py_DECREF(res); Py_DECREF(res);
if (pos != Py_None) { return PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_truncate, pos, NULL);
res = PyObject_CallMethodObjArgs((PyObject *) self,
_PyIO_str_seek, pos, NULL);
if (res == NULL)
return NULL;
Py_DECREF(res);
}
return PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_truncate, NULL);
} }
static PyObject * static PyObject *