 306cbb0e6e
			
		
	
	
		306cbb0e6e
		
			
		
	
	
	
		
			
	
		
	
	
		
			Some checks failed
		
		
	
	CI / golangci-lint (push) Failing after 3m54s
				CI / tests-on-windows (push) Has been skipped
				CI / tests-on-unix (1.13, ubuntu-latest) (push) Has been skipped
				CI / tests-on-unix (1.14, ubuntu-latest) (push) Has been skipped
				CI / check_generated (push) Has been skipped
				Release a tag / release (push) Failing after 4m22s
				
		
			
				
	
	
		
			232 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package test
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // errorCheck matches errors in outStr against comments in source files.
 | |
| // For each line of the source files which should generate an error,
 | |
| // there should be a comment of the form // ERROR "regexp".
 | |
| // If outStr has an error for a line which has no such comment,
 | |
| // this function will report an error.
 | |
| // Likewise if outStr does not have an error for a line which has a comment,
 | |
| // or if the error message does not match the <regexp>.
 | |
| // The <regexp> syntax is Perl but it's best to stick to egrep.
 | |
| //
 | |
| // Sources files are supplied as fullshort slice.
 | |
| // It consists of pairs: full path to source file and its base name.
 | |
| //nolint:gocyclo,funlen
 | |
