mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-20 16:40:08 -05:00
The CheckPrior function results validation did not take into account unknown values, because unknown values were not originally allowed in the plugin protocol from which this check was adapted. The test is simple, in that we can just reverse the check which originally didn't allow this change in value. Provider function return values types are defined by the protocol, and should be validated at that call site. The CheckPrior code paths should only be used for the exact purpose of detecting invalid changes in the return value between successive phases of execution.
190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// 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)
|
|
}
|
|
})
|
|
}
|
|
}
|