terraform test: connect mock behaviour to test framework (#34205)

* connect mock behaviour to test framework

* fix calling unconfigured providers
pull/34192/head
Liam Cervante 3 years ago committed by GitHub
parent aec48aa8d2
commit dedb296773
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,6 +24,7 @@ import (
"github.com/hashicorp/terraform/internal/moduletest"
configtest "github.com/hashicorp/terraform/internal/moduletest/config"
hcltest "github.com/hashicorp/terraform/internal/moduletest/hcl"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
@ -605,6 +606,7 @@ func (runner *TestFileRunner) destroy(config *configs.Config, state *states.Stat
planOpts := &terraform.PlanOpts{
Mode: plans.DestroyMode,
SetVariables: setVariables,
Overrides: mocking.PackageOverrides(run.Config, file.Config, config),
}
tfCtx, ctxDiags := terraform.NewContext(runner.Suite.Opts)
@ -675,6 +677,7 @@ func (runner *TestFileRunner) plan(config *configs.Config, state *states.State,
SkipRefresh: !run.Config.Options.Refresh,
SetVariables: variables,
ExternalReferences: references,
Overrides: mocking.PackageOverrides(run.Config, file.Config, config),
}
tfCtx, ctxDiags := terraform.NewContext(runner.Suite.Opts)

@ -193,6 +193,10 @@ func TestTest(t *testing.T) {
expected: "1 passed, 0 failed.",
code: 0,
},
"mocking": {
expected: "5 passed, 0 failed.",
code: 0,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {

@ -0,0 +1,30 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
configuration_aliases = [test.primary, test.secondary]
}
}
}
variable "instances" {
type = number
}
resource "test_resource" "primary" {
provider = test.primary
count = var.instances
}
resource "test_resource" "secondary" {
provider = test.secondary
count = var.instances
}
output "primary" {
value = test_resource.primary
}
output "secondary" {
value = test_resource.secondary
}

@ -0,0 +1,46 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
provider "test" {
alias = "primary"
}
provider "test" {
alias = "secondary"
}
variable "instances" {
type = number
}
variable "child_instances" {
type = number
}
resource "test_resource" "primary" {
provider = test.primary
count = var.instances
}
resource "test_resource" "secondary" {
provider = test.secondary
count = var.instances
}
module "child" {
count = var.instances
source = "./child"
providers = {
test.primary = test.primary
test.secondary = test.secondary
}
instances = var.child_instances
}

@ -0,0 +1,44 @@
override_module {
target = module.child[1]
outputs = {
primary = [
{
id = "bbbb"
}
]
secondary = [
{
id = "cccc"
}
]
}
}
variables {
instances = 3
child_instances = 1
}
run "test" {
assert {
condition = module.child[0].primary[0].id != "bbbb"
error_message = "wrongly applied mocks"
}
assert {
condition = module.child[1].primary[0].id == "bbbb"
error_message = "did not apply mocks"
}
assert {
condition = module.child[1].secondary[0].id == "cccc"
error_message = "did not apply mocks"
}
assert {
condition = module.child[2].secondary[0].id != "cccc"
error_message = "wrongly applied mocks"
}
}

@ -0,0 +1,70 @@
override_module {
target = module.child
outputs = {
primary = [
{
id = "bbbb"
}
]
secondary = [
{
id = "cccc"
}
]
}
}
variables {
instances = 3
child_instances = 1
}
run "test" {
override_module {
target = module.child[1]
outputs = {
primary = [
{
id = "aaaa"
}
]
secondary = [
{
id = "dddd"
}
]
}
}
assert {
condition = module.child[0].primary[0].id == "bbbb"
error_message = "wrongly applied mocks"
}
assert {
condition = module.child[0].secondary[0].id == "cccc"
error_message = "did not apply mocks"
}
assert {
condition = module.child[2].primary[0].id == "bbbb"
error_message = "wrongly applied mocks"
}
assert {
condition = module.child[2].secondary[0].id == "cccc"
error_message = "did not apply mocks"
}
assert {
condition = module.child[1].primary[0].id == "aaaa"
error_message = "did not apply mocks"
}
assert {
condition = module.child[1].secondary[0].id == "dddd"
error_message = "did not apply mocks"
}
}

@ -0,0 +1,7 @@
variables {
instances = 1
child_instances = 0
}
run "test" {}

@ -0,0 +1,39 @@
mock_provider "test" {
alias = "primary"
mock_resource "test_resource" {
defaults = {
id = "aaaa"
}
}
}
variables {
instances = 1
child_instances = 1
}
run "test" {
assert {
condition = test_resource.primary[0].id == "aaaa"
error_message = "did not apply mocks"
}
assert {
condition = module.child[0].primary[0].id == "aaaa"
error_message = "did not apply mocks"
}
assert {
condition = test_resource.secondary[0].id != "aaaa"
error_message = "wrongly applied mocks"
}
assert {
condition = module.child[0].secondary[0].id != "aaaa"
error_message = "wrongly applied mocks"
}
}

@ -0,0 +1,52 @@
mock_provider "test" {
alias = "primary"
mock_resource "test_resource" {
defaults = {
id = "aaaa"
}
}
override_resource {
target = test_resource.primary
values = {
id = "bbbb"
}
}
}
variables {
instances = 3
child_instances = 1
}
run "test" {
override_resource {
target = test_resource.primary[1]
values = {
id = "cccc"
}
}
assert {
condition = test_resource.primary[0].id == "bbbb"
error_message = "did not apply mocks"
}
assert {
condition = test_resource.primary[1].id == "cccc"
error_message = "did not apply mocks"
}
assert {
condition = test_resource.primary[2].id == "bbbb"
error_message = "did not apply mocks"
}
assert {
condition = module.child[0].primary[0].id == "aaaa"
error_message = "did not apply mocks"
}
}

@ -67,9 +67,7 @@ func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *m
if len(run.Config.Providers) > 0 {
// Then we'll only copy over and overwrite the specific providers asked
// for by this run block.
for _, ref := range run.Config.Providers {
testProvider, ok := file.Config.Providers[ref.InParent.String()]
if !ok {
// Then this reference was invalid as we didn't have the
@ -96,9 +94,10 @@ func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *m
AvailableVariables: availableVariables,
AvailableRunBlocks: availableRunBlocks,
},
Mock: testProvider.Mock,
MockData: testProvider.MockData,
DeclRange: testProvider.DeclRange,
}
}
} else {
// Otherwise, let's copy over and overwrite all providers specified by
@ -123,6 +122,8 @@ func TransformConfigForTest(config *configs.Config, run *moduletest.Run, file *m
AvailableVariables: availableVariables,
AvailableRunBlocks: availableRunBlocks,
},
Mock: provider.Mock,
MockData: provider.MockData,
DeclRange: provider.DeclRange,
}
}

