github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/cmd/dist/test.go (about)

     1  // Copyright 2015 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  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"os/exec"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  )
    21  
    22  func cmdtest() {
    23  	var t tester
    24  	flag.BoolVar(&t.listMode, "list", false, "list available tests")
    25  	flag.BoolVar(&t.noRebuild, "no-rebuild", false, "don't rebuild std and cmd packages")
    26  	flag.StringVar(&t.banner, "banner", "##### ", "banner prefix; blank means no section banners")
    27  	flag.StringVar(&t.runRxStr, "run", "", "run only those tests matching the regular expression; empty means to run all")
    28  	xflagparse(0)
    29  	t.run()
    30  }
    31  
    32  // tester executes cmdtest.
    33  type tester struct {
    34  	listMode  bool
    35  	noRebuild bool
    36  	runRxStr  string
    37  	runRx     *regexp.Regexp
    38  	banner    string // prefix, or "" for none
    39  
    40  	goroot     string
    41  	goarch     string
    42  	gohostarch string
    43  	goos       string
    44  	gohostos   string
    45  	cgoEnabled bool
    46  	partial    bool
    47  	haveTime   bool // the 'time' binary is available
    48  
    49  	tests        []distTest
    50  	timeoutScale int
    51  }
    52  
    53  // A distTest is a test run by dist test.
    54  // Each test has a unique name and belongs to a group (heading)
    55  type distTest struct {
    56  	name    string // unique test name; may be filtered with -run flag
    57  	heading string // group section; this header is printed before the test is run.
    58  	fn      func() error
    59  }
    60  
    61  func mustEnv(k string) string {
    62  	v := os.Getenv(k)
    63  	if v == "" {
    64  		log.Fatalf("Unset environment variable %v", k)
    65  	}
    66  	return v
    67  }
    68  
    69  func (t *tester) run() {
    70  	t.goroot = mustEnv("GOROOT")
    71  	t.goos = mustEnv("GOOS")
    72  	t.gohostos = mustEnv("GOHOSTOS")
    73  	t.goarch = mustEnv("GOARCH")
    74  	t.gohostarch = mustEnv("GOHOSTARCH")
    75  	slurp, err := exec.Command("go", "env", "CGO_ENABLED").Output()
    76  	if err != nil {
    77  		log.Fatalf("Error running go env CGO_ENABLED: %v", err)
    78  	}
    79  	t.cgoEnabled, _ = strconv.ParseBool(strings.TrimSpace(string(slurp)))
    80  
    81  	if t.hasBash() {
    82  		if _, err := exec.LookPath("time"); err == nil {
    83  			t.haveTime = true
    84  		}
    85  	}
    86  
    87  	if !t.noRebuild {
    88  		t.out("Building packages and commands.")
    89  		cmd := exec.Command("go", "install", "-a", "-v", "std", "cmd")
    90  		cmd.Stdout = os.Stdout
    91  		cmd.Stderr = os.Stderr
    92  		if err := cmd.Run(); err != nil {
    93  			log.Fatalf("building packages and commands: %v", err)
    94  		}
    95  	}
    96  
    97  	t.timeoutScale = 1
    98  	if t.goarch == "arm" || t.goos == "windows" {
    99  		t.timeoutScale = 2
   100  	}
   101  
   102  	if t.runRxStr != "" {
   103  		t.runRx = regexp.MustCompile(t.runRxStr)
   104  	}
   105  
   106  	t.registerTests()
   107  	if t.listMode {
   108  		for _, tt := range t.tests {
   109  			fmt.Println(tt.name)
   110  		}
   111  		return
   112  	}
   113  
   114  	// we must unset GOROOT_FINAL before tests, because runtime/debug requires
   115  	// correct access to source code, so if we have GOROOT_FINAL in effect,
   116  	// at least runtime/debug test will fail.
   117  	os.Unsetenv("GOROOT_FINAL")
   118  
   119  	var lastHeading string
   120  	for _, dt := range t.tests {
   121  		if t.runRx != nil && !t.runRx.MatchString(dt.name) {
   122  			t.partial = true
   123  			continue
   124  		}
   125  		if dt.heading != "" && lastHeading != dt.heading {
   126  			lastHeading = dt.heading
   127  			t.out(dt.heading)
   128  		}
   129  		if vflag > 0 {
   130  			fmt.Printf("# go tool dist test -run=^%s$\n", dt.name)
   131  		}
   132  		if err := dt.fn(); err != nil {
   133  			log.Fatalf("Failed: %v", err)
   134  		}
   135  	}
   136  	if t.partial {
   137  		fmt.Println("\nALL TESTS PASSED (some were excluded)")
   138  	} else {
   139  		fmt.Println("\nALL TESTS PASSED")
   140  	}
   141  }
   142  
   143  func (t *tester) timeout(sec int) string {
   144  	return "-timeout=" + fmt.Sprint(time.Duration(sec)*time.Second*time.Duration(t.timeoutScale))
   145  }
   146  
   147  func (t *tester) registerTests() {
   148  	// Register a separate logical test for each package in the standard library
   149  	// but actually group them together at execution time to share the cost of
   150  	// building packages shared between them.
   151  	all, err := exec.Command("go", "list", "std", "cmd").Output()
   152  	if err != nil {
   153  		log.Fatalf("Error running go list std cmd: %v", err)
   154  	}
   155  	// ranGoTest and stdMatches are state closed over by the
   156  	// stdlib testing func below. The tests are run sequentially,
   157  	// so there's no need for locks.
   158  	var (
   159  		ranGoTest  bool
   160  		stdMatches []string
   161  	)
   162  	for _, pkg := range strings.Fields(string(all)) {
   163  		testName := "go_test:" + pkg
   164  		if t.runRx == nil || t.runRx.MatchString(testName) {
   165  			stdMatches = append(stdMatches, pkg)
   166  		}
   167  		t.tests = append(t.tests, distTest{
   168  			name:    testName,
   169  			heading: "Testing packages.",
   170  			fn: func() error {
   171  				if ranGoTest {
   172  					return nil
   173  				}
   174  				ranGoTest = true
   175  				cmd := exec.Command("go", append([]string{
   176  					"test",
   177  					"-short",
   178  					t.timeout(120),
   179  					"-gcflags=" + os.Getenv("GO_GCFLAGS"),
   180  				}, stdMatches...)...)
   181  				cmd.Stdout = os.Stdout
   182  				cmd.Stderr = os.Stderr
   183  				return cmd.Run()
   184  			},
   185  		})
   186  	}
   187  
   188  	// Old hack for when Plan 9 on GCE was too slow.
   189  	// We're keeping this until test sharding (Issue 10029) is finished, though.
   190  	if os.Getenv("GOTESTONLY") == "std" {
   191  		t.partial = true
   192  		return
   193  	}
   194  
   195  	// Runtime CPU tests.
   196  	for _, cpu := range []string{"1", "2", "4"} {
   197  		cpu := cpu
   198  		testName := "runtime:cpu" + cpu
   199  		t.tests = append(t.tests, distTest{
   200  			name:    testName,
   201  			heading: "GOMAXPROCS=2 runtime -cpu=1,2,4",
   202  			fn: func() error {
   203  				cmd := t.dirCmd(".", "go", "test", "-short", t.timeout(300), "runtime", "-cpu="+cpu)
   204  				// We set GOMAXPROCS=2 in addition to -cpu=1,2,4 in order to test runtime bootstrap code,
   205  				// creation of first goroutines and first garbage collections in the parallel setting.
   206  				cmd.Env = mergeEnvLists([]string{"GOMAXPROCS=2"}, os.Environ())
   207  				return cmd.Run()
   208  			},
   209  		})
   210  	}
   211  
   212  	// sync tests
   213  	t.tests = append(t.tests, distTest{
   214  		name:    "sync_cpu",
   215  		heading: "sync -cpu=10",
   216  		fn: func() error {
   217  			return t.dirCmd(".", "go", "test", "sync", "-short", t.timeout(120), "-cpu=10").Run()
   218  		},
   219  	})
   220  
   221  	iOS := t.goos == "darwin" && (t.goarch == "arm" || t.goarch == "arm64")
   222  
   223  	if t.cgoEnabled && t.goos != "android" && !iOS {
   224  		// Disabled on android and iOS. golang.org/issue/8345
   225  		t.tests = append(t.tests, distTest{
   226  			name:    "cgo_stdio",
   227  			heading: "../misc/cgo/stdio",
   228  			fn: func() error {
   229  				return t.dirCmd("misc/cgo/stdio",
   230  					"go", "run", filepath.Join(os.Getenv("GOROOT"), "test/run.go"), "-", ".").Run()
   231  			},
   232  		})
   233  		t.tests = append(t.tests, distTest{
   234  			name:    "cgo_life",
   235  			heading: "../misc/cgo/life",
   236  			fn: func() error {
   237  				return t.dirCmd("misc/cgo/life",
   238  					"go", "run", filepath.Join(os.Getenv("GOROOT"), "test/run.go"), "-", ".").Run()
   239  			},
   240  		})
   241  	}
   242  	if t.cgoEnabled && t.goos != "android" && !iOS {
   243  		// TODO(crawshaw): reenable on android and iOS
   244  		// golang.org/issue/8345
   245  		//
   246  		// These tests are not designed to run off the host.
   247  		t.tests = append(t.tests, distTest{
   248  			name:    "cgo_test",
   249  			heading: "../misc/cgo/test",
   250  			fn:      t.cgoTest,
   251  		})
   252  	}
   253  
   254  	if t.raceDetectorSupported() {
   255  		t.tests = append(t.tests, distTest{
   256  			name:    "race",
   257  			heading: "Testing race detector",
   258  			fn:      t.raceTest,
   259  		})
   260  	}
   261  
   262  	if t.hasBash() && t.cgoEnabled && t.goos != "android" && t.goos != "darwin" {
   263  		t.registerTest("testgodefs", "../misc/cgo/testgodefs", "./test.bash")
   264  	}
   265  	if t.cgoEnabled {
   266  		if t.gohostos == "windows" {
   267  			t.tests = append(t.tests, distTest{
   268  				name:    "testso",
   269  				heading: "../misc/cgo/testso",
   270  				fn:      t.cgoTestSOWindows,
   271  			})
   272  		} else if t.hasBash() && t.goos != "android" && !iOS {
   273  			t.registerTest("testso", "../misc/cgo/testso", "./test.bash")
   274  		}
   275  		if t.extLink() && t.goos == "darwin" && t.goarch == "amd64" {
   276  			// TODO(crawshaw): add darwin/arm{,64}
   277  			t.registerTest("testcarchive", "../misc/cgo/testcarchive", "./test.bash")
   278  		}
   279  		if t.gohostos == "linux" && t.goarch == "amd64" {
   280  			t.registerTest("testasan", "../misc/cgo/testasan", "go", "run", "main.go")
   281  		}
   282  		if t.hasBash() && t.goos != "android" && !iOS && t.gohostos != "windows" {
   283  			t.registerTest("cgo_errors", "../misc/cgo/errors", "./test.bash")
   284  		}
   285  	}
   286  	if t.hasBash() && t.goos != "nacl" && t.goos != "android" && !iOS {
   287  		t.registerTest("doc_progs", "../doc/progs", "time", "go", "run", "run.go")
   288  		t.registerTest("wiki", "../doc/articles/wiki", "./test.bash")
   289  		t.registerTest("codewalk", "../doc/codewalk", "time", "./run")
   290  		t.registerTest("shootout", "../test/bench/shootout", "time", "./timing.sh", "-test")
   291  	}
   292  	if t.goos != "android" && !iOS {
   293  		t.registerTest("bench_go1", "../test/bench/go1", "go", "test")
   294  	}
   295  	if t.goos != "android" && !iOS {
   296  		// TODO(bradfitz): shard down into these tests, as
   297  		// this is one of the slowest (and most shardable)
   298  		// tests.
   299  		t.tests = append(t.tests, distTest{
   300  			name:    "test",
   301  			heading: "../test",
   302  			fn:      t.testDirTest,
   303  		})
   304  	}
   305  	if t.goos != "nacl" && t.goos != "android" && !iOS {
   306  		t.tests = append(t.tests, distTest{
   307  			name:    "api",
   308  			heading: "API check",
   309  			fn: func() error {
   310  				return t.dirCmd(".", "go", "run", filepath.Join(t.goroot, "src/cmd/api/run.go")).Run()
   311  			},
   312  		})
   313  	}
   314  
   315  }
   316  
   317  func (t *tester) registerTest(name, dirBanner, bin string, args ...string) {
   318  	if bin == "time" && !t.haveTime {
   319  		bin, args = args[0], args[1:]
   320  	}
   321  	t.tests = append(t.tests, distTest{
   322  		name:    name,
   323  		heading: dirBanner,
   324  		fn: func() error {
   325  			return t.dirCmd(filepath.Join(t.goroot, "src", dirBanner), bin, args...).Run()
   326  		},
   327  	})
   328  }
   329  
   330  func (t *tester) dirCmd(dir string, bin string, args ...string) *exec.Cmd {
   331  	cmd := exec.Command(bin, args...)
   332  	if filepath.IsAbs(dir) {
   333  		cmd.Dir = dir
   334  	} else {
   335  		cmd.Dir = filepath.Join(t.goroot, dir)
   336  	}
   337  	cmd.Stdout = os.Stdout
   338  	cmd.Stderr = os.Stderr
   339  	return cmd
   340  }
   341  
   342  func (t *tester) out(v string) {
   343  	if t.banner == "" {
   344  		return
   345  	}
   346  	fmt.Println("\n" + t.banner + v)
   347  }
   348  
   349  func (t *tester) extLink() bool {
   350  	pair := t.gohostos + "-" + t.goarch
   351  	switch pair {
   352  	case "android-arm",
   353  		"dragonfly-386", "dragonfly-amd64",
   354  		"freebsd-386", "freebsd-amd64", "freebsd-arm",
   355  		"linux-386", "linux-amd64", "linux-arm",
   356  		"netbsd-386", "netbsd-amd64",
   357  		"openbsd-386", "openbsd-amd64",
   358  		"windows-386", "windows-amd64":
   359  		return true
   360  	case "darwin-386", "darwin-amd64":
   361  		// linkmode=external fails on OS X 10.6 and earlier == Darwin
   362  		// 10.8 and earlier.
   363  		unameR, err := exec.Command("uname", "-r").Output()
   364  		if err != nil {
   365  			log.Fatalf("uname -r: %v", err)
   366  		}
   367  		major, _ := strconv.Atoi(string(unameR[:bytes.IndexByte(unameR, '.')]))
   368  		return major > 10
   369  	}
   370  	return false
   371  }
   372  
   373  func (t *tester) cgoTest() error {
   374  	env := mergeEnvLists([]string{"GOTRACEBACK=2"}, os.Environ())
   375  
   376  	iOS := t.goos == "darwin" && (t.goarch == "arm" || t.goarch == "arm64")
   377  	if t.goos == "android" || iOS {
   378  		cmd := t.dirCmd("misc/cgo/test", "go", "test")
   379  		cmd.Env = env
   380  		return cmd.Run()
   381  	}
   382  
   383  	cmd := t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", "-linkmode=auto")
   384  	cmd.Env = env
   385  	if err := cmd.Run(); err != nil {
   386  		return err
   387  	}
   388  
   389  	if t.gohostos != "dragonfly" {
   390  		// linkmode=internal fails on dragonfly since errno is a TLS relocation.
   391  		cmd := t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", "-linkmode=internal")
   392  		cmd.Env = env
   393  		if err := cmd.Run(); err != nil {
   394  			return err
   395  		}
   396  	}
   397  
   398  	pair := t.gohostos + "-" + t.goarch
   399  	switch pair {
   400  	case "openbsd-386", "openbsd-amd64":
   401  		// test linkmode=external, but __thread not supported, so skip testtls.
   402  		cmd := t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", "-linkmode=external")
   403  		cmd.Env = env
   404  		if err := cmd.Run(); err != nil {
   405  			return err
   406  		}
   407  	case "darwin-386", "darwin-amd64",
   408  		"windows-386", "windows-amd64":
   409  		if t.extLink() {
   410  			cmd := t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", "-linkmode=external")
   411  			cmd.Env = env
   412  			if err := cmd.Run(); err != nil {
   413  				return err
   414  			}
   415  		}
   416  	case "android-arm",
   417  		"dragonfly-386", "dragonfly-amd64",
   418  		"freebsd-386", "freebsd-amd64", "freebsd-arm",
   419  		"linux-386", "linux-amd64", "linux-arm",
   420  		"netbsd-386", "netbsd-amd64":
   421  
   422  		cmd := t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", "-linkmode=external")
   423  		cmd.Env = env
   424  		if err := cmd.Run(); err != nil {
   425  			return err
   426  		}
   427  		cmd = t.dirCmd("misc/cgo/testtls", "go", "test", "-ldflags", "-linkmode=auto")
   428  		cmd.Env = env
   429  		if err := cmd.Run(); err != nil {
   430  			return err
   431  		}
   432  		cmd = t.dirCmd("misc/cgo/testtls", "go", "test", "-ldflags", "-linkmode=external")
   433  		cmd.Env = env
   434  		if err := cmd.Run(); err != nil {
   435  			return err
   436  		}
   437  
   438  		switch pair {
   439  		case "netbsd-386", "netbsd-amd64":
   440  			// no static linking
   441  		case "freebsd-arm":
   442  			// -fPIC compiled tls code will use __tls_get_addr instead
   443  			// of __aeabi_read_tp, however, on FreeBSD/ARM, __tls_get_addr
   444  			// is implemented in rtld-elf, so -fPIC isn't compatible with
   445  			// static linking on FreeBSD/ARM with clang. (cgo depends on
   446  			// -fPIC fundamentally.)
   447  		default:
   448  			cc := mustEnv("CC")
   449  			cmd := t.dirCmd("misc/cgo/test",
   450  				cc, "-xc", "-o", "/dev/null", "-static", "-")
   451  			cmd.Env = env
   452  			cmd.Stdin = strings.NewReader("int main() {}")
   453  			if err := cmd.Run(); err != nil {
   454  				fmt.Println("No support for static linking found (lacks libc.a?), skip cgo static linking test.")
   455  			} else {
   456  				cmd = t.dirCmd("misc/cgo/testtls", "go", "test", "-ldflags", `-linkmode=external -extldflags "-static -pthread"`)
   457  				cmd.Env = env
   458  				if err := cmd.Run(); err != nil {
   459  					return err
   460  				}
   461  
   462  				cmd = t.dirCmd("misc/cgo/nocgo", "go", "test")
   463  				cmd.Env = env
   464  				if err := cmd.Run(); err != nil {
   465  					return err
   466  				}
   467  
   468  				cmd = t.dirCmd("misc/cgo/nocgo", "go", "test", "-ldflags", `-linkmode=external`)
   469  				cmd.Env = env
   470  				if err := cmd.Run(); err != nil {
   471  					return err
   472  				}
   473  
   474  				cmd = t.dirCmd("misc/cgo/nocgo", "go", "test", "-ldflags", `-linkmode=external -extldflags "-static -pthread"`)
   475  				cmd.Env = env
   476  				if err := cmd.Run(); err != nil {
   477  					return err
   478  				}
   479  			}
   480  
   481  			if pair != "freebsd-amd64" { // clang -pie fails to link misc/cgo/test
   482  				cmd := t.dirCmd("misc/cgo/test",
   483  					cc, "-xc", "-o", "/dev/null", "-pie", "-")
   484  				cmd.Env = env
   485  				cmd.Stdin = strings.NewReader("int main() {}")
   486  				if err := cmd.Run(); err != nil {
   487  					fmt.Println("No support for -pie found, skip cgo PIE test.")
   488  				} else {
   489  					cmd = t.dirCmd("misc/cgo/test", "go", "test", "-ldflags", `-linkmode=external -extldflags "-pie"`)
   490  					cmd.Env = env
   491  					if err := cmd.Run(); err != nil {
   492  						return fmt.Errorf("pie cgo/test: %v", err)
   493  					}
   494  					cmd = t.dirCmd("misc/cgo/testtls", "go", "test", "-ldflags", `-linkmode=external -extldflags "-pie"`)
   495  					cmd.Env = env
   496  					if err := cmd.Run(); err != nil {
   497  						return fmt.Errorf("pie cgo/testtls: %v", err)
   498  					}
   499  					cmd = t.dirCmd("misc/cgo/nocgo", "go", "test", "-ldflags", `-linkmode=external -extldflags "-pie"`)
   500  					cmd.Env = env
   501  					if err := cmd.Run(); err != nil {
   502  						return fmt.Errorf("pie cgo/nocgo: %v", err)
   503  					}
   504  				}
   505  			}
   506  		}
   507  	}
   508  
   509  	return nil
   510  }
   511  
   512  func (t *tester) cgoTestSOWindows() error {
   513  	cmd := t.dirCmd("misc/cgo/testso", `.\test`)
   514  	var buf bytes.Buffer
   515  	cmd.Stdout = &buf
   516  	cmd.Stderr = &buf
   517  	err := cmd.Run()
   518  	s := buf.String()
   519  	fmt.Println(s)
   520  	if err != nil {
   521  		return err
   522  	}
   523  	if strings.Contains(s, "FAIL") {
   524  		return errors.New("test failed")
   525  	}
   526  	return nil
   527  }
   528  
   529  func (t *tester) hasBash() bool {
   530  	switch t.gohostos {
   531  	case "windows", "plan9":
   532  		return false
   533  	}
   534  	return true
   535  }
   536  
   537  func (t *tester) raceDetectorSupported() bool {
   538  	switch t.gohostos {
   539  	case "linux", "darwin", "freebsd", "windows":
   540  		return t.cgoEnabled && t.goarch == "amd64" && t.gohostos == t.goos
   541  	}
   542  	return false
   543  }
   544  
   545  func (t *tester) raceTest() error {
   546  	if err := t.dirCmd(".", "go", "test", "-race", "-i", "runtime/race", "flag", "os/exec").Run(); err != nil {
   547  		return err
   548  	}
   549  	if err := t.dirCmd(".", "go", "test", "-race", "-run=Output", "runtime/race").Run(); err != nil {
   550  		return err
   551  	}
   552  	if err := t.dirCmd(".", "go", "test", "-race", "-short", "flag", "os/exec").Run(); err != nil {
   553  		return err
   554  	}
   555  	if t.extLink() {
   556  		// Test with external linking; see issue 9133.
   557  		if err := t.dirCmd(".", "go", "test", "-race", "-short", "-ldflags=-linkmode=external", "flag", "os/exec").Run(); err != nil {
   558  			return err
   559  		}
   560  	}
   561  	return nil
   562  }
   563  
   564  func (t *tester) testDirTest() error {
   565  	const runExe = "runtest.exe" // named exe for Windows, but harmless elsewhere
   566  	cmd := t.dirCmd("test", "go", "build", "-o", runExe, "run.go")
   567  	cmd.Env = mergeEnvLists([]string{"GOOS=" + t.gohostos, "GOARCH=" + t.gohostarch, "GOMAXPROCS="}, os.Environ())
   568  	if err := cmd.Run(); err != nil {
   569  		return err
   570  	}
   571  	absExe := filepath.Join(cmd.Dir, runExe)
   572  	defer os.Remove(absExe)
   573  	if t.haveTime {
   574  		return t.dirCmd("test", "time", absExe).Run()
   575  	}
   576  	return t.dirCmd("test", absExe).Run()
   577  }
   578  
   579  // mergeEnvLists merges the two environment lists such that
   580  // variables with the same name in "in" replace those in "out".
   581  // out may be mutated.
   582  func mergeEnvLists(in, out []string) []string {
   583  NextVar:
   584  	for _, inkv := range in {
   585  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   586  		for i, outkv := range out {
   587  			if strings.HasPrefix(outkv, k) {
   588  				out[i] = inkv
   589  				continue NextVar
   590  			}
   591  		}
   592  		out = append(out, inkv)
   593  	}
   594  	return out
   595  }