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