mirror of
				https://github.com/golang/go.git
				synced 2025-10-30 00:00:56 +00:00 
			
		
		
		
	
		
			
	
	
		
			455 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			455 lines
		
	
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | // #ignore | ||
|  | 
 | ||
|  | // Copyright 2012 The Go Authors.  All rights reserved. | ||
|  | // Use of this source code is governed by a BSD-style | ||
|  | // license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | // Run runs tests in the test directory. | ||
|  | //  | ||
|  | // TODO(bradfitz): docs of some sort, once we figure out how we're changing | ||
|  | // headers of files | ||
|  | package main | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"bytes" | ||
|  | 	"errors" | ||
|  | 	"flag" | ||
|  | 	"fmt" | ||
|  | 	"go/build" | ||
|  | 	"io/ioutil" | ||
|  | 	"log" | ||
|  | 	"os" | ||
|  | 	"os/exec" | ||
|  | 	"path/filepath" | ||
|  | 	"regexp" | ||
|  | 	"runtime" | ||
|  | 	"sort" | ||
|  | 	"strconv" | ||
|  | 	"strings" | ||
|  | ) | ||
|  | 
 | ||
|  | var ( | ||
|  | 	verbose     = flag.Bool("v", false, "verbose. if set, parallelism is set to 1.") | ||
|  | 	numParallel = flag.Int("n", 8, "number of parallel tests to run") | ||
|  | 	summary     = flag.Bool("summary", false, "show summary of results") | ||
|  | ) | ||
|  | 
 | ||
|  | var ( | ||
|  | 	// gc and ld are [568][gl]. | ||
|  | 	gc, ld string | ||
|  | 
 | ||
|  | 	// letter is the build.ArchChar | ||
|  | 	letter string | ||
|  | 
 | ||
|  | 	// dirs are the directories to look for *.go files in. | ||
|  | 	// TODO(bradfitz): just use all directories? | ||
|  | 	dirs = []string{".", "ken", "chan", "interface", "syntax", "dwarf", "fixedbugs", "bugs"} | ||
|  | 
 | ||
|  | 	// ratec controls the max number of tests running at a time. | ||
|  | 	ratec chan bool | ||
|  | 
 | ||
|  | 	// toRun is the channel of tests to run. | ||
|  | 	// It is nil until the first test is started. | ||
|  | 	toRun chan *test | ||
|  | ) | ||
|  | 
 | ||
|  | // maxTests is an upper bound on the total number of tests. | ||
|  | // It is used as a channel buffer size to make sure sends don't block. | ||
|  | const maxTests = 5000 | ||
|  | 
 | ||
