github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/chunker/chunker_test.go (about)

     1  package chunker
     2  
     3  import (
     4  	"bytes"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
    10  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/uploadinfo"
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/google/go-cmp/cmp/cmpopts"
    13  )
    14  
    15  var tests = []struct {
    16  	name       string
    17  	blob       []byte
    18  	wantChunks []*Chunk
    19  	chunkSize  int
    20  }{
    21  	{
    22  		name:       "empty",
    23  		wantChunks: []*Chunk{&Chunk{}},
    24  		chunkSize:  3,
    25  	},
    26  	{
    27  		name:       "one",
    28  		blob:       []byte("12"),
    29  		wantChunks: []*Chunk{&Chunk{Data: []byte("12")}},
    30  		chunkSize:  3,
    31  	},
    32  	{
    33  		name:       "one-even",
    34  		blob:       []byte("123"),
    35  		wantChunks: []*Chunk{&Chunk{Data: []byte("123")}},
    36  		chunkSize:  3,
    37  	},
    38  	{
    39  		name: "two",
    40  		blob: []byte("12345"),
    41  		wantChunks: []*Chunk{
    42  			&Chunk{Data: []byte("123")},
    43  			&Chunk{Data: []byte("45"), Offset: 3},
    44  		},
    45  		chunkSize: 3,
    46  	},
    47  	{
    48  		name: "three-even",
    49  		blob: []byte("123456789"),
    50  		wantChunks: []*Chunk{
    51  			&Chunk{Data: []byte("123")},
    52  			&Chunk{Data: []byte("456"), Offset: 3},
    53  			&Chunk{Data: []byte("789"), Offset: 6},
    54  		},
    55  		chunkSize: 3,
    56  	},
    57  	{
    58  		name: "three",
    59  		blob: []byte("123456789"),
    60  		wantChunks: []*Chunk{
    61  			&Chunk{Data: []byte("1234")},
    62  			&Chunk{Data: []byte("5678"), Offset: 4},
    63  			&Chunk{Data: []byte("9"), Offset: 8},
    64  		},
    65  		chunkSize: 4,
    66  	},
    67  	{
    68  		name: "many",
    69  		blob: []byte("1234567890abcdefghijklmnopqrstuvwxyz!"),
    70  		wantChunks: []*Chunk{
    71  			&Chunk{Data: []byte("1234")},
    72  			&Chunk{Data: []byte("5678"), Offset: 4},
    73  			&Chunk{Data: []byte("90ab"), Offset: 8},
    74  			&Chunk{Data: []byte("cdef"), Offset: 12},
    75  			&Chunk{Data: []byte("ghij"), Offset: 16},
    76  			&Chunk{Data: []byte("klmn"), Offset: 20},
    77  			&Chunk{Data: []byte("opqr"), Offset: 24},
    78  			&Chunk{Data: []byte("stuv"), Offset: 28},
    79  			&Chunk{Data: []byte("wxyz"), Offset: 32},
    80  			&Chunk{Data: []byte("!"), Offset: 36},
    81  		},
    82  		chunkSize: 4,
    83  	},
    84  }
    85  
    86  var bufferSizes = []int{3, 4, 8, 100}
    87  
    88  func TestChunkerFromBlob(t *testing.T) {
    89  	t.Parallel()
    90  	for _, tc := range tests {
    91  		t.Run(tc.name, func(t *testing.T) {
    92  			ue := uploadinfo.EntryFromBlob(tc.blob)
    93  			c, err := New(ue, false, tc.chunkSize)
    94  			if err != nil {
    95  				t.Fatalf("Could not make chunker from UEntry: %v", err)
    96  			}
    97  			var gotChunks []*Chunk
    98  			for _, wantChunk := range tc.wantChunks {
    99  				if !c.HasNext() {
   100  					t.Errorf("%s: c.HasNext() was false on blob %q , expecting next chunk %q", tc.name, tc.blob, string(wantChunk.Data))
   101  				}
   102  				got, err := c.Next()
   103  				if err != nil {
   104  					t.Errorf("%s: c.Next() gave error %v on blob %q , expecting next chunk %q", tc.name, err, tc.blob, string(wantChunk.Data))
   105  				}
   106  				gotChunks = append(gotChunks, got)
   107  			}
   108  			if diff := cmp.Diff(tc.wantChunks, gotChunks); diff != "" {
   109  				t.Errorf("%s: Chunker gave result diff (-want +got):\n%s", tc.name, diff)
   110  			}
   111  		})
   112  	}
   113  }
   114  
   115  func TestChunkerFromFile(t *testing.T) {
   116  	execRoot := t.TempDir()
   117  	for _, tc := range tests {
   118  		t.Run(tc.name, func(t *testing.T) {
   119  			path := filepath.Join(execRoot, tc.name)
   120  			if err := os.WriteFile(path, tc.blob, 0777); err != nil {
   121  				t.Fatalf("failed to write temp file: %v", err)
   122  			}
   123  			for _, bufSize := range bufferSizes {
   124  				if bufSize < tc.chunkSize {
   125  					continue
   126  				}
   127  				dg := digest.NewFromBlob(tc.blob)
   128  				IOBufferSize = bufSize
   129  				ue := uploadinfo.EntryFromFile(dg, path)
   130  				c, err := New(ue, false, tc.chunkSize)
   131  				if err != nil {
   132  					t.Fatalf("Could not make chunker from UEntry: %v", err)
   133  				}
   134  				var gotChunks []*Chunk
   135  				for _, wantChunk := range tc.wantChunks {
   136  					if !c.HasNext() {
   137  						t.Errorf("%s: c.HasNext() was false on blob %q buffer size %d, expecting next chunk %q", tc.name, tc.blob, bufSize, string(wantChunk.Data))
   138  					}
   139  					got, err := c.Next()
   140  					if err != nil {
   141  						t.Errorf("%s: c.Next() gave error %v on blob %q buffer size %d, expecting next chunk %q", tc.name, err, tc.blob, bufSize, string(wantChunk.Data))
   142  					}
   143  					gotChunks = append(gotChunks, got)
   144  				}
   145  				if diff := cmp.Diff(tc.wantChunks, gotChunks); diff != "" {
   146  					t.Errorf("%s: Chunker buffer size %d gave result diff (-want +got):\n%s", tc.name, bufSize, diff)
   147  				}
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestChunkerFullData(t *testing.T) {
   154  	t.Parallel()
   155  	for _, tc := range tests {
   156  		t.Run(tc.name, func(t *testing.T) {
   157  			ue := uploadinfo.EntryFromBlob(tc.blob)
   158  			c, err := New(ue, false, tc.chunkSize)
   159  			if err != nil {
   160  				t.Fatalf("Could not make chunker from UEntry: %v", err)
   161  			}
   162  			gotBlob, err := c.FullData()
   163  			if err != nil {
   164  				t.Errorf("c.FullData() gave error %v on blob %q", err, tc.blob)
   165  			}
   166  			if diff := cmp.Diff(tc.blob, gotBlob, cmpopts.EquateEmpty()); diff != "" {
   167  				t.Errorf("FullData gave result diff (-want +got):\n%s", diff)
   168  			}
   169  		})
   170  	}
   171  }
   172  
   173  func TestChunkerFromBlob_Reset(t *testing.T) {
   174  	t.Parallel()
   175  	for _, tc := range tests {
   176  		t.Run(tc.name, func(t *testing.T) {
   177  			for reset := 1; reset < len(tc.wantChunks); reset++ {
   178  				ue := uploadinfo.EntryFromBlob(tc.blob)
   179  				c, err := New(ue, false, tc.chunkSize)
   180  				if err != nil {
   181  					t.Fatalf("Could not make chunker from UEntry: %v", err)
   182  				}
   183  				var gotChunks []*Chunk
   184  				for i, wantChunk := range tc.wantChunks {
   185  					if !c.HasNext() {
   186  						t.Errorf("%s: c.HasNext() was false on blob %q , expecting next chunk %q", tc.name, tc.blob, string(wantChunk.Data))
   187  					}
   188  					got, err := c.Next()
   189  					if err != nil {
   190  						t.Errorf("%s: c.Next() gave error %v on blob %q , expecting next chunk %q", tc.name, err, tc.blob, string(wantChunk.Data))
   191  					}
   192  					gotChunks = append(gotChunks, got)
   193  					if i == reset {
   194  						if err := c.Reset(); err != nil {
   195  							t.Errorf("failed to reset: %v", err)
   196  						}
   197  						break
   198  					}
   199  				}
   200  				if diff := cmp.Diff(tc.wantChunks[:len(gotChunks)], gotChunks); diff != "" {
   201  					t.Errorf("%s: Chunker gave result diff (-want +got):\n%s", tc.name, diff)
   202  				}
   203  				gotChunks = nil
   204  				if reset >= len(tc.wantChunks) {
   205  					continue
   206  				}
   207  				for _, wantChunk := range tc.wantChunks {
   208  					if !c.HasNext() {
   209  						t.Errorf("%s: c.HasNext() was false on blob %q , expecting next chunk %q", tc.name, tc.blob, string(wantChunk.Data))
   210  					}
   211  					got, err := c.Next()
   212  					if err != nil {
   213  						t.Errorf("%s: c.Next() gave error %v on blob %q , expecting next chunk %q", tc.name, err, tc.blob, string(wantChunk.Data))
   214  					}
   215  					gotChunks = append(gotChunks, got)
   216  				}
   217  				if diff := cmp.Diff(tc.wantChunks, gotChunks); diff != "" {
   218  					t.Errorf("%s: Chunker gave result diff (-want +got):\n%s", tc.name, diff)
   219  				}
   220  			}
   221  		})
   222  	}
   223  }
   224  
   225  func TestChunkerFromFile_Reset(t *testing.T) {
   226  	execRoot := t.TempDir()
   227  	for _, tc := range tests {
   228  		t.Run(tc.name, func(t *testing.T) {
   229  			path := filepath.Join(execRoot, tc.name)
   230  			if err := os.WriteFile(path, tc.blob, 0777); err != nil {
   231  				t.Fatalf("failed to write temp file: %v", err)
   232  			}
   233  			for _, bufSize := range bufferSizes {
   234  				if bufSize < tc.chunkSize {
   235  					continue
   236  				}
   237  				dg := digest.NewFromBlob(tc.blob)
   238  				IOBufferSize = bufSize
   239  				for reset := 1; reset < len(tc.wantChunks); reset++ {
   240  					ue := uploadinfo.EntryFromFile(dg, path)
   241  					c, err := New(ue, false, tc.chunkSize)
   242  					if err != nil {
   243  						t.Fatalf("Could not make chunker from UEntry: %v", err)
   244  					}
   245  					var gotChunks []*Chunk
   246  					for i, wantChunk := range tc.wantChunks {
   247  						if !c.HasNext() {
   248  							t.Errorf("%s: c.HasNext() was false on blob %q buffer size %d, expecting next chunk %q", tc.name, tc.blob, bufSize, string(wantChunk.Data))
   249  						}
   250  						got, err := c.Next()
   251  						if err != nil {
   252  							t.Errorf("%s: c.Next() gave error %v on blob %q buffer size %d, expecting next chunk %q", tc.name, err, tc.blob, bufSize, string(wantChunk.Data))
   253  						}
   254  						gotChunks = append(gotChunks, got)
   255  						if i == reset {
   256  							if err := c.Reset(); err != nil {
   257  								t.Errorf("failed to reset: %v", err)
   258  							}
   259  							break
   260  						}
   261  					}
   262  					if diff := cmp.Diff(tc.wantChunks[:len(gotChunks)], gotChunks); diff != "" {
   263  						t.Errorf("%s: Chunker buffer size %d gave result diff (-want +got):\n%s", tc.name, bufSize, diff)
   264  					}
   265  					gotChunks = nil
   266  					if reset >= len(tc.wantChunks) {
   267  						continue
   268  					}
   269  					for _, wantChunk := range tc.wantChunks {
   270  						if !c.HasNext() {
   271  							t.Errorf("%s: c.HasNext() was false on blob %q buffer size %d, expecting next chunk %q", tc.name, tc.blob, bufSize, string(wantChunk.Data))
   272  						}
   273  						got, err := c.Next()
   274  						if err != nil {
   275  							t.Errorf("%s: c.Next() gave error %v on blob %q buffer size %d, expecting next chunk %q", tc.name, err, tc.blob, bufSize, string(wantChunk.Data))
   276  						}
   277  						gotChunks = append(gotChunks, got)
   278  					}
   279  					if diff := cmp.Diff(tc.wantChunks, gotChunks); diff != "" {
   280  						t.Errorf("%s: Chunker buffer size %d gave result diff (-want +got):\n%s", tc.name, bufSize, diff)
   281  					}
   282  				}
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestChunkerErrors_ErrEOF(t *testing.T) {
   289  	ue := uploadinfo.EntryFromBlob([]byte("12"))
   290  	c, err := New(ue, false, 2)
   291  	if err != nil {
   292  		t.Fatalf("Could not make chunker from UEntry: %v", err)
   293  	}
   294  	_, err = c.Next()
   295  	if err != nil {
   296  		t.Errorf("c.Next() gave error %v, expecting next chunk \"12\"", err)
   297  	}
   298  	got, err := c.Next()
   299  	if err == nil {
   300  		t.Errorf("c.Next() gave %v, %v, expecting _, error", got, err)
   301  	}
   302  }
   303  
   304  func TestChunkerResetOptimization_SmallFile(t *testing.T) {
   305  	// Files smaller than IOBufferSize are loaded into memory once and not re-read on Reset.
   306  	execRoot := t.TempDir()
   307  
   308  	blob := []byte("123")
   309  	path := filepath.Join(execRoot, "file")
   310  	if err := os.WriteFile(path, blob, 0777); err != nil {
   311  		t.Fatalf("failed to write temp file: %v", err)
   312  	}
   313  	dg := digest.NewFromBlob(blob)
   314  	IOBufferSize = 10
   315  	ue := uploadinfo.EntryFromFile(dg, path)
   316  	c, err := New(ue, false, 4)
   317  	if err != nil {
   318  		t.Fatalf("Could not make chunker from UEntry: %v", err)
   319  	}
   320  	got, err := c.Next()
   321  	if err != nil {
   322  		t.Errorf("c.Next() gave error %v", err)
   323  	}
   324  	wantChunk := &Chunk{Data: blob}
   325  	if diff := cmp.Diff(wantChunk, got); diff != "" {
   326  		t.Errorf("c.Next() gave result diff (-want +got):\n%s", diff)
   327  	}
   328  	if err := c.Reset(); err != nil {
   329  		t.Errorf("failed to reset: %v", err)
   330  	}
   331  	// Change the file contents.
   332  	if err := os.WriteFile(path, []byte("321"), 0777); err != nil {
   333  		t.Fatalf("failed to write temp file: %v", err)
   334  	}
   335  	got, err = c.Next()
   336  	if err != nil {
   337  		t.Errorf("c.Next() gave error %v", err)
   338  	}
   339  	if diff := cmp.Diff(wantChunk, got); diff != "" {
   340  		t.Errorf("c.Next() gave result diff (-want +got):\n%s", diff)
   341  	}
   342  }
   343  
   344  func TestChunkerResetOptimization_FullData(t *testing.T) {
   345  	// After FullData is called once, the file contents will remain loaded into memory and not
   346  	// re-read on Reset, even if the file is larger than IOBufferSize.
   347  	execRoot := t.TempDir()
   348  
   349  	blob := []byte("12345678")
   350  	path := filepath.Join(execRoot, "file")
   351  	if err := os.WriteFile(path, blob, 0777); err != nil {
   352  		t.Fatalf("failed to write temp file: %v", err)
   353  	}
   354  	dg := digest.NewFromBlob(blob)
   355  	IOBufferSize = 5
   356  	ue := uploadinfo.EntryFromFile(dg, path)
   357  	c, err := New(ue, false, 3)
   358  	if err != nil {
   359  		t.Fatalf("Could not make chunker from UEntry: %v", err)
   360  	}
   361  	got, err := c.FullData()
   362  	if err != nil {
   363  		t.Errorf("c.FullData() gave error %v", err)
   364  	}
   365  	if !bytes.Equal(got, blob) {
   366  		t.Errorf("c.FullData() gave result diff, want %q, got %q", string(blob), string(got))
   367  	}
   368  	if err := c.Reset(); err != nil {
   369  		t.Errorf("failed to reset: %v", err)
   370  	}
   371  	// Change the file contents.
   372  	if err := os.WriteFile(path, []byte("987654321"), 0777); err != nil {
   373  		t.Fatalf("failed to write temp file: %v", err)
   374  	}
   375  	got, err = c.FullData()
   376  	if err != nil {
   377  		t.Errorf("c.FullData() gave error %v", err)
   378  	}
   379  	if !bytes.Equal(got, blob) {
   380  		t.Errorf("c.FullData() gave result diff, want %q, got %q", string(blob), string(got))
   381  	}
   382  }