lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xbufio/seqbuf_ioat_test.go (about)

     1  // Copyright (C) 2017  Nexedi SA and Contributors.
     2  //                     Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  package xbufio
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"io"
    26  	"testing"
    27  )
    28  
    29  
    30  // XReader is an io.ReaderAt that reads first 256 bytes with content_i = i.
    31  // bytes in range [100, 104] give EIO on reading.
    32  type XReader struct {
    33  }
    34  
    35  var EIO = errors.New("input/output error")
    36  
    37  func (r *XReader) ReadAt(p []byte, pos int64) (n int, err error) {
    38  	for n < len(p) && pos < 0x100 {
    39  		if 100 <= pos && pos <= 104 {
    40  			err = EIO
    41  			break
    42  		}
    43  
    44  		p[n] = byte(pos)
    45  		n++
    46  		pos++
    47  	}
    48  
    49  	if n < len(p) && err == nil {
    50  		err = io.EOF
    51  	}
    52  
    53  	return n, err
    54  }
    55  
    56  
    57  // read @pos/len -> rb.pos, len(rb.buf)
    58  var xSeqBufTestv = []struct {pos int64; Len int; bufPos int64; bufLen int} {
    59  	{40,  5, 40, 10},	// 1st access, forward by default
    60  	{45,  7, 50, 10},	// part taken from buf, part read next, forward (trend)
    61  	{52,  5, 50, 10},	// everything taken from buf
    62  	{57,  5, 60, 10},	// part taken from buf, part read next (trend)
    63  	{60, 11, 60, 10},	// access > cap(buf), buf skipped
    64  	{71, 11, 60, 10},	// access > cap(buf), once again
    65  	{82, 10, 82, 10},	// access = cap(buf), should refill buf
    66  	{92,  5, 92,  8},	// next access - should refill buffer (trend), but only up to EIO range
    67  	{97,  4, 100, 0},	// this triggers user-visible EIO, buffer scratched
    68  	{101, 5, 101, 0},	// EIO again
    69  	{105, 5, 105, 10},	// past EIO range - buffer refilled
    70  	{88,  8, 88, 10},	// go back again a bit before EIO range
    71  	{96,  6, 98,  2},	// this uses both buffered data + result of next read which hits EIO
    72  	{110,70, 98,  2},	// very big access forward, buf untouched
    73  	{180,70, 98,  2},	// big access ~ forward
    74  
    75  	{170, 5, 170, 10},	// backward: buffer refilled forward because prev backward reading was below
    76  	{168, 4, 160, 10},	// backward: buffer refilled backward
    77  	{162, 6, 160, 10},	// backward: all data read from buffer
    78  	{150,12, 160, 10},	// big backward: buf untouched
    79  	{142, 6, 140, 10},	// backward: buffer refilled up to posLastIO
    80  	{130,12, 140, 10},	// big backward: buf untouched
    81  	{122, 9, 121, 10},	// backward overlapping with last bigio: buf correctly refilled
    82  	{131, 9, 131, 10},	// forward after backward: buf refilled forward
    83  	{122, 6, 121, 10},	// backward after forward: buf refilled backward
    84  	{131, 9, 131, 10},	// forward again
    85  	{136,20, 131, 10},	// big forward starting from inside filled buf
    86  	{128, 4, 126, 10},	// backward (not trend): buf refilled up to posLastIO
    87  
    88  	// interleaved backward + fwd-fwd-fwd
    89  	{200,10, 200, 10},	// reset @200
    90  	{194, 1, 190, 10},	// 1st backward access: buf refilled
    91  	{186, 1, 184, 10},	// trendy backward access - buf refilled up-to prev back read
    92  
    93  	{187, 1, 184, 10},	// fwd-fwd-fwd (all already buffered)
    94  	{188, 2, 184, 10},
    95  	{190, 3, 184, 10},
    96  
    97  	{182, 4, 174, 10},	// trendy backward access - part taken from buffer and buf refilled adjacent to previous backward IO
    98  
    99  	{168, 1, 168, 10},	// trendy backward access farther than cap(buf) - buf refilled right at @pos
   100  
   101  	{169, 7, 168, 10},	// fwd-fwd-fwd (partly buffered / partly loaded)
   102  	{176, 3, 178, 10},
   103  	{179, 6, 178, 10},
   104  
   105  	// interleaved forward + back-back-back
   106  	{200,10, 200, 10},	// reset @200
   107  	{206, 1, 200, 10},	// 1st forward access
   108  	{214, 1, 207, 10},	// trendy forward access - buf refilled adjacent to previous forward read
   109  
   110  	{213, 1, 207, 10},	// back-back-back (all already buffered)
   111  	{211, 2, 207, 10},
   112  	{207, 5, 207, 10},
   113  
   114  	{215, 4, 217, 10},	// trendy forward access - part taken from buffer and buf refilled adjacent to previous forward IO
   115  
   116  	{235, 1, 235, 10},	// trendy forward access farther than cap(buf) - buf refilled right at @pos
   117  
   118  	{234, 1, 225, 10},	// back-back-back (partly loaded / then partly buffered)
   119  	{230, 3, 225, 10},
   120  	{222, 8, 215, 10},
   121  	{219, 3, 215, 10},
   122  
   123  	// backward (non trend) vs not overlapping previous forward
   124  	{230,10, 230, 10},	// forward  @230 (1)
   125  	{220,10, 220, 10},	// backward @220 (2)
   126  	{250, 4, 250,  6},	// forward  @250 (3)
   127  	{245, 5, 240, 10},	// backward @245 (4)
   128  
   129  	{5, 4, 5, 10},		// forward near file start
   130  	{2, 3, 0, 10},		// backward: buf does not go beyond 0
   131  
   132  	{40, 0, 0, 10},		// zero-sized out-of-buffer read do not change buffer
   133  
   134  	// backward (not trend) vs EIO
   135  	{108,10, 108, 10},	// reset @108
   136  	{ 98, 1,  98,  2},	// backward not overlapping EIO: buf filled < EIO range
   137  	{108,10, 108, 10},	// reset @108
   138  	{ 99, 4,  98,  2},	// backward overlapping head EIO: buf filled < EIO range, EIO -> user
   139  	{108,10, 108, 10},	// reset @108
   140  	{ 99, 6,  98,  2},	// backward overlapping whole EIO range: buf filled <= EIO range, EIO -> user
   141  	{108,10, 108, 10},	// reset @108
   142  	{100, 4,  98,  2},	// backward = EIO range: buf filled < EIO range, EIO -> user
   143  	{110,10, 110, 10},	// reset @110
   144  	{101, 2, 100,  0},	// backward inside EIO range: buf scratched, EIO -> user
   145  	{110,10, 110, 10},	// reset @110
   146  	{103, 5, 100,  0},	// backward overlapping tail EIO: buf scratched, EIO -> user
   147  	{110,10, 110, 10},	// reset state: forward @110
   148  	{105, 7, 100,  0},	// backward client after EIO: buf scratched but read request satisfied
   149  
   150  	// backward (trend) vs EIO
   151  	// NOTE this is reverse of `backward (not trend) vs EIO
   152  	{110,10, 110, 10},	// reset state: forward @110
   153  	{105, 7, 100,  0},	// backward client after EIO: buf scratched but read request satisfied
   154  	{110,10, 110, 10},	// reset @110
   155  	{103, 5,  98,  2},	// backward overlapping tail EIO: buf scratched (XXX), EIO -> user
   156  	{110,10, 110, 10},	// reset @110
   157  	{101, 2,  93,  7},	// backward inside EIO range: buf scratched (XXX), EIO -> user
   158  	{108,10, 108, 10},	// reset @108
   159  	{100, 4,  94,  6},	// backward = EIO range: buf filled < EIO range, EIO -> user
   160  	{108,10, 108, 10},	// reset @108
   161  	{ 99, 6,  95,  5},	// backward overlapping whole EIO range: buf filled <= EIO range, EIO -> user
   162  	{108,10, 108, 10},	// reset @108
   163  	{ 99, 4,  98,  2},	// backward overlapping head EIO: buf filled < EIO range, EIO -> user
   164  	{108,10, 108, 10},	// reset @108
   165  	{ 98, 1,  89, 10},	// backward not overlapping EIO: buf filled according to backward trend
   166  
   167  	// forward (trend) vs EIO
   168  	{  0, 1,   0, 10},
   169  	{ 88,10,  88, 10},	// reset forward @98
   170  	{ 98, 1,  98,  2},	// forward not overlapping EIO: buf filled < EIO range
   171  	{  0, 1,   0, 10},
   172  	{ 88,10,  88, 10},	// reset forward @98
   173  	{ 99, 4,  98,  2},	// forward overlapping head EIO: buf filled < EIO range, EIO -> user
   174  	{  0, 1,   0, 10},
   175  	{ 88,10,  88, 10},	// reset forward @98
   176  	{ 99, 6,  98,  2},	// forward overlapping whole EIO range: buf filled <= EIO range, EIO -> user
   177  	{  0, 1,   0, 10},
   178  	{ 88,10,  88, 10},	// reset forward @98
   179  	{100, 4,  98,  2},	// forward = EIO range: buf filled < EIO range, EIO -> user
   180  	{  0, 1,   0, 10},
   181  	{ 90,10,  90, 10},	// reset forward @100
   182  	{101, 2, 100,  0},	// forward inside EIO range: buf scratched, EIO -> user
   183  	{  0, 1,   0, 10},
   184  	{ 90,10,  90, 10},	// reset forward @100
   185  	{103, 5, 100,  0},	// forward overlapping tail EIO: buf scratched, EIO -> user
   186  	{  0, 1,   0, 10},
   187  	{ 90,10,  90, 10},	// reset forward @100
   188  	{105, 2, 100,  0},	// forward client after EIO: buf scratched but read request satisfied
   189  
   190  	{  0, 1,   0, 10},
   191  	{ 90, 5,  90, 10},	// reset forward @95
   192  	{ 99, 3,  96,  4},	// forward jump client overlapping head EIO: buf filled < EIO range, EIO -> user
   193  
   194  	{  0, 1,   0, 10},
   195  	{ 89, 5,  89, 10},	// reset forward @94
   196  	{ 98, 2,  95,  5},	// forward jump client reading < EIO: buf filled < EIO range, user request satisfied
   197  
   198  
   199  	// EOF handling
   200  	{250, 4, 250,  6},	// access near EOF - buffer fill hits EOF, but not returns it to client
   201  	{254, 5, 256,  0},	// access overlapping EOF - EOF returned, buf scratched
   202  	{256, 1, 256,  0},	// access past EOF -> EOF
   203  	{257, 1, 257,  0},	// ----//----
   204  
   205  	// forward with jumps - buffer is still refilled adjacent to previous reading
   206  	// ( because jumps are not sequential access and we are optimizing for sequential cases.
   207  	//   also: if jump > cap(buf) reading will not be adjacent)
   208  	{ 0, 1,  0, 10},	// reset
   209  	{ 0, 5,  0, 10},
   210  	{ 9, 3,  6, 10},
   211  	{20, 3, 20, 10},
   212  }
   213  
   214  
   215  func TestSeqReaderAt(t *testing.T) {
   216  	r := &XReader{}
   217  	rb := NewSeqReaderAtSize(r, 10) // with 10 it is easier to do/check math for a human
   218  
   219  	for _, tt := range xSeqBufTestv {
   220  		pOk := make([]byte, tt.Len)
   221  		pB  := make([]byte, tt.Len)
   222  
   223  		nOk, errOk := r.ReadAt(pOk, tt.pos)
   224  		nB,  errB  := rb.ReadAt(pB, tt.pos)
   225  
   226  		pOk = pOk[:nOk]
   227  		pB  = pB[:nB]
   228  
   229  		// check that reading result is the same
   230  		if !(bytes.Equal(pB, pOk) && errB == errOk) {
   231  			t.Fatalf("%v: -> %v, %#v  ; want %v, %#v", tt, pB, errB, pOk, errOk)
   232  		}
   233  
   234  		// verify buffer state
   235  		if !(rb.pos == tt.bufPos && len(rb.buf) == tt.bufLen) {
   236  			t.Fatalf("%v: -> unexpected buffer state @%v #%v", tt, rb.pos, len(rb.buf))
   237  		}
   238  	}
   239  }
   240  
   241  // this is benchmark for how thin wrapper is, not for logic inside it
   242  func BenchmarkSeqReaderAt(b *testing.B) {
   243  	r := &XReader{}
   244  	rb := NewSeqReaderAtSize(r, 10) // same as in TestSeqReaderAt
   245  	buf := make([]byte, 128 /* > all .Len in xSeqBufTestv */)
   246  
   247  	for i := 0; i < b.N; i++ {
   248  		for _, tt := range xSeqBufTestv {
   249  			rb.ReadAt(buf[:tt.Len], tt.pos)
   250  		}
   251  	}
   252  }