github.com/cwandrews/docker@v1.7.0/pkg/tarsum/tarsum_test.go (about)

     1  package tarsum
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"crypto/md5"
     8  	"crypto/rand"
     9  	"crypto/sha1"
    10  	"crypto/sha256"
    11  	"crypto/sha512"
    12  	"encoding/hex"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"testing"
    18  )
    19  
    20  type testLayer struct {
    21  	filename string
    22  	options  *sizedOptions
    23  	jsonfile string
    24  	gzip     bool
    25  	tarsum   string
    26  	version  Version
    27  	hash     THash
    28  }
    29  
    30  var testLayers = []testLayer{
    31  	{
    32  		filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar",
    33  		jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json",
    34  		version:  Version0,
    35  		tarsum:   "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"},
    36  	{
    37  		filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar",
    38  		jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json",
    39  		version:  VersionDev,
    40  		tarsum:   "tarsum.dev+sha256:486b86e25c4db4551228154848bc4663b15dd95784b1588980f4ba1cb42e83e9"},
    41  	{
    42  		filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar",
    43  		jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json",
    44  		gzip:     true,
    45  		tarsum:   "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"},
    46  	{
    47  		// Tests existing version of TarSum when xattrs are present
    48  		filename: "testdata/xattr/layer.tar",
    49  		jsonfile: "testdata/xattr/json",
    50  		version:  Version0,
    51  		tarsum:   "tarsum+sha256:e86f81a4d552f13039b1396ed03ca968ea9717581f9577ef1876ea6ff9b38c98"},
    52  	{
    53  		// Tests next version of TarSum when xattrs are present
    54  		filename: "testdata/xattr/layer.tar",
    55  		jsonfile: "testdata/xattr/json",
    56  		version:  VersionDev,
    57  		tarsum:   "tarsum.dev+sha256:6235cd3a2afb7501bac541772a3d61a3634e95bc90bb39a4676e2cb98d08390d"},
    58  	{
    59  		filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar",
    60  		jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json",
    61  		tarsum:   "tarsum+sha256:ac672ee85da9ab7f9667ae3c32841d3e42f33cc52c273c23341dabba1c8b0c8b"},
    62  	{
    63  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
    64  		tarsum:  "tarsum+sha256:8bf12d7e67c51ee2e8306cba569398b1b9f419969521a12ffb9d8875e8836738"},
    65  	{
    66  		// this tar has two files with the same path
    67  		filename: "testdata/collision/collision-0.tar",
    68  		tarsum:   "tarsum+sha256:08653904a68d3ab5c59e65ef58c49c1581caa3c34744f8d354b3f575ea04424a"},
    69  	{
    70  		// this tar has the same two files (with the same path), but reversed order. ensuring is has different hash than above
    71  		filename: "testdata/collision/collision-1.tar",
    72  		tarsum:   "tarsum+sha256:b51c13fbefe158b5ce420d2b930eef54c5cd55c50a2ee4abdddea8fa9f081e0d"},
    73  	{
    74  		// this tar has newer of collider-0.tar, ensuring is has different hash
    75  		filename: "testdata/collision/collision-2.tar",
    76  		tarsum:   "tarsum+sha256:381547080919bb82691e995508ae20ed33ce0f6948d41cafbeb70ce20c73ee8e"},
    77  	{
    78  		// this tar has newer of collider-1.tar, ensuring is has different hash
    79  		filename: "testdata/collision/collision-3.tar",
    80  		tarsum:   "tarsum+sha256:f886e431c08143164a676805205979cd8fa535dfcef714db5515650eea5a7c0f"},
    81  	{
    82  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
    83  		tarsum:  "tarsum+md5:0d7529ec7a8360155b48134b8e599f53",
    84  		hash:    md5THash,
    85  	},
    86  	{
    87  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
    88  		tarsum:  "tarsum+sha1:f1fee39c5925807ff75ef1925e7a23be444ba4df",
    89  		hash:    sha1Hash,
    90  	},
    91  	{
    92  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
    93  		tarsum:  "tarsum+sha224:6319390c0b061d639085d8748b14cd55f697cf9313805218b21cf61c",
    94  		hash:    sha224Hash,
    95  	},
    96  	{
    97  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
    98  		tarsum:  "tarsum+sha384:a578ce3ce29a2ae03b8ed7c26f47d0f75b4fc849557c62454be4b5ffd66ba021e713b48ce71e947b43aab57afd5a7636",
    99  		hash:    sha384Hash,
   100  	},
   101  	{
   102  		options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory)
   103  		tarsum:  "tarsum+sha512:e9bfb90ca5a4dfc93c46ee061a5cf9837de6d2fdf82544d6460d3147290aecfabf7b5e415b9b6e72db9b8941f149d5d69fb17a394cbfaf2eac523bd9eae21855",
   104  		hash:    sha512Hash,
   105  	},
   106  }
   107  
   108  type sizedOptions struct {
   109  	num      int64
   110  	size     int64
   111  	isRand   bool
   112  	realFile bool
   113  }
   114  
   115  // make a tar:
   116  // * num is the number of files the tar should have
   117  // * size is the bytes per file
   118  // * isRand is whether the contents of the files should be a random chunk (otherwise it's all zeros)
   119  // * realFile will write to a TempFile, instead of an in memory buffer
   120  func sizedTar(opts sizedOptions) io.Reader {
   121  	var (
   122  		fh  io.ReadWriter
   123  		err error
   124  	)
   125  	if opts.realFile {
   126  		fh, err = ioutil.TempFile("", "tarsum")
   127  		if err != nil {
   128  			return nil
   129  		}
   130  	} else {
   131  		fh = bytes.NewBuffer([]byte{})
   132  	}
   133  	tarW := tar.NewWriter(fh)
   134  	defer tarW.Close()
   135  	for i := int64(0); i < opts.num; i++ {
   136  		err := tarW.WriteHeader(&tar.Header{
   137  			Name: fmt.Sprintf("/testdata%d", i),
   138  			Mode: 0755,
   139  			Uid:  0,
   140  			Gid:  0,
   141  			Size: opts.size,
   142  		})
   143  		if err != nil {
   144  			return nil
   145  		}
   146  		var rBuf []byte
   147  		if opts.isRand {
   148  			rBuf = make([]byte, 8)
   149  			_, err = rand.Read(rBuf)
   150  			if err != nil {
   151  				return nil
   152  			}
   153  		} else {
   154  			rBuf = []byte{0, 0, 0, 0, 0, 0, 0, 0}
   155  		}
   156  
   157  		for i := int64(0); i < opts.size/int64(8); i++ {
   158  			tarW.Write(rBuf)
   159  		}
   160  	}
   161  	return fh
   162  }
   163  
   164  func emptyTarSum(gzip bool) (TarSum, error) {
   165  	reader, writer := io.Pipe()
   166  	tarWriter := tar.NewWriter(writer)
   167  
   168  	// Immediately close tarWriter and write-end of the
   169  	// Pipe in a separate goroutine so we don't block.
   170  	go func() {
   171  		tarWriter.Close()
   172  		writer.Close()
   173  	}()
   174  
   175  	return NewTarSum(reader, !gzip, Version0)
   176  }
   177  
   178  // TestEmptyTar tests that tarsum does not fail to read an empty tar
   179  // and correctly returns the hex digest of an empty hash.
   180  func TestEmptyTar(t *testing.T) {
   181  	// Test without gzip.
   182  	ts, err := emptyTarSum(false)
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  
   187  	zeroBlock := make([]byte, 1024)
   188  	buf := new(bytes.Buffer)
   189  
   190  	n, err := io.Copy(buf, ts)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), zeroBlock) {
   196  		t.Fatalf("tarSum did not write the correct number of zeroed bytes: %d", n)
   197  	}
   198  
   199  	expectedSum := ts.Version().String() + "+sha256:" + hex.EncodeToString(sha256.New().Sum(nil))
   200  	resultSum := ts.Sum(nil)
   201  
   202  	if resultSum != expectedSum {
   203  		t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum)
   204  	}
   205  
   206  	// Test with gzip.
   207  	ts, err = emptyTarSum(true)
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	buf.Reset()
   212  
   213  	n, err = io.Copy(buf, ts)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	bufgz := new(bytes.Buffer)
   219  	gz := gzip.NewWriter(bufgz)
   220  	n, err = io.Copy(gz, bytes.NewBuffer(zeroBlock))
   221  	gz.Close()
   222  	gzBytes := bufgz.Bytes()
   223  
   224  	if n != int64(len(zeroBlock)) || !bytes.Equal(buf.Bytes(), gzBytes) {
   225  		t.Fatalf("tarSum did not write the correct number of gzipped-zeroed bytes: %d", n)
   226  	}
   227  
   228  	resultSum = ts.Sum(nil)
   229  
   230  	if resultSum != expectedSum {
   231  		t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum)
   232  	}
   233  
   234  	// Test without ever actually writing anything.
   235  	if ts, err = NewTarSum(bytes.NewReader([]byte{}), true, Version0); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	resultSum = ts.Sum(nil)
   240  
   241  	if resultSum != expectedSum {
   242  		t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum)
   243  	}
   244  }
   245  
   246  var (
   247  	md5THash   = NewTHash("md5", md5.New)
   248  	sha1Hash   = NewTHash("sha1", sha1.New)
   249  	sha224Hash = NewTHash("sha224", sha256.New224)
   250  	sha384Hash = NewTHash("sha384", sha512.New384)
   251  	sha512Hash = NewTHash("sha512", sha512.New)
   252  )
   253  
   254  func TestTarSums(t *testing.T) {
   255  	for _, layer := range testLayers {
   256  		var (
   257  			fh  io.Reader
   258  			err error
   259  		)
   260  		if len(layer.filename) > 0 {
   261  			fh, err = os.Open(layer.filename)
   262  			if err != nil {
   263  				t.Errorf("failed to open %s: %s", layer.filename, err)
   264  				continue
   265  			}
   266  		} else if layer.options != nil {
   267  			fh = sizedTar(*layer.options)
   268  		} else {
   269  			// What else is there to test?
   270  			t.Errorf("what to do with %#v", layer)
   271  			continue
   272  		}
   273  		if file, ok := fh.(*os.File); ok {
   274  			defer file.Close()
   275  		}
   276  
   277  		var ts TarSum
   278  		if layer.hash == nil {
   279  			//                           double negatives!
   280  			ts, err = NewTarSum(fh, !layer.gzip, layer.version)
   281  		} else {
   282  			ts, err = NewTarSumHash(fh, !layer.gzip, layer.version, layer.hash)
   283  		}
   284  		if err != nil {
   285  			t.Errorf("%q :: %q", err, layer.filename)
   286  			continue
   287  		}
   288  
   289  		// Read variable number of bytes to test dynamic buffer
   290  		dBuf := make([]byte, 1)
   291  		_, err = ts.Read(dBuf)
   292  		if err != nil {
   293  			t.Errorf("failed to read 1B from %s: %s", layer.filename, err)
   294  			continue
   295  		}
   296  		dBuf = make([]byte, 16*1024)
   297  		_, err = ts.Read(dBuf)
   298  		if err != nil {
   299  			t.Errorf("failed to read 16KB from %s: %s", layer.filename, err)
   300  			continue
   301  		}
   302  
   303  		// Read and discard remaining bytes
   304  		_, err = io.Copy(ioutil.Discard, ts)
   305  		if err != nil {
   306  			t.Errorf("failed to copy from %s: %s", layer.filename, err)
   307  			continue
   308  		}
   309  		var gotSum string
   310  		if len(layer.jsonfile) > 0 {
   311  			jfh, err := os.Open(layer.jsonfile)
   312  			if err != nil {
   313  				t.Errorf("failed to open %s: %s", layer.jsonfile, err)
   314  				continue
   315  			}
   316  			buf, err := ioutil.ReadAll(jfh)
   317  			if err != nil {
   318  				t.Errorf("failed to readAll %s: %s", layer.jsonfile, err)
   319  				continue
   320  			}
   321  			gotSum = ts.Sum(buf)
   322  		} else {
   323  			gotSum = ts.Sum(nil)
   324  		}
   325  
   326  		if layer.tarsum != gotSum {
   327  			t.Errorf("expecting [%s], but got [%s]", layer.tarsum, gotSum)
   328  		}
   329  	}
   330  }
   331  
   332  func TestIteration(t *testing.T) {
   333  	headerTests := []struct {
   334  		expectedSum string // TODO(vbatts) it would be nice to get individual sums of each
   335  		version     Version
   336  		hdr         *tar.Header
   337  		data        []byte
   338  	}{
   339  		{
   340  			"tarsum+sha256:626c4a2e9a467d65c33ae81f7f3dedd4de8ccaee72af73223c4bc4718cbc7bbd",
   341  			Version0,
   342  			&tar.Header{
   343  				Name:     "file.txt",
   344  				Size:     0,
   345  				Typeflag: tar.TypeReg,
   346  				Devminor: 0,
   347  				Devmajor: 0,
   348  			},
   349  			[]byte(""),
   350  		},
   351  		{
   352  			"tarsum.dev+sha256:6ffd43a1573a9913325b4918e124ee982a99c0f3cba90fc032a65f5e20bdd465",
   353  			VersionDev,
   354  			&tar.Header{
   355  				Name:     "file.txt",
   356  				Size:     0,
   357  				Typeflag: tar.TypeReg,
   358  				Devminor: 0,
   359  				Devmajor: 0,
   360  			},
   361  			[]byte(""),
   362  		},
   363  		{
   364  			"tarsum.dev+sha256:b38166c059e11fb77bef30bf16fba7584446e80fcc156ff46d47e36c5305d8ef",
   365  			VersionDev,
   366  			&tar.Header{
   367  				Name:     "another.txt",
   368  				Uid:      1000,
   369  				Gid:      1000,
   370  				Uname:    "slartibartfast",
   371  				Gname:    "users",
   372  				Size:     4,
   373  				Typeflag: tar.TypeReg,
   374  				Devminor: 0,
   375  				Devmajor: 0,
   376  			},
   377  			[]byte("test"),
   378  		},
   379  		{
   380  			"tarsum.dev+sha256:4cc2e71ac5d31833ab2be9b4f7842a14ce595ec96a37af4ed08f87bc374228cd",
   381  			VersionDev,
   382  			&tar.Header{
   383  				Name:     "xattrs.txt",
   384  				Uid:      1000,
   385  				Gid:      1000,
   386  				Uname:    "slartibartfast",
   387  				Gname:    "users",
   388  				Size:     4,
   389  				Typeflag: tar.TypeReg,
   390  				Xattrs: map[string]string{
   391  					"user.key1": "value1",
   392  					"user.key2": "value2",
   393  				},
   394  			},
   395  			[]byte("test"),
   396  		},
   397  		{
   398  			"tarsum.dev+sha256:65f4284fa32c0d4112dd93c3637697805866415b570587e4fd266af241503760",
   399  			VersionDev,
   400  			&tar.Header{
   401  				Name:     "xattrs.txt",
   402  				Uid:      1000,
   403  				Gid:      1000,
   404  				Uname:    "slartibartfast",
   405  				Gname:    "users",
   406  				Size:     4,
   407  				Typeflag: tar.TypeReg,
   408  				Xattrs: map[string]string{
   409  					"user.KEY1": "value1", // adding different case to ensure different sum
   410  					"user.key2": "value2",
   411  				},
   412  			},
   413  			[]byte("test"),
   414  		},
   415  		{
   416  			"tarsum+sha256:c12bb6f1303a9ddbf4576c52da74973c00d14c109bcfa76b708d5da1154a07fa",
   417  			Version0,
   418  			&tar.Header{
   419  				Name:     "xattrs.txt",
   420  				Uid:      1000,
   421  				Gid:      1000,
   422  				Uname:    "slartibartfast",
   423  				Gname:    "users",
   424  				Size:     4,
   425  				Typeflag: tar.TypeReg,
   426  				Xattrs: map[string]string{
   427  					"user.NOT": "CALCULATED",
   428  				},
   429  			},
   430  			[]byte("test"),
   431  		},
   432  	}
   433  	for _, htest := range headerTests {
   434  		s, err := renderSumForHeader(htest.version, htest.hdr, htest.data)
   435  		if err != nil {
   436  			t.Fatal(err)
   437  		}
   438  
   439  		if s != htest.expectedSum {
   440  			t.Errorf("expected sum: %q, got: %q", htest.expectedSum, s)
   441  		}
   442  	}
   443  
   444  }
   445  
   446  func renderSumForHeader(v Version, h *tar.Header, data []byte) (string, error) {
   447  	buf := bytes.NewBuffer(nil)
   448  	// first build our test tar
   449  	tw := tar.NewWriter(buf)
   450  	if err := tw.WriteHeader(h); err != nil {
   451  		return "", err
   452  	}
   453  	if _, err := tw.Write(data); err != nil {
   454  		return "", err
   455  	}
   456  	tw.Close()
   457  
   458  	ts, err := NewTarSum(buf, true, v)
   459  	if err != nil {
   460  		return "", err
   461  	}
   462  	tr := tar.NewReader(ts)
   463  	for {
   464  		hdr, err := tr.Next()
   465  		if hdr == nil || err == io.EOF {
   466  			// Signals the end of the archive.
   467  			break
   468  		}
   469  		if err != nil {
   470  			return "", err
   471  		}
   472  		if _, err = io.Copy(ioutil.Discard, tr); err != nil {
   473  			return "", err
   474  		}
   475  	}
   476  	return ts.Sum(nil), nil
   477  }
   478  
   479  func Benchmark9kTar(b *testing.B) {
   480  	buf := bytes.NewBuffer([]byte{})
   481  	fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar")
   482  	if err != nil {
   483  		b.Error(err)
   484  		return
   485  	}
   486  	n, err := io.Copy(buf, fh)
   487  	fh.Close()
   488  
   489  	reader := bytes.NewReader(buf.Bytes())
   490  
   491  	b.SetBytes(n)
   492  	b.ResetTimer()
   493  	for i := 0; i < b.N; i++ {
   494  		reader.Seek(0, 0)
   495  		ts, err := NewTarSum(reader, true, Version0)
   496  		if err != nil {
   497  			b.Error(err)
   498  			return
   499  		}
   500  		io.Copy(ioutil.Discard, ts)
   501  		ts.Sum(nil)
   502  	}
   503  }
   504  
   505  func Benchmark9kTarGzip(b *testing.B) {
   506  	buf := bytes.NewBuffer([]byte{})
   507  	fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar")
   508  	if err != nil {
   509  		b.Error(err)
   510  		return
   511  	}
   512  	n, err := io.Copy(buf, fh)
   513  	fh.Close()
   514  
   515  	reader := bytes.NewReader(buf.Bytes())
   516  
   517  	b.SetBytes(n)
   518  	b.ResetTimer()
   519  	for i := 0; i < b.N; i++ {
   520  		reader.Seek(0, 0)
   521  		ts, err := NewTarSum(reader, false, Version0)
   522  		if err != nil {
   523  			b.Error(err)
   524  			return
   525  		}
   526  		io.Copy(ioutil.Discard, ts)
   527  		ts.Sum(nil)
   528  	}
   529  }
   530  
   531  // this is a single big file in the tar archive
   532  func Benchmark1mbSingleFileTar(b *testing.B) {
   533  	benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, false)
   534  }
   535  
   536  // this is a single big file in the tar archive
   537  func Benchmark1mbSingleFileTarGzip(b *testing.B) {
   538  	benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, true)
   539  }
   540  
   541  // this is 1024 1k files in the tar archive
   542  func Benchmark1kFilesTar(b *testing.B) {
   543  	benchmarkTar(b, sizedOptions{1024, 1024, true, true}, false)
   544  }
   545  
   546  // this is 1024 1k files in the tar archive
   547  func Benchmark1kFilesTarGzip(b *testing.B) {
   548  	benchmarkTar(b, sizedOptions{1024, 1024, true, true}, true)
   549  }
   550  
   551  func benchmarkTar(b *testing.B, opts sizedOptions, isGzip bool) {
   552  	var fh *os.File
   553  	tarReader := sizedTar(opts)
   554  	if br, ok := tarReader.(*os.File); ok {
   555  		fh = br
   556  	}
   557  	defer os.Remove(fh.Name())
   558  	defer fh.Close()
   559  
   560  	b.SetBytes(opts.size * opts.num)
   561  	b.ResetTimer()
   562  	for i := 0; i < b.N; i++ {
   563  		ts, err := NewTarSum(fh, !isGzip, Version0)
   564  		if err != nil {
   565  			b.Error(err)
   566  			return
   567  		}
   568  		io.Copy(ioutil.Discard, ts)
   569  		ts.Sum(nil)
   570  		fh.Seek(0, 0)
   571  	}
   572  }