Source file src/cmd/pack/pack_test.go

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

View as plain text