Check-in [c28a763d5e]
Overview
SHA1:c28a763d5e932739911cbb29f9e01b1c2c2b7289
Date: 2014-12-24 19:07:41
User: spaskalev
Comment:Implemented ioutil/SizedWriter. CC at 100%.
Timelines: family | ancestors | descendants | both | trunk
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2014-12-24
21:40
[e1778aba98] Fixed SizedWriter behavior so that it follows io.Writer's Write(...) contract. Added more tests for 100% CC on the ioutil package. Predictor's compressor now uses SizedWriter and no longer has to do any internal buffering. (user: spaskalev, tags: trunk)
19:07
[c28a763d5e] Implemented ioutil/SizedWriter. CC at 100%. (user: spaskalev, tags: trunk)
08:24
[ffd1ab7b0c] Added an explicit copyright notice in the code. (user: spaskalev, tags: trunk)
Changes

Modified src/0dev.org/ioutil/ioutil.go from [98f8caa018] to [9da21d39e0].

     1         -// Package ioutil contains various constructs for io operations
            1  +// Package ioutil contains various constructs for io operations.
     2      2   package ioutil
     3      3   
     4      4   import (
     5      5   	"io"
     6      6   )
     7      7   
     8         -// An function alias type that implements io.Writer
            8  +// An function alias type that implements io.Writer.
     9      9   type WriterFunc func([]byte) (int, error)
    10     10   
    11         -// Delegates the call to the WriterFunc while implementing io.Writer
           11  +// Delegates the call to the WriterFunc while implementing io.Writer.
    12     12   func (w WriterFunc) Write(b []byte) (int, error) {
    13     13   	return w(b)
    14     14   }
    15     15   
    16         -// An function alias type that implements io.Reader
           16  +// An function alias type that implements io.Reader.
    17     17   type ReaderFunc func([]byte) (int, error)
    18     18   
    19         -// Delegates the call to the WriterFunc while implementing io.Reader
           19  +// Delegates the call to the WriterFunc while implementing io.Reader.
    20     20   func (r ReaderFunc) Read(b []byte) (int, error) {
    21     21   	return r(b)
    22     22   }
           23  +
           24  +// Returns a writer that delegates calls to Write(...) while ensuring
           25  +// that it is never called with less bytes than the specified amount.
           26  +//
           27  +// Calls with fewer bytes are buffered while a call with a nil slice
           28  +// causes the buffer to be flushed to the underlying writer.
           29  +func SizedWriter(writer io.Writer, size int) io.Writer {
           30  +	var buffer []byte = make([]byte, 0, size)
           31  +	var write WriterFunc
           32  +
           33  +	write = func(input []byte) (int, error) {
           34  +		var (
           35  +			count int
           36  +			err   error
           37  +		)
           38  +
           39  +		// Flush the buffer when called with no bytes to write
           40  +		if input == nil {
           41  +			// Call the writer with whatever we have in store..
           42  +			count, err = writer.Write(buffer)
           43  +
           44  +			// Advance the buffer
           45  +			buffer = buffer[:copy(buffer, buffer[count:])]
           46  +
           47  +			return 0, err
           48  +		}
           49  +
           50  +		// Delegate to the writer if the size is right
           51  +		if len(buffer) == 0 && len(input) >= size {
           52  +			return writer.Write(input)
           53  +		}
           54  +
           55  +		// Append data to the buffer
           56  +		count = copy(buffer[len(buffer):size], input)
           57  +		buffer = buffer[:len(buffer)+count]
           58  +
           59  +		// Return if we don't have enough bytes to write
           60  +		if len(buffer) < size {
           61  +			return len(input), nil
           62  +		}
           63  +
           64  +		// Flush the buffer as it is filled
           65  +		_, err = write(nil)
           66  +		if err != nil {
           67  +			return count, err
           68  +		}
           69  +
           70  +		// Handle the rest of the input
           71  +		return write(input[count:])
           72  +	}
           73  +
           74  +	return write
           75  +}
    23     76   
    24     77   // Returns a reader that delegates calls to Read(...) while ensuring
    25     78   // that the output buffer is never smaller than the required size
    26         -// and is downsized to a multiple of the required size if larger
           79  +// and is downsized to a multiple of the required size if larger.
    27     80   func SizedReader(reader io.Reader, size int) io.Reader {
    28     81   	var buffer []byte = make([]byte, 0, size)
    29     82   
    30     83   	return ReaderFunc(func(output []byte) (int, error) {
    31     84   		var (
    32         -			readCount int
    33         -			err       error
           85  +			count int
           86  +			err   error
    34     87   		)
    35     88   	start:
    36     89   		// Reply with the buffered data if there is any
    37     90   		if len(buffer) > 0 {
    38         -			readCount = copy(output, buffer)
           91  +			count = copy(output, buffer)
    39     92   
    40     93   			// Advance the data in the buffer
    41         -			buffer = buffer[:copy(buffer, buffer[readCount:])]
           94  +			buffer = buffer[:copy(buffer, buffer[count:])]
    42     95   
    43     96   			// Return count and error if we have read the whole buffer
    44     97   			if len(buffer) == 0 {
    45         -				return readCount, err
           98  +				return count, err
    46     99   			}
    47    100   
    48    101   			// Do not propagate an error until the buffer is exhausted
    49         -			return readCount, nil
          102  +			return count, nil
    50    103   		}
    51    104   
    52    105   		// Delegate if the buffer is empty and the destination buffer is large enough
    53    106   		if len(output) >= size {
    54    107   			return reader.Read(output[:(len(output)/size)*size])
    55    108   		}
    56    109   
    57    110   		// Perform a read into the buffer
    58         -		readCount, err = reader.Read(buffer[:size])
          111  +		count, err = reader.Read(buffer[:size])
    59    112   
    60    113   		// Size the buffer down to the read data size
    61    114   		// and restart if we have successfully read some bytes
    62         -		buffer = buffer[:readCount]
          115  +		buffer = buffer[:count]
    63    116   		if len(buffer) > 0 {
    64    117   			goto start
    65    118   		}
    66    119   
    67    120   		// Returning on err/misbehaving noop reader
    68    121   		return 0, err
    69    122   	})
    70    123   }

