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  }