golang.org/x/tools/gopls@v0.15.3/internal/debug/template_test.go (about) 1 // Copyright 2020 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 debug_test 6 7 // Provide 'static type checking' of the templates. This guards against changes in various 8 // gopls datastructures causing template execution to fail. The checking is done by 9 // the github.com/jba/templatecheck package. Before that is run, the test checks that 10 // its list of templates and their arguments corresponds to the arguments in 11 // calls to render(). The test assumes that all uses of templates are done through render(). 12 13 import ( 14 "go/ast" 15 "html/template" 16 "os" 17 "runtime" 18 "sort" 19 "strings" 20 "testing" 21 22 "github.com/jba/templatecheck" 23 "golang.org/x/tools/go/packages" 24 "golang.org/x/tools/gopls/internal/cache" 25 "golang.org/x/tools/gopls/internal/debug" 26 "golang.org/x/tools/gopls/internal/file" 27 "golang.org/x/tools/internal/testenv" 28 ) 29 30 var templates = map[string]struct { 31 tmpl *template.Template 32 data interface{} // a value of the needed type 33 }{ 34 "MainTmpl": {debug.MainTmpl, &debug.Instance{}}, 35 "DebugTmpl": {debug.DebugTmpl, nil}, 36 "RPCTmpl": {debug.RPCTmpl, &debug.Rpcs{}}, 37 "TraceTmpl": {debug.TraceTmpl, debug.TraceResults{}}, 38 "CacheTmpl": {debug.CacheTmpl, &cache.Cache{}}, 39 "SessionTmpl": {debug.SessionTmpl, &cache.Session{}}, 40 "ClientTmpl": {debug.ClientTmpl, &debug.Client{}}, 41 "ServerTmpl": {debug.ServerTmpl, &debug.Server{}}, 42 "FileTmpl": {debug.FileTmpl, *new(interface { 43 file.Handle 44 Kind() file.Kind // (overlay files only) 45 })}, 46 "InfoTmpl": {debug.InfoTmpl, "something"}, 47 "MemoryTmpl": {debug.MemoryTmpl, runtime.MemStats{}}, 48 "AnalysisTmpl": {debug.AnalysisTmpl, new(debug.State).Analysis()}, 49 } 50 51 func TestTemplates(t *testing.T) { 52 testenv.NeedsGoPackages(t) 53 testenv.NeedsLocalXTools(t) 54 55 cfg := &packages.Config{ 56 Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, 57 } 58 cfg.Env = os.Environ() 59 cfg.Env = append(cfg.Env, 60 "GOPACKAGESDRIVER=off", 61 "GOWORK=off", // necessary for -mod=mod below 62 "GOFLAGS=-mod=mod", 63 ) 64 65 pkgs, err := packages.Load(cfg, "golang.org/x/tools/gopls/internal/debug") 66 if err != nil { 67 t.Fatal(err) 68 } 69 if len(pkgs) != 1 { 70 t.Fatalf("expected a single package, but got %d", len(pkgs)) 71 } 72 p := pkgs[0] 73 if len(p.Errors) != 0 { 74 t.Fatalf("compiler error, e.g. %v", p.Errors[0]) 75 } 76 // find the calls to render in serve.go 77 tree := treeOf(p, "serve.go") 78 if tree == nil { 79 t.Fatalf("found no syntax tree for %s", "serve.go") 80 } 81 renders := callsOf(tree, "render") 82 if len(renders) == 0 { 83 t.Fatalf("found no calls to render") 84 } 85 var found = make(map[string]bool) 86 for _, r := range renders { 87 if len(r.Args) != 2 { 88 // template, func 89 t.Fatalf("got %d args, expected 2", len(r.Args)) 90 } 91 t0, ok := p.TypesInfo.Types[r.Args[0]] 92 if !ok || !t0.IsValue() || t0.Type.String() != "*html/template.Template" { 93 t.Fatalf("no type info for template") 94 } 95 if id, ok := r.Args[0].(*ast.Ident); !ok { 96 t.Errorf("expected *ast.Ident, got %T", r.Args[0]) 97 } else { 98 found[id.Name] = true 99 } 100 } 101 // make sure found and templates have the same templates 102 for k := range found { 103 if _, ok := templates[k]; !ok { 104 t.Errorf("code has template %s, but test does not", k) 105 } 106 } 107 for k := range templates { 108 if _, ok := found[k]; !ok { 109 t.Errorf("test has template %s, code does not", k) 110 } 111 } 112 // now check all the known templates, in alphabetic order, for determinacy 113 keys := []string{} 114 for k := range templates { 115 keys = append(keys, k) 116 } 117 sort.Strings(keys) 118 for _, k := range keys { 119 v := templates[k] 120 // the FuncMap is an annoyance; should not be necessary 121 if err := templatecheck.CheckHTML(v.tmpl, v.data); err != nil { 122 t.Errorf("%s: %v", k, err) 123 continue 124 } 125 t.Logf("%s ok", k) 126 } 127 } 128 129 func callsOf(tree *ast.File, name string) []*ast.CallExpr { 130 var ans []*ast.CallExpr 131 f := func(n ast.Node) bool { 132 x, ok := n.(*ast.CallExpr) 133 if !ok { 134 return true 135 } 136 if y, ok := x.Fun.(*ast.Ident); ok { 137 if y.Name == name { 138 ans = append(ans, x) 139 } 140 } 141 return true 142 } 143 ast.Inspect(tree, f) 144 return ans 145 } 146 147 func treeOf(p *packages.Package, fname string) *ast.File { 148 for _, tree := range p.Syntax { 149 loc := tree.Package 150 pos := p.Fset.PositionFor(loc, false) 151 if strings.HasSuffix(pos.Filename, fname) { 152 return tree 153 } 154 } 155 return nil 156 }