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  }