| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "bufio" |
| "cmd/internal/archive" |
| "fmt" |
| "internal/testenv" |
| "io" |
| "io/fs" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| // TestMain executes the test binary as the pack command if |
| // GO_PACKTEST_IS_PACK is set, and runs the tests otherwise. |
| func TestMain(m *testing.M) { |
| if os.Getenv("GO_PACKTEST_IS_PACK") != "" { |
| main() |
| os.Exit(0) |
| } |
| |
| os.Setenv("GO_PACKTEST_IS_PACK", "1") // Set for subprocesses to inherit. |
| os.Exit(m.Run()) |
| } |
| |
| // packPath returns the path to the "pack" binary to run. |
| func packPath(t testing.TB) string { |
| return testenv.Executable(t) |
| } |
| |
| // testCreate creates an archive in the specified directory. |
| func testCreate(t *testing.T, dir string) { |
| name := filepath.Join(dir, "pack.a") |
| ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) |
| // Add an entry by hand. |
| ar.addFile(helloFile.Reset()) |
| ar.a.File().Close() |
| // Now check it. |
| ar = openArchive(name, os.O_RDONLY, []string{helloFile.name}) |
| var buf strings.Builder |
| stdout = &buf |
| verbose = true |
| defer func() { |
| stdout = os.Stdout |
| verbose = false |
| }() |
| ar.scan(ar.printContents) |
| ar.a.File().Close() |
| result := buf.String() |
| // Expect verbose output plus file contents. |
| expect := fmt.Sprintf("%s\n%s", helloFile.name, helloFile.contents) |
| if result != expect { |
| t.Fatalf("expected %q got %q", expect, result) |
| } |
| } |
| |
| // Test that we can create an archive, write to it, and get the same contents back. |
| // Tests the rv and then the pv command on a new archive. |
| func TestCreate(t *testing.T) { |
| dir := t.TempDir() |
| testCreate(t, dir) |
| } |
| |
| // Test that we can create an archive twice with the same name (Issue 8369). |
| func TestCreateTwice(t *testing.T) { |
| dir := t.TempDir() |
| testCreate(t, dir) |
| testCreate(t, dir) |
| } |
| |
| // Test that we can create an archive, put some files in it, and get back a correct listing. |
| // Tests the tv command. |
| func TestTableOfContents(t *testing.T) { |
| dir := t.TempDir() |
| name := filepath.Join(dir, "pack.a") |
| ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) |
| |
| // Add some entries by hand. |
| ar.addFile(helloFile.Reset()) |
| ar.addFile(goodbyeFile.Reset()) |
| ar.a.File().Close() |
| |
| // Now print it. |
| var buf strings.Builder |
| stdout = &buf |
| verbose = true |
| defer func() { |
| stdout = os.Stdout |
| verbose = false |
| }() |
| ar = openArchive(name, os.O_RDONLY, nil) |
| ar.scan(ar.tableOfContents) |
| ar.a.File().Close() |
| result := buf.String() |
| // Expect verbose listing. |
| expect := fmt.Sprintf("%s\n%s\n", helloFile.Entry(), goodbyeFile.Entry()) |
| if result != expect { |
| t.Fatalf("expected %q got %q", expect, result) |
| } |
| |
| // Do it again without verbose. |
| verbose = false |
| buf.Reset() |
| ar = openArchive(name, os.O_RDONLY, nil) |
| ar.scan(ar.tableOfContents) |
| ar.a.File().Close() |
| result = buf.String() |
| // Expect non-verbose listing. |
| expect = fmt.Sprintf("%s\n%s\n", helloFile.name, goodbyeFile.name) |
| if result != expect { |
| t.Fatalf("expected %q got %q", expect, result) |
| } |
| |
| // Do it again with file list arguments. |
| verbose = false |
| buf.Reset() |
| ar = openArchive(name, os.O_RDONLY, []string{helloFile.name}) |
| ar.scan(ar.tableOfContents) |
| ar.a.File().Close() |
| result = buf.String() |
| // Expect only helloFile. |
| expect = fmt.Sprintf("%s\n", helloFile.name) |
| if result != expect { |
| t.Fatalf("expected %q got %q", expect, result) |
| } |
| } |
| |
| // Test that we can create an archive, put some files in it, and get back a file. |
| // Tests the x command. |
| func TestExtract(t *testing.T) { |
| dir := t.TempDir() |
| name := filepath.Join(dir, "pack.a") |
| ar := openArchive(name, os.O_RDWR|os.O_CREATE, nil) |
| // Add some entries by hand. |
| ar.addFile(helloFile.Reset()) |
| ar.addFile(goodbyeFile.Reset()) |
| ar.a.File().Close() |
| // Now extract one file. We chdir to the directory of the archive for simplicity. |
| t.Chdir(dir) |
| ar = openArchive(name, os.O_RDONLY, []string{goodbyeFile.name}) |
| ar.scan(ar.extractContents) |
| ar.a.File().Close() |
| data, err := os.ReadFile(goodbyeFile.name) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // Expect contents of file. |
| result := string(data) |
| expect := goodbyeFile.contents |
| if result != expect { |
| t.Fatalf("expected %q got %q", expect, result) |
| } |
| } |
| |
| // Test that pack-created archives can be understood by the tools. |
| func TestHello(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| testenv.MustInternalLink(t, false) |
| |
| dir := t.TempDir() |
| hello := filepath.Join(dir, "hello.go") |
| prog := ` |
| package main |
| func main() { |
| println("hello world") |
| } |
| ` |
| err := os.WriteFile(hello, []byte(prog), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| run := func(args ...string) string { |
| return doRun(t, dir, args...) |
| } |
| |
| importcfgfile := filepath.Join(dir, "hello.importcfg") |
| testenv.WriteImportcfg(t, importcfgfile, nil, hello) |
| |
| goBin := testenv.GoToolPath(t) |
| run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=main", "hello.go") |
| run(packPath(t), "grc", "hello.a", "hello.o") |
| run(goBin, "tool", "link", "-importcfg="+importcfgfile, "-o", "a.out", "hello.a") |
| out := run("./a.out") |
| if out != "hello world\n" { |
| t.Fatalf("incorrect output: %q, want %q", out, "hello world\n") |
| } |
| } |
| |
| // Test that pack works with very long lines in PKGDEF. |
| func TestLargeDefs(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping in -short mode") |
| } |
| testenv.MustHaveGoBuild(t) |
| |
| dir := t.TempDir() |
| large := filepath.Join(dir, "large.go") |
| f, err := os.Create(large) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b := bufio.NewWriter(f) |
| |
| printf := func(format string, args ...any) { |
| _, err := fmt.Fprintf(b, format, args...) |
| if err != nil { |
| t.Fatalf("Writing to %s: %v", large, err) |
| } |
| } |
| |
| printf("package large\n\ntype T struct {\n") |
| for i := 0; i < 1000; i++ { |
| printf("f%d int `tag:\"", i) |
| for j := 0; j < 100; j++ { |
| printf("t%d=%d,", j, j) |
| } |
| printf("\"`\n") |
| } |
| printf("}\n") |
| if err = b.Flush(); err != nil { |
| t.Fatal(err) |
| } |
| if err = f.Close(); err != nil { |
| t.Fatal(err) |
| } |
| |
| main := filepath.Join(dir, "main.go") |
| prog := ` |
| package main |
| import "large" |
| var V large.T |
| func main() { |
| println("ok") |
| } |
| ` |
| err = os.WriteFile(main, []byte(prog), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| run := func(args ...string) string { |
| return doRun(t, dir, args...) |
| } |
| |
| importcfgfile := filepath.Join(dir, "hello.importcfg") |
| testenv.WriteImportcfg(t, importcfgfile, nil) |
| |
| goBin := testenv.GoToolPath(t) |
| run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=large", "large.go") |
| run(packPath(t), "grc", "large.a", "large.o") |
| testenv.WriteImportcfg(t, importcfgfile, map[string]string{"large": filepath.Join(dir, "large.o")}, "runtime") |
| run(goBin, "tool", "compile", "-importcfg="+importcfgfile, "-p=main", "main.go") |
| run(goBin, "tool", "link", "-importcfg="+importcfgfile, "-L", ".", "-o", "a.out", "main.o") |
| out := run("./a.out") |
| if out != "ok\n" { |
| t.Fatalf("incorrect output: %q, want %q", out, "ok\n") |
| } |
| } |
| |
| // Test that "\n!\n" inside export data doesn't result in a truncated |
| // package definition when creating a .a archive from a .o Go object. |
| func TestIssue21703(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| dir := t.TempDir() |
| |
| const aSrc = `package a; const X = "\n!\n"` |
| err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(aSrc), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| const bSrc = `package b; import _ "a"` |
| err = os.WriteFile(filepath.Join(dir, "b.go"), []byte(bSrc), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| run := func(args ...string) string { |
| return doRun(t, dir, args...) |
| } |
| |
| goBin := testenv.GoToolPath(t) |
| run(goBin, "tool", "compile", "-p=a", "a.go") |
| run(packPath(t), "c", "a.a", "a.o") |
| run(goBin, "tool", "compile", "-p=b", "-I", ".", "b.go") |
| } |
| |
| // Test the "c" command can "see through" the archive generated by the compiler. |
| // This is peculiar. (See issue #43271) |
| func TestCreateWithCompilerObj(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| dir := t.TempDir() |
| src := filepath.Join(dir, "p.go") |
| prog := "package p; var X = 42\n" |
| err := os.WriteFile(src, []byte(prog), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| run := func(args ...string) string { |
| return doRun(t, dir, args...) |
| } |
| |
| goBin := testenv.GoToolPath(t) |
| run(goBin, "tool", "compile", "-pack", "-p=p", "-o", "p.a", "p.go") |
| run(packPath(t), "c", "packed.a", "p.a") |
| fi, err := os.Stat(filepath.Join(dir, "p.a")) |
| if err != nil { |
| t.Fatalf("stat p.a failed: %v", err) |
| } |
| fi2, err := os.Stat(filepath.Join(dir, "packed.a")) |
| if err != nil { |
| t.Fatalf("stat packed.a failed: %v", err) |
| } |
| // For compiler-generated object file, the "c" command is |
| // expected to get (essentially) the same file back, instead |
| // of packing it into a new archive with a single entry. |
| if want, got := fi.Size(), fi2.Size(); want != got { |
| t.Errorf("packed file with different size: want %d, got %d", want, got) |
| } |
| |
| // Test -linkobj flag as well. |
| run(goBin, "tool", "compile", "-p=p", "-linkobj", "p2.a", "-o", "p.x", "p.go") |
| run(packPath(t), "c", "packed2.a", "p2.a") |
| fi, err = os.Stat(filepath.Join(dir, "p2.a")) |
| if err != nil { |
| t.Fatalf("stat p2.a failed: %v", err) |
| } |
| fi2, err = os.Stat(filepath.Join(dir, "packed2.a")) |
| if err != nil { |
| t.Fatalf("stat packed2.a failed: %v", err) |
| } |
| if want, got := fi.Size(), fi2.Size(); want != got { |
| t.Errorf("packed file with different size: want %d, got %d", want, got) |
| } |
| |
| run(packPath(t), "c", "packed3.a", "p.x") |
| fi, err = os.Stat(filepath.Join(dir, "p.x")) |
| if err != nil { |
| t.Fatalf("stat p.x failed: %v", err) |
| } |
| fi2, err = os.Stat(filepath.Join(dir, "packed3.a")) |
| if err != nil { |
| t.Fatalf("stat packed3.a failed: %v", err) |
| } |
| if want, got := fi.Size(), fi2.Size(); want != got { |
| t.Errorf("packed file with different size: want %d, got %d", want, got) |
| } |
| } |
| |
| // Test the "r" command creates the output file if it does not exist. |
| func TestRWithNonexistentFile(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| dir := t.TempDir() |
| src := filepath.Join(dir, "p.go") |
| prog := "package p; var X = 42\n" |
| err := os.WriteFile(src, []byte(prog), 0666) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| run := func(args ...string) string { |
| return doRun(t, dir, args...) |
| } |
| |
| goBin := testenv.GoToolPath(t) |
| run(goBin, "tool", "compile", "-p=p", "-o", "p.o", "p.go") |
| run(packPath(t), "r", "p.a", "p.o") // should succeed |
| } |
| |
| // doRun runs a program in a directory and returns the output. |
| func doRun(t *testing.T, dir string, args ...string) string { |
| cmd := testenv.Command(t, args[0], args[1:]...) |
| cmd.Dir = dir |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| if t.Name() == "TestHello" && runtime.GOOS == "android" && runtime.GOARCH == "arm64" { |
| testenv.SkipFlaky(t, 58806) |
| } |
| t.Fatalf("%v: %v\n%s", args, err, string(out)) |
| } |
| return string(out) |
| } |
| |
| // Fake implementation of files. |
| |
| var helloFile = &FakeFile{ |
| name: "hello", |
| contents: "hello world", // 11 bytes, an odd number. |
| mode: 0644, |
| } |
| |
| var goodbyeFile = &FakeFile{ |
| name: "goodbye", |
| contents: "Sayonara, Jim", // 13 bytes, another odd number. |
| mode: 0644, |
| } |
| |
| // FakeFile implements FileLike and also fs.FileInfo. |
| type FakeFile struct { |
| name string |
| contents string |
| mode fs.FileMode |
| offset int |
| } |
| |
| // Reset prepares a FakeFile for reuse. |
| func (f *FakeFile) Reset() *FakeFile { |
| f.offset = 0 |
| return f |
| } |
| |
| // FileLike methods. |
| |
| func (f *FakeFile) Name() string { |
| // A bit of a cheat: we only have a basename, so that's also ok for FileInfo. |
| return f.name |
| } |
| |
| func (f *FakeFile) Stat() (fs.FileInfo, error) { |
| return f, nil |
| } |
| |
| func (f *FakeFile) Read(p []byte) (int, error) { |
| if f.offset >= len(f.contents) { |
| return 0, io.EOF |
| } |
| n := copy(p, f.contents[f.offset:]) |
| f.offset += n |
| return n, nil |
| } |
| |
| func (f *FakeFile) Close() error { |
| return nil |
| } |
| |
| // fs.FileInfo methods. |
| |
| func (f *FakeFile) Size() int64 { |
| return int64(len(f.contents)) |
| } |
| |
| func (f *FakeFile) Mode() fs.FileMode { |
| return f.mode |
| } |
| |
| func (f *FakeFile) ModTime() time.Time { |
| return time.Time{} |
| } |
| |
| func (f *FakeFile) IsDir() bool { |
| return false |
| } |
| |
| func (f *FakeFile) Sys() any { |
| return nil |
| } |
| |
| func (f *FakeFile) String() string { |
| return fs.FormatFileInfo(f) |
| } |
| |
| // Special helpers. |
| |
| func (f *FakeFile) Entry() *archive.Entry { |
| return &archive.Entry{ |
| Name: f.name, |
| Mtime: 0, // Defined to be zero. |
| Uid: 0, // Ditto. |
| Gid: 0, // Ditto. |
| Mode: f.mode, |
| Data: archive.Data{Size: int64(len(f.contents))}, |
| } |
| } |