| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package archive | |
| import ( | |
| "bytes" | |
| "debug/elf" | |
| "debug/macho" | |
| "debug/pe" | |
| "fmt" | |
| "internal/testenv" | |
| "internal/xcoff" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "runtime" | |
| "sync" | |
| "testing" | |
| "unicode/utf8" | |
| ) | |
| var buildDir string | |
| func TestMain(m *testing.M) { | |
| if !testenv.HasGoBuild() { | |
| return | |
| } | |
| exit := m.Run() | |
| if buildDir != "" { | |
| os.RemoveAll(buildDir) | |
| } | |
| os.Exit(exit) | |
| } | |
| func copyDir(dst, src string) error { | |
| err := os.MkdirAll(dst, 0777) | |
| if err != nil { | |
| return err | |
| } | |
| entries, err := os.ReadDir(src) | |
| if err != nil { | |
| return err | |
| } | |
| for _, entry := range entries { | |
| err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name())) | |
| if err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| func copyFile(dst, src string) (err error) { | |
| var s, d *os.File | |
| s, err = os.Open(src) | |
| if err != nil { | |
| return err | |
| } | |
| defer s.Close() | |
| d, err = os.Create(dst) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| e := d.Close() | |
| if err == nil { | |
| err = e | |
| } | |
| }() | |
| _, err = io.Copy(d, s) | |
| if err != nil { | |
| return err | |
| } | |
| return nil | |
| } | |
| var ( | |
| buildOnce sync.Once | |
| builtGoobjs goobjPaths | |
| buildErr error | |
| ) | |
| type goobjPaths struct { | |
| go1obj string | |
| go2obj string | |
| goarchive string | |
| cgoarchive string | |
| } | |
| func buildGoobj(t *testing.T) goobjPaths { | |
| buildOnce.Do(func() { | |
| buildErr = func() (err error) { | |
| buildDir, err = os.MkdirTemp("", "TestGoobj") | |
| if err != nil { | |
| return err | |
| } | |
| go1obj := filepath.Join(buildDir, "go1.o") | |
| go2obj := filepath.Join(buildDir, "go2.o") | |
| goarchive := filepath.Join(buildDir, "go.a") | |
| cgoarchive := "" | |
| gotool, err := testenv.GoTool() | |
| if err != nil { | |
| return err | |
| } | |
| go1src := filepath.Join("testdata", "go1.go") | |
| go2src := filepath.Join("testdata", "go2.go") | |
| importcfgfile := filepath.Join(buildDir, "importcfg") | |
| testenv.WriteImportcfg(t, importcfgfile, nil, go1src, go2src) | |
| out, err := testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out) | |
| } | |
| out, err = testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out) | |
| } | |
| out, err = testenv.Command(t, gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out) | |
| } | |
| if testenv.HasCGO() { | |
| cgoarchive = filepath.Join(buildDir, "mycgo.a") | |
| gopath := filepath.Join(buildDir, "gopath") | |
| err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo")) | |
| if err == nil { | |
| err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666) | |
| } | |
| if err != nil { | |
| return err | |
| } | |
| cmd := testenv.Command(t, gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo") | |
| cmd.Dir = filepath.Join(gopath, "src", "mycgo") | |
| cmd.Env = append(os.Environ(), "GOPATH="+gopath) | |
| out, err = cmd.CombinedOutput() | |
| if err != nil { | |
| return fmt.Errorf("go install mycgo: %v\n%s", err, out) | |
| } | |
| } | |
| builtGoobjs = goobjPaths{ | |
| go1obj: go1obj, | |
| go2obj: go2obj, | |
| goarchive: goarchive, | |
| cgoarchive: cgoarchive, | |
| } | |
| return nil | |
| }() | |
| }) | |
| if buildErr != nil { | |
| t.Helper() | |
| t.Fatal(buildErr) | |
| } | |
| return builtGoobjs | |
| } | |
| func TestParseGoobj(t *testing.T) { | |
| path := buildGoobj(t).go1obj | |
| f, err := os.Open(path) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer f.Close() | |
| a, err := Parse(f, false) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(a.Entries) != 2 { | |
| t.Errorf("expect 2 entry, found %d", len(a.Entries)) | |
| } | |
| for _, e := range a.Entries { | |
| if e.Type == EntryPkgDef { | |
| continue | |
| } | |
| if e.Type != EntryGoObj { | |
| t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) | |
| } | |
| if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { | |
| t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) | |
| } | |
| } | |
| } | |
| func TestParseArchive(t *testing.T) { | |
| path := buildGoobj(t).goarchive | |
| f, err := os.Open(path) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer f.Close() | |
| a, err := Parse(f, false) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if len(a.Entries) != 3 { | |
| t.Errorf("expect 3 entry, found %d", len(a.Entries)) | |
| } | |
| var found1 bool | |
| var found2 bool | |
| for _, e := range a.Entries { | |
| if e.Type == EntryPkgDef { | |
| continue | |
| } | |
| if e.Type != EntryGoObj { | |
| t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type) | |
| } | |
| if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { | |
| t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) | |
| } | |
| if e.Name == "go1.o" { | |
| found1 = true | |
| } | |
| if e.Name == "go2.o" { | |
| found2 = true | |
| } | |
| } | |
| if !found1 { | |
| t.Errorf(`object "go1.o" not found`) | |
| } | |
| if !found2 { | |
| t.Errorf(`object "go2.o" not found`) | |
| } | |
| } | |
| func TestParseCGOArchive(t *testing.T) { | |
| testenv.MustHaveCGO(t) | |
| path := buildGoobj(t).cgoarchive | |
| f, err := os.Open(path) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer f.Close() | |
| a, err := Parse(f, false) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| c1 := "c1" | |
| c2 := "c2" | |
| switch runtime.GOOS { | |
| case "darwin", "ios": | |
| c1 = "_" + c1 | |
| c2 = "_" + c2 | |
| case "windows": | |
| if runtime.GOARCH == "386" { | |
| c1 = "_" + c1 | |
| c2 = "_" + c2 | |
| } | |
| case "aix": | |
| c1 = "." + c1 | |
| c2 = "." + c2 | |
| } | |
| var foundgo, found1, found2 bool | |
| for _, e := range a.Entries { | |
| switch e.Type { | |
| default: | |
| t.Errorf("unknown object type") | |
| case EntryPkgDef: | |
| continue | |
| case EntryGoObj: | |
| foundgo = true | |
| if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) { | |
| t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader) | |
| } | |
| continue | |
| case EntryNativeObj: | |
| } | |
| obj := io.NewSectionReader(f, e.Offset, e.Size) | |
| switch runtime.GOOS { | |
| case "darwin", "ios": | |
| mf, err := macho.NewFile(obj) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if mf.Symtab == nil { | |
| continue | |
| } | |
| for _, s := range mf.Symtab.Syms { | |
| switch s.Name { | |
| case c1: | |
| found1 = true | |
| case c2: | |
| found2 = true | |
| } | |
| } | |
| case "windows": | |
| pf, err := pe.NewFile(obj) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| for _, s := range pf.Symbols { | |
| switch s.Name { | |
| case c1: | |
| found1 = true | |
| case c2: | |
| found2 = true | |
| } | |
| } | |
| case "aix": | |
| xf, err := xcoff.NewFile(obj) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| for _, s := range xf.Symbols { | |
| switch s.Name { | |
| case c1: | |
| found1 = true | |
| case c2: | |
| found2 = true | |
| } | |
| } | |
| default: // ELF | |
| ef, err := elf.NewFile(obj) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| syms, err := ef.Symbols() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| for _, s := range syms { | |
| switch s.Name { | |
| case c1: | |
| found1 = true | |
| case c2: | |
| found2 = true | |
| } | |
| } | |
| } | |
| } | |
| if !foundgo { | |
| t.Errorf(`go object not found`) | |
| } | |
| if !found1 { | |
| t.Errorf(`symbol %q not found`, c1) | |
| } | |
| if !found2 { | |
| t.Errorf(`symbol %q not found`, c2) | |
| } | |
| } | |
| func TestExactly16Bytes(t *testing.T) { | |
| var tests = []string{ | |
| "", | |
| "a", | |
| "日本語", | |
| "1234567890123456", | |
| "12345678901234567890", | |
| "1234567890123本語4567890", | |
| "12345678901234日本語567890", | |
| "123456789012345日本語67890", | |
| "1234567890123456日本語7890", | |
| "1234567890123456日本語7日本語890", | |
| } | |
| for _, str := range tests { | |
| got := exactly16Bytes(str) | |
| if len(got) != 16 { | |
| t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got)) | |
| } | |
| // Make sure it is full runes. | |
| for _, c := range got { | |
| if c == utf8.RuneError { | |
| t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got) | |
| } | |
| } | |
| } | |
| } |