github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/oracle_test.go (about) 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 oracle_test 6 7 // This file defines a test framework for oracle queries. 8 // 9 // The files beneath testdata/src/main 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/oracle -update 27 // to update the golden files. 28 29 import ( 30 "bytes" 31 "encoding/json" 32 "flag" 33 "fmt" 34 "go/build" 35 "go/parser" 36 "go/token" 37 "io" 38 "io/ioutil" 39 "os" 40 "os/exec" 41 "regexp" 42 "runtime" 43 "strconv" 44 "strings" 45 "testing" 46 47 "golang.org/x/tools/oracle" 48 ) 49 50 var updateFlag = flag.Bool("update", false, "Update the golden files.") 51 52 type query struct { 53 id string // unique id 54 verb string // query mode, e.g. "callees" 55 posn token.Position // position of of query 56 filename string 57 queryPos string // value of -pos flag 58 } 59 60 func parseRegexp(text string) (*regexp.Regexp, error) { 61 pattern, err := strconv.Unquote(text) 62 if err != nil { 63 return nil, fmt.Errorf("can't unquote %s", text) 64 } 65 return regexp.Compile(pattern) 66 } 67 68 // parseQueries parses and returns the queries in the named file. 69 func parseQueries(t *testing.T, filename string) []*query { 70 filedata, err := ioutil.ReadFile(filename) 71 if err != nil { 72 t.Fatal(err) 73 } 74 75 // Parse the file once to discover the test queries. 76 fset := token.NewFileSet() 77 f, err := parser.ParseFile(fset, filename, filedata, parser.ParseComments) 78 if err != nil { 79 t.Fatal(err) 80 } 81 82 lines := bytes.Split(filedata, []byte("\n")) 83 84 var queries []*query 85 queriesById := make(map[string]*query) 86 87 // Find all annotations of these forms: 88 expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp" 89 for _, c := range f.Comments { 90 text := strings.TrimSpace(c.Text()) 91 if text == "" || text[0] != '@' { 92 continue 93 } 94 posn := fset.Position(c.Pos()) 95 96 // @verb id "regexp" 97 match := expectRe.FindStringSubmatch(text) 98 if match == nil { 99 t.Errorf("%s: ill-formed query: %s", posn, text) 100 continue 101 } 102 103 id := match[2] 104 if prev, ok := queriesById[id]; ok { 105 t.Errorf("%s: duplicate id %s", posn, id) 106 t.Errorf("%s: previously used here", prev.posn) 107 continue 108 } 109 110 q := &query{ 111 id: id, 112 verb: match[1], 113 filename: filename, 114 posn: posn, 115 } 116 117 if match[3] != `"nopos"` { 118 selectRe, err := parseRegexp(match[3]) 119 if err != nil { 120 t.Errorf("%s: %s", posn, err) 121 continue 122 } 123 124 // Find text of the current line, sans query. 125 // (Queries must be // not /**/ comments.) 126 line := lines[posn.Line-1][:posn.Column-1] 127 128 // Apply regexp to current line to find input selection. 129 loc := selectRe.FindIndex(line) 130 if loc == nil { 131 t.Errorf("%s: selection pattern %s doesn't match line %q", 132 posn, match[3], string(line)) 133 continue 134 } 135 136 // Assumes ASCII. TODO(adonovan): test on UTF-8. 137 linestart := posn.Offset - (posn.Column - 1) 138 139 // Compute the file offsets. 140 q.queryPos = fmt.Sprintf("%s:#%d,#%d", 141 filename, linestart+loc[0], linestart+loc[1]) 142 } 143 144 queries = append(queries, q) 145 queriesById[id] = q 146 } 147 148 // Return the slice, not map, for deterministic iteration. 149 return queries 150 } 151 152 // WriteResult writes res (-format=plain) to w, stripping file locations. 153 func WriteResult(w io.Writer, q *oracle.Query) { 154 capture := new(bytes.Buffer) // capture standard output 155 q.WriteTo(capture) 156 for _, line := range strings.Split(capture.String(), "\n") { 157 // Remove a "file:line: " prefix. 158 if i := strings.Index(line, ": "); i >= 0 { 159 line = line[i+2:] 160 } 161 fmt.Fprintf(w, "%s\n", line) 162 } 163 } 164 165 // doQuery poses query q to the oracle and writes its response and 166 // error (if any) to out. 167 func doQuery(out io.Writer, q *query, useJson bool) { 168 fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id) 169 170 var buildContext = build.Default 171 buildContext.GOPATH = "testdata" 172 query := oracle.Query{ 173 Mode: q.verb, 174 Pos: q.queryPos, 175 Build: &buildContext, 176 Scope: []string{q.filename}, 177 Reflection: true, 178 } 179 if err := oracle.Run(&query); err != nil { 180 fmt.Fprintf(out, "\nError: %s\n", err) 181 return 182 } 183 184 if useJson { 185 // JSON output 186 b, err := json.MarshalIndent(query.Serial(), "", "\t") 187 if err != nil { 188 fmt.Fprintf(out, "JSON error: %s\n", err.Error()) 189 return 190 } 191 out.Write(b) 192 fmt.Fprintln(out) 193 } else { 194 // "plain" (compiler diagnostic format) output 195 WriteResult(out, &query) 196 } 197 } 198 199 func TestOracle(t *testing.T) { 200 switch runtime.GOOS { 201 case "android": 202 t.Skipf("skipping test on %q (no testdata dir)", runtime.GOOS) 203 case "windows": 204 t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS) 205 } 206 207 for _, filename := range []string{ 208 "testdata/src/calls/main.go", 209 "testdata/src/describe/main.go", 210 "testdata/src/freevars/main.go", 211 "testdata/src/implements/main.go", 212 "testdata/src/implements-methods/main.go", 213 "testdata/src/imports/main.go", 214 "testdata/src/peers/main.go", 215 "testdata/src/pointsto/main.go", 216 "testdata/src/referrers/main.go", 217 "testdata/src/reflection/main.go", 218 "testdata/src/what/main.go", 219 "testdata/src/whicherrs/main.go", 220 // JSON: 221 // TODO(adonovan): most of these are very similar; combine them. 222 "testdata/src/calls-json/main.go", 223 "testdata/src/peers-json/main.go", 224 "testdata/src/describe-json/main.go", 225 "testdata/src/implements-json/main.go", 226 "testdata/src/implements-methods-json/main.go", 227 "testdata/src/pointsto-json/main.go", 228 "testdata/src/referrers-json/main.go", 229 "testdata/src/what-json/main.go", 230 } { 231 useJson := strings.Contains(filename, "-json/") 232 queries := parseQueries(t, filename) 233 golden := filename + "lden" 234 got := filename + "t" 235 gotfh, err := os.Create(got) 236 if err != nil { 237 t.Errorf("Create(%s) failed: %s", got, err) 238 continue 239 } 240 defer gotfh.Close() 241 defer os.Remove(got) 242 243 // Run the oracle on each query, redirecting its output 244 // and error (if any) to the foo.got file. 245 for _, q := range queries { 246 doQuery(gotfh, q, useJson) 247 } 248 249 // Compare foo.got with foo.golden. 250 var cmd *exec.Cmd 251 switch runtime.GOOS { 252 case "plan9": 253 cmd = exec.Command("/bin/diff", "-c", golden, got) 254 default: 255 cmd = exec.Command("/usr/bin/diff", "-u", golden, got) 256 } 257 buf := new(bytes.Buffer) 258 cmd.Stdout = buf 259 cmd.Stderr = os.Stderr 260 if err := cmd.Run(); err != nil { 261 t.Errorf("Oracle tests for %s failed: %s.\n%s\n", 262 filename, err, buf) 263 264 if *updateFlag { 265 t.Logf("Updating %s...", golden) 266 if err := exec.Command("/bin/cp", got, golden).Run(); err != nil { 267 t.Errorf("Update failed: %s", err) 268 } 269 } 270 } 271 } 272 }