github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_eval_test.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "testing" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/providers" 15 "github.com/opentofu/opentofu/internal/states" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 func TestContextEval(t *testing.T) { 20 // This test doesn't check the "Want" value for impure funcs, so the value 21 // on those doesn't matter. 22 tests := []struct { 23 Input string 24 Want cty.Value 25 ImpureFunc bool 26 }{ 27 { // An impure function: allowed in the console, but the result is nondeterministic 28 `bcrypt("example")`, 29 cty.NilVal, 30 true, 31 }, 32 { 33 `keys(var.map)`, 34 cty.ListVal([]cty.Value{ 35 cty.StringVal("foo"), 36 cty.StringVal("baz"), 37 }), 38 true, 39 }, 40 { 41 `local.result`, 42 cty.NumberIntVal(6), 43 false, 44 }, 45 { 46 `module.child.result`, 47 cty.NumberIntVal(6), 48 false, 49 }, 50 } 51 52 // This module has a little bit of everything (and if it is missing somehitng, add to it): 53 // resources, variables, locals, modules, output 54 m := testModule(t, "eval-context-basic") 55 p := testProvider("test") 56 ctx := testContext2(t, &ContextOpts{ 57 Providers: map[addrs.Provider]providers.Factory{ 58 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 59 }, 60 }) 61 62 scope, diags := ctx.Eval(m, states.NewState(), addrs.RootModuleInstance, &EvalOpts{ 63 SetVariables: testInputValuesUnset(m.Module.Variables), 64 }) 65 if diags.HasErrors() { 66 t.Fatalf("Eval errors: %s", diags.Err()) 67 } 68 69 // Since we're testing 'eval' (used by tofu console), impure functions 70 // should be allowed by the scope. 71 if scope.PureOnly == true { 72 t.Fatal("wrong result: eval should allow impure funcs") 73 } 74 75 for _, test := range tests { 76 t.Run(test.Input, func(t *testing.T) { 77 // Parse the test input as an expression 78 expr, _ := hclsyntax.ParseExpression([]byte(test.Input), "<test-input>", hcl.Pos{Line: 1, Column: 1}) 79 got, diags := scope.EvalExpr(expr, cty.DynamicPseudoType) 80 81 if diags.HasErrors() { 82 t.Fatalf("unexpected error: %s", diags.Err()) 83 } 84 85 if !test.ImpureFunc { 86 if !got.RawEquals(test.Want) { 87 t.Fatalf("wrong result: want %#v, got %#v", test.Want, got) 88 } 89 } 90 }) 91 } 92 } 93 94 // ensure that we can execute a console when outputs have preconditions 95 func TestContextEval_outputsWithPreconditions(t *testing.T) { 96 m := testModuleInline(t, map[string]string{ 97 "main.tf": ` 98 module "mod" { 99 source = "./mod" 100 input = "ok" 101 } 102 103 output "out" { 104 value = module.mod.out 105 } 106 `, 107 108 "./mod/main.tf": ` 109 variable "input" { 110 type = string 111 } 112 113 output "out" { 114 value = var.input 115 116 precondition { 117 condition = var.input != "" 118 error_message = "error" 119 } 120 } 121 `, 122 }) 123 124 p := simpleMockProvider() 125 ctx := testContext2(t, &ContextOpts{ 126 Providers: map[addrs.Provider]providers.Factory{ 127 addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), 128 }, 129 }) 130 131 _, diags := ctx.Eval(m, states.NewState(), addrs.RootModuleInstance, &EvalOpts{ 132 SetVariables: testInputValuesUnset(m.Module.Variables), 133 }) 134 assertNoErrors(t, diags) 135 }