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 | // No testdata on Android. |
6 | |
7 | //go:build !android |
8 | // +build !android |
9 | |
10 | package pointer_test |
11 | |
12 | // This test uses 'expectation' comments embedded within testdata/*.go |
13 | // files to specify the expected pointer analysis behaviour. |
14 | // See below for grammar. |
15 | |
16 | import ( |
17 | "bytes" |
18 | "errors" |
19 | "fmt" |
20 | "go/token" |
21 | "go/types" |
22 | "io/ioutil" |
23 | "os" |
24 | "path/filepath" |
25 | "regexp" |
26 | "strconv" |
27 | "strings" |
28 | "testing" |
29 | "unsafe" |
30 | |
31 | "golang.org/x/tools/go/callgraph" |
32 | "golang.org/x/tools/go/packages" |
33 | "golang.org/x/tools/go/pointer" |
34 | "golang.org/x/tools/go/ssa" |
35 | "golang.org/x/tools/go/ssa/ssautil" |
36 | "golang.org/x/tools/go/types/typeutil" |
37 | "golang.org/x/tools/internal/typeparams" |
38 | ) |
39 | |
40 | var inputs = []string{ |
41 | "testdata/a_test.go", |
42 | "testdata/another.go", |
43 | "testdata/arrayreflect.go", |
44 | "testdata/arrays.go", |
45 | "testdata/channels.go", |
46 | "testdata/chanreflect.go", |
47 | "testdata/context.go", |
48 | "testdata/conv.go", |
49 | "testdata/extended.go", |
50 | "testdata/finalizer.go", |
51 | "testdata/flow.go", |
52 | "testdata/fmtexcerpt.go", |
53 | "testdata/func.go", |
54 | "testdata/funcreflect.go", |
55 | "testdata/hello.go", // NB: causes spurious failure of HVN cross-check |
56 | "testdata/interfaces.go", |
57 | "testdata/issue9002.go", |
58 | "testdata/mapreflect.go", |
59 | "testdata/maps.go", |
60 | "testdata/panic.go", |
61 | "testdata/recur.go", |
62 | "testdata/reflect.go", |
63 | "testdata/rtti.go", |
64 | "testdata/structreflect.go", |
65 | "testdata/structs.go", |
66 | // "testdata/timer.go", // TODO(adonovan): fix broken assumptions about runtime timers |
67 | } |
68 | |
69 | var raceEnabled = false |
70 | |
71 | // Expectation grammar: |
72 | // |
73 | // @calls f -> g |
74 | // |
75 | // A 'calls' expectation asserts that edge (f, g) appears in the |
76 | // callgraph. f and g are notated as per Function.String(), which |
77 | // may contain spaces (e.g. promoted method in anon struct). |
78 | // |
79 | // @pointsto a | b | c |
80 | // |
81 | // A 'pointsto' expectation asserts that the points-to set of its |
82 | // operand contains exactly the set of labels {a,b,c} notated as per |
83 | // labelString. |
84 | // |
85 | // A 'pointsto' expectation must appear on the same line as a |
86 | // print(x) statement; the expectation's operand is x. |
87 | // |
88 | // If one of the strings is "...", the expectation asserts that the |
89 | // points-to set at least the other labels. |
90 | // |
91 | // We use '|' because label names may contain spaces, e.g. methods |
92 | // of anonymous structs. |
93 | // |
94 | // Assertions within generic functions are treated as a union of all |
95 | // of the instantiations. |
96 | // |
97 | // From a theoretical perspective, concrete types in interfaces are |
98 | // labels too, but they are represented differently and so have a |
99 | // different expectation, @types, below. |
100 | // |
101 | // @types t | u | v |
102 | // |
103 | // A 'types' expectation asserts that the set of possible dynamic |
104 | // types of its interface operand is exactly {t,u,v}, notated per |
105 | // go/types.Type.String(). In other words, it asserts that the type |
106 | // component of the interface may point to that set of concrete type |
107 | // literals. It also works for reflect.Value, though the types |
108 | // needn't be concrete in that case. |
109 | // |
110 | // A 'types' expectation must appear on the same line as a |
111 | // print(x) statement; the expectation's operand is x. |
112 | // |
113 | // If one of the strings is "...", the expectation asserts that the |
114 | // interface's type may point to at least the other types. |
115 | // |
116 | // We use '|' because type names may contain spaces. |
117 | // |
118 | // Assertions within generic functions are treated as a union of all |
119 | // of the instantiations. |
120 | // |
121 | // @warning "regexp" |
122 | // |
123 | // A 'warning' expectation asserts that the analysis issues a |
124 | // warning that matches the regular expression within the string |
125 | // literal. |
126 | // |
127 | // @line id |
128 | // |
129 | // A line directive associates the name "id" with the current |
130 | // file:line. The string form of labels will use this id instead of |
131 | // a file:line, making @pointsto expectations more robust against |
132 | // perturbations in the source file. |
133 | // (NB, anon functions still include line numbers.) |
134 | type expectation struct { |
135 | kind string // "pointsto" | "pointstoquery" | "types" | "calls" | "warning" |
136 | filepath string |
137 | linenum int // source line number, 1-based |
138 | args []string |
139 | query string // extended query |
140 | extended []*pointer.Pointer // extended query pointer [per instantiation] |
141 | types []types.Type // for types |
142 | } |
143 | |
144 | func (e *expectation) String() string { |
145 | return fmt.Sprintf("@%s[%s]", e.kind, strings.Join(e.args, " | ")) |
146 | } |
147 | |
148 | func (e *expectation) errorf(format string, args ...interface{}) { |
149 | fmt.Printf("%s:%d: ", e.filepath, e.linenum) |
150 | fmt.Printf(format, args...) |
151 | fmt.Println() |
152 | } |
153 | |
154 | func (e *expectation) needsProbe() bool { |
155 | return e.kind == "pointsto" || e.kind == "pointstoquery" || e.kind == "types" |
156 | } |
157 | |
158 | // Find probes (call to print(x)) of same source file/line as expectation. |
159 | // |
160 | // May match multiple calls for different instantiations. |
161 | func findProbes(prog *ssa.Program, probes map[*ssa.CallCommon]bool, e *expectation) []*ssa.CallCommon { |
162 | var calls []*ssa.CallCommon |
163 | for call := range probes { |
164 | pos := prog.Fset.Position(call.Pos()) |
165 | if pos.Line == e.linenum && pos.Filename == e.filepath { |
166 | // TODO(adonovan): send this to test log (display only on failure). |
167 | // fmt.Printf("%s:%d: info: found probe for %s: %s\n", |
168 | // e.filepath, e.linenum, e, p.arg0) // debugging |
169 | calls = append(calls, call) |
170 | } |
171 | } |
172 | return calls |
173 | } |
174 | |
175 | // Find points to sets of probes (call to print(x)). |
176 | func probesPointTo(calls []*ssa.CallCommon, queries map[ssa.Value]pointer.Pointer) []pointer.PointsToSet { |
177 | ptss := make([]pointer.PointsToSet, len(calls)) |
178 | for i, call := range calls { |
179 | ptss[i] = queries[call.Args[0]].PointsTo() |
180 | } |
181 | return ptss |
182 | } |
183 | |
184 | // Find the types of the probes (call to print(x)). |
185 | // Returns an error if type of the probe cannot point. |
186 | func probesPointToTypes(calls []*ssa.CallCommon) ([]types.Type, error) { |
187 | tProbes := make([]types.Type, len(calls)) |
188 | for i, call := range calls { |
189 | tProbes[i] = call.Args[0].Type() |
190 | if !pointer.CanPoint(tProbes[i]) { |
191 | return nil, fmt.Errorf("expectation on non-pointerlike operand: %s", tProbes[i]) |
192 | } |
193 | } |
194 | return tProbes, nil |
195 | } |
196 | |
197 | func doOneInput(t *testing.T, input, fpath string) bool { |
198 | cfg := &packages.Config{ |
199 | Mode: packages.LoadAllSyntax, |
200 | Tests: true, |
201 | } |
202 | pkgs, err := packages.Load(cfg, fpath) |
203 | if err != nil { |
204 | fmt.Println(err) |
205 | return false |
206 | } |
207 | if packages.PrintErrors(pkgs) > 0 { |
208 | fmt.Println("loaded packages have errors") |
209 | return false |
210 | } |
211 | |
212 | // SSA creation + building. |
213 | mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics |
214 | prog, ssaPkgs := ssautil.AllPackages(pkgs, mode) |
215 | prog.Build() |
216 | |
217 | // main underlying packages.Package. |
218 | mainPpkg := pkgs[0] |
219 | mainpkg := ssaPkgs[0] |
220 | ptrmain := mainpkg // main package for the pointer analysis |
221 | if mainpkg.Func("main") == nil { |
222 | // For test programs without main, such as testdata/a_test.go, |
223 | // the package with the original code is "main [main.test]" and |
224 | // the package with the main is "main.test". |
225 | for i, pkg := range pkgs { |
226 | if pkg.ID == mainPpkg.ID+".test" { |
227 | ptrmain = ssaPkgs[i] |
228 | } else if pkg.ID == fmt.Sprintf("%s [%s.test]", mainPpkg.ID, mainPpkg.ID) { |
229 | mainpkg = ssaPkgs[i] |
230 | } |
231 | } |
232 | } |
233 | |
234 | // files in mainPpkg. |
235 | mainFiles := make(map[*token.File]bool) |
236 | for _, syn := range mainPpkg.Syntax { |
237 | mainFiles[prog.Fset.File(syn.Pos())] = true |
238 | } |
239 | |
240 | // Find all calls to the built-in print(x). Analytically, |
241 | // print is a no-op, but it's a convenient hook for testing |
242 | // the PTS of an expression, so our tests use it. |
243 | // Exclude generic bodies as these should be dead code for pointer. |
244 | // Instance of generics are included. |
245 | probes := make(map[*ssa.CallCommon]bool) |
246 | for fn := range ssautil.AllFunctions(prog) { |
247 | if isGenericBody(fn) { |
248 | continue // skip generic bodies |
249 | } |
250 | // TODO(taking): Switch to a more principled check like fn.declaredPackage() == mainPkg if Origin is exported. |
251 | if fn.Pkg == mainpkg || (fn.Pkg == nil && mainFiles[prog.Fset.File(fn.Pos())]) { |
252 | for _, b := range fn.Blocks { |
253 | for _, instr := range b.Instrs { |
254 | if instr, ok := instr.(ssa.CallInstruction); ok { |
255 | call := instr.Common() |
256 | if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" && len(call.Args) == 1 { |
257 | probes[instr.Common()] = true |
258 | } |
259 | } |
260 | } |
261 | } |
262 | } |
263 | } |
264 | |
265 | ok := true |
266 | |
267 | lineMapping := make(map[string]string) // maps "file:line" to @line tag |
268 | |
269 | // Parse expectations in this input. |
270 | var exps []*expectation |
271 | re := regexp.MustCompile("// *@([a-z]*) *(.*)$") |
272 | lines := strings.Split(input, "\n") |
273 | for linenum, line := range lines { |
274 | linenum++ // make it 1-based |
275 | if matches := re.FindAllStringSubmatch(line, -1); matches != nil { |
276 | match := matches[0] |
277 | kind, rest := match[1], match[2] |
278 | e := &expectation{kind: kind, filepath: fpath, linenum: linenum} |
279 | |
280 | if kind == "line" { |
281 | if rest == "" { |
282 | ok = false |
283 | e.errorf("@%s expectation requires identifier", kind) |
284 | } else { |
285 | lineMapping[fmt.Sprintf("%s:%d", fpath, linenum)] = rest |
286 | } |
287 | continue |
288 | } |
289 | |
290 | if e.needsProbe() && !strings.Contains(line, "print(") { |
291 | ok = false |
292 | e.errorf("@%s expectation must follow call to print(x)", kind) |
293 | continue |
294 | } |
295 | |
296 | switch kind { |
297 | case "pointsto": |
298 | e.args = split(rest, "|") |
299 | |
300 | case "pointstoquery": |
301 | args := strings.SplitN(rest, " ", 2) |
302 | e.query = args[0] |
303 | e.args = split(args[1], "|") |
304 | case "types": |
305 | for _, typstr := range split(rest, "|") { |
306 | var t types.Type = types.Typ[types.Invalid] // means "..." |
307 | if typstr != "..." { |
308 | tv, err := types.Eval(prog.Fset, mainpkg.Pkg, mainPpkg.Syntax[0].Pos(), typstr) |
309 | if err != nil { |
310 | ok = false |
311 | // Don't print err since its location is bad. |
312 | e.errorf("'%s' is not a valid type: %s", typstr, err) |
313 | continue |
314 | } |
315 | t = tv.Type |
316 | } |
317 | e.types = append(e.types, t) |
318 | } |
319 | |
320 | case "calls": |
321 | e.args = split(rest, "->") |
322 | // TODO(adonovan): eagerly reject the |
323 | // expectation if fn doesn't denote |
324 | // existing function, rather than fail |
325 | // the expectation after analysis. |
326 | if len(e.args) != 2 { |
327 | ok = false |
328 | e.errorf("@calls expectation wants 'caller -> callee' arguments") |
329 | continue |
330 | } |
331 | |
332 | case "warning": |
333 | lit, err := strconv.Unquote(strings.TrimSpace(rest)) |
334 | if err != nil { |
335 | ok = false |
336 | e.errorf("couldn't parse @warning operand: %s", err.Error()) |
337 | continue |
338 | } |
339 | e.args = append(e.args, lit) |
340 | |
341 | default: |
342 | ok = false |
343 | e.errorf("unknown expectation kind: %s", e) |
344 | continue |
345 | } |
346 | exps = append(exps, e) |
347 | } |
348 | } |
349 | |
350 | var log bytes.Buffer |
351 | fmt.Fprintf(&log, "Input: %s\n", fpath) |
352 | |
353 | // Run the analysis. |
354 | config := &pointer.Config{ |
355 | Reflection: true, |
356 | BuildCallGraph: true, |
357 | Mains: []*ssa.Package{ptrmain}, |
358 | Log: &log, |
359 | } |
360 | for probe := range probes { |
361 | v := probe.Args[0] |
362 | pos := prog.Fset.Position(probe.Pos()) |
363 | for _, e := range exps { |
364 | if e.linenum == pos.Line && e.filepath == pos.Filename && e.kind == "pointstoquery" { |
365 | extended, err := config.AddExtendedQuery(v, e.query) |
366 | if err != nil { |
367 | panic(err) |
368 | } |
369 | e.extended = append(e.extended, extended) |
370 | } |
371 | } |
372 | if pointer.CanPoint(v.Type()) { |
373 | config.AddQuery(v) |
374 | } |
375 | } |
376 | |
377 | // Print the log is there was an error or a panic. |
378 | complete := false |
379 | defer func() { |
380 | if !complete || !ok { |
381 | log.WriteTo(os.Stderr) |
382 | } |
383 | }() |
384 | |
385 | result, err := pointer.Analyze(config) |
386 | if err != nil { |
387 | panic(err) // internal error in pointer analysis |
388 | } |
389 | |
390 | // Check the expectations. |
391 | for _, e := range exps { |
392 | var tProbes []types.Type |
393 | var calls []*ssa.CallCommon |
394 | var ptss []pointer.PointsToSet |
395 | if e.needsProbe() { |
396 | calls = findProbes(prog, probes, e) |
397 | if len(calls) == 0 { |
398 | ok = false |
399 | e.errorf("unreachable print() statement has expectation %s", e) |
400 | continue |
401 | } |
402 | if e.extended == nil { |
403 | ptss = probesPointTo(calls, result.Queries) |
404 | } else { |
405 | ptss = make([]pointer.PointsToSet, len(e.extended)) |
406 | for i, p := range e.extended { |
407 | ptss[i] = p.PointsTo() |
408 | } |
409 | } |
410 | |
411 | var err error |
412 | tProbes, err = probesPointToTypes(calls) |
413 | if err != nil { |
414 | ok = false |
415 | e.errorf(err.Error()) |
416 | continue |
417 | } |
418 | } |
419 | |
420 | switch e.kind { |
421 | case "pointsto", "pointstoquery": |
422 | if !checkPointsToExpectation(e, ptss, lineMapping, prog) { |
423 | ok = false |
424 | } |
425 | |
426 | case "types": |
427 | if !checkTypesExpectation(e, ptss, tProbes) { |
428 | ok = false |
429 | } |
430 | |
431 | case "calls": |
432 | if !checkCallsExpectation(prog, e, result.CallGraph) { |
433 | ok = false |
434 | } |
435 | |
436 | case "warning": |
437 | if !checkWarningExpectation(prog, e, result.Warnings) { |
438 | ok = false |
439 | } |
440 | } |
441 | } |
442 | |
443 | complete = true |
444 | |
445 | // ok = false // debugging: uncomment to always see log |
446 | |
447 | return ok |
448 | } |
449 | |
450 | func labelString(l *pointer.Label, lineMapping map[string]string, prog *ssa.Program) string { |
451 | // Functions and Globals need no pos suffix, |
452 | // nor do allocations in intrinsic operations |
453 | // (for which we'll print the function name). |
454 | switch l.Value().(type) { |
455 | case nil, *ssa.Function, *ssa.Global: |
456 | return l.String() |
457 | } |
458 | |
459 | str := l.String() |
460 | if pos := l.Pos(); pos != token.NoPos { |
461 | // Append the position, using a @line tag instead of a line number, if defined. |
462 | posn := prog.Fset.Position(pos) |
463 | s := fmt.Sprintf("%s:%d", posn.Filename, posn.Line) |
464 | if tag, ok := lineMapping[s]; ok { |
465 | return fmt.Sprintf("%s@%s:%d", str, tag, posn.Column) |
466 | } |
467 | str = fmt.Sprintf("%s@%s", str, posn) |
468 | } |
469 | return str |
470 | } |
471 | |
472 | func checkPointsToExpectation(e *expectation, ptss []pointer.PointsToSet, lineMapping map[string]string, prog *ssa.Program) bool { |
473 | expected := make(map[string]int) |
474 | surplus := make(map[string]int) |
475 | exact := true |
476 | for _, g := range e.args { |
477 | if g == "..." { |
478 | exact = false |
479 | continue |
480 | } |
481 | expected[g]++ |
482 | } |
483 | // Find the set of labels that the probe's |
484 | // argument (x in print(x)) may point to. |
485 | for _, pts := range ptss { // treat ptss as union of points-to sets. |
486 | for _, label := range pts.Labels() { |
487 | name := labelString(label, lineMapping, prog) |
488 | if expected[name] > 0 { |
489 | expected[name]-- |
490 | } else if exact { |
491 | surplus[name]++ |
492 | } |
493 | } |
494 | } |
495 | // Report multiset difference: |
496 | ok := true |
497 | for _, count := range expected { |
498 | if count > 0 { |
499 | ok = false |
500 | e.errorf("value does not alias these expected labels: %s", join(expected)) |
501 | break |
502 | } |
503 | } |
504 | for _, count := range surplus { |
505 | if count > 0 { |
506 | ok = false |
507 | e.errorf("value may additionally alias these labels: %s", join(surplus)) |
508 | break |
509 | } |
510 | } |
511 | return ok |
512 | } |
513 | |
514 | func checkTypesExpectation(e *expectation, ptss []pointer.PointsToSet, typs []types.Type) bool { |
515 | var expected typeutil.Map |
516 | var surplus typeutil.Map |
517 | exact := true |
518 | for _, g := range e.types { |
519 | if g == types.Typ[types.Invalid] { |
520 | exact = false |
521 | continue |
522 | } |
523 | expected.Set(g, struct{}{}) |
524 | } |
525 | |
526 | if len(typs) != len(ptss) { |
527 | e.errorf("@types expectation internal error differing number of types(%d) and points to sets (%d)", len(typs), len(ptss)) |
528 | return false |
529 | } |
530 | |
531 | // Find the set of types that the probe's |
532 | // argument (x in print(x)) may contain. |
533 | for i := range ptss { |
534 | var Ts []types.Type |
535 | if pointer.CanHaveDynamicTypes(typs[i]) { |
536 | Ts = ptss[i].DynamicTypes().Keys() |
537 | } else { |
538 | Ts = append(Ts, typs[i]) // static type |
539 | } |
540 | for _, T := range Ts { |
541 | if expected.At(T) != nil { |
542 | expected.Delete(T) |
543 | } else if exact { |
544 | surplus.Set(T, struct{}{}) |
545 | } |
546 | } |
547 | } |
548 | // Report set difference: |
549 | ok := true |
550 | if expected.Len() > 0 { |
551 | ok = false |
552 | e.errorf("interface cannot contain these types: %s", expected.KeysString()) |
553 | } |
554 | if surplus.Len() > 0 { |
555 | ok = false |
556 | e.errorf("interface may additionally contain these types: %s", surplus.KeysString()) |
557 | } |
558 | return ok |
559 | } |
560 | |
561 | var errOK = errors.New("OK") |
562 | |
563 | func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool { |
564 | found := make(map[string]int) |
565 | err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { |
566 | // Name-based matching is inefficient but it allows us to |
567 | // match functions whose names that would not appear in an |
568 | // index ("<root>") or which are not unique ("func@1.2"). |
569 | if edge.Caller.Func.String() == e.args[0] { |
570 | calleeStr := edge.Callee.Func.String() |
571 | if calleeStr == e.args[1] { |
572 | return errOK // expectation satisfied; stop the search |
573 | } |
574 | found[calleeStr]++ |
575 | } |
576 | return nil |
577 | }) |
578 | if err == errOK { |
579 | return true |
580 | } |
581 | if len(found) == 0 { |
582 | e.errorf("didn't find any calls from %s", e.args[0]) |
583 | } |
584 | e.errorf("found no call from %s to %s, but only to %s", |
585 | e.args[0], e.args[1], join(found)) |
586 | return false |
587 | } |
588 | |
589 | func checkWarningExpectation(prog *ssa.Program, e *expectation, warnings []pointer.Warning) bool { |
590 | // TODO(adonovan): check the position part of the warning too? |
591 | re, err := regexp.Compile(e.args[0]) |
592 | if err != nil { |
593 | e.errorf("invalid regular expression in @warning expectation: %s", err.Error()) |
594 | return false |
595 | } |
596 | |
597 | if len(warnings) == 0 { |
598 | e.errorf("@warning %q expectation, but no warnings", e.args[0]) |
599 | return false |
600 | } |
601 | |
602 | for _, w := range warnings { |
603 | if re.MatchString(w.Message) { |
604 | return true |
605 | } |
606 | } |
607 | |
608 | e.errorf("@warning %q expectation not satisfied; found these warnings though:", e.args[0]) |
609 | for _, w := range warnings { |
610 | fmt.Printf("%s: warning: %s\n", prog.Fset.Position(w.Pos), w.Message) |
611 | } |
612 | return false |
613 | } |
614 | |
615 | func TestInput(t *testing.T) { |
616 | if testing.Short() { |
617 | t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113") |
618 | } |
619 | |
620 | wd, err := os.Getwd() |
621 | if err != nil { |
622 | t.Errorf("os.Getwd: %s", err) |
623 | return |
624 | } |
625 | |
626 | // 'go test' does a chdir so that relative paths in |
627 | // diagnostics no longer make sense relative to the invoking |
628 | // shell's cwd. We print a special marker so that Emacs can |
629 | // make sense of them. |
630 | fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd) |
631 | |
632 | for _, filename := range inputs { |
633 | filename := filename |
634 | t.Run(filename, func(t *testing.T) { |
635 | if filename == "testdata/a_test.go" { |
636 | // For some reason this particular file is way more expensive than the others. |
637 | if unsafe.Sizeof(unsafe.Pointer(nil)) <= 4 { |
638 | t.Skip("skipping memory-intensive test on platform with small address space; https://golang.org/issue/14113") |
639 | } |
640 | if raceEnabled { |
641 | t.Skip("skipping memory-intensive test under race detector; https://golang.org/issue/14113") |
642 | } |
643 | } else { |
644 | t.Parallel() |
645 | } |
646 | |
647 | content, err := ioutil.ReadFile(filename) |
648 | if err != nil { |
649 | t.Fatalf("couldn't read file '%s': %s", filename, err) |
650 | } |
651 | |
652 | fpath, err := filepath.Abs(filename) |
653 | if err != nil { |
654 | t.Fatalf("couldn't get absolute path for '%s': %s", filename, err) |
655 | } |
656 | |
657 | if !doOneInput(t, string(content), fpath) { |
658 | t.Fail() |
659 | } |
660 | }) |
661 | } |
662 | } |
663 | |
664 | // isGenericBody returns true if fn is the body of a generic function. |
665 | func isGenericBody(fn *ssa.Function) bool { |
666 | sig := fn.Signature |
667 | if typeparams.ForSignature(sig).Len() > 0 || typeparams.RecvTypeParams(sig).Len() > 0 { |
668 | return fn.Synthetic == "" |
669 | } |
670 | return false |
671 | } |
672 | |
673 | // join joins the elements of multiset with " | "s. |
674 | func join(set map[string]int) string { |
675 | var buf bytes.Buffer |
676 | sep := "" |
677 | for name, count := range set { |
678 | for i := 0; i < count; i++ { |
679 | buf.WriteString(sep) |
680 | sep = " | " |
681 | buf.WriteString(name) |
682 | } |
683 | } |
684 | return buf.String() |
685 | } |
686 | |
687 | // split returns the list of sep-delimited non-empty strings in s. |
688 | func split(s, sep string) (r []string) { |
689 | for _, elem := range strings.Split(s, sep) { |
690 | elem = strings.TrimSpace(elem) |
691 | if elem != "" { |
692 | r = append(r, elem) |
693 | } |
694 | } |
695 | return |
696 | } |
697 | |
698 | func TestTypeParam(t *testing.T) { |
699 | if !typeparams.Enabled { |
700 | t.Skip("TestTypeParamInput requires type parameters") |
701 | } |
702 | // Based on TestInput. Keep this up to date with that. |
703 | filename := "testdata/typeparams.go" |
704 | |
705 | if testing.Short() { |
706 | t.Skip("skipping in short mode; this test requires tons of memory; https://golang.org/issue/14113") |
707 | } |
708 | |
709 | wd, err := os.Getwd() |
710 | if err != nil { |
711 | t.Fatalf("os.Getwd: %s", err) |
712 | } |
713 | fmt.Fprintf(os.Stderr, "Entering directory `%s'\n", wd) |
714 | |
715 | content, err := ioutil.ReadFile(filename) |
716 | if err != nil { |
717 | t.Fatalf("couldn't read file '%s': %s", filename, err) |
718 | } |
719 | fpath, err := filepath.Abs(filename) |
720 | if err != nil { |
721 | t.Errorf("couldn't get absolute path for '%s': %s", filename, err) |
722 | } |
723 | |
724 | if !doOneInput(t, string(content), fpath) { |
725 | t.Fail() |
726 | } |
727 | } |
728 |
Members