From f93f662d7ecdb869cb4a3e730d4e02f7539cc8c9 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Thu, 28 Apr 2016 12:42:05 -0700 Subject: [PATCH] Bring mwc to 100% coverage. --- mwc/mwc_test.go | 49 +++++++++++++++++----- testio/testio.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 10 deletions(-) diff --git a/mwc/mwc_test.go b/mwc/mwc_test.go index c9af36c..bc96910 100644 --- a/mwc/mwc_test.go +++ b/mwc/mwc_test.go @@ -4,7 +4,8 @@ import ( "bytes" "testing" - "github.com/kisom/testio" + "github.com/kisom/goutils/testio" + "github.com/kisom/goutils/assert" ) func TestMWC(t *testing.T) { @@ -13,15 +14,43 @@ func TestMWC(t *testing.T) { mwc := MultiWriteCloser(buf1, buf2) - if _, err := mwc.Write([]byte("hello, world")); err != nil { - t.Fatalf("%v", err) - } + _, err := mwc.Write([]byte("hello, world")) + assert.NoErrorT(t, err) - if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) { - t.Fatal("write failed") - } + assert.BoolT(t, bytes.Equal(buf1.Bytes(), buf2.Bytes()), "write failed") + assert.BoolT(t, bytes.Equal(buf1.Bytes(), []byte("hello, world")), "write failed") - if !bytes.Equal(buf1.Bytes(), []byte("hello, world")) { - t.Fatal("writing failed") - } + err = mwc.Close() + assert.NoErrorT(t, err) +} + +func TestMWCShort(t *testing.T) { + buf1 := testio.NewBufCloser(nil) + buf2 := testio.NewBufCloser(nil) + buf3 := testio.NewBrokenWriter(5) + buf4 := testio.NewSilentBrokenWriter(5) + + mwc := MultiWriteCloser(buf1, buf2, buf3) + defer mwc.Close() + + _, err := mwc.Write([]byte("hello, world")) + assert.ErrorT(t, err, "expected a short write error", "but no error occurred") + mwc.Close() + + mwc = MultiWriteCloser(buf1, buf2, buf4) + _, err = mwc.Write([]byte("hello, world")) + assert.ErrorT(t, err, "expected a short write error", "but no error occurred") +} + +func TestMWCClose(t *testing.T) { + buf1 := testio.NewBufCloser(nil) + buf2 := testio.NewBufCloser(nil) + buf3 := testio.NewBrokenCloser(nil) + + mwc := MultiWriteCloser(buf1, buf2, buf3) + _, err := mwc.Write([]byte("hello, world")) + assert.NoErrorT(t, err) + + err = mwc.Close() + assert.ErrorT(t, err, "expected broken closer to fail") } diff --git a/testio/testio.go b/testio/testio.go index ce90562..9b5dd59 100644 --- a/testio/testio.go +++ b/testio/testio.go @@ -55,6 +55,59 @@ func (w *BrokenWriter) Reset() { w.current = 0 } +// Close is provided to satisfy the Closer interface. +func (w *BrokenWriter) Close() error { + w.Reset() + return nil +} + +// SilentBrokenWriter implements an io.Writer that fails after a +// certain number of bytes. However, this failure is silent: it just +// reports fewer bytes written than p. It doesn't actually store any +// data, and is used to verify that io.Writer implementations properly +// return errors on short writes. +type SilentBrokenWriter struct { + current, limit int +} + +// NewSilentBrokenWriter creates a new SilentBrokenWriter that can store only +// limit bytes. +func NewSilentBrokenWriter(limit int) *SilentBrokenWriter { + return &SilentBrokenWriter{limit: limit} +} + +// Write will write the byte slice to the SilentBrokenWriter, failing if the +// maximum number of bytes has been reached. +func (w *SilentBrokenWriter) Write(p []byte) (int, error) { + if (len(p) + w.current) <= w.limit { + w.current += len(p) + } else { + spill := (len(p) + w.current) - w.limit + w.current = w.limit + return len(p) - spill, nil + } + + return len(p), nil +} + +// Extend increases the byte limit to allow more data to be written. +func (w *SilentBrokenWriter) Extend(n int) { + w.limit += n +} + +// Reset clears the limit and bytes in the SilentBrokenWriter. Extend needs +// to be called to allow data to be written. +func (w *SilentBrokenWriter) Reset() { + w.limit = 0 + w.current = 0 +} + +// Close is provided to satisfy the Closer interface. +func (w *SilentBrokenWriter) Close() error { + w.Reset() + return nil +} + // BrokenReadWriter implements a broken reader and writer, backed by a // bytes.Buffer. type BrokenReadWriter struct { @@ -259,3 +312,53 @@ func (bc *BufferConn) ReadClient(p []byte) (int, error) { func (bc *BufferConn) Close() error { return nil } + +// BrokenCloser is a BufCloser that fails to close. +type BrokenCloser struct { + buf *bytes.Buffer +} + +// Write writes the data to the BrokenCloser. +func (buf *BrokenCloser) Write(p []byte) (int, error) { + return buf.buf.Write(p) +} + +// Read reads data from the BrokenCloser. +func (buf *BrokenCloser) Read(p []byte) (int, error) { + return buf.buf.Read(p) +} + +// Close is a stub function to satisfy the io.Closer interface. +func (buf *BrokenCloser) Close() error { + return errors.New("testio: broken closer is broken") +} + +// Reset clears the internal buffer. +func (buf *BrokenCloser) Reset() { + buf.buf.Reset() +} + +// Bytes returns the contents of the buffer as a byte slice. +func (buf *BrokenCloser) Bytes() []byte { + return buf.buf.Bytes() +} + +// NewBrokenCloser creates and initializes a new BrokenCloser using buf as +// its initial contents. It is intended to prepare a BrokenCloser to read +// existing data. It can also be used to size the internal buffer for +// writing. To do that, buf should have the desired capacity but a +// length of zero. +func NewBrokenCloser(buf []byte) *BrokenCloser { + bc := new(BrokenCloser) + bc.buf = bytes.NewBuffer(buf) + return bc +} + +// NewBrokenCloserString creates and initializes a new Buffer using +// string s as its initial contents. It is intended to prepare a +// buffer to read an existing string. +func NewBrokenCloserString(s string) *BrokenCloser { + buf := new(BrokenCloser) + buf.buf = bytes.NewBufferString(s) + return buf +}