|  | func main() { | ||
|  | 	flag.Parse() | ||
|  | 	if *verbose { | ||
|  | 		*numParallel = 1 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	ratec = make(chan bool, *numParallel) | ||
|  | 	var err error | ||
|  | 	letter, err = build.ArchChar(build.DefaultContext.GOARCH) | ||
|  | 	check(err) | ||
|  | 	gc = letter + "g" | ||
|  | 	ld = letter + "l" | ||
|  | 
 | ||
|  | 	var tests []*test | ||
|  | 	if flag.NArg() > 0 { | ||
|  | 		for _, arg := range flag.Args() { | ||
|  | 			if arg == "-" || arg == "--" { | ||
|  | 				// Permit running either: | ||
|  | 				// $ go run run.go - env.go | ||
|  | 				// $ go run run.go -- env.go | ||
|  | 				continue | ||
|  | 			} | ||
|  | 			if !strings.HasSuffix(arg, ".go") { | ||
|  | 				log.Fatalf("can't yet deal with non-go file %q", arg) | ||
|  | 			} | ||
|  | 			dir, file := filepath.Split(arg) | ||
|  | 			tests = append(tests, startTest(dir, file)) | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		for _, dir := range dirs { | ||
|  | 			for _, baseGoFile := range goFiles(dir) { | ||
|  | 				tests = append(tests, startTest(dir, baseGoFile)) | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	failed := false | ||
|  | 	resCount := map[string]int{} | ||
|  | 	for _, test := range tests { | ||
|  | 		<-test.donec | ||
|  | 		_, isSkip := test.err.(skipError) | ||
|  | 		if isSkip { | ||
|  | 			resCount["skip"]++ | ||
|  | 			if !*verbose { | ||
|  | 				continue | ||
|  | 			} | ||
|  | 		} | ||
|  | 		errStr := "pass" | ||
|  | 		if test.err != nil { | ||
|  | 			errStr = test.err.Error() | ||
|  | 			if !isSkip { | ||
|  | 				failed = true | ||
|  | 			} | ||
|  | 		} | ||
|  | 		resCount[errStr]++ | ||
|  | 		if !*verbose && test.err == nil { | ||
|  | 			continue | ||
|  | 	} | ||
|  | 		fmt.Printf("%-10s %-20s: %s\n", test.action, test.goFileName(), errStr) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if *summary { | ||
|  | 		for k, v := range resCount { | ||
|  | 			fmt.Printf("%5d %s\n", v, k) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if failed { | ||
|  | 		os.Exit(1) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func toolPath(name string) string { | ||
|  | 	p := filepath.Join(os.Getenv("GOROOT"), "bin", "tool", name) | ||
|  | 	if _, err := os.Stat(p); err != nil { | ||
|  | 		log.Fatalf("didn't find binary at %s", p) | ||
|  | 	} | ||
|  | 	return p | ||
|  | } | ||
|  | 
 | ||
|  | func goFiles(dir string) []string { | ||
|  | 	f, err := os.Open(dir) | ||
|  | 	check(err) | ||
|  | 	dirnames, err := f.Readdirnames(-1) | ||
|  | 	check(err) | ||
|  | 	names := []string{} | ||
|  | 	for _, name := range dirnames { | ||
|  | 		if strings.HasSuffix(name, ".go") { | ||
|  | 			names = append(names, name) | ||
|  | 		} | ||
|  | 	} | ||
|  | 	sort.Strings(names) | ||
|  | 	return names | ||
|  | } | ||
|  | 
 | ||
|  | // skipError describes why a test was skipped. | ||
|  | type skipError string | ||
|  | 
 | ||
|  | func (s skipError) Error() string { return string(s) } | ||
|  | 
 | ||
|  | func check(err error) { | ||
|  | 	if err != nil { | ||
|  | 		log.Fatal(err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // test holds the state of a test. | ||
|  | type test struct { | ||
|  | 	dir, gofile string | ||
|  | 	donec       chan bool // closed when done | ||
|  | 
 | ||
|  | 	src    string | ||
|  | 	action string // "compile", "build", "run", "errorcheck" | ||
|  | 
 | ||
|  | 	tempDir string | ||
|  | 	err     error | ||
|  | } | ||
|  | 
 | ||
|  | // startTest  | ||
|  | func startTest(dir, gofile string) *test { | ||
|  | 	t := &test{ | ||
|  | 		dir:    dir, | ||
|  | 		gofile: gofile, | ||
|  | 		donec:  make(chan bool, 1), | ||
|  | 	} | ||
|  | 	if toRun == nil { | ||
|  | 		toRun = make(chan *test, maxTests) | ||
|  | 		go runTests() | ||
|  | 	} | ||
|  | 	select { | ||
|  | 	case toRun <- t: | ||
|  | 	default: | ||
|  | 		panic("toRun buffer size (maxTests) is too small") | ||
|  | 	} | ||
|  | 	return t | ||
|  | } | ||
|  | 
 | ||
|  | // runTests runs tests in parallel, but respecting the order they | ||
|  | // were enqueued on the toRun channel. | ||
|  | func runTests() { | ||
|  | 	for { | ||
|  | 		ratec <- true | ||
|  | 		t := <-toRun | ||
|  | 		go func() { | ||
|  | 			t.run() | ||
|  | 			<-ratec | ||
|  | 		}() | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func (t *test) goFileName() string { | ||
|  | 	return filepath.Join(t.dir, t.gofile) | ||
|  | } | ||
|  | 
 | ||
|  | // run runs a test. | ||
|  | func (t *test) run() { | ||
|  | 	defer close(t.donec) | ||
|  | 
 | ||
|  | 	srcBytes, err := ioutil.ReadFile(t.goFileName()) | ||
|  | 	if err != nil { | ||
|  | 		t.err = err | ||
|  | 		return | ||
|  | 	} | ||
|  | 	t.src = string(srcBytes) | ||
|  | 	if t.src[0] == '\n' { | ||
|  | 		t.err = skipError("starts with newline") | ||
|  | 		return | ||
|  | 	} | ||
|  | 	pos := strings.Index(t.src, "\n\n") | ||
|  | 	if pos == -1 { | ||
|  | 		t.err = errors.New("double newline not found") | ||
|  | 		return | ||
|  | 	} | ||
|  | 	action := t.src[:pos] | ||
|  | 	if strings.HasPrefix(action, "//") { | ||
|  | 		action = action[2:] | ||
|  | 	} | ||
|  | 	action = strings.TrimSpace(action) | ||
|  | 
 | ||
|  | 	switch action { | ||
|  | 	case "compile", "build", "run", "errorcheck": | ||
|  | 		t.action = action | ||
|  | 	default: | ||
|  | 		t.err = skipError("skipped; unknown pattern: " + action) | ||
|  | 		t.action = "??" | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	t.makeTempDir() | ||
|  | 	defer os.RemoveAll(t.tempDir) | ||
|  | 
 | ||
|  | 	err = ioutil.WriteFile(filepath.Join(t.tempDir, t.gofile), srcBytes, 0644) | ||
|  | 	check(err) | ||
|  | 
 | ||
|  | 	cmd := exec.Command("go", "tool", gc, "-e", "-o", "a."+letter, t.gofile) | ||
|  | 	var buf bytes.Buffer | ||
|  | 	cmd.Stdout = &buf | ||
|  | 	cmd.Stderr = &buf | ||
|  | 	cmd.Dir = t.tempDir | ||
|  | 	err = cmd.Run() | ||
|  | 	out := buf.String() | ||
|  | 
 | ||
|  | 	if action == "errorcheck" { | ||
|  | 		t.err = t.errorCheck(out) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if err != nil { | ||
|  | 		t.err = fmt.Errorf("build = %v (%q)", err, out) | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if action == "compile" { | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if action == "build" || action == "run" { | ||
|  | 		buf.Reset() | ||
|  | 		cmd = exec.Command("go", "tool", ld, "-o", "a.out", "a."+letter) | ||
|  | 		cmd.Stdout = &buf | ||
|  | 		cmd.Stderr = &buf | ||
|  | 		cmd.Dir = t.tempDir | ||
|  | 		err = cmd.Run() | ||
|  | 		out = buf.String() | ||
|  | 		if err != nil { | ||
|  | 			t.err = fmt.Errorf("link = %v (%q)", err, out) | ||
|  | 			return | ||
|  | 		} | ||
|  | 		if action == "build" { | ||
|  | 			return | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if action == "run" { | ||
|  | 		buf.Reset() | ||
|  | 		cmd = exec.Command(filepath.Join(t.tempDir, "a.out")) | ||
|  | 		cmd.Stdout = &buf | ||
|  | 		cmd.Stderr = &buf | ||
|  | 		cmd.Dir = t.tempDir | ||
|  | 		cmd.Env = append(cmd.Env, "GOARCH="+runtime.GOARCH) | ||
|  | 		err = cmd.Run() | ||
|  | 		out = buf.String() | ||
|  | 		if err != nil { | ||
|  | 			t.err = fmt.Errorf("run = %v (%q)", err, out) | ||
|  | 			return | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if out != t.expectedOutput() { | ||
|  | 			t.err = fmt.Errorf("output differs; got:\n%s", out) | ||
|  | 		} | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	t.err = fmt.Errorf("unimplemented action %q", action) | ||
|  | } | ||
|  | 
 | ||
|  | func (t *test) String() string { | ||
|  | 	return filepath.Join(t.dir, t.gofile) | ||
|  | } | ||
|  | 
 | ||
|  | func (t *test) makeTempDir() { | ||
|  | 	var err error | ||
|  | 	t.tempDir, err = ioutil.TempDir("", "") | ||
|  | 	check(err) | ||
|  | } | ||
|  | 
 | ||
|  | func (t *test) expectedOutput() string { | ||
|  | 	filename := filepath.Join(t.dir, t.gofile) | ||
|  | 	filename = filename[:len(filename)-len(".go")] | ||
|  | 	filename += ".out" | ||
|  | 	b, _ := ioutil.ReadFile(filename) | ||
|  | 	return string(b) | ||
|  | } | ||
|  | 
 | ||
|  | func (t *test) errorCheck(outStr string) (err error) { | ||
|  | 	defer func() { | ||
|  | 		if *verbose && err != nil { | ||
|  | 			log.Printf("%s gc output:\n%s", t, outStr) | ||
|  | 		} | ||
|  | 	}() | ||
|  | 	var errs []error | ||
|  | 
 | ||
|  | 	var out []string | ||
|  | 	// 6g error messages continue onto additional lines with leading tabs. | ||
|  | 	// Split the output at the beginning of each line that doesn't begin with a tab. | ||
|  | 	for _, line := range strings.Split(outStr, "\n") { | ||
|  | 		if strings.HasPrefix(line, "\t") { | ||
|  | 			out[len(out)-1] += "\n" + line | ||
|  | 		} else { | ||
|  | 			out = append(out, line) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	for _, we := range t.wantedErrors() { | ||
|  | 		var errmsgs []string | ||
|  | 		errmsgs, out = partitionStrings(we.filterRe, out) | ||
|  | 		if len(errmsgs) == 0 { | ||
|  | 			errs = append(errs, fmt.Errorf("errchk: %s:%d: missing expected error: %s", we.file, we.lineNum, we.reStr)) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		matched := false | ||
|  | 		for _, errmsg := range errmsgs { | ||
|  | 			if we.re.MatchString(errmsg) { | ||
|  | 				matched = true | ||
|  | 			} else { | ||
|  | 				out = append(out, errmsg) | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if !matched { | ||
|  | 			errs = append(errs, fmt.Errorf("errchk: %s:%d: error(s) on line didn't match pattern: %s", we.file, we.lineNum, we.reStr)) | ||
|  | 			continue | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if len(errs) == 0 { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	if len(errs) == 1 { | ||
|  | 		return errs[0] | ||
|  | 	} | ||
|  | 	var buf bytes.Buffer | ||
|  | 	buf.WriteString("Multiple errors:\n") | ||
|  | 	for _, err := range errs { | ||
|  | 		fmt.Fprintf(&buf, "%s\n", err.Error()) | ||
|  | 	} | ||
|  | 	return errors.New(buf.String()) | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | func partitionStrings(rx *regexp.Regexp, strs []string) (matched, unmatched []string) { | ||
|  | 	for _, s := range strs { | ||
|  | 		if rx.MatchString(s) { | ||
|  | 			matched = append(matched, s) | ||
|  | 		} else { | ||
|  | 			unmatched = append(unmatched, s) | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | type wantedError struct { | ||
|  | 	reStr    string | ||
|  | 	re       *regexp.Regexp | ||
|  | 	lineNum  int | ||
|  | 	file     string | ||
|  | 	filterRe *regexp.Regexp // /^file:linenum\b/m | ||
|  | } | ||
|  | 
 | ||
|  | var ( | ||
|  | 	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) | ||
|  | 	errQuotesRx = regexp.MustCompile(`"([^"]*)"`) | ||
|  | 	lineRx      = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) | ||
|  | ) | ||
|  | 
 | ||
|  | func (t *test) wantedErrors() (errs []wantedError) { | ||
|  | 	for i, line := range strings.Split(t.src, "\n") { | ||
|  | 		lineNum := i + 1 | ||
|  | 		if strings.Contains(line, "////") { | ||
|  | 			// double comment disables ERROR | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		m := errRx.FindStringSubmatch(line) | ||
|  | 		if m == nil { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		all := m[1] | ||
|  | 		mm := errQuotesRx.FindAllStringSubmatch(all, -1) | ||
|  | 		if mm == nil { | ||
|  | 			log.Fatalf("invalid errchk line in %s: %s", t.goFileName(), line) | ||
|  | 		} | ||
|  | 		for _, m := range mm { | ||
|  | 			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { | ||
|  | 				n := lineNum | ||
|  | 				if strings.HasPrefix(m, "LINE+") { | ||
|  | 					delta, _ := strconv.Atoi(m[5:]) | ||
|  | 					n += delta | ||
|  | 				} else if strings.HasPrefix(m, "LINE-") { | ||
|  | 					delta, _ := strconv.Atoi(m[5:]) | ||
|  | 					n -= delta | ||
|  | 				} | ||
|  | 				return fmt.Sprintf("%s:%d", t.gofile, n) | ||
|  | 			}) | ||
|  | 			filterPattern := fmt.Sprintf(`^(\w+/)?%s:%d[:[]`, t.gofile, lineNum) | ||
|  | 			errs = append(errs, wantedError{ | ||
|  | 				reStr:    rx, | ||
|  | 				re:       regexp.MustCompile(rx), | ||
|  | 				filterRe: regexp.MustCompile(filterPattern), | ||
|  | 				lineNum:  lineNum, | ||
|  | 				file:     t.gofile, | ||
|  | 			}) | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return | ||
|  | } |