github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/tool.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "go/ast" 8 "go/build" 9 "go/doc" 10 "go/parser" 11 "go/scanner" 12 "go/token" 13 "go/types" 14 "io" 15 "io/ioutil" 16 "net" 17 "net/http" 18 "os" 19 "os/exec" 20 "path" 21 "path/filepath" 22 "runtime" 23 "sort" 24 "strconv" 25 "strings" 26 "syscall" 27 "text/template" 28 "time" 29 "unicode" 30 "unicode/utf8" 31 32 gbuild "github.com/goplusjs/gopherjs/build" 33 "github.com/goplusjs/gopherjs/compiler" 34 "github.com/goplusjs/gopherjs/internal/sysutil" 35 "github.com/kisielk/gotool" 36 "github.com/neelance/sourcemap" 37 "github.com/spf13/cobra" 38 "github.com/spf13/pflag" 39 "golang.org/x/crypto/ssh/terminal" 40 "golang.org/x/tools/go/buildutil" 41 ) 42 43 var currentDirectory string 44 45 func init() { 46 var err error 47 currentDirectory, err = os.Getwd() 48 if err != nil { 49 fmt.Fprintln(os.Stderr, err) 50 os.Exit(1) 51 } 52 currentDirectory, err = filepath.EvalSymlinks(currentDirectory) 53 if err != nil { 54 fmt.Fprintln(os.Stderr, err) 55 os.Exit(1) 56 } 57 gopaths := filepath.SplitList(build.Default.GOPATH) 58 if len(gopaths) == 0 { 59 fmt.Fprintf(os.Stderr, "$GOPATH not set. For more details see: go help gopath\n") 60 os.Exit(1) 61 } 62 if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" { 63 build.Default.GOOS = "darwin" 64 } 65 } 66 67 func main() { 68 var ( 69 options = &gbuild.Options{CreateMapFile: true} 70 pkgObj string 71 tags string 72 ) 73 74 flagVerbose := pflag.NewFlagSet("", 0) 75 flagVerbose.BoolVarP(&options.Verbose, "verbose", "v", false, "print the names of packages as they are compiled") 76 flagQuiet := pflag.NewFlagSet("", 0) 77 flagQuiet.BoolVarP(&options.Quiet, "quiet", "q", false, "suppress non-fatal warnings") 78 79 compilerFlags := pflag.NewFlagSet("", 0) 80 compilerFlags.BoolVarP(&options.Minify, "minify", "m", false, "minify generated code") 81 compilerFlags.BoolVar(&options.Color, "color", terminal.IsTerminal(int(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb", "colored output") 82 compilerFlags.StringVar(&tags, "tags", "", "a list of build tags to consider satisfied during the build") 83 compilerFlags.BoolVar(&options.MapToLocalDisk, "localmap", false, "use local paths for sourcemap") 84 compilerFlags.BoolVarP(&options.Rebuild, "force", "a", false, "force rebuilding of packages that are already up-to-date") 85 86 flagWatch := pflag.NewFlagSet("", 0) 87 flagWatch.BoolVarP(&options.Watch, "watch", "w", false, "watch for changes to the source files") 88 89 cmdBuild := &cobra.Command{ 90 Use: "build [packages]", 91 Short: "compile packages and dependencies", 92 } 93 cmdBuild.Flags().StringVarP(&pkgObj, "output", "o", "", "output file") 94 cmdBuild.Flags().AddFlagSet(flagVerbose) 95 cmdBuild.Flags().AddFlagSet(flagQuiet) 96 cmdBuild.Flags().AddFlagSet(compilerFlags) 97 cmdBuild.Flags().AddFlagSet(flagWatch) 98 cmdBuild.Run = func(cmd *cobra.Command, args []string) { 99 options.BuildTags = strings.Fields(tags) 100 for { 101 s := gbuild.NewSession(options) 102 103 err := func() error { 104 // Handle "gopherjs build [files]" ad-hoc package mode. 105 if len(args) > 0 && (strings.HasSuffix(args[0], ".go") || strings.HasSuffix(args[0], ".inc.js")) { 106 for _, arg := range args { 107 if !strings.HasSuffix(arg, ".go") && !strings.HasSuffix(arg, ".inc.js") { 108 return fmt.Errorf("named files must be .go or .inc.js files") 109 } 110 } 111 if pkgObj == "" { 112 basename := filepath.Base(args[0]) 113 pkgObj = basename[:len(basename)-3] + ".js" 114 } 115 names := make([]string, len(args)) 116 for i, name := range args { 117 name = filepath.ToSlash(name) 118 names[i] = name 119 if s.Watcher != nil { 120 s.Watcher.Add(name) 121 } 122 } 123 err := s.BuildFiles(args, pkgObj, currentDirectory) 124 return err 125 } 126 127 // Expand import path patterns. 128 patternContext := gbuild.NewBuildContext("", options.BuildTags) 129 pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args) 130 for _, pkgPath := range pkgs { 131 if s.Watcher != nil { 132 pkg, err := gbuild.NewBuildContext(s.InstallSuffix(), options.BuildTags).Import(pkgPath, "", build.FindOnly) 133 if err != nil { 134 return err 135 } 136 s.Watcher.Add(pkg.Dir) 137 } 138 pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags) 139 if err != nil { 140 return err 141 } 142 archive, err := s.BuildPackage(pkg) 143 if err != nil { 144 return err 145 } 146 if len(pkgs) == 1 { // Only consider writing output if single package specified. 147 if pkgObj == "" { 148 pkgObj = filepath.Base(pkg.Dir) + ".js" 149 } 150 if pkg.IsCommand() && !pkg.UpToDate { 151 if err := s.WriteCommandPackage(archive, pkgObj); err != nil { 152 return err 153 } 154 } 155 } 156 } 157 return nil 158 }() 159 exitCode := handleError(err, options, nil) 160 161 if s.Watcher == nil { 162 os.Exit(exitCode) 163 } 164 s.WaitForChange() 165 } 166 } 167 168 cmdInstall := &cobra.Command{ 169 Use: "install [packages]", 170 Short: "compile and install packages and dependencies", 171 } 172 cmdInstall.Flags().AddFlagSet(flagVerbose) 173 cmdInstall.Flags().AddFlagSet(flagQuiet) 174 cmdInstall.Flags().AddFlagSet(compilerFlags) 175 cmdInstall.Flags().AddFlagSet(flagWatch) 176 cmdInstall.Run = func(cmd *cobra.Command, args []string) { 177 options.BuildTags = strings.Fields(tags) 178 for { 179 s := gbuild.NewSession(options) 180 181 err := func() error { 182 // Expand import path patterns. 183 patternContext := gbuild.NewBuildContext("", options.BuildTags) 184 pkgs := (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args) 185 186 if cmd.Name() == "get" { 187 goGet := exec.Command("go", append([]string{"get", "-d", "-tags=js"}, pkgs...)...) 188 goGet.Stdout = os.Stdout 189 goGet.Stderr = os.Stderr 190 if err := goGet.Run(); err != nil { 191 return err 192 } 193 } 194 for _, pkgPath := range pkgs { 195 pkg, err := gbuild.Import(pkgPath, 0, s.InstallSuffix(), options.BuildTags) 196 if s.Watcher != nil && pkg != nil { // add watch even on error 197 s.Watcher.Add(pkg.Dir) 198 } 199 if err != nil { 200 return err 201 } 202 archive, err := s.BuildPackage(pkg) 203 if err != nil { 204 return err 205 } 206 207 if pkg.IsCommand() && !pkg.UpToDate { 208 if err := s.WriteCommandPackage(archive, pkg.PkgObj); err != nil { 209 return err 210 } 211 } 212 } 213 return nil 214 }() 215 exitCode := handleError(err, options, nil) 216 217 if s.Watcher == nil { 218 os.Exit(exitCode) 219 } 220 s.WaitForChange() 221 } 222 } 223 224 cmdDoc := &cobra.Command{ 225 Use: "doc [arguments]", 226 Short: "display documentation for the requested, package, method or symbol", 227 } 228 cmdDoc.Run = func(cmd *cobra.Command, args []string) { 229 goDoc := exec.Command("go", append([]string{"doc"}, args...)...) 230 goDoc.Stdout = os.Stdout 231 goDoc.Stderr = os.Stderr 232 goDoc.Env = append(os.Environ(), "GOARCH=js") 233 err := goDoc.Run() 234 exitCode := handleError(err, options, nil) 235 os.Exit(exitCode) 236 } 237 238 cmdGet := &cobra.Command{ 239 Use: "get [packages]", 240 Short: "download and install packages and dependencies", 241 } 242 cmdGet.Flags().AddFlagSet(flagVerbose) 243 cmdGet.Flags().AddFlagSet(flagQuiet) 244 cmdGet.Flags().AddFlagSet(compilerFlags) 245 cmdGet.Run = cmdInstall.Run 246 247 cmdRun := &cobra.Command{ 248 Use: "run [gofiles...] [arguments...]", 249 Short: "compile and run Go program", 250 } 251 cmdRun.Flags().AddFlagSet(flagVerbose) 252 cmdRun.Flags().AddFlagSet(flagQuiet) 253 cmdRun.Flags().AddFlagSet(compilerFlags) 254 cmdRun.Run = func(cmd *cobra.Command, args []string) { 255 err := func() error { 256 lastSourceArg := 0 257 for { 258 if lastSourceArg == len(args) || !(strings.HasSuffix(args[lastSourceArg], ".go") || strings.HasSuffix(args[lastSourceArg], ".inc.js")) { 259 break 260 } 261 lastSourceArg++ 262 } 263 if lastSourceArg == 0 { 264 return fmt.Errorf("gopherjs run: no go files listed") 265 } 266 267 tempfile, err := ioutil.TempFile(currentDirectory, filepath.Base(args[0])+".") 268 if err != nil && strings.HasPrefix(currentDirectory, runtime.GOROOT()) { 269 tempfile, err = ioutil.TempFile("", filepath.Base(args[0])+".") 270 } 271 if err != nil { 272 return err 273 } 274 defer func() { 275 tempfile.Close() 276 os.Remove(tempfile.Name()) 277 os.Remove(tempfile.Name() + ".map") 278 }() 279 s := gbuild.NewSession(options) 280 if err := s.BuildFiles(args[:lastSourceArg], tempfile.Name(), currentDirectory); err != nil { 281 return err 282 } 283 if err := runNode(tempfile.Name(), args[lastSourceArg:], "", options.Quiet); err != nil { 284 return err 285 } 286 return nil 287 }() 288 exitCode := handleError(err, options, nil) 289 290 os.Exit(exitCode) 291 } 292 293 cmdTest := &cobra.Command{ 294 Use: "test [packages]", 295 Short: "test packages", 296 } 297 bench := cmdTest.Flags().String("bench", "", "Run benchmarks matching the regular expression. By default, no benchmarks run. To run all benchmarks, use '--bench=.'.") 298 benchtime := cmdTest.Flags().String("benchtime", "", "Run enough iterations of each benchmark to take t, specified as a time.Duration (for example, -benchtime 1h30s). The default is 1 second (1s).") 299 count := cmdTest.Flags().String("count", "", "Run each test and benchmark n times (default 1). Examples are always run once.") 300 run := cmdTest.Flags().String("run", "", "Run only those tests and examples matching the regular expression.") 301 short := cmdTest.Flags().Bool("short", false, "Tell long-running tests to shorten their run time.") 302 verbose := cmdTest.Flags().BoolP("verbose", "v", false, "Log all tests as they are run. Also print all text from Log and Logf calls even if the test succeeds.") 303 compileOnly := cmdTest.Flags().BoolP("compileonly", "c", false, "Compile the test binary to pkg.test.js but do not run it (where pkg is the last element of the package's import path). The file name can be changed with the -o flag.") 304 outputFilename := cmdTest.Flags().StringP("output", "o", "", "Compile the test binary to the named file. The test still runs (unless -c is specified).") 305 cmdTest.Flags().AddFlagSet(compilerFlags) 306 cmdTest.Run = func(cmd *cobra.Command, args []string) { 307 options.BuildTags = strings.Fields(tags) 308 err := func() error { 309 // Expand import path patterns. 310 patternContext := gbuild.NewBuildContext("", options.BuildTags) 311 args = (&gotool.Context{BuildContext: *patternContext}).ImportPaths(args) 312 313 if *compileOnly && len(args) > 1 { 314 return errors.New("cannot use -c flag with multiple packages") 315 } 316 if *outputFilename != "" && len(args) > 1 { 317 return errors.New("cannot use -o flag with multiple packages") 318 } 319 320 pkgs := make([]*gbuild.PackageData, len(args)) 321 for i, pkgPath := range args { 322 var err error 323 pkgs[i], err = gbuild.Import(pkgPath, 0, "", options.BuildTags) 324 if err != nil { 325 return err 326 } 327 } 328 329 var exitErr error 330 for _, pkg := range pkgs { 331 if len(pkg.TestGoFiles) == 0 && len(pkg.XTestGoFiles) == 0 { 332 fmt.Printf("? \t%s\t[no test files]\n", pkg.ImportPath) 333 continue 334 } 335 s := gbuild.NewSession(options) 336 337 tests := &testFuncs{BuildContext: s.BuildContext(), Package: pkg.Package} 338 collectTests := func(testPkg *gbuild.PackageData, testPkgName string, needVar *bool) error { 339 if testPkgName == "_test" { 340 for _, file := range pkg.TestGoFiles { 341 if err := tests.load(pkg.Package.Dir, file, testPkgName, &tests.ImportTest, &tests.NeedTest); err != nil { 342 return err 343 } 344 } 345 } else { 346 for _, file := range pkg.XTestGoFiles { 347 if err := tests.load(pkg.Package.Dir, file, "_xtest", &tests.ImportXtest, &tests.NeedXtest); err != nil { 348 return err 349 } 350 } 351 } 352 _, err := s.BuildPackage(testPkg) 353 return err 354 } 355 356 if err := collectTests(makeTestPkg(pkg, false), "_test", &tests.NeedTest); err != nil { 357 return err 358 } 359 360 if err := collectTests(makeTestPkg(pkg, true), "_xtest", &tests.NeedXtest); err != nil { 361 return err 362 } 363 364 buf := new(bytes.Buffer) 365 if err := testmainTmpl.Execute(buf, tests); err != nil { 366 return err 367 } 368 369 fset := token.NewFileSet() 370 mainFile, err := parser.ParseFile(fset, "_testmain.go", buf, 0) 371 if err != nil { 372 return err 373 } 374 375 importContext := &compiler.ImportContext{ 376 Packages: s.Types, 377 Import: func(path string) (*compiler.Archive, error) { 378 if path == pkg.ImportPath || path == pkg.ImportPath+"_test" { 379 return s.Archives[path], nil 380 } 381 return s.BuildImportPath(path) 382 }, 383 } 384 mainPkgArchive, err := compiler.Compile("main", []*ast.File{mainFile}, fset, importContext, nil, options.Minify) 385 if err != nil { 386 return err 387 } 388 389 if *compileOnly && *outputFilename == "" { 390 *outputFilename = pkg.Package.Name + "_test.js" 391 } 392 393 var outfile *os.File 394 if *outputFilename != "" { 395 outfile, err = os.Create(*outputFilename) 396 if err != nil { 397 return err 398 } 399 } else { 400 outfile, err = ioutil.TempFile(currentDirectory, "test.") 401 if err != nil { 402 return err 403 } 404 } 405 defer func() { 406 outfile.Close() 407 if *outputFilename == "" { 408 os.Remove(outfile.Name()) 409 os.Remove(outfile.Name() + ".map") 410 } 411 }() 412 413 if err := s.WriteCommandPackage(mainPkgArchive, outfile.Name()); err != nil { 414 return err 415 } 416 417 if *compileOnly { 418 continue 419 } 420 421 var args []string 422 if *bench != "" { 423 args = append(args, "-test.bench", *bench) 424 } 425 if *benchtime != "" { 426 args = append(args, "-test.benchtime", *benchtime) 427 } 428 if *count != "" { 429 args = append(args, "-test.count", *count) 430 } 431 if *run != "" { 432 args = append(args, "-test.run", *run) 433 } 434 if *short { 435 args = append(args, "-test.short") 436 } 437 if *verbose { 438 args = append(args, "-test.v") 439 } 440 status := "ok " 441 start := time.Now() 442 if err := runNode(outfile.Name(), args, runTestDir(pkg), options.Quiet); err != nil { 443 if _, ok := err.(*exec.ExitError); !ok { 444 return err 445 } 446 exitErr = err 447 status = "FAIL" 448 } 449 fmt.Printf("%s\t%s\t%.3fs\n", status, pkg.ImportPath, time.Since(start).Seconds()) 450 } 451 return exitErr 452 }() 453 exitCode := handleError(err, options, nil) 454 455 os.Exit(exitCode) 456 } 457 458 cmdServe := &cobra.Command{ 459 Use: "serve [root]", 460 Short: "compile on-the-fly and serve", 461 } 462 cmdServe.Flags().AddFlagSet(flagVerbose) 463 cmdServe.Flags().AddFlagSet(flagQuiet) 464 cmdServe.Flags().AddFlagSet(compilerFlags) 465 var addr string 466 cmdServe.Flags().StringVarP(&addr, "http", "", ":8080", "HTTP bind address to serve") 467 cmdServe.Run = func(cmd *cobra.Command, args []string) { 468 options.BuildTags = strings.Fields(tags) 469 dirs := append(filepath.SplitList(build.Default.GOPATH), build.Default.GOROOT) 470 var root string 471 472 if len(args) > 1 { 473 cmdServe.HelpFunc()(cmd, args) 474 os.Exit(1) 475 } 476 477 if len(args) == 1 { 478 root = args[0] 479 if strings.HasPrefix(root, ".") { 480 root = filepath.Join(currentDirectory, root) 481 } 482 } 483 484 sourceFiles := http.FileServer(serveCommandFileSystem{ 485 serveRoot: root, 486 options: options, 487 dirs: dirs, 488 sourceMaps: make(map[string][]byte), 489 }) 490 491 ln, err := net.Listen("tcp", addr) 492 if err != nil { 493 fmt.Fprintln(os.Stderr, err) 494 os.Exit(1) 495 } 496 if tcpAddr := ln.Addr().(*net.TCPAddr); tcpAddr.IP.Equal(net.IPv4zero) || tcpAddr.IP.Equal(net.IPv6zero) { // Any available addresses. 497 fmt.Printf("serving at http://localhost:%d and on port %d of any available addresses\n", tcpAddr.Port, tcpAddr.Port) 498 } else { // Specific address. 499 fmt.Printf("serving at http://%s\n", tcpAddr) 500 } 501 fmt.Fprintln(os.Stderr, http.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}, sourceFiles)) 502 } 503 504 cmdVersion := &cobra.Command{ 505 Use: "version", 506 Short: "print GopherJS compiler version", 507 } 508 cmdVersion.Run = func(cmd *cobra.Command, args []string) { 509 if len(args) > 0 { 510 cmdServe.HelpFunc()(cmd, args) 511 os.Exit(1) 512 } 513 golangVersion := fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH) 514 515 fmt.Printf("GopherJS %s (build on %s)\n", compiler.Version, golangVersion) 516 } 517 518 rootCmd := &cobra.Command{ 519 Use: "gopherjs", 520 Long: "GopherJS is a tool for compiling Go source code to JavaScript.", 521 } 522 rootCmd.AddCommand(cmdBuild, cmdGet, cmdInstall, cmdRun, cmdTest, cmdServe, cmdVersion, cmdDoc) 523 err := rootCmd.Execute() 524 if err != nil { 525 os.Exit(2) 526 } 527 } 528 529 // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 530 // connections. It's used by ListenAndServe and ListenAndServeTLS so 531 // dead TCP connections (e.g. closing laptop mid-download) eventually 532 // go away. 533 type tcpKeepAliveListener struct { 534 *net.TCPListener 535 } 536 537 func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 538 tc, err := ln.AcceptTCP() 539 if err != nil { 540 return 541 } 542 tc.SetKeepAlive(true) 543 tc.SetKeepAlivePeriod(3 * time.Minute) 544 return tc, nil 545 } 546 547 type serveCommandFileSystem struct { 548 serveRoot string 549 options *gbuild.Options 550 dirs []string 551 sourceMaps map[string][]byte 552 } 553 554 func (fs serveCommandFileSystem) Open(requestName string) (http.File, error) { 555 name := path.Join(fs.serveRoot, requestName[1:]) // requestName[0] == '/' 556 dir, file := path.Split(name) 557 base := path.Base(dir) // base is parent folder name, which becomes the output file name. 558 559 isPkg := file == base+".js" 560 isMap := file == base+".js.map" 561 isIndex := file == "index.html" 562 563 if isPkg || isMap || isIndex { 564 // If we're going to be serving our special files, make sure there's a Go command in this folder. 565 s := gbuild.NewSession(fs.options) 566 pkg, err := gbuild.Import(path.Dir(name), 0, s.InstallSuffix(), fs.options.BuildTags) 567 if err != nil || pkg.Name != "main" { 568 isPkg = false 569 isMap = false 570 isIndex = false 571 } 572 switch { 573 case isPkg: 574 buf := new(bytes.Buffer) 575 browserErrors := new(bytes.Buffer) 576 err := func() error { 577 archive, err := s.BuildPackage(pkg) 578 if err != nil { 579 return err 580 } 581 582 sourceMapFilter := &compiler.SourceMapFilter{Writer: buf} 583 m := &sourcemap.Map{File: base + ".js"} 584 sourceMapFilter.MappingCallback = gbuild.NewMappingCallback(m, fs.options.GOROOT, fs.options.GOPATH, fs.options.MapToLocalDisk) 585 586 deps, err := compiler.ImportDependencies(archive, func(path string) (*compiler.Archive, error) { 587 _, archive, err := s.BuildImportPathWithPackage(path, pkg) 588 return archive, err 589 }) 590 if err != nil { 591 return err 592 } 593 if err := compiler.WriteProgramCode(deps, sourceMapFilter); err != nil { 594 return err 595 } 596 597 mapBuf := new(bytes.Buffer) 598 m.WriteTo(mapBuf) 599 buf.WriteString("//# sourceMappingURL=" + base + ".js.map\n") 600 fs.sourceMaps[name+".map"] = mapBuf.Bytes() 601 602 return nil 603 }() 604 handleError(err, fs.options, browserErrors) 605 if err != nil { 606 buf = browserErrors 607 } 608 return newFakeFile(base+".js", buf.Bytes()), nil 609 610 case isMap: 611 if content, ok := fs.sourceMaps[name]; ok { 612 return newFakeFile(base+".js.map", content), nil 613 } 614 } 615 } 616 617 if fs.serveRoot != "" { 618 dir := http.Dir(fs.serveRoot) 619 if f, err := dir.Open(name); err == nil { 620 return f, nil 621 } 622 if f, err := dir.Open(requestName); err == nil { 623 return f, nil 624 } 625 } 626 627 for _, d := range fs.dirs { 628 dir := http.Dir(filepath.Join(d, "src")) 629 630 f, err := dir.Open(name) 631 if err == nil { 632 return f, nil 633 } 634 635 // source maps are served outside of serveRoot 636 f, err = dir.Open(requestName) 637 if err == nil { 638 return f, nil 639 } 640 } 641 642 if isIndex { 643 // If there was no index.html file in any dirs, supply our own. 644 return newFakeFile("index.html", []byte(`<html><head><meta charset="utf-8"><script src="`+base+`.js"></script></head><body></body></html>`)), nil 645 } 646 647 return nil, os.ErrNotExist 648 } 649 650 type fakeFile struct { 651 name string 652 size int 653 io.ReadSeeker 654 } 655 656 func newFakeFile(name string, content []byte) *fakeFile { 657 return &fakeFile{name: name, size: len(content), ReadSeeker: bytes.NewReader(content)} 658 } 659 660 func (f *fakeFile) Close() error { 661 return nil 662 } 663 664 func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) { 665 return nil, os.ErrInvalid 666 } 667 668 func (f *fakeFile) Stat() (os.FileInfo, error) { 669 return f, nil 670 } 671 672 func (f *fakeFile) Name() string { 673 return f.name 674 } 675 676 func (f *fakeFile) Size() int64 { 677 return int64(f.size) 678 } 679 680 func (f *fakeFile) Mode() os.FileMode { 681 return 0 682 } 683 684 func (f *fakeFile) ModTime() time.Time { 685 return time.Time{} 686 } 687 688 func (f *fakeFile) IsDir() bool { 689 return false 690 } 691 692 func (f *fakeFile) Sys() interface{} { 693 return nil 694 } 695 696 // handleError handles err and returns an appropriate exit code. 697 // If browserErrors is non-nil, errors are written for presentation in browser. 698 func handleError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) int { 699 switch err := err.(type) { 700 case nil: 701 return 0 702 case compiler.ErrorList: 703 for _, entry := range err { 704 printError(entry, options, browserErrors) 705 } 706 return 1 707 case *exec.ExitError: 708 return err.Sys().(syscall.WaitStatus).ExitStatus() 709 default: 710 printError(err, options, browserErrors) 711 return 1 712 } 713 } 714 715 // printError prints err to Stderr with options. If browserErrors is non-nil, errors are also written for presentation in browser. 716 func printError(err error, options *gbuild.Options, browserErrors *bytes.Buffer) { 717 e := sprintError(err) 718 options.PrintError("%s\n", e) 719 if browserErrors != nil { 720 fmt.Fprintln(browserErrors, `console.error("`+template.JSEscapeString(e)+`");`) 721 } 722 } 723 724 // sprintError returns an annotated error string without trailing newline. 725 func sprintError(err error) string { 726 makeRel := func(name string) string { 727 if relname, err := filepath.Rel(currentDirectory, name); err == nil { 728 return relname 729 } 730 return name 731 } 732 733 switch e := err.(type) { 734 case *scanner.Error: 735 return fmt.Sprintf("%s:%d:%d: %s", makeRel(e.Pos.Filename), e.Pos.Line, e.Pos.Column, e.Msg) 736 case types.Error: 737 pos := e.Fset.Position(e.Pos) 738 return fmt.Sprintf("%s:%d:%d: %s", makeRel(pos.Filename), pos.Line, pos.Column, e.Msg) 739 default: 740 return fmt.Sprintf("%s", e) 741 } 742 } 743 744 // runNode runs script with args using Node.js in directory dir. 745 // If dir is empty string, current directory is used. 746 func runNode(script string, args []string, dir string, quiet bool) error { 747 var allArgs []string 748 if b, _ := strconv.ParseBool(os.Getenv("SOURCE_MAP_SUPPORT")); os.Getenv("SOURCE_MAP_SUPPORT") == "" || b { 749 allArgs = []string{"--require", "source-map-support/register"} 750 if err := exec.Command("node", "--require", "source-map-support/register", "--eval", "").Run(); err != nil { 751 if !quiet { 752 fmt.Fprintln(os.Stderr, "gopherjs: Source maps disabled. Install source-map-support module for nice stack traces. See https://github.com/gopherjs/gopherjs#gopherjs-run-gopherjs-test.") 753 } 754 allArgs = []string{} 755 } 756 } 757 758 if runtime.GOOS != "windows" { 759 // We've seen issues with stack space limits causing 760 // recursion-heavy standard library tests to fail (e.g., see 761 // https://github.com/gopherjs/gopherjs/pull/669#issuecomment-319319483). 762 // 763 // There are two separate limits in non-Windows environments: 764 // 765 // - OS process limit 766 // - Node.js (V8) limit 767 // 768 // GopherJS fetches the current OS process limit, and sets the 769 // Node.js limit to the same value. So both limits are kept in sync 770 // and can be controlled by setting OS process limit. E.g.: 771 // 772 // ulimit -s 10000 && gopherjs test 773 // 774 cur, err := sysutil.RlimitStack() 775 if err != nil { 776 return fmt.Errorf("failed to get stack size limit: %v", err) 777 } 778 allArgs = append(allArgs, fmt.Sprintf("--stack_size=%v", cur/1000)) // Convert from bytes to KB. 779 } 780 781 allArgs = append(allArgs, script) 782 allArgs = append(allArgs, args...) 783 784 node := exec.Command("node", allArgs...) 785 node.Dir = dir 786 node.Stdin = os.Stdin 787 node.Stdout = os.Stdout 788 node.Stderr = os.Stderr 789 err := node.Run() 790 if _, ok := err.(*exec.ExitError); err != nil && !ok { 791 err = fmt.Errorf("could not run Node.js: %s", err.Error()) 792 } 793 return err 794 } 795 796 // runTestDir returns the directory for Node.js to use when running tests for package p. 797 // Empty string means current directory. 798 func runTestDir(p *gbuild.PackageData) string { 799 if p.IsVirtual { 800 // The package is virtual and doesn't have a physical directory. Use current directory. 801 return "" 802 } 803 // Run tests in the package directory. 804 return p.Dir 805 } 806 807 type testFuncs struct { 808 BuildContext *build.Context 809 Tests []testFunc 810 Benchmarks []testFunc 811 Examples []testFunc 812 TestMain *testFunc 813 Package *build.Package 814 ImportTest bool 815 NeedTest bool 816 ImportXtest bool 817 NeedXtest bool 818 } 819 820 type testFunc struct { 821 Package string // imported package name (_test or _xtest) 822 Name string // function name 823 Output string // output, for examples 824 Unordered bool // output is allowed to be unordered. 825 } 826 827 var testFileSet = token.NewFileSet() 828 829 func (t *testFuncs) load(dir, file, pkg string, doImport, seen *bool) error { 830 f, err := buildutil.ParseFile(testFileSet, t.BuildContext, nil, dir, file, parser.ParseComments) 831 if err != nil { 832 return err 833 } 834 for _, d := range f.Decls { 835 n, ok := d.(*ast.FuncDecl) 836 if !ok { 837 continue 838 } 839 if n.Recv != nil { 840 continue 841 } 842 name := n.Name.String() 843 switch { 844 case isTestMain(n): 845 if t.TestMain != nil { 846 return errors.New("multiple definitions of TestMain") 847 } 848 t.TestMain = &testFunc{pkg, name, "", false} 849 *doImport, *seen = true, true 850 case isTest(name, "Test"): 851 t.Tests = append(t.Tests, testFunc{pkg, name, "", false}) 852 *doImport, *seen = true, true 853 case isTest(name, "Benchmark"): 854 t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false}) 855 *doImport, *seen = true, true 856 } 857 } 858 if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { 859 ex := doc.Examples(f) 860 sort.Sort(byOrder(ex)) 861 for _, e := range ex { 862 *doImport = true // import test file whether executed or not 863 if e.Output == "" && !e.EmptyOutput { 864 // Don't run examples with no output. 865 continue 866 } 867 t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered}) 868 *seen = true 869 } 870 } 871 872 return nil 873 } 874 875 type byOrder []*doc.Example 876 877 func (x byOrder) Len() int { return len(x) } 878 func (x byOrder) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 879 func (x byOrder) Less(i, j int) bool { return x[i].Order < x[j].Order } 880 881 // isTestMain tells whether fn is a TestMain(m *testing.M) function. 882 func isTestMain(fn *ast.FuncDecl) bool { 883 if fn.Name.String() != "TestMain" || 884 fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || 885 fn.Type.Params == nil || 886 len(fn.Type.Params.List) != 1 || 887 len(fn.Type.Params.List[0].Names) > 1 { 888 return false 889 } 890 ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr) 891 if !ok { 892 return false 893 } 894 // We can't easily check that the type is *testing.M 895 // because we don't know how testing has been imported, 896 // but at least check that it's *M or *something.M. 897 if name, ok := ptr.X.(*ast.Ident); ok && name.Name == "M" { 898 return true 899 } 900 if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == "M" { 901 return true 902 } 903 return false 904 } 905 906 // isTest tells whether name looks like a test (or benchmark, according to prefix). 907 // It is a Test (say) if there is a character after Test that is not a lower-case letter. 908 // We don't want TesticularCancer. 909 func isTest(name, prefix string) bool { 910 if !strings.HasPrefix(name, prefix) { 911 return false 912 } 913 if len(name) == len(prefix) { // "Test" is ok 914 return true 915 } 916 rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) 917 return !unicode.IsLower(rune) 918 } 919 920 var testmainTmpl = template.Must(template.New("main").Parse(` 921 package main 922 923 import ( 924 {{if not .TestMain}} 925 "os" 926 {{end}} 927 "testing" 928 "testing/internal/testdeps" 929 930 {{if .ImportTest}} 931 {{if .NeedTest}}_test{{else}}_{{end}} {{.Package.ImportPath | printf "%q"}} 932 {{end}} 933 {{if .ImportXtest}} 934 {{if .NeedXtest}}_xtest{{else}}_{{end}} {{.Package.ImportPath | printf "%s_test" | printf "%q"}} 935 {{end}} 936 ) 937 938 var tests = []testing.InternalTest{ 939 {{range .Tests}} 940 {"{{.Name}}", {{.Package}}.{{.Name}}}, 941 {{end}} 942 } 943 944 var benchmarks = []testing.InternalBenchmark{ 945 {{range .Benchmarks}} 946 {"{{.Name}}", {{.Package}}.{{.Name}}}, 947 {{end}} 948 } 949 950 var examples = []testing.InternalExample{ 951 {{range .Examples}} 952 {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}}, 953 {{end}} 954 } 955 956 func main() { 957 m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples) 958 {{with .TestMain}} 959 {{.Package}}.{{.Name}}(m) 960 {{else}} 961 os.Exit(m.Run()) 962 {{end}} 963 } 964 965 `))