package graphite

import (
	"fmt"
	"math"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/graphiteql"
)

func TestExecExprSuccess(t *testing.T) {
	ec := &evalConfig{
		startTime:   120e3,
		endTime:     210e3,
		storageStep: 30e3,
		currentTime: time.Unix(150e3, 0),
	}
	f := func(query string, expectedSeries []*series) {
		t.Helper()
		ecCopy := *ec
		nextSeries, err := execExpr(&ecCopy, query)
		if err != nil {
			t.Fatalf("unexpected error in execExpr(%q): %s", query, err)
		}
		ss, err := fetchAllSeries(nextSeries)
		if err != nil {
			t.Fatalf("cannot fetch all series: %s", err)
		}
		expr, err := graphiteql.Parse(query)
		if err != nil {
			t.Fatalf("cannot parse query %q: %s", query, err)
		}
		if err := compareSeries(ss, expectedSeries, expr); err != nil {
			t.Fatalf("series mismatch for query %q: %s\ngot series\n%s\nexpected series\n%s", query, err, printSeriess(ss), printSeriess(expectedSeries))
		}
		// make sure ec isn't changed during query execution.
		if !reflect.DeepEqual(ec, &ecCopy) {
			t.Fatalf("unexpected ec\ngot\n%v\nwant\n%v", &ecCopy, ec)
		}
	}

	f("absolute(constantLine(-1.23))", []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{1.23, 1.23, 1.23},
			Name:       "absolute(-1.23)",
			Tags:       map[string]string{"name": "-1.23", "absolute": "1"},
		},
	})
	f("add(constantLine(1.23), 4.57)", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{5.8, 5.8, 5.8},
			Name:           "add(1.23,4.57)",
			Tags:           map[string]string{"name": "1.23", "add": "4.57"},
			pathExpression: "add(1.23,4.57)",
		},
	})
	f("add(constantLine(-123), constant=-457)", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{-580, -580, -580},
			Name:           "add(-123,-457)",
			Tags:           map[string]string{"name": "-123", "add": "-457"},
			pathExpression: "add(-123,-457)",
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			constantLine(2)|alias("bar;aa=bb")
		),
		"sum"
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{3, 3},
			Name:       "sumSeries(constantLine(1),constantLine(2))",
			Tags:       map[string]string{"name": "sumSeries(constantLine(1),constantLine(2))", "aggregatedBy": "sum"},
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			time("bar", 10),
		),
		"count",
		xFilesFactor = 1,
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{2, 2},
			Name:       "countSeries(bar,constantLine(1))",
			Tags:       map[string]string{"name": "countSeries(bar,constantLine(1))", "aggregatedBy": "count"},
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			time("bar", 10)
		),
		"avg_zero"
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{70.5, 93},
			Name:       "avg_zeroSeries(bar,constantLine(1))",
			Tags:       map[string]string{"name": "avg_zeroSeries(bar,constantLine(1))", "aggregatedBy": "avg_zero"},
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			time("bar", 10)
		),
		"min"
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1, 1},
			Name:       "minSeries(bar,constantLine(1))",
			Tags:       map[string]string{"name": "minSeries(bar,constantLine(1))", "aggregatedBy": "min"},
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			time("bar", 10),
		),
		"diff",
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{-139, -184},
			Name:       "diffSeries(constantLine(1),bar)",
			Tags:       map[string]string{"name": "diffSeries(constantLine(1),bar)", "aggregatedBy": "diff"},
		},
	})
	f(`aggregate(
		group(
			constantLine(1)|alias("foo"),
			time("bar", 10),
		),
		"range",
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{139, 184},
			Name:       "rangeSeries(bar,constantLine(1))",
			Tags:       map[string]string{"name": "rangeSeries(bar,constantLine(1))", "aggregatedBy": "range"},
		},
	})
	f(`aggregate(
		group(
			constantLine(2)|alias("foo"),
			time("bar", 10),
		),
		"multiply",
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{280, 370},
			Name:       "multiplySeries(bar,constantLine(2))",
			Tags:       map[string]string{"name": "multiplySeries(bar,constantLine(2))", "aggregatedBy": "multiply"},
		},
	})
	f(`aggregate(
		group(
			constantLine(2)|alias("foo"),
			time("bar", 10),
		),
		"first",
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{2, 2},
			Name:       "firstSeries(constantLine(2),bar)",
			Tags:       map[string]string{"name": "firstSeries(constantLine(2),bar)", "aggregatedBy": "first"},
		},
	})
	f(`aggregate(
		group(
			constantLine(2)|alias("foo"),
			time("bar", 10),
		),
		"last",
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{140, 185},
			Name:       "lastSeries(constantLine(2),bar)",
			Tags:       map[string]string{"name": "lastSeries(constantLine(2),bar)", "aggregatedBy": "last"},
		},
	})
	f("aggregate(group(),'avg')", []*series{})
	f(`aggregateLine(
		group(
			time("foo", 10),
			time("bar", 25),
		)
	)`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{165, 165, 165},
			Name:       "aggregateLine(foo,165)",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{157.5, 157.5, 157.5},
			Name:       "aggregateLine(bar,157.5)",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`aggregateLine(constantLine(1),"count")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{3, 3, 3},
			Name:       "aggregateLine(1,3)",
			Tags:       map[string]string{"name": "1"},
		},
	})
	f(`aggregateLine(time('foo',10),"median")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{170, 170, 170},
			Name:       "aggregateLine(foo,170)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time('foo',10),"max")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{210, 210, 210},
			Name:       "aggregateLine(foo,210)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time('foo',10),"diff")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{-1410, -1410, -1410},
			Name:       "aggregateLine(foo,-1410)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time('foo',10),"stddev")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{28.722813232690143, 28.722813232690143, 28.722813232690143},
			Name:       "aggregateLine(foo,28.722813232690143)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time('foo',10),"range")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{90, 90, 90},
			Name:       "aggregateLine(foo,90)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time('foo',10),"multiply")`, []*series{
		{
			Timestamps: []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:     []float64{1.2799358208e+22, 1.2799358208e+22, 1.2799358208e+22},
			Name:       "aggregateLine(foo,1.2799358208e+22)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateLine(time("foo",20),func="min",keepStep=True)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 120, 120, 120, 120},
			Name:       "aggregateLine(foo,120)",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`aggregateWithWildcards(
		group(
			time("foo.bar", 30),
			time("foo.baz", 60)
		),
		func='max'
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, nan, 180},
			Name:       "foo.baz",
			Tags:       map[string]string{"name": "foo.baz", "aggregatedBy": "max"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "foo.bar",
			Tags:       map[string]string{"name": "foo.bar", "aggregatedBy": "max"},
		},
	})
	f(`aggregateWithWildcards(
		group(
			time("foo.bar", 30),
			time("foo.baz", 60)
		),
		func='median',
		1
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo",
			Tags:           map[string]string{"name": "medianSeries(foo.bar,foo.baz)", "aggregatedBy": "median"},
			pathExpression: "medianSeries(foo.bar,foo.baz)",
		},
	})
	f(`aggregateWithWildcards(
		group(
			time("foo.bar", 30),
			time("foo.baz", 60)
		),
		func='stddev',
		1, 0, 2
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{0, 0, 0},
			Name:           "",
			Tags:           map[string]string{"aggregatedBy": "stddev", "name": "stddevSeries(foo.bar,foo.baz)"},
			pathExpression: "stddevSeries(foo.bar,foo.baz)",
		},
	})

	f("alias(constantLine(123), 'foo.bar;baz=aaa')", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{123, 123, 123},
			Name:           "foo.bar;baz=aaa",
			Tags:           map[string]string{"name": "123"},
			pathExpression: "constantLine(123)",
		},
	})
	f(`aliasByMetric(
		group(
			time("foo.bar.baz;x=y"),
			time("aaa.bb", 30),
			summarize(group(time('a'),time('c.d.b')),'30s'),
		)
	)`, []*series{
		{
			Timestamps:     []int64{ec.startTime, ec.startTime + 60*1000},
			Values:         []float64{120, 180},
			Name:           "baz;x=y",
			Tags:           map[string]string{"name": "foo.bar.baz", "x": "y"},
			pathExpression: "foo.bar.baz;x=y",
		},
		{
			Timestamps:     []int64{ec.startTime, ec.startTime + 30*1000, ec.startTime + 60*1000, ec.startTime + 90*1000},
			Values:         []float64{120, 150, 180, 210},
			Name:           "bb",
			Tags:           map[string]string{"name": "aaa.bb"},
			pathExpression: "aaa.bb",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{120, nan, 180, nan},
			Name:           "a",
			Tags:           map[string]string{"name": "a", "summarize": "30s", "summarizeFunction": "sum"},
			pathExpression: "summarize(a,'30s','sum')",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{120, nan, 180, nan},
			Name:           "b",
			Tags:           map[string]string{"name": "c.d.b", "summarize": "30s", "summarizeFunction": "sum"},
			pathExpression: "summarize(c.d.b,'30s','sum')",
		},
	})
	f(`aliasByMetric(
                 summarize(
                  exclude(
                    groupByNode(
                      time("svc.default.first.prod.srv.1.http.returned-codes.500"),
                      8,
                      'sum'), 
                 '200'),
                '5min',
                'sum',
                false))`, []*series{
		{
			Timestamps: []int64{0},
			Values:     []float64{600},
			Name:       "500",
			Tags: map[string]string{
				"aggregatedBy":      "sum",
				"name":              "svc.default.first.prod.srv.1.http.returned-codes.500",
				"summarize":         "5min",
				"summarizeFunction": "sum",
			},
			pathExpression: "summarize(500,'5min','sum')",
		},
	})

	f(`aliasByNode(time("foo.bar.baz"))`, []*series{
		{
			Timestamps:     []int64{ec.startTime, ec.startTime + 60*1000},
			Values:         []float64{120, 180},
			Name:           "",
			Tags:           map[string]string{"name": "foo.bar.baz"},
			pathExpression: "foo.bar.baz",
		},
	})
	f(`aliasByTags(
		group(
			time("foo.bar.baz;aa=bb", 20),
			time("foo.xx", 50)
		),
		1, "aa"
	)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{120, 140, 160, 180, 200},
			Name:           "bar.bb",
			Tags:           map[string]string{"name": "foo.bar.baz", "aa": "bb"},
			pathExpression: "foo.bar.baz;aa=bb",
		},
		{
			Timestamps:     []int64{120000, 170000},
			Values:         []float64{120, 170},
			Name:           "xx",
			Tags:           map[string]string{"name": "foo.xx"},
			pathExpression: "foo.xx",
		},
	})
	f(`aliasQuery(
		group(
			time("foo.1.2", 20),
			time("foo.3.4", 50),
		),
		"foo\.([^.]+\.[^.]+)",
		"constantLine(\1)|alias('aaa.\1')",
		"foo %d bar %g"
	)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{120, 140, 160, 180, 200},
			Name:           "foo 1 bar 1.2",
			Tags:           map[string]string{"name": "foo.1.2"},
			pathExpression: "foo.1.2",
		},
		{
			Timestamps:     []int64{120000, 170000},
			Values:         []float64{120, 170},
			Name:           "foo 3 bar 3.4",
			Tags:           map[string]string{"name": "foo.3.4"},
			pathExpression: "foo.3.4",
		},
	})
	f(`aliasSub(
		group(
			time("foo.1.2", 20),
			time("foo.3.4", 50),
		),
		"foo\.([^.]+)\.([^.]+)",
		"bar\2\1.x\2"
	)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{120, 140, 160, 180, 200},
			Name:           "bar21.x2",
			Tags:           map[string]string{"name": "foo.1.2"},
			pathExpression: "foo.1.2",
		},
		{
			Timestamps:     []int64{120000, 170000},
			Values:         []float64{120, 170},
			Name:           "bar43.x4",
			Tags:           map[string]string{"name": "foo.3.4"},
			pathExpression: "foo.3.4",
		},
	})
	f(`alpha(time("foo",50),0.5)`, []*series{
		{
			Timestamps: []int64{120000, 170000},
			Values:     []float64{120, 170},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`applyByNode(
		time("foo.bar.baz",25),
		1,
		"time('%.abc;de=fg',50)"
	)`, []*series{
		{
			Timestamps:     []int64{120000, 170000},
			Values:         []float64{120, 170},
			Name:           "foo.bar.abc;de=fg",
			Tags:           map[string]string{"name": "foo.bar.abc", "de": "fg"},
			pathExpression: "foo.bar",
		},
	})
	f(`applyByNode(
		time("foo.bar.baz",25),
		1,
		"time('%.abc;de=fg',50)",
		"a.%.end"
	)`, []*series{
		{
			Timestamps:     []int64{120000, 170000},
			Values:         []float64{120, 170},
			Name:           "a.foo.bar.end",
			Tags:           map[string]string{"name": "foo.bar.abc", "de": "fg"},
			pathExpression: "foo.bar",
		},
	})
	f(`areaBetween(
		group(
			time("a"),
			time("b"),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "areaBetween(a)",
			Tags:       map[string]string{"name": "a", "areaBetween": "1"},
		},
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "areaBetween(b)",
			Tags:       map[string]string{"name": "b", "areaBetween": "1"},
		},
	})
	f(`asPercent(
		group(
			time("foo", 30),
			time("bar", 30),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{50, 50, 50},
			Name:       "asPercent(foo,sumSeries(bar,foo))",
			Tags:       map[string]string{"name": "asPercent(foo,sumSeries(bar,foo))"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{50, 50, 50},
			Name:       "asPercent(bar,sumSeries(bar,foo))",
			Tags:       map[string]string{"name": "asPercent(bar,sumSeries(bar,foo))"},
		},
	})
	f(`asPercent(
		group(
			time("foo", 17),
			time("bar", 23),
		),
		150
	)`, []*series{
		{
			Timestamps: []int64{120000, 137000, 154000, 171000, 188000, 205000},
			Values:     []float64{80, 91.33333333333333, 102.66666666666666, 113.99999999999999, 125.33333333333334, 136.66666666666666},
			Name:       "asPercent(foo,150)",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{80, 95.33333333333334, 110.66666666666667, 126},
			Name:       "asPercent(bar,150)",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`asPercent(
		group(
			time("foo.x", 30),
			time("bar.x", 30),
			time("bar.y", 30),
		),
		group(),
	)`, []*series{})
	f(`asPercent(
		group(
			time("foo.x", 30),
			time("bar.x", 30),
			time("bar.y", 30),
		),
		None,
		0
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{100, 100, 100},
			Name:       "asPercent(foo.x,foo.x)",
			Tags:       map[string]string{"name": "asPercent(foo.x,foo.x)"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{50, 50, 50},
			Name:       "asPercent(bar.x,sumSeries(bar.x,bar.y))",
			Tags:       map[string]string{"name": "asPercent(bar.x,sumSeries(bar.x,bar.y))"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{50, 50, 50},
			Name:       "asPercent(bar.y,sumSeries(bar.x,bar.y))",
			Tags:       map[string]string{"name": "asPercent(bar.y,sumSeries(bar.x,bar.y))"},
		},
	})
	f(`asPercent(
		group(
			time("foo;a=b", 30),
			time("bar", 30)
		),
		constantLine(100)|alias("baz;x=y")
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{135, 180},
			Name:       "asPercent(bar,baz;x=y)",
			Tags:       map[string]string{"name": "asPercent(bar,baz;x=y)"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{135, 180},
			Name:       "asPercent(foo;a=b,baz;x=y)",
			Tags:       map[string]string{"name": "asPercent(foo;a=b,baz;x=y)", "a": "b"},
		},
	})
	f(`asPercent(
		group(
			time("foo", 30),
			time("bar", 30),
		),
		group(
			time("x", 30),
			time("y", 30),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{100, 100, 100},
			Name:       "asPercent(bar,y)",
			Tags:       map[string]string{"name": "asPercent(bar,y)"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{100, 100, 100},
			Name:       "asPercent(foo,x)",
			Tags:       map[string]string{"name": "asPercent(foo,x)"},
		},
	})
	f(`asPercent(
		group(
			time("foo.x;c=d", 30),
			time("bar.b;a=b", 30),
			time("bar.a", 30)
		),
		group(
			time("bar.sss", 30),
			time("abc;e=g", 30)
		),
		0
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{100, 100, 100},
			Name:       "asPercent(bar.b;a=b,bar.sss)",
			Tags:       map[string]string{"name": "asPercent(bar.b;a=b,bar.sss)", "a": "b"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{100, 100, 100},
			Name:       "asPercent(bar.a,bar.sss)",
			Tags:       map[string]string{"name": "asPercent(bar.a,bar.sss)"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{nan, nan, nan},
			Name:       `asPercent(MISSING,abc;e=g)`,
			Tags:       map[string]string{"name": `asPercent(MISSING,abc;e=g)`},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{nan, nan, nan},
			Name:       "asPercent(foo.x;c=d,MISSING)",
			Tags:       map[string]string{"name": "asPercent(foo.x;c=d,MISSING)", "c": "d"},
		},
	})
	f(`averageAbove(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		160
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{220, 240, 260, 280, 300},
			Name:       "add(baz,100)",
			Tags:       map[string]string{"name": "baz", "add": "100"},
		},
	})
	f(`averageBelow(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		160
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{10, 10, 10},
			Name:           "bar",
			Tags:           map[string]string{"name": "10"},
			pathExpression: "constantLine(10)",
		},
	})
	f(`averageOutsidePercentile(
		group(
			add(time('a'),-10),
			time('b'),
			add(time('c'),10),
			add(time('d'),20),
		),
		75
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{110, 170},
			Name:       "add(a,-10)",
			Tags:       map[string]string{"name": "a", "add": "-10"},
		},
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{140, 200},
			Name:       "add(d,20)",
			Tags:       map[string]string{"name": "d", "add": "20"},
		},
	})
	f(`averageSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "averageSeries(bar,foo)",
			Tags:       map[string]string{"name": "averageSeries(bar,foo)", "aggregatedBy": "average"},
		},
	})
	f(`averageSeriesWithWildcards(
		group(
			time('foo.bar',30),
			time('foo.baz',30),
			time('xxx.yy',30),
		),
		1
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "xxx",
			Tags:           map[string]string{"aggregatedBy": "average", "name": "xxx.yy"},
			pathExpression: "xxx.yy",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "average", "name": "averageSeries(foo.bar,foo.baz)"},
			pathExpression: "averageSeries(foo.bar,foo.baz)",
		},
	})
	f(`averageSeriesWithWildcards(
		group(
			time('foo.bar',30),
			time('foo.baz',30),
			time('xxx.yy',30),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "xxx.yy",
			Tags:       map[string]string{"aggregatedBy": "average", "name": "xxx.yy"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "foo.bar",
			Tags:       map[string]string{"aggregatedBy": "average", "name": "foo.bar"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "foo.baz",
			Tags:       map[string]string{"aggregatedBy": "average", "name": "foo.baz"},
		},
	})
	f(`avg(
		group(
			time('foo',30),
			time('xxx',30),
		),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "averageSeries(bar,foo,xxx)",
			Tags:       map[string]string{"name": "averageSeries(bar,foo,xxx)", "aggregatedBy": "average"},
		},
	})
	f(`changed(
		group(
			constantLine(123)|alias('foo'),
			time('bar')
		)
	)`, []*series{
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{0, 0, 0},
			Name:           "changed(foo)",
			Tags:           map[string]string{"name": "123"},
			pathExpression: "changed(foo)",
		},
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{0, 1},
			Name:           "changed(bar)",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "changed(bar)",
		},
	})
	f(`color(time("foo",50),'green')`, []*series{
		{
			Timestamps: []int64{120000, 170000},
			Values:     []float64{120, 170},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`averageSeries(
		consolidateBy(
			group(
				time('foo',30),
				time('bar',30)
			),
			'first'
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       `averageSeries(consolidateBy(bar,'first'),consolidateBy(foo,'first'))`,
			Tags: map[string]string{
				"name":          "averageSeries(consolidateBy(bar,'first'),consolidateBy(foo,'first'))",
				"aggregatedBy":  "average",
				"consolidateBy": "first",
			},
		},
	})
	f(`constantLine(123) | alias("foo.bar;baz=aaa")`, []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{123, 123, 123},
			Name:           "foo.bar;baz=aaa",
			Tags:           map[string]string{"name": "123"},
			pathExpression: "constantLine(123)",
		},
	})
	f("constantLine(123.456)", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{123.456, 123.456, 123.456},
			Name:           "123.456",
			Tags:           map[string]string{"name": "123.456"},
			pathExpression: "constantLine(123.456)",
		},
	})
	f("constantLine(value=-123)", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{-123, -123, -123},
			Name:           "-123",
			Tags:           map[string]string{"name": "-123"},
			pathExpression: "constantLine(value=-123)",
		},
	})
	f(`countSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{2, 2, 2},
			Name:       "countSeries(bar,foo)",
			Tags:       map[string]string{"name": "countSeries(bar,foo)", "aggregatedBy": "count"},
		},
	})
	f(`averageSeries(
		cumulative(
			time('foo', 30)
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       `averageSeries(consolidateBy(foo,'sum'))`,
			Tags:       map[string]string{"name": "foo", "aggregatedBy": "average", "consolidateBy": "sum"},
		},
	})
	f(`currentAbove(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{220, 240, 260, 280, 300},
			Name:       "add(baz,100)",
			Tags:       map[string]string{"name": "baz", "add": "100"},
		},
	})
	f(`currentBelow(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{10, 10, 10},
			Name:           "bar",
			Tags:           map[string]string{"name": "10"},
			pathExpression: "constantLine(10)",
		},
	})
	f(`dashed(time('foo'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "dashed(foo,5)",
			Tags:           map[string]string{"name": "foo", "dashed": "5"},
			pathExpression: "foo",
		},
	})
	f(`delay(time('foo',20),1)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{nan, 120, 140, 160, 180},
			Name:       "delay(foo,1)",
			Tags:       map[string]string{"name": "foo", "delay": "1"},
		},
	})
	f(`delay(time('foo',20),-1)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{140, 160, 180, 200, nan},
			Name:       "delay(foo,-1)",
			Tags:       map[string]string{"name": "foo", "delay": "-1"},
		},
	})
	f(`delay(time('foo',20),0)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 140, 160, 180, 200},
			Name:       "delay(foo,0)",
			Tags:       map[string]string{"name": "foo", "delay": "0"},
		},
	})
	f(`delay(time('foo',20),100)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{nan, nan, nan, nan, nan},
			Name:       "delay(foo,100)",
			Tags:       map[string]string{"name": "foo", "delay": "100"},
		},
	})
	f(`delay(time('foo',20),-100)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{nan, nan, nan, nan, nan},
			Name:       "delay(foo,-100)",
			Tags:       map[string]string{"name": "foo", "delay": "-100"},
		},
	})
	f(`derivative(time('foo',25))`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{nan, 25, 25, 25},
			Name:       "derivative(foo)",
			Tags:       map[string]string{"name": "foo", "derivative": "1"},
		},
	})
	f(`diffSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0, 0, 0},
			Name:       "diffSeries(foo,bar)",
			Tags:       map[string]string{"name": "diffSeries(foo,bar)", "aggregatedBy": "diff"},
		},
	})
	f(`divideSeries(
		group(
			time('foo',30),
			time('bar',30)
		),
		add(time('xx',30),100)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0.5454545454545454, 0.6, 0.6428571428571429},
			Name:       "divideSeries(foo,add(xx,100))",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0.5454545454545454, 0.6, 0.6428571428571429},
			Name:       "divideSeries(bar,add(xx,100))",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`divideSeries(
		group(
			time('foo',20),
			time('bar',30)
		),
		group()
	)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{nan, nan, nan, nan, nan},
			Name:           "divideSeries(foo,MISSING)",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "divideSeries(foo,MISSING)",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{nan, nan, nan, nan},
			Name:           "divideSeries(bar,MISSING)",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "divideSeries(bar,MISSING)",
		},
	})
	f(`divideSeriesLists(
		group(
			time('foo',30),
			time('bar',30)
		),
		group(
			time('xx',30),
			time('y',30),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{1, 1, 1},
			Name:       "divideSeries(foo,xx)",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{1, 1, 1},
			Name:       "divideSeries(bar,y)",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`drawAsInfinite(time('a'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "drawAsInfinite(a)",
			Tags:           map[string]string{"name": "a", "drawAsInfinite": "1"},
			pathExpression: "a",
		},
	})
	f(`events()`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{nan, nan, nan},
			Name:       "events()",
			Tags:       map[string]string{"name": "events()"},
		},
	})
	f(`events("foo","bar")`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{nan, nan, nan},
			Name:       "events('foo','bar')",
			Tags:       map[string]string{"name": "events('foo','bar')"},
		},
	})
	f(`exclude(
		group(
			time("foo.bar.baz"),
			time("x"),
		),
		"bar"
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "x",
			Tags:       map[string]string{"name": "x"},
		},
	})
	f(`exp(scale(time('a',25),1e-2))`, []*series{
		{
			Timestamps:     []int64{120000, 145000, 170000, 195000},
			Values:         []float64{3.3201169227365472, 4.263114515168817, 5.4739473917272, 7.028687580589293},
			Name:           "exp(scale(a,0.01))",
			Tags:           map[string]string{"name": "a", "exp": "e"},
			pathExpression: "exp(scale(a,0.01))",
		},
	})
	f(`exponentialMovingAverage(time('a',20),'1min')`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{81.31147540983606, 83.23568933082504, 85.75255197571603, 88.8426322388073, 92.48713609983001},
			Name:       "exponentialMovingAverage(a,'1min')",
			Tags:       map[string]string{"name": "a", "exponentialMovingAverage": "'1min'"},
		},
	})
	f(`exponentialMovingAverage(time('a',20),'10s')`, []*series{
		{
			Timestamps: []int64{130000, 150000, 170000, 190000, 210000},
			Values:     []float64{113.63636363636364, 120.24793388429751, 129.2937640871525, 140.33126152585203, 152.998304884788},
			Name:       "exponentialMovingAverage(a,'10s')",
			Tags:       map[string]string{"name": "a", "exponentialMovingAverage": "'10s'"},
		},
	})
	f(`exponentialMovingAverage(time('a',20),5)`, []*series{
		{
			Timestamps: []int64{130000, 150000, 170000, 190000, 210000},
			Values:     []float64{70, 96.66666666666667, 121.11111111111111, 144.07407407407408, 166.0493827160494},
			Name:       "exponentialMovingAverage(a,5)",
			Tags:       map[string]string{"name": "a", "exponentialMovingAverage": "5"},
		},
	})
	f(`fallbackSeries(time('a'),constantLine(10))`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "a",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`fallbackSeries(group(),constantLine(10))`, []*series{
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{10, 10, 10},
			Name:           "10",
			Tags:           map[string]string{"name": "10"},
			pathExpression: "constantLine(10)",
		},
	})
	f(`filterSeries(
		group(
			time('a',20),
			add(time('b',20),200),
		),
		'last','>=',300
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{320, 340, 360, 380, 400},
			Name:       "add(b,200)",
			Tags:       map[string]string{"name": "b", "add": "200"},
		},
	})
	f(`filterSeries(
		group(
			time('a',20),
			add(time('b',20),200),
		),
		'first','<=',120
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 140, 160, 180, 200},
			Name:       "a",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`filterSeries(
		group(
			time('a',20),
			add(time('b',20),200),
		),
		'first','=',120
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 140, 160, 180, 200},
			Name:       "a",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`filterSeries(
		group(
			time('a',20),
			add(time('b',20),200),
		),
		'first','!=',120
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{320, 340, 360, 380, 400},
			Name:       "add(b,200)",
			Tags:       map[string]string{"name": "b", "add": "200"},
		},
	})
	f(`grep(
		group(
			time("foo.bar.baz"),
			time("x"),
		),
		"bar"
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo.bar.baz",
			Tags:       map[string]string{"name": "foo.bar.baz"},
		},
	})
	f("group()", []*series{})
	f("group(constantLine(1)|alias('foo'), constantLine(2) | alias('bar'))", []*series{
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{1, 1, 1},
			Name:           "foo",
			Tags:           map[string]string{"name": "1"},
			pathExpression: "constantLine(1)",
		},
		{
			Timestamps:     []int64{ec.startTime, (ec.startTime + ec.endTime) / 2, ec.endTime},
			Values:         []float64{2, 2, 2},
			Name:           "bar",
			Tags:           map[string]string{"name": "2"},
			pathExpression: "constantLine(2)",
		},
	})
	f(`groupByNode(
		group(
			time("foo.bar", 30),
			time("foo.baz", 30)
		),
		0
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "average", "name": "averageSeries(foo.bar,foo.baz)"},
			pathExpression: "averageSeries(foo.bar,foo.baz)",
		},
	})
	f(`groupByNode(
		group(
			time("foo.bar", 30),
			time("foo.baz", 30)
		),
		0,
		'last'
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "last", "name": "lastSeries(foo.bar,foo.baz)"},
			pathExpression: "lastSeries(foo.bar,foo.baz)",
		},
	})
	f(`groupByNodes(
		group(
			time("foo.bar", 30),
			time("foo.baz", 30)
		),
		callback='first',
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "",
			Tags:           map[string]string{"aggregatedBy": "first", "name": "firstSeries(foo.bar,foo.baz)"},
			pathExpression: "firstSeries(foo.bar,foo.baz)",
		},
	})
	f(`groupByNodes(
		group(
			time("foo.bar", 30),
			time("foo.baz", 30)
		),
		'median',
		0
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "median", "name": "medianSeries(foo.bar,foo.baz)"},
			pathExpression: "medianSeries(foo.bar,foo.baz)",
		},
	})
	f(`groupByTags(
		group(
			time("foo;bar=baz", 30),
			time("x;bar=baz;aa=bb", 30)
		),
		'median',
		'bar'
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "median;bar=baz",
			Tags:           map[string]string{"aggregatedBy": "median", "bar": "baz", "name": `medianSeries(foo;bar=baz,x;bar=baz;aa=bb)`},
			pathExpression: "medianSeries(foo;bar=baz,x;bar=baz;aa=bb)",
		},
	})
	f(`groupByTags(
		group(
			time("foo;bar=baz", 30),
			time("x;bar=baz;aa=bb", 30)
		),
		'median',
		'bar', 'name'
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "foo;bar=baz",
			Tags:           map[string]string{"aggregatedBy": "median", "bar": "baz", "name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "x;bar=baz",
			Tags:           map[string]string{"aa": "bb", "aggregatedBy": "median", "bar": "baz", "name": "x"},
			pathExpression: "x",
		},
	})
	f(`highest(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 147000, 174000, 201000},
			Values:     []float64{120, 147, 174, 201},
			Name:       "bar",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`highest(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		4,
		'avg'
	)`, []*series{
		{
			Timestamps: []int64{120000, 147000, 174000, 201000},
			Values:     []float64{120, 147, 174, 201},
			Name:       "bar",
			Tags:       map[string]string{"name": "bar"},
		},
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 145, 170, 195},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{120, 143, 166, 189},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
	})
	f(`highestAverage(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 147000, 174000, 201000},
			Values:     []float64{120, 147, 174, 201},
			Name:       "bar",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`highestCurrent(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 147000, 174000, 201000},
			Values:     []float64{120, 147, 174, 201},
			Name:       "bar",
			Tags:       map[string]string{"name": "bar"},
		},
	})
	f(`highestMax(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		2
	)`, []*series{
		{
			Timestamps: []int64{120000, 147000, 174000, 201000},
			Values:     []float64{120, 147, 174, 201},
			Name:       "bar",
			Tags:       map[string]string{"name": "bar"},
		},
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 145, 170, 195},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`hitcount(time('foo',20),'60s')`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{6000, 4000},
			Name:       "hitcount(foo,'60s')",
			Tags:       map[string]string{"name": "foo", "hitcount": "60s"},
		},
	})
	f(`hitcount(time('foo',25),'60s')`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{7875, 5475},
			Name:       "hitcount(foo,'60s')",
			Tags:       map[string]string{"name": "foo", "hitcount": "60s"},
		},
	})
	f(`hitcount(time('foo',25),'60s',true)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{7875, 5475},
			Name:       "hitcount(foo,'60s',true)",
			Tags:       map[string]string{"name": "foo", "hitcount": "60s"},
		},
	})
	f(`identity('foo')`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`integral(
		group(
			time('foo',30),
			time('bar',25),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{120, 270, 450, 660},
			Name:       "integral(foo)",
			Tags:       map[string]string{"name": "foo", "integral": "1"},
		},
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 265, 435, 630},
			Name:       "integral(bar)",
			Tags:       map[string]string{"name": "bar", "integral": "1"},
		},
	})
	f(`integralByInterval(
		group(
			time('foo',30),
			time('bar',25),
		),
		'60s'
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{120, 270, 180, 390},
			Name:       "integralByInterval(foo,'60s')",
			Tags:       map[string]string{"name": "foo", "integralByInterval": "1"},
		},
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 265, 435, 195},
			Name:       "integralByInterval(bar,'60s')",
			Tags:       map[string]string{"name": "bar", "integralByInterval": "1"},
		},
	})
	f(`interpolate(time('a'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "interpolate(a)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "interpolate(a)",
		},
	})
	f(`invert(time('a'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{0.008333333333333333, 0.005555555555555556},
			Name:           "invert(a)",
			Tags:           map[string]string{"name": "a", "invert": "1"},
			pathExpression: "invert(a)",
		},
	})
	f(`keepLastValue(removeAboveValue(time('a'),150))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 120},
			Name:           "keepLastValue(removeAboveValue(a,150))",
			Tags:           map[string]string{"name": "a"},
			pathExpression: ("keepLastValue(removeAboveValue(a,150))"),
		},
	})
	f(`limit(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 145, 170, 195},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`lineWidth(time('a'),2)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "a",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`logarithm(time('a'))`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{2.0791812460476247, 2.255272505103306},
			Name:       "log(a,10)",
			Tags:       map[string]string{"name": "a", "log": "10"},
		},
	})
	f(`logarithm(time('a'), 2)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{6.906890595608519, 7.491853096329675},
			Name:       "log(a,2)",
			Tags:       map[string]string{"name": "a", "log": "2"},
		},
	})
	f(`logarithm(time('a'),-2)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{nan, nan},
			Name:       "log(a,-2)",
			Tags:       map[string]string{"name": "a", "log": "-2"},
		},
	})
	f(`logit(invert(time('a')))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{-4.77912349311153, -5.187385805840755},
			Name:           "logit(invert(a))",
			Tags:           map[string]string{"name": "a", "invert": "1", "logit": "logit"},
			pathExpression: "logit(invert(a))",
		},
	})
	f(`logit(time('a'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{nan, nan},
			Name:           "logit(a)",
			Tags:           map[string]string{"name": "a", "logit": "logit"},
			pathExpression: "logit(a)",
		},
	})
	f(`lowest(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		)
	)`, []*series{
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{120, 143, 166, 189},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
	})
	f(`lowest(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		2,
		'sum'
	)`, []*series{
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{120, 143, 166, 189},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{120, 145, 170, 195},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`lowestAverage(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{120, 143, 166, 189},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
	})
	f(`lowestCurrent(
		group(
			time('foo',25),
			time('bar',27),
			time('baz',23),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 143000, 166000, 189000},
			Values:     []float64{120, 143, 166, 189},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
	})
	f(`maxSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "maxSeries(bar,foo)",
			Tags:       map[string]string{"name": "maxSeries(bar,foo)", "aggregatedBy": "max"},
		},
	})
	f(`maximumAbove(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{220, 240, 260, 280, 300},
			Name:       "add(baz,100)",
			Tags:       map[string]string{"name": "baz", "add": "100"},
		},
	})
	f(`maximumBelow(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{10, 10, 10},
			Name:           "bar",
			Tags:           map[string]string{"name": "10"},
			pathExpression: "constantLine(10)",
		},
	})
	f(`minMax(time('foo',20))`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{0, 0.25, 0.5, 0.75, 1},
			Name:           "minMax(foo)",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "minMax(foo)",
		},
	})
	f(`minSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "minSeries(bar,foo)",
			Tags:       map[string]string{"name": "minSeries(bar,foo)", "aggregatedBy": "min"},
		},
	})
	f(`minimumAbove(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{220, 240, 260, 280, 300},
			Name:       "add(baz,100)",
			Tags:       map[string]string{"name": "baz", "add": "100"},
		},
	})
	f(`minimumBelow(
		group(
			time('foo'),
			constantLine(10)|alias('bar'),
			time('baz', 20)|add(100),
		),
		200
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{10, 10, 10},
			Name:           "bar",
			Tags:           map[string]string{"name": "10"},
			pathExpression: "constantLine(10)",
		},
	})
	f(`mostDeviant(
		group(
			time('foo',18),
			time('bar',23),
			time('baz',30),
		),
		1
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{120, 150, 180, 210},
			Name:       "baz",
			Tags:       map[string]string{"name": "baz"},
		},
	})
	f(`movingAverage(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingAverage(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingAverage": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingAverage(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingAverage": "5"},
		},
	})
	f(`movingAverage(
		summarize(
			group(
				time('foo',10),
				time('bar',20),
			),'1m','sum',false
		),
		2
	)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{330, 690},
			Name:       "movingAverage(summarize(foo,'1m','sum'),2)",
			Tags:       map[string]string{"name": "foo", "movingAverage": "2", "summarize": "1m", "summarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{150, 330},
			Name:       "movingAverage(summarize(bar,'1m','sum'),2)",
			Tags:       map[string]string{"name": "bar", "movingAverage": "2", "summarize": "1m", "summarizeFunction": "sum"},
		},
	})
	f(`movingMax(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{90, 120, 150, 180},
			Name:       "movingMax(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingMax": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{90, 120, 150, 180},
			Name:       "movingMax(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingMax": "5"},
		},
	})
	f(`movingMedian(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingMedian(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingMedian": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingMedian(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingMedian": "5"},
		},
	})
	f(`movingMin(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{-30, 0, 30, 60},
			Name:       "movingMin(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingMin": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{-30, 0, 30, 60},
			Name:       "movingMin(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingMin": "5"},
		},
	})
	f(`movingSum(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{150, 300, 450, 600},
			Name:       "movingSum(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingSum": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{150, 300, 450, 600},
			Name:       "movingSum(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingSum": "5"},
		},
	})
	f(`movingWindow(
		group(
			time('foo',30),
			time('bar',30),
		),
		5
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingAvg(foo,5)",
			Tags:       map[string]string{"name": "foo", "movingAvg": "5"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{30, 60, 90, 120},
			Name:       "movingAvg(bar,5)",
			Tags:       map[string]string{"name": "bar", "movingAvg": "5"},
		},
	})
	f(`movingWindow(
		group(
			time('foo',30),
			time('bar',30),
		),
		'30s',
		'avg_zero'
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{90, 120, 150, 180},
			Name:       `movingAvg_zero(foo,'30s')`,
			Tags:       map[string]string{"name": "foo", "movingAvg_zero": "'30s'"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{90, 120, 150, 180},
			Name:       `movingAvg_zero(bar,'30s')`,
			Tags:       map[string]string{"name": "bar", "movingAvg_zero": "'30s'"},
		},
	})
	f(`multiplySeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{14400, 22500, 32400},
			Name:       "multiplySeries(bar,foo)",
			Tags:       map[string]string{"name": "multiplySeries(bar,foo)", "aggregatedBy": "multiply"},
		},
	})
	f(`multiplySeriesWithWildcards(
		group(
			time('foo.bar',30),
			time('foo.baz',30),
			time('xxx.yy',30),
		),
		1
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "xxx",
			Tags:           map[string]string{"aggregatedBy": "multiply", "name": "xxx.yy"},
			pathExpression: "xxx.yy",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{14400, 22500, 32400},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "multiply", "name": "multiplySeries(foo.bar,foo.baz)"},
			pathExpression: "multiplySeries(foo.bar,foo.baz)",
		},
	})
	f(`nPercentile(
		group(
			time('a',20),
			time('b',17)
		),
		30
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{140, 140, 140, 140, 140},
			Name:       "nPercentile(a,30)",
			Tags:       map[string]string{"name": "a", "nPercentile": "30"},
		},
		{
			Timestamps: []int64{120000, 137000, 154000, 171000, 188000, 205000},
			Values:     []float64{154, 154, 154, 154, 154, 154},
			Name:       "nPercentile(b,30)",
			Tags:       map[string]string{"name": "b", "nPercentile": "30"},
		},
	})
	f(`nonNegativeDerivative(time('foo.bar;baz=1',25))`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{nan, 25, 25, 25},
			Name:       "nonNegativeDerivative(foo.bar;baz=1)",
			Tags:       map[string]string{"name": "foo.bar", "baz": "1", "nonNegativeDerivative": "1"},
		},
	})
	f(`offset(time('a'),10)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{130, 190},
			Name:           "offset(a,10)",
			Tags:           map[string]string{"name": "a", "offset": "10"},
			pathExpression: "offset(a,10)",
		},
	})
	f(`offsetToZero(time('a',30))`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{0, 30, 60, 90},
			Name:           "offsetToZero(a)",
			Tags:           map[string]string{"name": "a", "offsetToZero": "120"},
			pathExpression: "offsetToZero(a)",
		},
	})
	f(`rangeOfSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0, 0, 0},
			Name:       "rangeOfSeries(bar,foo)",
			Tags:       map[string]string{"name": "rangeOfSeries(bar,foo)", "aggregatedBy": "rangeOf"},
		},
	})
	f(`pow(time('a'),0.5)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{10.954451150103322, 13.416407864998739},
			Name:           "pow(a,0.5)",
			Tags:           map[string]string{"name": "a", "pow": "0.5"},
			pathExpression: "pow(a,0.5)",
		},
	})
	f(`powSeries(
		time('a',30),
		time('b',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{3.1750423737803376e+249, math.Inf(1), math.Inf(1)},
			Name:       "powSeries(a,b)",
			Tags:       map[string]string{"name": "powSeries(a,b)", "aggregatedBy": "pow"},
		},
	})
	f(`removeAbovePercentile(time('a',35), 50)`, []*series{
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{120, 155, nan},
			Name:           "removeAbovePercentile(a,50)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "removeAbovePercentile(a,50)",
		},
	})
	f(`removeAboveValue(time('a'), 150)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, nan},
			Name:           "removeAboveValue(a,150)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "removeAboveValue(a,150)",
		},
	})
	f(`removeBelowPercentile(time('a'), 50)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{nan, 180},
			Name:           "removeBelowPercentile(a,50)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "removeBelowPercentile(a,50)",
		},
	})
	f(`removeBelowValue(time('a'), 150)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{nan, 180},
			Name:           "removeBelowValue(a,150)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "removeBelowValue(a,150)",
		},
	})
	f(`removeBetweenPercentile(
		group(
			time('a',30),
			time('b',30),
			time('c',30),
		),
		70
	)`, []*series{})
	f(`removeEmptySeries(time('a'))`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{120, 180},
			Name:       "a",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`removeEmptySeries(removeBelowValue(time('a'),150),1)`, []*series{})
	// if xFilesFactor is set, a single value in the series needs to be non-null for it to be
	// considered non-empty
	f(`removeEmptySeries(removeBelowValue(time('a'),150),0)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{nan, 180},
			Name:       "removeBelowValue(a,150)",
			Tags:       map[string]string{"name": "a"},
		},
	})
	f(`removeEmptySeries(removeBelowValue(time('a'),150),-1)`, []*series{
		{
			Timestamps: []int64{120000, 180000},
			Values:     []float64{nan, 180},
			Name:       "removeBelowValue(a,150)",
			Tags:       map[string]string{"name": "a"},
		},
	})

	f(`round(time('a',17),-1)`, []*series{
		{
			Timestamps:     []int64{120000, 137000, 154000, 171000, 188000, 205000},
			Values:         []float64{120, 140, 150, 170, 190, 210},
			Name:           "round(a,-1)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "round(a,-1)",
		},
	})
	f(`round(time('a',17))`, []*series{
		{
			Timestamps:     []int64{120000, 137000, 154000, 171000, 188000, 205000},
			Values:         []float64{120, 137, 154, 171, 188, 205},
			Name:           "round(a)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: "round(a)",
		},
	})
	f(`scale(time('a'),0.5)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{60, 90},
			Name:           "scale(a,0.5)",
			Tags:           map[string]string{"name": "a"},
			pathExpression: ("scale(a,0.5)"),
		},
	})
	f(`setXFilesFactor(
		time('foo',20),
		0.5
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 140, 160, 180, 200},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo", "xFilesFactor": "0.5"},
		},
	})
	f(`sumSeriesWithWildcards(
		group(
			time('foo.bar',30),
			time('foo.baz',30),
			time('xxx.yy',30),
		),
		1
	)`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "xxx",
			Tags:           map[string]string{"aggregatedBy": "sum", "name": "xxx.yy"},
			pathExpression: "xxx.yy",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{240, 300, 360},
			Name:           "foo",
			Tags:           map[string]string{"aggregatedBy": "sum", "name": "sumSeries(foo.bar,foo.baz)"},
			pathExpression: "sumSeries(foo.bar,foo.baz)",
		},
	})
	f(`summarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'45s'
	)`, []*series{
		{
			Timestamps: []int64{90000, 135000, 180000},
			Values:     []float64{333, 327, 411},
			Name:       `summarize(bar,'45s','sum')`,
			Tags:       map[string]string{"name": "bar", "summarize": "45s", "summarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{90000, 135000, 180000},
			Values:     []float64{438, 465, 802},
			Name:       `summarize(foo,'45s','sum')`,
			Tags:       map[string]string{"name": "foo", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`summarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'45s',
		'sum',
		True
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{558, 555},
			Name:       `summarize(foo,'45s','sum',true)`,
			Tags:       map[string]string{"name": "foo", "summarize": "45s", "summarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{423, 387},
			Name:       `summarize(bar,'45s','sum',true)`,
			Tags:       map[string]string{"name": "bar", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`summarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'45s',
		'sumSeries',
		True
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{558, 555},
			Name:       `summarize(foo,'45s','sumSeries',true)`,
			Tags:       map[string]string{"name": "foo", "summarize": "45s", "summarizeFunction": "sumSeries"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{423, 387},
			Name:       `summarize(bar,'45s','sumSeries',true)`,
			Tags:       map[string]string{"name": "bar", "summarize": "45s", "summarizeFunction": "sumSeries"},
		},
	})
	f(`summarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'45s',
		'last',
		True
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{159, 198},
			Name:       `summarize(foo,'45s','last',true)`,
			Tags:       map[string]string{"name": "foo", "summarize": "45s", "summarizeFunction": "last"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{162, 204},
			Name:       `summarize(bar,'45s','last',true)`,
			Tags:       map[string]string{"name": "bar", "summarize": "45s", "summarizeFunction": "last"},
		},
	})
	f(`time('foo.bar;baz=aa', 40)`, []*series{
		{
			Timestamps:     []int64{ec.startTime, ec.startTime + 40e3, ec.startTime + 80e3},
			Values:         []float64{float64(ec.startTime) / 1e3, float64(ec.startTime)/1e3 + 40, float64(ec.startTime)/1e3 + 80},
			Name:           "foo.bar;baz=aa",
			Tags:           map[string]string{"name": "foo.bar", "baz": "aa"},
			pathExpression: "foo.bar;baz=aa",
		},
	})
	f(`timeFunction("foo.bar.baz")`, []*series{
		{
			Timestamps:     []int64{ec.startTime, ec.startTime + 60e3},
			Values:         []float64{float64(ec.startTime) / 1e3, float64(ec.startTime)/1e3 + 60},
			Name:           "foo.bar.baz",
			Tags:           map[string]string{"name": "foo.bar.baz"},
			pathExpression: "foo.bar.baz",
		},
	})
	f(`timeFunction('foo.bar;baz=aa', step=30)`, []*series{
		{
			Timestamps: []int64{ec.startTime, ec.startTime + 30e3, ec.startTime + 60e3, ec.startTime + 90e3},
			Values:     []float64{float64(ec.startTime) / 1e3, float64(ec.startTime)/1e3 + 30, float64(ec.startTime)/1e3 + 60, float64(ec.startTime)/1e3 + 90},
			Name:       "foo.bar;baz=aa",
			Tags:       map[string]string{"name": "foo.bar", "baz": "aa"},
		},
	})
	f(`weightedAverage(time('foo',30),time('bar',30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "weightedAverage(foo,bar,)",
			Tags:       map[string]string{"name": "weightedAverage(foo,bar,)"},
		},
	})
	f(`weightedAverage(
		group(
			time("foo.x", 30),
			time("bar.y", 30),
		),
		group(
			time("bar.x", 30),
			time("foo.y", 30),
		),
		0
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "weightedAverage(bar.y,foo.x,bar.x,foo.y,0)",
			Tags:       map[string]string{"name": "weightedAverage(bar.y,foo.x,bar.x,foo.y,0)"},
		},
	})
	f(`weightedAverage(
		group(
			time("foo", 10) | alias("foo.bar1"),
			time("bar", 10) | alias("foo.bar2"),
		),
		group(
			time("bar", 10) | alias("foo.bar3"),
			time("foo", 10) | alias("foo.bar4"),
		),
        1
	)`, []*series{})
	f(`weightedAverage(
		group(
			time("foo0.bar2",30),
			time("foo0.bar1",30) ,
		),
		group(
			time("foo1.bar1",30),
			time("foo1.bar2",30),
		),
        1
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "weightedAverage(foo0.bar1,foo0.bar2,foo1.bar1,foo1.bar2,1)",
			Tags:       map[string]string{"name": "weightedAverage(foo0.bar1,foo0.bar2,foo1.bar1,foo1.bar2,1)"},
		},
	})
	f(`xFilesFactor(
		time('foo',20),
		0.5
	)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{120, 140, 160, 180, 200},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo", "xFilesFactor": "0.5"},
		},
	})
	f(`verticalLine("00:03_19700101","event","blue")`, []*series{
		{
			Timestamps: []int64{180000, 180000},
			Values:     []float64{1.0, 1.0},
			Name:       "event",
			Tags:       map[string]string{"name": "event"},
		},
	})
	f(`verticalLine("00:0319700101","event","blue")`, []*series{
		{
			Timestamps: []int64{180000, 180000},
			Values:     []float64{1.0, 1.0},
			Name:       "event",
			Tags:       map[string]string{"name": "event"},
		},
	})
	f(`useSeriesAbove(time('foo.baz',10),10000,"reqs","time")`, []*series{})
	f(`unique(time('foo',30),time('foo',30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{120.0, 150.0, 180.0, 210.0},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
	})
	f(`unique(time('foo',30),time('foo',40),time('foo.bar',40))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000, 210000},
			Values:     []float64{120.0, 150.0, 180.0, 210.0},
			Name:       "foo",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 160000, 200000},
			Values:     []float64{120.0, 160.0, 200.0},
			Name:       "foo.bar",
			Tags:       map[string]string{"name": "foo.bar"},
		},
	})
	f(`perSecond(time('foo.bar;baz=1',25))`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{nan, 1, 1, 1},
			Name:       "perSecond(foo.bar;baz=1)",
			Tags:       map[string]string{"name": "foo.bar", "baz": "1", "perSecond": "1"},
		},
	})
	f(`perSecond(time('foo.bar;baz=1',25),150)`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{nan, 1, nan, nan},
			Name:       "perSecond(foo.bar;baz=1)",
			Tags:       map[string]string{"name": "foo.bar", "baz": "1", "perSecond": "1"},
		},
	})
	f(`perSecond(time('foo.bar;baz=1',25),None,140)`, []*series{
		{
			Timestamps: []int64{120000, 145000, 170000, 195000},
			Values:     []float64{nan, nan, 1, 1},
			Name:       "perSecond(foo.bar;baz=1)",
			Tags:       map[string]string{"name": "foo.bar", "baz": "1", "perSecond": "1"},
		},
	})
	f(`percentileOfSeries(
		group(
			time('a',30),
			time('b',30),
			time('c',30)
		),
		40
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "percentileOfSeries(a,40)",
			Tags:       map[string]string{"name": "percentileOfSeries(a,40)"},
		},
	})
	f(`percentileOfSeries(
		group(
			time('a',30),
			time('b',30),
			time('c',30),
		),
		90
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "percentileOfSeries(a,90)",
			Tags:       map[string]string{"name": "percentileOfSeries(a,90)"},
		},
	})
	f(`transformNull(time('foo.bar',35),-1,time('foo.bar',30))`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 155, 190},
			Name:           "transformNull(foo.bar,-1,referenceSeries)",
			Tags:           map[string]string{"name": "foo.bar", "referenceSeries": "1", "transformNull": "-1"},
			pathExpression: "transformNull(foo.bar,-1,referenceSeries)",
		},
	})
	f(`transformNull(time('foo.bar',35),-1)`, []*series{
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{120, 155, 190},
			Name:           "transformNull(foo.bar,-1)",
			Tags:           map[string]string{"name": "foo.bar", "transformNull": "-1"},
			pathExpression: "transformNull(foo.bar,-1)",
		},
	})
	f(`timeShift(time('foo.bar;baz=1',25),"+1min")`, []*series{
		{
			Timestamps:     []int64{120000, 145000},
			Values:         []float64{180, 205},
			Name:           `timeShift(foo.bar;baz=1,'+1min')`,
			Tags:           map[string]string{"name": "foo.bar", "baz": "1", "timeShift": "+1min"},
			pathExpression: "foo.bar;baz=1",
		},
	})
	f(`timeShift(time('foo.bar;baz=1',25),"+1min",false)`, []*series{
		{
			Timestamps:     []int64{120000, 145000, 170000, 195000},
			Values:         []float64{180, 205, 230, 255},
			Name:           `timeShift(foo.bar;baz=1,'+1min')`,
			Tags:           map[string]string{"name": "foo.bar", "baz": "1", "timeShift": "+1min"},
			pathExpression: "foo.bar;baz=1",
		},
	})
	f(`timeShift(time('foo.bar;baz=1',25),"-1min",true)`, []*series{
		{
			Timestamps:     []int64{120000, 145000, 170000, 195000},
			Values:         []float64{60, 85, 110, 135},
			Name:           "timeShift(foo.bar;baz=1,'-1min')",
			Tags:           map[string]string{"name": "foo.bar", "baz": "1", "timeShift": "-1min"},
			pathExpression: "foo.bar;baz=1",
		},
	})
	f(`timeShift(time('foo.bar;baz=1',25),"1min",false,true)`, []*series{
		{
			Timestamps:     []int64{120000, 145000, 170000, 195000},
			Values:         []float64{60, 85, 110, 135},
			Name:           `timeShift(foo.bar;baz=1,'1min')`,
			Tags:           map[string]string{"name": "foo.bar", "baz": "1", "timeShift": "1min"},
			pathExpression: "foo.bar;baz=1",
		},
	})
	f(`timeSlice(time('foo.bar;bar=1',20),"00:00 19700101","00:03 19700101")`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{120, 140, 160, 180, nan},
			Name:           "timeSlice(foo.bar;bar=1,0,180)",
			Tags:           map[string]string{"name": "foo.bar", "bar": "1", "timeSliceEnd": "180", "timeSliceStart": "0"},
			pathExpression: "foo.bar;bar=1",
		},
	})
	f(`timeStack(time('foo.bar',35),"+1min",1,3)`, []*series{
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{180, 215, 250},
			Name:           "timeShift(foo.bar,+1min,1)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "1", "timeShiftUnit": "+1min"},
			pathExpression: `timeShift(foo.bar,+1min,1)`,
		},
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{240, 275, 310},
			Name:           "timeShift(foo.bar,+1min,2)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "2", "timeShiftUnit": "+1min"},
			pathExpression: `timeShift(foo.bar,+1min,2)`,
		},
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{300, 335, 370},
			Name:           "timeShift(foo.bar,+1min,3)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "3", "timeShiftUnit": "+1min"},
			pathExpression: `timeShift(foo.bar,+1min,3)`,
		},
	})
	f(`timeStack(time('foo.bar',35),"1min",1,3)`, []*series{
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{60, 95, 130},
			Name:           "timeShift(foo.bar,1min,1)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "1", "timeShiftUnit": "1min"},
			pathExpression: `timeShift(foo.bar,1min,1)`,
		},
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{0, 35, 70},
			Name:           "timeShift(foo.bar,1min,2)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "2", "timeShiftUnit": "1min"},
			pathExpression: `timeShift(foo.bar,1min,2)`,
		},
		{
			Timestamps:     []int64{120000, 155000, 190000},
			Values:         []float64{-60, -25, 10},
			Name:           "timeShift(foo.bar,1min,3)",
			Tags:           map[string]string{"name": "foo.bar", "timeShift": "3", "timeShiftUnit": "1min"},
			pathExpression: `timeShift(foo.bar,1min,3)`,
		},
	})
	f(`threshold(1.5)`, []*series{
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{1.5, 1.5, 1.5},
			Name:           "1.5",
			Tags:           map[string]string{"name": "1.5"},
			pathExpression: "threshold(1.5)",
		},
	})
	f(`threshold(1.5,"max","black")`, []*series{
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{1.5, 1.5, 1.5},
			Name:           "max",
			Tags:           map[string]string{"name": "1.5"},
			pathExpression: "threshold(1.5,'max','black')",
		},
	})
	f(`sum(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{240, 300, 360},
			Name:       "sumSeries(bar,foo)",
			Tags:       map[string]string{"name": "sumSeries(bar,foo)", "aggregatedBy": "sum"},
		},
	})
	f(`sumSeries(
		time('foo',30),
		time('bar',30),
	)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{240, 300, 360},
			Name:       "sumSeries(bar,foo)",
			Tags:       map[string]string{"name": "sumSeries(bar,foo)", "aggregatedBy": "sum"},
		},
	})
	f(`substr(time('collectd.test-db1.load.value;tag1=value1;tag2=value2'),1,3)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "test-db1.load",
			Tags:           map[string]string{"name": "collectd.test-db1.load.value", "tag1": "value1", "tag2": "value2"},
			pathExpression: "collectd.test-db1.load.value;tag1=value1;tag2=value2",
		},
	})

	f(`substr(time('foo.baz.host;tag1=value1;tag2=value2'),1)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "baz.host;tag1=value1;tag2=value2",
			Tags:           map[string]string{"name": "foo.baz.host", "tag1": "value1", "tag2": "value2"},
			pathExpression: "foo.baz.host;tag1=value1;tag2=value2",
		},
	})

	f(`substr(time('foo.baz.host;tag1=value1;tag2=value2'),5)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "",
			Tags:           map[string]string{"name": "foo.baz.host", "tag1": "value1", "tag2": "value2"},
			pathExpression: "foo.baz.host;tag1=value1;tag2=value2",
		},
	})
	f(`substr(time('foo.baz.host;tag1=value1;tag2=value2'),1,10)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "baz.host;tag1=value1;tag2=value2",
			Tags:           map[string]string{"name": "foo.baz.host", "tag1": "value1", "tag2": "value2"},
			pathExpression: "foo.baz.host;tag1=value1;tag2=value2",
		},
	})
	f(`substr(time('foo.baz.host;tag1=value1;tag2=value2'),-1)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "host;tag1=value1;tag2=value2",
			Tags:           map[string]string{"name": "foo.baz.host", "tag1": "value1", "tag2": "value2"},
			pathExpression: "foo.baz.host;tag1=value1;tag2=value2",
		},
	})
	f(`substr(time('foo.baz.host;tag1=value1;tag2=value2'),1,-1)`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{120, 180},
			Name:           "baz",
			Tags:           map[string]string{"name": "foo.baz.host", "tag1": "value1", "tag2": "value2"},
			pathExpression: "foo.baz.host;tag1=value1;tag2=value2",
		},
	})
	f(`stdev(time('foo.baz',20),3,0.1)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{0, 10, 16.32993161855452, 16.32993161855452, 16.32993161855452},
			Name:           "stdev(foo.baz,3)",
			Tags:           map[string]string{"name": "foo.baz", "stdev": "3"},
			pathExpression: "foo.baz",
		},
	})
	f(`stdev(time('foo.baz',20),3,0.5)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{nan, 10, 16.32993161855452, 16.32993161855452, 16.32993161855452},
			Name:           "stdev(foo.baz,3)",
			Tags:           map[string]string{"name": "foo.baz", "stdev": "3"},
			pathExpression: "foo.baz",
		},
	})

	f(`stddevSeries(time('foo.baz',30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0, 0, 0},
			Name:       "stddevSeries(foo.baz)",
			Tags:       map[string]string{"name": "foo.baz", "aggregatedBy": "stddev"},
		},
	})

	f(`stacked(
		group(
			time("foo", 30) | alias("foo1.bar2"),
			time("bar", 30) | alias("foo1.bar3")
		))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "stacked(foo1.bar2)",
			Tags:       map[string]string{"name": "foo", "stacked": "__DEFAULT__"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{240, 300, 360},
			Name:       "stacked(foo1.bar3)",
			Tags:       map[string]string{"name": "bar", "stacked": "__DEFAULT__"},
		},
	})
	f(`stacked(
		group(
			time("bar", 30)| alias("foo1.bar1"),
			time("foo", 30) | alias("foo1.bar2"),
			time("foo", 30) | alias("foo1.bar3")
		),
		''
     )`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120, 150, 180},
			Name:       "foo1.bar1",
			Tags:       map[string]string{"name": "bar"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{240, 300, 360},
			Name:       "foo1.bar2",
			Tags:       map[string]string{"name": "foo"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{360, 450, 540},
			Name:       "foo1.bar3",
			Tags:       map[string]string{"name": "foo"},
		},
	})

	f(`squareRoot(time('foo.baz',10))`, []*series{
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{10.954451150103322, 11.40175425099138, 11.832159566199232, 12.24744871391589, 12.649110640673518, 13.038404810405298, 13.416407864998739, 13.784048752090222, 14.142135623730951, 14.491376746189438},
			Name:           "squareRoot(foo.baz)",
			Tags:           map[string]string{"name": "foo.baz", "squareRoot": "1"},
			pathExpression: "squareRoot(foo.baz)",
		},
	})

	f(`sortByTotal(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2"),
			time("foo", 30) | alias("foo1.bar3")
     ))`, []*series{
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{120, 150, 180, 210},
			Name:           "foo1.bar3",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
	})

	f(`sortBy(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2")
             )
    )`, []*series{
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
	})
	f(`sortBy(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2"),
        ),'average',true)`, []*series{
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`sortBy(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2"),
        ),'multiply',true)`, []*series{
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`sortBy(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2"),
        ),'diff')`, []*series{
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`sortByName(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			time("foo", 15) | alias("foo1.bar2")
        ))`, []*series{
		{
			Timestamps:     []int64{120000, 135000, 150000, 165000, 180000, 195000, 210000},
			Values:         []float64{120, 135, 150, 165, 180, 195, 210},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "foo"},
			pathExpression: "foo",
		},
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`sortByMaxima(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			constantLine( 15) | alias("foo1.bar2")
        ))`, []*series{
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{15, 15, 15},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "15"},
			pathExpression: "constantLine(15)",
		},
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`sortByMinima(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			constantLine( 15) | alias("foo1.bar2")
        ))`, []*series{
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
		{
			Timestamps:     []int64{120000, 165000, 210000},
			Values:         []float64{15, 15, 15},
			Name:           "foo1.bar2",
			Tags:           map[string]string{"name": "15"},
			pathExpression: "constantLine(15)",
		},
	})
	f(`sortByMinima(
		group(
			time("bar", 10)| alias("foo1.bar1"),
			constantLine( 0) | alias("foo1.bar2")
        ))`, []*series{
		{
			Timestamps:     []int64{120000, 130000, 140000, 150000, 160000, 170000, 180000, 190000, 200000, 210000},
			Values:         []float64{120, 130, 140, 150, 160, 170, 180, 190, 200, 210},
			Name:           "foo1.bar1",
			Tags:           map[string]string{"name": "bar"},
			pathExpression: "bar",
		},
	})
	f(`smartSummarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'45s'
	)`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{423, 387},
			Name:       `smartSummarize(bar,'45s','sum')`,
			Tags:       map[string]string{"name": "bar", "smartSummarize": "45s", "smartSummarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{558, 555},
			Name:       `smartSummarize(foo,'45s','sum')`,
			Tags:       map[string]string{"name": "foo", "smartSummarize": "45s", "smartSummarizeFunction": "sum"},
		},
	})
	f(`smartSummarize(
		group(
			time('foo',13),
			time('bar',21),
		),
		'1min','sum','hour'
	)`, []*series{
		{
			Timestamps: []int64{0, 60000, 120000},
			Values:     []float64{130, 455, 598},
			Name:       `smartSummarize(foo,'1min','sum')`,
			Tags:       map[string]string{"name": "foo", "smartSummarize": "1min", "smartSummarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{0, 60000, 120000},
			Values:     []float64{63, 252, 441},
			Name:       `smartSummarize(bar,'1min','sum')`,
			Tags:       map[string]string{"name": "bar", "smartSummarize": "1min", "smartSummarizeFunction": "sum"},
		},
	})

	f(`sinFunction("base",1,30)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0.5806111842123143, -0.7148764296291645, -0.8011526357338306},
			Name:       "base",
			Tags:       map[string]string{"name": "base"},
		},
	})
	f(`sinFunction("base",2,30)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{1.1612223684246286, -1.429752859258329, -1.602305271467661},
			Name:       "base",
			Tags:       map[string]string{"name": "base"},
		},
	})
	f(`sinFunction("base",step=20)`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{0.5806111842123143, 0.9802396594403116, 0.21942525837900473, -0.8011526357338306, -0.8732972972139945},
			Name:       "base",
			Tags:       map[string]string{"name": "base"},
		},
	})

	f(`sigmoid(time('foo.baz'))`, []*series{
		{
			Timestamps:     []int64{120000, 180000},
			Values:         []float64{1, 1},
			Name:           "sigmoid(foo.baz)",
			Tags:           map[string]string{"name": "foo.baz", "sigmoid": "sigmoid"},
			pathExpression: "sigmoid(foo.baz)",
		},
	})

	f(`scaleToSeconds(time('foo.bas',20),5)`, []*series{
		{
			Timestamps:     []int64{120000, 140000, 160000, 180000, 200000},
			Values:         []float64{30, 35, 40, 45, 50},
			Name:           "scaleToSeconds(foo.bas,5)",
			Tags:           map[string]string{"name": "foo.bas", "scaleToSeconds": "5"},
			pathExpression: "scaleToSeconds(foo.bas,5)",
		},
	})

	f(`secondYAxis(time('foo.bas',30))`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000, 210000},
			Values:         []float64{120, 150, 180, 210},
			Name:           "secondYAxis(foo.bas)",
			Tags:           map[string]string{"name": "foo.bas", "secondYAxis": "1"},
			pathExpression: "foo.bas",
		},
	})

	f(`isNonNull(timeSlice(time('foo.bar',20),"00:00 19700101","00:03 19700101"))`, []*series{
		{
			Timestamps: []int64{120000, 140000, 160000, 180000, 200000},
			Values:     []float64{1, 1, 1, 1, 0},
			Name:       "isNonNull(timeSlice(foo.bar,0,180))",
			Tags:       map[string]string{"name": "foo.bar", "isNonNull": "1", "timeSliceEnd": "180", "timeSliceStart": "0"},
		},
	})
	f(`linearRegression(
      group(
        time("foo.baz",30),
        time("baz.bar",30),
      )
     )`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(foo.baz, 120, 210)",
			Tags:           map[string]string{"name": "foo.baz", "linearRegressions": "120, 210"},
			pathExpression: "linearRegression(foo.baz, 120, 210)",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(baz.bar, 120, 210)",
			Tags:           map[string]string{"name": "baz.bar", "linearRegressions": "120, 210"},
			pathExpression: "linearRegression(baz.bar, 120, 210)",
		},
	})
	f(`linearRegression(
      group(
        time("foo.baz",30),
        time("baz.bar",30),
      ),
      startSourceAt=100,
      EndSourceAt=None,
     )`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(foo.baz, 100, 210)",
			Tags:           map[string]string{"name": "foo.baz", "linearRegressions": "100, 210"},
			pathExpression: "linearRegression(foo.baz, 100, 210)",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(baz.bar, 100, 210)",
			Tags:           map[string]string{"name": "baz.bar", "linearRegressions": "100, 210"},
			pathExpression: "linearRegression(baz.bar, 100, 210)",
		},
	})
	f(`linearRegression(
      group(
        time("foo.baz",30),
        time("baz.bar",30),
      ),
      startSourceAt=None,
      endSourceAt="00:08 19700101"
     )`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(foo.baz, 120, 480)",
			Tags:           map[string]string{"name": "foo.baz", "linearRegressions": "120, 480"},
			pathExpression: "linearRegression(foo.baz, 120, 480)",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           "linearRegression(baz.bar, 120, 480)",
			Tags:           map[string]string{"name": "baz.bar", "linearRegressions": "120, 480"},
			pathExpression: "linearRegression(baz.bar, 120, 480)",
		},
	})
	f(`holtWintersForecast(time("foo.baz",30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00026583823248, 151.53351196300892, 182.8503377518708},
			Name:       "holtWintersForecast(foo.baz)",
			Tags:       map[string]string{"name": "holtWintersForecast(foo.baz)", "holtWintersForecast": "1"},
		},
	})
	f(`holtWintersForecast(time("foo.baz",30),"4d")`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00027210295323, 152.034912932407, 183.72178095512407},
			Name:       "holtWintersForecast(foo.baz)",
			Tags:       map[string]string{"name": "holtWintersForecast(foo.baz)", "holtWintersForecast": "1"},
		},
	})
	f(`holtWintersForecast(time("foo.baz",30),"8d","2d")`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00000001724152, 152.03464171718454, 183.72151060765324},
			Name:       "holtWintersForecast(foo.baz)",
			Tags:       map[string]string{"name": "holtWintersForecast(foo.baz)", "holtWintersForecast": "1"},
		},
	})
	f(`holtWintersConfidenceBands(time("foo.bar",30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00214864234894, 158.95265929117159, 196.72661235783855},
			Name:       "holtWintersConfidenceUpper(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceUpper": "1"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{119.99838303411602, 144.11436463484625, 168.97406314590305},
			Name:       "holtWintersConfidenceLower(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceLower": "1"},
		},
	})
	f(`holtWintersConfidenceBands(time("foo.bar",30),5,"4d")`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00407891280487, 165.87605713703562, 209.67633502193422},
			Name:       "holtWintersConfidenceUpper(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceUpper": "1"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{119.9964652931016, 138.19376872777838, 157.76722688831393},
			Name:       "holtWintersConfidenceLower(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceLower": "1"},
		},
	})
	f(`holtWintersConfidenceBands(time("foo.bar",30),5,"8d","2d")`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{120.00000014163967, 165.87883899077733, 209.679106539474},
			Name:       "holtWintersConfidenceUpper(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceUpper": "1"},
		},
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{119.99999989284336, 138.19044444359176, 157.7639146758325},
			Name:       "holtWintersConfidenceLower(foo.bar)",
			Tags:       map[string]string{"name": "foo.bar", "holtWintersConfidenceLower": "1"},
		},
	})

	f(`holtWintersAberration(time("baz.baf",30))`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0, 0, 0},
			Name:       "holtWintersAberration(baz.baf)",
			Tags:       map[string]string{"name": "baz.baf", "holtWintersAberration": "1"},
		},
	})
	f(`holtWintersAberration(time("baz.baf",30),2)`, []*series{
		{
			Timestamps: []int64{120000, 150000, 180000},
			Values:     []float64{0, 0, 0},
			Name:       "holtWintersAberration(baz.baf)",
			Tags:       map[string]string{"name": "baz.baf", "holtWintersAberration": "1"},
		},
	})

	f(`holtWintersConfidenceArea(time("foo.baz",30))`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120.00214864234894, 158.95265929117159, 196.72661235783855},
			Name:           "areaBetween(holtWintersConfidenceUpper(foo.baz))",
			Tags:           map[string]string{"holtWintersConfidenceUpper": "1", "areaBetween": "1", "name": "foo.baz"},
			pathExpression: "holtWintersConfidenceUpper(foo.baz)",
		},
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{119.99838303411602, 144.11436463484625, 168.97406314590305},
			Name:           "areaBetween(holtWintersConfidenceLower(foo.baz))",
			Tags:           map[string]string{"holtWintersConfidenceLower": "1", "areaBetween": "1", "name": "foo.baz"},
			pathExpression: "holtWintersConfidenceLower(foo.baz)",
		},
	})

	f(`groupByNode(summarize(
               group(
                       time('foo.bar.baz',20),
                       time('bar.foo.bad',20),
                       time('bar.foo.bad',20),
               ),
               '45s'
       ),1)`, []*series{
		{
			Timestamps:     []int64{120000, 165000},
			Values:         []float64{325, 400},
			Name:           `foo`,
			Tags:           map[string]string{"aggregatedBy": "average", "name": "bar.foo.bad", "summarize": "45s", "summarizeFunction": "sum"},
			pathExpression: "bar.foo.bad",
		},
		{
			Timestamps:     []int64{120000, 165000},
			Values:         []float64{325, 400},
			Name:           `bar`,
			Tags:           map[string]string{"aggregatedBy": "average", "name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
			pathExpression: "foo.bar.baz",
		},
	})
	f(`divideSeries(
    summarize(
               group(
                       time('foo.bar.baz',10),
                       time('bar.foo.bad',10)
               ),
               '45s'
       ),
    summarize(
               group(
                       time('foo.bar.baz',10)
               ),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1, 1},
			Name:       `divideSeries(summarize(foo.bar.baz,'45s','sum'),summarize(foo.bar.baz,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1, 1},
			Name:       `divideSeries(summarize(bar.foo.bad,'45s','sum'),summarize(foo.bar.baz,'45s','sum'))`,
			Tags:       map[string]string{"name": "bar.foo.bad", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`divideSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1, 1},
			Name:       `divideSeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`aggregateSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ), 'sum')`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1170, 2000},
			Name:       `sumSeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`sumSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{1170, 2000},
			Name:       `sumSeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`aggregateSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ), 'diff')`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{0, 0},
			Name:       `diffSeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`diffSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{0, 0},
			Name:       `diffSeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`aggregateSeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ), 'multiply')`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{342225, 1e+06},
			Name:       `multiplySeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`multiplySeriesLists(
    summarize(
               time('foo.bar.baz',10),
               '45s'
       ),
    summarize(
               time('bar.foo.bad',10),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{342225, 1e+06},
			Name:       `multiplySeries(summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'))`,
			Tags:       map[string]string{"name": "foo.bar.baz", "summarize": "45s", "summarizeFunction": "sum"},
		},
	})
	f(`weightedAverage(
    summarize(
               group(
                       time('foo.bar.baz',10),
                       time('bar.foo.bad',10)
               ),
               '45s'
       ),
    summarize(
               group(
                       time('bar.foo.bad',10),
                       time('foo.bar.baz',10)
               ),
               '45s'
       ))`, []*series{
		{
			Timestamps: []int64{120000, 165000},
			Values:     []float64{292.5, 500},
			Name:       `weightedAverage(summarize(bar.foo.bad,'45s','sum'),summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'),summarize(foo.bar.baz,'45s','sum'),)`,
			Tags:       map[string]string{"name": "weightedAverage(summarize(bar.foo.bad,'45s','sum'),summarize(foo.bar.baz,'45s','sum'),summarize(bar.foo.bad,'45s','sum'),summarize(foo.bar.baz,'45s','sum'),)"},
		},
	})
	f(`transformNull(
    time('foo.bar.baz',30),
    -1,
    summarize(
               group(
                       time('foo.bar.baz',10)
               ),
               '30s'
       ))`, []*series{
		{
			Timestamps:     []int64{120000, 150000, 180000},
			Values:         []float64{120, 150, 180},
			Name:           `transformNull(foo.bar.baz,-1,referenceSeries)`,
			Tags:           map[string]string{"name": "foo.bar.baz", "referenceSeries": "1", "transformNull": "-1"},
			pathExpression: ("transformNull(foo.bar.baz,-1,referenceSeries)"),
		},
	})
}