| func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) {
 | |
| 	var errs []error
 | |
| 	out := splitOutput(outStr, wantAuto)
 | |
| 	// Cut directory name.
 | |
| 	for i := range out {
 | |
| 		for j := 0; j < len(fullshort); j += 2 {
 | |
| 			full, short := fullshort[j], fullshort[j+1]
 | |
| 			out[i] = strings.Replace(out[i], full, short, -1)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var want []wantedError
 | |
| 	for j := 0; j < len(fullshort); j += 2 {
 | |
| 		full, short := fullshort[j], fullshort[j+1]
 | |
| 		want = append(want, wantedErrors(full, short)...)
 | |
| 	}
 | |
| 	for _, we := range want {
 | |
| 		var errmsgs []string
 | |
| 		if we.auto {
 | |
| 			errmsgs, out = partitionStrings("<autogenerated>", out)
 | |
| 		} else {
 | |
| 			errmsgs, out = partitionStrings(we.prefix, out)
 | |
| 		}
 | |
| 		if len(errmsgs) == 0 {
 | |
| 			errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr))
 | |
| 			continue
 | |
| 		}
 | |
| 		matched := false
 | |
| 		n := len(out)
 | |
| 		var textsToMatch []string
 | |
| 		for _, errmsg := range errmsgs {
 | |
| 			// Assume errmsg says "file:line: foo".
 | |
| 			// Cut leading "file:line: " to avoid accidental matching of file name instead of message.
 | |
| 			text := errmsg
 | |
| 			if i := strings.Index(text, " "); i >= 0 {
 | |
| 				text = text[i+1:]
 | |
| 			}
 | |
| 			if we.re.MatchString(text) {
 | |
| 				matched = true
 | |
| 			} else {
 | |
| 				out = append(out, errmsg)
 | |
| 				textsToMatch = append(textsToMatch, text)
 | |
| 			}
 | |
| 		}
 | |
| 		if !matched {
 | |
| 			err := fmt.Errorf("%s:%d: no match for %#q vs %q in:\n\t%s",
 | |
| 				we.file, we.lineNum, we.reStr, textsToMatch, strings.Join(out[n:], "\n\t"))
 | |
| 			errs = append(errs, err)
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(out) > 0 {
 | |
| 		errs = append(errs, fmt.Errorf("unmatched errors"))
 | |
| 		for _, errLine := range out {
 | |
| 			errs = append(errs, fmt.Errorf("%s", errLine))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(errs) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if len(errs) == 1 {
 | |
| 		return errs[0]
 | |
| 	}
 | |
| 	var buf bytes.Buffer
 | |
| 	fmt.Fprintf(&buf, "\n")
 | |
| 	for _, err := range errs {
 | |
| 		fmt.Fprintf(&buf, "%s\n", err.Error())
 | |
| 	}
 | |
| 	return errors.New(buf.String())
 | |
| }
 | |
| 
 | |
| func splitOutput(out string, wantAuto bool) []string {
 | |
| 	// gc 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.
 | |
| 	// <autogenerated> lines are impossible to match so those are filtered out.
 | |
| 	var res []string
 | |
| 	for _, line := range strings.Split(out, "\n") {
 | |
| 		line = strings.TrimSuffix(line, "\r") // normalize Windows output
 | |
| 		if strings.HasPrefix(line, "\t") {
 | |
| 			res[len(res)-1] += "\n" + line
 | |
| 		} else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "<autogenerated>") {
 | |
| 			continue
 | |
| 		} else if strings.TrimSpace(line) != "" {
 | |
| 			res = append(res, line)
 | |
| 		}
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| // matchPrefix reports whether s starts with file name prefix followed by a :,
 | |
| // and possibly preceded by a directory name.
 | |
| func matchPrefix(s, prefix string) bool {
 | |
| 	i := strings.Index(s, ":")
 | |
| 	if i < 0 {
 | |
| 		return false
 | |
| 	}
 | |
| 	j := strings.LastIndex(s[:i], "/")
 | |
| 	s = s[j+1:]
 | |
| 	if len(s) <= len(prefix) || s[:len(prefix)] != prefix {
 | |
| 		return false
 | |
| 	}
 | |
| 	if s[len(prefix)] == ':' {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func partitionStrings(prefix string, strs []string) (matched, unmatched []string) {
 | |
| 	for _, s := range strs {
 | |
| 		if matchPrefix(s, prefix) {
 | |
| 			matched = append(matched, s)
 | |
| 		} else {
 | |
| 			unmatched = append(unmatched, s)
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| type wantedError struct {
 | |
| 	reStr   string
 | |
| 	re      *regexp.Regexp
 | |
| 	lineNum int
 | |
| 	auto    bool // match <autogenerated> line
 | |
| 	file    string
 | |
| 	prefix  string
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	errRx       = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`)
 | |
| 	errAutoRx   = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`)
 | |
| 	errQuotesRx = regexp.MustCompile(`"([^"]*)"`)
 | |
| 	lineRx      = regexp.MustCompile(`LINE(([+-])(\d+))?`)
 | |
| )
 | |
| 
 | |
| // wantedErrors parses expected errors from comments in a file.
 | |
| //nolint:nakedret,gocyclo,funlen
 | |
| func wantedErrors(file, short string) (errs []wantedError) {
 | |
| 	cache := make(map[string]*regexp.Regexp)
 | |
| 
 | |
| 	src, err := ioutil.ReadFile(file)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	for i, line := range strings.Split(string(src), "\n") {
 | |
| 		lineNum := i + 1
 | |
| 		if strings.Contains(line, "////") {
 | |
| 			// double comment disables ERROR
 | |
| 			continue
 | |
| 		}
 | |
| 		var auto bool
 | |
| 		m := errAutoRx.FindStringSubmatch(line)
 | |
| 		if m != nil {
 | |
| 			auto = true
 | |
| 		} else {
 | |
| 			m = errRx.FindStringSubmatch(line)
 | |
| 		}
 | |
| 		if m == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		all := m[1]
 | |
| 		mm := errQuotesRx.FindAllStringSubmatch(all, -1)
 | |
| 		if mm == nil {
 | |
| 			log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line)
 | |
| 		}
 | |
| 		for _, m := range mm {
 | |
| 			replacedOnce := false
 | |
| 			rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string {
 | |
| 				if replacedOnce {
 | |
| 					return m
 | |
| 				}
 | |
| 				replacedOnce = true
 | |
| 				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", short, n)
 | |
| 			})
 | |
| 			re := cache[rx]
 | |
| 			if re == nil {
 | |
| 				var err error
 | |
| 				re, err = regexp.Compile(rx)
 | |
| 				if err != nil {
 | |
| 					log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err)
 | |
| 				}
 | |
| 				cache[rx] = re
 | |
| 			}
 | |
| 			prefix := fmt.Sprintf("%s:%d", short, lineNum)
 | |
| 			errs = append(errs, wantedError{
 | |
| 				reStr:   rx,
 | |
| 				re:      re,
 | |
| 				prefix:  prefix,
 | |
| 				auto:    auto,
 | |
| 				lineNum: lineNum,
 | |
| 				file:    short,
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 |