2015-01-21 11:07:45 -08:00
// Copyright 2014 The Prometheus Authors
2014-05-28 10:44:54 -07: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 12:22:08 -07:00
package template
2014-05-28 10:44:54 -07:00
import (
2017-10-24 21:21:42 -07:00
"context"
2015-03-28 11:51:41 -07:00
"math"
2017-05-13 06:47:04 -07:00
"net/url"
2014-05-28 10:44:54 -07:00
"testing"
2017-11-23 04:04:54 -08:00
"time"
2014-05-28 10:44:54 -07:00
2020-10-22 02:00:08 -07:00
"github.com/stretchr/testify/assert"
2016-12-25 02:34:22 -08:00
"github.com/prometheus/prometheus/pkg/labels"
2015-03-30 10:43:19 -07:00
"github.com/prometheus/prometheus/promql"
2014-05-28 10:44:54 -07:00
)
func TestTemplateExpansion ( t * testing . T ) {
2019-05-03 06:11:28 -07:00
scenarios := [ ] struct {
text string
output string
input interface { }
queryResult promql . Vector
shouldFail bool
html bool
errorMsg string
} {
2014-05-28 10:44:54 -07:00
{
// No template.
text : "plain text" ,
output : "plain text" ,
} ,
{
// Simple value.
text : "{{ 1 }}" ,
output : "1" ,
} ,
2018-02-16 23:57:25 -08: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 ,
errorMsg : "error parsing template test: template: test:1: unexpected unrecognized character in action: U+00A0 in command" ,
} ,
2014-06-10 07:30:06 -07: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 00:12:28 -07:00
{
2017-11-23 04:04:54 -08:00
text : "{{ query \"1.5\" | first | value }}" ,
output : "1.5" ,
queryResult : promql . Vector { { Point : promql . Point { T : 0 , V : 1.5 } } } ,
2015-05-11 00:12:28 -07:00
} ,
2014-05-28 10:44:54 -07:00
{
// Get value from query.
2017-11-23 04:04:54 -08: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 10:44:54 -07:00
output : "11" ,
} ,
{
// Get label from query.
2017-11-23 04:04:54 -08: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 10:44:54 -07:00
output : "a" ,
} ,
2020-07-09 01:43:32 -07: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 05:45:32 -08:00
{
// Missing label is empty when using label function.
2017-11-23 04:04:54 -08: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 05:45:32 -08:00
output : "" ,
} ,
{
// Missing label is empty when not using label function.
2017-11-23 04:04:54 -08: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 05:45:32 -08:00
output : "" ,
} ,
{
2017-11-23 04:04:54 -08: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 05:45:32 -08:00
output : "" ,
html : true ,
} ,
2014-05-28 10:44:54 -07:00
{
2014-08-05 11:56:05 -07:00
// Range over query and sort by label.
2017-11-23 04:04:54 -08: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 01:43:32 -07:00
} , {
Metric : labels . FromStrings ( labels . MetricName , "metric" , "instance" , "a" ) ,
Point : promql . Point { T : 0 , V : 11 } ,
2017-11-23 04:04:54 -08:00
} } ,
2014-05-28 10:44:54 -07:00
output : "a:11: b:21: " ,
} ,
{
// Unparsable template.
text : "{{" ,
shouldFail : true ,
2018-02-16 23:57:25 -08:00
errorMsg : "error parsing template test: template: test:1: unexpected unclosed action in command" ,
2014-05-28 10:44:54 -07:00
} ,
{
// Error in function.
2017-11-23 04:04:54 -08:00
text : "{{ query \"missing\" | first }}" ,
queryResult : promql . Vector { } ,
shouldFail : true ,
2018-02-16 23:57:25 -08: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 10:44:54 -07:00
} ,
{
// Panic.
2017-11-23 04:04:54 -08:00
text : "{{ (query \"missing\").banana }}" ,
queryResult : promql . Vector { } ,
shouldFail : true ,
2018-02-16 23:57:25 -08: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 10:44:54 -07:00
} ,
2014-06-05 06:07:54 -07:00
{
// Regex replacement.
text : "{{ reReplaceAll \"(a)b\" \"x$1\" \"ab\" }}" ,
output : "xa" ,
} ,
{
// Humanize.
2014-06-11 03:32:19 -07:00
text : "{{ range . }}{{ humanize . }}:{{ end }}" ,
input : [ ] float64 { 0.0 , 1.0 , 1234567.0 , .12 } ,
output : "0:1:1.235M:120m:" ,
2014-06-05 06:07:54 -07:00
} ,
{
// Humanize1024.
2014-06-11 03:32:19 -07:00
text : "{{ range . }}{{ humanize1024 . }}:{{ end }}" ,
input : [ ] float64 { 0.0 , 1.0 , 1048576.0 , .12 } ,
output : "0:1:1Mi:0.12:" ,
} ,
{
// HumanizeDuration - seconds.
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
2015-02-26 09:17:04 -08: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 03:32:19 -07:00
} ,
{
// HumanizeDuration - subsecond and fractional seconds.
text : "{{ range . }}{{ humanizeDuration . }}:{{ end }}" ,
input : [ ] float64 { .1 , .0001 , .12345 , 60.1 , 60.5 , 1.2345 , 12.345 } ,
2015-02-26 09:17:04 -08:00
output : "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:" ,
2014-06-05 06:07:54 -07:00
} ,
2015-03-28 11:51:41 -07:00
{
// Humanize* Inf and NaN.
2015-06-23 08:46:57 -07:00
text : "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}" ,
2015-03-28 11:51:41 -07:00
input : [ ] float64 { math . Inf ( 1 ) , math . Inf ( - 1 ) , math . NaN ( ) } ,
2015-06-23 08:46:57 -07:00
output : "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:" ,
} ,
2019-06-15 00:59:57 -07:00
{
// HumanizePercentage - model.SampleValue input.
text : "{{ -0.22222 | humanizePercentage }}:{{ 0.0 | humanizePercentage }}:{{ 0.1234567 | humanizePercentage }}:{{ 1.23456 | humanizePercentage }}" ,
output : "-22.22%:0%:12.35%:123.5%" ,
} ,
2015-06-23 08:46:57 -07:00
{
2015-08-20 08:18:46 -07:00
// HumanizeTimestamp - model.SampleValue input.
2015-06-23 08:46:57 -07:00
text : "{{ 1435065584.128 | humanizeTimestamp }}" ,
2015-06-23 12:15:32 -07:00
output : "2015-06-23 13:19:44.128 +0000 UTC" ,
2015-03-28 11:51:41 -07:00
} ,
2014-06-05 10:44:19 -07:00
{
// Title.
text : "{{ \"aa bb CC\" | title }}" ,
output : "Aa Bb CC" ,
} ,
2016-08-15 04:00:22 -07:00
{
// toUpper.
text : "{{ \"aa bb CC\" | toUpper }}" ,
output : "AA BB CC" ,
} ,
{
// toLower.
text : "{{ \"aA bB CC\" | toLower }}" ,
output : "aa bb cc" ,
} ,
2014-06-05 10:44:19 -07:00
{
// Match.
text : "{{ match \"a+\" \"aa\" }} {{ match \"a+\" \"b\" }}" ,
output : "true false" ,
} ,
2014-07-25 05:23:47 -07:00
{
// graphLink.
text : "{{ graphLink \"up\" }}" ,
2016-09-03 12:05:23 -07:00
output : "/graph?g0.expr=up&g0.tab=0" ,
2014-07-25 05:23:47 -07:00
} ,
{
// tableLink.
text : "{{ tableLink \"up\" }}" ,
2016-09-03 12:05:23 -07:00
output : "/graph?g0.expr=up&g0.tab=1" ,
2014-07-25 05:23:47 -07:00
} ,
2014-07-25 09:32:17 -07:00
{
// tmpl.
text : "{{ define \"a\" }}x{{ end }}{{ $name := \"a\"}}{{ tmpl $name . }}" ,
output : "x" ,
html : true ,
} ,
2017-05-13 06:47:04 -07:00
{
// pathPrefix.
text : "{{ pathPrefix }}" ,
output : "/path/prefix" ,
} ,
{
// externalURL.
text : "{{ externalURL }}" ,
output : "http://testhost:9090/path/prefix" ,
} ,
2014-05-28 10:44:54 -07:00
}
2017-05-13 06:47:04 -07:00
extURL , err := url . Parse ( "http://testhost:9090/path/prefix" )
if err != nil {
panic ( err )
}
2018-03-29 08:02:28 -07:00
for _ , s := range scenarios {
2017-11-23 04:04:54 -08:00
queryFunc := func ( _ context . Context , _ string , _ time . Time ) ( promql . Vector , error ) {
return s . queryResult , nil
}
2014-06-10 07:30:06 -07:00
var result string
var err error
2017-11-23 04:04:54 -08:00
expander := NewTemplateExpander ( context . Background ( ) , s . text , "test" , s . input , 0 , queryFunc , extURL )
2014-06-10 07:30:06 -07:00
if s . html {
result , err = expander . ExpandHTML ( nil )
} else {
result , err = expander . Expand ( )
}
2014-05-28 10:44:54 -07:00
if s . shouldFail {
2020-10-22 02:00:08 -07:00
assert . Error ( t , err , "%v" , s . text )
2014-05-28 10:44:54 -07:00
continue
}
2018-03-29 08:02:28 -07:00
2020-10-22 02:00:08 -07:00
assert . NoError ( t , err )
2018-03-29 08:02:28 -07:00
if err == nil {
2020-10-22 02:00:08 -07:00
assert . Equal ( t , result , s . output )
2014-05-28 10:44:54 -07:00
}
}
}