blob: d01281e7f1277baff4a39251ff69315365f58f38 [file] [log] [blame]
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log"
"sync"
"testing"
"google.golang.org/grpc"
pb "google.golang.org/genproto/googleapis/bytestream"
)
const (
testName = "testName"
testData = "0123456789"
)
var (
setupServerOnce sync.Once
server *Server
)
func TestNewServerWithInvalidInputs(t *testing.T) {
_, err := NewServer(grpc.NewServer(), nil, nil)
if err == nil {
t.Fatal("NewServer(nil, nil) should not succeed")
}
}
func TestServerWrite(t *testing.T) {
testCases := []struct {
name string
writeHandler WriteHandler
input []interface{}
writeCount int
allowEmptyCommits bool
allowOverwrite bool
wantErr bool
wantResponse int
}{
{
name: "empty resource name",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
wantResponse: 0,
}, {
name: "Recv returns io.EOF",
writeHandler: &TestWriteHandler{},
input: []interface{}{
io.EOF,
},
writeCount: 1,
wantErr: false,
wantResponse: 0,
}, {
name: "Recv returns error, 0 WriteRequests",
writeHandler: &TestWriteHandler{},
input: []interface{}{
errors.New("Recv returns error, 0 WriteRequests"),
},
writeCount: 1,
wantErr: true,
wantResponse: 0,
}, {
name: "simple test",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
},
writeCount: 1,
wantResponse: 1,
}, {
name: "Recv returns error, 1 WriteRequests",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: false,
Data: []byte(testData),
},
errors.New("Recv returns error, 1 WriteRequests"),
},
writeCount: 1,
wantErr: true,
wantResponse: 0,
}, {
name: "attempt to overwrite the same name",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 2,
wantErr: true,
wantResponse: 1,
}, {
name: "overwrite with the same name + AllowOverwrite",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
},
writeCount: 2,
allowOverwrite: true,
wantResponse: 2,
}, {
name: "two WriteRequests - 1st is empty",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: false,
Data: nil,
},
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
},
writeCount: 1,
wantResponse: 1,
allowEmptyCommits: true,
}, {
name: "two WriteRequests - 2nd is empty",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: false,
Data: []byte(testData),
},
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: int64(len(testData)),
FinishWrite: true,
Data: nil,
},
io.EOF,
},
writeCount: 1,
wantResponse: 1,
}, {
name: "two WriteRequests - all empty",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: false,
Data: nil,
},
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: nil,
},
},
writeCount: 1,
wantErr: true,
wantResponse: 1,
allowEmptyCommits: true,
}, {
name: "two WriteRequests - varying offset",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 100,
FinishWrite: false,
Data: []byte(testData),
},
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 100 + int64(len(testData)),
FinishWrite: true,
Data: []byte(testData),
},
io.EOF,
},
writeCount: 1,
wantResponse: 1,
}, {
name: "two WriteRequests - disjoint offset",
writeHandler: &TestWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 100,
FinishWrite: false,
Data: []byte(testData),
},
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 200,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
wantResponse: 0,
}, {
name: "fails with UngettableWriteHandler",
writeHandler: &UngettableWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
}, {
name: "fails with UnwritableWriteHandler",
writeHandler: &UnwritableWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
}, {
name: "fails with UnclosableWriteHandler",
writeHandler: &UnclosableWriteHandler{},
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
wantResponse: 1,
}, {
name: "fails with nil WriteHandler",
writeHandler: nil,
input: []interface{}{
&pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
},
},
writeCount: 1,
wantErr: true,
},
}
ctx := context.Background()
for _, tc := range testCases {
readHandler := &TestReadHandler{}
if tc.writeHandler != nil {
readHandler = nil
}
setupServer(readHandler, tc.writeHandler)
server.AllowOverwrite = tc.allowOverwrite
var requestCount, responseCount int
var err error
for i := 0; i < tc.writeCount; i++ {
err = server.rpc.Write(&fakeWriteServerImpl{
ctx: ctx,
receiver: func() (*pb.WriteRequest, error) {
if requestCount >= len(tc.input) {
t.Fatalf("%s: got %d call(s) to Recv, want %d from len(input)", tc.name, requestCount+1, len(tc.input))
}
v := tc.input[requestCount]
requestCount++
request, ok := v.(*pb.WriteRequest)
if ok {
return request, nil
}
err, ok := v.(error)
if !ok {
t.Fatalf("%s: unknown input: %v", tc.name, v)
}
return nil, err
},
sender: func(response *pb.WriteResponse) error {
if !tc.allowEmptyCommits && response.CommittedSize == 0 {
t.Fatalf("%s: invalid response: WriteResponse %v", tc.name, response)
}
responseCount++
return nil
},
})
gotErr := (err != nil)
if i+1 < tc.writeCount {
if gotErr {
t.Errorf("%s: Write got err=%v, wantErr=%t, but on Write[%d/%d]. Error should not happen until last call to Write.", tc.name, err, tc.wantErr, i+1, tc.writeCount)
break // The t.Errorf conditions below may erroneously fire, pay them no mind.
}
} else if gotErr != tc.wantErr {
t.Errorf("%s: Write got err=%v, wantErr=%t", tc.name, err, tc.wantErr)
break // The t.Errorf conditions below may erroneously fire, pay them no mind.
}
}
if requestCount != len(tc.input) {
t.Errorf("%s: got %d call(s) to Recv, want %d", tc.name, requestCount, len(tc.input))
}
if responseCount != tc.wantResponse {
t.Errorf("%s: got %d call(s) to SendProto, want %d", tc.name, responseCount, tc.wantResponse)
}
}
}
func TestServerWrite_SendAndCloseError(t *testing.T) {
const (
wantRequest = 2
wantResponse = 1
)
ctx := context.Background()
setupServer(nil, &TestWriteHandler{})
var requestCount, responseCount int
err := server.rpc.Write(&fakeWriteServerImpl{
ctx: ctx,
receiver: func() (*pb.WriteRequest, error) {
if requestCount >= wantRequest {
t.Fatalf("got %d call(s) to Recv, want %d", requestCount+1, wantRequest)
}
requestCount++
return &pb.WriteRequest{
ResourceName: testName,
WriteOffset: 0,
FinishWrite: true,
Data: []byte(testData),
}, nil
},
sender: func(response *pb.WriteResponse) error {
responseCount++
return errors.New("TestServerWrite SendProto error")
},
})
if err == nil {
t.Errorf("Write should have failed, but succeeded")
}
if requestCount != wantRequest {
t.Errorf("got %d call(s) to Recv, want %d", requestCount, wantRequest)
}
if responseCount != wantResponse {
t.Errorf("got %d call(s) to SendProto, want %d", responseCount, wantResponse)
}
}
func TestQueryWriteStatus(t *testing.T) {
testCases := []struct {
name string
existingName string
requestName string
wantErr bool
}{
{
name: "existing name should work",
existingName: testName,
requestName: testName,
}, {
name: "missing name should break",
existingName: testName,
requestName: "invalidName",
wantErr: true,
},
}
ctx := context.Background()
for _, tc := range testCases {
setupServer(nil, &TestWriteHandler{})
server.status[tc.existingName] = &pb.QueryWriteStatusResponse{}
_, err := server.rpc.QueryWriteStatus(ctx, &pb.QueryWriteStatusRequest{
ResourceName: tc.requestName,
})
if gotErr := (err != nil); gotErr != tc.wantErr {
t.Errorf("%s: QueryWriteStatus(%q) got err=%v, wantErr=%t", tc.name, tc.requestName, err, tc.wantErr)
}
}
}
func TestServerRead(t *testing.T) {
testCases := []struct {
name string
readHandler ReadHandler
input *pb.ReadRequest
readCount int
wantErr bool
wantResponse []string
}{
{
name: "empty resource name",
readHandler: &TestReadHandler{},
input: &pb.ReadRequest{
ReadLimit: 1,
},
readCount: 1,
wantErr: true,
wantResponse: []string{},
}, {
name: "test ReadLimit=-1",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: -1,
},
readCount: 1,
wantErr: true,
}, {
name: "test ReadLimit=1",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: 1,
},
readCount: 1,
wantResponse: []string{"0"},
}, {
name: "test ReadLimit=2",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: 2,
},
readCount: 1,
wantResponse: []string{"01"},
}, {
name: "test ReadOffset=1 ReadLimit=2",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 1,
ReadLimit: 2,
},
readCount: 1,
wantResponse: []string{"12"},
}, {
name: "test ReadOffset=2 ReadLimit=2",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 2,
ReadLimit: 2,
},
readCount: 1,
wantResponse: []string{"23"},
}, {
name: "read all testData at exactly the limit",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)),
},
readCount: 1,
wantResponse: []string{"0123456789"},
}, {
name: "read all testData",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)) * 2,
},
readCount: 1,
wantResponse: []string{"0123456789"},
}, {
name: "read all testData 2 times",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)) * 2,
},
readCount: 2,
wantResponse: []string{"0123456789", "0123456789"},
}, {
name: "test ReadLimit=0",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: 0,
},
readCount: 1,
wantResponse: []string{"0123456789"},
}, {
name: "test ReadLimit=1000",
readHandler: &TestReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: 1000,
},
readCount: 1,
wantResponse: []string{"0123456789"},
}, {
name: "fails with UngettableReadHandler",
readHandler: &UngettableReadHandler{},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)),
},
readCount: 1,
wantErr: true,
}, {
name: "fails with UnreadableReadHandler",
readHandler: &UnreadableReadHandler{},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)),
},
readCount: 1,
wantErr: true,
}, {
name: "fails with UnclosableReadHandler",
readHandler: &UnclosableReadHandler{buf: testData},
input: &pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)) * 2,
},
readCount: 1,
wantErr: true,
wantResponse: []string{"0123456789"},
}, {
name: "fails with nil ReadRequest",
readHandler: &TestReadHandler{buf: testData},
readCount: 1,
wantErr: true,
}, {
name: "fails with nil ReadHandler",
readHandler: nil,
readCount: 1,
wantErr: true,
},
}
ctx := context.Background()
for _, tc := range testCases {
var writeHandler WriteHandler
if tc.readHandler == nil {
writeHandler = &TestWriteHandler{}
}
setupServer(tc.readHandler, writeHandler)
var responseCount int
var err error
for i := 0; i < tc.readCount; i++ {
err = server.rpc.Read(tc.input, &fakeReadServerImpl{
ctx: ctx,
sender: func(response *pb.ReadResponse) error {
if responseCount >= len(tc.wantResponse) {
t.Fatalf("%s: got %d call(s) to Send(), want %d", tc.name, responseCount+1, len(tc.wantResponse))
}
if got, want := string(response.Data), tc.wantResponse[responseCount]; got != want {
t.Fatalf("%s: response[%d] got %q, want %q", tc.name, responseCount, got, want)
}
responseCount++
return nil
},
})
gotErr := (err != nil)
if i+1 < tc.readCount {
if gotErr {
t.Errorf("%s: Read got err=%v, wantErr=%t, but on Read[%d/%d]. Error should not happen until last call to Read", tc.name, err, tc.wantErr, i+1, tc.readCount)
break
}
} else if gotErr != tc.wantErr {
t.Errorf("%s: Read got err=%v, wantErr=%t", tc.name, err, tc.wantErr)
break
}
}
if responseCount != len(tc.wantResponse) {
t.Errorf("%s: got %d call(s) to Send, want %d", tc.name, responseCount, len(tc.wantResponse))
}
}
}
func TestServerRead_SendError(t *testing.T) {
setupServer(&TestReadHandler{buf: testData}, nil)
err := server.rpc.Read(&pb.ReadRequest{
ResourceName: testName,
ReadOffset: 0,
ReadLimit: int64(len(testData)) * 2,
}, &fakeReadServerImpl{
ctx: context.Background(),
sender: func(response *pb.ReadResponse) error {
if string(response.Data) != testData {
t.Fatalf("Send: got %v, want %q", response, testData)
}
return errors.New("TestServerRead Send() error")
},
})
if err == nil {
t.Fatal("Read() should have failed, but succeeded")
}
}
type fakeWriteServerImpl struct {
pb.ByteStream_WriteServer
ctx context.Context
receiver func() (*pb.WriteRequest, error)
sender func(*pb.WriteResponse) error
}
func (fake *fakeWriteServerImpl) Context() context.Context {
return fake.ctx
}
func (fake *fakeWriteServerImpl) Recv() (*pb.WriteRequest, error) {
return fake.receiver()
}
func (fake *fakeWriteServerImpl) SendMsg(m interface{}) error {
return fake.sender(m.(*pb.WriteResponse))
}
func (fake *fakeWriteServerImpl) SendAndClose(m *pb.WriteResponse) error {
fake.sender(m)
return nil
}
type fakeReadServerImpl struct {
pb.ByteStream_ReadServer
ctx context.Context
sender func(*pb.ReadResponse) error
}
func (fake *fakeReadServerImpl) Context() context.Context {
return fake.ctx
}
func (fake *fakeReadServerImpl) Send(response *pb.ReadResponse) error {
return fake.sender(response)
}
type TestWriteHandler struct {
buf bytes.Buffer // bytes.Buffer implements io.Writer
name string // This service can handle one name only.
}
func (w *TestWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
if w.name == "" {
w.name = name
} else if w.name != name {
return nil, fmt.Errorf("writer already has name=%q, now a new name=%q confuses me", w.name, name)
}
// initOffset is ignored.
return &w.buf, nil
}
func (w *TestWriteHandler) Close(ctx context.Context, name string) error {
w.name = ""
w.buf.Reset()
return nil
}
type UngettableWriteHandler struct{}
func (w *UngettableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
return nil, errors.New("UngettableWriteHandler.GetWriter() always fails")
}
func (w *UngettableWriteHandler) Close(ctx context.Context, name string) error {
return nil
}
type UnwritableWriter struct{}
func (w *UnwritableWriter) Write(p []byte) (int, error) {
return 0, errors.New("UnwritableWriter.Write() always fails")
}
type UnwritableWriteHandler struct{}
func (w *UnwritableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
return &UnwritableWriter{}, nil
}
func (w *UnwritableWriteHandler) Close(ctx context.Context, name string) error {
return nil
}
type UnclosableWriter struct{}
func (w *UnclosableWriter) Write(p []byte) (int, error) {
return len(p), nil
}
type UnclosableWriteHandler struct{}
func (w *UnclosableWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
return &UnclosableWriter{}, nil
}
func (w *UnclosableWriteHandler) Close(ctx context.Context, name string) error {
return errors.New("UnclosableWriteHandler.Close() always fails")
}
type TestReadHandler struct {
buf string
name string // This service can handle one name only.
}
// GetWriter() returns an io.ReaderAt to accept reads from the given name.
func (r *TestReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
if r.name == "" {
r.name = name
} else if r.name != name {
return nil, fmt.Errorf("reader already has name=%q, now a new name=%q confuses me", r.name, name)
}
return bytes.NewReader([]byte(r.buf)), nil
}
// Close does nothing.
func (r *TestReadHandler) Close(ctx context.Context, name string) error {
return nil
}
type UngettableReadHandler struct{}
func (r *UngettableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
return nil, errors.New("UngettableReadHandler.GetReader() always fails")
}
func (r *UngettableReadHandler) Close(ctx context.Context, name string) error {
return nil
}
type UnreadableReader struct{}
func (r *UnreadableReader) ReadAt(p []byte, offset int64) (int, error) {
return 0, errors.New("UnreadableReader.ReadAt() always fails")
}
type UnreadableReadHandler struct{}
func (r *UnreadableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
return &UnreadableReader{}, nil
}
func (r *UnreadableReadHandler) Close(ctx context.Context, name string) error {
return nil
}
type UnclosableReadHandler struct {
buf string
}
func (r *UnclosableReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
return bytes.NewReader([]byte(r.buf)), nil
}
func (r *UnclosableReadHandler) Close(ctx context.Context, name string) error {
return fmt.Errorf("UnclosableReader.Close(%s) always fails", name)
}
func registerServer() {
gsrv := grpc.NewServer()
var err error
server, err = NewServer(gsrv, &TestReadHandler{}, &TestWriteHandler{})
if err != nil {
log.Fatalf("NewServer() failed: %v", err)
}
}
func setupServer(readHandler ReadHandler, writeHandler WriteHandler) {
setupServerOnce.Do(registerServer)
server.status = make(map[string]*pb.QueryWriteStatusResponse)
server.readHandler = readHandler
server.writeHandler = writeHandler
}