func TestExecExprFailure(t *testing.T) {
	f := func(query string) {
		t.Helper()
		ec := &evalConfig{
			startTime:   120e3,
			endTime:     420e3,
			storageStep: 60e3,
		}
		nextSeries, err := execExpr(ec, query)
		if err == nil {
			if _, err = drainAllSeries(nextSeries); err == nil {
				t.Fatalf("expecting non-nil error for query %q", query)
			}
			nextSeries = nil
		}
		if nextSeries != nil {
			t.Fatalf("expecting nil nextSeries")
		}
	}
	f("123")
	f("nonExistingFunc()")

	f("absolute()")
	f("absolute(1)")
	f("absolute('foo')")

	f("add()")
	f("add(foo.bar)")
	f("add(1.23)")
	f("add(1.23, 4.56)")
	f("add(time('a'), baz)")

	f("aggregate()")
	f("x.y|aggregate()")
	f("aggregate(1)")
	f("aggregate(time('a'), 123)")
	f("aggregate(time('a'), 123, bar.baz)")
	f("aggregate(time('a'), bar)")
	f("aggregate(time('a'), 'non-existing-func')")
	f("aggregate(time('a'), 'sum', 'bar')")
	f("aggregate(1,'sum')")

	f("aggregateLine()")
	f("aggregateLine(123)")
	f("aggregateLine(time('a'), bar)")
	f("aggregateLine(time('a'),'non-existing-func')")
	f("aggregateLine(time('a'),keepStep=aaa)")
	f("aggregateLine(time('a'),'sum',123)")

	f("aggregateWithWildcards()")
	f("aggregateWithWildcards(time('a'),bar)")
	f("aggregateWithWildcards(constantLine(123),'non-existing-func')")
	f("aggregateWithWildcards(time('a'),'sum',bar)")
	f("aggregateWithWildcards(1,'sum')")

	f("alias()")
	f("alias(time('a'))")
	f("alias(time('a'),123)")
	f("alias(1,'aa')")

	f("aliasByMetric()")
	f("aliasByMetric(123)")

	f("aliasByNode()")
	f("aliasByNode(123)")
	f("aliasByNode(time('a'),bar)")

	f("aliasByTags()")
	f("aliasByTags(123)")
	f("aliasByTags(time('a'),bar)")

	f("aliasQuery()")
	f("aliasQuery(1,2,3,4)")
	f("aliasQuery(1,'foo[bar',3,4)")
	f("aliasQuery(1,'foo',3,4)")
	f("aliasQuery(1,'foo','bar',4)")
	f("aliasQuery(constantLine(1)|alias('x'),'x','abc(de','aa')")
	f("aliasQuery(constantLine(1)|alias('x'),'x','group()','aa')")
	f("aliasQuery(1,'foo','bar','aaa')")

	f("aliasSub()")
	f("aliasSub(1,2,3)")
	f("aliasSub(1,'foo[bar',3)")
	f("aliasSub(1,'foo',3)")
	f("aliasSub(1,'foo','bar')")

	f("alpha()")
	f("alpha(1,2)")
	f("alpha(1,foo)")

	f("applyByNode()")
	f("applyByNode(1,2,3)")
	f("applyByNode(1,foo,3)")
	f("applyByNode(1,2,'aaa',4)")
	f("applyByNode(1,2,'foo')")

	f("areaBetween()")
	f("areaBetween(1)")
	f("areaBetween(group(time('1'),time('2'),time('3')))")

	f("asPercent()")
	f("asPercent(1)")
	f("asPercent(time('abc'),'foo')")
	f("asPercent(1,'foo',bar)")
	f("asPercent(time('abc'),100,1)")
	f("asPercent(time('a'),group(time('b'),time('c')))")

	f("averageAbove()")
	f("averageAbove(1,2)")
	f("averageAbove(1,foo)")

	f("averageBelow()")
	f("averageBelow(1,2)")
	f("averageBelow(1,foo)")

	f("averageOutsidePercentile()")
	f("averageOutsidePercentile(1,2)")
	f("averageOutsidePercentile(1,'foo')")

	f("averageSeries(1)")
	f("averageSeries(time('a'),1)")

	f("averageSeriesWithWildcards()")
	f("averageSeriesWithWildcards(1)")
	f("averageSeriesWithWildcards(time('a'),'foo')")

	f("avg(1)")

	f("changed()")
	f("changed(1)")

	f("color()")
	f("color(1,'foo')")
	f("color(1,foo)")

	f("consolidateBy()")
	f("consolidateBy(1,2)")
	f("consolidateBy(1,'foobar')")
	f("consolidateBy(1,'sum')")

	f("constantLine()")
	f("constantLine('foobar')")
	f("constantLine(time('a'))")
	f("constantLine(true)")
	f("constantLine(None)")
	f("constantLine(constantLine(123))")
	f("constantLine(foo=123)")
	f("constantLine(123, 456)")

	f("countSeries(1)")
	f("countSeries(time('a'),1)")

	f("cumulative()")
	f("cumulative(1)")

	f("currentAbove()")
	f("currentAbove(1,2)")
	f("currentAbove(1,foo)")

	f("currentBelow()")
	f("currentBelow(1,2)")
	f("currentBelow(1,foo)")

	f("dashed()")
	f("dashed(1)")
	f("dashed(time('a'),'foo')")

	f("delay()")
	f("delay(1,2)")
	f("delay(time('a'),'foo')")

	f("derivative()")
	f("derivative(1)")

	f("diffSeries(1)")
	f("diffSeries(time('a'),1)")

	f("divideSeries()")
	f("divideSeries(1,2)")
	f("divideSeries(time('a'),group(time('a'),time('b')))")

	f("divideSeriesLists()")
	f("divideSeriesLists(1,2)")
	f("divideSeriesLists(time('a'),2)")
	f("divideSeriesLists(time('a'),group(time('b'),time('c')))")

	f("drawAsInfinite()")
	f("drawAsInfinite(1)")

	f("events(1)")

	f("exclude()")
	f("exclude(1)")
	f("exclude(time('a'),2)")
	f("exclude(1,'foo')")
	f("exclude(1,'f[')")

	f("exp()")
	f("exp(1)")

	f("exponentialMovingAverage()")
	f("exponentialMovingAverage(1,time('a'))")
	f("exponentialMovingAverage(time('a'),'foobar')")

	f("fallbackSeries()")
	f("fallbackSeries(1,2)")
	f("fallbackSeries(group(),2)")

	f("filterSeries()")
	f("filterSeries(1,2,3,4)")
	f("filterSeries(time('a'),'foo','bar','baz')")
	f("filterSeries(time('a'),'sum',1,'baz')")
	f("filterSeries(time('a'),'sum','bar','baz')")
	f("filterSeries(time('a'),'sum','>','baz')")
	f("filterSeries(time('a'),'foo','>',3)")
	f("filterSeries(time('a'),'sum','xxx',3)")

	f("grep()")
	f("grep(1)")
	f("grep(time('a'),2)")
	f("grep(1,'foo')")
	f("grep(1,'f[')")

	f("group(1)")
	f("group('a.b.c')")
	f("group(xx=aa.bb)")
	f("group(constantLine(1),123)")

	f("groupByNode()")
	f("groupByNode(1,123)")
	f("groupByNode(1,time('a'))")
	f("groupByNode(time('a'),1,'foobar')")
	f("groupByNode(time('a'),1,2)")

	f("groupByNodes()")
	f("groupByNodes(time('a'),123)")
	f("groupByNodes(time('a'),'foobar')")
	f("groupByNodes(time('a'),'sum',time('b'))")
	f("groupByNodes(1,'sum')")

	f("groupByTags()")
	f("groupByTags(1,1)")
	f("groupByTags(1,'foo')")
	f("groupByTags(1,'sum',1)")

	f("highest()")
	f("highest(1,'foo')")
	f("highest(1,2,3)")
	f("highest(1,2,'foo')")
	f("highest(1,2,'sum')")

	f("highestAverage()")
	f("highestAverage(1,2)")
	f("highestAverage(1,'foo')")

	f("highestCurrent()")
	f("highestCurrent(1,2)")
	f("highestCurrent(1,'foo')")

	f("highestMax()")
	f("highestMax(1,2)")
	f("highestMax(1,'foo')")

	f("hitcount()")
	f("hitcount(1,2)")
	f("hitcount(1,'5min')")
	f("hitcount(1,'5min','foo')")

	f("identity()")
	f("identity(1)")

	f("integral()")
	f("integral('a')")

	f("integralByInterval()")
	f("integralByInterval(1,2)")
	f("integralByInterval(1,'1h')")

	f("interpolate()")
	f("interpolate(1)")

	f("invert()")
	f("invert(1)")

	f("keepLastValue()")
	f("keepLastValue(1)")

	f("limit()")
	f("limit(1,2)")
	f("limit(1,'foo')")

	f("lineWidth()")
	f("lineWidth(1,2)")
	f("lineWidth(1,'foo')")

	f("logarithm()")
	f("logarithm(1)")
	f("logarithm(1,'foo')")

	f("logit()")
	f("logit(1)")

	f("lowest()")
	f("lowest(1,'foo')")
	f("lowest(1,2,3)")
	f("lowest(1,2,'foo')")
	f("lowest(1,2,'sum')")

	f("lowestAverage()")
	f("lowestAverage(1,2)")
	f("lowestAverage(1,'foo')")

	f("lowestCurrent()")
	f("lowestCurrent(1,2)")
	f("lowestCurrent(1,'foo')")

	f("maxSeries(1)")
	f("maxSeries(time('a'),1)")

	f("maximumAbove()")
	f("maximumAbove(1,2)")
	f("maximumAbove(1,foo)")

	f("maximumBelow()")
	f("maximumBelow(1,2)")
	f("maximumBelow(1,foo)")

	f("minMax()")
	f("minMax(1)")

	f("minSeries(1)")
	f("minSeries(time('a'),1)")

	f("minimumAbove()")
	f("minimumAbove(1,2)")
	f("minimumAbove(1,foo)")

	f("minimumBelow()")
	f("minimumBelow(1,2)")
	f("minimumBelow(1,foo)")

	f("mostDeviant()")
	f("mostDeviant(1,2)")
	f("mostDeviant(1,foo)")

	f("movingAverage()")
	f("movingAverage(time('a'),time('b'))")
	f("movingAverage(1,1)")
	f("movingAverage(time('a),1,'foo')")
	f("movingAverage(foo=a,bar=2,baz=3)")

	f("movingMax()")
	f("movingMax(1,'5min')")
	f("movingMax(1,foo=true)")

	f("movingMedian()")
	f("movingMedian(1,'5min')")
	f("movingMedian(1,foo=true)")

	f("movingMin()")
	f("movingMin(1,'5min')")
	f("movingMin(1,foo=true)")

	f("movingSum()")
	f("movingSum(1,'5min')")
	f("movingSum(1,foo=true)")

	f("movingWindow()")
	f("movingWindow(1,foo)")
	f("movingWindow(1,'foo')")
	f("movingWindow(1,-1)")
	f("movingWindow(1,2)")
	f("movingWindow(1,2,3)")
	f("movingWindow(1,2,'non-existing-aggr-func')")
	f("movingWindow(1,2,'sum',foo)")

	f("multiplySeries(1)")
	f("multiplySeries(time('a'),1)")

	f("multiplyWithWildcards()")
	f("multiplyWithWildcards(time('a'),bar)")
	f("multiplyWithWildcards(constantLine(123),'non-existing-func')")
	f("multiplyWithWildcards(time('a'),'sum',bar)")
	f("multiplyWithWildcards(1,'sum')")

	f(`nPercentile()`)
	f(`nPercentile(1,1)`)
	f(`nPercentile(1,'foo')`)

	f(`nonNegativeDerivative()`)
	f(`nonNegativeDerivative(1)`)

	f("offset()")
	f("offset(1,2)")
	f("offset(time('a'),'fo')")

	f("offsetToZero()")
	f("offsetToZero(1)")

	f("pow()")
	f("pow(1,2)")
	f("pow(1,'foo')")

	f("rangeOfSeries(1)")
	f("rangeOfSeries(time('a'),1)")

	f("randomWalk()")
	f("randomWalk(1)")
	f("randomWalk('foo','bar')")

	f("removeAbovePercentile()")
	f("removeAbovePercentile(1, 2)")
	f("removeAbovePercentile(1, 'foo')")

	f("removeAboveValue()")
	f("removeAboveValue(1, 2)")
	f("removeAboveValue(1, 'foo')")

	f("removeBelowPercentile()")
	f("removeBelowPercentile(1, 2)")
	f("removeBelowPercentile(1, 'foo')")

	f("removeBelowValue()")
	f("removeBelowValue(1, 2)")
	f("removeBelowValue(1, 'foo')")

	f("removeBetweenPercentile()")
	f("removeBetweenPercentile(1,2)")
	f("removeBetweenPercentile(1,'foo')")

	f("removeEmptySeries()")
	f("removeEmptySeries(1)")
	f("removeEmptySeries(1,'fii')")

	f("round()")
	f("round(1)")
	f("round(1,'foo')")

	f("scale()")
	f("scale(1,2)")
	f("scale(time('a'),'foo')")

	f("setXFilesFactor()")
	f("setXFilesFactor(1,'foo')")
	f("setXFilesFactor(1,0.5)")

	f("sumWithWildcards()")
	f("sumWithWildcards(time('a'),bar)")
	f("sumWithWildcards(constantLine(123),'non-existing-func')")
	f("sumWithWildcards(time('a'),'sum',bar)")
	f("sumWithWildcards(1,'sum')")

	f("summarize()")
	f("summarize(1,2)")
	f("summarize(1,'foobar')")
	f("summarize(1,'-2min')")
	f("summarize(1, '0seconds')")
	f("summarize(1,'2min',3)")
	f("summarize(1,'1s','non-existing-func')")
	f("summarize(1,'1s','sum',3)")
	f("summarize(1,'1s')")

	f("time()")
	f("time(1)")

	f("timeFunction()")
	f("timeFunction(1)")
	f("timeFunction(False)")
	f("timeFunction(None)")
	f("timeFunction(constantLine(123))")
	f("timeFunction(foo='bar')")
	f("timeFunction('foo', 'bar')")

	f(`verticalLine("12:3420131108","event","blue",5)`)
	f(`verticalLine(10)`)
	f(`verticalLine("12:3420131108",4,5)`)
	f(`verticalLine("12:3420131108","event",4)`)
	f(`verticalLine("12:3420131108SF1bad","event")`)
	f(`verticalLine("00:01 19700101","event")`)

	f(`useSeriesAbove()`)
	f(`useSeriesAbove(1,10)`)
	f(`useSeriesAbove(1,10,10,5)`)
	f(`useSeriesAbove(1,"10",10,15)`)
	f(`useSeriesAbove(1,10,"(?=<bad>10)",15)`)
	f(`useSeriesAbove(1,10,"10",5)`)

	f(`unique(5,2)`)

	f(`perSecond()`)
	f(`perSecond(1)`)

	f(`percentileOfSeries()`)
	f(`percentileOfSeries(1)`)

	f(`substr()`)
	f(`substr(time('a'),'foo')`)
	f(`substr(time('a'),1,'foo')`)

	f(`sumSeries(1)`)
	f("sumSeries(time('a'),1)")

	f(`threshold()`)
	f(`threshold("bad arg")`)
	f(`threshold(1.5,5,"black")`)
	f(`threshold(1.5,"max",5)`)

	f(`timeShift()`)
	f(`timeShift(time('a'),1)`)
	f(`timeShift(time('a'),'foo')`)

	f(`timeSlice()`)
	f(`timeSlice(time('a'),1)`)
	f(`timeSlice(time('a'),'foo')`)
	f(`timeSlice(time('a'),'5min',1)`)
	f(`timeSlice(time('a'),'5min','bar')`)
	f(`timeSlice(1,'5min','10min')`)

	f(`timeStack()`)
	f(`timeStack(time('a'),timeShiftUnit=123)`)
	f(`timeStack(time('a'),'foo')`)
	f(`timeStack(time('a'),'1m',timeShiftStart='foo')`)
	f(`timeStack(time('a'),'1m',timeShiftEnd='bar')`)
	f(`timeStack(time('a'),'1m',10,1)`)

	f(`transformNull()`)
	f(`transformNull(1,-1,5,2)`)
	f(`transformNull(None)`)
	f(`transformNull(time('a'),2,'xxx')`)
	f(`transformNull(time('a'),'foo')`)

	f("weightedAverage()")
	f("weightedAverage(1,2)")
	f("weightedAverage(time('a'),2)")
	f("weightedAverage(time('a'),time('b'),foo.bar)")
	f("weightedAverage(time('a'),group(time('b'),time('c')))")

	f("xFilesFactor()")
	f("xFilesFactor(1,'foo')")
	f("xFilesFactor(1,0.5)")

	f(`stdev()`)
	f(`stdev(1,3,0.5)`)
	f(`stdev(1,"5",0.5)`)
	f(`stdev(1,3,"0.5")`)

	f(`stddevSeries(5)`)
	f(`stddevSeries(1)`)

	f(`stacked()`)
	f(`stacked(1)`)
	f(`stacked(1,5)`)

	f(`squareRoot()`)
	f(`squareRoot(5)`)

	f(`sortByTotal()`)
	f(`sortByTotal(1)`)

	f(`sortBy()`)
	f(`sortBy(1)`)
	f(`sortBy(1,5)`)
	f(`sortBy(1,'bad func name')`)
	f(`sortBy(1,'sum','non bool')`)

	f(`sortByName()`)
	f(`sortByName(1)`)
	f(`sortByName(1,"bad bool")`)
	f(`sortByName(1,true,"bad bool")`)
	f(`sortByName(1,5,5,6)`)

	f(`sortByMinima()`)
	f(`sortByMinima(1)`)

	f(`sortByMaxima()`)
	f(`sortByMaxima(1)`)

	f(`smartSummarize(1)`)
	f(`smartSummarize(1,"1d")`)
	f(`smartSummarize(1,"1d","sum","1light year")`)
	f(`smartSummarize(1,1)`)
	f(`smartSummarize(1,"1d",1)`)
	f(`smartSummarize(1,"1light year")`)
	f(`smartSummarize(1,"-1d")`)
	f(`smartSummarize(1,"1d","bad func")`)
	f(`smartSummarize(1,"1d","sum",true)`)

	f(`sinFunction()`)
	f(`sinFunction(5)`)
	f(`sinFunction("name","bad arg")`)
	f(`sinFunction("name",1,"bad arg")`)
	f(`sinFunction(1,-2,3)`)

	f(`sigmoid()`)
	f(`sigmoid(1)`)

	f(`scaleToSeconds()`)
	f(`scaleToSeconds(1,10)`)
	f(`scaleToSeconds(1,"10")`)

	f(`secondYAxis()`)
	f(`secondYAxis(1)`)

	f(`isNonNull()`)
	f(`isNonNull(1)`)

	f(`linearRegression()`)
	f(`linearRegression(10)`)
	f(`linearRegression(none.exist.metric)`)
	f(`linearRegression(none.exist.metric,"badarg1")`)
	f(`linearRegression(time("foo.baz",15),"-1min","badargv2")`)

	f(`holtWintersForecast()`)
	f(`holtWintersForecast(none.exist.metric)`)
	f(`holtWintersForecast(none.exist.metric,124124)`)
	f(`holtWintersForecast(none.exist.metric,7d,"ads124")`)
	f(`holtWintersForecast(none.exist.metric,"7d","ads124")`)
	f(`holtWintersForecast(none.exist.metric,"afsf","7d")`)
	f(`holtWintersForecast(none.exist.metric,"7d",124214)`)

	f(`holtWintersConfidenceBands()`)
	f(`holtWintersConfidenceBands(none.exist.metric)`)
	f(`holtWintersConfidenceBands(none.exist.metric,"124124")`)
	f(`holtWintersConfidenceBands(none.exist.metric,7,123)`)
	f(`holtWintersConfidenceBands(none.exist.metric,7,"ads124")`)
	f(`holtWintersConfidenceBands(none.exist.metric,7,"7d","ads124")`)
	f(`holtWintersConfidenceBands(none.exist.metric,7,"afsf","7d")`)
	f(`holtWintersConfidenceBands(none.exist.metric,7,"7d",124214)`)

	f(`holtWintersAberration()`)
	f(`holtWintersAberration(124)`)
	f(`holtWintersAberration(none.exist.metric)`)

	f(`holtWintersConfidenceArea(group(time("foo.baz",15),time("foo.baz",15)))`)
	f(`holtWintersConfidenceArea()`)

	// too long query
	f(`sumSeries(` + strings.Repeat("metric.very.long.name.that.takes.space,", 500) + `metric.final)`)
}

