123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- // Copyright 2011 Google Inc. All rights reserved.
- // Use of this source code is governed by the Apache 2.0
- // license that can be found in the LICENSE file.
- package blobstore
- import (
- "io"
- "os"
- "strconv"
- "strings"
- "testing"
- "google.golang.org/appengine"
- "google.golang.org/appengine/internal/aetesting"
- pb "google.golang.org/appengine/internal/blobstore"
- )
- const rbs = readBufferSize
- func min(x, y int) int {
- if x < y {
- return x
- }
- return y
- }
- func fakeFetchData(req *pb.FetchDataRequest, res *pb.FetchDataResponse) error {
- i0 := int(*req.StartIndex)
- i1 := int(*req.EndIndex + 1) // Blobstore's end-indices are inclusive; Go's are exclusive.
- bk := *req.BlobKey
- if i := strings.Index(bk, "."); i != -1 {
- // Strip everything past the ".".
- bk = bk[:i]
- }
- switch bk {
- case "a14p":
- const s = "abcdefghijklmnop"
- i0 := min(len(s), i0)
- i1 := min(len(s), i1)
- res.Data = []byte(s[i0:i1])
- case "longBlob":
- res.Data = make([]byte, i1-i0)
- for i := range res.Data {
- res.Data[i] = 'A' + uint8(i0/rbs)
- i0++
- }
- }
- return nil
- }
- // step is one step of a readerTest.
- // It consists of a Reader method to call, the method arguments
- // (lenp, offset, whence) and the expected results.
- type step struct {
- method string
- lenp int
- offset int64
- whence int
- want string
- wantErr error
- }
- var readerTest = []struct {
- blobKey string
- step []step
- }{
- {"noSuchBlobKey", []step{
- {"Read", 8, 0, 0, "", io.EOF},
- }},
- {"a14p.0", []step{
- // Test basic reads.
- {"Read", 1, 0, 0, "a", nil},
- {"Read", 3, 0, 0, "bcd", nil},
- {"Read", 1, 0, 0, "e", nil},
- {"Read", 2, 0, 0, "fg", nil},
- // Test Seek.
- {"Seek", 0, 2, os.SEEK_SET, "2", nil},
- {"Read", 5, 0, 0, "cdefg", nil},
- {"Seek", 0, 2, os.SEEK_CUR, "9", nil},
- {"Read", 1, 0, 0, "j", nil},
- // Test reads up to and past EOF.
- {"Read", 5, 0, 0, "klmno", nil},
- {"Read", 5, 0, 0, "p", nil},
- {"Read", 5, 0, 0, "", io.EOF},
- // Test ReadAt.
- {"ReadAt", 4, 0, 0, "abcd", nil},
- {"ReadAt", 4, 3, 0, "defg", nil},
- {"ReadAt", 4, 12, 0, "mnop", nil},
- {"ReadAt", 4, 13, 0, "nop", io.EOF},
- {"ReadAt", 4, 99, 0, "", io.EOF},
- }},
- {"a14p.1", []step{
- // Test Seek before any reads.
- {"Seek", 0, 2, os.SEEK_SET, "2", nil},
- {"Read", 1, 0, 0, "c", nil},
- // Test that ReadAt doesn't affect the Read offset.
- {"ReadAt", 3, 9, 0, "jkl", nil},
- {"Read", 3, 0, 0, "def", nil},
- }},
- {"a14p.2", []step{
- // Test ReadAt before any reads or seeks.
- {"ReadAt", 2, 14, 0, "op", nil},
- }},
- {"longBlob.0", []step{
- // Test basic read.
- {"Read", 1, 0, 0, "A", nil},
- // Test that Read returns early when the buffer is exhausted.
- {"Seek", 0, rbs - 2, os.SEEK_SET, strconv.Itoa(rbs - 2), nil},
- {"Read", 5, 0, 0, "AA", nil},
- {"Read", 3, 0, 0, "BBB", nil},
- // Test that what we just read is still in the buffer.
- {"Seek", 0, rbs - 2, os.SEEK_SET, strconv.Itoa(rbs - 2), nil},
- {"Read", 5, 0, 0, "AABBB", nil},
- // Test ReadAt.
- {"ReadAt", 3, rbs - 4, 0, "AAA", nil},
- {"ReadAt", 6, rbs - 4, 0, "AAAABB", nil},
- {"ReadAt", 8, rbs - 4, 0, "AAAABBBB", nil},
- {"ReadAt", 5, rbs - 4, 0, "AAAAB", nil},
- {"ReadAt", 2, rbs - 4, 0, "AA", nil},
- // Test seeking backwards from the Read offset.
- {"Seek", 0, 2*rbs - 8, os.SEEK_SET, strconv.Itoa(2*rbs - 8), nil},
- {"Read", 1, 0, 0, "B", nil},
- {"Read", 1, 0, 0, "B", nil},
- {"Read", 1, 0, 0, "B", nil},
- {"Read", 1, 0, 0, "B", nil},
- {"Read", 8, 0, 0, "BBBBCCCC", nil},
- }},
- {"longBlob.1", []step{
- // Test ReadAt with a slice larger than the buffer size.
- {"LargeReadAt", 2*rbs - 2, 0, 0, strconv.Itoa(2*rbs - 2), nil},
- {"LargeReadAt", 2*rbs - 1, 0, 0, strconv.Itoa(2*rbs - 1), nil},
- {"LargeReadAt", 2*rbs + 0, 0, 0, strconv.Itoa(2*rbs + 0), nil},
- {"LargeReadAt", 2*rbs + 1, 0, 0, strconv.Itoa(2*rbs + 1), nil},
- {"LargeReadAt", 2*rbs + 2, 0, 0, strconv.Itoa(2*rbs + 2), nil},
- {"LargeReadAt", 2*rbs - 2, 1, 0, strconv.Itoa(2*rbs - 2), nil},
- {"LargeReadAt", 2*rbs - 1, 1, 0, strconv.Itoa(2*rbs - 1), nil},
- {"LargeReadAt", 2*rbs + 0, 1, 0, strconv.Itoa(2*rbs + 0), nil},
- {"LargeReadAt", 2*rbs + 1, 1, 0, strconv.Itoa(2*rbs + 1), nil},
- {"LargeReadAt", 2*rbs + 2, 1, 0, strconv.Itoa(2*rbs + 2), nil},
- }},
- }
- func TestReader(t *testing.T) {
- for _, rt := range readerTest {
- c := aetesting.FakeSingleContext(t, "blobstore", "FetchData", fakeFetchData)
- r := NewReader(c, appengine.BlobKey(rt.blobKey))
- for i, step := range rt.step {
- var (
- got string
- gotErr error
- n int
- offset int64
- )
- switch step.method {
- case "LargeReadAt":
- p := make([]byte, step.lenp)
- n, gotErr = r.ReadAt(p, step.offset)
- got = strconv.Itoa(n)
- case "Read":
- p := make([]byte, step.lenp)
- n, gotErr = r.Read(p)
- got = string(p[:n])
- case "ReadAt":
- p := make([]byte, step.lenp)
- n, gotErr = r.ReadAt(p, step.offset)
- got = string(p[:n])
- case "Seek":
- offset, gotErr = r.Seek(step.offset, step.whence)
- got = strconv.FormatInt(offset, 10)
- default:
- t.Fatalf("unknown method: %s", step.method)
- }
- if gotErr != step.wantErr {
- t.Fatalf("%s step %d: got error %v want %v", rt.blobKey, i, gotErr, step.wantErr)
- }
- if got != step.want {
- t.Fatalf("%s step %d: got %q want %q", rt.blobKey, i, got, step.want)
- }
- }
- }
- }
|