main_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // Copyright (c) 2019, The Garble Authors.
  2. // See LICENSE for licensing information.
  3. package main
  4. import (
  5. "flag"
  6. "fmt"
  7. "go/ast"
  8. "go/printer"
  9. "go/token"
  10. "io/fs"
  11. mathrand "math/rand"
  12. "os"
  13. "os/exec"
  14. "path/filepath"
  15. "regexp"
  16. "runtime"
  17. "strings"
  18. "testing"
  19. "time"
  20. "github.com/google/go-cmp/cmp"
  21. "github.com/rogpeppe/go-internal/goproxytest"
  22. "github.com/rogpeppe/go-internal/gotooltest"
  23. "github.com/rogpeppe/go-internal/testscript"
  24. ah "mvdan.cc/garble/internal/asthelper"
  25. )
  26. var proxyURL string
  27. func TestMain(m *testing.M) {
  28. os.Exit(testscript.RunMain(garbleMain{m}, map[string]func() int{
  29. "garble": main1,
  30. }))
  31. }
  32. type garbleMain struct {
  33. m *testing.M
  34. }
  35. func (m garbleMain) Run() int {
  36. // Start the Go proxy server running for all tests.
  37. srv, err := goproxytest.NewServer("testdata/mod", "")
  38. if err != nil {
  39. panic(fmt.Sprintf("cannot start proxy: %v", err))
  40. }
  41. proxyURL = srv.URL
  42. return m.m.Run()
  43. }
  44. var update = flag.Bool("u", false, "update testscript output files")
  45. func TestScript(t *testing.T) {
  46. t.Parallel()
  47. execPath, err := os.Executable()
  48. if err != nil {
  49. t.Fatal(err)
  50. }
  51. p := testscript.Params{
  52. Dir: filepath.Join("testdata", "script"),
  53. Setup: func(env *testscript.Env) error {
  54. env.Vars = append(env.Vars,
  55. // Use testdata/mod as our module proxy.
  56. "GOPROXY="+proxyURL,
  57. // We use our own proxy, so avoid sum.golang.org.
  58. "GONOSUMDB=*",
  59. // "go build" starts many short-lived Go processes,
  60. // such as asm, buildid, compile, and link.
  61. // They don't allocate huge amounts of memory,
  62. // and they'll exit within seconds,
  63. // so using the GC is basically a waste of CPU.
  64. // Turn it off entirely, releasing memory on exit.
  65. //
  66. // We don't want this setting always on,
  67. // as it could result in memory problems for users.
  68. // But it helps for our test suite,
  69. // as the packages are relatively small.
  70. "GOGC=off",
  71. "gofullversion="+runtime.Version(),
  72. "EXEC_PATH="+execPath,
  73. )
  74. if os.Getenv("TESTSCRIPT_COVER_DIR") != "" {
  75. // Don't reuse the build cache if we want to collect
  76. // code coverage. Otherwise, many toolexec calls would
  77. // be avoided and the coverage would be incomplete.
  78. // TODO: to not make "go test" insanely slow, we could still use
  79. // an empty GOCACHE, but share it between all the test scripts.
  80. env.Vars = append(env.Vars, "GOCACHE="+filepath.Join(env.WorkDir, "go-cache-tmp"))
  81. }
  82. return nil
  83. },
  84. // TODO: this condition should probably be supported by gotooltest
  85. Condition: func(cond string) (bool, error) {
  86. switch cond {
  87. case "cgo":
  88. out, err := exec.Command("go", "env", "CGO_ENABLED").CombinedOutput()
  89. if err != nil {
  90. return false, err
  91. }
  92. result := strings.TrimSpace(string(out))
  93. switch result {
  94. case "0", "1":
  95. return result == "1", nil
  96. default:
  97. return false, fmt.Errorf("unknown CGO_ENABLED: %q", result)
  98. }
  99. }
  100. return false, fmt.Errorf("unknown condition")
  101. },
  102. Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
  103. "sleep": sleep,
  104. "binsubstr": binsubstr,
  105. "bincmp": bincmp,
  106. "generate-literals": generateLiterals,
  107. "setenvfile": setenvfile,
  108. "grepfiles": grepfiles,
  109. },
  110. UpdateScripts: *update,
  111. }
  112. if err := gotooltest.Setup(&p); err != nil {
  113. t.Fatal(err)
  114. }
  115. testscript.Run(t, p)
  116. }
  117. func createFile(ts *testscript.TestScript, path string) *os.File {
  118. file, err := os.Create(ts.MkAbs(path))
  119. if err != nil {
  120. ts.Fatalf("%v", err)
  121. }
  122. return file
  123. }
  124. // sleep is akin to a shell's sleep builtin.
  125. // Note that tests should almost never use this; it's currently only used to
  126. // work around a low-level Go syscall race on Linux.
  127. func sleep(ts *testscript.TestScript, neg bool, args []string) {
  128. if len(args) != 1 {
  129. ts.Fatalf("usage: sleep duration")
  130. }
  131. d, err := time.ParseDuration(args[0])
  132. if err != nil {
  133. ts.Fatalf("%v", err)
  134. }
  135. time.Sleep(d)
  136. }
  137. func binsubstr(ts *testscript.TestScript, neg bool, args []string) {
  138. if len(args) < 2 {
  139. ts.Fatalf("usage: binsubstr file substr...")
  140. }
  141. data := ts.ReadFile(args[0])
  142. var failed []string
  143. for _, substr := range args[1:] {
  144. match := strings.Contains(data, substr)
  145. if match && neg {
  146. failed = append(failed, substr)
  147. } else if !match && !neg {
  148. failed = append(failed, substr)
  149. }
  150. }
  151. if len(failed) > 0 && neg {
  152. ts.Fatalf("unexpected match for %q in %s", failed, args[0])
  153. } else if len(failed) > 0 {
  154. ts.Fatalf("expected match for %q in %s", failed, args[0])
  155. }
  156. }
  157. func bincmp(ts *testscript.TestScript, neg bool, args []string) {
  158. if len(args) != 2 {
  159. ts.Fatalf("usage: bincmp file1 file2")
  160. }
  161. for _, arg := range args {
  162. switch arg {
  163. case "stdout", "stderr":
  164. // Note that the diffoscope call below would not deal with
  165. // stdout/stderr either.
  166. ts.Fatalf("bincmp is for binary files. did you mean cmp?")
  167. }
  168. }
  169. data1 := ts.ReadFile(args[0])
  170. data2 := ts.ReadFile(args[1])
  171. if neg {
  172. if data1 == data2 {
  173. ts.Fatalf("%s and %s don't differ", args[0], args[1])
  174. }
  175. return
  176. }
  177. if data1 != data2 {
  178. if _, err := exec.LookPath("diffoscope"); err != nil {
  179. ts.Logf("diffoscope is not installing; skipping binary diff")
  180. } else {
  181. // We'll error below; ignore the exec error here.
  182. ts.Exec("diffoscope",
  183. "--diff-context", "2", // down from 7 by default
  184. "--max-text-report-size", "4096", // no limit (in bytes) by default; avoid huge output
  185. ts.MkAbs(args[0]), ts.MkAbs(args[1]))
  186. }
  187. sizeDiff := len(data2) - len(data1)
  188. ts.Fatalf("%s and %s differ; diffoscope above, size diff: %+d",
  189. args[0], args[1], sizeDiff)
  190. }
  191. }
  192. func generateStringLit(size int) *ast.BasicLit {
  193. buffer := make([]byte, size)
  194. _, err := mathrand.Read(buffer)
  195. if err != nil {
  196. panic(err)
  197. }
  198. return ah.StringLit(string(buffer))
  199. }
  200. func generateLiterals(ts *testscript.TestScript, neg bool, args []string) {
  201. if neg {
  202. ts.Fatalf("unsupported: ! generate-literals")
  203. }
  204. if len(args) != 1 {
  205. ts.Fatalf("usage: generate-literals file")
  206. }
  207. codePath := args[0]
  208. // Add 100 randomly small literals.
  209. var statements []ast.Stmt
  210. for i := 0; i < 100; i++ {
  211. literal := generateStringLit(1 + mathrand.Intn(255))
  212. statements = append(statements, &ast.AssignStmt{
  213. Lhs: []ast.Expr{ast.NewIdent("_")},
  214. Tok: token.ASSIGN,
  215. Rhs: []ast.Expr{literal},
  216. })
  217. }
  218. // Add 5 huge literals, to make sure we don't try to obfuscate them.
  219. // 5 * 128KiB is large enough that it would take a very, very long time
  220. // to obfuscate those literals with our simple code.
  221. for i := 0; i < 5; i++ {
  222. literal := generateStringLit(128 << 10)
  223. statements = append(statements, &ast.AssignStmt{
  224. Lhs: []ast.Expr{ast.NewIdent("_")},
  225. Tok: token.ASSIGN,
  226. Rhs: []ast.Expr{literal},
  227. })
  228. }
  229. file := &ast.File{
  230. Name: ast.NewIdent("main"),
  231. Decls: []ast.Decl{&ast.FuncDecl{
  232. Name: ast.NewIdent("extraLiterals"),
  233. Type: &ast.FuncType{Params: &ast.FieldList{}},
  234. Body: ah.BlockStmt(statements...),
  235. }},
  236. }
  237. codeFile := createFile(ts, codePath)
  238. defer codeFile.Close()
  239. if err := printer.Fprint(codeFile, token.NewFileSet(), file); err != nil {
  240. ts.Fatalf("%v", err)
  241. }
  242. }
  243. func setenvfile(ts *testscript.TestScript, neg bool, args []string) {
  244. if neg {
  245. ts.Fatalf("unsupported: ! setenvfile")
  246. }
  247. if len(args) != 2 {
  248. ts.Fatalf("usage: setenvfile name file")
  249. }
  250. ts.Setenv(args[0], ts.ReadFile(args[1]))
  251. }
  252. func grepfiles(ts *testscript.TestScript, neg bool, args []string) {
  253. if len(args) != 2 {
  254. ts.Fatalf("usage: grepfiles path pattern")
  255. }
  256. anyFound := false
  257. path, pattern := args[0], args[1]
  258. rx := regexp.MustCompile(pattern)
  259. // TODO: use fs.SkipAll in Go 1.20 and later.
  260. errSkipAll := fmt.Errorf("sentinel error: stop walking")
  261. if err := filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
  262. if err != nil {
  263. return err
  264. }
  265. if rx.MatchString(path) {
  266. if neg {
  267. return fmt.Errorf("%q matches %q", path, pattern)
  268. } else {
  269. anyFound = true
  270. return errSkipAll
  271. }
  272. }
  273. return nil
  274. }); err != nil && err != errSkipAll {
  275. ts.Fatalf("%s", err)
  276. }
  277. if !neg && !anyFound {
  278. ts.Fatalf("no matches for %q", pattern)
  279. }
  280. }
  281. func TestSplitFlagsFromArgs(t *testing.T) {
  282. t.Parallel()
  283. tests := []struct {
  284. name string
  285. args []string
  286. want [2][]string
  287. }{
  288. {"Empty", []string{}, [2][]string{{}, nil}},
  289. {
  290. "JustFlags",
  291. []string{"-foo", "bar", "-baz"},
  292. [2][]string{{"-foo", "bar", "-baz"}, nil},
  293. },
  294. {
  295. "JustArgs",
  296. []string{"some", "pkgs"},
  297. [2][]string{{}, {"some", "pkgs"}},
  298. },
  299. {
  300. "FlagsAndArgs",
  301. []string{"-foo=bar", "baz"},
  302. [2][]string{{"-foo=bar"}, {"baz"}},
  303. },
  304. {
  305. "BoolFlagsAndArgs",
  306. []string{"-race", "pkg"},
  307. [2][]string{{"-race"}, {"pkg"}},
  308. },
  309. {
  310. "ExplicitBoolFlag",
  311. []string{"-race=true", "pkg"},
  312. [2][]string{{"-race=true"}, {"pkg"}},
  313. },
  314. }
  315. for _, test := range tests {
  316. test := test
  317. t.Run(test.name, func(t *testing.T) {
  318. t.Parallel()
  319. flags, args := splitFlagsFromArgs(test.args)
  320. got := [2][]string{flags, args}
  321. if diff := cmp.Diff(test.want, got); diff != "" {
  322. t.Fatalf("splitFlagsFromArgs(%q) mismatch (-want +got):\n%s", test.args, diff)
  323. }
  324. })
  325. }
  326. }
  327. func TestFilterForwardBuildFlags(t *testing.T) {
  328. t.Parallel()
  329. tests := []struct {
  330. name string
  331. flags []string
  332. want []string
  333. }{
  334. {"Empty", []string{}, nil},
  335. {
  336. "NoBuild",
  337. []string{"-short", "-json"},
  338. nil,
  339. },
  340. {
  341. "Mixed",
  342. []string{"-short", "-tags", "foo", "-mod=readonly", "-json"},
  343. []string{"-tags", "foo", "-mod=readonly"},
  344. },
  345. {
  346. "NonBinarySkipped",
  347. []string{"-o", "binary", "-tags", "foo"},
  348. []string{"-tags", "foo"},
  349. },
  350. }
  351. for _, test := range tests {
  352. test := test
  353. t.Run(test.name, func(t *testing.T) {
  354. t.Parallel()
  355. got, _ := filterForwardBuildFlags(test.flags)
  356. if diff := cmp.Diff(test.want, got); diff != "" {
  357. t.Fatalf("filterForwardBuildFlags(%q) mismatch (-want +got):\n%s", test.flags, diff)
  358. }
  359. })
  360. }
  361. }
  362. func TestFlagValue(t *testing.T) {
  363. t.Parallel()
  364. tests := []struct {
  365. name string
  366. flags []string
  367. flagName string
  368. want string
  369. }{
  370. {"StrSpace", []string{"-buildid", "bar"}, "-buildid", "bar"},
  371. {"StrSpaceDash", []string{"-buildid", "-bar"}, "-buildid", "-bar"},
  372. {"StrEqual", []string{"-buildid=bar"}, "-buildid", "bar"},
  373. {"StrEqualDash", []string{"-buildid=-bar"}, "-buildid", "-bar"},
  374. {"StrMissing", []string{"-foo"}, "-buildid", ""},
  375. {"StrNotFollowed", []string{"-buildid"}, "-buildid", ""},
  376. {"StrEmpty", []string{"-buildid="}, "-buildid", ""},
  377. }
  378. for _, test := range tests {
  379. test := test
  380. t.Run(test.name, func(t *testing.T) {
  381. t.Parallel()
  382. got := flagValue(test.flags, test.flagName)
  383. if got != test.want {
  384. t.Fatalf("flagValue(%q, %q) got %q, want %q",
  385. test.flags, test.flagName, got, test.want)
  386. }
  387. })
  388. }
  389. }