@@ -26,6 +26,7 @@ import (
26
26
"net/http"
27
27
"net/url"
28
28
"os"
29
+ "slices"
29
30
"strconv"
30
31
"strings"
31
32
"testing"
@@ -904,6 +905,7 @@ func TestOpenWriterEmulated(t *testing.T) {
904
905
setError : func (_ error ) {}, // no-op
905
906
progress : func (_ int64 ) {}, // no-op
906
907
setObj : func (o * ObjectAttrs ) { gotAttrs = o },
908
+ setFlush : func (f func () (int64 , error )) {},
907
909
}
908
910
pw , err := client .OpenWriter (params )
909
911
if err != nil {
@@ -997,7 +999,7 @@ func TestOpenAppendableWriterEmulated(t *testing.T) {
997
999
})
998
1000
}
999
1001
1000
- func TestOpenAppendableWriterMultipleFlushesEmulated (t * testing.T ) {
1002
+ func TestOpenAppendableWriterMultipleChunksEmulated (t * testing.T ) {
1001
1003
transportClientTest (skipHTTP ("appends only supported via gRPC" ), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1002
1004
// Populate test data.
1003
1005
_ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {
@@ -1052,6 +1054,187 @@ func TestOpenAppendableWriterMultipleFlushesEmulated(t *testing.T) {
1052
1054
})
1053
1055
}
1054
1056
1057
+ func TestWriterFlushEmulated (t * testing.T ) {
1058
+ transportClientTest (skipHTTP ("appends only supported via gRPC" ), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1059
+ // Populate test data.
1060
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {
1061
+ Name : bucket ,
1062
+ }, nil )
1063
+ if err != nil {
1064
+ t .Fatalf ("client.CreateBucket: %v" , err )
1065
+ }
1066
+ prefix := time .Now ().Nanosecond ()
1067
+ objName := fmt .Sprintf ("%d-object-%d" , prefix , time .Now ().Nanosecond ())
1068
+
1069
+ vc := & Client {tc : client }
1070
+ w := vc .Bucket (bucket ).Object (objName ).NewWriter (ctx )
1071
+ w .Append = true
1072
+ w .ChunkSize = 3 * MiB
1073
+ var gotOffsets []int64
1074
+ w .ProgressFunc = func (offset int64 ) {
1075
+ gotOffsets = append (gotOffsets , offset )
1076
+ }
1077
+ // Flush at 0 and at a range of values, both aligned with ChunkSize and where explicit flushes are expected.
1078
+ wantOffsets := []int64 {0 , 3 * MiB , 4 * MiB + 1024 , 4 * MiB + 2048 , 4 * MiB + 3072 , 6 * MiB + 4096 , 9 * MiB }
1079
+
1080
+ // Flush first with no data since this is a special case to test.
1081
+ off , err := w .Flush ()
1082
+ if err != nil {
1083
+ t .Errorf ("flushing at 0: %v" , err )
1084
+ }
1085
+ if off != 0 {
1086
+ t .Errorf ("flushing at 0: got %v offset, want 0" , off )
1087
+ }
1088
+
1089
+ // Write first 4 MiB data, expect progress every 3 MiB.
1090
+ _ , err = w .Write (randomBytes9MiB [0 : 4 * MiB ])
1091
+ if err != nil {
1092
+ t .Fatalf ("writing test data: got %v; want ok" , err )
1093
+ }
1094
+
1095
+ // Write another 1KiB three times and do a Flush each time.
1096
+ for i := 0 ; i < 3 ; i ++ {
1097
+ start := int64 (4 * MiB + i * 1024 )
1098
+ end := int64 (4 * MiB + (i + 1 )* 1024 )
1099
+ n , err := w .Write (randomBytes9MiB [start :end ])
1100
+ if err != nil {
1101
+ t .Fatalf ("writing 1k data: got %v; want ok" , err )
1102
+ }
1103
+ if n != 1024 {
1104
+ t .Errorf ("writing 1k data: got %v bytes written, want %v" , n , 1024 )
1105
+ }
1106
+ off , err := w .Flush ()
1107
+
1108
+ if err != nil {
1109
+ t .Fatalf ("flushing 1k data: got %v; want ok" , err )
1110
+ }
1111
+ if off != end {
1112
+ t .Errorf ("flushing 1k data: got %v bytes committed, want %v" , off , end )
1113
+ }
1114
+ }
1115
+
1116
+ // Do one more Flush that would require multiple messages (over 2 MiB).
1117
+ n , err := w .Write (randomBytes9MiB [4 * MiB + 3072 : 6 * MiB + 4096 ])
1118
+ if err != nil {
1119
+ t .Fatalf ("writing 2MiB + 1k data: got %v; want ok" , err )
1120
+ }
1121
+ if n != 2 * MiB + 1024 {
1122
+ t .Errorf ("writing 2 MiB + 1k data: got %v bytes written, want %v" , n , 2 * MiB + 1024 )
1123
+ }
1124
+ off , err = w .Flush ()
1125
+ if err != nil {
1126
+ t .Fatalf ("flushing 2 MiB + 1k data: got %v; want ok" , err )
1127
+ }
1128
+ if off != 6 * MiB + 4096 {
1129
+ t .Errorf ("flushing 2 MiB + 1k data: got %v bytes committed, want %v" , off , 6 * MiB + 4096 )
1130
+ }
1131
+
1132
+ // Do one more zero-byte flush; expect noop for this.
1133
+ off , err = w .Flush ()
1134
+ if err != nil {
1135
+ t .Fatalf ("flushing 0b data: got %v; want ok" , err )
1136
+ }
1137
+ if off != 6 * MiB + 4096 {
1138
+ t .Errorf ("flushing 0b data: got %v bytes committed, want %v" , off , 6 * MiB + 4096 )
1139
+ }
1140
+
1141
+ // Write remainder of data and close the writer.
1142
+ if _ , err := w .Write (randomBytes9MiB [6 * MiB + 4096 :]); err != nil {
1143
+ t .Fatalf ("writing remaining data: got %v; want ok" , err )
1144
+ }
1145
+ if err := w .Close (); err != nil {
1146
+ t .Fatalf ("closing writer: %v" , err )
1147
+ }
1148
+
1149
+ // Check that Flush after close fails.
1150
+ if _ , err = w .Flush (); err == nil {
1151
+ t .Errorf ("flush: expected error after close, got nil" )
1152
+ }
1153
+
1154
+ // Check offsets
1155
+ if ! slices .Equal (gotOffsets , wantOffsets ) {
1156
+ t .Errorf ("progress offsets: got %v, want %v" , gotOffsets , wantOffsets )
1157
+ }
1158
+
1159
+ // Download object and check data
1160
+ r , err := veneerClient .Bucket (bucket ).Object (objName ).NewReader (ctx )
1161
+ defer r .Close ()
1162
+ if err != nil {
1163
+ t .Fatalf ("opening reading: %v" , err )
1164
+ }
1165
+ wantLen := len (randomBytes9MiB )
1166
+ got , err := io .ReadAll (r )
1167
+ if n := len (got ); n != wantLen {
1168
+ t .Fatalf ("expected to read %d bytes, but got %d (%v)" , wantLen , n , err )
1169
+ }
1170
+ if diff := cmp .Diff (got , randomBytes9MiB ); diff != "" {
1171
+ t .Fatalf ("checking written content: got(-), want(+):\n %s" , diff )
1172
+ }
1173
+ })
1174
+ }
1175
+
1176
+ func TestWriterFlushAtCloseEmulated (t * testing.T ) {
1177
+ transportClientTest (skipHTTP ("appends only supported via gRPC" ), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1178
+ // Populate test data.
1179
+ _ , err := client .CreateBucket (ctx , project , bucket , & BucketAttrs {
1180
+ Name : bucket ,
1181
+ }, nil )
1182
+ if err != nil {
1183
+ t .Fatalf ("client.CreateBucket: %v" , err )
1184
+ }
1185
+ prefix := time .Now ().Nanosecond ()
1186
+ objName := fmt .Sprintf ("%d-object-%d" , prefix , time .Now ().Nanosecond ())
1187
+
1188
+ vc := & Client {tc : client }
1189
+ w := vc .Bucket (bucket ).Object (objName ).NewWriter (ctx )
1190
+ w .Append = true
1191
+ w .ChunkSize = MiB
1192
+ var gotOffsets []int64
1193
+ w .ProgressFunc = func (offset int64 ) {
1194
+ gotOffsets = append (gotOffsets , offset )
1195
+ }
1196
+ wantOffsets := []int64 {MiB , 2 * MiB , 3 * MiB }
1197
+
1198
+ // Test Flush right before close only.
1199
+ n , err := w .Write (randomBytes3MiB )
1200
+ if err != nil {
1201
+ t .Fatalf ("writing data: got %v; want ok" , err )
1202
+ }
1203
+ if n != 3 * MiB {
1204
+ t .Errorf ("writing data: got %v bytes written, want %v" , n , 3 * MiB )
1205
+ }
1206
+ off , err := w .Flush ()
1207
+ if err != nil {
1208
+ t .Fatalf ("flush: got %v; want ok" , err )
1209
+ }
1210
+ if off != 3 * MiB {
1211
+ t .Errorf ("flushing data: got %v bytes written, want %v" , off , 3 * MiB )
1212
+ }
1213
+ if err := w .Close (); err != nil {
1214
+ t .Fatalf ("closing writer: %v" , err )
1215
+ }
1216
+ // Check offsets
1217
+ if ! slices .Equal (gotOffsets , wantOffsets ) {
1218
+ t .Errorf ("progress offsets: got %v, want %v" , gotOffsets , wantOffsets )
1219
+ }
1220
+
1221
+ // Download object and check data
1222
+ r , err := veneerClient .Bucket (bucket ).Object (objName ).NewReader (ctx )
1223
+ defer r .Close ()
1224
+ if err != nil {
1225
+ t .Fatalf ("opening reading: %v" , err )
1226
+ }
1227
+ wantLen := 3 * MiB
1228
+ got , err := io .ReadAll (r )
1229
+ if n := len (got ); n != wantLen {
1230
+ t .Fatalf ("expected to read %d bytes, but got %d (%v)" , wantLen , n , err )
1231
+ }
1232
+ if diff := cmp .Diff (got , randomBytes3MiB ); diff != "" {
1233
+ t .Fatalf ("checking written content: got(-), want(+):\n %s" , diff )
1234
+ }
1235
+ })
1236
+ }
1237
+
1055
1238
func TestListNotificationsEmulated (t * testing.T ) {
1056
1239
transportClientTest (skipGRPC ("notifications not implemented" ), t , func (t * testing.T , ctx context.Context , project , bucket string , client storageClient ) {
1057
1240
// Populate test object.
@@ -1705,6 +1888,7 @@ func TestObjectConditionsEmulated(t *testing.T) {
1705
1888
},
1706
1889
progress : nil ,
1707
1890
setObj : nil ,
1891
+ setFlush : func (f func () (int64 , error )) {},
1708
1892
})
1709
1893
return err
1710
1894
},
@@ -2074,6 +2258,7 @@ func TestWriterChunkTransferTimeoutEmulated(t *testing.T) {
2074
2258
setError : func (_ error ) {}, // no-op
2075
2259
progress : func (_ int64 ) {}, // no-op
2076
2260
setObj : func (o * ObjectAttrs ) { gotAttrs = o },
2261
+ setFlush : func (func () (int64 , error )) {}, // no-op
2077
2262
}
2078
2263
2079
2264
pw , err := client .OpenWriter (params )
@@ -2168,6 +2353,7 @@ func TestWriterChunkRetryDeadlineEmulated(t *testing.T) {
2168
2353
setError : func (_ error ) {}, // no-op
2169
2354
progress : func (_ int64 ) {}, // no-op
2170
2355
setObj : func (_ * ObjectAttrs ) {},
2356
+ setFlush : func (f func () (int64 , error )) {},
2171
2357
}
2172
2358
2173
2359
pw , err := client .OpenWriter (params , & idempotentOption {true })
0 commit comments