mirror of https://github.com/golang/go.git
182 lines
4.9 KiB
Go
182 lines
4.9 KiB
Go
// 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
|
|
}
|