func compareSeries(ss, ssExpected []*series, expr graphiteql.Expr) error {
	if len(ss) != len(ssExpected) {
		return fmt.Errorf("unexpected series count; got %d; want %d", len(ss), len(ssExpected))
	}
	m := make(map[string]*series)
	for _, s := range ssExpected {
		m[s.Name] = s
	}
	exprStrExpected := string(expr.AppendString(nil))
	for _, s := range ss {
		sExpected := m[s.Name]
		if sExpected == nil {
			return fmt.Errorf("missing series with name %q", s.Name)
		}
		if !reflect.DeepEqual(s.Tags, sExpected.Tags) {
			return fmt.Errorf("unexpected tag for series %q\ngot\n%s\nwant\n%s", s.Name, s.Tags, sExpected.Tags)
		}
		if !reflect.DeepEqual(s.Timestamps, sExpected.Timestamps) {
			return fmt.Errorf("unexpected timestamps for series %q\ngot\n%d\nwant\n%d", s.Name, s.Timestamps, sExpected.Timestamps)
		}

		if !equalFloats(s.Values, sExpected.Values) {
			return fmt.Errorf("unexpected values for series %q\ngot\n%g\nwant\n%g", s.Name, s.Values, sExpected.Values)
		}
		expectedPathExpression := sExpected.Name
		if sExpected.pathExpression != "" {
			expectedPathExpression = sExpected.pathExpression
		}
		if expectedPathExpression != s.pathExpression {
			return fmt.Errorf("unexpected pathExpression for series %q\ngot\n%s\nwant\n%s", s.Name, s.pathExpression, expectedPathExpression)
		}
		exprStr := string(s.expr.AppendString(nil))
		if exprStr != exprStrExpected {
			return fmt.Errorf("unexpected expr for series %q\ngot\n%s\nwant\n%s", s.Name, exprStr, exprStrExpected)
		}
	}
	return nil
}

