// Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package debug_test // Provide 'static type checking' of the templates. This guards against changes is various // gopls datastructures causing template execution to fail. The checking is done by // the github.com/jba/templatecheck pacakge. Before that is run, the test checks that // its list of templates and their arguments corresponds to the arguments in // calls to render(). The test assumes that all uses of templates are done through render(). import ( "go/ast" "html/template" "log" "runtime" "sort" "strings" "testing" "github.com/jba/templatecheck" "golang.org/x/tools/go/packages" "golang.org/x/tools/internal/lsp/cache" "golang.org/x/tools/internal/lsp/debug" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) type tdata struct { tmpl *template.Template data interface{} // a value of the needed type } var templates = map[string]tdata{ "MainTmpl": {debug.MainTmpl, &debug.Instance{}}, "DebugTmpl": {debug.DebugTmpl, nil}, "RPCTmpl": {debug.RPCTmpl, &debug.Rpcs{}}, "TraceTmpl": {debug.TraceTmpl, debug.TraceResults{}}, "CacheTmpl": {debug.CacheTmpl, &cache.Cache{}}, "SessionTmpl": {debug.SessionTmpl, &cache.Session{}}, "ViewTmpl": {debug.ViewTmpl, &cache.View{}}, "ClientTmpl": {debug.ClientTmpl, &debug.Client{}}, "ServerTmpl": {debug.ServerTmpl, &debug.Server{}}, //"FileTmpl": {FileTmpl, source.Overlay{}}, // need to construct a source.Overlay in init "InfoTmpl": {debug.InfoTmpl, "something"}, "MemoryTmpl": {debug.MemoryTmpl, runtime.MemStats{}}, } // construct a source.Overlay for fileTmpl type fakeOverlay struct{} func (fakeOverlay) Version() int32 { return 0 } func (fakeOverlay) Session() string { return "" } func (fakeOverlay) VersionedFileIdentity() source.VersionedFileIdentity { return source.VersionedFileIdentity{} } func (fakeOverlay) FileIdentity() source.FileIdentity { return source.FileIdentity{} } func (fakeOverlay) Kind() source.FileKind { return 0 } func (fakeOverlay) Read() ([]byte, error) { return nil, nil } func (fakeOverlay) Saved() bool { return true } func (fakeOverlay) URI() span.URI { return "" } var _ source.Overlay = fakeOverlay{} func init() { log.SetFlags(log.Lshortfile) var v fakeOverlay templates["FileTmpl"] = tdata{debug.FileTmpl, v} } func TestTemplates(t *testing.T) { if runtime.GOOS == "android" { t.Skip("this test is not supported for Android") } cfg := &packages.Config{ Mode: packages.NeedTypesInfo | packages.LoadAllSyntax, // figure out what's necessary PJW } pkgs, err := packages.Load(cfg, "golang.org/x/tools/internal/lsp/debug") if err != nil { t.Fatal(err) } if len(pkgs) != 1 { t.Fatalf("expected a single package, but got %d", len(pkgs)) } p := pkgs[0] if len(p.Errors) != 0 { t.Fatalf("compiler error, e.g. %v", p.Errors[0]) } // find the calls to render in serve.go tree := treeOf(p, "serve.go") if tree == nil { t.Fatalf("found no syntax tree for %s", "serve.go") } renders := callsOf(p, tree, "render") if len(renders) == 0 { t.Fatalf("found no calls to render") } var found = make(map[string]bool) for _, r := range renders { if len(r.Args) != 2 { // template, func t.Fatalf("got %d args, expected 2", len(r.Args)) } t0, ok := p.TypesInfo.Types[r.Args[0]] if !ok || !t0.IsValue() || t0.Type.String() != "*html/template.Template" { t.Fatalf("no type info for template") } if id, ok := r.Args[0].(*ast.Ident); !ok { t.Errorf("expected *ast.Ident, got %T", r.Args[0]) } else { found[id.Name] = true } } // make sure found and templates have the same templates for k := range found { if _, ok := templates[k]; !ok { t.Errorf("code has template %s, but test does not", k) } } for k := range templates { if _, ok := found[k]; !ok { t.Errorf("test has template %s, code does not", k) } } // now check all the known templates, in alphabetic order, for determinacy keys := []string{} for k := range templates { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { v := templates[k] // the FuncMap is an annoyance; should not be necessary if err := templatecheck.CheckHTML(v.tmpl, v.data); err != nil { t.Errorf("%s: %v", k, err) } } } func callsOf(p *packages.Package, tree *ast.File, name string) []*ast.CallExpr { var ans []*ast.CallExpr f := func(n ast.Node) bool { x, ok := n.(*ast.CallExpr) if !ok { return true } if y, ok := x.Fun.(*ast.Ident); ok { if y.Name == name { ans = append(ans, x) } } return true } ast.Inspect(tree, f) return ans } func treeOf(p *packages.Package, fname string) *ast.File { for _, tree := range p.Syntax { loc := tree.Package pos := p.Fset.PositionFor(loc, false) if strings.HasSuffix(pos.Filename, fname) { return tree } } return nil }