main_test.go 10 KB

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