func equalFloats(a, b []float64) bool {
	if len(a) != len(b) {
		return false
	}
	for i, v1 := range a {
		v2 := b[i]
		if math.IsNaN(v1) {
			if math.IsNaN(v2) {
				continue
			}
			return false
		} else if math.IsNaN(v2) {
			return false
		}
		eps := math.Abs(v1) / 1e9
		if math.Abs(v1-v2) > eps {
			return false
		}
	}
	return true
}

func printSeriess(ss []*series) string {
	var sb strings.Builder
	fmt.Fprintf(&sb, "[\n")
	for _, s := range ss {
		fmt.Fprintf(&sb, "\t{name=%q,tags=%v,timestamps=%s,values=%s}\n", s.Name, s.Tags, formatTimestamps(s.Timestamps), formatValues(s.Values))
	}
	fmt.Fprintf(&sb, "]\n")
	return sb.String()
}

func formatValues(vs []float64) string {
	if len(vs) == 0 {
		return "[]"
	}
	var sb strings.Builder
	fmt.Fprintf(&sb, "[ ")
	for i, v := range vs {
		if math.IsNaN(v) {
			fmt.Fprintf(&sb, "nan")
		} else {
			fmt.Fprintf(&sb, "%g", v)
		}
		if i != len(vs)-1 {
			fmt.Fprintf(&sb, ", ")
		}
	}
	fmt.Fprintf(&sb, " ]")
	return sb.String()
}

