github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/packages/packagestest/expect.go (about)

     1  // Copyright 2018 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 packagestest
     6  
     7  import (
     8  	"fmt"
     9  	"go/token"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"regexp"
    15  	"strings"
    16  
    17  	"github.com/powerman/golang-tools/go/expect"
    18  	"github.com/powerman/golang-tools/go/packages"
    19  	"github.com/powerman/golang-tools/internal/span"
    20  )
    21  
    22  const (
    23  	markMethod    = "mark"
    24  	eofIdentifier = "EOF"
    25  )
    26  
    27  // Expect invokes the supplied methods for all expectation notes found in
    28  // the exported source files.
    29  //
    30  // All exported go source files are parsed to collect the expectation
    31  // notes.
    32  // See the documentation for expect.Parse for how the notes are collected
    33  // and parsed.
    34  //
    35  // The methods are supplied as a map of name to function, and those functions
    36  // will be matched against the expectations by name.
    37  // Notes with no matching function will be skipped, and functions with no
    38  // matching notes will not be invoked.
    39  // If there are no registered markers yet, a special pass will be run first
    40  // which adds any markers declared with @mark(Name, pattern) or @name. These
    41  // call the Mark method to add the marker to the global set.
    42  // You can register the "mark" method to override these in your own call to
    43  // Expect. The bound Mark function is usable directly in your method map, so
    44  //    exported.Expect(map[string]interface{}{"mark": exported.Mark})
    45  // replicates the built in behavior.
    46  //
    47  // Method invocation
    48  //
    49  // When invoking a method the expressions in the parameter list need to be
    50  // converted to values to be passed to the method.
    51  // There are a very limited set of types the arguments are allowed to be.
    52  //   expect.Note : passed the Note instance being evaluated.
    53  //   string : can be supplied either a string literal or an identifier.
    54  //   int : can only be supplied an integer literal.
    55  //   *regexp.Regexp : can only be supplied a regular expression literal
    56  //   token.Pos : has a file position calculated as described below.
    57  //   token.Position : has a file position calculated as described below.
    58  //   expect.Range: has a start and end position as described below.
    59  //   interface{} : will be passed any value
    60  //
    61  // Position calculation
    62  //
    63  // There is some extra handling when a parameter is being coerced into a
    64  // token.Pos, token.Position or Range type argument.
    65  //
    66  // If the parameter is an identifier, it will be treated as the name of an
    67  // marker to look up (as if markers were global variables).
    68  //
    69  // If it is a string or regular expression, then it will be passed to
    70  // expect.MatchBefore to look up a match in the line at which it was declared.
    71  //
    72  // It is safe to call this repeatedly with different method sets, but it is
    73  // not safe to call it concurrently.
    74  func (e *Exported) Expect(methods map[string]interface{}) error {
    75  	if err := e.getNotes(); err != nil {
    76  		return err
    77  	}
    78  	if err := e.getMarkers(); err != nil {
    79  		return err
    80  	}
    81  	var err error
    82  	ms := make(map[string]method, len(methods))
    83  	for name, f := range methods {
    84  		mi := method{f: reflect.ValueOf(f)}
    85  		mi.converters = make([]converter, mi.f.Type().NumIn())
    86  		for i := 0; i < len(mi.converters); i++ {
    87  			mi.converters[i], err = e.buildConverter(mi.f.Type().In(i))
    88  			if err != nil {
    89  				return fmt.Errorf("invalid method %v: %v", name, err)
    90  			}
    91  		}
    92  		ms[name] = mi
    93  	}
    94  	for _, n := range e.notes {
    95  		if n.Args == nil {
    96  			// simple identifier form, convert to a call to mark
    97  			n = &expect.Note{
    98  				Pos:  n.Pos,
    99  				Name: markMethod,
   100  				Args: []interface{}{n.Name, n.Name},
   101  			}
   102  		}
   103  		mi, ok := ms[n.Name]
   104  		if !ok {
   105  			continue
   106  		}
   107  		params := make([]reflect.Value, len(mi.converters))
   108  		args := n.Args
   109  		for i, convert := range mi.converters {
   110  			params[i], args, err = convert(n, args)
   111  			if err != nil {
   112  				return fmt.Errorf("%v: %v", e.ExpectFileSet.Position(n.Pos), err)
   113  			}
   114  		}
   115  		if len(args) > 0 {
   116  			return fmt.Errorf("%v: unwanted args got %+v extra", e.ExpectFileSet.Position(n.Pos), args)
   117  		}
   118  		//TODO: catch the error returned from the method
   119  		mi.f.Call(params)
   120  	}
   121  	return nil
   122  }
   123  
   124  // Range is a type alias for span.Range for backwards compatibility, prefer
   125  // using span.Range directly.
   126  type Range = span.Range
   127  
   128  // Mark adds a new marker to the known set.
   129  func (e *Exported) Mark(name string, r Range) {
   130  	if e.markers == nil {
   131  		e.markers = make(map[string]span.Range)
   132  	}
   133  	e.markers[name] = r
   134  }
   135  
   136  func (e *Exported) getNotes() error {
   137  	if e.notes != nil {
   138  		return nil
   139  	}
   140  	notes := []*expect.Note{}
   141  	var dirs []string
   142  	for _, module := range e.written {
   143  		for _, filename := range module {
   144  			dirs = append(dirs, filepath.Dir(filename))
   145  		}
   146  	}
   147  	for filename := range e.Config.Overlay {
   148  		dirs = append(dirs, filepath.Dir(filename))
   149  	}
   150  	pkgs, err := packages.Load(e.Config, dirs...)
   151  	if err != nil {
   152  		return fmt.Errorf("unable to load packages for directories %s: %v", dirs, err)
   153  	}
   154  	seen := make(map[token.Position]struct{})
   155  	for _, pkg := range pkgs {
   156  		for _, filename := range pkg.GoFiles {
   157  			content, err := e.FileContents(filename)
   158  			if err != nil {
   159  				return err
   160  			}
   161  			l, err := expect.Parse(e.ExpectFileSet, filename, content)
   162  			if err != nil {
   163  				return fmt.Errorf("failed to extract expectations: %v", err)
   164  			}
   165  			for _, note := range l {
   166  				pos := e.ExpectFileSet.Position(note.Pos)
   167  				if _, ok := seen[pos]; ok {
   168  					continue
   169  				}
   170  				notes = append(notes, note)
   171  				seen[pos] = struct{}{}
   172  			}
   173  		}
   174  	}
   175  	if _, ok := e.written[e.primary]; !ok {
   176  		e.notes = notes
   177  		return nil
   178  	}
   179  	// Check go.mod markers regardless of mode, we need to do this so that our marker count
   180  	// matches the counts in the summary.txt.golden file for the test directory.
   181  	if gomod, found := e.written[e.primary]["go.mod"]; found {
   182  		// If we are in Modules mode, then we need to check the contents of the go.mod.temp.
   183  		if e.Exporter == Modules {
   184  			gomod += ".temp"
   185  		}
   186  		l, err := goModMarkers(e, gomod)
   187  		if err != nil {
   188  			return fmt.Errorf("failed to extract expectations for go.mod: %v", err)
   189  		}
   190  		notes = append(notes, l...)
   191  	}
   192  	e.notes = notes
   193  	return nil
   194  }
   195  
   196  func goModMarkers(e *Exported, gomod string) ([]*expect.Note, error) {
   197  	if _, err := os.Stat(gomod); os.IsNotExist(err) {
   198  		// If there is no go.mod file, we want to be able to continue.
   199  		return nil, nil
   200  	}
   201  	content, err := e.FileContents(gomod)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	if e.Exporter == GOPATH {
   206  		return expect.Parse(e.ExpectFileSet, gomod, content)
   207  	}
   208  	gomod = strings.TrimSuffix(gomod, ".temp")
   209  	// If we are in Modules mode, copy the original contents file back into go.mod
   210  	if err := ioutil.WriteFile(gomod, content, 0644); err != nil {
   211  		return nil, nil
   212  	}
   213  	return expect.Parse(e.ExpectFileSet, gomod, content)
   214  }
   215  
   216  func (e *Exported) getMarkers() error {
   217  	if e.markers != nil {
   218  		return nil
   219  	}
   220  	// set markers early so that we don't call getMarkers again from Expect
   221  	e.markers = make(map[string]span.Range)
   222  	return e.Expect(map[string]interface{}{
   223  		markMethod: e.Mark,
   224  	})
   225  }
   226  
   227  var (
   228  	noteType       = reflect.TypeOf((*expect.Note)(nil))
   229  	identifierType = reflect.TypeOf(expect.Identifier(""))
   230  	posType        = reflect.TypeOf(token.Pos(0))
   231  	positionType   = reflect.TypeOf(token.Position{})
   232  	rangeType      = reflect.TypeOf(span.Range{})
   233  	spanType       = reflect.TypeOf(span.Span{})
   234  	fsetType       = reflect.TypeOf((*token.FileSet)(nil))
   235  	regexType      = reflect.TypeOf((*regexp.Regexp)(nil))
   236  	exportedType   = reflect.TypeOf((*Exported)(nil))
   237  )
   238  
   239  // converter converts from a marker's argument parsed from the comment to
   240  // reflect values passed to the method during Invoke.
   241  // It takes the args remaining, and returns the args it did not consume.
   242  // This allows a converter to consume 0 args for well known types, or multiple
   243  // args for compound types.
   244  type converter func(*expect.Note, []interface{}) (reflect.Value, []interface{}, error)
   245  
   246  // method is used to track information about Invoke methods that is expensive to
   247  // calculate so that we can work it out once rather than per marker.
   248  type method struct {
   249  	f          reflect.Value // the reflect value of the passed in method
   250  	converters []converter   // the parameter converters for the method
   251  }
   252  
   253  // buildConverter works out what function should be used to go from an ast expressions to a reflect
   254  // value of the type expected by a method.
   255  // It is called when only the target type is know, it returns converters that are flexible across
   256  // all supported expression types for that target type.
   257  func (e *Exported) buildConverter(pt reflect.Type) (converter, error) {
   258  	switch {
   259  	case pt == noteType:
   260  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   261  			return reflect.ValueOf(n), args, nil
   262  		}, nil
   263  	case pt == fsetType:
   264  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   265  			return reflect.ValueOf(e.ExpectFileSet), args, nil
   266  		}, nil
   267  	case pt == exportedType:
   268  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   269  			return reflect.ValueOf(e), args, nil
   270  		}, nil
   271  	case pt == posType:
   272  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   273  			r, remains, err := e.rangeConverter(n, args)
   274  			if err != nil {
   275  				return reflect.Value{}, nil, err
   276  			}
   277  			return reflect.ValueOf(r.Start), remains, nil
   278  		}, nil
   279  	case pt == positionType:
   280  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   281  			r, remains, err := e.rangeConverter(n, args)
   282  			if err != nil {
   283  				return reflect.Value{}, nil, err
   284  			}
   285  			return reflect.ValueOf(e.ExpectFileSet.Position(r.Start)), remains, nil
   286  		}, nil
   287  	case pt == rangeType:
   288  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   289  			r, remains, err := e.rangeConverter(n, args)
   290  			if err != nil {
   291  				return reflect.Value{}, nil, err
   292  			}
   293  			return reflect.ValueOf(r), remains, nil
   294  		}, nil
   295  	case pt == spanType:
   296  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   297  			r, remains, err := e.rangeConverter(n, args)
   298  			if err != nil {
   299  				return reflect.Value{}, nil, err
   300  			}
   301  			spn, err := r.Span()
   302  			if err != nil {
   303  				return reflect.Value{}, nil, err
   304  			}
   305  			return reflect.ValueOf(spn), remains, nil
   306  		}, nil
   307  	case pt == identifierType:
   308  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   309  			if len(args) < 1 {
   310  				return reflect.Value{}, nil, fmt.Errorf("missing argument")
   311  			}
   312  			arg := args[0]
   313  			args = args[1:]
   314  			switch arg := arg.(type) {
   315  			case expect.Identifier:
   316  				return reflect.ValueOf(arg), args, nil
   317  			default:
   318  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
   319  			}
   320  		}, nil
   321  
   322  	case pt == regexType:
   323  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   324  			if len(args) < 1 {
   325  				return reflect.Value{}, nil, fmt.Errorf("missing argument")
   326  			}
   327  			arg := args[0]
   328  			args = args[1:]
   329  			if _, ok := arg.(*regexp.Regexp); !ok {
   330  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to *regexp.Regexp", arg)
   331  			}
   332  			return reflect.ValueOf(arg), args, nil
   333  		}, nil
   334  
   335  	case pt.Kind() == reflect.String:
   336  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   337  			if len(args) < 1 {
   338  				return reflect.Value{}, nil, fmt.Errorf("missing argument")
   339  			}
   340  			arg := args[0]
   341  			args = args[1:]
   342  			switch arg := arg.(type) {
   343  			case expect.Identifier:
   344  				return reflect.ValueOf(string(arg)), args, nil
   345  			case string:
   346  				return reflect.ValueOf(arg), args, nil
   347  			default:
   348  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
   349  			}
   350  		}, nil
   351  	case pt.Kind() == reflect.Int64:
   352  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   353  			if len(args) < 1 {
   354  				return reflect.Value{}, nil, fmt.Errorf("missing argument")
   355  			}
   356  			arg := args[0]
   357  			args = args[1:]
   358  			switch arg := arg.(type) {
   359  			case int64:
   360  				return reflect.ValueOf(arg), args, nil
   361  			default:
   362  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to int", arg)
   363  			}
   364  		}, nil
   365  	case pt.Kind() == reflect.Bool:
   366  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   367  			if len(args) < 1 {
   368  				return reflect.Value{}, nil, fmt.Errorf("missing argument")
   369  			}
   370  			arg := args[0]
   371  			args = args[1:]
   372  			b, ok := arg.(bool)
   373  			if !ok {
   374  				return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to bool", arg)
   375  			}
   376  			return reflect.ValueOf(b), args, nil
   377  		}, nil
   378  	case pt.Kind() == reflect.Slice:
   379  		return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   380  			converter, err := e.buildConverter(pt.Elem())
   381  			if err != nil {
   382  				return reflect.Value{}, nil, err
   383  			}
   384  			result := reflect.MakeSlice(reflect.SliceOf(pt.Elem()), 0, len(args))
   385  			for range args {
   386  				value, remains, err := converter(n, args)
   387  				if err != nil {
   388  					return reflect.Value{}, nil, err
   389  				}
   390  				result = reflect.Append(result, value)
   391  				args = remains
   392  			}
   393  			return result, args, nil
   394  		}, nil
   395  	default:
   396  		if pt.Kind() == reflect.Interface && pt.NumMethod() == 0 {
   397  			return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
   398  				if len(args) < 1 {
   399  					return reflect.Value{}, nil, fmt.Errorf("missing argument")
   400  				}
   401  				return reflect.ValueOf(args[0]), args[1:], nil
   402  			}, nil
   403  		}
   404  		return nil, fmt.Errorf("param has unexpected type %v (kind %v)", pt, pt.Kind())
   405  	}
   406  }
   407  
   408  func (e *Exported) rangeConverter(n *expect.Note, args []interface{}) (span.Range, []interface{}, error) {
   409  	if len(args) < 1 {
   410  		return span.Range{}, nil, fmt.Errorf("missing argument")
   411  	}
   412  	arg := args[0]
   413  	args = args[1:]
   414  	switch arg := arg.(type) {
   415  	case expect.Identifier:
   416  		// handle the special identifiers
   417  		switch arg {
   418  		case eofIdentifier:
   419  			// end of file identifier, look up the current file
   420  			f := e.ExpectFileSet.File(n.Pos)
   421  			eof := f.Pos(f.Size())
   422  			return span.Range{FileSet: e.ExpectFileSet, Start: eof, End: token.NoPos}, args, nil
   423  		default:
   424  			// look up an marker by name
   425  			mark, ok := e.markers[string(arg)]
   426  			if !ok {
   427  				return span.Range{}, nil, fmt.Errorf("cannot find marker %v", arg)
   428  			}
   429  			return mark, args, nil
   430  		}
   431  	case string:
   432  		start, end, err := expect.MatchBefore(e.ExpectFileSet, e.FileContents, n.Pos, arg)
   433  		if err != nil {
   434  			return span.Range{}, nil, err
   435  		}
   436  		if start == token.NoPos {
   437  			return span.Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.ExpectFileSet.Position(n.Pos), arg)
   438  		}
   439  		return span.Range{FileSet: e.ExpectFileSet, Start: start, End: end}, args, nil
   440  	case *regexp.Regexp:
   441  		start, end, err := expect.MatchBefore(e.ExpectFileSet, e.FileContents, n.Pos, arg)
   442  		if err != nil {
   443  			return span.Range{}, nil, err
   444  		}
   445  		if start == token.NoPos {
   446  			return span.Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.ExpectFileSet.Position(n.Pos), arg)
   447  		}
   448  		return span.Range{FileSet: e.ExpectFileSet, Start: start, End: end}, args, nil
   449  	default:
   450  		return span.Range{}, nil, fmt.Errorf("cannot convert %v to pos", arg)
   451  	}
   452  }