diff --git a/sync_file_reader.go b/sync_file_reader.go index 69f474d..1aa1b89 100644 --- a/sync_file_reader.go +++ b/sync_file_reader.go @@ -1,7 +1,6 @@ package goadb import ( - "fmt" "io" "github.com/zach-klippenstein/goadb/util" @@ -15,6 +14,9 @@ type syncFileReader struct { // Reader for the current chunk only. chunkReader io.Reader + + // False until the DONE chunk is encountered. + eof bool } var _ io.ReadCloser = &syncFileReader{} @@ -26,18 +28,30 @@ func newSyncFileReader(s wire.SyncScanner) (r io.ReadCloser, err error) { // Read the header for the first chunk to consume any errors. if _, err = r.Read([]byte{}); err != nil { - r.Close() - return nil, err + if err == io.EOF { + // EOF means the file was empty. This still means the file was opened successfully, + // and the next time the caller does a read they'll get the EOF and handle it themselves. + err = nil + } else { + r.Close() + return nil, err + } } return } func (r *syncFileReader) Read(buf []byte) (n int, err error) { + if r.eof { + return 0, io.EOF + } + if r.chunkReader == nil { chunkReader, err := readNextChunk(r.scanner) if err != nil { - // If this is EOF, we've read the last chunk. - // Either way, we want to pass it up to the caller. + if err == io.EOF { + // We just read the last chunk, set our flag before passing it up. + r.eof = true + } return 0, err } r.chunkReader = chunkReader @@ -82,7 +96,7 @@ func readNextChunk(r wire.SyncScanner) (io.Reader, error) { case wire.StatusSyncDone: return nil, io.EOF default: - return nil, fmt.Errorf("expected chunk id '%s' or '%s', but got '%s'", + return nil, util.Errorf(util.AssertionError, "expected chunk id '%s' or '%s', but got '%s'", wire.StatusSyncData, wire.StatusSyncDone, []byte(status)) } } diff --git a/sync_file_reader_test.go b/sync_file_reader_test.go index d2b2a55..83da0e8 100644 --- a/sync_file_reader_test.go +++ b/sync_file_reader_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/zach-klippenstein/goadb/util" "github.com/zach-klippenstein/goadb/wire" + "io/ioutil" ) func TestReadNextChunk(t *testing.T) { @@ -44,7 +45,7 @@ func TestReadNextChunkInvalidChunkId(t *testing.T) { // Read 1st chunk _, err := readNextChunk(s) - assert.EqualError(t, err, "expected chunk id 'DATA' or 'DONE', but got 'ATAD'") + assert.EqualError(t, err, "AssertionError: expected chunk id 'DATA' or 'DONE', but got 'ATAD'") } func TestReadMultipleCalls(t *testing.T) { @@ -91,6 +92,20 @@ func TestReadError(t *testing.T) { assert.EqualError(t, err, "AdbError: server error for read-chunk request: fail ({Request:read-chunk ServerMsg:fail})") } +func TestReadEmpty(t *testing.T) { + s := wire.NewSyncScanner(strings.NewReader( + "DONE")) + r, err := newSyncFileReader(s) + assert.NoError(t, err) + + // Multiple read calls that return EOF is a valid case. + for i := 0; i < 5; i++ { + data, err := ioutil.ReadAll(r) + assert.NoError(t, err) + assert.Empty(t, data) + } +} + func TestReadErrorNotFound(t *testing.T) { s := wire.NewSyncScanner(strings.NewReader( "FAIL\031\000\000\000No such file or directory")) diff --git a/util.go b/util.go index 6291f23..92d83ef 100644 --- a/util.go +++ b/util.go @@ -26,7 +26,7 @@ func wrapClientError(err error, client interface{}, operation string, args ...in return nil } if _, ok := err.(*util.Err); !ok { - panic("err is not a *util.Err") + panic("err is not a *util.Err: " + err.Error()) } clientType := reflect.TypeOf(client) diff --git a/wire/sync_sender.go b/wire/sync_sender.go index 9189080..182678a 100644 --- a/wire/sync_sender.go +++ b/wire/sync_sender.go @@ -68,8 +68,7 @@ func (s *realSyncSender) SendBytes(data []byte) error { if err := s.SendInt32(int32(length)); err != nil { return util.WrapErrorf(err, util.NetworkError, "error sending data length on sync sender") } - return util.WrapErrorf(writeFully(s.Writer, data), - util.NetworkError, "error sending data on sync sender") + return writeFully(s.Writer, data) } func (s *realSyncSender) Close() error {