2018-01-28 14:56:30 +00:00
package pongo2_test
import (
"bytes"
2019-04-21 17:58:15 +00:00
"encoding/json"
2018-01-28 14:56:30 +00:00
"fmt"
"io/ioutil"
2019-04-21 17:58:15 +00:00
"os"
2018-01-28 14:56:30 +00:00
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"github.com/flosch/pongo2"
2019-04-21 17:58:15 +00:00
"github.com/juju/errors"
2018-01-28 14:56:30 +00:00
)
var adminList = [ ] string { "user2" }
var time1 = time . Date ( 2014 , 06 , 10 , 15 , 30 , 15 , 0 , time . UTC )
var time2 = time . Date ( 2011 , 03 , 21 , 8 , 37 , 56 , 12 , time . UTC )
type post struct {
Text string
Created time . Time
}
type user struct {
Name string
Validated bool
}
type comment struct {
Author * user
Date time . Time
Text string
}
func isAdmin ( u * user ) bool {
for _ , a := range adminList {
if a == u . Name {
return true
}
}
return false
}
func ( u * user ) Is_admin ( ) * pongo2 . Value {
return pongo2 . AsValue ( isAdmin ( u ) )
}
func ( u * user ) Is_admin2 ( ) bool {
return isAdmin ( u )
}
func ( p * post ) String ( ) string {
return ":-)"
}
/ *
* Start setup sandbox
* /
type tagSandboxDemoTag struct {
}
func ( node * tagSandboxDemoTag ) Execute ( ctx * pongo2 . ExecutionContext , writer pongo2 . TemplateWriter ) * pongo2 . Error {
writer . WriteString ( "hello" )
return nil
}
func tagSandboxDemoTagParser ( doc * pongo2 . Parser , start * pongo2 . Token , arguments * pongo2 . Parser ) ( pongo2 . INodeTag , * pongo2 . Error ) {
return & tagSandboxDemoTag { } , nil
}
func BannedFilterFn ( in * pongo2 . Value , params * pongo2 . Value ) ( * pongo2 . Value , * pongo2 . Error ) {
return in , nil
}
func init ( ) {
pongo2 . DefaultSet . Debug = true
pongo2 . RegisterFilter ( "banned_filter" , BannedFilterFn )
pongo2 . RegisterFilter ( "unbanned_filter" , BannedFilterFn )
pongo2 . RegisterTag ( "banned_tag" , tagSandboxDemoTagParser )
pongo2 . RegisterTag ( "unbanned_tag" , tagSandboxDemoTagParser )
pongo2 . DefaultSet . BanFilter ( "banned_filter" )
pongo2 . DefaultSet . BanTag ( "banned_tag" )
2019-04-21 17:58:15 +00:00
f , err := ioutil . TempFile ( os . TempDir ( ) , "pongo2_" )
2018-01-28 14:56:30 +00:00
if err != nil {
2019-04-21 17:58:15 +00:00
panic ( fmt . Sprintf ( "cannot write to %s" , os . TempDir ( ) ) )
}
defer f . Close ( )
_ , err = f . Write ( [ ] byte ( "Hello from pongo2" ) )
if err != nil {
panic ( fmt . Sprintf ( "cannot write to %s" , os . TempDir ( ) ) )
2018-01-28 14:56:30 +00:00
}
pongo2 . DefaultSet . Globals [ "temp_file" ] = f . Name ( )
}
/ *
* End setup sandbox
* /
var tplContext = pongo2 . Context {
"number" : 11 ,
"simple" : map [ string ] interface { } {
"number" : 42 ,
"name" : "john doe" ,
"included_file" : "INCLUDES.helper" ,
"included_file_not_exists" : "INCLUDES.helper.not_exists" ,
2019-04-21 17:58:15 +00:00
"nil" : nil ,
"uint" : uint ( 8 ) ,
"float" : float64 ( 3.1415 ) ,
"str" : "string" ,
"chinese_hello_world" : "你好世界" ,
"bool_true" : true ,
"bool_false" : false ,
2018-01-28 14:56:30 +00:00
"newline_text" : ` this is a text
with a new line in it ` ,
"long_text" : ` This is a simple text .
This too , as a paragraph .
Right ?
Yep ! ` ,
"escape_js_test" : ` escape sequences \r\n\'\" special chars "?!=$<> ` ,
"one_item_list" : [ ] int { 99 } ,
"multiple_item_list" : [ ] int { 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 } ,
"unsorted_int_list" : [ ] int { 192 , 581 , 22 , 1 , 249 , 9999 , 1828591 , 8271 } ,
"fixed_item_list" : [ ... ] int { 1 , 2 , 3 , 4 } ,
"misc_list" : [ ] interface { } { "Hello" , 99 , 3.14 , "good" } ,
"escape_text" : "This is \\a Test. \"Yep\". 'Yep'." ,
"xss" : "<script>alert(\"uh oh\");</script>" ,
"intmap" : map [ int ] string {
1 : "one" ,
5 : "five" ,
2 : "two" ,
} ,
"strmap" : map [ string ] string {
"abc" : "def" ,
"bcd" : "efg" ,
"zab" : "cde" ,
"gh" : "kqm" ,
"ukq" : "qqa" ,
"aab" : "aba" ,
} ,
"func_add" : func ( a , b int ) int {
return a + b
} ,
"func_add_iface" : func ( a , b interface { } ) interface { } {
return a . ( int ) + b . ( int )
} ,
"func_variadic" : func ( msg string , args ... interface { } ) string {
return fmt . Sprintf ( msg , args ... )
} ,
"func_variadic_sum_int" : func ( args ... int ) int {
// Create a sum
s := 0
for _ , i := range args {
s += i
}
return s
} ,
"func_variadic_sum_int2" : func ( args ... * pongo2 . Value ) * pongo2 . Value {
// Create a sum
s := 0
for _ , i := range args {
s += i . Integer ( )
}
return pongo2 . AsValue ( s )
} ,
} ,
"complex" : map [ string ] interface { } {
"is_admin" : isAdmin ,
"post" : post {
Text : "<h2>Hello!</h2><p>Welcome to my new blog page. I'm using pongo2 which supports {{ variables }} and {% tags %}.</p>" ,
Created : time2 ,
} ,
"comments" : [ ] * comment {
& comment {
Author : & user {
Name : "user1" ,
Validated : true ,
} ,
Date : time1 ,
Text : "\"pongo2 is nice!\"" ,
} ,
& comment {
Author : & user {
Name : "user2" ,
Validated : true ,
} ,
Date : time2 ,
Text : "comment2 with <script>unsafe</script> tags in it" ,
} ,
& comment {
Author : & user {
Name : "user3" ,
Validated : false ,
} ,
Date : time1 ,
Text : "<b>hello!</b> there" ,
} ,
} ,
"comments2" : [ ] * comment {
& comment {
Author : & user {
Name : "user1" ,
Validated : true ,
} ,
Date : time2 ,
Text : "\"pongo2 is nice!\"" ,
} ,
& comment {
Author : & user {
Name : "user1" ,
Validated : true ,
} ,
Date : time1 ,
Text : "comment2 with <script>unsafe</script> tags in it" ,
} ,
& comment {
Author : & user {
Name : "user3" ,
Validated : false ,
} ,
Date : time1 ,
Text : "<b>hello!</b> there" ,
} ,
} ,
} ,
}
2019-04-21 17:58:15 +00:00
func TestTemplate_Functions ( t * testing . T ) {
mydict := map [ string ] interface { } {
"foo" : "bar" ,
"foobar" : 8379 ,
}
tests := [ ] struct {
name string
template string
context pongo2 . Context
want string
errorMessage string
wantErr bool
} {
{
name : "NoError" ,
template : "{{ testFunc(mydict) }}" ,
context : pongo2 . Context {
"mydict" : mydict ,
"testFunc" : func ( i interface { } ) ( string , error ) {
d , err := json . Marshal ( i )
return string ( d ) , err
} ,
} ,
want : ` { "foo":"bar","foobar":8379} ` ,
wantErr : false ,
} ,
{
name : "WithError" ,
template : "{{ testFunc(mydict) }}" ,
context : pongo2 . Context {
"mydict" : mydict ,
"testFunc" : func ( i interface { } ) ( string , error ) {
return "" , errors . New ( "something went wrong" )
} ,
} ,
errorMessage : "[Error (where: execution) in <string> | Line 1 Col 4 near 'testFunc'] something went wrong" ,
wantErr : true ,
} ,
{
name : "TooMuchArguments" ,
template : "{{ testFunc(mydict) }}" ,
context : pongo2 . Context {
"mydict" : mydict ,
"testFunc" : func ( i interface { } ) ( string , int , error ) {
return "" , 0 , nil
} ,
} ,
errorMessage : "[Error (where: execution) in <string> | Line 1 Col 4 near 'testFunc'] 'testFunc' must have exactly 1 or 2 output arguments, the second argument must be of type error" ,
wantErr : true ,
} ,
{
name : "InvalidArguments" ,
template : "{{ testFunc(mydict) }}" ,
context : pongo2 . Context {
"mydict" : map [ string ] interface { } {
"foo" : "bar" ,
"foobar" : 8379 ,
} ,
"testFunc" : func ( i interface { } ) ( string , int ) {
return "" , 0
} ,
} ,
errorMessage : "[Error (where: execution) in <string> | Line 1 Col 4 near 'testFunc'] The second return value is not an error" ,
wantErr : true ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
tpl , _ := pongo2 . FromString ( "{{ testFunc(mydict) }}" )
got , err := tpl . Execute ( tt . context )
if err != nil {
if ! tt . wantErr {
t . Errorf ( "Template.Execute() error = %v, wantErr %v" , err , tt . wantErr )
return
}
if err . Error ( ) != tt . errorMessage {
t . Errorf ( "Template.Execute() error = %v, expected error %v" , err , tt . errorMessage )
return
}
}
if got != tt . want {
t . Errorf ( "Template.Execute() = %v, want %v" , got , tt . want )
}
} )
}
}
2018-01-28 14:56:30 +00:00
func TestTemplates ( t * testing . T ) {
// Add a global to the default set
pongo2 . Globals [ "this_is_a_global_variable" ] = "this is a global text"
matches , err := filepath . Glob ( "./template_tests/*.tpl" )
if err != nil {
t . Fatal ( err )
}
for idx , match := range matches {
2019-04-21 17:58:15 +00:00
t . Run ( fmt . Sprintf ( "%03d-%s" , idx + 1 , match ) , func ( t * testing . T ) {
tpl , err := pongo2 . FromFile ( match )
if err != nil {
t . Fatalf ( "Error on FromFile('%s'): %s" , match , err . Error ( ) )
}
testFilename := fmt . Sprintf ( "%s.out" , match )
testOut , rerr := ioutil . ReadFile ( testFilename )
if rerr != nil {
t . Fatalf ( "Error on ReadFile('%s'): %s" , testFilename , rerr . Error ( ) )
}
tplOut , err := tpl . ExecuteBytes ( tplContext )
2018-01-28 14:56:30 +00:00
if err != nil {
2019-04-21 17:58:15 +00:00
t . Fatalf ( "Error on Execute('%s'): %s" , match , err . Error ( ) )
}
tplOut = testTemplateFixes . fixIfNeeded ( match , tplOut )
if bytes . Compare ( testOut , tplOut ) != 0 {
t . Logf ( "Template (rendered) '%s': '%s'" , match , tplOut )
errFilename := filepath . Base ( fmt . Sprintf ( "%s.error" , match ) )
err := ioutil . WriteFile ( errFilename , [ ] byte ( tplOut ) , 0600 )
if err != nil {
t . Fatalf ( err . Error ( ) )
}
t . Logf ( "get a complete diff with command: 'diff -ya %s %s'" , testFilename , errFilename )
t . Errorf ( "Failed: test_out != tpl_out for %s" , match )
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
} )
}
}
type testTemplateFixesT map [ * regexp . Regexp ] func ( string ) string
func ( instance testTemplateFixesT ) fixIfNeeded ( name string , in [ ] byte ) [ ] byte {
out := string ( in )
for r , f := range instance {
if r . MatchString ( name ) {
out = f ( out )
2018-01-28 14:56:30 +00:00
}
}
2019-04-21 17:58:15 +00:00
return [ ] byte ( out )
}
var testTemplateFixes = testTemplateFixesT {
regexp . MustCompile ( ` .*template_tests[/\\]macro\.tpl ` ) : func ( in string ) string {
out := regexp . MustCompile ( ` (?:\.[/\\]|)(template_tests)[/\\](macro\.tpl) ` ) . ReplaceAllString ( in , "$1/$2" )
return out
} ,
2018-01-28 14:56:30 +00:00
}
func TestExecutionErrors ( t * testing . T ) {
//debug = true
matches , err := filepath . Glob ( "./template_tests/*-execution.err" )
if err != nil {
t . Fatal ( err )
}
for idx , match := range matches {
2019-04-21 17:58:15 +00:00
t . Run ( fmt . Sprintf ( "%03d-%s" , idx + 1 , match ) , func ( t * testing . T ) {
testData , err := ioutil . ReadFile ( match )
tests := strings . Split ( string ( testData ) , "\n" )
2018-01-28 14:56:30 +00:00
2019-04-21 17:58:15 +00:00
checkFilename := fmt . Sprintf ( "%s.out" , match )
checkData , err := ioutil . ReadFile ( checkFilename )
2018-01-28 14:56:30 +00:00
if err != nil {
2019-04-21 17:58:15 +00:00
t . Fatalf ( "Error on ReadFile('%s'): %s" , checkFilename , err . Error ( ) )
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
checks := strings . Split ( string ( checkData ) , "\n" )
2018-01-28 14:56:30 +00:00
2019-04-21 17:58:15 +00:00
if len ( checks ) != len ( tests ) {
t . Fatal ( "Template lines != Checks lines" )
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
for idx , test := range tests {
if strings . TrimSpace ( test ) == "" {
continue
}
if strings . TrimSpace ( checks [ idx ] ) == "" {
t . Fatalf ( "[%s Line %d] Check is empty (must contain an regular expression)." ,
match , idx + 1 )
}
tpl , err := pongo2 . FromString ( test )
if err != nil {
t . Fatalf ( "Error on FromString('%s'): %s" , test , err . Error ( ) )
}
tpl , err = pongo2 . FromBytes ( [ ] byte ( test ) )
if err != nil {
t . Fatalf ( "Error on FromBytes('%s'): %s" , test , err . Error ( ) )
}
_ , err = tpl . ExecuteBytes ( tplContext )
if err == nil {
t . Fatalf ( "[%s Line %d] Expected error for (got none): %s" ,
match , idx + 1 , tests [ idx ] )
}
re := regexp . MustCompile ( fmt . Sprintf ( "^%s$" , checks [ idx ] ) )
if ! re . MatchString ( err . Error ( ) ) {
t . Fatalf ( "[%s Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s" ,
match , idx + 1 , test , err . Error ( ) , checks [ idx ] )
}
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
} )
2018-01-28 14:56:30 +00:00
}
}
func TestCompilationErrors ( t * testing . T ) {
//debug = true
matches , err := filepath . Glob ( "./template_tests/*-compilation.err" )
if err != nil {
t . Fatal ( err )
}
for idx , match := range matches {
2019-04-21 17:58:15 +00:00
t . Run ( fmt . Sprintf ( "%03d-%s" , idx + 1 , match ) , func ( t * testing . T ) {
2018-01-28 14:56:30 +00:00
2019-04-21 17:58:15 +00:00
testData , err := ioutil . ReadFile ( match )
tests := strings . Split ( string ( testData ) , "\n" )
2018-01-28 14:56:30 +00:00
2019-04-21 17:58:15 +00:00
checkFilename := fmt . Sprintf ( "%s.out" , match )
checkData , err := ioutil . ReadFile ( checkFilename )
if err != nil {
t . Fatalf ( "Error on ReadFile('%s'): %s" , checkFilename , err . Error ( ) )
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
checks := strings . Split ( string ( checkData ) , "\n" )
2018-01-28 14:56:30 +00:00
2019-04-21 17:58:15 +00:00
if len ( checks ) != len ( tests ) {
t . Fatal ( "Template lines != Checks lines" )
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
for idx , test := range tests {
if strings . TrimSpace ( test ) == "" {
continue
}
if strings . TrimSpace ( checks [ idx ] ) == "" {
t . Fatalf ( "[%s Line %d] Check is empty (must contain an regular expression)." ,
match , idx + 1 )
}
_ , err = pongo2 . FromString ( test )
if err == nil {
t . Fatalf ( "[%s | Line %d] Expected error for (got none): %s" , match , idx + 1 , tests [ idx ] )
}
re := regexp . MustCompile ( fmt . Sprintf ( "^%s$" , checks [ idx ] ) )
if ! re . MatchString ( err . Error ( ) ) {
t . Fatalf ( "[%s | Line %d] Error for '%s' (err = '%s') does not match the (regexp-)check: %s" ,
match , idx + 1 , test , err . Error ( ) , checks [ idx ] )
}
2018-01-28 14:56:30 +00:00
}
2019-04-21 17:58:15 +00:00
} )
2018-01-28 14:56:30 +00:00
}
}
func TestBaseDirectory ( t * testing . T ) {
mustStr := "Hello from template_tests/base_dir_test/"
fs := pongo2 . MustNewLocalFileSystemLoader ( "" )
s := pongo2 . NewSet ( "test set with base directory" , fs )
s . Globals [ "base_directory" ] = "template_tests/base_dir_test/"
if err := fs . SetBaseDir ( s . Globals [ "base_directory" ] . ( string ) ) ; err != nil {
t . Fatal ( err )
}
matches , err := filepath . Glob ( "./template_tests/base_dir_test/subdir/*" )
if err != nil {
t . Fatal ( err )
}
for _ , match := range matches {
2019-04-21 17:58:15 +00:00
match = strings . Replace ( match , fmt . Sprintf ( "template_tests%cbase_dir_test%c" , filepath . Separator , filepath . Separator ) , "" , - 1 )
2018-01-28 14:56:30 +00:00
tpl , err := s . FromFile ( match )
if err != nil {
t . Fatal ( err )
}
out , err := tpl . Execute ( nil )
if err != nil {
t . Fatal ( err )
}
if out != mustStr {
t . Errorf ( "%s: out ('%s') != mustStr ('%s')" , match , out , mustStr )
}
}
}
func BenchmarkCache ( b * testing . B ) {
cacheSet := pongo2 . NewSet ( "cache set" , pongo2 . MustNewLocalFileSystemLoader ( "" ) )
for i := 0 ; i < b . N ; i ++ {
tpl , err := cacheSet . FromCache ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkCacheDebugOn ( b * testing . B ) {
cacheDebugSet := pongo2 . NewSet ( "cache set" , pongo2 . MustNewLocalFileSystemLoader ( "" ) )
cacheDebugSet . Debug = true
for i := 0 ; i < b . N ; i ++ {
tpl , err := cacheDebugSet . FromFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkExecuteComplexWithSandboxActive ( b * testing . B ) {
tpl , err := pongo2 . FromFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkCompileAndExecuteComplexWithSandboxActive ( b * testing . B ) {
buf , err := ioutil . ReadFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
preloadedTpl := string ( buf )
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
tpl , err := pongo2 . FromString ( preloadedTpl )
if err != nil {
b . Fatal ( err )
}
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkParallelExecuteComplexWithSandboxActive ( b * testing . B ) {
tpl , err := pongo2 . FromFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
b . ResetTimer ( )
b . RunParallel ( func ( pb * testing . PB ) {
for pb . Next ( ) {
err := tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
} )
}
func BenchmarkExecuteComplexWithoutSandbox ( b * testing . B ) {
s := pongo2 . NewSet ( "set without sandbox" , pongo2 . MustNewLocalFileSystemLoader ( "" ) )
tpl , err := s . FromFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkCompileAndExecuteComplexWithoutSandbox ( b * testing . B ) {
buf , err := ioutil . ReadFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
preloadedTpl := string ( buf )
s := pongo2 . NewSet ( "set without sandbox" , pongo2 . MustNewLocalFileSystemLoader ( "" ) )
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
tpl , err := s . FromString ( preloadedTpl )
if err != nil {
b . Fatal ( err )
}
err = tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
}
func BenchmarkParallelExecuteComplexWithoutSandbox ( b * testing . B ) {
s := pongo2 . NewSet ( "set without sandbox" , pongo2 . MustNewLocalFileSystemLoader ( "" ) )
tpl , err := s . FromFile ( "template_tests/complex.tpl" )
if err != nil {
b . Fatal ( err )
}
b . ResetTimer ( )
b . RunParallel ( func ( pb * testing . PB ) {
for pb . Next ( ) {
err := tpl . ExecuteWriterUnbuffered ( tplContext , ioutil . Discard )
if err != nil {
b . Fatal ( err )
}
}
} )
}