diff options
Diffstat (limited to 'src/pkg/io')
-rw-r--r-- | src/pkg/io/Makefile | 3 | ||||
-rw-r--r-- | src/pkg/io/io.go | 25 | ||||
-rw-r--r-- | src/pkg/io/io_test.go | 76 | ||||
-rw-r--r-- | src/pkg/io/ioutil/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/io/ioutil/ioutil.go | 2 | ||||
-rw-r--r-- | src/pkg/io/ioutil/ioutil_test.go | 12 | ||||
-rw-r--r-- | src/pkg/io/ioutil/tempfile_test.go | 2 | ||||
-rw-r--r-- | src/pkg/io/multi.go | 60 | ||||
-rw-r--r-- | src/pkg/io/multi_test.go | 88 | ||||
-rw-r--r-- | src/pkg/io/pipe.go | 27 | ||||
-rw-r--r-- | src/pkg/io/pipe_test.go | 12 |
11 files changed, 278 insertions, 31 deletions
diff --git a/src/pkg/io/Makefile b/src/pkg/io/Makefile index 8c27ce551..9786002f0 100644 --- a/src/pkg/io/Makefile +++ b/src/pkg/io/Makefile @@ -2,11 +2,12 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../Make.$(GOARCH) +include ../../Make.inc TARG=io GOFILES=\ io.go\ + multi.go\ pipe.go\ include ../../Make.pkg diff --git a/src/pkg/io/io.go b/src/pkg/io/io.go index dcdc883b1..1a6eca95a 100644 --- a/src/pkg/io/io.go +++ b/src/pkg/io/io.go @@ -19,6 +19,9 @@ type Error struct { // but failed to return an explicit error. var ErrShortWrite os.Error = &Error{"short write"} +// ErrShortBuffer means that a read required a longer buffer than was provided. +var ErrShortBuffer os.Error = &Error{"short buffer"} + // ErrUnexpectedEOF means that os.EOF was encountered in the // middle of reading a fixed-size block or data structure. var ErrUnexpectedEOF os.Error = &Error{"unexpected EOF"} @@ -165,8 +168,11 @@ func WriteString(w Writer, s string) (n int, err os.Error) { // The error is os.EOF only if no bytes were read. // 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. func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) { - n = 0 + if len(buf) < min { + return 0, ErrShortBuffer + } for n < min { nn, e := r.Read(buf[n:]) if nn > 0 { @@ -179,7 +185,7 @@ func ReadAtLeast(r Reader, buf []byte, min int) (n int, err os.Error) { return n, e } } - return n, nil + return } // ReadFull reads exactly len(buf) bytes from r into buf. @@ -197,10 +203,15 @@ func ReadFull(r Reader, buf []byte) (n int, err os.Error) { // If dst implements the ReaderFrom interface, // the copy is implemented by calling dst.ReadFrom(src). func Copyn(dst Writer, src Reader, n int64) (written int64, err os.Error) { - // If the writer has a ReadFrom method, use it to to do the copy. + // 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 { - return rt.ReadFrom(LimitReader(src, n)) + written, err = rt.ReadFrom(LimitReader(src, n)) + if written < n && err == nil { + // rt stopped early; must have been EOF. + err = os.EOF + } + return } buf := make([]byte, 32*1024) for written < n { @@ -240,12 +251,12 @@ func Copyn(dst Writer, src Reader, n int64) (written int64, err os.Error) { // Otherwise, if src implements the WriterTo interface, // the copy is implemented by calling src.WriteTo(dst). func Copy(dst Writer, src Reader) (written int64, err os.Error) { - // If the writer has a ReadFrom method, use it to to do the copy. + // If the writer has a ReadFrom method, use it to do the copy. // Avoids an allocation and a copy. if rt, ok := dst.(ReaderFrom); ok { return rt.ReadFrom(src) } - // Similarly, if the reader has a WriteTo method, use it to to do the copy. + // Similarly, if the reader has a WriteTo method, use it to do the copy. if wt, ok := src.(WriterTo); ok { return wt.WriteTo(dst) } @@ -336,7 +347,7 @@ func (s *SectionReader) Seek(offset int64, whence int) (ret int64, err os.Error) case 2: offset += s.limit } - if offset < s.off || offset > s.limit { + if offset < s.base || offset > s.limit { return 0, os.EINVAL } s.off = offset diff --git a/src/pkg/io/io_test.go b/src/pkg/io/io_test.go index 4ad1e5951..4fcd85e69 100644 --- a/src/pkg/io/io_test.go +++ b/src/pkg/io/io_test.go @@ -7,6 +7,8 @@ package io_test import ( "bytes" . "io" + "os" + "strings" "testing" ) @@ -78,3 +80,77 @@ func TestCopynWriteTo(t *testing.T) { t.Errorf("Copyn did not work properly") } } + +type noReadFrom struct { + w Writer +} + +func (w *noReadFrom) Write(p []byte) (n int, err os.Error) { + return w.w.Write(p) +} + +func TestCopynEOF(t *testing.T) { + // Test that EOF behavior is the same regardless of whether + // argument to Copyn has ReadFrom. + + b := new(bytes.Buffer) + + n, err := Copyn(&noReadFrom{b}, strings.NewReader("foo"), 3) + if n != 3 || err != nil { + t.Errorf("Copyn(noReadFrom, foo, 3) = %d, %v; want 3, nil", n, err) + } + + n, err = Copyn(&noReadFrom{b}, strings.NewReader("foo"), 4) + if n != 3 || err != os.EOF { + t.Errorf("Copyn(noReadFrom, foo, 4) = %d, %v; want 3, EOF", n, err) + } + + n, err = Copyn(b, strings.NewReader("foo"), 3) // b has read from + if n != 3 || err != nil { + t.Errorf("Copyn(bytes.Buffer, foo, 3) = %d, %v; want 3, nil", n, err) + } + + n, err = Copyn(b, strings.NewReader("foo"), 4) // b has read from + if n != 3 || err != os.EOF { + t.Errorf("Copyn(bytes.Buffer, foo, 4) = %d, %v; want 3, EOF", n, err) + } +} + +func TestReadAtLeast(t *testing.T) { + var rb bytes.Buffer + rb.Write([]byte("0123")) + buf := make([]byte, 2) + n, err := ReadAtLeast(&rb, buf, 2) + if err != nil { + t.Error(err) + } + n, err = ReadAtLeast(&rb, buf, 4) + if err != ErrShortBuffer { + t.Errorf("expected ErrShortBuffer got %v", err) + } + if n != 0 { + t.Errorf("expected to have read 0 bytes, got %v", n) + } + n, err = ReadAtLeast(&rb, buf, 1) + if err != nil { + t.Error(err) + } + if n != 2 { + t.Errorf("expected to have read 2 bytes, got %v", n) + } + n, err = ReadAtLeast(&rb, buf, 2) + if err != os.EOF { + t.Errorf("expected EOF, got %v", err) + } + if n != 0 { + t.Errorf("expected to have read 0 bytes, got %v", n) + } + rb.Write([]byte("4")) + n, err = ReadAtLeast(&rb, buf, 2) + if err != ErrUnexpectedEOF { + t.Errorf("expected ErrUnexpectedEOF, got %v", err) + } + if n != 1 { + t.Errorf("expected to have read 1 bytes, got %v", n) + } +} diff --git a/src/pkg/io/ioutil/Makefile b/src/pkg/io/ioutil/Makefile index 77b75bcec..d406d4b7d 100644 --- a/src/pkg/io/ioutil/Makefile +++ b/src/pkg/io/ioutil/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../../Make.$(GOARCH) +include ../../../Make.inc TARG=io/ioutil GOFILES=\ diff --git a/src/pkg/io/ioutil/ioutil.go b/src/pkg/io/ioutil/ioutil.go index d33869380..fb3fdcda1 100644 --- a/src/pkg/io/ioutil/ioutil.go +++ b/src/pkg/io/ioutil/ioutil.go @@ -49,7 +49,7 @@ func ReadFile(filename string) ([]byte, os.Error) { // WriteFile writes data to a file named by filename. // If the file does not exist, WriteFile creates it with permissions perm; // otherwise WriteFile truncates it before writing. -func WriteFile(filename string, data []byte, perm int) os.Error { +func WriteFile(filename string, data []byte, perm uint32) os.Error { f, err := os.Open(filename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, perm) if err != nil { return err diff --git a/src/pkg/io/ioutil/ioutil_test.go b/src/pkg/io/ioutil/ioutil_test.go index ecbf41ca6..150ee6d63 100644 --- a/src/pkg/io/ioutil/ioutil_test.go +++ b/src/pkg/io/ioutil/ioutil_test.go @@ -37,7 +37,7 @@ func TestReadFile(t *testing.T) { } func TestWriteFile(t *testing.T) { - filename := "_obj/rumpelstilzchen" + filename := "_test/rumpelstilzchen" data := "Programming today is a race between software engineers striving to " + "build bigger and better idiot-proof programs, and the Universe trying " + "to produce bigger and better idiots. So far, the Universe is winning." @@ -74,19 +74,19 @@ func TestReadDir(t *testing.T) { } foundTest := false - foundObj := false + foundTestDir := false for _, dir := range list { switch { case dir.IsRegular() && dir.Name == "ioutil_test.go": foundTest = true - case dir.IsDirectory() && dir.Name == "_obj": - foundObj = true + case dir.IsDirectory() && dir.Name == "_test": + foundTestDir = true } } if !foundTest { t.Fatalf("ReadDir %s: test file not found", dirname) } - if !foundObj { - t.Fatalf("ReadDir %s: _obj directory not found", dirname) + if !foundTestDir { + t.Fatalf("ReadDir %s: _test directory not found", dirname) } } diff --git a/src/pkg/io/ioutil/tempfile_test.go b/src/pkg/io/ioutil/tempfile_test.go index fe43f9566..d949a86cf 100644 --- a/src/pkg/io/ioutil/tempfile_test.go +++ b/src/pkg/io/ioutil/tempfile_test.go @@ -23,7 +23,7 @@ func TestTempFile(t *testing.T) { t.Errorf("TempFile(dir, `ioutil_test`) = %v, %v", f, err) } if f != nil { - re := testing.MustCompile("^" + regexp.QuoteMeta(dir) + "/ioutil_test[0-9]+$") + re := regexp.MustCompile("^" + regexp.QuoteMeta(dir) + "/ioutil_test[0-9]+$") if !re.MatchString(f.Name()) { t.Errorf("TempFile(`"+dir+"`, `ioutil_test`) created bad name %s", f.Name()) } diff --git a/src/pkg/io/multi.go b/src/pkg/io/multi.go new file mode 100644 index 000000000..88e4f1b76 --- /dev/null +++ b/src/pkg/io/multi.go @@ -0,0 +1,60 @@ +// Copyright 2010 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 io + +import "os" + +type multiReader struct { + readers []Reader +} + +func (mr *multiReader) Read(p []byte) (n int, err os.Error) { + for len(mr.readers) > 0 { + n, err = mr.readers[0].Read(p) + if n > 0 || err != os.EOF { + if err == os.EOF { + // This shouldn't happen. + // Well-behaved Readers should never + // return non-zero bytes read with an + // EOF. But if so, we clean it. + err = nil + } + return + } + mr.readers = mr.readers[1:] + } + return 0, os.EOF +} + +// MultiReader returns a Reader that's the logical concatenation of +// the provided input readers. They're read sequentially. Once all +// inputs are drained, Read will return os.EOF. +func MultiReader(readers ...Reader) Reader { + return &multiReader{readers} +} + +type multiWriter struct { + writers []Writer +} + +func (t *multiWriter) Write(p []byte) (n int, err os.Error) { + for _, w := range t.writers { + n, err = w.Write(p) + if err != nil { + return + } + if n != len(p) { + err = ErrShortWrite + return + } + } + return len(p), nil +} + +// MultiWriter creates a writer that duplicates its writes to all the +// provided writers, similar to the Unix tee(1) command. +func MultiWriter(writers ...Writer) Writer { + return &multiWriter{writers} +} diff --git a/src/pkg/io/multi_test.go b/src/pkg/io/multi_test.go new file mode 100644 index 000000000..3ecb7c75d --- /dev/null +++ b/src/pkg/io/multi_test.go @@ -0,0 +1,88 @@ +// Copyright 2010 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 io_test + +import ( + . "io" + "bytes" + "crypto/sha1" + "fmt" + "os" + "strings" + "testing" +) + +func TestMultiReader(t *testing.T) { + var mr Reader + var buf []byte + nread := 0 + withFooBar := func(tests func()) { + r1 := strings.NewReader("foo ") + r2 := strings.NewReader("bar") + mr = MultiReader(r1, r2) + buf = make([]byte, 20) + tests() + } + expectRead := func(size int, expected string, eerr os.Error) { + nread++ + n, gerr := mr.Read(buf[0:size]) + if n != len(expected) { + t.Errorf("#%d, expected %d bytes; got %d", + nread, len(expected), n) + } + got := string(buf[0:n]) + if got != expected { + t.Errorf("#%d, expected %q; got %q", + nread, expected, got) + } + if gerr != eerr { + t.Errorf("#%d, expected error %v; got %v", + nread, eerr, gerr) + } + buf = buf[n:] + } + withFooBar(func() { + expectRead(2, "fo", nil) + expectRead(5, "o ", nil) + expectRead(5, "bar", nil) + expectRead(5, "", os.EOF) + }) + withFooBar(func() { + expectRead(4, "foo ", nil) + expectRead(1, "b", nil) + expectRead(3, "ar", nil) + expectRead(1, "", os.EOF) + }) + withFooBar(func() { + expectRead(5, "foo ", nil) + }) +} + +func TestMultiWriter(t *testing.T) { + sha1 := sha1.New() + sink := new(bytes.Buffer) + mw := MultiWriter(sha1, sink) + + sourceString := "My input text." + source := strings.NewReader(sourceString) + written, err := Copy(mw, source) + + if written != int64(len(sourceString)) { + t.Errorf("short write of %d, not %d", written, len(sourceString)) + } + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + sha1hex := fmt.Sprintf("%x", sha1.Sum()) + if sha1hex != "01cb303fa8c30a64123067c5aa6284ba7ec2d31b" { + t.Error("incorrect sha1 value") + } + + if sink.String() != sourceString { + t.Errorf("expected %q; got %q", sourceString, sink.String()) + } +} diff --git a/src/pkg/io/pipe.go b/src/pkg/io/pipe.go index 898526921..df76418b9 100644 --- a/src/pkg/io/pipe.go +++ b/src/pkg/io/pipe.go @@ -52,6 +52,13 @@ func (p *pipe) run() { case <-p.done: if ndone++; ndone == 2 { // both reader and writer are gone + // close out any existing i/o + if r1 == nil { + p.r2 <- pipeResult{0, os.EINVAL} + } + if w1 == nil { + p.w2 <- pipeResult{0, os.EINVAL} + } return } continue @@ -89,6 +96,11 @@ func (p *pipe) run() { p.r2 <- pipeResult{0, werr} continue } + if rerr != nil { + // read end is closed + p.r2 <- pipeResult{0, os.EINVAL} + continue + } r1 = nil // disable Read until this one is done case wb = <-w1: if rerr != nil { @@ -96,6 +108,11 @@ func (p *pipe) run() { p.w2 <- pipeResult{0, rerr} continue } + if werr != nil { + // write end is closed + p.w2 <- pipeResult{0, os.EINVAL} + continue + } w1 = nil // disable Write until this one is done } @@ -275,20 +292,14 @@ func Pipe() (*PipeReader, *PipeWriter) { r.c2 = p.r2 r.cclose = p.rclose r.done = p.done - // TODO(rsc): Should be able to write - // runtime.SetFinalizer(r, (*PipeReader).finalizer) - // but 6g doesn't see the finalizer method. - runtime.SetFinalizer(&r.pipeHalf, (*pipeHalf).finalizer) + runtime.SetFinalizer(r, (*PipeReader).finalizer) w := new(PipeWriter) w.c1 = p.w1 w.c2 = p.w2 w.cclose = p.wclose w.done = p.done - // TODO(rsc): Should be able to write - // runtime.SetFinalizer(w, (*PipeWriter).finalizer) - // but 6g doesn't see the finalizer method. - runtime.SetFinalizer(&w.pipeHalf, (*pipeHalf).finalizer) + runtime.SetFinalizer(w, (*PipeWriter).finalizer) return r, w } diff --git a/src/pkg/io/pipe_test.go b/src/pkg/io/pipe_test.go index 902d7a0a3..bd4b94f0a 100644 --- a/src/pkg/io/pipe_test.go +++ b/src/pkg/io/pipe_test.go @@ -157,12 +157,12 @@ func (p pipeTest) String() string { } var pipeTests = []pipeTest{ - pipeTest{true, nil, false}, - pipeTest{true, nil, true}, - pipeTest{true, ErrShortWrite, true}, - pipeTest{false, nil, false}, - pipeTest{false, nil, true}, - pipeTest{false, ErrShortWrite, true}, + {true, nil, false}, + {true, nil, true}, + {true, ErrShortWrite, true}, + {false, nil, false}, + {false, nil, true}, + {false, ErrShortWrite, true}, } func delayClose(t *testing.T, cl closer, ch chan int, tt pipeTest) { |