fix(storage): fix panic when Flush called early (#11934) · googleapis/google-cloud-go@7d0b8a7
@@ -1235,6 +1235,118 @@ func TestWriterFlushAtCloseEmulated(t *testing.T) {
12351235 })
12361236}
123712371238+// Tests small flush (under 512 bytes) to verify that logic avoiding
1239+// content type sniffing works as expected in this case.
1240+func TestWriterSmallFlushEmulated(t *testing.T) {
1241+transportClientTest(skipHTTP("appends only supported via gRPC"), t, func(t *testing.T, ctx context.Context, project, bucket string, client storageClient) {
1242+// Create test bucket.
1243+_, err := client.CreateBucket(ctx, project, bucket, &BucketAttrs{
1244+Name: bucket,
1245+ }, nil)
1246+if err != nil {
1247+t.Fatalf("client.CreateBucket: %v", err)
1248+ }
1249+prefix := time.Now().Nanosecond()
1250+testCases := []struct {
1251+initialBytes []byte
1252+wantContentType string
1253+ }{
1254+ {
1255+initialBytes: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
1256+wantContentType: "application/octet-stream",
1257+ },
1258+ {
1259+initialBytes: []byte("helloworld"),
1260+wantContentType: "text/plain; charset=utf-8",
1261+ },
1262+ }
1263+1264+for _, tc := range testCases {
1265+t.Run(tc.wantContentType, func(t *testing.T) {
1266+objName := fmt.Sprintf("%d-object-%d", prefix, time.Now().Nanosecond())
1267+1268+vc := &Client{tc: client}
1269+w := vc.Bucket(bucket).Object(objName).NewWriter(ctx)
1270+w.Append = true
1271+w.ChunkSize = MiB
1272+var gotOffsets []int64
1273+w.ProgressFunc = func(offset int64) {
1274+gotOffsets = append(gotOffsets, offset)
1275+ }
1276+wantOffsets := []int64{10, 1010, 1010 + MiB, 1010 + 2*MiB, 3 * MiB}
1277+1278+// Make content with fixed first 10 bytes which will yield
1279+// expected type when sniffed.
1280+content := bytes.Clone(randomBytes3MiB)
1281+copy(content, tc.initialBytes)
1282+1283+// Test Flush at a 10 byte offset.
1284+n, err := w.Write(content[:10])
1285+if err != nil {
1286+t.Fatalf("writing data: got %v; want ok", err)
1287+ }
1288+if n != 10 {
1289+t.Errorf("writing data: got %v bytes written, want %v", n, 10)
1290+ }
1291+off, err := w.Flush()
1292+if err != nil {
1293+t.Fatalf("flush: got %v; want ok", err)
1294+ }
1295+if off != 10 {
1296+t.Errorf("flushing data: got %v bytes written, want %v", off, 10)
1297+ }
1298+// Write another 1000 bytes and flush again.
1299+n, err = w.Write(content[10:1010])
1300+if err != nil {
1301+t.Fatalf("writing data: got %v; want ok", err)
1302+ }
1303+if n != 1000 {
1304+t.Errorf("writing data: got %v bytes written, want %v", n, 1000)
1305+ }
1306+off, err = w.Flush()
1307+if err != nil {
1308+t.Fatalf("flush: got %v; want ok", err)
1309+ }
1310+if off != 1010 {
1311+t.Errorf("flushing data: got %v bytes written, want %v", off, 1010)
1312+ }
1313+// Write the rest of the object
1314+_, err = w.Write(content[1010:])
1315+if err != nil {
1316+t.Fatalf("writing data: got %v; want ok", err)
1317+ }
1318+if err := w.Close(); err != nil {
1319+t.Fatalf("closing writer: %v", err)
1320+ }
1321+// Check offsets
1322+if !slices.Equal(gotOffsets, wantOffsets) {
1323+t.Errorf("progress offsets: got %v, want %v", gotOffsets, wantOffsets)
1324+ }
1325+1326+// Download object and check data
1327+r, err := veneerClient.Bucket(bucket).Object(objName).NewReader(ctx)
1328+defer r.Close()
1329+if err != nil {
1330+t.Fatalf("opening reading: %v", err)
1331+ }
1332+wantLen := 3 * MiB
1333+got, err := io.ReadAll(r)
1334+if n := len(got); n != wantLen {
1335+t.Fatalf("expected to read %d bytes, but got %d (%v)", wantLen, n, err)
1336+ }
1337+if diff := cmp.Diff(got, content); diff != "" {
1338+t.Errorf("checking written content: got(-), want(+):\n%s", diff)
1339+ }
1340+// Check expected content type.
1341+if got, want := r.Attrs.ContentType, tc.wantContentType; got != want {
1342+t.Errorf("content type: got %v, want %v", got, want)
1343+ }
1344+ })
1345+ }
1346+1347+ })
1348+}
1349+12381350func TestListNotificationsEmulated(t *testing.T) {
12391351transportClientTest(skipGRPC("notifications not implemented"), t, func(t *testing.T, ctx context.Context, project, bucket string, client storageClient) {
12401352// Populate test object.