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

View as plain text