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