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
5package main
6
7import (
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.
25func 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.
36func 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
49var (
50	packPathOnce sync.Once
51	packExePath  string
52	packPathErr  error
53)
54
55// testCreate creates an archive in the specified directory.
56func 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.
83func 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).
89func 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.
97func 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.
154func 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.
193func 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.
228func 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.
300func 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)
329func 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.
392func 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.
413func 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
428var helloFile = &FakeFile{
429	name:     "hello",
430	contents: "hello world", // 11 bytes, an odd number.
431	mode:     0644,
432}
433
434var 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.
441type FakeFile struct {
442	name     string
443	contents string
444	mode     fs.FileMode
445	offset   int
446}
447
448// Reset prepares a FakeFile for reuse.
449func (f *FakeFile) Reset() *FakeFile {
450	f.offset = 0
451	return f
452}
453
454// FileLike methods.
455
456func (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
461func (f *FakeFile) Stat() (fs.FileInfo, error) {
462	return f, nil
463}
464
465func (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
474func (f *FakeFile) Close() error {
475	return nil
476}
477
478// fs.FileInfo methods.
479
480func (f *FakeFile) Size() int64 {
481	return int64(len(f.contents))
482}
483
484func (f *FakeFile) Mode() fs.FileMode {
485	return f.mode
486}
487
488func (f *FakeFile) ModTime() time.Time {
489	return time.Time{}
490}
491
492func (f *FakeFile) IsDir() bool {
493	return false
494}
495
496func (f *FakeFile) Sys() any {
497	return nil
498}
499
500func (f *FakeFile) String() string {
501	return fs.FormatFileInfo(f)
502}
503
504// Special helpers.
505
506func (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