...

Source file src/golang.org/x/tools/go/packages/packagestest/expect.go

Documentation: golang.org/x/tools/go/packages/packagestest

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

View as plain text