package twig import ( "bytes" "testing" ) // BenchmarkNodePooling tests the impact of node object pooling on memory allocations func BenchmarkNodePooling(b *testing.B) { // Create test templates that use different node types heavily tests := []struct { name string template string }{ { name: "TextNodes", template: "This is a template with lots of text. It has multiple paragraphs.\nAnd newlines.\nAnd more text.", }, { name: "PrintNodes", template: "{{ a }} {{ b }} {{ c }} {{ d }} {{ e }} {{ f }} {{ g }} {{ h }} {{ i }} {{ j }}", }, { name: "IfNodes", template: "{% if a %}A{% endif %}{% if b %}B{% endif %}{% if c %}C{% endif %}{% if d %}D{% endif %}{% if e %}E{% endif %}", }, { name: "ForNodes", template: "{% for i in items %}{{ i }}{% endfor %}{% for j in range(1, 10) %}{{ j }}{% endfor %}", }, { name: "MixedNodes", template: "Text {{ var }} {% if cond %}Conditional Content {{ value }}{% endif %}{% for item in items %}{{ item.name }}{% endfor %}", }, { name: "ComplexTemplate", template: `

{{ page.title }}

{% if user.authenticated %}

Welcome back, {{ user.name }}!

{% if user.admin %}

Admin Tools

{% endif %} {% else %}

Welcome, guest! Please login.

{% endif %}

Recent Items

`, }, } // Create a test context with all variables needed testContext := map[string]interface{}{ "a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "cond": true, "value": "Value", "var": "Variable", "items": []map[string]interface{}{ {"name": "Item 1", "title": "Title 1", "description": "Description 1", "featured": true, "tags": []string{"tag1", "tag2"}}, {"name": "Item 2", "title": "Title 2", "description": "Description 2", "featured": false, "tags": []string{"tag2", "tag3"}}, }, "page": map[string]interface{}{"title": "Page Title"}, "user": map[string]interface{}{ "authenticated": true, "name": "John Doe", "admin": true, }, "admin_tools": []map[string]interface{}{ {"name": "Dashboard", "url": "/admin/dashboard"}, {"name": "Users", "url": "/admin/users"}, {"name": "Settings", "url": "/admin/settings"}, }, } for _, tt := range tests { b.Run(tt.name, func(b *testing.B) { engine := New() // Pre-compile the template to isolate rendering performance tmpl, err := engine.ParseTemplate(tt.template) if err != nil { b.Fatalf("Failed to parse template: %v", err) } // Create a reusable buffer to avoid allocations in the benchmark loop buf := new(bytes.Buffer) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { buf.Reset() // Render directly to buffer to avoid string conversions err = tmpl.RenderTo(buf, testContext) if err != nil { b.Fatalf("Failed to render template: %v", err) } } }) } } // BenchmarkTokenPooling tests the impact of token pooling during parsing func BenchmarkTokenPooling(b *testing.B) { // Create test templates with varying numbers of tokens tests := []struct { name string template string }{ { name: "SimpleTokens", template: "{{ var }} {{ var2 }} {{ var3 }}", }, { name: "MediumTokens", template: "{% if cond %}Text{{ var }}{% else %}OtherText{{ var2 }}{% endif %}{% for item in items %}{{ item }}{% endfor %}", }, { name: "ManyTokens", template: ` {% for i in range(1, 10) %} {% if i > 5 %} {{ i }} is greater than 5 {% else %} {{ i }} is less than or equal to 5 {% if i == 5 %} {{ i }} is exactly 5 {% endif %} {% endif %} {% endfor %} `, }, } for _, tt := range tests { b.Run(tt.name, func(b *testing.B) { engine := New() b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { tmpl, err := engine.ParseTemplate(tt.template) if err != nil { b.Fatalf("Failed to parse template: %v", err) } // Ensure the template is valid if tmpl == nil { b.Fatalf("Template is nil") } } }) } } // BenchmarkFullTemplateLifecycle tests the full lifecycle of template parsing and rendering func BenchmarkFullTemplateLifecycle(b *testing.B) { // Complex template that heavily exercises the node pool complexTemplate := `

{{ page.title }}

{% if user.authenticated %}

Welcome, {{ user.name }}

{% else %}

Please login to continue.

Login
{% endif %}
` // Create a test context with all variables needed testContext := map[string]interface{}{ "page": map[string]interface{}{ "title": "Dashboard", }, "user": map[string]interface{}{ "authenticated": true, "name": "John Doe", }, "menu_items": []map[string]interface{}{ { "label": "Home", "url": "/", "active": true, "sub_items": []map[string]interface{}{}, }, { "label": "Products", "url": "/products", "active": false, "sub_items": []map[string]interface{}{ { "label": "Category 1", "url": "/products/cat1", }, { "label": "Category 2", "url": "/products/cat2", }, }, }, { "label": "About", "url": "/about", "active": false, "sub_items": []map[string]interface{}{}, }, }, } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { engine := New() // Parse the template (tests token pooling) tmpl, err := engine.ParseTemplate(complexTemplate) if err != nil { b.Fatalf("Failed to parse template: %v", err) } // Render the template (tests node pooling) _, err = tmpl.Render(testContext) if err != nil { b.Fatalf("Failed to render template: %v", err) } } }