2015-01-21 19:07:45 +00:00
// Copyright 2014 The Prometheus Authors
2014-05-28 17:44:54 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-05-28 19:22:08 +00:00
package template
2014-05-28 17:44:54 +00:00
import (
2017-10-25 04:21:42 +00:00
"context"
2015-03-28 18:51:41 +00:00
"math"
2017-05-13 13:47:04 +00:00
"net/url"
2014-05-28 17:44:54 +00:00
"testing"
2017-11-23 12:04:54 +00:00
"time"
2014-05-28 17:44:54 +00:00
2020-10-29 09:43:23 +00:00
"github.com/stretchr/testify/require"
2020-10-22 09:00:08 +00:00
2016-12-25 10:34:22 +00:00
"github.com/prometheus/prometheus/pkg/labels"
2015-03-30 17:43:19 +00:00
"github.com/prometheus/prometheus/promql"
2014-05-28 17:44:54 +00:00
)
func TestTemplateExpansion ( t * testing . T ) {
2019-05-03 13:11:28 +00:00
scenarios := [ ] struct {
text string
output string
input interface { }
queryResult promql . Vector
shouldFail bool
html bool
errorMsg string
} {
2014-05-28 17:44:54 +00:00
{
// No template.
text : "plain text" ,
output : "plain text" ,
} ,
{
// Simple value.
text : "{{ 1 }}" ,
output : "1" ,
} ,
2018-02-17 07:57:25 +00:00
{
// Non-ASCII space (not allowed in text/template, see https://github.com/golang/go/blob/master/src/text/template/parse/lex.go#L98)
text : "{{ }}" ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : "error parsing template test: template: test:1: unrecognized character in action: U+00A0" ,
2018-02-17 07:57:25 +00:00
} ,
2014-06-10 14:30:06 +00:00
{
// HTML escaping.
text : "{{ \"<b>\" }}" ,
output : "<b>" ,
html : true ,
} ,
{
// Disabling HTML escaping.
text : "{{ \"<b>\" | safeHtml }}" ,
output : "<b>" ,
html : true ,
} ,
{
// HTML escaping doesn't apply to non-html.
text : "{{ \"<b>\" }}" ,
output : "<b>" ,
} ,
{
// Pass multiple arguments to templates.
text : "{{define \"x\"}}{{.arg0}} {{.arg1}}{{end}}{{template \"x\" (args 1 \"2\")}}" ,
output : "1 2" ,
} ,
2015-05-11 07:12:28 +00:00
{
2017-11-23 12:04:54 +00:00
text : "{{ query \"1.5\" | first | value }}" ,
output : "1.5" ,
queryResult : promql . Vector { { Point : promql . Point { T : 0 , V : 1.5 } } } ,
2015-05-11 07:12:28 +00:00
} ,
2014-05-28 17:44:54 +00:00
{
// Get value from query.
2017-11-23 12:04:54 +00:00
text : "{{ query \"metric{instance='a'}\" | first | value }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
2014-05-28 17:44:54 +00:00
output : "11" ,
} ,
{
// Get label from query.
2017-11-23 12:04:54 +00:00
text : "{{ query \"metric{instance='a'}\" | first | label \"instance\" }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
2014-05-28 17:44:54 +00:00
output : "a" ,
} ,
2020-07-09 08:43:32 +00:00
{
// Get label "__value__" from query.
text : "{{ query \"metric{__value__='a'}\" | first | strvalue }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "__value__" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
output : "a" ,
} ,
2015-11-28 13:45:32 +00:00
{
// Missing label is empty when using label function.
2017-11-23 12:04:54 +00:00
text : "{{ query \"metric{instance='a'}\" | first | label \"foo\" }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
2015-11-28 13:45:32 +00:00
output : "" ,
} ,
{
// Missing label is empty when not using label function.
2017-11-23 12:04:54 +00:00
text : "{{ $x := query \"metric\" | first }}{{ $x.Labels.foo }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
2015-11-28 13:45:32 +00:00
output : "" ,
} ,
{
2017-11-23 12:04:54 +00:00
text : "{{ $x := query \"metric\" | first }}{{ $x.Labels.foo }}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
} } ,
2015-11-28 13:45:32 +00:00
output : "" ,
html : true ,
} ,
2014-05-28 17:44:54 +00:00
{
2014-08-05 18:56:05 +00:00
// Range over query and sort by label.
2017-11-23 12:04:54 +00:00
text : "{{ range query \"metric\" | sortByLabel \"instance\" }}{{.Labels.instance}}:{{.Value}}: {{end}}" ,
queryResult : promql . Vector {
{
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "b" ) ,
Point : promql . Point { T : 0 , V : 21 } ,
2020-07-09 08:43:32 +00:00
} , {
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
2017-11-23 12:04:54 +00:00
} } ,
2014-05-28 17:44:54 +00:00
output : "a:11: b:21: " ,
} ,
{
// Unparsable template.
text : "{{" ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : "error parsing template test: template: test:1: unclosed action" ,
2014-05-28 17:44:54 +00:00
} ,
{
// Error in function.
2017-11-23 12:04:54 +00:00
text : "{{ query \"missing\" | first }}" ,
queryResult : promql . Vector { } ,
shouldFail : true ,
2018-02-17 07:57:25 +00:00
errorMsg : "error executing template test: template: test:1:21: executing \"test\" at <first>: error calling first: first() called on vector with no elements" ,
2014-05-28 17:44:54 +00:00
} ,
{
// Panic.
2017-11-23 12:04:54 +00:00
text : "{{ (query \"missing\").banana }}" ,
queryResult : promql . Vector { } ,
shouldFail : true ,
2018-02-17 07:57:25 +00:00
errorMsg : "error executing template test: template: test:1:10: executing \"test\" at <\"missing\">: can't evaluate field banana in type template.queryResult" ,
2014-05-28 17:44:54 +00:00
} ,
2014-06-05 13:07:54 +00:00
{
// Regex replacement.
text : "{{ reReplaceAll \"(a)b\" \"x$1\" \"ab\" }}" ,
output : "xa" ,
} ,
{
2021-04-13 22:30:15 +00:00
// Humanize - float64.
2014-06-11 10:32:19 +00:00
text : "{{ range . }}{{ humanize . }}:{{ end }}" ,
input : [ ] float64 { 0.0 , 1.0 , 1234567.0 , .12 } ,
output : "0:1:1.235M:120m:" ,
2014-06-05 13:07:54 +00:00
} ,
{
2021-04-13 22:30:15 +00:00
// Humanize - string.
text : "{{ range . }}{{ humanize . }}:{{ end }}" ,
input : [ ] string { "0.0" , "1.0" , "1234567.0" , ".12" } ,
output : "0:1:1.235M:120m:" ,
} ,
{
// Humanize - string with error.
text : ` {{ humanize "one" }} ` ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : ` error executing template test: template: test:1:3: executing "test" at <humanize "one">: error calling humanize: strconv.ParseFloat: parsing "one": invalid syntax ` ,
2021-04-13 22:30:15 +00:00
} ,
{
// Humanize1024 - float64.
2014-06-11 10:32:19 +00:00
text : "{{ range . }}{{ humanize1024 . }}:{{ end }}" ,
input : [ ] float64 { 0.0 , 1.0 , 1048576.0 , .12 } ,
output : "0:1:1Mi:0.12:" ,
} ,
{
2021-04-13 22:30:15 +00:00
// Humanize1024 - string.
text : "{{ range . }}{{ humanize1024 . }}:{{ end }}" ,
input : [ ] string { "0.0" , "1.0" , "1048576.0" , ".12" } ,
output : "0:1:1Mi:0.12:" ,
} ,
{
// Humanize1024 - string with error.
text : ` {{ humanize1024 "one" }} ` ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : ` error executing template test: template: test:1:3: executing "test" at <humanize1024 "one">: error calling humanize1024: strconv.ParseFloat: parsing "one": invalid syntax ` ,
2021-04-13 22:30:15 +00:00
} ,
{
// HumanizeDuration - seconds - float64.
2014-06-11 10:32:19 +00:00
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
2015-02-26 17:17:04 +00:00
input : [ ] float64 { 0 , 1 , 60 , 3600 , 86400 , 86400 + 3600 , - ( 86400 * 2 + 3600 * 3 + 60 * 4 + 5 ) , 899.99 } ,
output : "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:1d 1h 0m 0s:-2d 3h 4m 5s:14m 59s:" ,
2014-06-11 10:32:19 +00:00
} ,
{
2021-04-13 22:30:15 +00:00
// HumanizeDuration - seconds - string.
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
input : [ ] string { "0" , "1" , "60" , "3600" , "86400" } ,
output : "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:" ,
} ,
{
// HumanizeDuration - subsecond and fractional seconds - float64.
2014-06-11 10:32:19 +00:00
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
input : [ ] float64 { .1 , .0001 , .12345 , 60.1 , 60.5 , 1.2345 , 12.345 } ,
2015-02-26 17:17:04 +00:00
output : "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:" ,
2014-06-05 13:07:54 +00:00
} ,
2015-03-28 18:51:41 +00:00
{
2021-04-13 22:30:15 +00:00
// HumanizeDuration - subsecond and fractional seconds - string.
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
input : [ ] string { ".1" , ".0001" , ".12345" , "60.1" , "60.5" , "1.2345" , "12.345" } ,
output : "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:" ,
} ,
{
// HumanizeDuration - string with error.
text : ` {{ humanizeDuration "one" }} ` ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : ` error executing template test: template: test:1:3: executing "test" at <humanizeDuration "one">: error calling humanizeDuration: strconv.ParseFloat: parsing "one": invalid syntax ` ,
2021-04-13 22:30:15 +00:00
} ,
{
// Humanize* Inf and NaN - float64.
2015-06-23 15:46:57 +00:00
text : "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}" ,
2015-03-28 18:51:41 +00:00
input : [ ] float64 { math . Inf ( 1 ) , math . Inf ( - 1 ) , math . NaN ( ) } ,
2015-06-23 15:46:57 +00:00
output : "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:" ,
} ,
2019-06-15 07:59:57 +00:00
{
2021-04-13 22:30:15 +00:00
// Humanize* Inf and NaN - string.
text : "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}" ,
input : [ ] string { "+Inf" , "-Inf" , "NaN" } ,
output : "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:" ,
} ,
{
// HumanizePercentage - model.SampleValue input - float64.
2019-06-15 07:59:57 +00:00
text : "{{ -0.22222 | humanizePercentage }}:{{ 0.0 | humanizePercentage }}:{{ 0.1234567 | humanizePercentage }}:{{ 1.23456 | humanizePercentage }}" ,
output : "-22.22%:0%:12.35%:123.5%" ,
} ,
2015-06-23 15:46:57 +00:00
{
2021-04-13 22:30:15 +00:00
// HumanizePercentage - model.SampleValue input - string.
text : ` {{ "-0.22222" | humanizePercentage }} : {{ "0.0" | humanizePercentage }} : {{ "0.1234567" | humanizePercentage }} : {{ "1.23456" | humanizePercentage }} ` ,
output : "-22.22%:0%:12.35%:123.5%" ,
} ,
{
// HumanizePercentage - model.SampleValue input - string with error.
text : ` {{ "one" | humanizePercentage }} ` ,
shouldFail : true ,
2021-09-02 14:49:55 +00:00
errorMsg : ` error executing template test: template: test:1:11: executing "test" at <humanizePercentage>: error calling humanizePercentage: strconv.ParseFloat: parsing "one": invalid syntax ` ,
2021-04-13 22:30:15 +00:00
} ,
{
// HumanizeTimestamp - model.SampleValue input - float64.
2015-06-23 15:46:57 +00:00
text : "{{ 1435065584.128 | humanizeTimestamp }}" ,
2015-06-23 19:15:32 +00:00
output : "2015-06-23 13:19:44.128 +0000 UTC" ,
2015-03-28 18:51:41 +00:00
} ,
2021-04-13 22:30:15 +00:00
{
// HumanizeTimestamp - model.SampleValue input - string.
text : ` {{ "1435065584.128" | humanizeTimestamp }} ` ,
output : "2015-06-23 13:19:44.128 +0000 UTC" ,
} ,
2014-06-05 17:44:19 +00:00
{
// Title.
text : "{{ \"aa bb CC\" | title }}" ,
output : "Aa Bb CC" ,
} ,
2016-08-15 11:00:22 +00:00
{
// toUpper.
text : "{{ \"aa bb CC\" | toUpper }}" ,
output : "AA BB CC" ,
} ,
{
// toLower.
text : "{{ \"aA bB CC\" | toLower }}" ,
output : "aa bb cc" ,
} ,
2014-06-05 17:44:19 +00:00
{
// Match.
text : "{{ match \"a+\" \"aa\" }} {{ match \"a+\" \"b\" }}" ,
output : "true false" ,
} ,
2014-07-25 12:23:47 +00:00
{
// graphLink.
text : "{{ graphLink \"up\" }}" ,
2016-09-03 19:05:23 +00:00
output : "/graph?g0.expr=up&g0.tab=0" ,
2014-07-25 12:23:47 +00:00
} ,
{
// tableLink.
text : "{{ tableLink \"up\" }}" ,
2016-09-03 19:05:23 +00:00
output : "/graph?g0.expr=up&g0.tab=1" ,
2014-07-25 12:23:47 +00:00
} ,
2014-07-25 16:32:17 +00:00
{
// tmpl.
text : "{{ define \"a\" }}x{{ end }}{{ $name := \"a\"}}{{ tmpl $name . }}" ,
output : "x" ,
html : true ,
} ,
2017-05-13 13:47:04 +00:00
{
// pathPrefix.
text : "{{ pathPrefix }}" ,
output : "/path/prefix" ,
} ,
{
// externalURL.
text : "{{ externalURL }}" ,
output : "http://testhost:9090/path/prefix" ,
} ,
2014-05-28 17:44:54 +00:00
}
2017-05-13 13:47:04 +00:00
extURL , err := url . Parse ( "http://testhost:9090/path/prefix" )
if err != nil {
panic ( err )
}
2018-03-29 15:02:28 +00:00
for _ , s := range scenarios {
2017-11-23 12:04:54 +00:00
queryFunc := func ( _ context . Context , _ string , _ time . Time ) ( promql . Vector , error ) {
return s . queryResult , nil
}
2014-06-10 14:30:06 +00:00
var result string
var err error
2017-11-23 12:04:54 +00:00
expander := NewTemplateExpander ( context . Background ( ) , s . text , "test" , s . input , 0 , queryFunc , extURL )
2014-06-10 14:30:06 +00:00
if s . html {
result , err = expander . ExpandHTML ( nil )
} else {
result , err = expander . Expand ( )
}
2014-05-28 17:44:54 +00:00
if s . shouldFail {
2020-10-29 09:43:23 +00:00
require . Error ( t , err , "%v" , s . text )
2021-09-02 14:49:55 +00:00
require . EqualError ( t , err , s . errorMsg )
2014-05-28 17:44:54 +00:00
continue
}
2018-03-29 15:02:28 +00:00
2020-10-29 09:43:23 +00:00
require . NoError ( t , err )
2018-03-29 15:02:28 +00:00
if err == nil {
2020-10-29 09:43:23 +00:00
require . Equal ( t , result , s . output )
2014-05-28 17:44:54 +00:00
}
}
}