@@ -1506,6 +1506,145 @@ func TestRetryReadStallEmulated(t *testing.T) {
15061506 }
15071507}
15081508
1509+ func TestWriterChunkTransferTimeoutEmulated (t * testing.T ) {
1510+ transportClientTest (skipGRPC ("service is not implemented" ), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1511+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {}, nil )
1512+ if err != nil {
1513+ t .Fatalf ("creating bucket: %v" , err )
1514+ }
1515+
1516+ chunkSize := 2 * 1024 * 1024 // 2 MiB
1517+ fileSize := 5 * 1024 * 1024 // 5 MiB
1518+ tests := []struct {
1519+ name string
1520+ instructions map [string ][]string
1521+ chunkTransferTimeout time.Duration
1522+ expectedSuccess bool
1523+ }{
1524+ {
1525+ name : "stall-on-first-chunk-with-chunk-transfer-timeout-zero" ,
1526+ instructions : map [string ][]string {
1527+ "storage.objects.insert" : {"stall-for-10s-after-1024K" },
1528+ },
1529+ chunkTransferTimeout : 0 ,
1530+ expectedSuccess : false ,
1531+ },
1532+ {
1533+ name : "stall-on-first-chunk-with-chunk-transfer-timeout-nonzero" ,
1534+ instructions : map [string ][]string {
1535+ "storage.objects.insert" : {"stall-for-10s-after-1024K" },
1536+ },
1537+ chunkTransferTimeout : 100 * time .Millisecond ,
1538+ expectedSuccess : true ,
1539+ },
1540+ {
1541+ name : "stall-on-second-chunk-with-chunk-transfer-timeout-zero" ,
1542+ instructions : map [string ][]string {
1543+ "storage.objects.insert" : {"stall-for-10s-after-3072K" },
1544+ },
1545+ chunkTransferTimeout : 0 ,
1546+ expectedSuccess : false ,
1547+ },
1548+ {
1549+ name : "stall-on-second-chunk-with-chunk-transfer-timeout-nonzero" ,
1550+ instructions : map [string ][]string {
1551+ "storage.objects.insert" : {"stall-for-10s-after-3072K" },
1552+ },
1553+ chunkTransferTimeout : 100 * time .Millisecond ,
1554+ expectedSuccess : true ,
1555+ },
1556+ {
1557+ name : "stall-on-first-chunk-twice-with-chunk-transfer-timeout-zero" ,
1558+ instructions : map [string ][]string {
1559+ "storage.objects.insert" : {"stall-for-10s-after-1024K" , "stall-for-10s-after-1024K" },
1560+ },
1561+ chunkTransferTimeout : 0 ,
1562+ expectedSuccess : false ,
1563+ },
1564+ {
1565+ name : "stall-on-first-chunk-twice-with-chunk-transfer-timeout-nonzero" ,
1566+ instructions : map [string ][]string {
1567+ "storage.objects.insert" : {"stall-for-10s-after-1024K" , "stall-for-10s-after-1024K" },
1568+ },
1569+ chunkTransferTimeout : 100 * time .Millisecond ,
1570+ expectedSuccess : true ,
1571+ },
1572+ }
1573+
1574+ for _ , tc := range tests {
1575+ t .Run (tc .name , func (t * testing.T ) {
1576+ testID := createRetryTest (t , client , tc .instructions )
1577+ var cancel context.CancelFunc
1578+ rCtx := callctx .SetHeaders (ctx , "x-retry-test-id" , testID )
1579+ rCtx , cancel = context .WithTimeout (rCtx , 1 * time .Second )
1580+ defer cancel ()
1581+
1582+ prefix := time .Now ().Nanosecond ()
1583+ want := & ObjectAttrs {
1584+ Bucket : bucket ,
1585+ Name : fmt .Sprintf ("%d-object-%d" , prefix , time .Now ().Nanosecond ()),
1586+ Generation : defaultGen ,
1587+ }
1588+
1589+ var gotAttrs * ObjectAttrs
1590+ params := & openWriterParams {
1591+ attrs : want ,
1592+ bucket : bucket ,
1593+ chunkSize : chunkSize ,
1594+ chunkTransferTimeout : tc .chunkTransferTimeout ,
1595+ ctx : rCtx ,
1596+ donec : make (chan struct {}),
1597+ setError : func (_ error ) {}, // no-op
1598+ progress : func (_ int64 ) {}, // no-op
1599+ setObj : func (o * ObjectAttrs ) { gotAttrs = o },
1600+ }
1601+
1602+ pw , err := client .OpenWriter (params )
1603+ if err != nil {
1604+ t .Fatalf ("failed to open writer: %v" , err )
1605+ }
1606+ buffer := bytes .Repeat ([]byte ("A" ), fileSize )
1607+ _ , err = pw .Write (buffer )
1608+ if tc .expectedSuccess {
1609+ if err != nil {
1610+ t .Fatalf ("failed to populate test data: %v" , err )
1611+ }
1612+ if err := pw .Close (); err != nil {
1613+ t .Fatalf ("closing object: %v" , err )
1614+ }
1615+ select {
1616+ case <- params .donec :
1617+ }
1618+ if gotAttrs == nil {
1619+ t .Fatalf ("Writer finished, but resulting object wasn't set" )
1620+ }
1621+ if diff := cmp .Diff (gotAttrs .Name , want .Name ); diff != "" {
1622+ t .Fatalf ("Resulting object name: got(-),want(+):\n %s" , diff )
1623+ }
1624+
1625+ r , err := veneerClient .Bucket (bucket ).Object (want .Name ).NewReader (ctx )
1626+ if err != nil {
1627+ t .Fatalf ("opening reading: %v" , err )
1628+ }
1629+ wantLen := len (buffer )
1630+ got := make ([]byte , wantLen )
1631+ n , err := r .Read (got )
1632+ if n != wantLen {
1633+ t .Fatalf ("expected to read %d bytes, but got %d" , wantLen , n )
1634+ }
1635+ if diff := cmp .Diff (got , buffer ); diff != "" {
1636+ t .Fatalf ("checking written content: got(-),want(+):\n %s" , diff )
1637+ }
1638+ } else {
1639+ if ! errors .Is (err , context .DeadlineExceeded ) {
1640+ t .Fatalf ("expected context deadline exceeded found %v" , err )
1641+ }
1642+ }
1643+ })
1644+ }
1645+ })
1646+ }
1647+
15091648// createRetryTest creates a bucket in the emulator and sets up a test using the
15101649// Retry Test API for the given instructions. This is intended for emulator tests
15111650// of retry behavior that are not covered by conformance tests.
0 commit comments