Skip to content

Commit b9f6b23

Browse files
author
Liam Cervante
authored
testing framework: add support for functions in variables and providers (#34204)
1 parent 4ce385a commit b9f6b23

File tree

5 files changed

+187
-129
lines changed

5 files changed

+187
-129
lines changed

internal/command/test_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ func TestTest(t *testing.T) {
189189
expected: "4 passed, 0 failed.",
190190
code: 0,
191191
},
192+
"functions_available": {
193+
expected: "1 passed, 0 failed.",
194+
code: 0,
195+
},
192196
}
193197
for name, tc := range tcs {
194198
t.Run(name, func(t *testing.T) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
variable "input" {
3+
type = string
4+
}
5+
6+
resource "test_resource" "resource" {
7+
value = var.input
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
run "test" {
3+
variables {
4+
input = jsonencode({key:"value"})
5+
}
6+
7+
assert {
8+
condition = jsondecode(test_resource.resource.value).key == "value"
9+
error_message = "wrong value"
10+
}
11+
}

internal/lang/functions.go

Lines changed: 158 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -27,136 +27,10 @@ var impureFunctions = []string{
2727
func (s *Scope) Functions() map[string]function.Function {
2828
s.funcsLock.Lock()
2929
if s.funcs == nil {
30-
// Some of our functions are just directly the cty stdlib functions.
31-
// Others are implemented in the subdirectory "funcs" here in this
32-
// repository. New functions should generally start out their lives
33-
// in the "funcs" directory and potentially graduate to cty stdlib
34-
// later if the functionality seems to be something domain-agnostic
35-
// that would be useful to all applications using cty functions.
36-
37-
s.funcs = map[string]function.Function{
38-
"abs": stdlib.AbsoluteFunc,
39-
"abspath": funcs.AbsPathFunc,
40-
"alltrue": funcs.AllTrueFunc,
41-
"anytrue": funcs.AnyTrueFunc,
42-
"basename": funcs.BasenameFunc,
43-
"base64decode": funcs.Base64DecodeFunc,
44-
"base64encode": funcs.Base64EncodeFunc,
45-
"base64gzip": funcs.Base64GzipFunc,
46-
"base64sha256": funcs.Base64Sha256Func,
47-
"base64sha512": funcs.Base64Sha512Func,
48-
"bcrypt": funcs.BcryptFunc,
49-
"can": tryfunc.CanFunc,
50-
"ceil": stdlib.CeilFunc,
51-
"chomp": stdlib.ChompFunc,
52-
"cidrhost": funcs.CidrHostFunc,
53-
"cidrnetmask": funcs.CidrNetmaskFunc,
54-
"cidrsubnet": funcs.CidrSubnetFunc,
55-
"cidrsubnets": funcs.CidrSubnetsFunc,
56-
"coalesce": funcs.CoalesceFunc,
57-
"coalescelist": stdlib.CoalesceListFunc,
58-
"compact": stdlib.CompactFunc,
59-
"concat": stdlib.ConcatFunc,
60-
"contains": stdlib.ContainsFunc,
61-
"csvdecode": stdlib.CSVDecodeFunc,
62-
"dirname": funcs.DirnameFunc,
63-
"distinct": stdlib.DistinctFunc,
64-
"element": stdlib.ElementFunc,
65-
"endswith": funcs.EndsWithFunc,
66-
"chunklist": stdlib.ChunklistFunc,
67-
"file": funcs.MakeFileFunc(s.BaseDir, false),
68-
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
69-
"fileset": funcs.MakeFileSetFunc(s.BaseDir),
70-
"filebase64": funcs.MakeFileFunc(s.BaseDir, true),
71-
"filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir),
72-
"filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir),
73-
"filemd5": funcs.MakeFileMd5Func(s.BaseDir),
74-
"filesha1": funcs.MakeFileSha1Func(s.BaseDir),
75-
"filesha256": funcs.MakeFileSha256Func(s.BaseDir),
76-
"filesha512": funcs.MakeFileSha512Func(s.BaseDir),
77-
"flatten": stdlib.FlattenFunc,
78-
"floor": stdlib.FloorFunc,
79-
"format": stdlib.FormatFunc,
80-
"formatdate": stdlib.FormatDateFunc,
81-
"formatlist": stdlib.FormatListFunc,
82-
"indent": stdlib.IndentFunc,
83-
"index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible
84-
"join": stdlib.JoinFunc,
85-
"jsondecode": stdlib.JSONDecodeFunc,
86-
"jsonencode": stdlib.JSONEncodeFunc,
87-
"keys": stdlib.KeysFunc,
88-
"length": funcs.LengthFunc,
89-
"list": funcs.ListFunc,
90-
"log": stdlib.LogFunc,
91-
"lookup": funcs.LookupFunc,
92-
"lower": stdlib.LowerFunc,
93-
"map": funcs.MapFunc,
94-
"matchkeys": funcs.MatchkeysFunc,
95-
"max": stdlib.MaxFunc,
96-
"md5": funcs.Md5Func,
97-
"merge": stdlib.MergeFunc,
98-
"min": stdlib.MinFunc,
99-
"one": funcs.OneFunc,
100-
"parseint": stdlib.ParseIntFunc,
101-
"pathexpand": funcs.PathExpandFunc,
102-
"pow": stdlib.PowFunc,
103-
"range": stdlib.RangeFunc,
104-
"regex": stdlib.RegexFunc,
105-
"regexall": stdlib.RegexAllFunc,
106-
"replace": funcs.ReplaceFunc,
107-
"reverse": stdlib.ReverseListFunc,
108-
"rsadecrypt": funcs.RsaDecryptFunc,
109-
"sensitive": funcs.SensitiveFunc,
110-
"nonsensitive": funcs.NonsensitiveFunc,
111-
"setintersection": stdlib.SetIntersectionFunc,
112-
"setproduct": stdlib.SetProductFunc,
113-
"setsubtract": stdlib.SetSubtractFunc,
114-
"setunion": stdlib.SetUnionFunc,
115-
"sha1": funcs.Sha1Func,
116-
"sha256": funcs.Sha256Func,
117-
"sha512": funcs.Sha512Func,
118-
"signum": stdlib.SignumFunc,
119-
"slice": stdlib.SliceFunc,
120-
"sort": stdlib.SortFunc,
121-
"split": stdlib.SplitFunc,
122-
"startswith": funcs.StartsWithFunc,
123-
"strcontains": funcs.StrContainsFunc,
124-
"strrev": stdlib.ReverseFunc,
125-
"substr": stdlib.SubstrFunc,
126-
"sum": funcs.SumFunc,
127-
"textdecodebase64": funcs.TextDecodeBase64Func,
128-
"textencodebase64": funcs.TextEncodeBase64Func,
129-
"timestamp": funcs.TimestampFunc,
130-
"timeadd": stdlib.TimeAddFunc,
131-
"timecmp": funcs.TimeCmpFunc,
132-
"title": stdlib.TitleFunc,
133-
"tostring": funcs.MakeToFunc(cty.String),
134-
"tonumber": funcs.MakeToFunc(cty.Number),
135-
"tobool": funcs.MakeToFunc(cty.Bool),
136-
"toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)),
137-
"tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)),
138-
"tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
139-
"transpose": funcs.TransposeFunc,
140-
"trim": stdlib.TrimFunc,
141-
"trimprefix": stdlib.TrimPrefixFunc,
142-
"trimspace": stdlib.TrimSpaceFunc,
143-
"trimsuffix": stdlib.TrimSuffixFunc,
144-
"try": tryfunc.TryFunc,
145-
"upper": stdlib.UpperFunc,
146-
"urlencode": funcs.URLEncodeFunc,
147-
"uuid": funcs.UUIDFunc,
148-
"uuidv5": funcs.UUIDV5Func,
149-
"values": stdlib.ValuesFunc,
150-
"yamldecode": ctyyaml.YAMLDecodeFunc,
151-
"yamlencode": ctyyaml.YAMLEncodeFunc,
152-
"zipmap": stdlib.ZipmapFunc,
153-
}
30+
s.funcs = baseFunctions(s.BaseDir)
15431

155-
s.funcs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, func() map[string]function.Function {
156-
// The templatefile function prevents recursive calls to itself
157-
// by copying this map and overwriting the "templatefile" entry.
158-
return s.funcs
159-
})
32+
// Then we add some functions that are only relevant when being accessed
33+
// from inside a specific scope.
16034

16135
if s.ConsoleMode {
16236
// The type function is only available in terraform console.
@@ -190,6 +64,161 @@ func (s *Scope) Functions() map[string]function.Function {
19064
return s.funcs
19165
}
19266

67+
// TestingFunctions returns the set of functions available to the testing
68+
// framework. Generally, the testing framework doesn't have access to a specific
69+
// state or plan when executing these functions so some of the functions
70+
// available normally are not available during tests.
71+
func TestingFunctions() map[string]function.Function {
72+
// The baseDir is always the current directory during the tests.
73+
fs := baseFunctions(".")
74+
75+
// Add a description to each function and parameter based on the
76+
// contents of descriptionList.
77+
// One must create a matching description entry whenever a new
78+
// function is introduced.
79+
for name, f := range fs {
80+
fs[name] = funcs.WithDescription(name, f)
81+
}
82+
83+
return fs
84+
}
85+
86+
// baseFunctions loads the set of functions that are used in both the testing
87+
// framework and the main Terraform operations.
88+
func baseFunctions(baseDir string) map[string]function.Function {
89+
// Some of our functions are just directly the cty stdlib functions.
90+
// Others are implemented in the subdirectory "funcs" here in this
91+
// repository. New functions should generally start out their lives
92+
// in the "funcs" directory and potentially graduate to cty stdlib
93+
// later if the functionality seems to be something domain-agnostic
94+
// that would be useful to all applications using cty functions.
95+
fs := map[string]function.Function{
96+
"abs": stdlib.AbsoluteFunc,
97+
"abspath": funcs.AbsPathFunc,
98+
"alltrue": funcs.AllTrueFunc,
99+
"anytrue": funcs.AnyTrueFunc,
100+
"basename": funcs.BasenameFunc,
101+
"base64decode": funcs.Base64DecodeFunc,
102+
"base64encode": funcs.Base64EncodeFunc,
103+
"base64gzip": funcs.Base64GzipFunc,
104+
"base64sha256": funcs.Base64Sha256Func,
105+
"base64sha512": funcs.Base64Sha512Func,
106+
"bcrypt": funcs.BcryptFunc,
107+
"can": tryfunc.CanFunc,
108+
"ceil": stdlib.CeilFunc,
109+
"chomp": stdlib.ChompFunc,
110+
"cidrhost": funcs.CidrHostFunc,
111+
"cidrnetmask": funcs.CidrNetmaskFunc,
112+
"cidrsubnet": funcs.CidrSubnetFunc,
113+
"cidrsubnets": funcs.CidrSubnetsFunc,
114+
"coalesce": funcs.CoalesceFunc,
115+
"coalescelist": stdlib.CoalesceListFunc,
116+
"compact": stdlib.CompactFunc,
117+
"concat": stdlib.ConcatFunc,
118+
"contains": stdlib.ContainsFunc,
119+
"csvdecode": stdlib.CSVDecodeFunc,
120+
"dirname": funcs.DirnameFunc,
121+
"distinct": stdlib.DistinctFunc,
122+
"element": stdlib.ElementFunc,
123+
"endswith": funcs.EndsWithFunc,
124+
"chunklist": stdlib.ChunklistFunc,
125+
"file": funcs.MakeFileFunc(baseDir, false),
126+
"fileexists": funcs.MakeFileExistsFunc(baseDir),
127+
"fileset": funcs.MakeFileSetFunc(baseDir),
128+
"filebase64": funcs.MakeFileFunc(baseDir, true),
129+
"filebase64sha256": funcs.MakeFileBase64Sha256Func(baseDir),
130+
"filebase64sha512": funcs.MakeFileBase64Sha512Func(baseDir),
131+
"filemd5": funcs.MakeFileMd5Func(baseDir),
132+
"filesha1": funcs.MakeFileSha1Func(baseDir),
133+
"filesha256": funcs.MakeFileSha256Func(baseDir),
134+
"filesha512": funcs.MakeFileSha512Func(baseDir),
135+
"flatten": stdlib.FlattenFunc,
136+
"floor": stdlib.FloorFunc,
137+
"format": stdlib.FormatFunc,
138+
"formatdate": stdlib.FormatDateFunc,
139+
"formatlist": stdlib.FormatListFunc,
140+
"indent": stdlib.IndentFunc,
141+
"index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible
142+
"join": stdlib.JoinFunc,
143+
"jsondecode": stdlib.JSONDecodeFunc,
144+
"jsonencode": stdlib.JSONEncodeFunc,
145+
"keys": stdlib.KeysFunc,
146+
"length": funcs.LengthFunc,
147+
"list": funcs.ListFunc,
148+
"log": stdlib.LogFunc,
149+
"lookup": funcs.LookupFunc,
150+
"lower": stdlib.LowerFunc,
151+
"map": funcs.MapFunc,
152+
"matchkeys": funcs.MatchkeysFunc,
153+
"max": stdlib.MaxFunc,
154+
"md5": funcs.Md5Func,
155+
"merge": stdlib.MergeFunc,
156+
"min": stdlib.MinFunc,
157+
"one": funcs.OneFunc,
158+
"parseint": stdlib.ParseIntFunc,
159+
"pathexpand": funcs.PathExpandFunc,
160+
"pow": stdlib.PowFunc,
161+
"range": stdlib.RangeFunc,
162+
"regex": stdlib.RegexFunc,
163+
"regexall": stdlib.RegexAllFunc,
164+
"replace": funcs.ReplaceFunc,
165+
"reverse": stdlib.ReverseListFunc,
166+
"rsadecrypt": funcs.RsaDecryptFunc,
167+
"sensitive": funcs.SensitiveFunc,
168+
"nonsensitive": funcs.NonsensitiveFunc,
169+
"setintersection": stdlib.SetIntersectionFunc,
170+
"setproduct": stdlib.SetProductFunc,
171+
"setsubtract": stdlib.SetSubtractFunc,
172+
"setunion": stdlib.SetUnionFunc,
173+
"sha1": funcs.Sha1Func,
174+
"sha256": funcs.Sha256Func,
175+
"sha512": funcs.Sha512Func,
176+
"signum": stdlib.SignumFunc,
177+
"slice": stdlib.SliceFunc,
178+
"sort": stdlib.SortFunc,
179+
"split": stdlib.SplitFunc,
180+
"startswith": funcs.StartsWithFunc,
181+
"strcontains": funcs.StrContainsFunc,
182+
"strrev": stdlib.ReverseFunc,
183+
"substr": stdlib.SubstrFunc,
184+
"sum": funcs.SumFunc,
185+
"textdecodebase64": funcs.TextDecodeBase64Func,
186+
"textencodebase64": funcs.TextEncodeBase64Func,
187+
"timestamp": funcs.TimestampFunc,
188+
"timeadd": stdlib.TimeAddFunc,
189+
"timecmp": funcs.TimeCmpFunc,
190+
"title": stdlib.TitleFunc,
191+
"tostring": funcs.MakeToFunc(cty.String),
192+
"tonumber": funcs.MakeToFunc(cty.Number),
193+
"tobool": funcs.MakeToFunc(cty.Bool),
194+
"toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)),
195+
"tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)),
196+
"tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)),
197+
"transpose": funcs.TransposeFunc,
198+
"trim": stdlib.TrimFunc,
199+
"trimprefix": stdlib.TrimPrefixFunc,
200+
"trimspace": stdlib.TrimSpaceFunc,
201+
"trimsuffix": stdlib.TrimSuffixFunc,
202+
"try": tryfunc.TryFunc,
203+
"upper": stdlib.UpperFunc,
204+
"urlencode": funcs.URLEncodeFunc,
205+
"uuid": funcs.UUIDFunc,
206+
"uuidv5": funcs.UUIDV5Func,
207+
"values": stdlib.ValuesFunc,
208+
"yamldecode": ctyyaml.YAMLDecodeFunc,
209+
"yamlencode": ctyyaml.YAMLEncodeFunc,
210+
"zipmap": stdlib.ZipmapFunc,
211+
}
212+
213+
fs["templatefile"] = funcs.MakeTemplateFileFunc(baseDir, func() map[string]function.Function {
214+
// The templatefile function prevents recursive calls to itself
215+
// by copying this map and overwriting the "templatefile" entry.
216+
return fs
217+
})
218+
219+
return fs
220+
}
221+
193222
// experimentalFunction checks whether the given experiment is enabled for
194223
// the recieving scope. If so, it will return the given function verbatim.
195224
// If not, it will return a placeholder function that just returns an

internal/moduletest/hcl/context.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const (
4646
// expressions to be evaluated will pass evaluation. Anything present in the
4747
// expressions argument will be validated to make sure the only reference the
4848
// availableVariables and availableRunBlocks.
49+
//
50+
// We perform some pre-validation of the expected expressions that this context
51+
// will be used to evaluate. This is just so we can provide some better error
52+
// messages and diagnostics. The expressions argument could be empty without
53+
// affecting the returned context.
4954
func EvalContext(target EvalContextTarget, expressions []hcl.Expression, availableVariables map[string]cty.Value, availableRunBlocks map[string]*terraform.TestContext) (*hcl.EvalContext, tfdiags.Diagnostics) {
5055
var diags tfdiags.Diagnostics
5156

@@ -227,5 +232,6 @@ func EvalContext(target EvalContextTarget, expressions []hcl.Expression, availab
227232
}
228233
return variables
229234
}(),
235+
Functions: lang.TestingFunctions(),
230236
}, diags
231237
}

0 commit comments

Comments
 (0)