diff options
Diffstat (limited to 'src/pkg/io')
-rw-r--r-- | src/pkg/io/io.go | 81 | ||||
-rw-r--r-- | src/pkg/io/io_test.go | 76 | ||||
-rw-r--r-- | src/pkg/io/ioutil/blackhole.go | 23 | ||||
-rw-r--r-- | src/pkg/io/ioutil/ioutil.go | 6 | ||||
-rw-r--r-- | src/pkg/io/ioutil/ioutil_test.go | 3 | ||||
-rw-r--r-- | src/pkg/io/ioutil/tempfile.go | 10 | ||||
-rw-r--r-- | src/pkg/io/ioutil/tempfile_test.go | 3 |
7 files changed, 141 insertions, 61 deletions
diff --git a/src/pkg/io/io.go b/src/pkg/io/io.go index 54bf159eb..23d05e575 100644 --- a/src/pkg/io/io.go +++ b/src/pkg/io/io.go @@ -130,11 +130,23 @@ type ReadWriteSeeker interface { } // ReaderFrom is the interface that wraps the ReadFrom method. +// +// ReadFrom reads data from r until EOF or error. +// The return value n is the number of bytes read. +// Any error except io.EOF encountered during the read is also returned. +// +// The Copy function uses ReaderFrom if available. type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) } // WriterTo is the interface that wraps the WriteTo method. +// +// WriteTo writes data to w until there's no more data to write or +// when an error occurs. The return value n is the number of bytes +// written. Any error encountered during the write is also returned. +// +// The Copy function uses WriterTo if available. type WriterTo interface { WriteTo(w Writer) (n int64, err error) } @@ -204,6 +216,11 @@ type ByteScanner interface { UnreadByte() error } +// ByteWriter is the interface that wraps the WriteByte method. +type ByteWriter interface { + WriteByte(c byte) error +} + // RuneReader is the interface that wraps the ReadRune method. // // ReadRune reads a single UTF-8 encoded Unicode character @@ -245,6 +262,7 @@ func WriteString(w Writer, s string) (n int, err error) { // If an EOF happens after reading fewer than min bytes, // ReadAtLeast returns ErrUnexpectedEOF. // If min is greater than the length of buf, ReadAtLeast returns ErrShortBuffer. +// On return, n >= min if and only if err == nil. func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) { if len(buf) < min { return 0, ErrShortBuffer @@ -254,12 +272,10 @@ func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) { nn, err = r.Read(buf[n:]) n += nn } - if err == EOF { - if n >= min { - err = nil - } else if n > 0 { - err = ErrUnexpectedEOF - } + if n >= min { + err = nil + } else if n > 0 && err == EOF { + err = ErrUnexpectedEOF } return } @@ -269,56 +285,28 @@ func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) { // The error is EOF only if no bytes were read. // If an EOF happens after reading some but not all the bytes, // ReadFull returns ErrUnexpectedEOF. +// On return, n == len(buf) if and only if err == nil. func ReadFull(r Reader, buf []byte) (n int, err error) { return ReadAtLeast(r, buf, len(buf)) } // CopyN copies n bytes (or until an error) from src to dst. // It returns the number of bytes copied and the earliest -// error encountered while copying. Because Read can -// return the full amount requested as well as an error -// (including EOF), so can CopyN. +// error encountered while copying. +// On return, written == n if and only if err == nil. // // If dst implements the ReaderFrom interface, // the copy is implemented using it. func CopyN(dst Writer, src Reader, n int64) (written int64, err error) { - // If the writer has a ReadFrom method, use it to do the copy. - // Avoids a buffer allocation and a copy. - if rt, ok := dst.(ReaderFrom); ok { - written, err = rt.ReadFrom(LimitReader(src, n)) - if written < n && err == nil { - // rt stopped early; must have been EOF. - err = EOF - } - return + written, err = Copy(dst, LimitReader(src, n)) + if written == n { + return n, nil } - buf := make([]byte, 32*1024) - for written < n { - l := len(buf) - if d := n - written; d < int64(l) { - l = int(d) - } - nr, er := src.Read(buf[0:l]) - if nr > 0 { - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = ErrShortWrite - break - } - } - if er != nil { - err = er - break - } + if written < n && err == nil { + // src stopped early; must have been EOF. + err = EOF } - return written, err + return } // Copy copies from src to dst until either EOF is reached @@ -451,6 +439,11 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { off += s.base if max := s.limit - off; int64(len(p)) > max { p = p[0:max] + n, err = s.r.ReadAt(p, off) + if err == nil { + err = EOF + } + return n, err } return s.r.ReadAt(p, off) } diff --git a/src/pkg/io/io_test.go b/src/pkg/io/io_test.go index 1e671b59b..1bc451e44 100644 --- a/src/pkg/io/io_test.go +++ b/src/pkg/io/io_test.go @@ -6,6 +6,8 @@ package io_test import ( "bytes" + "errors" + "fmt" . "io" "strings" "testing" @@ -88,6 +90,12 @@ func (w *noReadFrom) Write(p []byte) (n int, err error) { return w.w.Write(p) } +type wantedAndErrReader struct{} + +func (wantedAndErrReader) Read(p []byte) (int, error) { + return len(p), errors.New("wantedAndErrReader error") +} + func TestCopyNEOF(t *testing.T) { // Test that EOF behavior is the same regardless of whether // argument to CopyN has ReadFrom. @@ -113,6 +121,16 @@ func TestCopyNEOF(t *testing.T) { if n != 3 || err != EOF { t.Errorf("CopyN(bytes.Buffer, foo, 4) = %d, %v; want 3, EOF", n, err) } + + n, err = CopyN(b, wantedAndErrReader{}, 5) + if n != 5 || err != nil { + t.Errorf("CopyN(bytes.Buffer, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) + } + + n, err = CopyN(&noReadFrom{b}, wantedAndErrReader{}, 5) + if n != 5 || err != nil { + t.Errorf("CopyN(noReadFrom, wantedAndErrReader, 5) = %d, %v; want 5, nil", n, err) + } } func TestReadAtLeast(t *testing.T) { @@ -120,22 +138,30 @@ func TestReadAtLeast(t *testing.T) { testReadAtLeast(t, &rb) } -// A version of bytes.Buffer that returns n > 0, EOF on Read +// A version of bytes.Buffer that returns n > 0, err on Read // when the input is exhausted. -type dataAndEOFBuffer struct { +type dataAndErrorBuffer struct { + err error bytes.Buffer } -func (r *dataAndEOFBuffer) Read(p []byte) (n int, err error) { +func (r *dataAndErrorBuffer) Read(p []byte) (n int, err error) { n, err = r.Buffer.Read(p) if n > 0 && r.Buffer.Len() == 0 && err == nil { - err = EOF + err = r.err } return } func TestReadAtLeastWithDataAndEOF(t *testing.T) { - var rb dataAndEOFBuffer + var rb dataAndErrorBuffer + rb.err = EOF + testReadAtLeast(t, &rb) +} + +func TestReadAtLeastWithDataAndError(t *testing.T) { + var rb dataAndErrorBuffer + rb.err = fmt.Errorf("fake error") testReadAtLeast(t, &rb) } @@ -169,8 +195,12 @@ func testReadAtLeast(t *testing.T, rb ReadWriter) { } rb.Write([]byte("4")) n, err = ReadAtLeast(rb, buf, 2) - if err != ErrUnexpectedEOF { - t.Errorf("expected ErrUnexpectedEOF, got %v", err) + want := ErrUnexpectedEOF + if rb, ok := rb.(*dataAndErrorBuffer); ok && rb.err != EOF { + want = rb.err + } + if err != want { + t.Errorf("expected %v, got %v", want, err) } if n != 1 { t.Errorf("expected to have read 1 bytes, got %v", n) @@ -203,3 +233,35 @@ func TestTeeReader(t *testing.T) { t.Errorf("closed tee: ReadFull(r, dst) = %d, %v; want 0, EPIPE", n, err) } } + +func TestSectionReader_ReadAt(tst *testing.T) { + dat := "a long sample data, 1234567890" + tests := []struct { + data string + off int + n int + bufLen int + at int + exp string + err error + }{ + {data: "", off: 0, n: 10, bufLen: 2, at: 0, exp: "", err: EOF}, + {data: dat, off: 0, n: len(dat), bufLen: 0, at: 0, exp: "", err: nil}, + {data: dat, off: len(dat), n: 1, bufLen: 1, at: 0, exp: "", err: EOF}, + {data: dat, off: 0, n: len(dat) + 2, bufLen: len(dat), at: 0, exp: dat, err: nil}, + {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 0, exp: dat[:len(dat)/2], err: nil}, + {data: dat, off: 0, n: len(dat), bufLen: len(dat), at: 0, exp: dat, err: nil}, + {data: dat, off: 0, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[2 : 2+len(dat)/2], err: nil}, + {data: dat, off: 3, n: len(dat), bufLen: len(dat) / 2, at: 2, exp: dat[5 : 5+len(dat)/2], err: nil}, + {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 - 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: nil}, + {data: dat, off: 3, n: len(dat) / 2, bufLen: len(dat)/2 + 2, at: 2, exp: dat[5 : 5+len(dat)/2-2], err: EOF}, + } + for i, t := range tests { + r := strings.NewReader(t.data) + s := NewSectionReader(r, int64(t.off), int64(t.n)) + buf := make([]byte, t.bufLen) + if n, err := s.ReadAt(buf, int64(t.at)); n != len(t.exp) || string(buf[:n]) != t.exp || err != t.err { + tst.Fatalf("%d: ReadAt(%d) = %q, %v; expected %q, %v", i, t.at, buf[:n], err, t.exp, t.err) + } + } +} diff --git a/src/pkg/io/ioutil/blackhole.go b/src/pkg/io/ioutil/blackhole.go new file mode 100644 index 000000000..101d2c121 --- /dev/null +++ b/src/pkg/io/ioutil/blackhole.go @@ -0,0 +1,23 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ioutil + +var blackHoleBuf = make(chan []byte, 1) + +func blackHole() []byte { + select { + case b := <-blackHoleBuf: + return b + default: + } + return make([]byte, 8192) +} + +func blackHolePut(p []byte) { + select { + case blackHoleBuf <- p: + default: + } +} diff --git a/src/pkg/io/ioutil/ioutil.go b/src/pkg/io/ioutil/ioutil.go index f072b8c75..0eb146c0a 100644 --- a/src/pkg/io/ioutil/ioutil.go +++ b/src/pkg/io/ioutil/ioutil.go @@ -130,12 +130,12 @@ func (devNull) Write(p []byte) (int, error) { return len(p), nil } -var blackHole = make([]byte, 8192) - func (devNull) ReadFrom(r io.Reader) (n int64, err error) { + buf := blackHole() + defer blackHolePut(buf) readSize := 0 for { - readSize, err = r.Read(blackHole) + readSize, err = r.Read(buf) n += int64(readSize) if err != nil { if err == io.EOF { diff --git a/src/pkg/io/ioutil/ioutil_test.go b/src/pkg/io/ioutil/ioutil_test.go index d9c43bead..c297847b4 100644 --- a/src/pkg/io/ioutil/ioutil_test.go +++ b/src/pkg/io/ioutil/ioutil_test.go @@ -2,10 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ioutil_test +package ioutil import ( - . "io/ioutil" "os" "testing" ) diff --git a/src/pkg/io/ioutil/tempfile.go b/src/pkg/io/ioutil/tempfile.go index 42d2e6758..4a06e9756 100644 --- a/src/pkg/io/ioutil/tempfile.go +++ b/src/pkg/io/ioutil/tempfile.go @@ -8,26 +8,30 @@ import ( "os" "path/filepath" "strconv" + "sync" "time" ) -// Random number state, accessed without lock; racy but harmless. +// Random number state. // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. var rand uint32 +var randmu sync.Mutex func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { + randmu.Lock() r := rand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rand = r + randmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } @@ -38,8 +42,8 @@ func nextSuffix() string { // for temporary files (see os.TempDir). // Multiple programs calling TempFile simultaneously // will not choose the same file. The caller can use f.Name() -// to find the name of the file. It is the caller's responsibility to -// remove the file when no longer needed. +// to find the pathname of the file. It is the caller's responsibility +// to remove the file when no longer needed. func TempFile(dir, prefix string) (f *os.File, err error) { if dir == "" { dir = os.TempDir() diff --git a/src/pkg/io/ioutil/tempfile_test.go b/src/pkg/io/ioutil/tempfile_test.go index 80c62f672..d2a132a11 100644 --- a/src/pkg/io/ioutil/tempfile_test.go +++ b/src/pkg/io/ioutil/tempfile_test.go @@ -2,10 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ioutil_test +package ioutil import ( - . "io/ioutil" "os" "path/filepath" "regexp" |