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+12381350

func TestListNotificationsEmulated(t *testing.T) {

12391351

transportClientTest(skipGRPC("notifications not implemented"), t, func(t *testing.T, ctx context.Context, project, bucket string, client storageClient) {

12401352

// Populate test object.