github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/types/errors/codes_test.go (about) 1 // Copyright 2020 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 errors_test 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/constant" 11 "go/importer" 12 "go/parser" 13 "go/token" 14 "reflect" 15 "strings" 16 "testing" 17 18 "github.com/go-asm/go/testenv" 19 20 . "go/types" 21 ) 22 23 func TestErrorCodeExamples(t *testing.T) { 24 testenv.MustHaveGoBuild(t) // go command needed to resolve std .a files for importer.Default(). 25 26 walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { 27 t.Run(name, func(t *testing.T) { 28 doc := spec.Doc.Text() 29 examples := strings.Split(doc, "Example:") 30 for i := 1; i < len(examples); i++ { 31 example := strings.TrimSpace(examples[i]) 32 err := checkExample(t, example) 33 if err == nil { 34 t.Fatalf("no error in example #%d", i) 35 } 36 typerr, ok := err.(Error) 37 if !ok { 38 t.Fatalf("not a types.Error: %v", err) 39 } 40 if got := readCode(typerr); got != value { 41 t.Errorf("%s: example #%d returned code %d (%s), want %d", name, i, got, err, value) 42 } 43 } 44 }) 45 }) 46 } 47 48 func walkCodes(t *testing.T, f func(string, int, *ast.ValueSpec)) { 49 t.Helper() 50 fset := token.NewFileSet() 51 file, err := parser.ParseFile(fset, "codes.go", nil, parser.ParseComments) 52 if err != nil { 53 t.Fatal(err) 54 } 55 conf := Config{Importer: importer.Default()} 56 info := &Info{ 57 Types: make(map[ast.Expr]TypeAndValue), 58 Defs: make(map[*ast.Ident]Object), 59 Uses: make(map[*ast.Ident]Object), 60 } 61 _, err = conf.Check("types", fset, []*ast.File{file}, info) 62 if err != nil { 63 t.Fatal(err) 64 } 65 for _, decl := range file.Decls { 66 decl, ok := decl.(*ast.GenDecl) 67 if !ok || decl.Tok != token.CONST { 68 continue 69 } 70 for _, spec := range decl.Specs { 71 spec, ok := spec.(*ast.ValueSpec) 72 if !ok || len(spec.Names) == 0 { 73 continue 74 } 75 obj := info.ObjectOf(spec.Names[0]) 76 if named, ok := obj.Type().(*Named); ok && named.Obj().Name() == "Code" { 77 if len(spec.Names) != 1 { 78 t.Fatalf("bad Code declaration for %q: got %d names, want exactly 1", spec.Names[0].Name, len(spec.Names)) 79 } 80 codename := spec.Names[0].Name 81 value := int(constant.Val(obj.(*Const).Val()).(int64)) 82 f(codename, value, spec) 83 } 84 } 85 } 86 } 87 88 func readCode(err Error) int { 89 v := reflect.ValueOf(err) 90 return int(v.FieldByName("go116code").Int()) 91 } 92 93 func checkExample(t *testing.T, example string) error { 94 t.Helper() 95 fset := token.NewFileSet() 96 if !strings.HasPrefix(example, "package") { 97 example = "package p\n\n" + example 98 } 99 file, err := parser.ParseFile(fset, "example.go", example, 0) 100 if err != nil { 101 t.Fatal(err) 102 } 103 conf := Config{ 104 FakeImportC: true, 105 Importer: importer.Default(), 106 } 107 _, err = conf.Check("example", fset, []*ast.File{file}, nil) 108 return err 109 } 110 111 func TestErrorCodeStyle(t *testing.T) { 112 // The set of error codes is large and intended to be self-documenting, so 113 // this test enforces some style conventions. 114 forbiddenInIdent := []string{ 115 // use invalid instead 116 "illegal", 117 // words with a common short-form 118 "argument", 119 "assertion", 120 "assignment", 121 "boolean", 122 "channel", 123 "condition", 124 "declaration", 125 "expression", 126 "function", 127 "initial", // use init for initializer, initialization, etc. 128 "integer", 129 "interface", 130 "iterat", // use iter for iterator, iteration, etc. 131 "literal", 132 "operation", 133 "package", 134 "pointer", 135 "receiver", 136 "signature", 137 "statement", 138 "variable", 139 } 140 forbiddenInComment := []string{ 141 // lhs and rhs should be spelled-out. 142 "lhs", "rhs", 143 // builtin should be hyphenated. 144 "builtin", 145 // Use dot-dot-dot. 146 "ellipsis", 147 } 148 nameHist := make(map[int]int) 149 longestName := "" 150 maxValue := 0 151 152 walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { 153 if name == "_" { 154 return 155 } 156 nameHist[len(name)]++ 157 if value > maxValue { 158 maxValue = value 159 } 160 if len(name) > len(longestName) { 161 longestName = name 162 } 163 if !token.IsExported(name) { 164 t.Errorf("%q is not exported", name) 165 } 166 lower := strings.ToLower(name) 167 for _, bad := range forbiddenInIdent { 168 if strings.Contains(lower, bad) { 169 t.Errorf("%q contains forbidden word %q", name, bad) 170 } 171 } 172 doc := spec.Doc.Text() 173 if doc == "" { 174 t.Errorf("%q is undocumented", name) 175 } else if !strings.HasPrefix(doc, name) { 176 t.Errorf("doc for %q does not start with the error code name", name) 177 } 178 lowerComment := strings.ToLower(strings.TrimPrefix(doc, name)) 179 for _, bad := range forbiddenInComment { 180 if strings.Contains(lowerComment, bad) { 181 t.Errorf("doc for %q contains forbidden word %q", name, bad) 182 } 183 } 184 }) 185 186 if testing.Verbose() { 187 var totChars, totCount int 188 for chars, count := range nameHist { 189 totChars += chars * count 190 totCount += count 191 } 192 avg := float64(totChars) / float64(totCount) 193 fmt.Println() 194 fmt.Printf("%d error codes\n", totCount) 195 fmt.Printf("average length: %.2f chars\n", avg) 196 fmt.Printf("max length: %d (%s)\n", len(longestName), longestName) 197 } 198 }