github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/tpl/internal/go_templates/htmltemplate/clone_test.go (about)

     1  // Copyright 2011 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  // +build go1.13,!windows
     6  
     7  package template
     8  
     9  import (
    10  	"bytes"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  
    18  	"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
    19  )
    20  
    21  func TestAddParseTreeHTML(t *testing.T) {
    22  	root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} "></a>{{end}}`))
    23  	tree, err := parse.Parse("t", `{{define "b"}}<a href="{{end}}`, "", "", nil, nil)
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	added := Must(root.AddParseTree("b", tree["b"]))
    28  	b := new(bytes.Buffer)
    29  	err = added.ExecuteTemplate(b, "a", "1>0")
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	if got, want := b.String(), ` 1&gt;0 <a href=" 1%3e0 "></a>`; got != want {
    34  		t.Errorf("got %q want %q", got, want)
    35  	}
    36  }
    37  
    38  func TestClone(t *testing.T) {
    39  	// The {{.}} will be executed with data "<i>*/" in different contexts.
    40  	// In the t0 template, it will be in a text context.
    41  	// In the t1 template, it will be in a URL context.
    42  	// In the t2 template, it will be in a JavaScript context.
    43  	// In the t3 template, it will be in a CSS context.
    44  	const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}`
    45  	b := new(bytes.Buffer)
    46  
    47  	// Create an incomplete template t0.
    48  	t0 := Must(New("t0").Parse(tmpl))
    49  
    50  	// Clone t0 as t1.
    51  	t1 := Must(t0.Clone())
    52  	Must(t1.Parse(`{{define "lhs"}} <a href=" {{end}}`))
    53  	Must(t1.Parse(`{{define "rhs"}} "></a> {{end}}`))
    54  
    55  	// Execute t1.
    56  	b.Reset()
    57  	if err := t1.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
    58  		t.Fatal(err)
    59  	}
    60  	if got, want := b.String(), ` <a href=" %3ci%3e*/ "></a> `; got != want {
    61  		t.Errorf("t1: got %q want %q", got, want)
    62  	}
    63  
    64  	// Clone t0 as t2.
    65  	t2 := Must(t0.Clone())
    66  	Must(t2.Parse(`{{define "lhs"}} <p onclick="javascript: {{end}}`))
    67  	Must(t2.Parse(`{{define "rhs"}} "></p> {{end}}`))
    68  
    69  	// Execute t2.
    70  	b.Reset()
    71  	if err := t2.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	if got, want := b.String(), ` <p onclick="javascript: &#34;\u003ci\u003e*/&#34; "></p> `; got != want {
    75  		t.Errorf("t2: got %q want %q", got, want)
    76  	}
    77  
    78  	// Clone t0 as t3, but do not execute t3 yet.
    79  	t3 := Must(t0.Clone())
    80  	Must(t3.Parse(`{{define "lhs"}} <style> {{end}}`))
    81  	Must(t3.Parse(`{{define "rhs"}} </style> {{end}}`))
    82  
    83  	// Complete t0.
    84  	Must(t0.Parse(`{{define "lhs"}} ( {{end}}`))
    85  	Must(t0.Parse(`{{define "rhs"}} ) {{end}}`))
    86  
    87  	// Clone t0 as t4. Redefining the "lhs" template should not fail.
    88  	t4 := Must(t0.Clone())
    89  	if _, err := t4.Parse(`{{define "lhs"}} OK {{end}}`); err != nil {
    90  		t.Errorf(`redefine "lhs": got err %v want nil`, err)
    91  	}
    92  	// Cloning t1 should fail as it has been executed.
    93  	if _, err := t1.Clone(); err == nil {
    94  		t.Error("cloning t1: got nil err want non-nil")
    95  	}
    96  	// Redefining the "lhs" template in t1 should fail as it has been executed.
    97  	if _, err := t1.Parse(`{{define "lhs"}} OK {{end}}`); err == nil {
    98  		t.Error(`redefine "lhs": got nil err want non-nil`)
    99  	}
   100  
   101  	// Execute t0.
   102  	b.Reset()
   103  	if err := t0.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	if got, want := b.String(), ` ( &lt;i&gt;*/ ) `; got != want {
   107  		t.Errorf("t0: got %q want %q", got, want)
   108  	}
   109  
   110  	// Clone t0. This should fail, as t0 has already executed.
   111  	if _, err := t0.Clone(); err == nil {
   112  		t.Error(`t0.Clone(): got nil err want non-nil`)
   113  	}
   114  
   115  	// Similarly, cloning sub-templates should fail.
   116  	if _, err := t0.Lookup("a").Clone(); err == nil {
   117  		t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`)
   118  	}
   119  	if _, err := t0.Lookup("lhs").Clone(); err == nil {
   120  		t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`)
   121  	}
   122  
   123  	// Execute t3.
   124  	b.Reset()
   125  	if err := t3.ExecuteTemplate(b, "a", "<i>*/"); err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	if got, want := b.String(), ` <style> ZgotmplZ </style> `; got != want {
   129  		t.Errorf("t3: got %q want %q", got, want)
   130  	}
   131  }
   132  
   133  func TestTemplates(t *testing.T) {
   134  	names := []string{"t0", "a", "lhs", "rhs"}
   135  	// Some template definitions borrowed from TestClone.
   136  	const tmpl = `
   137  		{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}
   138  		{{define "lhs"}} <a href=" {{end}}
   139  		{{define "rhs"}} "></a> {{end}}`
   140  	t0 := Must(New("t0").Parse(tmpl))
   141  	templates := t0.Templates()
   142  	if len(templates) != len(names) {
   143  		t.Errorf("expected %d templates; got %d", len(names), len(templates))
   144  	}
   145  	for _, name := range names {
   146  		found := false
   147  		for _, tmpl := range templates {
   148  			if name == tmpl.text.Name() {
   149  				found = true
   150  				break
   151  			}
   152  		}
   153  		if !found {
   154  			t.Error("could not find template", name)
   155  		}
   156  	}
   157  }
   158  
   159  // This used to crash; https://golang.org/issue/3281
   160  func TestCloneCrash(t *testing.T) {
   161  	t1 := New("all")
   162  	Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`))
   163  	t1.Clone()
   164  }
   165  
   166  // Ensure that this guarantee from the docs is upheld:
   167  // "Further calls to Parse in the copy will add templates
   168  // to the copy but not to the original."
   169  func TestCloneThenParse(t *testing.T) {
   170  	t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`))
   171  	t1 := Must(t0.Clone())
   172  	Must(t1.Parse(`{{define "embedded"}}t1{{end}}`))
   173  	if len(t0.Templates())+1 != len(t1.Templates()) {
   174  		t.Error("adding a template to a clone added it to the original")
   175  	}
   176  	// double check that the embedded template isn't available in the original
   177  	err := t0.ExecuteTemplate(io.Discard, "a", nil)
   178  	if err == nil {
   179  		t.Error("expected 'no such template' error")
   180  	}
   181  }
   182  
   183  // https://golang.org/issue/5980
   184  func TestFuncMapWorksAfterClone(t *testing.T) {
   185  	funcs := FuncMap{"customFunc": func() (string, error) {
   186  		return "", errors.New("issue5980")
   187  	}}
   188  
   189  	// get the expected error output (no clone)
   190  	uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}"))
   191  	wantErr := uncloned.Execute(io.Discard, nil)
   192  
   193  	// toClone must be the same as uncloned. It has to be recreated from scratch,
   194  	// since cloning cannot occur after execution.
   195  	toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}"))
   196  	cloned := Must(toClone.Clone())
   197  	gotErr := cloned.Execute(io.Discard, nil)
   198  
   199  	if wantErr.Error() != gotErr.Error() {
   200  		t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
   201  	}
   202  }
   203  
   204  // https://golang.org/issue/16101
   205  func TestTemplateCloneExecuteRace(t *testing.T) {
   206  	const (
   207  		input   = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>`
   208  		overlay = `{{define "b"}}A{{end}}`
   209  	)
   210  	outer := Must(New("outer").Parse(input))
   211  	tmpl := Must(Must(outer.Clone()).Parse(overlay))
   212  
   213  	var wg sync.WaitGroup
   214  	for i := 0; i < 10; i++ {
   215  		wg.Add(1)
   216  		go func() {
   217  			defer wg.Done()
   218  			for i := 0; i < 100; i++ {
   219  				if err := tmpl.Execute(io.Discard, "data"); err != nil {
   220  					panic(err)
   221  				}
   222  			}
   223  		}()
   224  	}
   225  	wg.Wait()
   226  }
   227  
   228  func TestTemplateCloneLookup(t *testing.T) {
   229  	// Template.escape makes an assumption that the template associated
   230  	// with t.Name() is t. Check that this holds.
   231  	tmpl := Must(New("x").Parse("a"))
   232  	tmpl = Must(tmpl.Clone())
   233  	if tmpl.Lookup(tmpl.Name()) != tmpl {
   234  		t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl")
   235  	}
   236  }
   237  
   238  func TestCloneGrowth(t *testing.T) {
   239  	tmpl := Must(New("root").Parse(`<title>{{block "B". }}Arg{{end}}</title>`))
   240  	tmpl = Must(tmpl.Clone())
   241  	Must(tmpl.Parse(`{{define "B"}}Text{{end}}`))
   242  	for i := 0; i < 10; i++ {
   243  		tmpl.Execute(io.Discard, nil)
   244  	}
   245  	if len(tmpl.DefinedTemplates()) > 200 {
   246  		t.Fatalf("too many templates: %v", len(tmpl.DefinedTemplates()))
   247  	}
   248  }
   249  
   250  // https://golang.org/issue/17735
   251  func TestCloneRedefinedName(t *testing.T) {
   252  	const base = `
   253  {{ define "a" -}}<title>{{ template "b" . -}}</title>{{ end -}}
   254  {{ define "b" }}{{ end -}}
   255  `
   256  	const page = `{{ template "a" . }}`
   257  
   258  	t1 := Must(New("a").Parse(base))
   259  
   260  	for i := 0; i < 2; i++ {
   261  		t2 := Must(t1.Clone())
   262  		t2 = Must(t2.New(fmt.Sprintf("%d", i)).Parse(page))
   263  		err := t2.Execute(io.Discard, nil)
   264  		if err != nil {
   265  			t.Fatal(err)
   266  		}
   267  	}
   268  }
   269  
   270  // Issue 24791.
   271  func TestClonePipe(t *testing.T) {
   272  	a := Must(New("a").Parse(`{{define "a"}}{{range $v := .A}}{{$v}}{{end}}{{end}}`))
   273  	data := struct{ A []string }{A: []string{"hi"}}
   274  	b := Must(a.Clone())
   275  	var buf strings.Builder
   276  	if err := b.Execute(&buf, &data); err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	if got, want := buf.String(), "hi"; got != want {
   280  		t.Errorf("got %q want %q", got, want)
   281  	}
   282  }