Skip to content

Commit 3575f71

Browse files
committed
Add support for zero chunk size values with tests
1 parent 0ec3e5c commit 3575f71

File tree

6 files changed

+117
-22
lines changed

6 files changed

+117
-22
lines changed

gcsfs/file.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ func NewGcsFileFromOldFH(
9090
return res
9191
}
9292

93-
// SetUploadChunkSizeByte The default chunk size for uploading files to GCS is 16MB.
94-
// This buffer size may use memory excessively. You can adjust it to a value that is a multiple of 256KiB.
95-
func (o *GcsFile) SetUploadChunkSizeByte(size int) {
93+
// SetUploadChunkSizeByte sets the chunk size for uploads.
94+
// To disable chunking, set this to 0.
95+
// For custom sizes, GCS requires multiples of 256KiB (262144 bytes).
96+
func (o *GcsFile) SetUploadChunkSizeByte(size *int) {
9697
o.resource.uploadChunkSizeByte = size
9798
}
9899

gcsfs/file_resource.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type gcsFileResource struct {
4646
name string
4747
fileMode os.FileMode
4848

49-
uploadChunkSizeByte int
49+
uploadChunkSizeByte *int
5050

5151
currentGcsSize int64
5252
offset int64
@@ -188,13 +188,8 @@ func (o *gcsFileResource) WriteAt(b []byte, off int64) (n int, err error) {
188188
// It will however require a download and upload of the original file but it
189189
// can't be avoided if we should support seek-write-operations on GCS.
190190

191-
chunkSize := o.fs.UploadChunkSizeByte
192-
if o.uploadChunkSizeByte != 0 {
193-
chunkSize = o.uploadChunkSizeByte
194-
}
195-
196-
if chunkSize != 0 {
197-
w.SetChunkSize(chunkSize)
191+
if size, ok := o.getEffectiveChunkSize(); ok {
192+
w.SetChunkSize(size)
198193
}
199194

200195
objAttrs, err := o.obj.Attrs(o.ctx)
@@ -235,6 +230,16 @@ func (o *gcsFileResource) WriteAt(b []byte, off int64) (n int, err error) {
235230
return written, err
236231
}
237232

233+
func (o *gcsFileResource) getEffectiveChunkSize() (int, bool) {
234+
if o.uploadChunkSizeByte != nil {
235+
return *o.uploadChunkSizeByte, true
236+
}
237+
if o.fs.UploadChunkSizeByte != nil {
238+
return *o.fs.UploadChunkSizeByte, true
239+
}
240+
return 0, false
241+
}
242+
238243
func min(x, y int) int {
239244
if x < y {
240245
return x

gcsfs/fs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type Fs struct {
4343
rawGcsObjects map[string]*GcsFile
4444

4545
autoRemoveEmptyFolders bool // trigger for creating "virtual folders" (not required by GCSs)
46-
UploadChunkSizeByte int
46+
UploadChunkSizeByte *int
4747
}
4848

4949
func NewGcsFs(ctx context.Context, client stiface.Client) *Fs {
@@ -63,7 +63,7 @@ func NewGcsFsWithSeparator(ctx context.Context, client stiface.Client, folderSep
6363

6464
// SetUploadChunkSizeByte The default chunk size for uploading files to GCS is 16MB.
6565
// This buffer size may use memory excessively. You can adjust it to a value that is a multiple of 256KiB.
66-
func (fs *Fs) SetUploadChunkSizeByte(uploadChunkSize int) {
66+
func (fs *Fs) SetUploadChunkSizeByte(uploadChunkSize *int) {
6767
fs.UploadChunkSizeByte = uploadChunkSize
6868
}
6969

gcsfs/gcs.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ func (fs *GcsFs) Name() string {
8585
return fs.source.Name()
8686
}
8787

88-
func (fs *GcsFs) SetUploadChunkSizeByte(size int) {
88+
// SetUploadChunkSizeByte sets the chunk size for uploads.
89+
// To disable chunking, set this to 0.
90+
// For custom sizes, GCS requires multiples of 256KiB (262144 bytes).
91+
func (fs *GcsFs) SetUploadChunkSizeByte(size *int) {
8992
fs.source.SetUploadChunkSizeByte(size)
9093
}
9194

gcsfs/gcs_mocks.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import (
1818
"strings"
1919

2020
"cloud.google.com/go/storage"
21+
"github.com/spf13/afero"
2122
"google.golang.org/api/iterator"
2223

23-
"github.com/spf13/afero"
2424
"github.com/spf13/afero/gcsfs/internal/stiface"
2525
)
2626

@@ -31,15 +31,28 @@ func normSeparators(s string) string {
3131

3232
type clientMock struct {
3333
stiface.Client
34-
fs afero.Fs
34+
fs afero.Fs
35+
buckets map[string]*bucketMock
3536
}
3637

3738
func newClientMock() *clientMock {
38-
return &clientMock{fs: afero.NewMemMapFs()}
39+
return &clientMock{
40+
fs: afero.NewMemMapFs(),
41+
buckets: make(map[string]*bucketMock),
42+
}
3943
}
4044

4145
func (m *clientMock) Bucket(name string) stiface.BucketHandle {
42-
return &bucketMock{bucketName: name, fs: m.fs}
46+
if b, ok := m.buckets[name]; ok {
47+
return b
48+
}
49+
b := &bucketMock{
50+
bucketName: name,
51+
fs: m.fs,
52+
objects: make(map[string]*objectMock),
53+
}
54+
m.buckets[name] = b
55+
return b
4356
}
4457

4558
type bucketMock struct {
@@ -48,14 +61,24 @@ type bucketMock struct {
4861
bucketName string
4962

5063
fs afero.Fs
64+
65+
objects map[string]*objectMock
5166
}
5267

5368
func (m *bucketMock) Attrs(context.Context) (*storage.BucketAttrs, error) {
5469
return &storage.BucketAttrs{}, nil
5570
}
5671

5772
func (m *bucketMock) Object(name string) stiface.ObjectHandle {
58-
return &objectMock{name: name, fs: m.fs}
73+
if m.objects == nil {
74+
m.objects = make(map[string]*objectMock)
75+
}
76+
if obj, ok := m.objects[name]; ok {
77+
return obj
78+
}
79+
obj := &objectMock{name: name, fs: m.fs, lastChunkSize: -1}
80+
m.objects[name] = obj
81+
return obj
5982
}
6083

6184
func (m *bucketMock) Objects(_ context.Context, q *storage.Query) (it stiface.ObjectIterator) {
@@ -67,10 +90,12 @@ type objectMock struct {
6790

6891
name string
6992
fs afero.Fs
93+
94+
lastChunkSize int // Track this for testing
7095
}
7196

7297
func (o *objectMock) NewWriter(_ context.Context) stiface.Writer {
73-
return &writerMock{name: o.name, fs: o.fs}
98+
return &writerMock{name: o.name, fs: o.fs, parent: o}
7499
}
75100

76101
func (o *objectMock) NewRangeReader(
@@ -146,12 +171,19 @@ func (o *objectMock) Attrs(_ context.Context) (*storage.ObjectAttrs, error) {
146171
type writerMock struct {
147172
stiface.Writer
148173

149-
name string
150-
fs afero.Fs
174+
name string
175+
fs afero.Fs
176+
parent *objectMock
151177

152178
file afero.File
153179
}
154180

181+
func (w *writerMock) SetChunkSize(s int) {
182+
if w.parent != nil {
183+
w.parent.lastChunkSize = s
184+
}
185+
}
186+
155187
func (w *writerMock) Write(p []byte) (n int, err error) {
156188
if w.name == "" {
157189
return 0, ErrEmptyObjectName

gcsfs/gcs_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,3 +861,57 @@ func TestGcsRemoveAll(t *testing.T) {
861861
}
862862
})
863863
}
864+
865+
func TestUploadChunkSize(t *testing.T) {
866+
tests := []struct {
867+
testName string
868+
chunkSizeAssignedVal *int
869+
expectedResultChunkSize int
870+
}{
871+
{
872+
testName: "1MB setting",
873+
874+
chunkSizeAssignedVal: func() *int { val := 1 * 1024 * 1024; return &val }(),
875+
876+
expectedResultChunkSize: 1 * 1024 * 1024,
877+
},
878+
{
879+
testName: "0 (disabled chunking)",
880+
881+
chunkSizeAssignedVal: func() *int { val := 0; return &val }(),
882+
883+
expectedResultChunkSize: 0,
884+
},
885+
{
886+
testName: "nil (GCS default)",
887+
888+
chunkSizeAssignedVal: nil,
889+
expectedResultChunkSize: -1, // Sentinel value for "never called"
890+
},
891+
}
892+
893+
for i, tt := range tests {
894+
t.Run(tt.testName, func(t *testing.T) {
895+
// Create a fresh isolated mock for this sub-test
896+
ctx := context.Background()
897+
mockClient := newClientMock()
898+
fs := &GcsFs{NewGcsFs(ctx, mockClient)}
899+
900+
fs.SetUploadChunkSizeByte(tt.chunkSizeAssignedVal)
901+
902+
fileName := fmt.Sprintf("test-chunk-%d.txt", i)
903+
fullObjectName := filepath.Join(bucketName, fileName)
904+
905+
f, _ := fs.Create(fullObjectName)
906+
907+
// Trigger writer
908+
f.Write([]byte("data"))
909+
910+
obj := mockClient.Bucket(bucketName).Object(fileName).(*objectMock)
911+
912+
if obj.lastChunkSize != tt.expectedResultChunkSize {
913+
t.Errorf("Expected %d, got %d", tt.expectedResultChunkSize, obj.lastChunkSize)
914+
}
915+
})
916+
}
917+
}

0 commit comments

Comments
 (0)