github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/pack/pack_test.go (about)

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