github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/main_test.go (about)

     1  package main
     2  
     3  // This file tests the compiler by running Go files in testdata/*.go and
     4  // comparing their output with the expected output in testdata/*.txt.
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"os/exec"
    15  	"reflect"
    16  	"regexp"
    17  	"runtime"
    18  	"slices"
    19  	"strings"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/aykevl/go-wasm"
    25  	"github.com/tinygo-org/tinygo/builder"
    26  	"github.com/tinygo-org/tinygo/compileopts"
    27  	"github.com/tinygo-org/tinygo/goenv"
    28  )
    29  
    30  const TESTDATA = "testdata"
    31  
    32  var testTarget = flag.String("target", "", "override test target")
    33  
    34  var supportedLinuxArches = map[string]string{
    35  	"AMD64Linux": "linux/amd64",
    36  	"X86Linux":   "linux/386",
    37  	"ARMLinux":   "linux/arm/6",
    38  	"ARM64Linux": "linux/arm64",
    39  	"WASIp1":     "wasip1/wasm",
    40  }
    41  
    42  func init() {
    43  	major, _, _ := goenv.GetGorootVersion()
    44  	if major < 21 {
    45  		// Go 1.20 backwards compatibility.
    46  		// Should be removed once we drop support for Go 1.20.
    47  		delete(supportedLinuxArches, "WASIp1")
    48  	}
    49  }
    50  
    51  var sema = make(chan struct{}, runtime.NumCPU())
    52  
    53  func TestBuild(t *testing.T) {
    54  	t.Parallel()
    55  
    56  	tests := []string{
    57  		"alias.go",
    58  		"atomic.go",
    59  		"binop.go",
    60  		"calls.go",
    61  		"cgo/",
    62  		"channel.go",
    63  		"embed/",
    64  		"float.go",
    65  		"gc.go",
    66  		"generics.go",
    67  		"goroutines.go",
    68  		"init.go",
    69  		"init_multi.go",
    70  		"interface.go",
    71  		"json.go",
    72  		"map.go",
    73  		"math.go",
    74  		"oldgo/",
    75  		"print.go",
    76  		"reflect.go",
    77  		"slice.go",
    78  		"sort.go",
    79  		"stdlib.go",
    80  		"string.go",
    81  		"structs.go",
    82  		"testing.go",
    83  		"timers.go",
    84  		"zeroalloc.go",
    85  	}
    86  
    87  	// Go 1.21 made some changes to the language, which we can only test when
    88  	// we're actually on Go 1.21.
    89  	_, minor, err := goenv.GetGorootVersion()
    90  	if err != nil {
    91  		t.Fatal("could not get version:", minor)
    92  	}
    93  	if minor >= 21 {
    94  		tests = append(tests, "go1.21.go")
    95  	}
    96  	if minor >= 22 {
    97  		tests = append(tests, "go1.22/")
    98  	}
    99  
   100  	if *testTarget != "" {
   101  		// This makes it possible to run one specific test (instead of all),
   102  		// which is especially useful to quickly check whether some changes
   103  		// affect a particular target architecture.
   104  		runPlatTests(optionsFromTarget(*testTarget, sema), tests, t)
   105  		return
   106  	}
   107  
   108  	t.Run("Host", func(t *testing.T) {
   109  		t.Parallel()
   110  		runPlatTests(optionsFromTarget("", sema), tests, t)
   111  	})
   112  
   113  	// Test a few build options.
   114  	t.Run("build-options", func(t *testing.T) {
   115  		t.Parallel()
   116  
   117  		// Test with few optimizations enabled (no inlining, etc).
   118  		t.Run("opt=1", func(t *testing.T) {
   119  			t.Parallel()
   120  			opts := optionsFromTarget("", sema)
   121  			opts.Opt = "1"
   122  			runTestWithConfig("stdlib.go", t, opts, nil, nil)
   123  		})
   124  
   125  		// Test with only the bare minimum of optimizations enabled.
   126  		// TODO: fix this for stdlib.go, which currently fails.
   127  		t.Run("opt=0", func(t *testing.T) {
   128  			t.Parallel()
   129  			opts := optionsFromTarget("", sema)
   130  			opts.Opt = "0"
   131  			runTestWithConfig("print.go", t, opts, nil, nil)
   132  		})
   133  
   134  		t.Run("ldflags", func(t *testing.T) {
   135  			t.Parallel()
   136  			opts := optionsFromTarget("", sema)
   137  			opts.GlobalValues = map[string]map[string]string{
   138  				"main": {
   139  					"someGlobal": "foobar",
   140  				},
   141  			}
   142  			runTestWithConfig("ldflags.go", t, opts, nil, nil)
   143  		})
   144  	})
   145  
   146  	if testing.Short() {
   147  		// Don't test other targets when the -short flag is used. Only test the
   148  		// host system.
   149  		return
   150  	}
   151  
   152  	t.Run("EmulatedCortexM3", func(t *testing.T) {
   153  		t.Parallel()
   154  		runPlatTests(optionsFromTarget("cortex-m-qemu", sema), tests, t)
   155  	})
   156  
   157  	t.Run("EmulatedRISCV", func(t *testing.T) {
   158  		t.Parallel()
   159  		runPlatTests(optionsFromTarget("riscv-qemu", sema), tests, t)
   160  	})
   161  
   162  	t.Run("AVR", func(t *testing.T) {
   163  		t.Parallel()
   164  		runPlatTests(optionsFromTarget("simavr", sema), tests, t)
   165  	})
   166  
   167  	if runtime.GOOS == "linux" {
   168  		for name, osArch := range supportedLinuxArches {
   169  			options := optionsFromOSARCH(osArch, sema)
   170  			if options.GOARCH != runtime.GOARCH { // Native architecture already run above.
   171  				t.Run(name, func(t *testing.T) {
   172  					runPlatTests(options, tests, t)
   173  				})
   174  			}
   175  		}
   176  		t.Run("WebAssembly", func(t *testing.T) {
   177  			t.Parallel()
   178  			runPlatTests(optionsFromTarget("wasm", sema), tests, t)
   179  		})
   180  		t.Run("WASI", func(t *testing.T) {
   181  			t.Parallel()
   182  			runPlatTests(optionsFromTarget("wasip1", sema), tests, t)
   183  		})
   184  	}
   185  }
   186  
   187  func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
   188  	emuCheck(t, options)
   189  
   190  	spec, err := compileopts.LoadTarget(&options)
   191  	if err != nil {
   192  		t.Fatal("failed to load target spec:", err)
   193  	}
   194  
   195  	// FIXME: this should really be:
   196  	// isWebAssembly := strings.HasPrefix(spec.Triple, "wasm")
   197  	isWASI := strings.HasPrefix(options.Target, "wasi")
   198  	isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
   199  
   200  	for _, name := range tests {
   201  		if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
   202  			switch name {
   203  			case "timers.go":
   204  				// Timer tests do not work because syscall.seek is implemented
   205  				// as Assembly in mainline Go and causes linker failure
   206  				continue
   207  			}
   208  		}
   209  		if options.Target == "simavr" {
   210  			// Not all tests are currently supported on AVR.
   211  			// Skip the ones that aren't.
   212  			switch name {
   213  			case "reflect.go":
   214  				// Reflect tests do not run correctly, probably because of the
   215  				// limited amount of memory.
   216  				continue
   217  
   218  			case "gc.go":
   219  				// Does not pass due to high mark false positive rate.
   220  				continue
   221  
   222  			case "json.go", "stdlib.go", "testing.go":
   223  				// Too big for AVR. Doesn't fit in flash/RAM.
   224  				continue
   225  
   226  			case "math.go":
   227  				// Needs newer picolibc version (for sqrt).
   228  				continue
   229  
   230  			case "cgo/":
   231  				// CGo function pointers don't work on AVR (needs LLVM 16 and
   232  				// some compiler changes).
   233  				continue
   234  
   235  			default:
   236  			}
   237  		}
   238  		name := name // redefine to avoid race condition
   239  		t.Run(name, func(t *testing.T) {
   240  			t.Parallel()
   241  			runTest(name, options, t, nil, nil)
   242  		})
   243  	}
   244  	if !strings.HasPrefix(spec.Emulator, "simavr ") {
   245  		t.Run("env.go", func(t *testing.T) {
   246  			t.Parallel()
   247  			runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
   248  		})
   249  	}
   250  	if isWebAssembly {
   251  		t.Run("alias.go-scheduler-none", func(t *testing.T) {
   252  			t.Parallel()
   253  			options := compileopts.Options(options)
   254  			options.Scheduler = "none"
   255  			runTest("alias.go", options, t, nil, nil)
   256  		})
   257  	}
   258  	if options.Target == "" || isWASI {
   259  		t.Run("filesystem.go", func(t *testing.T) {
   260  			t.Parallel()
   261  			runTest("filesystem.go", options, t, nil, nil)
   262  		})
   263  	}
   264  	if options.Target == "" || options.Target == "wasm" || isWASI {
   265  		t.Run("rand.go", func(t *testing.T) {
   266  			t.Parallel()
   267  			runTest("rand.go", options, t, nil, nil)
   268  		})
   269  	}
   270  	if !isWebAssembly {
   271  		// The recover() builtin isn't supported yet on WebAssembly and Windows.
   272  		t.Run("recover.go", func(t *testing.T) {
   273  			t.Parallel()
   274  			runTest("recover.go", options, t, nil, nil)
   275  		})
   276  	}
   277  }
   278  
   279  func emuCheck(t *testing.T, options compileopts.Options) {
   280  	// Check if the emulator is installed.
   281  	spec, err := compileopts.LoadTarget(&options)
   282  	if err != nil {
   283  		t.Fatal("failed to load target spec:", err)
   284  	}
   285  	if spec.Emulator != "" {
   286  		emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
   287  		_, err := exec.LookPath(emulatorCommand)
   288  		if err != nil {
   289  			if errors.Is(err, exec.ErrNotFound) {
   290  				t.Skipf("emulator not installed: %q", emulatorCommand)
   291  			}
   292  
   293  			t.Errorf("searching for emulator: %v", err)
   294  			return
   295  		}
   296  	}
   297  }
   298  
   299  func optionsFromTarget(target string, sema chan struct{}) compileopts.Options {
   300  	return compileopts.Options{
   301  		// GOOS/GOARCH are only used if target == ""
   302  		GOOS:          goenv.Get("GOOS"),
   303  		GOARCH:        goenv.Get("GOARCH"),
   304  		GOARM:         goenv.Get("GOARM"),
   305  		Target:        target,
   306  		Semaphore:     sema,
   307  		InterpTimeout: 180 * time.Second,
   308  		Debug:         true,
   309  		VerifyIR:      true,
   310  		Opt:           "z",
   311  	}
   312  }
   313  
   314  // optionsFromOSARCH returns a set of options based on the "osarch" string. This
   315  // string is in the form of "os/arch/subarch", with the subarch only sometimes
   316  // being necessary. Examples are "darwin/amd64" or "linux/arm/7".
   317  func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options {
   318  	parts := strings.Split(osarch, "/")
   319  	options := compileopts.Options{
   320  		GOOS:          parts[0],
   321  		GOARCH:        parts[1],
   322  		Semaphore:     sema,
   323  		InterpTimeout: 180 * time.Second,
   324  		Debug:         true,
   325  		VerifyIR:      true,
   326  		Opt:           "z",
   327  	}
   328  	if options.GOARCH == "arm" {
   329  		options.GOARM = parts[2]
   330  	}
   331  	return options
   332  }
   333  
   334  func runTest(name string, options compileopts.Options, t *testing.T, cmdArgs, environmentVars []string) {
   335  	runTestWithConfig(name, t, options, cmdArgs, environmentVars)
   336  }
   337  
   338  func runTestWithConfig(name string, t *testing.T, options compileopts.Options, cmdArgs, environmentVars []string) {
   339  	// Get the expected output for this test.
   340  	// Note: not using filepath.Join as it strips the path separator at the end
   341  	// of the path.
   342  	path := TESTDATA + "/" + name
   343  	// Get the expected output for this test.
   344  	txtpath := path[:len(path)-3] + ".txt"
   345  	pkgName := "./" + path
   346  	if path[len(path)-1] == '/' {
   347  		txtpath = path + "out.txt"
   348  		options.Directory = path
   349  		pkgName = "."
   350  	}
   351  	expected, err := os.ReadFile(txtpath)
   352  	if err != nil {
   353  		t.Fatal("could not read expected output file:", err)
   354  	}
   355  
   356  	config, err := builder.NewConfig(&options)
   357  	if err != nil {
   358  		t.Fatal(err)
   359  	}
   360  
   361  	// Build the test binary.
   362  	stdout := &bytes.Buffer{}
   363  	_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
   364  		return cmd.Run()
   365  	})
   366  	if err != nil {
   367  		printCompilerError(t.Log, err)
   368  		t.Fail()
   369  		return
   370  	}
   371  
   372  	// putchar() prints CRLF, convert it to LF.
   373  	actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
   374  	expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows
   375  
   376  	if config.EmulatorName() == "simavr" {
   377  		// Strip simavr log formatting.
   378  		actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
   379  		actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1)
   380  		actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1)
   381  		actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1)
   382  	}
   383  	if name == "testing.go" {
   384  		// Strip actual time.
   385  		re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`)
   386  		actual = re.ReplaceAllLiteral(actual, []byte{'(', '0', '.', '0', '0', 's', ')'})
   387  	}
   388  
   389  	// Check whether the command ran successfully.
   390  	fail := false
   391  	if err != nil {
   392  		t.Log("failed to run:", err)
   393  		fail = true
   394  	} else if !bytes.Equal(expected, actual) {
   395  		t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual))
   396  		fail = true
   397  	}
   398  
   399  	if fail {
   400  		r := bufio.NewReader(bytes.NewReader(actual))
   401  		for {
   402  			line, err := r.ReadString('\n')
   403  			if err != nil {
   404  				break
   405  			}
   406  			t.Log("stdout:", line[:len(line)-1])
   407  		}
   408  		t.Fail()
   409  	}
   410  }
   411  
   412  // Test WebAssembly files for certain properties.
   413  func TestWebAssembly(t *testing.T) {
   414  	t.Parallel()
   415  	type testCase struct {
   416  		name          string
   417  		panicStrategy string
   418  		imports       []string
   419  	}
   420  	for _, tc := range []testCase{
   421  		// Test whether there really are no imports when using -panic=trap. This
   422  		// tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161.
   423  		{name: "panic-default", imports: []string{"wasi_snapshot_preview1.fd_write"}},
   424  		{name: "panic-trap", panicStrategy: "trap", imports: []string{}},
   425  	} {
   426  		tc := tc
   427  		t.Run(tc.name, func(t *testing.T) {
   428  			t.Parallel()
   429  			tmpdir := t.TempDir()
   430  			options := optionsFromTarget("wasi", sema)
   431  			options.PanicStrategy = tc.panicStrategy
   432  			config, err := builder.NewConfig(&options)
   433  			if err != nil {
   434  				t.Fatal(err)
   435  			}
   436  
   437  			result, err := builder.Build("testdata/trivialpanic.go", ".wasm", tmpdir, config)
   438  			if err != nil {
   439  				t.Fatal("failed to build binary:", err)
   440  			}
   441  			f, err := os.Open(result.Binary)
   442  			if err != nil {
   443  				t.Fatal("could not open output binary:", err)
   444  			}
   445  			defer f.Close()
   446  			module, err := wasm.Parse(f)
   447  			if err != nil {
   448  				t.Fatal("could not parse output binary:", err)
   449  			}
   450  
   451  			// Test the list of imports.
   452  			if tc.imports != nil {
   453  				var imports []string
   454  				for _, section := range module.Sections {
   455  					switch section := section.(type) {
   456  					case *wasm.SectionImport:
   457  						for _, symbol := range section.Entries {
   458  							imports = append(imports, symbol.Module+"."+symbol.Field)
   459  						}
   460  					}
   461  				}
   462  				if !slices.Equal(imports, tc.imports) {
   463  					t.Errorf("import list not as expected!\nexpected: %v\nactual:   %v", tc.imports, imports)
   464  				}
   465  			}
   466  		})
   467  	}
   468  }
   469  
   470  func TestTest(t *testing.T) {
   471  	t.Parallel()
   472  
   473  	type targ struct {
   474  		name string
   475  		opts compileopts.Options
   476  	}
   477  	targs := []targ{
   478  		// Host
   479  		{"Host", optionsFromTarget("", sema)},
   480  	}
   481  	if !testing.Short() {
   482  		if runtime.GOOS == "linux" {
   483  			for name, osArch := range supportedLinuxArches {
   484  				options := optionsFromOSARCH(osArch, sema)
   485  				if options.GOARCH != runtime.GOARCH { // Native architecture already run above.
   486  					targs = append(targs, targ{name, options})
   487  				}
   488  			}
   489  		}
   490  
   491  		targs = append(targs,
   492  			// QEMU microcontrollers
   493  			targ{"EmulatedCortexM3", optionsFromTarget("cortex-m-qemu", sema)},
   494  			targ{"EmulatedRISCV", optionsFromTarget("riscv-qemu", sema)},
   495  
   496  			// Node/Wasmtime
   497  			targ{"WASM", optionsFromTarget("wasm", sema)},
   498  			targ{"WASI", optionsFromTarget("wasip1", sema)},
   499  		)
   500  	}
   501  	for _, targ := range targs {
   502  		targ := targ
   503  		t.Run(targ.name, func(t *testing.T) {
   504  			t.Parallel()
   505  
   506  			emuCheck(t, targ.opts)
   507  
   508  			t.Run("Pass", func(t *testing.T) {
   509  				t.Parallel()
   510  
   511  				// Test a package which builds and passes normally.
   512  
   513  				var wg sync.WaitGroup
   514  				defer wg.Wait()
   515  
   516  				out := ioLogger(t, &wg)
   517  				defer out.Close()
   518  
   519  				opts := targ.opts
   520  				passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/pass", out, out, &opts, "")
   521  				if err != nil {
   522  					t.Errorf("test error: %v", err)
   523  				}
   524  				if !passed {
   525  					t.Error("test failed")
   526  				}
   527  			})
   528  
   529  			t.Run("Fail", func(t *testing.T) {
   530  				t.Parallel()
   531  
   532  				// Test a package which builds fine but fails.
   533  
   534  				var wg sync.WaitGroup
   535  				defer wg.Wait()
   536  
   537  				out := ioLogger(t, &wg)
   538  				defer out.Close()
   539  
   540  				opts := targ.opts
   541  				passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/fail", out, out, &opts, "")
   542  				if err != nil {
   543  					t.Errorf("test error: %v", err)
   544  				}
   545  				if passed {
   546  					t.Error("test passed")
   547  				}
   548  			})
   549  
   550  			if targ.name != "Host" {
   551  				// Emulated tests are somewhat slow, and these do not need to be run across every platform.
   552  				return
   553  			}
   554  
   555  			t.Run("Nothing", func(t *testing.T) {
   556  				t.Parallel()
   557  
   558  				// Test a package with no test files.
   559  
   560  				var wg sync.WaitGroup
   561  				defer wg.Wait()
   562  
   563  				out := ioLogger(t, &wg)
   564  				defer out.Close()
   565  
   566  				var output bytes.Buffer
   567  				opts := targ.opts
   568  				passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/nothing", io.MultiWriter(&output, out), out, &opts, "")
   569  				if err != nil {
   570  					t.Errorf("test error: %v", err)
   571  				}
   572  				if !passed {
   573  					t.Error("test failed")
   574  				}
   575  				if !strings.Contains(output.String(), "[no test files]") {
   576  					t.Error("missing [no test files] in output")
   577  				}
   578  			})
   579  
   580  			t.Run("BuildErr", func(t *testing.T) {
   581  				t.Parallel()
   582  
   583  				// Test a package which fails to build.
   584  
   585  				var wg sync.WaitGroup
   586  				defer wg.Wait()
   587  
   588  				out := ioLogger(t, &wg)
   589  				defer out.Close()
   590  
   591  				opts := targ.opts
   592  				passed, err := Test("github.com/tinygo-org/tinygo/tests/testing/builderr", out, out, &opts, "")
   593  				if err == nil {
   594  					t.Error("test did not error")
   595  				}
   596  				if passed {
   597  					t.Error("test passed")
   598  				}
   599  			})
   600  		})
   601  	}
   602  }
   603  
   604  func ioLogger(t *testing.T, wg *sync.WaitGroup) io.WriteCloser {
   605  	r, w := io.Pipe()
   606  	wg.Add(1)
   607  	go func() {
   608  		defer wg.Done()
   609  		defer r.Close()
   610  
   611  		scanner := bufio.NewScanner(r)
   612  		for scanner.Scan() {
   613  			t.Log(scanner.Text())
   614  		}
   615  	}()
   616  
   617  	return w
   618  }
   619  
   620  func TestGetListOfPackages(t *testing.T) {
   621  	opts := optionsFromTarget("", sema)
   622  	tests := []struct {
   623  		pkgs          []string
   624  		expectedPkgs  []string
   625  		expectesError bool
   626  	}{
   627  		{
   628  			pkgs: []string{"./tests/testing/recurse/..."},
   629  			expectedPkgs: []string{
   630  				"github.com/tinygo-org/tinygo/tests/testing/recurse",
   631  				"github.com/tinygo-org/tinygo/tests/testing/recurse/subdir",
   632  			},
   633  		},
   634  		{
   635  			pkgs: []string{"./tests/testing/pass"},
   636  			expectedPkgs: []string{
   637  				"github.com/tinygo-org/tinygo/tests/testing/pass",
   638  			},
   639  		},
   640  		{
   641  			pkgs:          []string{"./tests/testing"},
   642  			expectesError: true,
   643  		},
   644  	}
   645  
   646  	for _, test := range tests {
   647  		actualPkgs, err := getListOfPackages(test.pkgs, &opts)
   648  		if err != nil && !test.expectesError {
   649  			t.Errorf("unexpected error: %v", err)
   650  		} else if err == nil && test.expectesError {
   651  			t.Error("expected error, but got none")
   652  		}
   653  
   654  		if !reflect.DeepEqual(test.expectedPkgs, actualPkgs) {
   655  			t.Errorf("expected two slices to be equal, expected %v got %v", test.expectedPkgs, actualPkgs)
   656  		}
   657  	}
   658  }
   659  
   660  // This TestMain is necessary because TinyGo may also be invoked to run certain
   661  // LLVM tools in a separate process. Not capturing these invocations would lead
   662  // to recursive tests.
   663  func TestMain(m *testing.M) {
   664  	if len(os.Args) >= 2 {
   665  		switch os.Args[1] {
   666  		case "clang", "ld.lld", "wasm-ld":
   667  			// Invoke a specific tool.
   668  			err := builder.RunTool(os.Args[1], os.Args[2:]...)
   669  			if err != nil {
   670  				fmt.Fprintln(os.Stderr, err)
   671  				os.Exit(1)
   672  			}
   673  			os.Exit(0)
   674  		}
   675  	}
   676  
   677  	// Run normal tests.
   678  	os.Exit(m.Run())
   679  }