GoPLS Viewer

Home|gopls/go/packages/packagestest/expect.go
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
5package packagestest
6
7import (
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
21const (
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.
76func (e *ExportedExpect(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]methodlen(methods))
85    for namef := range methods {
86        mi := method{freflect.ValueOf(f)}
87        mi.converters = make([]convertermi.f.Type().NumIn())
88        for i := 0i < 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"nameerr)
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                NamemarkMethod,
102                Args: []interface{}{n.Namen.Name},
103            }
104        }
105        miok := ms[n.Name]
106        if !ok {
107            continue
108        }
109        params := make([]reflect.Valuelen(mi.converters))
110        args := n.Args
111        for iconvert := range mi.converters {
112            params[i], argserr = convert(nargs)
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.
127type Range struct {
128    TokFile    *token.File // non-nil
129    StartEnd 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.
143type rangeSetter interface {
144    SetRange(file *token.Filestartend token.Pos)
145}
146
147// Mark adds a new marker to the known set.
148func (e *ExportedMark(name stringr Range) {
149    if e.markers == nil {
150        e.markers = make(map[string]Range)
151    }
152    e.markers[name] = r
153}
154
155func (e *ExportedgetNotes() 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(dirsfilepath.Dir(filename))
164        }
165    }
166    for filename := range e.Config.Overlay {
167        dirs = append(dirsfilepath.Dir(filename))
168    }
169    pkgserr := packages.Load(e.Configdirs...)
170    if err != nil {
171        return fmt.Errorf("unable to load packages for directories %s: %v"dirserr)
172    }
173    seen := make(map[token.Position]struct{})
174    for _pkg := range pkgs {
175        for _filename := range pkg.GoFiles {
176            contenterr := e.FileContents(filename)
177            if err != nil {
178                return err
179            }
180            lerr := expect.Parse(e.ExpectFileSetfilenamecontent)
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(notesnote)
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 gomodfound := 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        lerr := goModMarkers(egomod)
206        if err != nil {
207            return fmt.Errorf("failed to extract expectations for go.mod: %v"err)
208        }
209        notes = append(notesl...)
210    }
211    e.notes = notes
212    return nil
213}
214
215func goModMarkers(e *Exportedgomod string) ([]*expect.Noteerror) {
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 nilnil
219    }
220    contenterr := e.FileContents(gomod)
221    if err != nil {
222        return nilerr
223    }
224    if e.Exporter == GOPATH {
225        return expect.Parse(e.ExpectFileSetgomodcontent)
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(gomodcontent0644); err != nil {
230        return nilnil
231    }
232    return expect.Parse(e.ExpectFileSetgomodcontent)
233}
234
235func (e *ExportedgetMarkers() 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        markMethode.Mark,
243    })
244}
245
246var (
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.
263type 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.
267type 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.
276func (e *ExportedbuildConverter(pt reflect.Type) (convertererror) {
277    switch {
278    case pt == noteType:
279        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
280            return reflect.ValueOf(n), argsnil
281        }, nil
282    case pt == fsetType:
283        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
284            return reflect.ValueOf(e.ExpectFileSet), argsnil
285        }, nil
286    case pt == exportedType:
287        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
288            return reflect.ValueOf(e), argsnil
289        }, nil
290    case pt == posType:
291        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
292            rremainserr := e.rangeConverter(nargs)
293            if err != nil {
294                return reflect.Value{}, nilerr
295            }
296            return reflect.ValueOf(r.Start), remainsnil
297        }, nil
298    case pt == positionType:
299        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
300            rremainserr := e.rangeConverter(nargs)
301            if err != nil {
302                return reflect.Value{}, nilerr
303            }
304            return reflect.ValueOf(e.ExpectFileSet.Position(r.Start)), remainsnil
305        }, nil
306    case pt == rangeType:
307        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
308            rremainserr := e.rangeConverter(nargs)
309            if err != nil {
310                return reflect.Value{}, nilerr
311            }
312            return reflect.ValueOf(r), remainsnil
313        }, nil
314    case reflect.PtrTo(pt).AssignableTo(rangeSetterType):
315        // (*pt).SetRange method exists: call it.
316        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
317            rremainserr := e.rangeConverter(nargs)
318            if err != nil {
319                return reflect.Value{}, nilerr
320            }
321            v := reflect.New(pt)
322            v.Interface().(rangeSetter).SetRange(r.TokFiler.Startr.End)
323            return v.Elem(), remainsnil
324        }, nil
325    case pt == identifierType:
326        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
327            if len(args) < 1 {
328                return reflect.Value{}, nilfmt.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), argsnil
335            default:
336                return reflect.Value{}, nilfmt.Errorf("cannot convert %v to string"arg)
337            }
338        }, nil
339
340    case pt == regexType:
341        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
342            if len(args) < 1 {
343                return reflect.Value{}, nilfmt.Errorf("missing argument")
344            }
345            arg := args[0]
346            args = args[1:]
347            if _ok := arg.(*regexp.Regexp); !ok {
348                return reflect.Value{}, nilfmt.Errorf("cannot convert %v to *regexp.Regexp"arg)
349            }
350            return reflect.ValueOf(arg), argsnil
351        }, nil
352
353    case pt.Kind() == reflect.String:
354        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
355            if len(args) < 1 {
356                return reflect.Value{}, nilfmt.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)), argsnil
363            case string:
364                return reflect.ValueOf(arg), argsnil
365            default:
366                return reflect.Value{}, nilfmt.Errorf("cannot convert %v to string"arg)
367            }
368        }, nil
369    case pt.Kind() == reflect.Int64:
370        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
371            if len(args) < 1 {
372                return reflect.Value{}, nilfmt.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), argsnil
379            default:
380                return reflect.Value{}, nilfmt.Errorf("cannot convert %v to int"arg)
381            }
382        }, nil
383    case pt.Kind() == reflect.Bool:
384        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
385            if len(args) < 1 {
386                return reflect.Value{}, nilfmt.Errorf("missing argument")
387            }
388            arg := args[0]
389            args = args[1:]
390            bok := arg.(bool)
391            if !ok {
392                return reflect.Value{}, nilfmt.Errorf("cannot convert %v to bool"arg)
393            }
394            return reflect.ValueOf(b), argsnil
395        }, nil
396    case pt.Kind() == reflect.Slice:
397        return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
398            convertererr := e.buildConverter(pt.Elem())
399            if err != nil {
400                return reflect.Value{}, nilerr
401            }
402            result := reflect.MakeSlice(reflect.SliceOf(pt.Elem()), 0len(args))
403            for range args {
404                valueremainserr := converter(nargs)
405                if err != nil {
406                    return reflect.Value{}, nilerr
407                }
408                result = reflect.Append(resultvalue)
409                args = remains
410            }
411            return resultargsnil
412        }, nil
413    default:
414        if pt.Kind() == reflect.Interface && pt.NumMethod() == 0 {
415            return func(n *expect.Noteargs []interface{}) (reflect.Value, []interface{}, error) {
416                if len(args) < 1 {
417                    return reflect.Value{}, nilfmt.Errorf("missing argument")
418                }
419                return reflect.ValueOf(args[0]), args[1:], nil
420            }, nil
421        }
422        return nilfmt.Errorf("param has unexpected type %v (kind %v)"ptpt.Kind())
423    }
424}
425
426func (e *ExportedrangeConverter(n *expect.Noteargs []interface{}) (Range, []interface{}, error) {
427    tokFile := e.ExpectFileSet.File(n.Pos)
428    if len(args) < 1 {
429        return Range{}, nilfmt.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(tokFileeofeof), argsnil
441        default:
442            // look up an marker by name
443            markok := e.markers[string(arg)]
444            if !ok {
445                return Range{}, nilfmt.Errorf("cannot find marker %v"arg)
446            }
447            return markargsnil
448        }
449    case string:
450        startenderr := expect.MatchBefore(e.ExpectFileSete.FileContentsn.Posarg)
451        if err != nil {
452            return Range{}, nilerr
453        }
454        if !start.IsValid() {
455            return Range{}, nilfmt.Errorf("%v: pattern %s did not match"e.ExpectFileSet.Position(n.Pos), arg)
456        }
457        return newRange(tokFilestartend), argsnil
458    case *regexp.Regexp:
459        startenderr := expect.MatchBefore(e.ExpectFileSete.FileContentsn.Posarg)
460        if err != nil {
461            return Range{}, nilerr
462        }
463        if !start.IsValid() {
464            return Range{}, nilfmt.Errorf("%v: pattern %s did not match"e.ExpectFileSet.Position(n.Pos), arg)
465        }
466        return newRange(tokFilestartend), argsnil
467    default:
468        return Range{}, nilfmt.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.
473func newRange(file *token.Filestartend token.PosRange {
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]"startfileBasefileEnd))
484    }
485    if int(end) < fileBase || int(end) > fileEnd {
486        panic(fmt.Sprintf("invalid end: %d not in [%d, %d]"endfileBasefileEnd))
487    }
488    if start > end {
489        panic("invalid start: greater than end")
490    }
491    return Range{
492        TokFilefile,
493        Start:   start,
494        End:     end,
495    }
496}
497
MembersX
Exported.buildConverter.BlockStmt.BlockStmt.RangeStmt_13530.BlockStmt.value
Exported.rangeConverter.BlockStmt.start
newRange.file
regexp
goModMarkers._
Exported.buildConverter
strings
goModMarkers.err
goModMarkers
Exported.buildConverter.pt
Exported.Expect.RangeStmt_3272.n
Exported.Mark.r
Range.Start
Exported.getNotes.dirs
goModMarkers.e
method
Exported.rangeConverter.n
fmt
Exported.Expect
Exported.getNotes.RangeStmt_5263.filename
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.BlockStmt.err
Exported.getNotes.BlockStmt.l
Exported.buildConverter.BlockStmt.BlockStmt.err
ioutil
reflect
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.BlockStmt.l
Exported.buildConverter.BlockStmt.BlockStmt.r
newRange.end
Exported.Expect.methods
Exported.Expect.RangeStmt_2933.BlockStmt.mi
Exported.Expect.RangeStmt_2933.name
Exported.getNotes.pkgs
Exported.getNotes.err
Exported.getNotes.RangeStmt_5545.pkg
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.filename
goModMarkers.content
eofIdentifier
Exported.Expect.e
Exported.rangeConverter.e
Exported.getNotes.RangeStmt_5137.BlockStmt.RangeStmt_5174.filename
converter
method.f
token
Exported.Mark
Exported.buildConverter.BlockStmt.BlockStmt.RangeStmt_13530.BlockStmt.remains
Exported.Expect.RangeStmt_3272.BlockStmt.args
Range
Exported.getNotes
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.BlockStmt.RangeStmt_5850.BlockStmt.pos
Exported.getNotes.BlockStmt.err
Exported.getMarkers.e
expect
Exported.Expect.RangeStmt_3272.BlockStmt.params
Exported.rangeConverter.tokFile
newRange.fileBase
method.converters
Exported.buildConverter.BlockStmt.BlockStmt.result
Exported.Expect.err
Exported.Expect.ms
Exported.Expect.RangeStmt_2933.BlockStmt.i
Exported.Expect.RangeStmt_3272.BlockStmt.RangeStmt_3610.convert
Range.TokFile
Range.End
os
markMethod
Exported.rangeConverter.BlockStmt.BlockStmt.eof
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.BlockStmt.content
goModMarkers.gomod
Exported.buildConverter.BlockStmt.BlockStmt.remains
Exported.rangeConverter.BlockStmt.err
newRange
packages
Exported.Expect.RangeStmt_2933.f
Exported.buildConverter.BlockStmt.BlockStmt.converter
Exported.buildConverter.BlockStmt.BlockStmt.RangeStmt_13530.BlockStmt.err
Exported.rangeConverter.BlockStmt.end
newRange.start
Exported.getNotes.seen
Exported.getNotes.RangeStmt_5545.BlockStmt.RangeStmt_5574.BlockStmt.RangeStmt_5850.note
Exported.getMarkers
Exported.buildConverter.e
Exported.buildConverter.BlockStmt.BlockStmt.v
rangeSetter
Exported.getNotes.notes
Exported.Mark.e
Exported.getNotes.RangeStmt_5137.module
filepath
Exported.Expect.RangeStmt_3272.BlockStmt.RangeStmt_3610.i
Exported.rangeConverter
Exported.rangeConverter.args
Exported.Mark.name
Exported.getNotes.e
Members
X