github.com/openshift/source-to-image@v1.4.1-0.20240516041539-bf52fc02204e/pkg/tar/tar_test.go (about)

     1  package tar
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"testing"
    13  	"time"
    14  
    15  	s2ierr "github.com/openshift/source-to-image/pkg/errors"
    16  	"github.com/openshift/source-to-image/pkg/util/fs"
    17  )
    18  
    19  type dirDesc struct {
    20  	name         string
    21  	modifiedDate time.Time
    22  	mode         os.FileMode
    23  }
    24  
    25  type fileDesc struct {
    26  	name         string
    27  	modifiedDate time.Time
    28  	mode         os.FileMode
    29  	content      string
    30  	shouldSkip   bool
    31  	target       string
    32  }
    33  
    34  type linkDesc struct {
    35  	linkName string
    36  	fileName string
    37  }
    38  
    39  func createTestFiles(baseDir string, dirs []dirDesc, files []fileDesc, links []linkDesc) error {
    40  	for _, dd := range dirs {
    41  		fileName := filepath.Join(baseDir, dd.name)
    42  		err := os.Mkdir(fileName, dd.mode)
    43  		if err != nil {
    44  			return err
    45  		}
    46  		os.Chmod(fileName, dd.mode) // umask
    47  	}
    48  	for _, fd := range files {
    49  		fileName := filepath.Join(baseDir, fd.name)
    50  		err := ioutil.WriteFile(fileName, []byte(fd.content), fd.mode)
    51  		if err != nil {
    52  			return err
    53  		}
    54  		os.Chmod(fileName, fd.mode)
    55  		os.Chtimes(fileName, fd.modifiedDate, fd.modifiedDate)
    56  	}
    57  	for _, ld := range links {
    58  		linkName := filepath.Join(baseDir, ld.linkName)
    59  		if err := os.MkdirAll(filepath.Dir(linkName), 0700); err != nil {
    60  			return err
    61  		}
    62  		if err := os.Symlink(ld.fileName, linkName); err != nil {
    63  			return err
    64  		}
    65  	}
    66  	for _, dd := range dirs {
    67  		fileName := filepath.Join(baseDir, dd.name)
    68  		os.Chtimes(fileName, dd.modifiedDate, dd.modifiedDate)
    69  	}
    70  	return nil
    71  }
    72  
    73  func verifyTarFile(t *testing.T, filename string, dirs []dirDesc, files []fileDesc, links []linkDesc) {
    74  	if runtime.GOOS == "windows" {
    75  		for i := range files {
    76  			if files[i].mode&0700 == 0400 {
    77  				files[i].mode = 0444
    78  			} else {
    79  				files[i].mode = 0666
    80  			}
    81  		}
    82  		for i := range dirs {
    83  			if dirs[i].mode&0700 == 0500 {
    84  				dirs[i].mode = 0555
    85  			} else {
    86  				dirs[i].mode = 0777
    87  			}
    88  		}
    89  	}
    90  	dirsToVerify := make(map[string]dirDesc)
    91  	for _, dd := range dirs {
    92  		dirsToVerify[dd.name] = dd
    93  	}
    94  	filesToVerify := make(map[string]fileDesc)
    95  	for _, fd := range files {
    96  		if !fd.shouldSkip {
    97  			filesToVerify[fd.name] = fd
    98  		}
    99  	}
   100  	linksToVerify := make(map[string]linkDesc)
   101  	for _, ld := range links {
   102  		linksToVerify[ld.linkName] = ld
   103  	}
   104  
   105  	file, err := os.Open(filename)
   106  	defer file.Close()
   107  	if err != nil {
   108  		t.Fatalf("Cannot open tar file %q: %v", filename, err)
   109  	}
   110  	tr := tar.NewReader(file)
   111  	for {
   112  		hdr, err := tr.Next()
   113  		if hdr == nil {
   114  			break
   115  		}
   116  		if err != nil {
   117  			t.Fatalf("Error reading tar %q: %v", filename, err)
   118  		}
   119  		finfo := hdr.FileInfo()
   120  		if dd, ok := dirsToVerify[hdr.Name]; ok {
   121  			delete(dirsToVerify, hdr.Name)
   122  			if finfo.Mode()&os.ModeDir == 0 {
   123  				t.Errorf("Incorrect dir %q", finfo.Name())
   124  			}
   125  			if finfo.Mode().Perm() != dd.mode {
   126  				t.Errorf("Dir %q from tar %q does not match expected mode. Expected: %v, actual: %v",
   127  					hdr.Name, filename, dd.mode, finfo.Mode().Perm())
   128  			}
   129  			if !dd.modifiedDate.IsZero() && finfo.ModTime().UTC() != dd.modifiedDate {
   130  				t.Errorf("Dir %q from tar %q does not match expected modified date. Expected: %v, actual: %v",
   131  					hdr.Name, filename, dd.modifiedDate, finfo.ModTime().UTC())
   132  			}
   133  		} else if fd, ok := filesToVerify[hdr.Name]; ok {
   134  			delete(filesToVerify, hdr.Name)
   135  			if finfo.Mode().Perm() != fd.mode {
   136  				t.Errorf("File %q from tar %q does not match expected mode. Expected: %v, actual: %v",
   137  					hdr.Name, filename, fd.mode, finfo.Mode().Perm())
   138  			}
   139  			if !fd.modifiedDate.IsZero() && finfo.ModTime().UTC() != fd.modifiedDate {
   140  				t.Errorf("File %q from tar %q does not match expected modified date. Expected: %v, actual: %v",
   141  					hdr.Name, filename, fd.modifiedDate, finfo.ModTime().UTC())
   142  			}
   143  			fileBytes, err := ioutil.ReadAll(tr)
   144  			if err != nil {
   145  				t.Fatalf("Error reading tar %q: %v", filename, err)
   146  			}
   147  			fileContent := string(fileBytes)
   148  			if fileContent != fd.content {
   149  				t.Errorf("Content for file %q in tar %q doesn't match expected value. Expected: %q, Actual: %q",
   150  					finfo.Name(), filename, fd.content, fileContent)
   151  			}
   152  		} else if ld, ok := linksToVerify[hdr.Name]; ok {
   153  			delete(linksToVerify, hdr.Name)
   154  			if finfo.Mode()&os.ModeSymlink == 0 {
   155  				t.Errorf("Incorrect link %q", finfo.Name())
   156  			}
   157  			if hdr.Linkname != ld.fileName {
   158  				t.Errorf("Incorrect link location. Expected: %q, Actual %q", ld.fileName, hdr.Linkname)
   159  			}
   160  		} else {
   161  			t.Errorf("Cannot find file %q from tar in files to verify.", hdr.Name)
   162  		}
   163  	}
   164  
   165  	if len(filesToVerify) > 0 || len(linksToVerify) > 0 {
   166  		t.Errorf("Did not find all expected files in tar: fileToVerify %v, linksToVerify %v", filesToVerify, linksToVerify)
   167  	}
   168  }
   169  
   170  func TestCreateTarStreamIncludeParentDir(t *testing.T) {
   171  	tempDir, err := ioutil.TempDir("", "testtar")
   172  	defer os.RemoveAll(tempDir)
   173  	if err != nil {
   174  		t.Fatalf("Cannot create temp directory for test: %v", err)
   175  	}
   176  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   177  	testDirs := []dirDesc{
   178  		{"dir01", modificationDate, 0700},
   179  		{"dir01/.git", modificationDate, 0755},
   180  		{"dir01/dir02", modificationDate, 0755},
   181  		{"dir01/dir03", modificationDate, 0775},
   182  	}
   183  	testFiles := []fileDesc{
   184  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   185  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   186  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   187  		{"dir01/.git/hello.txt", modificationDate, 0600, "Ignore file content", true, ""},
   188  	}
   189  	if err = createTestFiles(tempDir, testDirs, testFiles, []linkDesc{}); err != nil {
   190  		t.Fatalf("Cannot create test files: %v", err)
   191  	}
   192  	th := New(fs.NewFileSystem())
   193  	tarFile, err := ioutil.TempFile("", "testtarout")
   194  	if err != nil {
   195  		t.Fatalf("Unable to create temporary file %v", err)
   196  	}
   197  	defer os.Remove(tarFile.Name())
   198  	err = th.CreateTarStream(tempDir, true, tarFile)
   199  	if err != nil {
   200  		t.Fatalf("Unable to create tar file %v", err)
   201  	}
   202  	tarFile.Close()
   203  	for i := range testDirs {
   204  		testDirs[i].name = filepath.ToSlash(filepath.Join(filepath.Base(tempDir), testDirs[i].name))
   205  	}
   206  	for i := range testFiles {
   207  		testFiles[i].name = filepath.ToSlash(filepath.Join(filepath.Base(tempDir), testFiles[i].name))
   208  	}
   209  	verifyTarFile(t, tarFile.Name(), testDirs, testFiles, []linkDesc{})
   210  }
   211  
   212  func TestCreateTar(t *testing.T) {
   213  	th := New(fs.NewFileSystem())
   214  	tempDir, err := ioutil.TempDir("", "testtar")
   215  	defer os.RemoveAll(tempDir)
   216  	if err != nil {
   217  		t.Fatalf("Cannot create temp directory for test: %v", err)
   218  	}
   219  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   220  	testDirs := []dirDesc{
   221  		{"dir01", modificationDate, 0700},
   222  		{"dir01/.git", modificationDate, 0755},
   223  		{"dir01/dir02", modificationDate, 0755},
   224  		{"dir01/dir03", modificationDate, 0775},
   225  		{"link", modificationDate, 0775},
   226  	}
   227  	testFiles := []fileDesc{
   228  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   229  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   230  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   231  		{"dir01/.git/hello.txt", modificationDate, 0600, "Ignore file content", true, ""},
   232  		{"dir01/dir03/tëst3.txt", modificationDate, 0444, "Test utf-8 file header content", false, ""},
   233  	}
   234  	testLinks := []linkDesc{
   235  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   236  		{"link/errfilelink", "../dir01/missing.target"},
   237  		{"link/okdirlink", "../dir01/dir02"},
   238  		{"link/errdirlink", "../dir01/.git"},
   239  	}
   240  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   241  		t.Fatalf("Cannot create test files: %v", err)
   242  	}
   243  
   244  	tarFile, err := th.CreateTarFile("", tempDir)
   245  	defer os.Remove(tarFile)
   246  	if err != nil {
   247  		t.Fatalf("Unable to create new tar upload file: %v", err)
   248  	}
   249  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   250  }
   251  
   252  func TestCreateTarIncludeDotGit(t *testing.T) {
   253  	th := New(fs.NewFileSystem())
   254  	th.SetExclusionPattern(regexp.MustCompile("test3.txt"))
   255  	tempDir, err := ioutil.TempDir("", "testtar")
   256  	defer os.RemoveAll(tempDir)
   257  	if err != nil {
   258  		t.Fatalf("Cannot create temp directory for test: %v", err)
   259  	}
   260  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   261  	testDirs := []dirDesc{
   262  		{"dir01", modificationDate, 0700},
   263  		{"dir01/.git", modificationDate, 0755},
   264  		{"dir01/dir02", modificationDate, 0755},
   265  		{"dir01/dir03", modificationDate, 0775},
   266  		{"link", modificationDate, 0775},
   267  	}
   268  	testFiles := []fileDesc{
   269  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   270  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   271  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", true, ""},
   272  		{"dir01/.git/hello.txt", modificationDate, 0600, "Allow .git content", false, ""},
   273  	}
   274  	testLinks := []linkDesc{
   275  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   276  		{"link/errfilelink", "../dir01/missing.target"},
   277  		{"link/okdirlink", "../dir01/dir02"},
   278  		{"link/okdirlink2", "../dir01/.git"},
   279  	}
   280  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   281  		t.Fatalf("Cannot create test files: %v", err)
   282  	}
   283  
   284  	tarFile, err := th.CreateTarFile("", tempDir)
   285  	defer os.Remove(tarFile)
   286  	if err != nil {
   287  		t.Fatalf("Unable to create new tar upload file: %v", err)
   288  	}
   289  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   290  }
   291  
   292  func TestCreateTarEmptyRegexp(t *testing.T) {
   293  	th := New(fs.NewFileSystem())
   294  	th.SetExclusionPattern(regexp.MustCompile(""))
   295  	tempDir, err := ioutil.TempDir("", "testtar")
   296  	defer os.RemoveAll(tempDir)
   297  	if err != nil {
   298  		t.Fatalf("Cannot create temp directory for test: %v", err)
   299  	}
   300  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   301  	testDirs := []dirDesc{
   302  		{"dir01", modificationDate, 0700},
   303  		{"dir01/.git", modificationDate, 0755},
   304  		{"dir01/dir02", modificationDate, 0755},
   305  		{"dir01/dir03", modificationDate, 0775},
   306  		{"link", modificationDate, 0775},
   307  	}
   308  	testFiles := []fileDesc{
   309  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   310  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   311  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   312  		{"dir01/.git/hello.txt", modificationDate, 0600, "Allow .git content", false, ""},
   313  	}
   314  	testLinks := []linkDesc{
   315  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   316  		{"link/errfilelink", "../dir01/missing.target"},
   317  		{"link/okdirlink", "../dir01/dir02"},
   318  		{"link/okdirlink2", "../dir01/.git"},
   319  	}
   320  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   321  		t.Fatalf("Cannot create test files: %v", err)
   322  	}
   323  
   324  	tarFile, err := th.CreateTarFile("", tempDir)
   325  	defer os.Remove(tarFile)
   326  	if err != nil {
   327  		t.Fatalf("Unable to create new tar upload file: %v", err)
   328  	}
   329  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   330  }
   331  
   332  func createTestTar(files []fileDesc, writer io.Writer) error {
   333  	tw := tar.NewWriter(writer)
   334  	defer tw.Close()
   335  	for _, fd := range files {
   336  		if isSymLink(fd.mode) {
   337  			if err := addSymLink(tw, &fd); err != nil {
   338  				msg := "unable to add symbolic link %q (points to %q) to archive: %v"
   339  				return fmt.Errorf(msg, fd.name, fd.target, err)
   340  			}
   341  			continue
   342  		}
   343  		if fd.mode.IsDir() {
   344  			if err := addDir(tw, &fd); err != nil {
   345  				msg := "unable to add dir %q to archive: %v"
   346  				return fmt.Errorf(msg, fd.name, err)
   347  			}
   348  			continue
   349  		}
   350  		if err := addRegularFile(tw, &fd); err != nil {
   351  			return fmt.Errorf("unable to add file %q to archive: %v", fd.name, err)
   352  		}
   353  	}
   354  	return nil
   355  }
   356  
   357  func addRegularFile(tw *tar.Writer, fd *fileDesc) error {
   358  	contentBytes := []byte(fd.content)
   359  	hdr := &tar.Header{
   360  		Name:       fd.name,
   361  		Mode:       int64(fd.mode),
   362  		Size:       int64(len(contentBytes)),
   363  		Typeflag:   tar.TypeReg,
   364  		AccessTime: time.Now(),
   365  		ModTime:    fd.modifiedDate,
   366  		ChangeTime: fd.modifiedDate,
   367  	}
   368  	if err := tw.WriteHeader(hdr); err != nil {
   369  		return err
   370  	}
   371  	_, err := tw.Write(contentBytes)
   372  	return err
   373  }
   374  
   375  func addDir(tw *tar.Writer, fd *fileDesc) error {
   376  	hdr := &tar.Header{
   377  		Name:       fd.name,
   378  		Mode:       int64(fd.mode & 0777),
   379  		Typeflag:   tar.TypeDir,
   380  		AccessTime: time.Now(),
   381  		ModTime:    fd.modifiedDate,
   382  		ChangeTime: fd.modifiedDate,
   383  	}
   384  	return tw.WriteHeader(hdr)
   385  }
   386  
   387  func addSymLink(tw *tar.Writer, fd *fileDesc) error {
   388  	if len(fd.target) == 0 {
   389  		return fmt.Errorf("link %q must point to somewhere, but target wasn't defined", fd.name)
   390  	}
   391  
   392  	hdr := &tar.Header{
   393  		Name:     fd.name,
   394  		Linkname: fd.target,
   395  		Mode:     int64(fd.mode & os.ModePerm),
   396  		Typeflag: tar.TypeSymlink,
   397  		ModTime:  fd.modifiedDate,
   398  	}
   399  
   400  	return tw.WriteHeader(hdr)
   401  }
   402  
   403  func isSymLink(mode os.FileMode) bool {
   404  	return mode&os.ModeSymlink == os.ModeSymlink
   405  }
   406  
   407  func verifyDirectory(t *testing.T, dir string, dirs []dirDesc, files []fileDesc, links []linkDesc) {
   408  	if dirs == nil {
   409  		dirs = []dirDesc{}
   410  	}
   411  	if files == nil {
   412  		files = []fileDesc{}
   413  	}
   414  	if links == nil {
   415  		links = []linkDesc{}
   416  	}
   417  	if runtime.GOOS == "windows" {
   418  		for i := range files {
   419  			files[i].name = filepath.FromSlash(files[i].name)
   420  			if files[i].mode&0200 == 0200 {
   421  				// if the file is user writable make it writable for everyone
   422  				files[i].mode |= 0666
   423  			} else {
   424  				// if the file is only readable, make it readable for everyone
   425  				// first clear the r/w permission bits
   426  				files[i].mode &^= 0666
   427  				// then set r permission for all
   428  				files[i].mode |= 0444
   429  			}
   430  			if files[i].mode.IsDir() {
   431  				// if the file is a directory, make it executable for everyone
   432  				files[i].mode |= 0111
   433  			} else {
   434  				// if it's not a directory, clear the executable bits as they are
   435  				// irrelevant on windows.
   436  				files[i].mode &^= 0111
   437  			}
   438  			files[i].target = filepath.FromSlash(files[i].target)
   439  		}
   440  		for j := range dirs {
   441  			dirs[j].name = filepath.FromSlash(dirs[j].name)
   442  			if dirs[j].mode&0200 == 0200 {
   443  				// if the file is user writable make it writable for everyone
   444  				dirs[j].mode |= 0666
   445  			} else {
   446  				// if the file is only readable, make it readable for everyone
   447  				// first clear the r/w permission bits
   448  				dirs[j].mode &^= 0666
   449  				// then set r permission for all
   450  				dirs[j].mode |= 0444
   451  			}
   452  			if dirs[j].mode.IsDir() {
   453  				// if the file is a directory, make it executable for everyone
   454  				dirs[j].mode |= 0111
   455  			} else {
   456  				// if it's not a directory, clear the executable bits as they are
   457  				// irrelevant on windows.
   458  				dirs[j].mode &^= 0111
   459  			}
   460  
   461  		}
   462  		for k := range links {
   463  			links[k].fileName = filepath.FromSlash(links[k].fileName)
   464  			links[k].linkName = filepath.FromSlash(links[k].linkName)
   465  		}
   466  	}
   467  	dirsToVerify := make(map[string]dirDesc)
   468  	for _, dd := range dirs {
   469  		dirsToVerify[dd.name] = dd
   470  	}
   471  	pathsToVerify := make(map[string]fileDesc)
   472  	for _, fd := range files {
   473  		pathsToVerify[fd.name] = fd
   474  	}
   475  	linksToVerify := make(map[string]linkDesc)
   476  	for _, ld := range links {
   477  		linksToVerify[ld.linkName] = ld
   478  	}
   479  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   480  		if path == dir {
   481  			return nil
   482  		}
   483  		relpath := path[len(dir)+1:]
   484  		if dd, ok := dirsToVerify[relpath]; ok {
   485  			if !info.IsDir() {
   486  				t.Errorf("Incorrect dir %q", info.Name())
   487  			}
   488  			if info.Mode().Perm() != dd.mode {
   489  				t.Errorf("Dir %q does not match expected mode. Expected: %v, actual: %v",
   490  					info.Name(), dd.mode, info.Mode().Perm())
   491  			}
   492  			// Do not test directories - mod time will be time directory was created on filesystem
   493  		} else if fd, ok := pathsToVerify[relpath]; ok {
   494  			if info.Mode() != fd.mode {
   495  				t.Errorf("File mode is not equal for %q. Expected: %v, Actual: %v",
   496  					relpath, fd.mode, info.Mode())
   497  			}
   498  			// TODO: check modification time for symlinks when extractLink() will support it
   499  			if info.ModTime().UTC() != fd.modifiedDate && !isSymLink(fd.mode) && !fd.mode.IsDir() {
   500  				t.Errorf("File modified date is not equal for %q. Expected: %v, Actual: %v",
   501  					relpath, fd.modifiedDate, info.ModTime())
   502  			}
   503  			if !info.IsDir() {
   504  				contentBytes, err := ioutil.ReadFile(path)
   505  				if err != nil {
   506  					t.Errorf("Error reading file %q: %v", path, err)
   507  					return err
   508  				}
   509  				content := string(contentBytes)
   510  				if content != fd.content {
   511  					t.Errorf("File content is not equal for %q. Expected: %q, Actual: %q",
   512  						relpath, fd.content, content)
   513  				}
   514  			}
   515  			if isSymLink(fd.mode) {
   516  				target, err := os.Readlink(path)
   517  				if err != nil {
   518  					t.Errorf("Error reading symlink %q: %v", path, err)
   519  					return err
   520  				}
   521  				if target != fd.target {
   522  					msg := "Symbolic link %q points to wrong path. Expected: %q, Actual: %q"
   523  					t.Errorf(msg, fd.name, fd.target, target)
   524  				}
   525  			}
   526  		} else if ld, ok := linksToVerify[relpath]; ok {
   527  			if isSymLink(info.Mode()) {
   528  				target, err := os.Readlink(path)
   529  				if err != nil {
   530  					t.Errorf("Error reading symlink %q: %v", path, err)
   531  					return err
   532  				}
   533  				if target != ld.fileName {
   534  					t.Errorf("Incorrect link location. Expected: %q, Actual %q", ld.fileName, target)
   535  				}
   536  			} else {
   537  				t.Errorf("Expected file %q to be a symlink", path)
   538  			}
   539  		} else {
   540  			t.Errorf("Unexpected file found: %q", relpath)
   541  		}
   542  		return nil
   543  	})
   544  	if err != nil {
   545  		t.Fatalf("Error walking directory %q: %v", dir, err)
   546  	}
   547  }
   548  
   549  func TestExtractTarStream(t *testing.T) {
   550  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   551  	var symLinkMode os.FileMode = 0777
   552  	if runtime.GOOS == "darwin" {
   553  		// Symlinks show up as Lrwxr-xr-x on macOS
   554  		symLinkMode = 0755
   555  	}
   556  	testFiles := []fileDesc{
   557  		{"dir01", modificationDate, 0700 | os.ModeDir, "", false, ""},
   558  		{"dir01/.git", modificationDate, 0755 | os.ModeDir, "", false, ""},
   559  		{"dir01/dir02", modificationDate, 0755 | os.ModeDir, "", false, ""},
   560  		{"dir01/dir03", modificationDate, 0775 | os.ModeDir, "", false, ""},
   561  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   562  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   563  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   564  		{"dir01/symlink", modificationDate, os.ModeSymlink | symLinkMode, "Test3 file content", false, "../dir01/dir03/test3.txt"},
   565  		{"dir01/dir03/tëst3.txt", modificationDate, 0444, "utf-8 header file content", false, ""},
   566  	}
   567  	reader, writer := io.Pipe()
   568  	destDir, err := ioutil.TempDir("", "testExtract")
   569  	if err != nil {
   570  		t.Fatalf("Cannot create temp directory: %v", err)
   571  	}
   572  	defer os.RemoveAll(destDir)
   573  	th := New(fs.NewFileSystem())
   574  
   575  	go func() {
   576  		err := createTestTar(testFiles, writer)
   577  		if err != nil {
   578  			t.Fatalf("Error creating tar stream: %v", err)
   579  		}
   580  		writer.CloseWithError(err)
   581  	}()
   582  	th.ExtractTarStream(destDir, reader)
   583  	verifyDirectory(t, destDir, nil, testFiles, nil)
   584  }
   585  
   586  func TestExtractTarStreamTimeout(t *testing.T) {
   587  	reader, writer := io.Pipe()
   588  	destDir, err := ioutil.TempDir("", "testExtract")
   589  	if err != nil {
   590  		t.Fatalf("Cannot create temp directory: %v", err)
   591  	}
   592  	defer os.RemoveAll(destDir)
   593  	th := New(fs.NewFileSystem())
   594  	th.(*stiTar).timeout = 5 * time.Millisecond
   595  	time.AfterFunc(30*time.Millisecond, func() { writer.Close() })
   596  	err = th.ExtractTarStream(destDir, reader)
   597  	if e, ok := err.(s2ierr.Error); err == nil || (ok && e.ErrorCode != s2ierr.TarTimeoutError) {
   598  		t.Errorf("Did not get the expected timeout error. err = %v", err)
   599  	}
   600  }
   601  
   602  func TestRoundTripTar(t *testing.T) {
   603  	tarWriter := New(fs.NewFileSystem())
   604  	tarReader := New(fs.NewFileSystem())
   605  	tempDir, err := ioutil.TempDir("", "testtar")
   606  	defer os.RemoveAll(tempDir)
   607  	if err != nil {
   608  		t.Fatalf("Cannot create temp input directory for test: %v", err)
   609  	}
   610  	destDir, err := ioutil.TempDir("", "testExtract")
   611  	defer os.RemoveAll(destDir)
   612  	if err != nil {
   613  		t.Fatalf("Cannot create temp extract directory for test: %v", err)
   614  	}
   615  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   616  	testDirs := []dirDesc{
   617  		{"dir01", modificationDate, 0700},
   618  		{"dir01/.git", modificationDate, 0755},
   619  		{"dir01/dir02", modificationDate, 0755},
   620  		{"dir01/dir03", modificationDate, 0775},
   621  		{"link", modificationDate, 0775},
   622  	}
   623  	testFiles := []fileDesc{
   624  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   625  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   626  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   627  		{"dir01/.git/hello.txt", modificationDate, 0600, "Ignore file content", true, ""},
   628  		{"dir01/dir03/tëst3.txt", modificationDate, 0444, "Test utf-8 file header content", false, ""},
   629  	}
   630  	testLinks := []linkDesc{
   631  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   632  		{"link/errfilelink", "../dir01/missing.target"},
   633  		{"link/okdirlink", "../dir01/dir02"},
   634  		{"link/okdirlink2", "../dir01/.git"},
   635  	}
   636  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   637  		t.Fatalf("Cannot create test files: %v", err)
   638  	}
   639  
   640  	r, w := io.Pipe()
   641  	go func() {
   642  		writeErr := tarWriter.CreateTarStream(tempDir, false, w)
   643  		if writeErr != nil {
   644  			t.Errorf("Unable to create tar stream: %v", err)
   645  		}
   646  		w.CloseWithError(writeErr)
   647  	}()
   648  
   649  	err = tarReader.ExtractTarStream(destDir, r)
   650  	if err != nil {
   651  		t.Errorf("error extracting tar stream: %v", err)
   652  	}
   653  	verifyDirectory(t, destDir, testDirs, testFiles, testLinks)
   654  }