1
2
3
4
5 package archive
6
7 import (
8 "bytes"
9 "debug/elf"
10 "debug/macho"
11 "debug/pe"
12 "fmt"
13 "internal/testenv"
14 "internal/xcoff"
15 "io"
16 "os"
17 "path/filepath"
18 "runtime"
19 "sync"
20 "testing"
21 "unicode/utf8"
22 )
23
24 var buildDir string
25
26 func TestMain(m *testing.M) {
27 if !testenv.HasGoBuild() {
28 return
29 }
30
31 exit := m.Run()
32
33 if buildDir != "" {
34 os.RemoveAll(buildDir)
35 }
36 os.Exit(exit)
37 }
38
39 func copyDir(dst, src string) error {
40 err := os.MkdirAll(dst, 0777)
41 if err != nil {
42 return err
43 }
44 entries, err := os.ReadDir(src)
45 if err != nil {
46 return err
47 }
48 for _, entry := range entries {
49 err = copyFile(filepath.Join(dst, entry.Name()), filepath.Join(src, entry.Name()))
50 if err != nil {
51 return err
52 }
53 }
54 return nil
55 }
56
57 func copyFile(dst, src string) (err error) {
58 var s, d *os.File
59 s, err = os.Open(src)
60 if err != nil {
61 return err
62 }
63 defer s.Close()
64 d, err = os.Create(dst)
65 if err != nil {
66 return err
67 }
68 defer func() {
69 e := d.Close()
70 if err == nil {
71 err = e
72 }
73 }()
74 _, err = io.Copy(d, s)
75 if err != nil {
76 return err
77 }
78 return nil
79 }
80
81 var (
82 buildOnce sync.Once
83 builtGoobjs goobjPaths
84 buildErr error
85 )
86
87 type goobjPaths struct {
88 go1obj string
89 go2obj string
90 goarchive string
91 cgoarchive string
92 }
93
94 func buildGoobj(t *testing.T) goobjPaths {
95 buildOnce.Do(func() {
96 buildErr = func() (err error) {
97 buildDir, err = os.MkdirTemp("", "TestGoobj")
98 if err != nil {
99 return err
100 }
101
102 go1obj := filepath.Join(buildDir, "go1.o")
103 go2obj := filepath.Join(buildDir, "go2.o")
104 goarchive := filepath.Join(buildDir, "go.a")
105 cgoarchive := ""
106
107 gotool, err := testenv.GoTool()
108 if err != nil {
109 return err
110 }
111
112 go1src := filepath.Join("testdata", "go1.go")
113 go2src := filepath.Join("testdata", "go2.go")
114
115 importcfgfile := filepath.Join(buildDir, "importcfg")
116 testenv.WriteImportcfg(t, importcfgfile, nil, go1src, go2src)
117
118 out, err := testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go1obj, go1src).CombinedOutput()
119 if err != nil {
120 return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go1obj, go1src, err, out)
121 }
122 out, err = testenv.Command(t, gotool, "tool", "compile", "-importcfg="+importcfgfile, "-p=p", "-o", go2obj, go2src).CombinedOutput()
123 if err != nil {
124 return fmt.Errorf("go tool compile -o %s %s: %v\n%s", go2obj, go2src, err, out)
125 }
126 out, err = testenv.Command(t, gotool, "tool", "pack", "c", goarchive, go1obj, go2obj).CombinedOutput()
127 if err != nil {
128 return fmt.Errorf("go tool pack c %s %s %s: %v\n%s", goarchive, go1obj, go2obj, err, out)
129 }
130
131 if testenv.HasCGO() {
132 cgoarchive = filepath.Join(buildDir, "mycgo.a")
133 gopath := filepath.Join(buildDir, "gopath")
134 err = copyDir(filepath.Join(gopath, "src", "mycgo"), filepath.Join("testdata", "mycgo"))
135 if err == nil {
136 err = os.WriteFile(filepath.Join(gopath, "src", "mycgo", "go.mod"), []byte("module mycgo\n"), 0666)
137 }
138 if err != nil {
139 return err
140 }
141 cmd := testenv.Command(t, gotool, "build", "-buildmode=archive", "-o", cgoarchive, "-gcflags=all="+os.Getenv("GO_GCFLAGS"), "mycgo")
142 cmd.Dir = filepath.Join(gopath, "src", "mycgo")
143 cmd.Env = append(os.Environ(), "GOPATH="+gopath)
144 out, err = cmd.CombinedOutput()
145 if err != nil {
146 return fmt.Errorf("go install mycgo: %v\n%s", err, out)
147 }
148 }
149
150 builtGoobjs = goobjPaths{
151 go1obj: go1obj,
152 go2obj: go2obj,
153 goarchive: goarchive,
154 cgoarchive: cgoarchive,
155 }
156 return nil
157 }()
158 })
159
160 if buildErr != nil {
161 t.Helper()
162 t.Fatal(buildErr)
163 }
164 return builtGoobjs
165 }
166
167 func TestParseGoobj(t *testing.T) {
168 path := buildGoobj(t).go1obj
169
170 f, err := os.Open(path)
171 if err != nil {
172 t.Fatal(err)
173 }
174 defer f.Close()
175
176 a, err := Parse(f, false)
177 if err != nil {
178 t.Fatal(err)
179 }
180 if len(a.Entries) != 2 {
181 t.Errorf("expect 2 entry, found %d", len(a.Entries))
182 }
183 for _, e := range a.Entries {
184 if e.Type == EntryPkgDef {
185 continue
186 }
187 if e.Type != EntryGoObj {
188 t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
189 }
190 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
191 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
192 }
193 }
194 }
195
196 func TestParseArchive(t *testing.T) {
197 path := buildGoobj(t).goarchive
198
199 f, err := os.Open(path)
200 if err != nil {
201 t.Fatal(err)
202 }
203 defer f.Close()
204
205 a, err := Parse(f, false)
206 if err != nil {
207 t.Fatal(err)
208 }
209 if len(a.Entries) != 3 {
210 t.Errorf("expect 3 entry, found %d", len(a.Entries))
211 }
212 var found1 bool
213 var found2 bool
214 for _, e := range a.Entries {
215 if e.Type == EntryPkgDef {
216 continue
217 }
218 if e.Type != EntryGoObj {
219 t.Errorf("wrong type of object: want EntryGoObj, got %v", e.Type)
220 }
221 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
222 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
223 }
224 if e.Name == "go1.o" {
225 found1 = true
226 }
227 if e.Name == "go2.o" {
228 found2 = true
229 }
230 }
231 if !found1 {
232 t.Errorf(`object "go1.o" not found`)
233 }
234 if !found2 {
235 t.Errorf(`object "go2.o" not found`)
236 }
237 }
238
239 func TestParseCGOArchive(t *testing.T) {
240 testenv.MustHaveCGO(t)
241
242 path := buildGoobj(t).cgoarchive
243
244 f, err := os.Open(path)
245 if err != nil {
246 t.Fatal(err)
247 }
248 defer f.Close()
249
250 a, err := Parse(f, false)
251 if err != nil {
252 t.Fatal(err)
253 }
254
255 c1 := "c1"
256 c2 := "c2"
257 switch runtime.GOOS {
258 case "darwin", "ios":
259 c1 = "_" + c1
260 c2 = "_" + c2
261 case "windows":
262 if runtime.GOARCH == "386" {
263 c1 = "_" + c1
264 c2 = "_" + c2
265 }
266 case "aix":
267 c1 = "." + c1
268 c2 = "." + c2
269 }
270
271 var foundgo, found1, found2 bool
272
273 for _, e := range a.Entries {
274 switch e.Type {
275 default:
276 t.Errorf("unknown object type")
277 case EntryPkgDef:
278 continue
279 case EntryGoObj:
280 foundgo = true
281 if !bytes.Contains(e.Obj.TextHeader, []byte(runtime.GOARCH)) {
282 t.Errorf("text header does not contain GOARCH %s: %q", runtime.GOARCH, e.Obj.TextHeader)
283 }
284 continue
285 case EntryNativeObj:
286 }
287
288 obj := io.NewSectionReader(f, e.Offset, e.Size)
289 switch runtime.GOOS {
290 case "darwin", "ios":
291 mf, err := macho.NewFile(obj)
292 if err != nil {
293 t.Fatal(err)
294 }
295 if mf.Symtab == nil {
296 continue
297 }
298 for _, s := range mf.Symtab.Syms {
299 switch s.Name {
300 case c1:
301 found1 = true
302 case c2:
303 found2 = true
304 }
305 }
306 case "windows":
307 pf, err := pe.NewFile(obj)
308 if err != nil {
309 t.Fatal(err)
310 }
311 for _, s := range pf.Symbols {
312 switch s.Name {
313 case c1:
314 found1 = true
315 case c2:
316 found2 = true
317 }
318 }
319 case "aix":
320 xf, err := xcoff.NewFile(obj)
321 if err != nil {
322 t.Fatal(err)
323 }
324 for _, s := range xf.Symbols {
325 switch s.Name {
326 case c1:
327 found1 = true
328 case c2:
329 found2 = true
330 }
331 }
332 default:
333 ef, err := elf.NewFile(obj)
334 if err != nil {
335 t.Fatal(err)
336 }
337 syms, err := ef.Symbols()
338 if err != nil {
339 t.Fatal(err)
340 }
341 for _, s := range syms {
342 switch s.Name {
343 case c1:
344 found1 = true
345 case c2:
346 found2 = true
347 }
348 }
349 }
350 }
351
352 if !foundgo {
353 t.Errorf(`go object not found`)
354 }
355 if !found1 {
356 t.Errorf(`symbol %q not found`, c1)
357 }
358 if !found2 {
359 t.Errorf(`symbol %q not found`, c2)
360 }
361 }
362
363 func TestExactly16Bytes(t *testing.T) {
364 var tests = []string{
365 "",
366 "a",
367 "日本語",
368 "1234567890123456",
369 "12345678901234567890",
370 "1234567890123本語4567890",
371 "12345678901234日本語567890",
372 "123456789012345日本語67890",
373 "1234567890123456日本語7890",
374 "1234567890123456日本語7日本語890",
375 }
376 for _, str := range tests {
377 got := exactly16Bytes(str)
378 if len(got) != 16 {
379 t.Errorf("exactly16Bytes(%q) is %q, length %d", str, got, len(got))
380 }
381
382 for _, c := range got {
383 if c == utf8.RuneError {
384 t.Errorf("exactly16Bytes(%q) is %q, has partial rune", str, got)
385 }
386 }
387 }
388 }
389
View as plain text