go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/gcloud/gs/limited_test.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package gs 16 17 import ( 18 "errors" 19 "fmt" 20 "io" 21 "testing" 22 23 . "github.com/smartystreets/goconvey/convey" 24 ) 25 26 type testReaderClient struct { 27 Client 28 29 newReaderErr error 30 readErr error 31 closeErr error 32 33 maxRead int64 34 data map[Path][]byte 35 readers map[*testReader]struct{} 36 totalReaders int 37 } 38 39 func (trc *testReaderClient) NewReader(p Path, offset, length int64) (io.ReadCloser, error) { 40 if err := trc.newReaderErr; err != nil { 41 return nil, err 42 } 43 44 if trc.maxRead > 0 && length > 0 && length > trc.maxRead { 45 return nil, fmt.Errorf("maximum read exceeded (%d > %d)", length, trc.maxRead) 46 } 47 48 data := trc.data[p] 49 if int64(len(data)) < offset { 50 return nil, errors.New("reading past EOF") 51 } 52 data = data[offset:] 53 if length >= 0 && length < int64(len(data)) { 54 data = data[:length] 55 } 56 tr := &testReader{ 57 client: trc, 58 data: data, 59 } 60 trc.readers[tr] = struct{}{} 61 trc.totalReaders++ 62 return tr, nil 63 } 64 65 type testReader struct { 66 client *testReaderClient 67 data []byte 68 closed bool 69 } 70 71 func (tr *testReader) checkClosed() error { 72 if tr.closed { 73 return errors.New("already closed") 74 } 75 return nil 76 } 77 78 func (tr *testReader) Close() error { 79 delete(tr.client.readers, tr) 80 81 if err := tr.client.closeErr; err != nil { 82 return err 83 } 84 if err := tr.checkClosed(); err != nil { 85 return err 86 } 87 tr.closed = true 88 return nil 89 } 90 91 func (tr *testReader) Read(b []byte) (amt int, err error) { 92 if err = tr.client.readErr; err != nil { 93 return 94 } 95 96 amt = copy(b, tr.data) 97 tr.data = tr.data[amt:] 98 if len(tr.data) == 0 { 99 err = io.EOF 100 } 101 return 102 } 103 104 func TestLimitedClient(t *testing.T) { 105 t.Parallel() 106 107 Convey(`Testing limited client`, t, func() { 108 const path = "gs://bucket/file" 109 data := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 110 trc := testReaderClient{ 111 data: map[Path][]byte{ 112 path: data, 113 }, 114 readers: map[*testReader]struct{}{}, 115 } 116 defer So(trc.readers, ShouldHaveLength, 0) 117 118 lc := &LimitedClient{ 119 Client: &trc, 120 } 121 122 setLimit := func(limit int) { 123 lc.MaxReadBytes = int64(limit) 124 trc.maxRead = int64(limit) 125 } 126 127 Convey(`With no read limit, can read the full stream.`, func() { 128 r, err := lc.NewReader(path, 0, -1) 129 So(err, ShouldBeNil) 130 defer r.Close() 131 132 d, err := io.ReadAll(r) 133 So(err, ShouldBeNil) 134 So(d, ShouldResemble, data) 135 }) 136 137 for _, limit := range []int{-1, 1, 2, 5, len(data) - 1, len(data), len(data) + 1} { 138 Convey(fmt.Sprintf(`Variety test: with a read limit of %d`, limit), func() { 139 setLimit(limit) 140 141 Convey(`Can read the full stream with no limit.`, func() { 142 r, err := lc.NewReader(path, 0, -1) 143 So(err, ShouldBeNil) 144 defer r.Close() 145 146 d, err := io.ReadAll(r) 147 So(err, ShouldBeNil) 148 So(d, ShouldResemble, data) 149 }) 150 151 Convey(`Can read a partial stream.`, func() { 152 r, err := lc.NewReader(path, 3, 6) 153 So(err, ShouldBeNil) 154 defer r.Close() 155 156 d, err := io.ReadAll(r) 157 So(err, ShouldBeNil) 158 So(d, ShouldResemble, data[3:9]) 159 }) 160 161 Convey(`Can read an offset stream with a limit.`, func() { 162 r, err := lc.NewReader(path, 3, 6) 163 So(err, ShouldBeNil) 164 defer r.Close() 165 166 d, err := io.ReadAll(r) 167 So(err, ShouldBeNil) 168 So(d, ShouldResemble, data[3:9]) 169 }) 170 }) 171 } 172 173 Convey(`With a read limit of 2`, func() { 174 setLimit(2) 175 176 Convey(`Reading full stream creates expected readers.`, func() { 177 r, err := lc.NewReader(path, 0, -1) 178 So(err, ShouldBeNil) 179 defer r.Close() 180 181 d, err := io.ReadAll(r) 182 So(err, ShouldBeNil) 183 So(d, ShouldResemble, data) 184 So(trc.totalReaders, ShouldEqual, 6) // One for each, plus real EOF. 185 }) 186 187 Convey(`Reading partial stream (even) creates expected readers.`, func() { 188 r, err := lc.NewReader(path, 3, 6) 189 So(err, ShouldBeNil) 190 defer r.Close() 191 192 d, err := io.ReadAll(r) 193 So(err, ShouldBeNil) 194 So(d, ShouldResemble, data[3:9]) 195 So(trc.totalReaders, ShouldEqual, 3) 196 }) 197 198 Convey(`Reading partial stream (odd) creates expected readers.`, func() { 199 r, err := lc.NewReader(path, 3, 5) 200 So(err, ShouldBeNil) 201 defer r.Close() 202 203 d, err := io.ReadAll(r) 204 So(err, ShouldBeNil) 205 So(d, ShouldResemble, data[3:8]) 206 So(trc.totalReaders, ShouldEqual, 3) 207 }) 208 209 Convey(`Configured to error on new reader, returns that error.`, func() { 210 trc.newReaderErr = errors.New("test error") 211 _, err := lc.NewReader(path, 3, 5) 212 So(err, ShouldEqual, trc.newReaderErr) 213 }) 214 215 Convey(`Configured to error on close, returns that error on read.`, func() { 216 r, err := lc.NewReader(path, 0, -1) 217 So(err, ShouldBeNil) 218 defer r.Close() 219 220 trc.readErr = errors.New("test error") 221 _, err = io.ReadAll(r) 222 So(err, ShouldEqual, trc.readErr) 223 }) 224 225 Convey(`Configured to error on read, returns that error.`, func() { 226 r, err := lc.NewReader(path, 3, 5) 227 So(err, ShouldBeNil) 228 defer r.Close() 229 230 trc.closeErr = errors.New("test error") 231 _, err = io.ReadAll(r) 232 So(err, ShouldEqual, trc.closeErr) 233 }) 234 }) 235 }) 236 }