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  }