github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/client/bytestream_test.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"testing"
     8  
     9  	"github.com/pkg/errors"
    10  	"google.golang.org/grpc"
    11  
    12  	// Redundant imports are required for the google3 mirror. Aliases should not be changed.
    13  	bsgrpc "google.golang.org/genproto/googleapis/bytestream"
    14  	bspb "google.golang.org/genproto/googleapis/bytestream"
    15  )
    16  
    17  var logStreamData = []byte("Hello World! This is large data to send.")
    18  
    19  type logStream struct {
    20  	logStreamID   string
    21  	logicalOffset int64
    22  	finalized     bool
    23  }
    24  
    25  func TestWriteBytesAtRemoteOffsetSuccess_LogStream(t *testing.T) {
    26  	tests := []struct {
    27  		description   string
    28  		ls            *logStream
    29  		data          []byte
    30  		dataPartsLen  int
    31  		doNotFinalize bool
    32  		initialOffset int64
    33  		wantBytesLen  int64
    34  	}{
    35  		{
    36  			description:   "valid data with offset 0",
    37  			ls:            &logStream{logicalOffset: 0},
    38  			data:          logStreamData,
    39  			doNotFinalize: true,
    40  			initialOffset: 0,
    41  			dataPartsLen:  3,
    42  			wantBytesLen:  int64(len(logStreamData)),
    43  		},
    44  		{
    45  			description:   "valid data with non-zero offset",
    46  			ls:            &logStream{logicalOffset: 4},
    47  			data:          logStreamData,
    48  			doNotFinalize: true,
    49  			initialOffset: 4,
    50  			dataPartsLen:  3,
    51  			wantBytesLen:  int64(len(logStreamData)),
    52  		},
    53  		{
    54  			description:   "one big chunk",
    55  			ls:            &logStream{logicalOffset: 0},
    56  			data:          logStreamData,
    57  			doNotFinalize: false,
    58  			initialOffset: 0,
    59  			dataPartsLen:  1,
    60  			wantBytesLen:  int64(len(logStreamData)),
    61  		},
    62  		{
    63  			description:   "empty data",
    64  			ls:            &logStream{logicalOffset: 0},
    65  			data:          []byte{},
    66  			doNotFinalize: false,
    67  			initialOffset: 0,
    68  			dataPartsLen:  1,
    69  			wantBytesLen:  0,
    70  		},
    71  	}
    72  
    73  	b := newServer(t)
    74  	defer b.shutDown()
    75  	for _, test := range tests {
    76  		t.Run(test.description, func(t *testing.T) {
    77  			size := len(test.data)/test.dataPartsLen + 1
    78  			start := int(test.initialOffset)
    79  			end := size
    80  			lsID := test.description
    81  			test.ls.logStreamID = lsID
    82  			b.fake.logStreams[lsID] = test.ls
    83  			ChunkMaxSize(size).Apply(b.client)
    84  
    85  			for i := 0; i < test.dataPartsLen; i++ {
    86  				if end > len(test.data) {
    87  					end = len(test.data)
    88  				}
    89  				if i == test.dataPartsLen-1 {
    90  					test.doNotFinalize = false
    91  				}
    92  
    93  				writtenBytes, err := b.client.WriteBytesAtRemoteOffset(b.ctx, lsID, test.data[start:end], test.doNotFinalize, test.initialOffset)
    94  				if err != nil {
    95  					t.Errorf("WriteBytesAtRemoteOffset() failed unexpectedly: %v", err)
    96  				}
    97  				if b.fake.logStreams[lsID].logicalOffset != int64(end) {
    98  					t.Errorf("WriteBytesAtRemoteOffset() = %d, want %d", b.fake.logStreams[lsID].logicalOffset, end)
    99  				}
   100  				// LogStream shouldn't be finalized when we set ByteStreamOptFinishWrite false.
   101  				if i != test.dataPartsLen-1 && b.fake.logStreams[lsID].finalized {
   102  					t.Error("WriteBytesAtRemoteOffset() incorrectly finalized LogStream")
   103  				}
   104  
   105  				test.initialOffset += writtenBytes
   106  				start = end
   107  				end += size
   108  			}
   109  
   110  			if b.fake.logStreams[lsID].logicalOffset != test.wantBytesLen {
   111  				t.Errorf("WriteBytesAtRemoteOffset() = %d, want %d", b.fake.logStreams[lsID].logicalOffset, test.wantBytesLen)
   112  			}
   113  			if !b.fake.logStreams[lsID].finalized {
   114  				t.Error("WriteBytesAtRemoteOffset() didn't correctly finalize LogStream")
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func TestWriteBytesAtRemoteOffsetErrors_LogStream(t *testing.T) {
   121  	tests := []struct {
   122  		description   string
   123  		ls            *logStream
   124  		data          []byte
   125  		initialOffset int64
   126  	}{
   127  		{
   128  			description:   "invalid write to finalized logstream",
   129  			ls:            &logStream{logicalOffset: 0, finalized: true},
   130  			data:          logStreamData,
   131  			initialOffset: 0,
   132  		},
   133  		{
   134  			description:   "not found",
   135  			ls:            nil,
   136  			data:          logStreamData,
   137  			initialOffset: 0,
   138  		},
   139  		{
   140  			description:   "invalid smaller offset",
   141  			ls:            &logStream{logicalOffset: 4},
   142  			data:          logStreamData,
   143  			initialOffset: 1,
   144  		},
   145  		{
   146  			description:   "invalid larger offset",
   147  			ls:            &logStream{logicalOffset: 2},
   148  			data:          logStreamData,
   149  			initialOffset: 4,
   150  		},
   151  		{
   152  			description:   "invalid negative offset",
   153  			ls:            &logStream{logicalOffset: 0},
   154  			data:          logStreamData,
   155  			initialOffset: -1,
   156  		},
   157  	}
   158  
   159  	b := newServer(t)
   160  	defer b.shutDown()
   161  	for _, test := range tests {
   162  		t.Run(test.description, func(t *testing.T) {
   163  			lsID := test.description
   164  			if test.ls != nil {
   165  				test.ls.logStreamID = lsID
   166  				b.fake.logStreams[lsID] = test.ls
   167  			}
   168  			data := []byte("Hello World!")
   169  			ChunkMaxSize(len(data)).Apply(b.client)
   170  
   171  			writtenBytes, err := b.client.WriteBytesAtRemoteOffset(b.ctx, lsID, data, false, test.initialOffset)
   172  			if err == nil {
   173  				t.Errorf("WriteBytesAtRemoteOffset(ctx, %s, %s, false, %d) got nil error, want non-nil error", lsID, string(data), test.initialOffset)
   174  			}
   175  			if writtenBytes != 0 {
   176  				t.Errorf("WriteBytesAtRemoteOffset(ctx, %s, %s, false, %d) got %d byte(s), want 0 byte", lsID, string(data), test.initialOffset, writtenBytes)
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  type ByteStream struct {
   183  	logStreams map[string]*logStream
   184  }
   185  
   186  type Server struct {
   187  	client   *Client
   188  	listener net.Listener
   189  	server   *grpc.Server
   190  	fake     *ByteStream
   191  	ctx      context.Context
   192  }
   193  
   194  func newServer(t *testing.T) *Server {
   195  	s := &Server{ctx: context.Background()}
   196  	var err error
   197  	s.listener, err = net.Listen("tcp", ":0")
   198  	if err != nil {
   199  		t.Fatalf("Cannot listen: %v", err)
   200  	}
   201  	s.server = grpc.NewServer()
   202  	s.fake = &ByteStream{logStreams: make(map[string]*logStream)}
   203  	bsgrpc.RegisterByteStreamServer(s.server, s.fake)
   204  
   205  	go s.server.Serve(s.listener)
   206  	s.client, err = NewClient(s.ctx, instance, DialParams{
   207  		Service:    s.listener.Addr().String(),
   208  		NoSecurity: true,
   209  	}, StartupCapabilities(false), ChunkMaxSize(2))
   210  	if err != nil {
   211  		t.Fatalf("Error connecting to server: %v", err)
   212  	}
   213  	return s
   214  }
   215  
   216  func (s *Server) shutDown() {
   217  	s.client.Close()
   218  	s.listener.Close()
   219  	s.server.Stop()
   220  }
   221  
   222  func (b *ByteStream) QueryWriteStatus(context.Context, *bspb.QueryWriteStatusRequest) (*bspb.QueryWriteStatusResponse, error) {
   223  	return &bspb.QueryWriteStatusResponse{}, nil
   224  }
   225  
   226  func (b *ByteStream) Read(req *bspb.ReadRequest, stream bsgrpc.ByteStream_ReadServer) error {
   227  	return nil
   228  }
   229  
   230  // Write implements the write operation for LogStream Write API.
   231  func (b *ByteStream) Write(stream bsgrpc.ByteStream_WriteServer) error {
   232  	defer stream.SendAndClose(&bspb.WriteResponse{})
   233  	req, err := stream.Recv()
   234  	if err != nil {
   235  		return errors.Wrap(err, "failed to write")
   236  	}
   237  
   238  	ls, ok := b.logStreams[req.GetResourceName()]
   239  	if !ok {
   240  		return fmt.Errorf("unable to find LogStream")
   241  	}
   242  	if ls.finalized {
   243  		return fmt.Errorf("unable to extend finalized LogStream")
   244  	}
   245  	if ls.logicalOffset != req.GetWriteOffset() || ls.logicalOffset < 0 {
   246  		return fmt.Errorf("incorrect LogStream offset")
   247  	}
   248  	ls.finalized = req.GetFinishWrite()
   249  	ls.logicalOffset += int64(len(req.GetData()))
   250  
   251  	return nil
   252  }