| // 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))}, | 
 | 	} | 
 | } |