Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / cmd / guru / guru_test.go
1 // Copyright 2013 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 main_test
6
7 // This file defines a test framework for guru queries.
8 //
9 // The files beneath testdata/src contain Go programs containing
10 // query annotations of the form:
11 //
12 //   @verb id "select"
13 //
14 // where verb is the query mode (e.g. "callers"), id is a unique name
15 // for this query, and "select" is a regular expression matching the
16 // substring of the current line that is the query's input selection.
17 //
18 // The expected output for each query is provided in the accompanying
19 // .golden file.
20 //
21 // (Location information is not included because it's too fragile to
22 // display as text.  TODO(adonovan): think about how we can test its
23 // correctness, since it is critical information.)
24 //
25 // Run this test with:
26 //      % go test golang.org/x/tools/cmd/guru -update
27 // to update the golden files.
28
29 import (
30         "bytes"
31         "flag"
32         "fmt"
33         "go/build"
34         "go/parser"
35         "go/token"
36         "io"
37         "io/ioutil"
38         "log"
39         "os"
40         "os/exec"
41         "path/filepath"
42         "regexp"
43         "runtime"
44         "sort"
45         "strconv"
46         "strings"
47         "sync"
48         "testing"
49
50         guru "golang.org/x/tools/cmd/guru"
51         "golang.org/x/tools/internal/testenv"
52 )
53
54 func init() {
55         // This test currently requires GOPATH mode.
56         // Explicitly disabling module mode should suffix, but
57         // we'll also turn off GOPROXY just for good measure.
58         if err := os.Setenv("GO111MODULE", "off"); err != nil {
59                 log.Fatal(err)
60         }
61         if err := os.Setenv("GOPROXY", "off"); err != nil {
62                 log.Fatal(err)
63         }
64 }
65
66 var updateFlag = flag.Bool("update", false, "Update the golden files.")
67
68 type query struct {
69         id       string         // unique id
70         verb     string         // query mode, e.g. "callees"
71         posn     token.Position // query position
72         filename string
73         queryPos string // query position in command-line syntax
74 }
75
76 func parseRegexp(text string) (*regexp.Regexp, error) {
77         pattern, err := strconv.Unquote(text)
78         if err != nil {
79                 return nil, fmt.Errorf("can't unquote %s", text)
80         }
81         return regexp.Compile(pattern)
82 }
83
84 // parseQueries parses and returns the queries in the named file.
85 func parseQueries(t *testing.T, filename string) []*query {
86         filedata, err := ioutil.ReadFile(filename)
87         if err != nil {
88                 t.Fatal(err)
89         }
90
91         // Parse the file once to discover the test queries.
92         fset := token.NewFileSet()
93         f, err := parser.ParseFile(fset, filename, filedata, parser.ParseComments)
94         if err != nil {
95                 t.Fatal(err)
96         }
97
98         lines := bytes.Split(filedata, []byte("\n"))
99
100         var queries []*query
101         queriesById := make(map[string]*query)
102
103         // Find all annotations of these forms:
104         expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
105         for _, c := range f.Comments {
106                 text := strings.TrimSpace(c.Text())
107                 if text == "" || text[0] != '@' {
108                         continue
109                 }
110                 posn := fset.Position(c.Pos())
111
112                 // @verb id "regexp"
113                 match := expectRe.FindStringSubmatch(text)
114                 if match == nil {
115                         t.Errorf("%s: ill-formed query: %s", posn, text)
116                         continue
117                 }
118
119                 id := match[2]
120                 if prev, ok := queriesById[id]; ok {
121                         t.Errorf("%s: duplicate id %s", posn, id)
122                         t.Errorf("%s: previously used here", prev.posn)
123                         continue
124                 }
125
126                 q := &query{
127                         id:       id,
128                         verb:     match[1],
129                         filename: filename,
130                         posn:     posn,
131                 }
132
133                 if match[3] != `"nopos"` {
134                         selectRe, err := parseRegexp(match[3])
135                         if err != nil {
136                                 t.Errorf("%s: %s", posn, err)
137                                 continue
138                         }
139
140                         // Find text of the current line, sans query.
141                         // (Queries must be // not /**/ comments.)
142                         line := lines[posn.Line-1][:posn.Column-1]
143
144                         // Apply regexp to current line to find input selection.
145                         loc := selectRe.FindIndex(line)
146                         if loc == nil {
147                                 t.Errorf("%s: selection pattern %s doesn't match line %q",
148                                         posn, match[3], string(line))
149                                 continue
150                         }
151
152                         // Assumes ASCII. TODO(adonovan): test on UTF-8.
153                         linestart := posn.Offset - (posn.Column - 1)
154
155                         // Compute the file offsets.
156                         q.queryPos = fmt.Sprintf("%s:#%d,#%d",
157                                 filename, linestart+loc[0], linestart+loc[1])
158                 }
159
160                 queries = append(queries, q)
161                 queriesById[id] = q
162         }
163
164         // Return the slice, not map, for deterministic iteration.
165         return queries
166 }
167
168 // doQuery poses query q to the guru and writes its response and
169 // error (if any) to out.
170 func doQuery(out io.Writer, q *query, json bool) {
171         fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)
172
173         var buildContext = build.Default
174         buildContext.GOPATH = "testdata"
175         pkg := filepath.Dir(strings.TrimPrefix(q.filename, "testdata/src/"))
176
177         gopathAbs, _ := filepath.Abs(buildContext.GOPATH)
178
179         var outputMu sync.Mutex // guards outputs
180         var outputs []string    // JSON objects or lines of text
181         outputFn := func(fset *token.FileSet, qr guru.QueryResult) {
182                 outputMu.Lock()
183                 defer outputMu.Unlock()
184                 if json {
185                         jsonstr := string(qr.JSON(fset))
186                         // Sanitize any absolute filenames that creep in.
187                         jsonstr = strings.Replace(jsonstr, gopathAbs, "$GOPATH", -1)
188                         outputs = append(outputs, jsonstr)
189                 } else {
190                         // suppress position information
191                         qr.PrintPlain(func(_ interface{}, format string, args ...interface{}) {
192                                 outputs = append(outputs, fmt.Sprintf(format, args...))
193                         })
194                 }
195         }
196
197         query := guru.Query{
198                 Pos:        q.queryPos,
199                 Build:      &buildContext,
200                 Scope:      []string{pkg},
201                 Reflection: true,
202                 Output:     outputFn,
203         }
204
205         if err := guru.Run(q.verb, &query); err != nil {
206                 fmt.Fprintf(out, "\nError: %s\n", err)
207                 return
208         }
209
210         // In a "referrers" query, references are sorted within each
211         // package but packages are visited in arbitrary order,
212         // so for determinism we sort them.  Line 0 is a caption.
213         if q.verb == "referrers" {
214                 sort.Strings(outputs[1:])
215         }
216
217         for _, output := range outputs {
218                 fmt.Fprintf(out, "%s\n", output)
219         }
220
221         if !json {
222                 io.WriteString(out, "\n")
223         }
224 }
225
226 func TestGuru(t *testing.T) {
227         if testing.Short() {
228                 // These tests are super slow.
229                 // TODO: make a lighter version of the tests for short mode?
230                 t.Skipf("skipping in short mode")
231         }
232         switch runtime.GOOS {
233         case "android":
234                 t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS)
235         case "windows":
236                 t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
237         }
238
239         for _, filename := range []string{
240                 "testdata/src/alias/alias.go",
241                 "testdata/src/calls/main.go",
242                 "testdata/src/describe/main.go",
243                 "testdata/src/freevars/main.go",
244                 "testdata/src/implements/main.go",
245                 "testdata/src/implements-methods/main.go",
246                 "testdata/src/imports/main.go",
247                 "testdata/src/peers/main.go",
248                 "testdata/src/pointsto/main.go",
249                 "testdata/src/referrers/main.go",
250                 "testdata/src/reflection/main.go",
251                 "testdata/src/what/main.go",
252                 "testdata/src/whicherrs/main.go",
253                 "testdata/src/softerrs/main.go",
254                 // JSON:
255                 // TODO(adonovan): most of these are very similar; combine them.
256                 "testdata/src/calls-json/main.go",
257                 "testdata/src/peers-json/main.go",
258                 "testdata/src/definition-json/main.go",
259                 "testdata/src/describe-json/main.go",
260                 "testdata/src/implements-json/main.go",
261                 "testdata/src/implements-methods-json/main.go",
262                 "testdata/src/pointsto-json/main.go",
263                 "testdata/src/referrers-json/main.go",
264                 "testdata/src/what-json/main.go",
265         } {
266                 filename := filename
267                 name := strings.Split(filename, "/")[2]
268                 t.Run(name, func(t *testing.T) {
269                         t.Parallel()
270                         if filename == "testdata/src/referrers/main.go" && runtime.GOOS == "plan9" {
271                                 // Disable this test on plan9 since it expects a particular
272                                 // wording for a "no such file or directory" error.
273                                 t.Skip()
274                         }
275                         json := strings.Contains(filename, "-json/")
276                         queries := parseQueries(t, filename)
277                         golden := filename + "lden"
278                         gotfh, err := ioutil.TempFile("", filepath.Base(filename)+"t")
279                         if err != nil {
280                                 t.Fatal(err)
281                         }
282                         got := gotfh.Name()
283                         defer func() {
284                                 gotfh.Close()
285                                 os.Remove(got)
286                         }()
287
288                         // Run the guru on each query, redirecting its output
289                         // and error (if any) to the foo.got file.
290                         for _, q := range queries {
291                                 doQuery(gotfh, q, json)
292                         }
293
294                         // Compare foo.got with foo.golden.
295                         var cmd *exec.Cmd
296                         switch runtime.GOOS {
297                         case "plan9":
298                                 cmd = exec.Command("/bin/diff", "-c", golden, got)
299                         default:
300                                 cmd = exec.Command("/usr/bin/diff", "-u", golden, got)
301                         }
302                         testenv.NeedsTool(t, cmd.Path)
303                         buf := new(bytes.Buffer)
304                         cmd.Stdout = buf
305                         cmd.Stderr = os.Stderr
306                         if err := cmd.Run(); err != nil {
307                                 t.Errorf("Guru tests for %s failed: %s.\n%s\n",
308                                         filename, err, buf)
309
310                                 if *updateFlag {
311                                         t.Logf("Updating %s...", golden)
312                                         if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
313                                                 t.Errorf("Update failed: %s", err)
314                                         }
315                                 }
316                         }
317                 })
318         }
319 }
320
321 func TestIssue14684(t *testing.T) {
322         var buildContext = build.Default
323         buildContext.GOPATH = "testdata"
324         query := guru.Query{
325                 Pos:   "testdata/src/README.txt:#1",
326                 Build: &buildContext,
327         }
328         err := guru.Run("freevars", &query)
329         if err == nil {
330                 t.Fatal("guru query succeeded unexpectedly")
331         }
332         if got, want := err.Error(), "testdata/src/README.txt is not a Go source file"; got != want {
333                 t.Errorf("query error was %q, want %q", got, want)
334         }
335 }