kubesphere.io/s2irun@v3.2.1+incompatible/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/kubesphere/s2irun/pkg/errors"
    16  	"github.com/kubesphere/s2irun/pkg/utils/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  	}
   233  	testLinks := []linkDesc{
   234  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   235  		{"link/errfilelink", "../dir01/missing.target"},
   236  		{"link/okdirlink", "../dir01/dir02"},
   237  		{"link/errdirlink", "../dir01/.git"},
   238  	}
   239  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   240  		t.Fatalf("Cannot create test files: %v", err)
   241  	}
   242  
   243  	tarFile, err := th.CreateTarFile("", tempDir)
   244  	defer os.Remove(tarFile)
   245  	if err != nil {
   246  		t.Fatalf("Unable to create new tar upload file: %v", err)
   247  	}
   248  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   249  }
   250  
   251  func TestCreateTarIncludeDotGit(t *testing.T) {
   252  	th := New(fs.NewFileSystem())
   253  	th.SetExclusionPattern(regexp.MustCompile("test3.txt"))
   254  	tempDir, err := ioutil.TempDir("", "testtar")
   255  	defer os.RemoveAll(tempDir)
   256  	if err != nil {
   257  		t.Fatalf("Cannot create temp directory for test: %v", err)
   258  	}
   259  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   260  	testDirs := []dirDesc{
   261  		{"dir01", modificationDate, 0700},
   262  		{"dir01/.git", modificationDate, 0755},
   263  		{"dir01/dir02", modificationDate, 0755},
   264  		{"dir01/dir03", modificationDate, 0775},
   265  		{"link", modificationDate, 0775},
   266  	}
   267  	testFiles := []fileDesc{
   268  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   269  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   270  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", true, ""},
   271  		{"dir01/.git/hello.txt", modificationDate, 0600, "Allow .git content", false, ""},
   272  	}
   273  	testLinks := []linkDesc{
   274  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   275  		{"link/errfilelink", "../dir01/missing.target"},
   276  		{"link/okdirlink", "../dir01/dir02"},
   277  		{"link/okdirlink2", "../dir01/.git"},
   278  	}
   279  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   280  		t.Fatalf("Cannot create test files: %v", err)
   281  	}
   282  
   283  	tarFile, err := th.CreateTarFile("", tempDir)
   284  	defer os.Remove(tarFile)
   285  	if err != nil {
   286  		t.Fatalf("Unable to create new tar upload file: %v", err)
   287  	}
   288  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   289  }
   290  
   291  func TestCreateTarEmptyRegexp(t *testing.T) {
   292  	th := New(fs.NewFileSystem())
   293  	th.SetExclusionPattern(regexp.MustCompile(""))
   294  	tempDir, err := ioutil.TempDir("", "testtar")
   295  	defer os.RemoveAll(tempDir)
   296  	if err != nil {
   297  		t.Fatalf("Cannot create temp directory for test: %v", err)
   298  	}
   299  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   300  	testDirs := []dirDesc{
   301  		{"dir01", modificationDate, 0700},
   302  		{"dir01/.git", modificationDate, 0755},
   303  		{"dir01/dir02", modificationDate, 0755},
   304  		{"dir01/dir03", modificationDate, 0775},
   305  		{"link", modificationDate, 0775},
   306  	}
   307  	testFiles := []fileDesc{
   308  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   309  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   310  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   311  		{"dir01/.git/hello.txt", modificationDate, 0600, "Allow .git content", false, ""},
   312  	}
   313  	testLinks := []linkDesc{
   314  		{"link/okfilelink", "../dir01/dir02/test1.txt"},
   315  		{"link/errfilelink", "../dir01/missing.target"},
   316  		{"link/okdirlink", "../dir01/dir02"},
   317  		{"link/okdirlink2", "../dir01/.git"},
   318  	}
   319  	if err = createTestFiles(tempDir, testDirs, testFiles, testLinks); err != nil {
   320  		t.Fatalf("Cannot create test files: %v", err)
   321  	}
   322  
   323  	tarFile, err := th.CreateTarFile("", tempDir)
   324  	defer os.Remove(tarFile)
   325  	if err != nil {
   326  		t.Fatalf("Unable to create new tar upload file: %v", err)
   327  	}
   328  	verifyTarFile(t, tarFile, testDirs, testFiles, testLinks)
   329  }
   330  
   331  func createTestTar(files []fileDesc, writer io.Writer) error {
   332  	tw := tar.NewWriter(writer)
   333  	defer tw.Close()
   334  	for _, fd := range files {
   335  		if isSymLink(fd.mode) {
   336  			if err := addSymLink(tw, &fd); err != nil {
   337  				msg := "unable to add symbolic link %q (points to %q) to archive: %v"
   338  				return fmt.Errorf(msg, fd.name, fd.target, err)
   339  			}
   340  			continue
   341  		}
   342  		if fd.mode.IsDir() {
   343  			if err := addDir(tw, &fd); err != nil {
   344  				msg := "unable to add dir %q to archive: %v"
   345  				return fmt.Errorf(msg, fd.name, err)
   346  			}
   347  			continue
   348  		}
   349  		if err := addRegularFile(tw, &fd); err != nil {
   350  			return fmt.Errorf("unable to add file %q to archive: %v", fd.name, err)
   351  		}
   352  	}
   353  	return nil
   354  }
   355  
   356  func addRegularFile(tw *tar.Writer, fd *fileDesc) error {
   357  	contentBytes := []byte(fd.content)
   358  	hdr := &tar.Header{
   359  		Name:       fd.name,
   360  		Mode:       int64(fd.mode),
   361  		Size:       int64(len(contentBytes)),
   362  		Typeflag:   tar.TypeReg,
   363  		AccessTime: time.Now(),
   364  		ModTime:    fd.modifiedDate,
   365  		ChangeTime: fd.modifiedDate,
   366  	}
   367  	if err := tw.WriteHeader(hdr); err != nil {
   368  		return err
   369  	}
   370  	_, err := tw.Write(contentBytes)
   371  	return err
   372  }
   373  
   374  func addDir(tw *tar.Writer, fd *fileDesc) error {
   375  	hdr := &tar.Header{
   376  		Name:       fd.name,
   377  		Mode:       int64(fd.mode & 0777),
   378  		Typeflag:   tar.TypeDir,
   379  		AccessTime: time.Now(),
   380  		ModTime:    fd.modifiedDate,
   381  		ChangeTime: fd.modifiedDate,
   382  	}
   383  	return tw.WriteHeader(hdr)
   384  }
   385  
   386  func addSymLink(tw *tar.Writer, fd *fileDesc) error {
   387  	if len(fd.target) == 0 {
   388  		return fmt.Errorf("link %q must point to somewhere, but target wasn't defined", fd.name)
   389  	}
   390  
   391  	hdr := &tar.Header{
   392  		Name:     fd.name,
   393  		Linkname: fd.target,
   394  		Mode:     int64(fd.mode & os.ModePerm),
   395  		Typeflag: tar.TypeSymlink,
   396  		ModTime:  fd.modifiedDate,
   397  	}
   398  
   399  	return tw.WriteHeader(hdr)
   400  }
   401  
   402  func isSymLink(mode os.FileMode) bool {
   403  	return mode&os.ModeSymlink == os.ModeSymlink
   404  }
   405  
   406  func verifyDirectory(t *testing.T, dir string, files []fileDesc) {
   407  	if runtime.GOOS == "windows" {
   408  		for i := range files {
   409  			files[i].name = filepath.FromSlash(files[i].name)
   410  			if files[i].mode&0200 == 0200 {
   411  				// if the file is user writable make it writable for everyone
   412  				files[i].mode |= 0666
   413  			} else {
   414  				// if the file is only readable, make it readable for everyone
   415  				// first clear the r/w permission bits
   416  				files[i].mode &^= 0666
   417  				// then set r permission for all
   418  				files[i].mode |= 0444
   419  			}
   420  			if files[i].mode.IsDir() {
   421  				// if the file is a directory, make it executable for everyone
   422  				files[i].mode |= 0111
   423  			} else {
   424  				// if it's not a directory, clear the executable bits as they are
   425  				// irrelevant on windows.
   426  				files[i].mode &^= 0111
   427  			}
   428  			files[i].target = filepath.FromSlash(files[i].target)
   429  		}
   430  	}
   431  	pathsToVerify := make(map[string]fileDesc)
   432  	for _, fd := range files {
   433  		pathsToVerify[fd.name] = fd
   434  	}
   435  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   436  		if path == dir {
   437  			return nil
   438  		}
   439  		relpath := path[len(dir)+1:]
   440  		if fd, ok := pathsToVerify[relpath]; ok {
   441  			if info.Mode() != fd.mode {
   442  				t.Errorf("File mode is not equal for %q. Expected: %v, Actual: %v",
   443  					relpath, fd.mode, info.Mode())
   444  			}
   445  			// TODO: check modification time for symlinks when extractLink() will support it
   446  			if info.ModTime().UTC() != fd.modifiedDate && !isSymLink(fd.mode) && !fd.mode.IsDir() {
   447  				t.Errorf("File modified date is not equal for %q. Expected: %v, Actual: %v",
   448  					relpath, fd.modifiedDate, info.ModTime())
   449  			}
   450  			if !info.IsDir() {
   451  				contentBytes, err := ioutil.ReadFile(path)
   452  				if err != nil {
   453  					t.Errorf("Error reading file %q: %v", path, err)
   454  					return err
   455  				}
   456  				content := string(contentBytes)
   457  				if content != fd.content {
   458  					t.Errorf("File content is not equal for %q. Expected: %q, Actual: %q",
   459  						relpath, fd.content, content)
   460  				}
   461  			}
   462  			if isSymLink(fd.mode) {
   463  				target, err := os.Readlink(path)
   464  				if err != nil {
   465  					t.Errorf("Error reading symlink %q: %v", path, err)
   466  					return err
   467  				}
   468  				if target != fd.target {
   469  					msg := "Symbolic link %q points to wrong path. Expected: %q, Actual: %q"
   470  					t.Errorf(msg, fd.name, fd.target, target)
   471  				}
   472  			}
   473  		} else {
   474  			t.Errorf("Unexpected file found: %q", relpath)
   475  		}
   476  		return nil
   477  	})
   478  	if err != nil {
   479  		t.Fatalf("Error walking directory %q: %v", dir, err)
   480  	}
   481  }
   482  
   483  func TestExtractTarStream(t *testing.T) {
   484  	modificationDate := time.Date(2011, time.March, 5, 23, 30, 1, 0, time.UTC)
   485  	var symLinkMode os.FileMode = 0777
   486  	if runtime.GOOS == "darwin" {
   487  		// Symlinks show up as Lrwxr-xr-x on macOS
   488  		symLinkMode = 0755
   489  	}
   490  	testFiles := []fileDesc{
   491  		{"dir01", modificationDate, 0700 | os.ModeDir, "", false, ""},
   492  		{"dir01/.git", modificationDate, 0755 | os.ModeDir, "", false, ""},
   493  		{"dir01/dir02", modificationDate, 0755 | os.ModeDir, "", false, ""},
   494  		{"dir01/dir03", modificationDate, 0775 | os.ModeDir, "", false, ""},
   495  		{"dir01/dir02/test1.txt", modificationDate, 0700, "Test1 file content", false, ""},
   496  		{"dir01/test2.git", modificationDate, 0660, "Test2 file content", false, ""},
   497  		{"dir01/dir03/test3.txt", modificationDate, 0444, "Test3 file content", false, ""},
   498  		{"dir01/symlink", modificationDate, os.ModeSymlink | symLinkMode, "Test3 file content", false, "../dir01/dir03/test3.txt"},
   499  	}
   500  	reader, writer := io.Pipe()
   501  	destDir, err := ioutil.TempDir("", "testExtract")
   502  	if err != nil {
   503  		t.Fatalf("Cannot create temp directory: %v", err)
   504  	}
   505  	defer os.RemoveAll(destDir)
   506  	th := New(fs.NewFileSystem())
   507  
   508  	go func() {
   509  		err := createTestTar(testFiles, writer)
   510  		if err != nil {
   511  			t.Fatalf("Error creating tar stream: %v", err)
   512  		}
   513  		writer.CloseWithError(err)
   514  	}()
   515  	th.ExtractTarStream(destDir, reader)
   516  	verifyDirectory(t, destDir, testFiles)
   517  }
   518  
   519  func TestExtractTarStreamTimeout(t *testing.T) {
   520  	reader, writer := io.Pipe()
   521  	destDir, err := ioutil.TempDir("", "testExtract")
   522  	if err != nil {
   523  		t.Fatalf("Cannot create temp directory: %v", err)
   524  	}
   525  	defer os.RemoveAll(destDir)
   526  	th := New(fs.NewFileSystem())
   527  	th.(*stiTar).timeout = 5 * time.Millisecond
   528  	time.AfterFunc(30*time.Millisecond, func() { writer.Close() })
   529  	err = th.ExtractTarStream(destDir, reader)
   530  	if e, ok := err.(s2ierr.Error); err == nil || (ok && e.ErrorCode != s2ierr.TarTimeoutError) {
   531  		t.Errorf("Did not get the expected timeout error. err = %v", err)
   532  	}
   533  }