github.com/christopherobin/docker@v1.6.2/pkg/tarsum/tarsum_test.go (about)

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