Source file src/go/types/errorcodes_test.go

     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 doc == "" {
   175  			t.Errorf("%q is undocumented", name)
   176  		} else if !strings.HasPrefix(doc, name) {
   177  			t.Errorf("doc for %q does not start with the error code name", name)
   178  		}
   179  		lowerComment := strings.ToLower(strings.TrimPrefix(doc, name))
   180  		for _, bad := range forbiddenInComment {
   181  			if strings.Contains(lowerComment, bad) {
   182  				t.Errorf("doc for %q contains forbidden word %q", name, bad)
   183  			}
   184  		}
   185  	})
   186  
   187  	if testing.Verbose() {
   188  		var totChars, totCount int
   189  		for chars, count := range nameHist {
   190  			totChars += chars * count
   191  			totCount += count
   192  		}
   193  		avg := float64(totChars) / float64(totCount)
   194  		fmt.Println()
   195  		fmt.Printf("%d error codes\n", totCount)
   196  		fmt.Printf("average length: %.2f chars\n", avg)
   197  		fmt.Printf("max length: %d (%s)\n", len(longestName), longestName)
   198  	}
   199  }
   200  

View as plain text