Modified src/0dev.org/ioutil/ioutil_test.go from [f071d3739a] to [ac59af05f3].

     1      1   package ioutil
     2      2   
     3      3   import (
     4      4   	diff "0dev.org/diff"
     5      5   	"bytes"
            6  +	"errors"
     6      7   	"io"
     7      8   	"testing"
     8      9   )
     9     10   
    10         -func TestWriter(t *testing.T) {
           11  +func TestWriterFunc(t *testing.T) {
    11     12   	var (
    12     13   		input  []byte = []byte{0, 1, 2, 3, 4, 5, 6, 7}
    13     14   		output []byte
    14     15   
    15     16   		reader *bytes.Reader = bytes.NewReader(input)
    16     17   		buffer bytes.Buffer
    17     18   	)
................................................................................
    23     24   	delta := diff.Diff(diff.D{len(input), len(output),
    24     25   		func(i, j int) bool { return input[i] == output[j] }})
    25     26   	if len(delta.Added) > 0 || len(delta.Removed) > 0 {
    26     27   		t.Error("Differences detected ", delta)
    27     28   	}
    28     29   }
    29     30   
    30         -func TestReader(t *testing.T) {
           31  +func TestReaderFunc(t *testing.T) {
    31     32   	var (
    32     33   		input  []byte = []byte{0, 1, 2, 3, 4, 5, 6, 7}
    33     34   		output []byte
    34     35   
    35     36   		reader *bytes.Reader = bytes.NewReader(input)
    36     37   		buffer bytes.Buffer
    37     38   	)
................................................................................
    43     44   	delta := diff.Diff(diff.D{len(input), len(output),
    44     45   		func(i, j int) bool { return input[i] == output[j] }})
    45     46   	if len(delta.Added) > 0 || len(delta.Removed) > 0 {
    46     47   		t.Error("Differences detected ", delta)
    47     48   	}
    48     49   }
    49     50   
    50         -func TestBlockReader(t *testing.T) {
           51  +func TestSizedWriter(t *testing.T) {
           52  +	var (
           53  +		buffer bytes.Buffer
           54  +		writer io.Writer = SizedWriter(&buffer, 4)
           55  +	)
           56  +
           57  +	count, err := writer.Write([]byte("12"))
           58  +	if count != 2 {
           59  +		t.Error("Unexpected write count from SizedWriter", count)
           60  +	}
           61  +	if err != nil {
           62  +		t.Error("Unexpected error from SizedWriter", err)
           63  +	}
           64  +
           65  +	count, err = writer.Write([]byte("3456"))
           66  +	if count != 2 {
           67  +		t.Error("Unexpected write count from SizedWriter", count)
           68  +	}
           69  +	if err != nil {
           70  +		t.Error("Unexpected error from SizedWriter", err)
           71  +	}
           72  +	if buffer.String() != "1234" {
           73  +		t.Error("Unexpected value in wrapped writer", buffer.String())
           74  +	}
           75  +
           76  +	// Flush the buffer
           77  +	count, err = writer.Write(nil)
           78  +	if count != 0 {
           79  +		t.Error("Unexpected write count from SizedWriter", count)
           80  +	}
           81  +	if err != nil {
           82  +		t.Error("Unexpected error from SizedWriter", err)
           83  +	}
           84  +	if buffer.String() != "123456" {
           85  +		t.Error("Unexpected value in wrapped writer", buffer.String())
           86  +	}
           87  +
           88  +	count, err = writer.Write([]byte("7890"))
           89  +	if count != 4 {
           90  +		t.Error("Unexpected write count from SizedWriter", count)
           91  +	}
           92  +	if err != nil {
           93  +		t.Error("Unexpected error from SizedWriter", err)
           94  +	}
           95  +	if buffer.String() != "1234567890" {
           96  +		t.Error("Unexpected value in wrapped writer", buffer.String())
           97  +	}
           98  +}
           99  +
          100  +func TestSizedWriterError(t *testing.T) {
          101  +	var (
          102  +		errorWriter io.Writer = WriterFunc(func([]byte) (int, error) {
          103  +			return 1, errors.New("Invalid write")
          104  +		})
          105  +		writer io.Writer = SizedWriter(errorWriter, 2)
          106  +	)
          107  +
          108  +	count, err := writer.Write([]byte("1"))
          109  +	if count != 1 {
          110  +		t.Error("Unexpected write count from SizedWriter", count)
          111  +	}
          112  +	if err != nil {
          113  +		t.Error("Unexpected error from SizedWriter", err)
          114  +	}
          115  +
          116  +	count, err = writer.Write([]byte("2"))
          117  +	if count != 1 {
          118  +		t.Error("Unexpected write count from SizedWriter", count)
          119  +	}
          120  +	if err == nil {
          121  +		t.Error("Unexpected lack of error from SizedWriter")
          122  +	}
          123  +}
          124  +
          125  +func TestSizedReader(t *testing.T) {
    51    126   	var (
    52    127   		input  []byte = []byte{0, 1, 2, 3, 4, 5, 6, 7}
    53    128   		output []byte = make([]byte, 16)
    54    129   
    55    130   		reader *bytes.Reader = bytes.NewReader(input)
    56    131   		min    io.Reader     = SizedReader(reader, 4)
    57    132   	)
    58    133   
    59    134   	// Expecting a read count of 2
    60    135   	count, err := min.Read(output[:2])
    61    136   	if count != 2 {
    62         -		t.Error("Invalid read count from MinReader", count)
          137  +		t.Error("Invalid read count from SizedReader", count)
    63    138   	}
    64    139   	if err != nil {
    65         -		t.Error("Unexpected error from MinReader", err)
          140  +		t.Error("Unexpected error from SizedReader", err)
    66    141   	}
    67    142   
    68    143   	// Expecting a read count of 2 as it should have 2 bytes in its buffer
    69    144   	count, err = min.Read(output[:3])
    70    145   	if count != 2 {
    71         -		t.Error("Invalid read count from MinReader", count)
          146  +		t.Error("Invalid read count from SizedReader", count)
    72    147   	}
    73    148   	if err != nil {
    74         -		t.Error("Unexpected error from MinReader", err)
          149  +		t.Error("Unexpected error from SizedReader", err)
    75    150   	}
    76    151   
    77    152   	// Expecting a read count of 4 as the buffer should be empty
    78    153   	count, err = min.Read(output[:4])
    79    154   	if count != 4 {
    80         -		t.Error("Invalid read count from MinReader", count)
          155  +		t.Error("Invalid read count from SizedReader", count)
    81    156   	}
    82    157   	if err != nil {
    83         -		t.Error("Unexpected error from MinReader", err)
          158  +		t.Error("Unexpected error from SizedReader", err)
    84    159   	}
    85    160   
    86    161   	// Expecting a read count of 0 with an EOF as the buffer should be empty
    87    162   	count, err = min.Read(output[:1])
    88    163   	if count != 0 {
    89         -		t.Error("Invalid read count from MinReader", count)
          164  +		t.Error("Invalid read count from SizedReader", count)
    90    165   	}
    91    166   	if err != io.EOF {
    92         -		t.Error("Unexpected error from MinReader", err)
          167  +		t.Error("Unexpected error from SizedReader", err)
    93    168   	}
    94    169   }