From 46848f333c366dd25dc8a8d896afcad172758671 Mon Sep 17 00:00:00 2001 From: Tiago Natel Date: Wed, 6 Dec 2023 00:06:59 +0000 Subject: [PATCH] chore(perf): optimise tm_alltrue() and tm_anytrue(). Signed-off-by: Tiago Natel --- .github/workflows/benchmark.yml | 2 +- globals/globals_test.go | 129 ++++++++++++++++++++++++++++ makefiles/common.mk | 2 +- stdlib/collection.go | 145 ++++++++++++++++++++++++++++++++ stdlib/funcs.go | 4 + 5 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 stdlib/collection.go diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 906bbe60c4..022c623ce3 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -28,7 +28,7 @@ jobs: id: benchmark run: | echo "result<> $GITHUB_OUTPUT - echo "$(make bench/check new=${{ github.event.pull_request.head.sha }})" >> $GITHUB_OUTPUT + echo "$(make bench/check new=${{ github.event.pull_request.head.sha }} old=${{ github.event.pull_request.base.ref }})" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - uses: marocchino/sticky-pull-request-comment@v2 diff --git a/globals/globals_test.go b/globals/globals_test.go index 0d9f136b5b..b3e9377382 100644 --- a/globals/globals_test.go +++ b/globals/globals_test.go @@ -2870,6 +2870,135 @@ func TestLoadGlobals(t *testing.T) { ), }, }, + { + name: "tm_alltrue with literal list of bool", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([true, 1==1, false==false, !false])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_alltrue with literal tuple containing non-boolean", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([true, 1==1, "string", {}])`), + ), + }, + }, + wantErr: errors.E(globals.ErrEval), + }, + { + name: "tm_alltrue with literal for-loop", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([for i in [true, 1==1, !false] : i])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_alltrue with funcall", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue(tm_distinct([true, !false, 1==1]))`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + + { + name: "tm_anytrue with literal list of bool", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([false, 1!=1, false==false, !false])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_anytrue with literal tuple containing non-boolean", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([false, 1!=1, "string", {}])`), + ), + }, + }, + wantErr: errors.E(globals.ErrEval), + }, + { + name: "tm_anytrue with literal for-loop", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([for i in [false, 1!=1, !false] : i])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_anytrue with funcall", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue(tm_distinct([false, false, 1==1]))`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, { name: "globals.map label conflicts with global name", layout: []string{"s:stack"}, diff --git a/makefiles/common.mk b/makefiles/common.mk index 18239d3bf6..d771c1c4ea 100644 --- a/makefiles/common.mk +++ b/makefiles/common.mk @@ -116,7 +116,7 @@ bench/check: allocdelta="+20%" bench/check: timedelta="+20%" bench/check: name=github.com/terramate-io/terramate bench/check: pkg?=./... -bench/check: old=main +bench/check: old?=main bench/check: new?=$(shell git rev-parse HEAD) bench/check: @$(BENCH_CHECK) -mod $(name) -pkg $(pkg) -go-test-flags "-benchmem,-count=20,-run=Bench" \ diff --git a/stdlib/collection.go b/stdlib/collection.go new file mode 100644 index 0000000000..80fcde3e00 --- /dev/null +++ b/stdlib/collection.go @@ -0,0 +1,145 @@ +// Copyright 2023 Terramate GmbH +// SPDX-License-Identifier: MPL-2.0 + +package stdlib + +import ( + "github.com/hashicorp/hcl/v2/ext/customdecode" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/hcl/eval" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// AllTrueFunc implements the `tm_alltrue()` function. +func AllTrueFunc() function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: customdecode.ExpressionClosureType, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + argClosure := customdecode.ExpressionClosureFromVal(args[0]) + evalctx := eval.NewContextFrom(argClosure.EvalContext) + listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr) + if !ok { + v, err := evalctx.Eval(argClosure.Expression) + if err != nil { + return cty.False, err + } + if !v.Type().IsListType() && !v.Type().IsTupleType() { + return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName()) + } + result := true + i := 0 + for it := v.ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + return cty.UnknownVal(cty.Bool), nil + } + if v.IsNull() { + return cty.False, nil + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result && v.True() + if !result { + return cty.False, nil + } + i++ + } + return cty.True, nil + } + + result := true + for i, expr := range listExpression.Exprs { + v, err := evalctx.Eval(expr) + if err != nil { + return cty.False, err + } + if v.IsNull() { + return cty.False, nil + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result && v.True() + if !result { + return cty.False, nil + } + } + return cty.True, nil + }, + }) +} + +// AnyTrueFunc implements the `tm_anytrue()` function. +func AnyTrueFunc() function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: customdecode.ExpressionClosureType, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + argClosure := customdecode.ExpressionClosureFromVal(args[0]) + evalctx := eval.NewContextFrom(argClosure.EvalContext) + listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr) + if !ok { + v, err := evalctx.Eval(argClosure.Expression) + if err != nil { + return cty.False, err + } + if !v.Type().IsListType() && !v.Type().IsTupleType() { + return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName()) + } + result := false + i := 0 + for it := v.ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + continue + } + if v.IsNull() { + continue + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result || v.True() + if result { + return cty.True, nil + } + i++ + } + return cty.False, nil + } + + result := false + for i, expr := range listExpression.Exprs { + v, err := evalctx.Eval(expr) + if err != nil { + return cty.False, err + } + if v.IsNull() { + continue + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result || v.True() + if result { + return cty.True, nil + } + } + return cty.False, nil + }, + }) +} diff --git a/stdlib/funcs.go b/stdlib/funcs.go index d1d65e265c..be71dfab7d 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -67,6 +67,10 @@ func Functions(basedir string) map[string]function.Function { // sane ternary tmfuncs["tm_ternary"] = TernaryFunc() + // optimized collection functions + tmfuncs["tm_alltrue"] = AllTrueFunc() + tmfuncs["tm_anytrue"] = AnyTrueFunc() + tmfuncs["tm_version_match"] = VersionMatch() return tmfuncs }