github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/dummy-data-generator_test.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bytes" 22 "errors" 23 "fmt" 24 "io" 25 "testing" 26 ) 27 28 var alphabets = []byte("abcdefghijklmnopqrstuvwxyz0123456789") 29 30 // DummyDataGen returns a reader that repeats the bytes in `alphabets` 31 // upto the desired length. 32 type DummyDataGen struct { 33 b []byte 34 idx, length int64 35 } 36 37 // NewDummyDataGen returns a ReadSeeker over the first `totalLength` 38 // bytes from the infinite stream consisting of repeated 39 // concatenations of `alphabets`. 40 // 41 // The skipOffset (generally = 0) can be used to skip a given number 42 // of bytes from the beginning of the infinite stream. This is useful 43 // to compare such streams of bytes that may be split up, because: 44 // 45 // Given the function: 46 // 47 // f := func(r io.Reader) string { 48 // b, _ := io.ReadAll(r) 49 // return string(b) 50 // } 51 // 52 // for example, the following is true: 53 // 54 // f(NewDummyDataGen(100, 0)) == f(NewDummyDataGen(50, 0)) + f(NewDummyDataGen(50, 50)) 55 func NewDummyDataGen(totalLength, skipOffset int64) io.ReadSeeker { 56 if totalLength < 0 { 57 panic("Negative length passed to DummyDataGen!") 58 } 59 if skipOffset < 0 { 60 panic("Negative rotations are not allowed") 61 } 62 63 skipOffset %= int64(len(alphabets)) 64 as := make([]byte, 2*len(alphabets)) 65 copy(as, alphabets) 66 copy(as[len(alphabets):], alphabets) 67 b := as[skipOffset : skipOffset+int64(len(alphabets))] 68 return &DummyDataGen{ 69 length: totalLength, 70 b: b, 71 } 72 } 73 74 func (d *DummyDataGen) Read(b []byte) (n int, err error) { 75 k := len(b) 76 numLetters := int64(len(d.b)) 77 for k > 0 && d.idx < d.length { 78 w := copy(b[len(b)-k:], d.b[d.idx%numLetters:]) 79 k -= w 80 d.idx += int64(w) 81 n += w 82 } 83 if d.idx >= d.length { 84 extraBytes := d.idx - d.length 85 n -= int(extraBytes) 86 if n < 0 { 87 n = 0 88 } 89 err = io.EOF 90 } 91 return 92 } 93 94 func (d *DummyDataGen) Seek(offset int64, whence int) (int64, error) { 95 switch whence { 96 case io.SeekStart: 97 if offset < 0 { 98 return 0, errors.New("Invalid offset") 99 } 100 d.idx = offset 101 case io.SeekCurrent: 102 if d.idx+offset < 0 { 103 return 0, errors.New("Invalid offset") 104 } 105 d.idx += offset 106 case io.SeekEnd: 107 if d.length+offset < 0 { 108 return 0, errors.New("Invalid offset") 109 } 110 d.idx = d.length + offset 111 } 112 return d.idx, nil 113 } 114 115 func TestDummyDataGenerator(t *testing.T) { 116 readAll := func(r io.Reader) string { 117 b, _ := io.ReadAll(r) 118 return string(b) 119 } 120 checkEq := func(a, b string) { 121 if a != b { 122 t.Fatalf("Unexpected equality failure") 123 } 124 } 125 126 checkEq(readAll(NewDummyDataGen(0, 0)), "") 127 128 checkEq(readAll(NewDummyDataGen(10, 0)), readAll(NewDummyDataGen(10, int64(len(alphabets))))) 129 130 checkEq(readAll(NewDummyDataGen(100, 0)), readAll(NewDummyDataGen(50, 0))+readAll(NewDummyDataGen(50, 50))) 131 132 r := NewDummyDataGen(100, 0) 133 r.Seek(int64(len(alphabets)), 0) 134 checkEq(readAll(r), readAll(NewDummyDataGen(100-int64(len(alphabets)), 0))) 135 } 136 137 // Compares all the bytes returned by the given readers. Any Read 138 // errors cause a `false` result. A string describing the error is 139 // also returned. 140 func cmpReaders(r1, r2 io.Reader) (bool, string) { 141 bufLen := 32 * 1024 142 b1, b2 := make([]byte, bufLen), make([]byte, bufLen) 143 for i := 0; true; i++ { 144 n1, e1 := io.ReadFull(r1, b1) 145 n2, e2 := io.ReadFull(r2, b2) 146 if n1 != n2 { 147 return false, fmt.Sprintf("Read %d != %d bytes from the readers", n1, n2) 148 } 149 if !bytes.Equal(b1[:n1], b2[:n2]) { 150 return false, fmt.Sprintf("After reading %d equal buffers (32Kib each), we got the following two strings:\n%v\n%v\n", 151 i, b1, b2) 152 } 153 // Check if stream has ended 154 if (e1 == io.ErrUnexpectedEOF && e2 == io.ErrUnexpectedEOF) || (e1 == io.EOF && e2 == io.EOF) { 155 break 156 } 157 if e1 != nil || e2 != nil { 158 return false, fmt.Sprintf("Got unexpected error values: %v == %v", e1, e2) 159 } 160 } 161 return true, "" 162 } 163 164 func TestCmpReaders(t *testing.T) { 165 { 166 r1 := bytes.NewReader([]byte("abc")) 167 r2 := bytes.NewReader([]byte("abc")) 168 ok, msg := cmpReaders(r1, r2) 169 if !(ok && msg == "") { 170 t.Fatalf("unexpected") 171 } 172 } 173 174 { 175 r1 := bytes.NewReader([]byte("abc")) 176 r2 := bytes.NewReader([]byte("abcd")) 177 ok, _ := cmpReaders(r1, r2) 178 if ok { 179 t.Fatalf("unexpected") 180 } 181 } 182 }