honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/pattern/parser_test.go (about)

     1  package pattern
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	goparser "go/parser"
     7  	"go/token"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"runtime"
    12  	"strings"
    13  	"testing"
    14  
    15  	"honnef.co/go/tools/debug"
    16  )
    17  
    18  func TestParse(t *testing.T) {
    19  	inputs := []string{
    20  		`(Binding "name" _)`,
    21  		`(Binding "name" _:[])`,
    22  		`(Binding "name" _:_:[])`,
    23  	}
    24  
    25  	p := Parser{}
    26  	for _, input := range inputs {
    27  		if _, err := p.Parse(input); err != nil {
    28  			t.Errorf("failed to parse %q: %s", input, err)
    29  		}
    30  	}
    31  }
    32  
    33  func FuzzParse(f *testing.F) {
    34  	var files []*ast.File
    35  	fset := token.NewFileSet()
    36  
    37  	// Ideally we'd check against as much source code as possible, but that's fairly slow, on the order of 500ms per
    38  	// pattern when checking against the whole standard library.
    39  	//
    40  	// We pick the runtime package in the hopes that it contains the most diverse, and weird, code.
    41  	filepath.Walk(runtime.GOROOT()+"/src/runtime", func(path string, info os.FileInfo, err error) error {
    42  		if err != nil {
    43  			// XXX error handling
    44  			panic(err)
    45  		}
    46  		if !strings.HasSuffix(path, ".go") {
    47  			return nil
    48  		}
    49  		f, err := goparser.ParseFile(fset, path, nil, goparser.SkipObjectResolution)
    50  		if err != nil {
    51  			return nil
    52  		}
    53  		files = append(files, f)
    54  		return nil
    55  	})
    56  
    57  	parse := func(in string, allowTypeInfo bool) (Pattern, bool) {
    58  		p := Parser{
    59  			AllowTypeInfo: allowTypeInfo,
    60  		}
    61  		pat, err := p.Parse(string(in))
    62  		if err != nil {
    63  			if strings.Contains(err.Error(), "internal error") {
    64  				panic(err)
    65  			}
    66  			return Pattern{}, false
    67  		}
    68  		return pat, true
    69  	}
    70  
    71  	f.Fuzz(func(t *testing.T, in []byte) {
    72  		defer func() {
    73  			if err := recover(); err != nil {
    74  				str := fmt.Sprint(err)
    75  				if strings.Contains(str, "binding already created:") {
    76  					// This is an invalid pattern, not a real failure
    77  				} else {
    78  					// Re-panic the original panic
    79  					panic(err)
    80  				}
    81  			}
    82  		}()
    83  		// Parse twice, once with AllowTypeInfo set to true to exercise the parser, and once with it set to false so we
    84  		// can actually use it in Match, as we don't have type information available.
    85  
    86  		pat, ok := parse(string(in), true)
    87  		if !ok {
    88  			return
    89  		}
    90  		// Make sure we can turn it back into a string
    91  		_ = pat.Root.String()
    92  
    93  		pat, ok = parse(string(in), false)
    94  		if !ok {
    95  			return
    96  		}
    97  		// Make sure we can turn it back into a string
    98  		_ = pat.Root.String()
    99  
   100  		// Don't check patterns with too many relevant nodes; it's too expensive
   101  		if len(pat.Relevant) < 20 {
   102  			// Make sure trying to match nodes doesn't panic
   103  			for _, f := range files {
   104  				ast.Inspect(f, func(node ast.Node) bool {
   105  					rt := reflect.TypeOf(node)
   106  					// We'd prefer calling Match on all nodes, not just those the pattern deems relevant, to find more bugs.
   107  					// However, doing so has a 10x cost in execution time.
   108  					if _, ok := pat.Relevant[rt]; ok {
   109  						Match(pat, node)
   110  					}
   111  					return true
   112  				})
   113  			}
   114  		}
   115  	})
   116  }
   117  
   118  func TestMatchAlias(t *testing.T) {
   119  	p1 := MustParse(`(CallExpr (Symbol "foo.Alias") _)`)
   120  	p2 := MustParse(`(CallExpr (Symbol "int") _)`)
   121  
   122  	f, _, info, err := debug.TypeCheck(`
   123  package pkg
   124  type Alias = int
   125  func _() { _ = Alias(0) }
   126  `)
   127  	if err != nil {
   128  		t.Fatal(err)
   129  	}
   130  
   131  	m := &Matcher{
   132  		TypesInfo: info,
   133  	}
   134  	node := f.Decls[1].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt).Rhs[0]
   135  
   136  	if debug.AliasesEnabled() {
   137  		// Check that we can match on the name of the alias
   138  		if ok := m.Match(p1, node); !ok {
   139  			t.Errorf("%s did not match", p1.Root)
   140  		}
   141  	}
   142  
   143  	// Check that we can match on the name of the alias's target
   144  	if ok := m.Match(p2, node); !ok {
   145  		t.Errorf("%s did not match", p2.Root)
   146  	}
   147  }