func formatTimestamps(tss []int64) string {
	if len(tss) == 0 {
		return "[]"
	}
	var sb strings.Builder
	fmt.Fprintf(&sb, "[ ")
	for i, ts := range tss {
		fmt.Fprintf(&sb, "%d", ts)
		if i != len(tss)-1 {
			fmt.Fprintf(&sb, ", ")
		}
	}
	fmt.Fprintf(&sb, " ]")
	return sb.String()
}

func TestSafePathExpression(t *testing.T) {
	// Save original value and restore after test
	originalMaxLen := *maxGraphitePathExpressionLen
	defer func() {
		*maxGraphitePathExpressionLen = originalMaxLen
	}()

	t.Run("nil expression", func(t *testing.T) {
		result := safePathExpression(nil)
		if result != "" {
			t.Fatalf("expected empty string for nil expression, got: %q", result)
		}
	})

	t.Run("short expression - no truncation", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 50
		expr := &graphiteql.MetricExpr{Query: "metric.cpu.usage"}
		result := safePathExpression(expr)
		expected := "metric.cpu.usage"
		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
	})

	t.Run("long expression - with truncation", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 20
		longQuery := "vertica.metrics.fr4.verticamultitenant-eon.request_resource_consumption.very_long_metric_name"
		expr := &graphiteql.MetricExpr{Query: longQuery}
		result := safePathExpression(expr)
		expectedPrefix := longQuery[:20]
		expectedSuffix := "..."
		expected := expectedPrefix + expectedSuffix

		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
		if len(result) != 23 { // 20 + 3 for "..."
			t.Fatalf("expected result length 23, got %d", len(result))
		}
		if !strings.HasSuffix(result, "...") {
			t.Fatalf("expected result to end with '...', got %q", result)
		}
	})

	t.Run("truncation disabled", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 0 // Disable truncation
		longQuery := "very.long.metric.name.that.would.normally.be.truncated.but.should.not.be"
		expr := &graphiteql.MetricExpr{Query: longQuery}
		result := safePathExpression(expr)

		if result != longQuery {
			t.Fatalf("expected full string %q when truncation disabled, got %q", longQuery, result)
		}
	})

	t.Run("function expression", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 30
		// Create a function expression: sum(metric.cpu.usage)
		args := []*graphiteql.ArgExpr{
			{Expr: &graphiteql.MetricExpr{Query: "metric.cpu.usage"}},
		}
		funcExpr := &graphiteql.FuncExpr{
			FuncName: "sum",
			Args:     args,
		}

		result := safePathExpression(funcExpr)
		expected := "sum(metric.cpu.usage)"

		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
	})

	t.Run("complex nested function - truncated", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 15
		// Create nested functions: sum(avg(metric.cpu.usage))
		innerArgs := []*graphiteql.ArgExpr{
			{Expr: &graphiteql.MetricExpr{Query: "metric.cpu.usage"}},
		}
		innerFunc := &graphiteql.FuncExpr{
			FuncName: "avg",
			Args:     innerArgs,
		}
		outerArgs := []*graphiteql.ArgExpr{
			{Expr: innerFunc},
		}
		outerFunc := &graphiteql.FuncExpr{
			FuncName: "sum",
			Args:     outerArgs,
		}

		result := safePathExpression(outerFunc)

		if len(result) != 18 { // 15 + 3 for "..."
			t.Fatalf("expected result length 18, got %d", len(result))
		}
		if !strings.HasSuffix(result, "...") {
			t.Fatalf("expected result to end with '...', got %q", result)
		}
		if !strings.HasPrefix(result, "sum(avg(metric") {
			t.Fatalf("expected result to start with 'sum(avg(metric', got %q", result)
		}
	})

	t.Run("boundary case - exact length", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 10
		expr := &graphiteql.MetricExpr{Query: "metric.cpu"} // Exactly 10 characters
		result := safePathExpression(expr)
		expected := "metric.cpu"

		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
	})

	t.Run("boundary case - one character over", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 10
		expr := &graphiteql.MetricExpr{Query: "metric.cpu.x"} // 11 characters
		result := safePathExpression(expr)
		expected := "metric.cpu..."

		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
	})
}

func TestSafePathExpressionFromString(t *testing.T) {
	// Save original value and restore after test
	originalMaxLen := *maxGraphitePathExpressionLen
	defer func() {
		*maxGraphitePathExpressionLen = originalMaxLen
	}()

	t.Run("short string - no truncation", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 50
		input := "sumSeries(metric1,metric2)"
		result := safePathExpressionFromString(input)

		if result != input {
			t.Fatalf("expected %q, got %q", input, result)
		}
	})

	t.Run("long string - with truncation", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 20
		input := "sumSeries(very.long.metric.name.that.exceeds.limit,another.metric)"
		result := safePathExpressionFromString(input)
		expected := "sumSeries(very.long...."

		if result != expected {
			t.Fatalf("expected %q, got %q", expected, result)
		}
	})

	t.Run("truncation disabled", func(t *testing.T) {
		*maxGraphitePathExpressionLen = 0
		input := "very.long.string.that.would.normally.be.truncated"
		result := safePathExpressionFromString(input)

		if result != input {
			t.Fatalf("expected full string when truncation disabled, got %q", result)
		}
	})
}
