buildinfo_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Copyright 2021 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. package buildinfo_test
  5. import (
  6. "bytes"
  7. "debug/buildinfo"
  8. "flag"
  9. "internal/testenv"
  10. "os"
  11. "os/exec"
  12. "path"
  13. "path/filepath"
  14. "regexp"
  15. "runtime"
  16. "strings"
  17. "testing"
  18. )
  19. var flagAll = flag.Bool("all", false, "test all supported GOOS/GOARCH platforms, instead of only the current platform")
  20. // TestReadFile confirms that ReadFile can read build information from binaries
  21. // on supported target platforms. It builds a trivial binary on the current
  22. // platforms (or all platforms if -all is set) in various configurations and
  23. // checks that build information can or cannot be read.
  24. func TestReadFile(t *testing.T) {
  25. if testing.Short() {
  26. t.Skip("test requires compiling and linking, which may be slow")
  27. }
  28. testenv.MustHaveGoBuild(t)
  29. type platform struct{ goos, goarch string }
  30. platforms := []platform{
  31. {"aix", "ppc64"},
  32. {"darwin", "amd64"},
  33. {"darwin", "arm64"},
  34. {"linux", "386"},
  35. {"linux", "amd64"},
  36. {"windows", "386"},
  37. {"windows", "amd64"},
  38. }
  39. runtimePlatform := platform{runtime.GOOS, runtime.GOARCH}
  40. haveRuntimePlatform := false
  41. for _, p := range platforms {
  42. if p == runtimePlatform {
  43. haveRuntimePlatform = true
  44. break
  45. }
  46. }
  47. if !haveRuntimePlatform {
  48. platforms = append(platforms, runtimePlatform)
  49. }
  50. buildWithModules := func(t *testing.T, goos, goarch string) string {
  51. dir := t.TempDir()
  52. gomodPath := filepath.Join(dir, "go.mod")
  53. gomodData := []byte("module example.com/m\ngo 1.18\n")
  54. if err := os.WriteFile(gomodPath, gomodData, 0666); err != nil {
  55. t.Fatal(err)
  56. }
  57. helloPath := filepath.Join(dir, "hello.go")
  58. helloData := []byte("package main\nfunc main() {}\n")
  59. if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
  60. t.Fatal(err)
  61. }
  62. outPath := filepath.Join(dir, path.Base(t.Name()))
  63. cmd := exec.Command("go", "build", "-o="+outPath)
  64. cmd.Dir = dir
  65. cmd.Env = append(os.Environ(), "GO111MODULE=on", "GOOS="+goos, "GOARCH="+goarch)
  66. stderr := &bytes.Buffer{}
  67. cmd.Stderr = stderr
  68. if err := cmd.Run(); err != nil {
  69. t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
  70. }
  71. return outPath
  72. }
  73. buildWithGOPATH := func(t *testing.T, goos, goarch string) string {
  74. gopathDir := t.TempDir()
  75. pkgDir := filepath.Join(gopathDir, "src/example.com/m")
  76. if err := os.MkdirAll(pkgDir, 0777); err != nil {
  77. t.Fatal(err)
  78. }
  79. helloPath := filepath.Join(pkgDir, "hello.go")
  80. helloData := []byte("package main\nfunc main() {}\n")
  81. if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
  82. t.Fatal(err)
  83. }
  84. outPath := filepath.Join(gopathDir, path.Base(t.Name()))
  85. cmd := exec.Command("go", "build", "-o="+outPath)
  86. cmd.Dir = pkgDir
  87. cmd.Env = append(os.Environ(), "GO111MODULE=off", "GOPATH="+gopathDir, "GOOS="+goos, "GOARCH="+goarch)
  88. stderr := &bytes.Buffer{}
  89. cmd.Stderr = stderr
  90. if err := cmd.Run(); err != nil {
  91. t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
  92. }
  93. return outPath
  94. }
  95. damageBuildInfo := func(t *testing.T, name string) {
  96. data, err := os.ReadFile(name)
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. i := bytes.Index(data, []byte("\xff Go buildinf:"))
  101. if i < 0 {
  102. t.Fatal("Go buildinf not found")
  103. }
  104. data[i+2] = 'N'
  105. if err := os.WriteFile(name, data, 0666); err != nil {
  106. t.Fatal(err)
  107. }
  108. }
  109. goVersionRe := regexp.MustCompile("(?m)^go\t.*\n")
  110. buildRe := regexp.MustCompile("(?m)^build\t.*\n")
  111. cleanOutputForComparison := func(got string) string {
  112. // Remove or replace anything that might depend on the test's environment
  113. // so we can check the output afterward with a string comparison.
  114. // We'll remove all build lines except the compiler, just to make sure
  115. // build lines are included.
  116. got = goVersionRe.ReplaceAllString(got, "go\tGOVERSION\n")
  117. got = buildRe.ReplaceAllStringFunc(got, func(match string) string {
  118. if strings.HasPrefix(match, "build\t-compiler=") {
  119. return match
  120. }
  121. return ""
  122. })
  123. return got
  124. }
  125. cases := []struct {
  126. name string
  127. build func(t *testing.T, goos, goarch string) string
  128. want string
  129. wantErr string
  130. }{
  131. {
  132. name: "doesnotexist",
  133. build: func(t *testing.T, goos, goarch string) string {
  134. return "doesnotexist.txt"
  135. },
  136. wantErr: "doesnotexist",
  137. },
  138. {
  139. name: "empty",
  140. build: func(t *testing.T, _, _ string) string {
  141. dir := t.TempDir()
  142. name := filepath.Join(dir, "empty")
  143. if err := os.WriteFile(name, nil, 0666); err != nil {
  144. t.Fatal(err)
  145. }
  146. return name
  147. },
  148. wantErr: "unrecognized file format",
  149. },
  150. {
  151. name: "valid_modules",
  152. build: buildWithModules,
  153. want: "go\tGOVERSION\n" +
  154. "path\texample.com/m\n" +
  155. "mod\texample.com/m\t(devel)\t\n" +
  156. "build\t-compiler=gc\n",
  157. },
  158. {
  159. name: "invalid_modules",
  160. build: func(t *testing.T, goos, goarch string) string {
  161. name := buildWithModules(t, goos, goarch)
  162. damageBuildInfo(t, name)
  163. return name
  164. },
  165. wantErr: "not a Go executable",
  166. },
  167. {
  168. name: "valid_gopath",
  169. build: buildWithGOPATH,
  170. want: "go\tGOVERSION\n" +
  171. "path\texample.com/m\n" +
  172. "build\t-compiler=gc\n",
  173. },
  174. {
  175. name: "invalid_gopath",
  176. build: func(t *testing.T, goos, goarch string) string {
  177. name := buildWithGOPATH(t, goos, goarch)
  178. damageBuildInfo(t, name)
  179. return name
  180. },
  181. wantErr: "not a Go executable",
  182. },
  183. }
  184. for _, p := range platforms {
  185. p := p
  186. t.Run(p.goos+"_"+p.goarch, func(t *testing.T) {
  187. if p != runtimePlatform && !*flagAll {
  188. t.Skipf("skipping platforms other than %s_%s because -all was not set", runtimePlatform.goos, runtimePlatform.goarch)
  189. }
  190. for _, tc := range cases {
  191. tc := tc
  192. t.Run(tc.name, func(t *testing.T) {
  193. t.Parallel()
  194. name := tc.build(t, p.goos, p.goarch)
  195. if info, err := buildinfo.ReadFile(name); err != nil {
  196. if tc.wantErr == "" {
  197. t.Fatalf("unexpected error: %v", err)
  198. } else if errMsg := err.Error(); !strings.Contains(errMsg, tc.wantErr) {
  199. t.Fatalf("got error %q; want error containing %q", errMsg, tc.wantErr)
  200. }
  201. } else {
  202. if tc.wantErr != "" {
  203. t.Fatalf("unexpected success; want error containing %q", tc.wantErr)
  204. }
  205. got := info.String()
  206. if clean := cleanOutputForComparison(string(got)); got != tc.want && clean != tc.want {
  207. t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want)
  208. }
  209. }
  210. })
  211. }
  212. })
  213. }
  214. }