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 }