// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package lang import ( "fmt" "testing" "github.com/hashicorp/terraform/internal/addrs" "github.com/zclconf/go-cty/cty" // set the correct global logger for tests _ "github.com/hashicorp/terraform/internal/logging" ) func TestFunctionCache(t *testing.T) { testAddr := addrs.NewDefaultProvider("test") type testCall struct { provider addrs.Provider name string args []cty.Value result cty.Value } tests := []struct { first, second testCall expectErr bool }{ { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.False, }, // result changed from true => false expectErr: true, }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.UnknownVal(cty.Bool), }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.False, }, // result changed from unknown => false expectErr: false, }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, second: testCall{ provider: addrs.NewDefaultProvider("fake"), name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.False, }, // OK because provider changed }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, second: testCall{ provider: testAddr, name: "func", args: []cty.Value{cty.StringVal("ok")}, result: cty.False, }, // OK because function name changed }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok")}, result: cty.True, }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.StringVal("ok"), cty.StringVal("ok")}, result: cty.False, }, // OK because args changed }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.ObjectVal(map[string]cty.Value{ "attr": cty.NumberIntVal(1), })}, result: cty.True, }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.ObjectVal(map[string]cty.Value{ "attr": cty.NumberIntVal(2), })}, result: cty.False, }, // OK because args changed }, { first: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.UnknownVal(cty.Object(map[string]cty.Type{ "attr": cty.Number, }))}, result: cty.UnknownVal(cty.Bool), }, second: testCall{ provider: testAddr, name: "fun", args: []cty.Value{cty.ObjectVal(map[string]cty.Value{ "attr": cty.NumberIntVal(2), })}, result: cty.False, }, // OK because args changed from unknown to known }, } for i, test := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { results := NewFunctionResultsTable(nil) err := results.CheckPriorProvider(test.first.provider, test.first.name, test.first.args, test.first.result) if err != nil { t.Fatal("error on first call!", err) } err = results.CheckPriorProvider(test.second.provider, test.second.name, test.second.args, test.second.result) if err != nil && !test.expectErr { t.Fatal(err) } if err == nil && test.expectErr { t.Fatal("expected error") } // reload the data to ensure we validate identically newResults := NewFunctionResultsTable(results.GetHashes()) originalErr := err != nil reloadedErr := newResults.CheckPriorProvider(test.second.provider, test.second.name, test.second.args, test.second.result) != nil if originalErr != reloadedErr { t.Fatalf("original check returned err:%t, reloaded check returned err:%t", originalErr, reloadedErr) } }) } }