Skip to content

Commit fb17dc0

Browse files
committed
Sized ReadAll, multipart upload list tests
1 parent a1f6dc1 commit fb17dc0

10 files changed

+336
-83
lines changed

backend.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ type Backend interface {
7474

7575
// PutObject should assume that the key is valid. The map containing meta
7676
// may be nil.
77-
PutObject(bucketName, key string, meta map[string]string, input io.Reader) error
77+
//
78+
// The size can be used if the backend needs to read the whole reader; use
79+
// gofakes3.ReadAll() for this job rather than ioutil.ReadAll().
80+
PutObject(bucketName, key string, meta map[string]string, input io.Reader, size int64) error
7881

7982
DeleteMulti(bucketName string, objects ...string) (DeleteResult, error)
8083
}

backend/s3bolt/backend.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/hex"
77
"fmt"
88
"io"
9-
"io/ioutil"
109
"log"
1110

1211
"github.com/boltdb/bolt"
@@ -277,8 +276,8 @@ func (db *Backend) GetObject(bucketName, objectName string) (*gofakes3.Object, e
277276
return t.Object(), nil
278277
}
279278

280-
func (db *Backend) PutObject(bucketName, objectName string, meta map[string]string, input io.Reader) error {
281-
bts, err := ioutil.ReadAll(input)
279+
func (db *Backend) PutObject(bucketName, objectName string, meta map[string]string, input io.Reader, size int64) error {
280+
bts, err := gofakes3.ReadAll(input, size)
282281
if err != nil {
283282
return err
284283
}

backend/s3mem/backend.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"crypto/md5"
66
"encoding/hex"
77
"io"
8-
"io/ioutil"
98
"sync"
109

1110
"github.com/johannesboyne/gofakes3"
@@ -170,7 +169,7 @@ func (db *Backend) GetObject(bucketName, objectName string) (*gofakes3.Object, e
170169
}, nil
171170
}
172171

173-
func (db *Backend) PutObject(bucketName, objectName string, meta map[string]string, input io.Reader) error {
172+
func (db *Backend) PutObject(bucketName, objectName string, meta map[string]string, input io.Reader, size int64) error {
174173
db.lock.Lock()
175174
defer db.lock.Unlock()
176175

@@ -179,7 +178,7 @@ func (db *Backend) PutObject(bucketName, objectName string, meta map[string]stri
179178
return gofakes3.BucketNotFound(bucketName)
180179
}
181180

182-
bts, err := ioutil.ReadAll(input)
181+
bts, err := gofakes3.ReadAll(input, size)
183182
if err != nil {
184183
return err
185184
}

gofakes3.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ func (g *GoFakeS3) createObjectBrowserUpload(bucket string, w http.ResponseWrite
287287
return ResourceError(ErrKeyTooLong, key)
288288
}
289289

290-
if err := g.storage.PutObject(bucket, key, meta, infile); err != nil {
290+
if err := g.storage.PutObject(bucket, key, meta, infile, fileHeader.Size); err != nil {
291291
return err
292292
}
293293

@@ -310,6 +310,11 @@ func (g *GoFakeS3) createObject(bucket, object string, w http.ResponseWriter, r
310310
return err
311311
}
312312

313+
size, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
314+
if err != nil || size <= 0 {
315+
return ErrMissingContentLength
316+
}
317+
313318
if len(object) > KeySizeLimit {
314319
return ResourceError(ErrKeyTooLong, object)
315320
}
@@ -327,7 +332,7 @@ func (g *GoFakeS3) createObject(bucket, object string, w http.ResponseWriter, r
327332
}
328333
}
329334

330-
if err := g.storage.PutObject(bucket, object, meta, rdr); err != nil {
335+
if err := g.storage.PutObject(bucket, object, meta, rdr, size); err != nil {
331336
return err
332337
}
333338

@@ -445,8 +450,8 @@ func (g *GoFakeS3) putMultipartUploadPart(bucket, object string, uploadID Upload
445450
return ErrInvalidPart
446451
}
447452

448-
// FIXME: this could be a global check.
449-
if r.Header.Get("Content-Length") == "" {
453+
size, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
454+
if err != nil || size <= 0 {
450455
return ErrMissingContentLength
451456
}
452457

@@ -473,7 +478,7 @@ func (g *GoFakeS3) putMultipartUploadPart(bucket, object string, uploadID Upload
473478
}
474479
}
475480

476-
body, err := ioutil.ReadAll(rdr)
481+
body, err := ReadAll(rdr, size)
477482
if err != nil {
478483
return err
479484
}
@@ -521,7 +526,7 @@ func (g *GoFakeS3) completeMultipartUpload(bucket, object string, uploadID Uploa
521526
return err
522527
}
523528

524-
if err := g.storage.PutObject(bucket, object, upload.Meta, bytes.NewReader(fileBody)); err != nil {
529+
if err := g.storage.PutObject(bucket, object, upload.Meta, bytes.NewReader(fileBody), int64(len(fileBody))); err != nil {
525530
return err
526531
}
527532

@@ -541,6 +546,9 @@ func (g *GoFakeS3) listMultipartUploads(bucket string, w http.ResponseWriter, r
541546
if err != nil {
542547
return ErrInvalidURI
543548
}
549+
if maxUploads == 0 {
550+
maxUploads = DefaultMaxUploads
551+
}
544552

545553
out, err := g.uploader.List(bucket, marker, prefix, maxUploads)
546554
if err != nil {

init_test.go

+166-20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"encoding/hex"
1212
"flag"
1313
"fmt"
14+
"io"
1415
"io/ioutil"
1516
"log"
1617
"net/http/httptest"
@@ -26,11 +27,18 @@ import (
2627
"github.com/aws/aws-sdk-go/aws/credentials"
2728
"github.com/aws/aws-sdk-go/aws/session"
2829
"github.com/aws/aws-sdk-go/service/s3"
30+
"github.com/aws/aws-sdk-go/service/s3/s3manager"
2931
"github.com/johannesboyne/gofakes3"
3032
"github.com/johannesboyne/gofakes3/backend/s3mem"
3133
)
3234

33-
const defaultBucket = "mybucket"
35+
const (
36+
defaultBucket = "mybucket"
37+
38+
// docs say MB, client SDK uses MiB, gofakes3 assumes MB as the lowest common denominator
39+
// to accept, but we need to satisfy the client SDK in the test suite so this is MiB:
40+
defaultUploadPartSize = 5 * 1024 * 1024
41+
)
3442

3543
var (
3644
logFile string
@@ -196,7 +204,7 @@ func (ts *testServer) objectExists(bucket, key string) bool {
196204

197205
func (ts *testServer) putString(bucket, key string, meta map[string]string, in string) {
198206
ts.Helper()
199-
ts.OK(ts.backend.PutObject(bucket, key, meta, strings.NewReader(in)))
207+
ts.OK(ts.backend.PutObject(bucket, key, meta, strings.NewReader(in), int64(len(in))))
200208
}
201209

202210
func (ts *testServer) objectAsString(bucket, key string) string {
@@ -243,19 +251,146 @@ func (ts *testServer) assertLs(bucket string, prefix string, expectedPrefixes []
243251
ls.assertContents(ts.TT, expectedPrefixes, expectedObjects)
244252
}
245253

254+
type multipartUploadOptions struct {
255+
partSize int64
256+
}
257+
258+
func (ts *testServer) assertMultipartUpload(bucket, object string, body interface{}, options *multipartUploadOptions) {
259+
if options == nil {
260+
options = &multipartUploadOptions{}
261+
}
262+
if options.partSize <= 0 {
263+
options.partSize = defaultUploadPartSize
264+
}
265+
266+
s3 := ts.s3Client()
267+
uploader := s3manager.NewUploaderWithClient(s3)
268+
269+
contents := readBody(ts.TT, body)
270+
upParams := &s3manager.UploadInput{
271+
Bucket: aws.String(defaultBucket),
272+
Key: aws.String("uploadtest"),
273+
Body: bytes.NewReader(contents),
274+
ContentMD5: aws.String(hashMD5Bytes(contents).Base64()),
275+
}
276+
277+
out, err := uploader.Upload(upParams, func(u *s3manager.Uploader) {
278+
u.LeavePartsOnError = true
279+
u.PartSize = options.partSize
280+
})
281+
ts.OK(err)
282+
_ = out
283+
284+
ts.assertObject(defaultBucket, "uploadtest", nil, body)
285+
}
286+
287+
func (ts *testServer) createMultipartUpload(bucket, object string, meta map[string]string) (uploadID string) {
288+
svc := ts.s3Client()
289+
mpu, err := svc.CreateMultipartUpload(&s3.CreateMultipartUploadInput{
290+
Bucket: aws.String(bucket),
291+
Key: aws.String(object),
292+
})
293+
ts.OK(err)
294+
return *mpu.UploadId
295+
}
296+
297+
// assertListMultipartUploads
298+
//
299+
// If marker is not an empty string, it should be in the format "[<object>][/<uploadID>]".
300+
// Each item in expectedUploads must be in the format "<object>/<uploadID>".
301+
func (ts *testServer) assertListMultipartUploads(
302+
bucket string,
303+
marker string,
304+
prefix *gofakes3.Prefix,
305+
limit int64,
306+
expectedUploads ...string,
307+
) {
308+
ts.Helper()
309+
svc := ts.s3Client()
310+
rq := &s3.ListMultipartUploadsInput{
311+
Bucket: aws.String(bucket),
312+
MaxUploads: aws.Int64(limit),
313+
}
314+
315+
var expectedKeyMarker, expectedUploadIDMarker string
316+
if marker != "" {
317+
parts := strings.SplitN(marker, "/", 2)
318+
expectedKeyMarker = parts[0]
319+
if len(parts) == 2 {
320+
expectedUploadIDMarker = parts[1]
321+
}
322+
rq.KeyMarker = aws.String(expectedKeyMarker)
323+
rq.UploadIdMarker = aws.String(expectedUploadIDMarker)
324+
}
325+
326+
var expectedPrefix, expectedDelimiter string
327+
if prefix != nil {
328+
rq.Prefix = aws.String(prefix.Prefix)
329+
rq.Delimiter = aws.String(prefix.Delimiter)
330+
expectedPrefix, expectedDelimiter = prefix.Prefix, prefix.Delimiter
331+
}
332+
333+
rs, err := svc.ListMultipartUploads(rq)
334+
ts.OK(err)
335+
336+
{ // assert response fields match input
337+
var foundPrefix, foundDelimiter, foundKeyMarker, foundUploadIDMarker string
338+
if rs.Delimiter != nil {
339+
foundDelimiter = *rs.Delimiter
340+
}
341+
if rs.Prefix != nil {
342+
foundPrefix = *rs.Prefix
343+
}
344+
if rs.KeyMarker != nil {
345+
foundKeyMarker = *rs.KeyMarker
346+
}
347+
if rs.UploadIdMarker != nil {
348+
foundUploadIDMarker = *rs.UploadIdMarker
349+
}
350+
if foundPrefix != expectedPrefix {
351+
ts.Fatal("unexpected prefix", foundPrefix, "!=", expectedPrefix)
352+
}
353+
if foundDelimiter != expectedDelimiter {
354+
ts.Fatal("unexpected delimiter", foundDelimiter, "!=", expectedDelimiter)
355+
}
356+
if foundKeyMarker != expectedKeyMarker {
357+
ts.Fatal("unexpected key marker", foundKeyMarker, "!=", expectedKeyMarker)
358+
}
359+
if foundUploadIDMarker != expectedUploadIDMarker {
360+
ts.Fatal("unexpected upload ID marker", foundUploadIDMarker, "!=", expectedUploadIDMarker)
361+
}
362+
var foundUploads int64
363+
if rs.MaxUploads != nil {
364+
foundUploads = *rs.MaxUploads
365+
}
366+
if limit > 0 && foundUploads != limit {
367+
ts.Fatal("unexpected max uploads", foundUploads, "!=", limit)
368+
}
369+
}
370+
371+
var foundUploads []string
372+
for _, up := range rs.Uploads {
373+
foundUploads = append(foundUploads, fmt.Sprintf("%s/%s", *up.Key, *up.UploadId))
374+
}
375+
376+
if !reflect.DeepEqual(foundUploads, expectedUploads) {
377+
ts.Fatal("upload list mismatch:", foundUploads, "!=", expectedUploads)
378+
}
379+
}
380+
246381
// If meta is nil, the metadata is not checked.
247382
// If meta is map[string]string{}, it is checked against the empty map.
248383
//
249-
// If contents is a string or a []byte, it is compared against the object's contents,
250-
// otherwise a panic occurs.
384+
// If contents is a string, a []byte or an io.Reader, it is compared against
385+
// the object's contents, otherwise a panic occurs.
251386
func (ts *testServer) assertObject(bucket string, object string, meta map[string]string, contents interface{}) {
252387
ts.Helper()
253388

254389
obj, err := ts.backend.GetObject(bucket, object)
255390
ts.OK(err)
256391
defer obj.Contents.Close()
257392

258-
data, err := ioutil.ReadAll(obj.Contents)
393+
data, err := gofakes3.ReadAll(obj.Contents, obj.Size)
259394
ts.OK(err)
260395

261396
if meta != nil {
@@ -264,17 +399,7 @@ func (ts *testServer) assertObject(bucket string, object string, meta map[string
264399
}
265400
}
266401

267-
var checkContents []byte
268-
switch contents := contents.(type) {
269-
case nil:
270-
case string:
271-
checkContents = []byte(contents)
272-
case []byte:
273-
checkContents = contents
274-
default:
275-
panic("unexpected contents")
276-
}
277-
402+
checkContents := readBody(ts.TT, contents)
278403
if !bytes.Equal(checkContents, data) {
279404
ts.Fatal("data mismatch") // FIXME: more detail
280405
}
@@ -288,6 +413,12 @@ func (ts *testServer) Close() {
288413
ts.server.Close()
289414
}
290415

416+
func hashMD5Bytes(body []byte) hashValue {
417+
h := md5.New()
418+
h.Write(body)
419+
return hashValue(h.Sum(nil))
420+
}
421+
291422
type hashValue []byte
292423

293424
func (h hashValue) Base64() string { return base64.StdEncoding.EncodeToString(h) }
@@ -298,7 +429,7 @@ var (
298429
randMu sync.Mutex
299430
)
300431

301-
func randomFileBody(size int64) ([]byte, hashValue) {
432+
func randomFileBody(size int64) []byte {
302433
randMu.Lock()
303434
defer randMu.Unlock()
304435

@@ -320,7 +451,22 @@ func randomFileBody(size int64) ([]byte, hashValue) {
320451
}
321452

322453
b = b[:size]
323-
h := md5.New()
324-
h.Write(b)
325-
return b, hashValue(h.Sum(nil))
454+
return b
455+
}
456+
457+
func readBody(tt gofakes3.TT, body interface{}) []byte {
458+
switch body := body.(type) {
459+
case nil:
460+
return []byte{}
461+
case string:
462+
return []byte(body)
463+
case []byte:
464+
return body
465+
case io.Reader:
466+
out, err := ioutil.ReadAll(body)
467+
tt.OK(err)
468+
return out
469+
default:
470+
panic("unexpected contents")
471+
}
326472
}

messages.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ type ListMultipartUploadsResult struct {
190190
// Sets the maximum number of multipart uploads, from 1 to 1,000, to return
191191
// in the response body. 1,000 is the maximum number of uploads that can be
192192
// returned in a response.
193-
MaxUploads string `xml:"MaxUploads,omitempty"`
193+
MaxUploads int64 `xml:"MaxUploads,omitempty"`
194194

195195
Delimiter string `xml:"Delimiter,omitempty"`
196196

0 commit comments

Comments
 (0)