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

View as plain text