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

View as plain text