1// Copyright 2014 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package main 6 7import ( 8 "bufio" 9 "cmd/internal/archive" 10 "fmt" 11 "internal/testenv" 12 "io" 13 "io/fs" 14 "os" 15 "path/filepath" 16 "runtime" 17 "strings" 18 "sync" 19 "testing" 20 "time" 21) 22 23// TestMain executes the test binary as the pack command if 24// GO_PACKTEST_IS_PACK is set, and runs the tests otherwise. 25func TestMain(m *testing.M) { 26 if os.Getenv("GO_PACKTEST_IS_PACK") != "" { 27 main() 28 os.Exit(0) 29 } 30 31 os.Setenv("GO_PACKTEST_IS_PACK", "1") // Set for subprocesses to inherit. 32 os.Exit(m.Run()) 33} 34 35// packPath returns the path to the "pack" binary to run. 36func packPath(t testing.TB) string { 37 t.Helper() 38 testenv.MustHaveExec(t) 39 40 packPathOnce.Do(func() { 41 packExePath, packPathErr = os.Executable() 42 }) 43 if packPathErr != nil { 44 t.Fatal(packPathErr) 45 } 46 return packExePath 47} 48 49var ( 50 packPathOnce sync.Once 51 packExePath string 52 packPathErr error 53) 54 55// testCreate creates an archive in the specified directory. 56func testCreate(t *testing.T, dir string) { 57 name := filepath.Join(dir, "pack.a") 58 ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) 59 // Add an entry by hand. 60 ar.addFile(helloFile.Reset()) 61 ar.a.File().Close() 62 // Now check it. 63 ar = openArchive(name, os.O_RDONLY, []string{helloFile.name}) 64 var buf strings.Builder 65 stdout = &buf 66 verbose = true 67 defer func() { 68 stdout = os.Stdout 69 verbose = false 70 }() 71 ar.scan(ar.printContents) 72 ar.a.File().Close() 73 result := buf.String() 74 // Expect verbose output plus file contents. 75 expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents) 76 if result != expect { 77 t.Fatalf("expected %q got %q", expect, result) 78 } 79} 80 81// Test that we can create an archive, write to it, and get the same contents back. 82// Tests the rv and then the pv command on a new archive. 83func TestCreate(t *testing.T) { 84 dir := t.TempDir() 85 testCreate(t, dir) 86} 87 88// Test that we can create an archive twice with the same name (Issue 8369). 89func TestCreateTwice(t *testing.T) { 90 dir := t.TempDir() 91 testCreate(t, dir) 92 testCreate(t, dir) 93} 94 95// Test that we can create an archive, put some files in it, and get back a correct listing. 96// Tests the tv command. 97func TestTableOfContents(t *testing.T) { 98 dir := t.TempDir() 99 name := filepath.Join(dir, "pack.a") 100 ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) 101 102 // Add some entries by hand. 103 ar.addFile(helloFile.Reset()) 104 ar.addFile(goodbyeFile.Reset()) 105 ar.a.File().Close() 106 107 // Now print it. 108 var buf strings.Builder 109 stdout = &buf 110 verbose = true 111 defer func() { 112 stdout = os.Stdout 113 verbose = false 114 }() 115 ar = openArchive(name, os.O_RDONLY, nil) 116 ar.scan(ar.tableOfContents) 117 ar.a.File().Close() 118 result := buf.String() 119 // Expect verbose listing. 120 expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry()) 121 if result != expect { 122 t.Fatalf("expected %q got %q", expect, result) 123 } 124 125 // Do it again without verbose. 126 verbose = false 127 buf.Reset() 128 ar = openArchive(name, os.O_RDONLY, nil) 129 ar.scan(ar.tableOfContents) 130 ar.a.File().Close() 131 result = buf.String() 132 // Expect non-verbose listing. 133 expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name) 134 if result != expect { 135 t.Fatalf("expected %q got %q", expect, result) 136 } 137 138 // Do it again with file list arguments. 139 verbose = false 140 buf.Reset() 141 ar = openArchive(name, os.O_RDONLY, []string{helloFile.name}) 142 ar.scan(ar.tableOfContents) 143 ar.a.File().Close() 144 result = buf.String() 145 // Expect only helloFile. 146 expect = fmt.Sprintf("%s\n", helloFile.name) 147 if result != expect { 148 t.Fatalf("expected %q got %q", expect, result) 149 } 150} 151 152// Test that we can create an archive, put some files in it, and get back a file. 153// Tests the x command. 154func TestExtract(t *testing.T) { 155 dir := t.TempDir() 156 name := filepath.Join(dir, "pack.a") 157 ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) 158 // Add some entries by hand. 159 ar.addFile(helloFile.Reset()) 160 ar.addFile(goodbyeFile.Reset()) 161 ar.a.File().Close() 162 // Now extract one file. We chdir to the directory of the archive for simplicity. 163 pwd, err := os.Getwd() 164 if err != nil { 165 t.Fatal("os.Getwd: ", err) 166 } 167 err = os.Chdir(dir) 168 if err != nil { 169 t.Fatal("os.Chdir: ", err) 170 } 171 defer func() { 172 err := os.Chdir(pwd) 173 if err != nil { 174 t.Fatal("os.Chdir: ", err) 175 } 176 }() 177 ar = openArchive(name, os.O_RDONLY, []string{goodbyeFile.name}) 178 ar.scan(ar.extractContents) 179 ar.a.File().Close() 180 data, err := os.ReadFile(goodbyeFile.name) 181 if err != nil { 182 t.Fatal(err) 183 } 184 // Expect contents of file. 185 result := string(data) 186 expect := goodbyeFile.contents 187 if result != expect { 188 t.Fatalf("expected %q got %q", expect, result) 189 } 190} 191 192// Test that pack-created archives can be understood by the tools. 193func TestHello(t *testing.T) { 194 testenv.MustHaveGoBuild(t) 195 testenv.MustInternalLink(t, false) 196 197 dir := t.TempDir() 198 hello := filepath.Join(dir, "hello.go") 199 prog := ` 200 package main 201 func main() { 202 println("hello world") 203 } 204 ` 205 err := os.WriteFile(hello, []byte(prog), 0666) 206 if err != nil { 207 t.Fatal(err) 208 } 209 210 run := func(args ...string) string { 211 return doRun(t, dir, args...) 212 } 213 214 importcfgfile := filepath.Join(dir, "hello.importcfg") 215 testenv.WriteImportcfg(t, importcfgfile, nil, hello) 216 217 goBin := testenv.GoToolPath(t) 218 run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=main", "hello.go") 219 run(packPath(t), "grc", "hello.a", "hello.o") 220 run(goBin, "tool", "link", "-importcfg="+importcfgfile, "-o", "a.out", "hello.a") 221 out := run("./a.out") 222 if out != "hello world\n" { 223 t.Fatalf("incorrect output: %q, want %q", out, "hello world\n") 224 } 225} 226 227// Test that pack works with very long lines in PKGDEF. 228func TestLargeDefs(t *testing.T) { 229 if testing.Short() { 230 t.Skip("skipping in -short mode") 231 } 232 testenv.MustHaveGoBuild(t) 233 234 dir := t.TempDir() 235 large := filepath.Join(dir, "large.go") 236 f, err := os.Create(large) 237 if err != nil { 238 t.Fatal(err) 239 } 240 b := bufio.NewWriter(f) 241 242 printf := func(format string, args ...any) { 243 _, err := fmt.Fprintf(b, format, args...) 244 if err != nil { 245 t.Fatalf("Writing to %s: %v", large, err) 246 } 247 } 248 249 printf("package large\n\ntype T struct {\n") 250 for i := 0; i < 1000; i++ { 251 printf("f%d int `tag:\"", i) 252 for j := 0; j < 100; j++ { 253 printf("t%d=%d,", j, j) 254 } 255 printf("\"`\n") 256 } 257 printf("}\n") 258 if err = b.Flush(); err != nil { 259 t.Fatal(err) 260 } 261 if err = f.Close(); err != nil { 262 t.Fatal(err) 263 } 264 265 main := filepath.Join(dir, "main.go") 266 prog := ` 267 package main 268 import "large" 269 var V large.T 270 func main() { 271 println("ok") 272 } 273 ` 274 err = os.WriteFile(main, []byte(prog), 0666) 275 if err != nil { 276 t.Fatal(err) 277 } 278 279 run := func(args ...string) string { 280 return doRun(t, dir, args...) 281 } 282 283 importcfgfile := filepath.Join(dir, "hello.importcfg") 284 testenv.WriteImportcfg(t, importcfgfile, nil) 285 286 goBin := testenv.GoToolPath(t) 287 run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=large", "large.go") 288 run(packPath(t), "grc", "large.a", "large.o") 289 testenv.WriteImportcfg(t, importcfgfile, map[string]string{"large": filepath.Join(dir, "large.o")}, "runtime") 290 run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=main", "main.go") 291 run(goBin, "tool", "link", "-importcfg="+importcfgfile, "-L", ".", "-o", "a.out", "main.o") 292 out := run("./a.out") 293 if out != "ok\n" { 294 t.Fatalf("incorrect output: %q, want %q", out, "ok\n") 295 } 296} 297 298// Test that "\n!\n" inside export data doesn't result in a truncated 299// package definition when creating a .a archive from a .o Go object. 300func TestIssue21703(t *testing.T) { 301 testenv.MustHaveGoBuild(t) 302 303 dir := t.TempDir() 304 305 const aSrc = `package a; const X = "\n!\n"` 306 err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(aSrc), 0666) 307 if err != nil { 308 t.Fatal(err) 309 } 310 311 const bSrc = `package b; import _ "a"` 312 err = os.WriteFile(filepath.Join(dir, "b.go"), []byte(bSrc), 0666) 313 if err != nil { 314 t.Fatal(err) 315 } 316 317 run := func(args ...string) string { 318 return doRun(t, dir, args...) 319 } 320 321 goBin := testenv.GoToolPath(t) 322 run(goBin, "tool", "compile", "-p=a", "a.go") 323 run(packPath(t), "c", "a.a", "a.o") 324 run(goBin, "tool", "compile", "-p=b", "-I", ".", "b.go") 325} 326 327// Test the "c" command can "see through" the archive generated by the compiler. 328// This is peculiar. (See issue #43271) 329func TestCreateWithCompilerObj(t *testing.T) { 330 testenv.MustHaveGoBuild(t) 331 332 dir := t.TempDir() 333 src := filepath.Join(dir, "p.go") 334 prog := "package p; var X = 42\n" 335 err := os.WriteFile(src, []byte(prog), 0666) 336 if err != nil { 337 t.Fatal(err) 338 } 339 340 run := func(args ...string) string { 341 return doRun(t, dir, args...) 342 } 343 344 goBin := testenv.GoToolPath(t) 345 run(goBin, "tool", "compile", "-pack", "-p=p", "-o", "p.a", "p.go") 346 run(packPath(t), "c", "packed.a", "p.a") 347 fi, err := os.Stat(filepath.Join(dir, "p.a")) 348 if err != nil { 349 t.Fatalf("stat p.a failed: %v", err) 350 } 351 fi2, err := os.Stat(filepath.Join(dir, "packed.a")) 352 if err != nil { 353 t.Fatalf("stat packed.a failed: %v", err) 354 } 355 // For compiler-generated object file, the "c" command is 356 // expected to get (essentially) the same file back, instead 357 // of packing it into a new archive with a single entry. 358 if want, got := fi.Size(), fi2.Size(); want != got { 359 t.Errorf("packed file with different size: want %d, got %d", want, got) 360 } 361 362 // Test -linkobj flag as well. 363 run(goBin, "tool", "compile", "-p=p", "-linkobj", "p2.a", "-o", "p.x", "p.go") 364 run(packPath(t), "c", "packed2.a", "p2.a") 365 fi, err = os.Stat(filepath.Join(dir, "p2.a")) 366 if err != nil { 367 t.Fatalf("stat p2.a failed: %v", err) 368 } 369 fi2, err = os.Stat(filepath.Join(dir, "packed2.a")) 370 if err != nil { 371 t.Fatalf("stat packed2.a failed: %v", err) 372 } 373 if want, got := fi.Size(), fi2.Size(); want != got { 374 t.Errorf("packed file with different size: want %d, got %d", want, got) 375 } 376 377 run(packPath(t), "c", "packed3.a", "p.x") 378 fi, err = os.Stat(filepath.Join(dir, "p.x")) 379 if err != nil { 380 t.Fatalf("stat p.x failed: %v", err) 381 } 382 fi2, err = os.Stat(filepath.Join(dir, "packed3.a")) 383 if err != nil { 384 t.Fatalf("stat packed3.a failed: %v", err) 385 } 386 if want, got := fi.Size(), fi2.Size(); want != got { 387 t.Errorf("packed file with different size: want %d, got %d", want, got) 388 } 389} 390 391// Test the "r" command creates the output file if it does not exist. 392func TestRWithNonexistentFile(t *testing.T) { 393 testenv.MustHaveGoBuild(t) 394 395 dir := t.TempDir() 396 src := filepath.Join(dir, "p.go") 397 prog := "package p; var X = 42\n" 398 err := os.WriteFile(src, []byte(prog), 0666) 399 if err != nil { 400 t.Fatal(err) 401 } 402 403 run := func(args ...string) string { 404 return doRun(t, dir, args...) 405 } 406 407 goBin := testenv.GoToolPath(t) 408 run(goBin, "tool", "compile", "-p=p", "-o", "p.o", "p.go") 409 run(packPath(t), "r", "p.a", "p.o") // should succeed 410} 411 412// doRun runs a program in a directory and returns the output. 413func doRun(t *testing.T, dir string, args ...string) string { 414 cmd := testenv.Command(t, args[0], args[1:]...) 415 cmd.Dir = dir 416 out, err := cmd.CombinedOutput() 417 if err != nil { 418 if t.Name() == "TestHello" && runtime.GOOS == "android" && runtime.GOARCH == "arm64" { 419 testenv.SkipFlaky(t, 58806) 420 } 421 t.Fatalf("%v: %v\n%s", args, err, string(out)) 422 } 423 return string(out) 424} 425 426// Fake implementation of files. 427 428var helloFile = &FakeFile{ 429 name: "hello", 430 contents: "hello world", // 11 bytes, an odd number. 431 mode: 0644, 432} 433 434var goodbyeFile = &FakeFile{ 435 name: "goodbye", 436 contents: "Sayonara, Jim", // 13 bytes, another odd number. 437 mode: 0644, 438} 439 440// FakeFile implements FileLike and also fs.FileInfo. 441type FakeFile struct { 442 name string 443 contents string 444 mode fs.FileMode 445 offset int 446} 447 448// Reset prepares a FakeFile for reuse. 449func (f *FakeFile) Reset() *FakeFile { 450 f.offset = 0 451 return f 452} 453 454// FileLike methods. 455 456func (f *FakeFile) Name() string { 457 // A bit of a cheat: we only have a basename, so that's also ok for FileInfo. 458 return f.name 459} 460 461func (f *FakeFile) Stat() (fs.FileInfo, error) { 462 return f, nil 463} 464 465func (f *FakeFile) Read(p []byte) (int, error) { 466 if f.offset >= len(f.contents) { 467 return 0, io.EOF 468 } 469 n := copy(p, f.contents[f.offset:]) 470 f.offset += n 471 return n, nil 472} 473 474func (f *FakeFile) Close() error { 475 return nil 476} 477 478// fs.FileInfo methods. 479 480func (f *FakeFile) Size() int64 { 481 return int64(len(f.contents)) 482} 483 484func (f *FakeFile) Mode() fs.FileMode { 485 return f.mode 486} 487 488func (f *FakeFile) ModTime() time.Time { 489 return time.Time{} 490} 491 492func (f *FakeFile) IsDir() bool { 493 return false 494} 495 496func (f *FakeFile) Sys() any { 497 return nil 498} 499 500func (f *FakeFile) String() string { 501 return fs.FormatFileInfo(f) 502} 503 504// Special helpers. 505 506func (f *FakeFile) Entry() *archive.Entry { 507 return &archive.Entry{ 508 Name: f.name, 509 Mtime: 0, // Defined to be zero. 510 Uid: 0, // Ditto. 511 Gid: 0, // Ditto. 512 Mode: f.mode, 513 Data: archive.Data{Size: int64(len(f.contents))}, 514 } 515} 516