...

Source file src/github.com/containerd/stargz-snapshotter/estargz/build_test.go

Documentation: github.com/containerd/stargz-snapshotter/estargz

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  /*
    18     Copyright 2019 The Go Authors. All rights reserved.
    19     Use of this source code is governed by a BSD-style
    20     license that can be found in the LICENSE file.
    21  */
    22  
    23  package estargz
    24  
    25  import (
    26  	"archive/tar"
    27  	"bytes"
    28  	"compress/gzip"
    29  	"fmt"
    30  	"io"
    31  	"reflect"
    32  	"testing"
    33  )
    34  
    35  func TestSort(t *testing.T) {
    36  	longname1 := longstring(120)
    37  	longname2 := longstring(150)
    38  
    39  	tests := []struct {
    40  		name             string
    41  		in               []tarEntry
    42  		log              []string // MUST NOT include "./" prefix here
    43  		want             []tarEntry
    44  		wantFail         bool
    45  		allowMissedFiles []string
    46  	}{
    47  		{
    48  			name: "nolog",
    49  			in: tarOf(
    50  				file("foo.txt", "foo"),
    51  				dir("bar/"),
    52  				file("bar/baz.txt", "baz"),
    53  				file("bar/bar.txt", "bar"),
    54  			),
    55  			want: tarOf(
    56  				noPrefetchLandmark(),
    57  				file("foo.txt", "foo"),
    58  				dir("bar/"),
    59  				file("bar/baz.txt", "baz"),
    60  				file("bar/bar.txt", "bar"),
    61  			),
    62  		},
    63  		{
    64  			name: "identical",
    65  			in: tarOf(
    66  				file("foo.txt", "foo"),
    67  				dir("bar/"),
    68  				file("bar/baz.txt", "baz"),
    69  				file("bar/bar.txt", "bar"),
    70  				file("bar/baa.txt", "baa"),
    71  			),
    72  			log: []string{"foo.txt", "bar/baz.txt"},
    73  			want: tarOf(
    74  				file("foo.txt", "foo"),
    75  				dir("bar/"),
    76  				file("bar/baz.txt", "baz"),
    77  				prefetchLandmark(),
    78  				file("bar/bar.txt", "bar"),
    79  				file("bar/baa.txt", "baa"),
    80  			),
    81  		},
    82  		{
    83  			name: "shuffle_reg",
    84  			in: tarOf(
    85  				file("foo.txt", "foo"),
    86  				file("baz.txt", "baz"),
    87  				file("bar.txt", "bar"),
    88  				file("baa.txt", "baa"),
    89  			),
    90  			log: []string{"baa.txt", "bar.txt", "baz.txt"},
    91  			want: tarOf(
    92  				file("baa.txt", "baa"),
    93  				file("bar.txt", "bar"),
    94  				file("baz.txt", "baz"),
    95  				prefetchLandmark(),
    96  				file("foo.txt", "foo"),
    97  			),
    98  		},
    99  		{
   100  			name: "shuffle_directory",
   101  			in: tarOf(
   102  				file("foo.txt", "foo"),
   103  				dir("bar/"),
   104  				file("bar/bar.txt", "bar"),
   105  				file("bar/baa.txt", "baa"),
   106  				dir("baz/"),
   107  				file("baz/baz1.txt", "baz"),
   108  				file("baz/baz2.txt", "baz"),
   109  				dir("baz/bazbaz/"),
   110  				file("baz/bazbaz/bazbaz_b.txt", "baz"),
   111  				file("baz/bazbaz/bazbaz_a.txt", "baz"),
   112  			),
   113  			log: []string{"baz/bazbaz/bazbaz_a.txt", "baz/baz2.txt", "foo.txt"},
   114  			want: tarOf(
   115  				dir("baz/"),
   116  				dir("baz/bazbaz/"),
   117  				file("baz/bazbaz/bazbaz_a.txt", "baz"),
   118  				file("baz/baz2.txt", "baz"),
   119  				file("foo.txt", "foo"),
   120  				prefetchLandmark(),
   121  				dir("bar/"),
   122  				file("bar/bar.txt", "bar"),
   123  				file("bar/baa.txt", "baa"),
   124  				file("baz/baz1.txt", "baz"),
   125  				file("baz/bazbaz/bazbaz_b.txt", "baz"),
   126  			),
   127  		},
   128  		{
   129  			name: "shuffle_link",
   130  			in: tarOf(
   131  				file("foo.txt", "foo"),
   132  				file("baz.txt", "baz"),
   133  				link("bar.txt", "baz.txt"),
   134  				file("baa.txt", "baa"),
   135  			),
   136  			log: []string{"baz.txt"},
   137  			want: tarOf(
   138  				file("baz.txt", "baz"),
   139  				prefetchLandmark(),
   140  				file("foo.txt", "foo"),
   141  				link("bar.txt", "baz.txt"),
   142  				file("baa.txt", "baa"),
   143  			),
   144  		},
   145  		{
   146  			name: "longname",
   147  			in: tarOf(
   148  				file("foo.txt", "foo"),
   149  				file(longname1, "test"),
   150  				dir("bar/"),
   151  				file("bar/bar.txt", "bar"),
   152  				file("bar/baa.txt", "baa"),
   153  				file(fmt.Sprintf("bar/%s", longname2), "test2"),
   154  			),
   155  			log: []string{fmt.Sprintf("bar/%s", longname2), longname1},
   156  			want: tarOf(
   157  				dir("bar/"),
   158  				file(fmt.Sprintf("bar/%s", longname2), "test2"),
   159  				file(longname1, "test"),
   160  				prefetchLandmark(),
   161  				file("foo.txt", "foo"),
   162  				file("bar/bar.txt", "bar"),
   163  				file("bar/baa.txt", "baa"),
   164  			),
   165  		},
   166  		{
   167  			name: "various_types",
   168  			in: tarOf(
   169  				file("foo.txt", "foo"),
   170  				symlink("foo2", "foo.txt"),
   171  				chardev("foochar", 10, 50),
   172  				blockdev("fooblock", 15, 20),
   173  				fifo("fifoo"),
   174  			),
   175  			log: []string{"fifoo", "foo2", "foo.txt", "fooblock"},
   176  			want: tarOf(
   177  				fifo("fifoo"),
   178  				symlink("foo2", "foo.txt"),
   179  				file("foo.txt", "foo"),
   180  				blockdev("fooblock", 15, 20),
   181  				prefetchLandmark(),
   182  				chardev("foochar", 10, 50),
   183  			),
   184  		},
   185  		{
   186  			name: "existing_landmark",
   187  			in: tarOf(
   188  				file("baa.txt", "baa"),
   189  				file("bar.txt", "bar"),
   190  				file("baz.txt", "baz"),
   191  				prefetchLandmark(),
   192  				file("foo.txt", "foo"),
   193  			),
   194  			log: []string{"foo.txt", "bar.txt"},
   195  			want: tarOf(
   196  				file("foo.txt", "foo"),
   197  				file("bar.txt", "bar"),
   198  				prefetchLandmark(),
   199  				file("baa.txt", "baa"),
   200  				file("baz.txt", "baz"),
   201  			),
   202  		},
   203  		{
   204  			name: "existing_landmark_nolog",
   205  			in: tarOf(
   206  				file("baa.txt", "baa"),
   207  				file("bar.txt", "bar"),
   208  				file("baz.txt", "baz"),
   209  				prefetchLandmark(),
   210  				file("foo.txt", "foo"),
   211  			),
   212  			want: tarOf(
   213  				noPrefetchLandmark(),
   214  				file("baa.txt", "baa"),
   215  				file("bar.txt", "bar"),
   216  				file("baz.txt", "baz"),
   217  				file("foo.txt", "foo"),
   218  			),
   219  		},
   220  		{
   221  			name: "existing_noprefetch_landmark",
   222  			in: tarOf(
   223  				noPrefetchLandmark(),
   224  				file("baa.txt", "baa"),
   225  				file("bar.txt", "bar"),
   226  				file("baz.txt", "baz"),
   227  				file("foo.txt", "foo"),
   228  			),
   229  			log: []string{"foo.txt", "bar.txt"},
   230  			want: tarOf(
   231  				file("foo.txt", "foo"),
   232  				file("bar.txt", "bar"),
   233  				prefetchLandmark(),
   234  				file("baa.txt", "baa"),
   235  				file("baz.txt", "baz"),
   236  			),
   237  		},
   238  		{
   239  			name: "existing_noprefetch_landmark_nolog",
   240  			in: tarOf(
   241  				noPrefetchLandmark(),
   242  				file("baa.txt", "baa"),
   243  				file("bar.txt", "bar"),
   244  				file("baz.txt", "baz"),
   245  				file("foo.txt", "foo"),
   246  			),
   247  			want: tarOf(
   248  				noPrefetchLandmark(),
   249  				file("baa.txt", "baa"),
   250  				file("bar.txt", "bar"),
   251  				file("baz.txt", "baz"),
   252  				file("foo.txt", "foo"),
   253  			),
   254  		},
   255  		{
   256  			name: "not_existing_file",
   257  			in: tarOf(
   258  				file("foo.txt", "foo"),
   259  				file("baz.txt", "baz"),
   260  				file("bar.txt", "bar"),
   261  				file("baa.txt", "baa"),
   262  			),
   263  			log:      []string{"baa.txt", "bar.txt", "dummy"},
   264  			want:     tarOf(),
   265  			wantFail: true,
   266  		},
   267  		{
   268  			name: "not_existing_file_allow_fail",
   269  			in: tarOf(
   270  				file("foo.txt", "foo"),
   271  				file("baz.txt", "baz"),
   272  				file("bar.txt", "bar"),
   273  				file("baa.txt", "baa"),
   274  			),
   275  			log: []string{"baa.txt", "dummy1", "bar.txt", "dummy2"},
   276  			want: tarOf(
   277  				file("baa.txt", "baa"),
   278  				file("bar.txt", "bar"),
   279  				prefetchLandmark(),
   280  				file("foo.txt", "foo"),
   281  				file("baz.txt", "baz"),
   282  			),
   283  			allowMissedFiles: []string{"dummy1", "dummy2"},
   284  		},
   285  		{
   286  			name: "duplicated_entry",
   287  			in: tarOf(
   288  				file("foo.txt", "foo"),
   289  				dir("bar/"),
   290  				file("bar/baz.txt", "baz"),
   291  				dir("bar/"),
   292  				file("bar/baz.txt", "baz2"),
   293  			),
   294  			log: []string{"bar/baz.txt"},
   295  			want: tarOf(
   296  				dir("bar/"),
   297  				file("bar/baz.txt", "baz2"),
   298  				prefetchLandmark(),
   299  				file("foo.txt", "foo"),
   300  			),
   301  		},
   302  		{
   303  			name: "hardlink",
   304  			in: tarOf(
   305  				file("baz.txt", "aaaaa"),
   306  				link("bazlink", "baz.txt"),
   307  			),
   308  			log: []string{"bazlink"},
   309  			want: tarOf(
   310  				file("baz.txt", "aaaaa"),
   311  				link("bazlink", "baz.txt"),
   312  				prefetchLandmark(),
   313  			),
   314  		},
   315  		{
   316  			name: "root_relative_file",
   317  			in: tarOf(
   318  				dir("./"),
   319  				file("./foo.txt", "foo"),
   320  				file("./baz.txt", "baz"),
   321  				file("./baa.txt", "baa"),
   322  				link("./bazlink", "./baz.txt"),
   323  			),
   324  			log: []string{"baa.txt", "bazlink"},
   325  			want: tarOf(
   326  				dir("./"),
   327  				file("./baa.txt", "baa"),
   328  				file("./baz.txt", "baz"),
   329  				link("./bazlink", "./baz.txt"),
   330  				prefetchLandmark(),
   331  				file("./foo.txt", "foo"),
   332  			),
   333  		},
   334  		{
   335  			// GNU tar accepts tar containing absolute path
   336  			name: "root_absolute_file",
   337  			in: tarOf(
   338  				dir("/"),
   339  				file("/foo.txt", "foo"),
   340  				file("/baz.txt", "baz"),
   341  				file("/baa.txt", "baa"),
   342  				link("/bazlink", "/baz.txt"),
   343  			),
   344  			log: []string{"baa.txt", "bazlink"},
   345  			want: tarOf(
   346  				dir("/"),
   347  				file("/baa.txt", "baa"),
   348  				file("/baz.txt", "baz"),
   349  				link("/bazlink", "/baz.txt"),
   350  				prefetchLandmark(),
   351  				file("/foo.txt", "foo"),
   352  			),
   353  		},
   354  	}
   355  	for _, tt := range tests {
   356  		for _, srcCompression := range srcCompressions {
   357  			srcCompression := srcCompression
   358  			for _, logprefix := range allowedPrefix {
   359  				logprefix := logprefix
   360  				for _, tarprefix := range allowedPrefix {
   361  					tarprefix := tarprefix
   362  					t.Run(fmt.Sprintf("%s-logprefix=%q-tarprefix=%q-src=%d", tt.name, logprefix, tarprefix, srcCompression), func(t *testing.T) {
   363  						// Sort tar file
   364  						var pfiles []string
   365  						for _, f := range tt.log {
   366  							pfiles = append(pfiles, logprefix+f)
   367  						}
   368  						var opts []Option
   369  						var missedFiles []string
   370  						if tt.allowMissedFiles != nil {
   371  							opts = append(opts, WithAllowPrioritizeNotFound(&missedFiles))
   372  						}
   373  						rc, err := Build(compressBlob(t, buildTar(t, tt.in, tarprefix), srcCompression),
   374  							append(opts, WithPrioritizedFiles(pfiles))...)
   375  						if tt.wantFail {
   376  							if err != nil {
   377  								return
   378  							}
   379  							t.Errorf("This test must fail but succeeded")
   380  							return
   381  						}
   382  						if err != nil {
   383  							t.Errorf("failed to build stargz: %v", err)
   384  						}
   385  						defer rc.Close()
   386  						zr, err := gzip.NewReader(rc)
   387  						if err != nil {
   388  							t.Fatalf("failed to create gzip reader: %v", err)
   389  						}
   390  						if tt.allowMissedFiles != nil {
   391  							want := map[string]struct{}{}
   392  							for _, f := range tt.allowMissedFiles {
   393  								want[logprefix+f] = struct{}{}
   394  							}
   395  							got := map[string]struct{}{}
   396  							for _, f := range missedFiles {
   397  								got[f] = struct{}{}
   398  							}
   399  							if !reflect.DeepEqual(got, want) {
   400  								t.Errorf("unexpected missed files: want %v, got: %v",
   401  									want, got)
   402  								return
   403  							}
   404  						}
   405  						gotTar := tar.NewReader(zr)
   406  
   407  						// Compare all
   408  						wantTar := tar.NewReader(buildTar(t, tt.want, tarprefix))
   409  						for {
   410  							// Fetch and parse next header.
   411  							gotH, wantH, err := next(t, gotTar, wantTar)
   412  							if err != nil {
   413  								if err == io.EOF {
   414  									break
   415  								} else {
   416  									t.Fatalf("Failed to parse tar file: %v", err)
   417  								}
   418  							}
   419  
   420  							if !reflect.DeepEqual(gotH, wantH) {
   421  								t.Errorf("different header (got = name:%q,type:%d,size:%d; want = name:%q,type:%d,size:%d)",
   422  									gotH.Name, gotH.Typeflag, gotH.Size, wantH.Name, wantH.Typeflag, wantH.Size)
   423  								return
   424  
   425  							}
   426  
   427  							got, err := io.ReadAll(gotTar)
   428  							if err != nil {
   429  								t.Fatal("failed to read got tar payload")
   430  							}
   431  							want, err := io.ReadAll(wantTar)
   432  							if err != nil {
   433  								t.Fatal("failed to read want tar payload")
   434  							}
   435  							if !bytes.Equal(got, want) {
   436  								t.Errorf("different payload (got = %q; want = %q)", string(got), string(want))
   437  								return
   438  							}
   439  						}
   440  					})
   441  				}
   442  			}
   443  		}
   444  	}
   445  }
   446  
   447  func next(t *testing.T, a *tar.Reader, b *tar.Reader) (ah *tar.Header, bh *tar.Header, err error) {
   448  	eofA, eofB := false, false
   449  
   450  	ah, err = nextWithSkipTOC(a)
   451  	if err != nil {
   452  		if err == io.EOF {
   453  			eofA = true
   454  		} else {
   455  			t.Fatalf("Failed to parse tar file: %q", err)
   456  		}
   457  	}
   458  
   459  	bh, err = nextWithSkipTOC(b)
   460  	if err != nil {
   461  		if err == io.EOF {
   462  			eofB = true
   463  		} else {
   464  			t.Fatalf("Failed to parse tar file: %q", err)
   465  		}
   466  	}
   467  
   468  	if eofA != eofB {
   469  		if !eofA {
   470  			t.Logf("a = %q", ah.Name)
   471  		}
   472  		if !eofB {
   473  			t.Logf("b = %q", bh.Name)
   474  		}
   475  		t.Fatalf("got eof %t != %t", eofB, eofA)
   476  	}
   477  	if eofA {
   478  		err = io.EOF
   479  	}
   480  
   481  	return
   482  }
   483  
   484  func nextWithSkipTOC(a *tar.Reader) (h *tar.Header, err error) {
   485  	if h, err = a.Next(); err != nil {
   486  		return
   487  	}
   488  	if h.Name == TOCTarName {
   489  		return nextWithSkipTOC(a)
   490  	}
   491  	return
   492  }
   493  
   494  func longstring(size int) (str string) {
   495  	unit := "long"
   496  	for i := 0; i < size/len(unit)+1; i++ {
   497  		str = fmt.Sprintf("%s%s", str, unit)
   498  	}
   499  
   500  	return str[:size]
   501  }
   502  
   503  func TestCountReader(t *testing.T) {
   504  	tests := []struct {
   505  		name    string
   506  		ops     func(*countReadSeeker) error
   507  		wantPos int64
   508  	}{
   509  		{
   510  			name: "nop",
   511  			ops: func(pw *countReadSeeker) error {
   512  				return nil
   513  			},
   514  			wantPos: 0,
   515  		},
   516  		{
   517  			name: "read",
   518  			ops: func(pw *countReadSeeker) error {
   519  				size := 5
   520  				if _, err := pw.Read(make([]byte, size)); err != nil {
   521  					return err
   522  				}
   523  				return nil
   524  			},
   525  			wantPos: 5,
   526  		},
   527  		{
   528  			name: "readtwice",
   529  			ops: func(pw *countReadSeeker) error {
   530  				size1, size2 := 5, 3
   531  				if _, err := pw.Read(make([]byte, size1)); err != nil {
   532  					if err != io.EOF {
   533  						return err
   534  					}
   535  				}
   536  				if _, err := pw.Read(make([]byte, size2)); err != nil {
   537  					if err != io.EOF {
   538  						return err
   539  					}
   540  				}
   541  				return nil
   542  			},
   543  			wantPos: 8,
   544  		},
   545  		{
   546  			name: "seek_start",
   547  			ops: func(pw *countReadSeeker) error {
   548  				size := int64(5)
   549  				if _, err := pw.Seek(size, io.SeekStart); err != nil {
   550  					if err != io.EOF {
   551  						return err
   552  					}
   553  				}
   554  				return nil
   555  			},
   556  			wantPos: 5,
   557  		},
   558  		{
   559  			name: "seek_start_twice",
   560  			ops: func(pw *countReadSeeker) error {
   561  				size1, size2 := int64(5), int64(3)
   562  				if _, err := pw.Seek(size1, io.SeekStart); err != nil {
   563  					if err != io.EOF {
   564  						return err
   565  					}
   566  				}
   567  				if _, err := pw.Seek(size2, io.SeekStart); err != nil {
   568  					if err != io.EOF {
   569  						return err
   570  					}
   571  				}
   572  				return nil
   573  			},
   574  			wantPos: 3,
   575  		},
   576  		{
   577  			name: "seek_current",
   578  			ops: func(pw *countReadSeeker) error {
   579  				size := int64(5)
   580  				if _, err := pw.Seek(size, io.SeekCurrent); err != nil {
   581  					if err != io.EOF {
   582  						return err
   583  					}
   584  				}
   585  				return nil
   586  			},
   587  			wantPos: 5,
   588  		},
   589  		{
   590  			name: "seek_current_twice",
   591  			ops: func(pw *countReadSeeker) error {
   592  				size1, size2 := int64(5), int64(3)
   593  				if _, err := pw.Seek(size1, io.SeekCurrent); err != nil {
   594  					if err != io.EOF {
   595  						return err
   596  					}
   597  				}
   598  				if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
   599  					if err != io.EOF {
   600  						return err
   601  					}
   602  				}
   603  				return nil
   604  			},
   605  			wantPos: 8,
   606  		},
   607  		{
   608  			name: "seek_current_twice_negative",
   609  			ops: func(pw *countReadSeeker) error {
   610  				size1, size2 := int64(5), int64(-3)
   611  				if _, err := pw.Seek(size1, io.SeekCurrent); err != nil {
   612  					if err != io.EOF {
   613  						return err
   614  					}
   615  				}
   616  				if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
   617  					if err != io.EOF {
   618  						return err
   619  					}
   620  				}
   621  				return nil
   622  			},
   623  			wantPos: 2,
   624  		},
   625  		{
   626  			name: "mixed",
   627  			ops: func(pw *countReadSeeker) error {
   628  				size1, size2, size3, size4, size5 := int64(5), int64(-3), int64(4), int64(-1), int64(6)
   629  				if _, err := pw.Read(make([]byte, size1)); err != nil {
   630  					if err != io.EOF {
   631  						return err
   632  					}
   633  				}
   634  				if _, err := pw.Seek(size2, io.SeekCurrent); err != nil {
   635  					if err != io.EOF {
   636  						return err
   637  					}
   638  				}
   639  				if _, err := pw.Seek(size3, io.SeekStart); err != nil {
   640  					if err != io.EOF {
   641  						return err
   642  					}
   643  				}
   644  				if _, err := pw.Seek(size4, io.SeekCurrent); err != nil {
   645  					if err != io.EOF {
   646  						return err
   647  					}
   648  				}
   649  				if _, err := pw.Read(make([]byte, size5)); err != nil {
   650  					if err != io.EOF {
   651  						return err
   652  					}
   653  				}
   654  				return nil
   655  			},
   656  			wantPos: 9,
   657  		},
   658  	}
   659  
   660  	for _, tt := range tests {
   661  		t.Run(tt.name, func(t *testing.T) {
   662  			pw, err := newCountReadSeeker(bytes.NewReader(make([]byte, 100)))
   663  			if err != nil {
   664  				t.Fatalf("failed to make position watcher: %q", err)
   665  			}
   666  
   667  			if err := tt.ops(pw); err != nil {
   668  				t.Fatalf("failed to operate position watcher: %q", err)
   669  			}
   670  
   671  			gotPos := pw.currentPos()
   672  			if tt.wantPos != gotPos {
   673  				t.Errorf("current position: %d; want %d", gotPos, tt.wantPos)
   674  			}
   675  		})
   676  	}
   677  
   678  }
   679  

View as plain text