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  }