package test // These tests check for meta level items, like trailing whitespace, correct file naming etc. import ( "bufio" "fmt" "go/ast" "go/parser" "go/token" "os" "path/filepath" "strings" "testing" "unicode" ) func TestTrailingWhitespace(t *testing.T) { err := filepath.Walk("..", hasTrailingWhitespace) if err != nil { t.Fatal(err) } } func hasTrailingWhitespace(path string, info os.FileInfo, _ error) error { // Only handle regular files, skip files that are executable and skip file in the // root that start with a . if !info.Mode().IsRegular() { return nil } if info.Mode().Perm()&0111 != 0 { return nil } if strings.HasPrefix(path, "../.") { return nil } file, err := os.Open(path) if err != nil { return nil } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { text := scanner.Text() trimmed := strings.TrimRightFunc(text, unicode.IsSpace) if len(text) != len(trimmed) { return fmt.Errorf("file %q has trailing whitespace, text: %q", path, text) } } return scanner.Err() } func TestFileNameHyphen(t *testing.T) { err := filepath.Walk("..", hasHyphen) if err != nil { t.Fatal(err) } } func hasHyphen(path string, info os.FileInfo, _ error) error { // only for regular files, not starting with a . and those that are go files. if !info.Mode().IsRegular() { return nil } if strings.HasPrefix(path, "../.") { return nil } if filepath.Ext(path) != ".go" { return nil } if strings.Index(path, "-") > 0 { return fmt.Errorf("file %q has a hyphen, please use underscores in file names", path) } return nil } // Test if error messages start with an upper case. func TestLowercaseLog(t *testing.T) { err := filepath.Walk("..", hasLowercase) if err != nil { t.Fatal(err) } } func hasLowercase(path string, info os.FileInfo, _ error) error { // only for regular files, not starting with a . and those that are go files. if !info.Mode().IsRegular() { return nil } if strings.HasPrefix(path, "../.") { return nil } if !strings.HasSuffix(path, "_test.go") { return nil } fs := token.NewFileSet() f, err := parser.ParseFile(fs, path, nil, parser.AllErrors) if err != nil { return err } l := &logfmt{} ast.Walk(l, f) if l.err != nil { return l.err } return nil } type logfmt struct { err error } func (l logfmt) Visit(n ast.Node) ast.Visitor { if n == nil { return nil } ce, ok := n.(*ast.CallExpr) if !ok { return l } se, ok := ce.Fun.(*ast.SelectorExpr) if !ok { return l } id, ok := se.X.(*ast.Ident) if !ok { return l } if id.Name != "t" { //t *testing.T return l } switch se.Sel.Name { case "Errorf": case "Logf": case "Log": case "Fatalf": case "Fatal": default: return l } // Check first arg, that should have basic lit with capital if len(ce.Args) < 1 { return l } bl, ok := ce.Args[0].(*ast.BasicLit) if !ok { return l } if bl.Kind != token.STRING { return l } if strings.HasPrefix(bl.Value, "\"%s") || strings.HasPrefix(bl.Value, "\"%d") { return l } if strings.HasPrefix(bl.Value, "\"%v") || strings.HasPrefix(bl.Value, "\"%+v") { return l } for i, u := range bl.Value { // disregard " if i == 1 && !unicode.IsUpper(u) { l.err = fmt.Errorf("test error message %s doesn't start with an uppercase", bl.Value) return nil } if i == 1 { break } } return l } func TestImportTesting(t *testing.T) { err := filepath.Walk("..", hasImportTesting) if err != nil { t.Fatal(err) } } func hasImportTesting(path string, info os.FileInfo, _ error) error { // only for regular files, not starting with a . and those that are go files. if !info.Mode().IsRegular() { return nil } if strings.HasPrefix(path, "../.") { return nil } if strings.HasSuffix(path, "_test.go") { return nil } if strings.HasSuffix(path, ".go") { fs := token.NewFileSet() f, err := parser.ParseFile(fs, path, nil, parser.AllErrors) if err != nil { return err } for _, im := range f.Imports { if im.Path.Value == `"testing"` { return fmt.Errorf("file %q is importing %q", path, "testing") } } } return nil } func TestImportOrdering(t *testing.T) { err := filepath.Walk("..", hasImportOrdering) if err != nil { t.Fatal(err) } } func hasImportOrdering(path string, info os.FileInfo, _ error) error { if !info.Mode().IsRegular() { return nil } if strings.HasPrefix(path, "../.") { return nil } if filepath.Ext(path) != ".go" { return nil } fs := token.NewFileSet() f, err := parser.ParseFile(fs, path, nil, parser.AllErrors) if err != nil { return err } if len(f.Imports) == 0 { return nil } // 3 blocks total, if // 3 blocks: std + coredns + 3rd party // 2 blocks: std + coredns, std + 3rd party, coredns + 3rd party // 1 block: std, coredns, 3rd party // first entry in a block specifies the type (std, coredns, 3rd party) // we want: std, coredns, 3rd party // more than 3 blocks as an error blocks := [3][]*ast.ImportSpec{} prevpos := 0 bl := 0 for _, im := range f.Imports { line := fs.Position(im.Path.Pos()).Line if line-prevpos > 1 && prevpos > 0 { bl++ } if bl > 2 { return fmt.Errorf("more than %d import blocks in %q", bl, path) } blocks[bl] = append(blocks[bl], im) prevpos = line } // if it: // contains strings github.com/coredns/coredns -> coredns // contains dots -> 3rd // no dots -> std ip := [3]string{} // type per block, just string, either std, coredns, 3rd for i := 0; i <= bl; i++ { ip[i] = importtype(blocks[i][0].Path.Value) } // Ok, now that we have the type, let's see if all members adhere to it. // After that we check if the are in the right order. for i := 0; i < bl; i++ { for _, p := range blocks[i] { t := importtype(p.Path.Value) if t != ip[i] { return fmt.Errorf("import path for %s is not of the same type %q in %q", p.Path.Value, ip[i], path) } } } // check order switch bl { case 0: // we don't care case 1: if ip[0] == "std" && ip[1] == "coredns" { break // OK } if ip[0] == "std" && ip[1] == "3rd" { break // OK } if ip[0] == "coredns" && ip[1] == "3rd" { break // OK } return fmt.Errorf("import path in %q are not in the right order (std -> coredns -> 3rd)", path) case 2: if ip[0] == "std" && ip[1] == "coredns" && ip[2] == "3rd" { break // OK } return fmt.Errorf("import path in %q are not in the right order (std -> coredns -> 3rd)", path) } return nil } func importtype(s string) string { if strings.Contains(s, "github.com/coredns/coredns") { return "coredns" } if strings.Contains(s, ".") { return "3rd" } return "std" }