...

Source file src/go/types/errorcodes_test.go

Documentation: go/types

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

View as plain text