@ -7,8 +7,10 @@ import (
"fmt"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
"github.com/hashicorp/terraform/internal/moduletest/mocking"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -54,27 +56,66 @@ func (m *Mock) ValidateProviderConfig(request ValidateProviderConfigRequest) (re
func (m *Mock) ValidateResourceConfig(request ValidateResourceConfigRequest) ValidateResourceConfigResponse {
// We'll just pass this through to the underlying provider. The mock should
// support the same resource syntax as the original provider.
// support the same resource syntax as the original provider and we can call
// validate without needing to configure the provider first.
return m.Provider.ValidateResourceConfig(request)
}
func (m *Mock) ValidateDataResourceConfig(request ValidateDataResourceConfigRequest) ValidateDataResourceConfigResponse {
// We'll just pass this through to the underlying provider. The mock should
// support the same data source syntax as the original provider.
// support the same data source syntax as the original provider and we can
// call validate without needing to configure the provider first.
return m.Provider.ValidateDataResourceConfig(request)
}
func (m *Mock) UpgradeResourceState(request UpgradeResourceStateRequest) UpgradeResourceStateResponse {
// It's unlikely this will ever be called on a mocked provider, given they
// can only execute from inside tests. But we don't need to anything special
// here, let's just have the original provider handle it.
return m.Provider.UpgradeResourceState(request)
func (m *Mock) UpgradeResourceState(request UpgradeResourceStateRequest) (response UpgradeResourceStateResponse) {
// We can't do this from a mocked provider, so we just return whatever state
// is in the request back unchanged.
schema := m.GetProviderSchema()
response.Diagnostics = response.Diagnostics.Append(schema.Diagnostics)
if schema.Diagnostics.HasErrors() {
// We couldn't retrieve the schema for some reason, so the mock
// provider can't really function.
return response
}
resource, exists := schema.ResourceTypes[request.TypeName]
if !exists {
// This means something has gone wrong much earlier, we should have
// failed a validation somewhere if a resource type doesn't exist.
panic(fmt.Errorf("failed to retrieve schema for resource %s", request.TypeName))
}
schemaType := resource.Block.ImpliedType()
var value cty.Value
var err error
switch {
case request.RawStateFlatmap != nil:
value, err = hcl2shim.HCL2ValueFromFlatmap(request.RawStateFlatmap, schemaType)
case len(request.RawStateJSON) > 0:
value, err = ctyjson.Unmarshal(request.RawStateJSON, schemaType)
}
if err != nil {
// Generally, we shouldn't get an error here. The mocked providers are
// only used in tests, and we can't use different versions of providers
// within/between tests so the types should always match up. As such,
// we're not gonna return a super detailed error here.
response.Diagnostics = response.Diagnostics.Append(err)
return response
}
response.UpgradedState = value
return response
}
func (m *Mock) ConfigureProvider(request ConfigureProviderRequest) (response ConfigureProviderResponse) {
// Do nothing here, we don't have anything to configure within the mocked
// providers and we don't want to call the original providers from here as
// they may try to talk to their underlying cloud providers.
// providers. We don't want to call the original providers from here as
// they may try to talk to their underlying cloud providers and we
// definitely don't have the right configuration or credentials for this.
return response
}

@ -200,8 +200,23 @@ func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdi
}
}
// we don't ever remove a module that's been overridden - it will
// have outputs that have been set by the user and wouldn't be
// removed during normal operations as the module would have created
// resources. Overrides are only set during tests, and stop the
// module creating resources but we still care about the outputs.
overridden := false
if overrides := ctx.Overrides(); !overrides.Empty() {
_, overridden = overrides.GetOverride(mod.Addr)
if !overridden && len(mod.Addr) > 0 && mod.Addr[len(mod.Addr)-1].InstanceKey != addrs.NoKey {
// Could be all module instances are overridden.
_, overridden = overrides.GetOverride(mod.Addr.ContainingModule())
}
}
// empty child modules are always removed
if len(mod.Resources) == 0 && !mod.Addr.IsRoot() {
if len(mod.Resources) == 0 && !mod.Addr.IsRoot() && !overridden {
delete(state.Modules, modKey)
}
}

Loading…
Cancel
Save