github.com/anchore/syft@v1.38.2/syft/internal/unionreader/union_reader_test.go (about)

     1  package unionreader
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"strings"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/syft/syft/file"
    14  )
    15  
    16  func Test_getUnionReader_notUnionReader(t *testing.T) {
    17  	expectedContents := "this is a test"
    18  	reader := io.NopCloser(strings.NewReader(expectedContents))
    19  
    20  	// make certain that the test fixture does not implement the union reader
    21  	_, ok := reader.(UnionReader)
    22  	require.False(t, ok)
    23  
    24  	actual, err := GetUnionReader(reader)
    25  	require.NoError(t, err)
    26  
    27  	_, ok = actual.(UnionReader)
    28  	require.True(t, ok)
    29  
    30  	b, err := io.ReadAll(actual)
    31  	require.NoError(t, err)
    32  
    33  	assert.Equal(t, expectedContents, string(b))
    34  }
    35  
    36  type panickingUnionReader struct{}
    37  
    38  func (p2 *panickingUnionReader) ReadAt(p []byte, off int64) (n int, err error) {
    39  	panic("don't call this in your unit test!")
    40  }
    41  
    42  func (p2 *panickingUnionReader) Seek(offset int64, whence int) (int64, error) {
    43  	panic("don't call this in your unit test!")
    44  }
    45  
    46  func (p2 *panickingUnionReader) Read(p []byte) (n int, err error) {
    47  	panic("don't call this in your unit test!")
    48  }
    49  
    50  func (p2 *panickingUnionReader) Close() error {
    51  	panic("don't call this in your unit test!")
    52  }
    53  
    54  var _ UnionReader = (*panickingUnionReader)(nil)
    55  
    56  func Test_getUnionReader_fileLocationReadCloser(t *testing.T) {
    57  	// panickingUnionReader is a UnionReader
    58  	p := &panickingUnionReader{}
    59  	embedsUnionReader := file.NewLocationReadCloser(file.Location{}, p)
    60  
    61  	// embedded union reader is returned without "ReadAll" invocation
    62  	ur, err := GetUnionReader(embedsUnionReader)
    63  	require.NoError(t, err)
    64  	require.Equal(t, p, ur)
    65  }
    66  
    67  func TestReaderAtAdapter_ReadAt(t *testing.T) {
    68  	testData := "Hello, World! This is a test string for ReadAt."
    69  
    70  	t.Run("basic functionality", func(t *testing.T) {
    71  		reader := newReadSeekCloser(strings.NewReader(testData))
    72  		adapter := newReaderAtAdapter(reader)
    73  
    74  		tests := []struct {
    75  			name     string
    76  			offset   int64
    77  			length   int
    78  			expected string
    79  		}{
    80  			{name: "read from beginning", offset: 0, length: 5, expected: "Hello"},
    81  			{name: "read from middle", offset: 7, length: 5, expected: "World"},
    82  			{name: "read from end", offset: int64(len(testData) - 4), length: 4, expected: "dAt."},
    83  			{name: "read single character", offset: 12, length: 1, expected: "!"},
    84  		}
    85  
    86  		for _, tt := range tests {
    87  			t.Run(tt.name, func(t *testing.T) {
    88  				buf := make([]byte, tt.length)
    89  				n, err := adapter.ReadAt(buf, tt.offset)
    90  
    91  				if err != nil && err != io.EOF {
    92  					t.Fatalf("Unexpected error: %v", err)
    93  				}
    94  
    95  				result := string(buf[:n])
    96  				if result != tt.expected {
    97  					t.Errorf("Expected %q, got %q", tt.expected, result)
    98  				}
    99  			})
   100  		}
   101  	})
   102  
   103  	t.Run("edge cases", func(t *testing.T) {
   104  		tests := []struct {
   105  			name        string
   106  			data        string
   107  			offset      int64
   108  			bufSize     int
   109  			expectedN   int
   110  			expectedErr error
   111  			expectedStr string
   112  		}{
   113  			{
   114  				name:        "beyond EOF",
   115  				data:        "Hello",
   116  				offset:      10,
   117  				bufSize:     5,
   118  				expectedN:   0,
   119  				expectedErr: io.EOF,
   120  				expectedStr: "",
   121  			},
   122  			{
   123  				name:        "partial read",
   124  				data:        "Hello",
   125  				offset:      2,
   126  				bufSize:     10,
   127  				expectedN:   3,
   128  				expectedErr: nil,
   129  				expectedStr: "llo",
   130  			},
   131  			{
   132  				name:        "empty buffer",
   133  				data:        "Hello",
   134  				offset:      0,
   135  				bufSize:     0,
   136  				expectedN:   0,
   137  				expectedErr: nil,
   138  				expectedStr: "",
   139  			},
   140  		}
   141  
   142  		for _, tt := range tests {
   143  			t.Run(tt.name, func(t *testing.T) {
   144  				reader := newReadSeekCloser(strings.NewReader(tt.data))
   145  				adapter := newReaderAtAdapter(reader)
   146  
   147  				buf := make([]byte, tt.bufSize)
   148  				n, err := adapter.ReadAt(buf, tt.offset)
   149  
   150  				if err != tt.expectedErr {
   151  					t.Errorf("Expected error %v, got %v", tt.expectedErr, err)
   152  				}
   153  
   154  				if n != tt.expectedN {
   155  					t.Errorf("Expected %d bytes read, got %d", tt.expectedN, n)
   156  				}
   157  
   158  				result := string(buf[:n])
   159  				if result != tt.expectedStr {
   160  					t.Errorf("Expected %q, got %q", tt.expectedStr, result)
   161  				}
   162  			})
   163  		}
   164  	})
   165  
   166  	t.Run("multiple reads from same position", func(t *testing.T) {
   167  		reader := newReadSeekCloser(strings.NewReader(testData))
   168  		adapter := newReaderAtAdapter(reader)
   169  
   170  		// read the same data multiple times
   171  		for i := 0; i < 3; i++ {
   172  			buf := make([]byte, 5)
   173  			n, err := adapter.ReadAt(buf, 7)
   174  
   175  			if err != nil && err != io.EOF {
   176  				t.Fatalf("ReadAt %d failed: %v", i, err)
   177  			}
   178  
   179  			result := string(buf[:n])
   180  			if result != "World" {
   181  				t.Errorf("ReadAt %d: expected 'World', got %q", i, result)
   182  			}
   183  		}
   184  	})
   185  
   186  	t.Run("concurrent access", func(t *testing.T) {
   187  		td := "0123456789abcdefghijklmnopqrstuvwxyz"
   188  		reader := newReadSeekCloser(strings.NewReader(td))
   189  		adapter := newReaderAtAdapter(reader)
   190  
   191  		const numGoroutines = 10
   192  		const numReads = 100
   193  
   194  		var wg sync.WaitGroup
   195  		results := make(chan bool, numGoroutines*numReads)
   196  
   197  		for i := 0; i < numGoroutines; i++ {
   198  			wg.Add(1)
   199  			go func(goroutineID int) {
   200  				defer wg.Done()
   201  
   202  				for j := 0; j < numReads; j++ {
   203  					offset := int64(goroutineID % len(td))
   204  					buf := make([]byte, 1)
   205  
   206  					n, err := adapter.ReadAt(buf, offset)
   207  					if err != nil && err != io.EOF {
   208  						results <- false
   209  						return
   210  					}
   211  
   212  					if n > 0 {
   213  						expected := td[offset]
   214  						if buf[0] != expected {
   215  							results <- false
   216  							return
   217  						}
   218  					}
   219  					results <- true
   220  				}
   221  			}(i)
   222  		}
   223  
   224  		wg.Wait()
   225  		close(results)
   226  
   227  		successCount := 0
   228  		totalCount := 0
   229  		for success := range results {
   230  			totalCount++
   231  			if success {
   232  				successCount++
   233  			}
   234  		}
   235  
   236  		if successCount != totalCount {
   237  			t.Errorf("Concurrent reads failed: %d/%d successful", successCount, totalCount)
   238  		}
   239  	})
   240  }
   241  
   242  func TestReaderAtAdapter_PositionHandling(t *testing.T) {
   243  	testData := "Hello, World!"
   244  
   245  	t.Run("preserves position after ReadAt", func(t *testing.T) {
   246  		reader := newReadSeekCloser(strings.NewReader(testData))
   247  		adapter := newReaderAtAdapter(reader)
   248  
   249  		// move to a specific position
   250  		initialPos := int64(7)
   251  		_, err := adapter.Seek(initialPos, io.SeekStart)
   252  		if err != nil {
   253  			t.Fatalf("Failed to seek: %v", err)
   254  		}
   255  
   256  		// read using ReadAt
   257  		buf := make([]byte, 5)
   258  		_, err = adapter.ReadAt(buf, 0)
   259  		if err != nil && err != io.EOF {
   260  			t.Fatalf("ReadAt failed: %v", err)
   261  		}
   262  
   263  		// verify position is preserved
   264  		currentPos, err := adapter.Seek(0, io.SeekCurrent)
   265  		if err != nil {
   266  			t.Fatalf("Failed to get current position: %v", err)
   267  		}
   268  
   269  		if currentPos != initialPos {
   270  			t.Errorf("Position not preserved. Expected %d, got %d", initialPos, currentPos)
   271  		}
   272  	})
   273  
   274  	t.Run("does not affect regular reads", func(t *testing.T) {
   275  		reader := newReadSeekCloser(strings.NewReader(testData))
   276  		adapter := newReaderAtAdapter(reader)
   277  
   278  		// read first few bytes normally
   279  		normalBuf := make([]byte, 5)
   280  		n, err := adapter.Read(normalBuf)
   281  		if err != nil {
   282  			t.Fatalf("Normal read failed: %v", err)
   283  		}
   284  		if string(normalBuf[:n]) != "Hello" {
   285  			t.Errorf("Expected 'Hello', got %q", string(normalBuf[:n]))
   286  		}
   287  
   288  		// use ReadAt to read from a different position
   289  		readAtBuf := make([]byte, 5)
   290  		n, err = adapter.ReadAt(readAtBuf, 7)
   291  		if err != nil && err != io.EOF {
   292  			t.Fatalf("ReadAt failed: %v", err)
   293  		}
   294  		if string(readAtBuf[:n]) != "World" {
   295  			t.Errorf("Expected 'World', got %q", string(readAtBuf[:n]))
   296  		}
   297  
   298  		// continue normal reading - should pick up where we left off
   299  		continueBuf := make([]byte, 2)
   300  		n, err = adapter.Read(continueBuf)
   301  		if err != nil {
   302  			t.Fatalf("Continue read failed: %v", err)
   303  		}
   304  		if string(continueBuf[:n]) != ", " {
   305  			t.Errorf("Expected ', ', got %q", string(continueBuf[:n]))
   306  		}
   307  	})
   308  }
   309  
   310  func TestReaderAtAdapter_Close(t *testing.T) {
   311  	reader := newReadSeekCloser(bytes.NewReader([]byte("test data")))
   312  	adapter := newReaderAtAdapter(reader)
   313  
   314  	// test that adapter can be closed
   315  	err := adapter.Close()
   316  	if err != nil {
   317  		t.Errorf("Close failed: %v", err)
   318  	}
   319  
   320  	if !reader.closed {
   321  		t.Error("Underlying reader was not closed")
   322  	}
   323  }
   324  
   325  type readSeekCloser struct {
   326  	io.ReadSeeker
   327  	closed bool
   328  }
   329  
   330  func newReadSeekCloser(rs io.ReadSeeker) *readSeekCloser {
   331  	return &readSeekCloser{ReadSeeker: rs}
   332  }
   333  
   334  func (r *readSeekCloser) Close() error {
   335  	r.closed = true
   336  	return nil
   337  }