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.
7 // Provide 'static type checking' of the templates. This guards against changes is various
8 // gopls datastructures causing template execution to fail. The checking is done by
9 // the github.com/jba/templatecheck pacakge. 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().
22 "github.com/jba/templatecheck"
23 "golang.org/x/tools/go/packages"
24 "golang.org/x/tools/internal/lsp/cache"
25 "golang.org/x/tools/internal/lsp/debug"
26 "golang.org/x/tools/internal/lsp/source"
27 "golang.org/x/tools/internal/span"
31 tmpl *template.Template
32 data interface{} // a value of the needed type
35 var templates = map[string]tdata{
36 "MainTmpl": {debug.MainTmpl, &debug.Instance{}},
37 "DebugTmpl": {debug.DebugTmpl, nil},
38 "RPCTmpl": {debug.RPCTmpl, &debug.Rpcs{}},
39 "TraceTmpl": {debug.TraceTmpl, debug.TraceResults{}},
40 "CacheTmpl": {debug.CacheTmpl, &cache.Cache{}},
41 "SessionTmpl": {debug.SessionTmpl, &cache.Session{}},
42 "ViewTmpl": {debug.ViewTmpl, &cache.View{}},
43 "ClientTmpl": {debug.ClientTmpl, &debug.Client{}},
44 "ServerTmpl": {debug.ServerTmpl, &debug.Server{}},
45 //"FileTmpl": {FileTmpl, source.Overlay{}}, // need to construct a source.Overlay in init
46 "InfoTmpl": {debug.InfoTmpl, "something"},
47 "MemoryTmpl": {debug.MemoryTmpl, runtime.MemStats{}},
50 // construct a source.Overlay for fileTmpl
51 type fakeOverlay struct{}
53 func (fakeOverlay) Version() int32 {
56 func (fakeOverlay) Session() string {
59 func (fakeOverlay) VersionedFileIdentity() source.VersionedFileIdentity {
60 return source.VersionedFileIdentity{}
62 func (fakeOverlay) FileIdentity() source.FileIdentity {
63 return source.FileIdentity{}
65 func (fakeOverlay) Kind() source.FileKind {
68 func (fakeOverlay) Read() ([]byte, error) {
71 func (fakeOverlay) Saved() bool {
74 func (fakeOverlay) URI() span.URI {
78 var _ source.Overlay = fakeOverlay{}
81 log.SetFlags(log.Lshortfile)
83 templates["FileTmpl"] = tdata{debug.FileTmpl, v}
86 func TestTemplates(t *testing.T) {
87 if runtime.GOOS == "android" {
88 t.Skip("this test is not supported for Android")
90 cfg := &packages.Config{
91 Mode: packages.NeedTypesInfo | packages.LoadAllSyntax, // figure out what's necessary PJW
93 pkgs, err := packages.Load(cfg, "golang.org/x/tools/internal/lsp/debug")
98 t.Fatalf("expected a single package, but got %d", len(pkgs))
101 if len(p.Errors) != 0 {
102 t.Fatalf("compiler error, e.g. %v", p.Errors[0])
104 // find the calls to render in serve.go
105 tree := treeOf(p, "serve.go")
107 t.Fatalf("found no syntax tree for %s", "serve.go")
109 renders := callsOf(p, tree, "render")
110 if len(renders) == 0 {
111 t.Fatalf("found no calls to render")
113 var found = make(map[string]bool)
114 for _, r := range renders {
115 if len(r.Args) != 2 {
117 t.Fatalf("got %d args, expected 2", len(r.Args))
119 t0, ok := p.TypesInfo.Types[r.Args[0]]
120 if !ok || !t0.IsValue() || t0.Type.String() != "*html/template.Template" {
121 t.Fatalf("no type info for template")
123 if id, ok := r.Args[0].(*ast.Ident); !ok {
124 t.Errorf("expected *ast.Ident, got %T", r.Args[0])
126 found[id.Name] = true
129 // make sure found and templates have the same templates
130 for k := range found {
131 if _, ok := templates[k]; !ok {
132 t.Errorf("code has template %s, but test does not", k)
135 for k := range templates {
136 if _, ok := found[k]; !ok {
137 t.Errorf("test has template %s, code does not", k)
140 // now check all the known templates, in alphabetic order, for determinacy
142 for k := range templates {
143 keys = append(keys, k)
146 for _, k := range keys {
148 // the FuncMap is an annoyance; should not be necessary
149 if err := templatecheck.CheckHTML(v.tmpl, v.data); err != nil {
150 t.Errorf("%s: %v", k, err)
155 func callsOf(p *packages.Package, tree *ast.File, name string) []*ast.CallExpr {
156 var ans []*ast.CallExpr
157 f := func(n ast.Node) bool {
158 x, ok := n.(*ast.CallExpr)
162 if y, ok := x.Fun.(*ast.Ident); ok {
172 func treeOf(p *packages.Package, fname string) *ast.File {
173 for _, tree := range p.Syntax {
175 pos := p.Fset.PositionFor(loc, false)
176 if strings.HasSuffix(pos.Filename, fname) {