mirror of https://github.com/golang/go.git
516 lines
12 KiB
Go
516 lines
12 KiB
Go
// 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"
|
|
"sync"
|
|
"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 {
|
|
t.Helper()
|
|
testenv.MustHaveExec(t)
|
|
|
|
packPathOnce.Do(func() {
|
|
packExePath, packPathErr = os.Executable()
|
|
})
|
|
if packPathErr != nil {
|
|
t.Fatal(packPathErr)
|
|
}
|
|
return packExePath
|
|
}
|
|
|
|
var (
|
|
packPathOnce sync.Once
|
|
packExePath string
|
|
packPathErr error
|
|
)
|
|
|
|
// 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.
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal("os.Getwd: ", err)
|
|
}
|
|
err = os.Chdir(dir)
|
|
if err != nil {
|
|
t.Fatal("os.Chdir: ", err)
|
|
}
|
|
defer func() {
|
|
err := os.Chdir(pwd)
|
|
if err != nil {
|
|
t.Fatal("os.Chdir: ", err)
|
|
}
|
|
}()
|
|
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))},
|
|
}
|
|
}
|