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 }