mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
[testing framework] prepare for beta phase of development (#33445)
This commit is contained in:
parent
3613e972c5
commit
ea162f6ab5
@ -63,7 +63,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
loader, err := c.initConfigLoader()
|
||||
diags = diags.Append(err)
|
||||
if err != nil {
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return 1
|
||||
}
|
||||
c.loader = loader
|
||||
@ -71,7 +71,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
|
||||
config, configDiags := loader.LoadConfigWithTests(".", "tests")
|
||||
diags = diags.Append(configDiags)
|
||||
if configDiags.HasErrors() {
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ func (c *TestCommand) ExecuteTestSuite(suite *moduletest.Suite, config *configs.
|
||||
diags = diags.Append(err)
|
||||
if err != nil {
|
||||
suite.Status = suite.Status.Merge(moduletest.Error)
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return
|
||||
}
|
||||
|
||||
@ -121,10 +121,10 @@ func (c *TestCommand) ExecuteTestSuite(suite *moduletest.Suite, config *configs.
|
||||
diags = diags.Append(ctxDiags)
|
||||
if ctxDiags.HasErrors() {
|
||||
suite.Status = suite.Status.Merge(moduletest.Error)
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, nil, diags)
|
||||
return
|
||||
}
|
||||
c.View.Diagnostics(diags) // Print out any warnings from the setup.
|
||||
view.Diagnostics(nil, nil, diags) // Print out any warnings from the setup.
|
||||
|
||||
var files []string
|
||||
for name := range suite.Files {
|
||||
@ -148,7 +148,7 @@ func (c *TestCommand) ExecuteTestFile(ctx *terraform.Context, file *moduletest.F
|
||||
if diags.HasErrors() {
|
||||
file.Status = file.Status.Merge(moduletest.Error)
|
||||
view.File(file)
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, file, diags)
|
||||
return
|
||||
}
|
||||
|
||||
@ -166,48 +166,13 @@ func (c *TestCommand) ExecuteTestFile(ctx *terraform.Context, file *moduletest.F
|
||||
if planDiags.HasErrors() {
|
||||
// This is bad, we need to tell the user that we couldn't clean up
|
||||
// and they need to go and manually delete some resources.
|
||||
|
||||
c.Streams.Eprintf("Terraform encountered an error destroying resources created during the test.\n\n")
|
||||
c.View.Diagnostics(planDiags)
|
||||
|
||||
if state.HasManagedResourceInstanceObjects() {
|
||||
c.Streams.Eprintf("Terraform left the following resources in state, they need to be cleaned up manually:\n\n")
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
if resource.DeposedKey != states.NotDeposed {
|
||||
c.Streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
|
||||
continue
|
||||
}
|
||||
c.Streams.Eprintf(" - %s\n", resource.Instance)
|
||||
}
|
||||
}
|
||||
|
||||
view.DestroySummary(planDiags, file, state)
|
||||
return
|
||||
}
|
||||
c.View.Diagnostics(planDiags) // Print out any warnings from the destroy plan.
|
||||
view.Diagnostics(nil, file, planDiags) // Print out any warnings from the destroy plan.
|
||||
|
||||
finalState, applyDiags := ctx.Apply(plan, config)
|
||||
if applyDiags.HasErrors() {
|
||||
// This is bad, we need to tell the user that we couldn't clean up
|
||||
// and they need to go and manually delete some resources.
|
||||
|
||||
c.Streams.Eprintf("Terraform encountered an error destroying resources created during the test.\n\n")
|
||||
}
|
||||
c.View.Diagnostics(applyDiags) // Print out any warnings from the destroy apply.
|
||||
|
||||
if finalState.HasManagedResourceInstanceObjects() {
|
||||
// Then we need to print dialog telling the user they need to clean
|
||||
// things up, and we should mark the overall test as errored.
|
||||
|
||||
c.Streams.Eprintf("Terraform left the following resources in state, they need to be cleaned up manually:\n\n")
|
||||
for _, resource := range state.AllResourceInstanceObjectAddrs() {
|
||||
if resource.DeposedKey != states.NotDeposed {
|
||||
c.Streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
|
||||
continue
|
||||
}
|
||||
c.Streams.Eprintf(" - %s\n", resource.Instance)
|
||||
}
|
||||
|
||||
}
|
||||
view.DestroySummary(applyDiags, file, finalState)
|
||||
}()
|
||||
|
||||
file.Status = file.Status.Merge(moduletest.Pass)
|
||||
@ -222,7 +187,7 @@ func (c *TestCommand) ExecuteTestFile(ctx *terraform.Context, file *moduletest.F
|
||||
}
|
||||
|
||||
view.File(file)
|
||||
c.View.Diagnostics(diags)
|
||||
view.Diagnostics(nil, file, diags)
|
||||
|
||||
for _, run := range file.Runs {
|
||||
view.Run(run, file)
|
||||
|
@ -4,6 +4,15 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
testing_command "github.com/hashicorp/terraform/internal/command/testing"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
)
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
@ -11,6 +20,7 @@ func TestTest(t *testing.T) {
|
||||
args []string
|
||||
expected string
|
||||
code int
|
||||
skip bool
|
||||
}{
|
||||
"simple_pass": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
@ -24,6 +34,10 @@ func TestTest(t *testing.T) {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"pass_with_outputs": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"pass_with_variables": {
|
||||
expected: "2 passed, 0 failed.",
|
||||
code: 0,
|
||||
@ -32,23 +46,75 @@ func TestTest(t *testing.T) {
|
||||
expected: "2 passed, 0 failed.",
|
||||
code: 0,
|
||||
},
|
||||
"expect_failures_checks": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
// TODO(liamcervante): Enable this when support for expect_failures
|
||||
// has been added.
|
||||
skip: true,
|
||||
},
|
||||
"expect_failures_inputs": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
// TODO(liamcervante): Enable this when support for expect_failures
|
||||
// has been added.
|
||||
skip: true,
|
||||
},
|
||||
"expect_failures_outputs": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
// TODO(liamcervante): Enable this when support for expect_failures
|
||||
// has been added.
|
||||
skip: true,
|
||||
},
|
||||
"expect_failures_resources": {
|
||||
expected: "1 passed, 0 failed.",
|
||||
code: 0,
|
||||
// TODO(liamcervante): Enable this when support for expect_failures
|
||||
// has been added.
|
||||
skip: true,
|
||||
},
|
||||
"simple_fail": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
},
|
||||
"custom_condition_checks": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
// TODO(liamcervante): Enable this, at the moment checks aren't
|
||||
// causing the tests to fail when they should. Also, it's not
|
||||
// skipping warnings during the plan when it should.
|
||||
skip: true,
|
||||
},
|
||||
"custom_condition_inputs": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
},
|
||||
"custom_condition_outputs": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
},
|
||||
"custom_condition_resources": {
|
||||
expected: "0 passed, 1 failed.",
|
||||
code: 1,
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", name)), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := planFixtureProvider()
|
||||
provider := testing_command.NewProvider(nil)
|
||||
view, done := testView(t)
|
||||
|
||||
c := &TestCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
testingOverrides: metaOverridesForProvider(provider.Provider),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
@ -63,6 +129,158 @@ func TestTest(t *testing.T) {
|
||||
if !strings.Contains(output.Stdout(), tc.expected) {
|
||||
t.Errorf("output didn't contain expected string:\n\n%s", output.All())
|
||||
}
|
||||
|
||||
if provider.ResourceCount() > 0 {
|
||||
t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_ProviderAlias(t *testing.T) {
|
||||
// TODO(liamcervante): Enable this test once we have added support for
|
||||
// provider aliasing and customisation into the testing framework.
|
||||
t.Skip()
|
||||
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", "with_provider_alias")), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
|
||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||
"test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
ui := new(cli.MockUi)
|
||||
|
||||
meta := Meta{
|
||||
testingOverrides: metaOverridesForProvider(provider.Provider),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Streams: streams,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
init := &InitCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
if code := init.Run(nil); code != 0 {
|
||||
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
command := &TestCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
code := command.Run(nil)
|
||||
output := done(t)
|
||||
|
||||
printedOutput := false
|
||||
|
||||
if code != 0 {
|
||||
printedOutput = true
|
||||
t.Errorf("expected status code 0 but got %d: %s", code, output.All())
|
||||
}
|
||||
|
||||
if provider.ResourceCount() > 0 {
|
||||
if !printedOutput {
|
||||
t.Errorf("should have deleted all resources on completion but left %s\n\n%s", provider.ResourceString(), output.All())
|
||||
} else {
|
||||
t.Errorf("should have deleted all resources on completion but left %s", provider.ResourceString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest_ModuleDependencies(t *testing.T) {
|
||||
// TODO(liamcervante): Enable this test once we have added support for
|
||||
// module customisation into the testing framework.
|
||||
t.Skip()
|
||||
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", "with_setup_module")), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Our two providers will share a common set of values to make things
|
||||
// easier.
|
||||
store := &testing_command.ResourceStore{
|
||||
Data: make(map[string]cty.Value),
|
||||
}
|
||||
|
||||
// We set it up so the module provider will update the data sources
|
||||
// available to the core mock provider.
|
||||
test := testing_command.NewProvider(store)
|
||||
setup := testing_command.NewProvider(store)
|
||||
|
||||
test.SetDataPrefix("data")
|
||||
test.SetResourcePrefix("resource")
|
||||
|
||||
// Let's make the setup provider write into the data for test provider.
|
||||
setup.SetResourcePrefix("data")
|
||||
|
||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||
"test": {"1.0.0"},
|
||||
"setup": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
ui := new(cli.MockUi)
|
||||
|
||||
meta := Meta{
|
||||
testingOverrides: &testingOverrides{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): providers.FactoryFixed(test.Provider),
|
||||
addrs.NewDefaultProvider("setup"): providers.FactoryFixed(setup.Provider),
|
||||
},
|
||||
},
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Streams: streams,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
init := &InitCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
if code := init.Run(nil); code != 0 {
|
||||
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
command := &TestCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
code := command.Run(nil)
|
||||
output := done(t)
|
||||
|
||||
printedOutput := false
|
||||
|
||||
if code != 0 {
|
||||
printedOutput = true
|
||||
t.Errorf("expected status code 0 but got %d: %s", code, output.All())
|
||||
}
|
||||
|
||||
if test.ResourceCount() > 0 {
|
||||
if !printedOutput {
|
||||
printedOutput = true
|
||||
t.Errorf("should have deleted all resources on completion but left %s\n\n%s", test.ResourceString(), output.All())
|
||||
} else {
|
||||
t.Errorf("should have deleted all resources on completion but left %s", test.ResourceString())
|
||||
}
|
||||
}
|
||||
|
||||
if setup.ResourceCount() > 0 {
|
||||
if !printedOutput {
|
||||
t.Errorf("should have deleted all resources on completion but left %s\n\n%s", setup.ResourceString(), output.All())
|
||||
} else {
|
||||
t.Errorf("should have deleted all resources on completion but left %s", setup.ResourceString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
internal/command/testdata/test/custom_condition_checks/main.tf
vendored
Normal file
15
internal/command/testdata/test/custom_condition_checks/main.tf
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
}
|
||||
|
||||
check "expected_to_fail" {
|
||||
assert {
|
||||
condition = test_resource.resource.value != var.input
|
||||
error_message = "this really should fail"
|
||||
}
|
||||
}
|
5
internal/command/testdata/test/custom_condition_checks/main.tftest
vendored
Normal file
5
internal/command/testdata/test/custom_condition_checks/main.tftest
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {}
|
13
internal/command/testdata/test/custom_condition_inputs/main.tf
vendored
Normal file
13
internal/command/testdata/test/custom_condition_inputs/main.tf
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = var.input == "something very specific"
|
||||
error_message = "this should definitely fail"
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
}
|
5
internal/command/testdata/test/custom_condition_inputs/main.tftest
vendored
Normal file
5
internal/command/testdata/test/custom_condition_inputs/main.tftest
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {}
|
13
internal/command/testdata/test/custom_condition_outputs/main.tf
vendored
Normal file
13
internal/command/testdata/test/custom_condition_outputs/main.tf
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
output "output" {
|
||||
value = var.input
|
||||
|
||||
precondition {
|
||||
condition = var.input == "something incredibly specific"
|
||||
error_message = "this should fail"
|
||||
}
|
||||
}
|
5
internal/command/testdata/test/custom_condition_outputs/main.tftest
vendored
Normal file
5
internal/command/testdata/test/custom_condition_outputs/main.tftest
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {}
|
15
internal/command/testdata/test/custom_condition_resources/main.tf
vendored
Normal file
15
internal/command/testdata/test/custom_condition_resources/main.tf
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
|
||||
lifecycle {
|
||||
postcondition {
|
||||
condition = self.value != var.input
|
||||
error_message = "this really should fail"
|
||||
}
|
||||
}
|
||||
}
|
5
internal/command/testdata/test/custom_condition_resources/main.tftest
vendored
Normal file
5
internal/command/testdata/test/custom_condition_resources/main.tftest
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {}
|
15
internal/command/testdata/test/expect_failures_checks/main.tf
vendored
Normal file
15
internal/command/testdata/test/expect_failures_checks/main.tf
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
}
|
||||
|
||||
check "expected_to_fail" {
|
||||
assert {
|
||||
condition = test_resource.resource.value != var.input
|
||||
error_message = "this really should fail"
|
||||
}
|
||||
}
|
9
internal/command/testdata/test/expect_failures_checks/main.tftest
vendored
Normal file
9
internal/command/testdata/test/expect_failures_checks/main.tftest
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {
|
||||
expect_failures = [
|
||||
check.expected_to_fail
|
||||
]
|
||||
}
|
13
internal/command/testdata/test/expect_failures_inputs/main.tf
vendored
Normal file
13
internal/command/testdata/test/expect_failures_inputs/main.tf
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = var.input == "something very specific"
|
||||
error_message = "this should definitely fail"
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
}
|
9
internal/command/testdata/test/expect_failures_inputs/main.tftest
vendored
Normal file
9
internal/command/testdata/test/expect_failures_inputs/main.tftest
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {
|
||||
expect_failures = [
|
||||
var.input
|
||||
]
|
||||
}
|
13
internal/command/testdata/test/expect_failures_outputs/main.tf
vendored
Normal file
13
internal/command/testdata/test/expect_failures_outputs/main.tf
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
output "output" {
|
||||
value = var.input
|
||||
|
||||
precondition {
|
||||
condition = var.input == "something incredibly specific"
|
||||
error_message = "this should fail"
|
||||
}
|
||||
}
|
9
internal/command/testdata/test/expect_failures_outputs/main.tftest
vendored
Normal file
9
internal/command/testdata/test/expect_failures_outputs/main.tftest
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {
|
||||
expect_failures = [
|
||||
output.output
|
||||
]
|
||||
}
|
15
internal/command/testdata/test/expect_failures_resources/main.tf
vendored
Normal file
15
internal/command/testdata/test/expect_failures_resources/main.tf
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "resource" {
|
||||
value = var.input
|
||||
|
||||
lifecycle {
|
||||
postcondition {
|
||||
condition = self.value != var.input
|
||||
error_message = "this really should fail"
|
||||
}
|
||||
}
|
||||
}
|
15
internal/command/testdata/test/expect_failures_resources/main.tftest
vendored
Normal file
15
internal/command/testdata/test/expect_failures_resources/main.tftest
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
variables {
|
||||
input = "some value"
|
||||
}
|
||||
|
||||
run "test" {
|
||||
|
||||
assert {
|
||||
condition = test_resource.resource.value == "some value"
|
||||
error_message = "since we used a postcondition, it should still have actually created the resource"
|
||||
}
|
||||
|
||||
expect_failures = [
|
||||
test_resource.resource
|
||||
]
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
||||
locals {
|
||||
value = test_instance.foo.ami
|
||||
value = test_resource.foo.value
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = local.value == "bar"
|
||||
error_message = "invalid ami value"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
7
internal/command/testdata/test/pass_with_outputs/main.tf
vendored
Normal file
7
internal/command/testdata/test/pass_with_outputs/main.tf
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
||||
output "value" {
|
||||
value = test_resource.foo.value
|
||||
}
|
6
internal/command/testdata/test/pass_with_outputs/main.tftest
vendored
Normal file
6
internal/command/testdata/test/pass_with_outputs/main.tftest
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = output.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
@ -2,6 +2,6 @@ variable "input" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_instance" "foo" {
|
||||
ami = var.input
|
||||
resource "test_resource" "foo" {
|
||||
value = var.input
|
||||
}
|
||||
|
@ -2,20 +2,20 @@ variables {
|
||||
input = "bar"
|
||||
}
|
||||
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "bar"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
variables {
|
||||
input = "zap"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "zap"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "zap"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
|
||||
command = plan
|
||||
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "bar"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "bar"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "zap"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "zap"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "bar"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
resource "test_resource" "foo" {
|
||||
value = "bar"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
run "validate_test_instance" {
|
||||
run "validate_test_resource" {
|
||||
assert {
|
||||
condition = test_instance.foo.ami == "bar"
|
||||
error_message = "invalid ami value"
|
||||
condition = test_resource.foo.value == "bar"
|
||||
error_message = "invalid value"
|
||||
}
|
||||
}
|
||||
|
12
internal/command/testdata/test/with_provider_alias/main.tf
vendored
Normal file
12
internal/command/testdata/test/with_provider_alias/main.tf
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
variable "managed_id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
data "test_data_source" "managed_data" {
|
||||
id = var.managed_id
|
||||
}
|
||||
|
||||
resource "test_resource" "created" {
|
||||
value = data.test_data_source.managed_data.value
|
||||
}
|
37
internal/command/testdata/test/with_provider_alias/main.tftest
vendored
Normal file
37
internal/command/testdata/test/with_provider_alias/main.tftest
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
provider "test" {
|
||||
data_prefix = "data"
|
||||
resource_prefix = "resource"
|
||||
}
|
||||
|
||||
provider "test" {
|
||||
alias = "setup"
|
||||
|
||||
# The setup provider will write into the main providers data sources.
|
||||
resource_prefix = "data"
|
||||
}
|
||||
|
||||
variables {
|
||||
managed_id = "B853C121"
|
||||
}
|
||||
|
||||
run "setup" {
|
||||
module {
|
||||
source = "./setup"
|
||||
}
|
||||
|
||||
variables {
|
||||
value = "Hello, world!"
|
||||
id = "B853C121"
|
||||
}
|
||||
|
||||
providers = {
|
||||
test = test.setup
|
||||
}
|
||||
}
|
||||
|
||||
run "test" {
|
||||
assert {
|
||||
condition = test_resource.created.value == "Hello, world!"
|
||||
error_message = "bad value"
|
||||
}
|
||||
}
|
12
internal/command/testdata/test/with_provider_alias/setup/main.tf
vendored
Normal file
12
internal/command/testdata/test/with_provider_alias/setup/main.tf
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
variable "value" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "managed" {
|
||||
id = var.id
|
||||
value = var.value
|
||||
}
|
12
internal/command/testdata/test/with_setup_module/main.tf
vendored
Normal file
12
internal/command/testdata/test/with_setup_module/main.tf
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
variable "managed_id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
data "test_data_source" "managed_data" {
|
||||
id = var.managed_id
|
||||
}
|
||||
|
||||
resource "test_resource" "created" {
|
||||
value = data.test_data_source.managed_data.value
|
||||
}
|
21
internal/command/testdata/test/with_setup_module/main.tftest
vendored
Normal file
21
internal/command/testdata/test/with_setup_module/main.tftest
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
variables {
|
||||
managed_id = "B853C121"
|
||||
}
|
||||
|
||||
run "setup" {
|
||||
module {
|
||||
source = "./setup"
|
||||
}
|
||||
|
||||
variables {
|
||||
value = "Hello, world!"
|
||||
id = "B853C121"
|
||||
}
|
||||
}
|
||||
|
||||
run "test" {
|
||||
assert {
|
||||
condition = test_resource.created.value == "Hello, world!"
|
||||
error_message = "bad value"
|
||||
}
|
||||
}
|
13
internal/command/testdata/test/with_setup_module/setup/main.tf
vendored
Normal file
13
internal/command/testdata/test/with_setup_module/setup/main.tf
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
variable "value" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "test_resource" "managed" {
|
||||
provider = setup
|
||||
id = var.id
|
||||
value = var.value
|
||||
}
|
271
internal/command/testing/test_provider.go
Normal file
271
internal/command/testing/test_provider.go
Normal file
@ -0,0 +1,271 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
var (
|
||||
ProviderSchema = &providers.GetProviderSchemaResponse{
|
||||
Provider: providers.Schema{
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"data_prefix": {Type: cty.String, Optional: true},
|
||||
"resource_prefix": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_resource": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||
"value": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_data_source": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Required: true},
|
||||
"value": {Type: cty.String, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// TestProvider is a wrapper around terraform.MockProvider that defines dynamic
|
||||
// schemas, and keeps track of the resources and data sources that it contains.
|
||||
type TestProvider struct {
|
||||
Provider *terraform.MockProvider
|
||||
|
||||
data, resource cty.Value
|
||||
|
||||
Store *ResourceStore
|
||||
}
|
||||
|
||||
func NewProvider(store *ResourceStore) *TestProvider {
|
||||
if store == nil {
|
||||
store = &ResourceStore{
|
||||
Data: make(map[string]cty.Value),
|
||||
}
|
||||
}
|
||||
|
||||
provider := &TestProvider{
|
||||
Provider: new(terraform.MockProvider),
|
||||
Store: store,
|
||||
}
|
||||
|
||||
provider.Provider.GetProviderSchemaResponse = ProviderSchema
|
||||
provider.Provider.ConfigureProviderFn = provider.ConfigureProvider
|
||||
provider.Provider.PlanResourceChangeFn = provider.PlanResourceChange
|
||||
provider.Provider.ApplyResourceChangeFn = provider.ApplyResourceChange
|
||||
provider.Provider.ReadResourceFn = provider.ReadResource
|
||||
provider.Provider.ReadDataSourceFn = provider.ReadDataSource
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func (provider *TestProvider) DataPrefix() string {
|
||||
var prefix string
|
||||
if !provider.data.IsNull() && provider.data.IsKnown() {
|
||||
prefix = provider.data.AsString()
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func (provider *TestProvider) SetDataPrefix(prefix string) {
|
||||
provider.data = cty.StringVal(prefix)
|
||||
}
|
||||
|
||||
func (provider *TestProvider) GetDataKey(id string) string {
|
||||
if !provider.data.IsNull() && provider.data.IsKnown() {
|
||||
return path.Join(provider.data.AsString(), id)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ResourcePrefix() string {
|
||||
var prefix string
|
||||
if !provider.resource.IsNull() && provider.resource.IsKnown() {
|
||||
prefix = provider.resource.AsString()
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func (provider *TestProvider) SetResourcePrefix(prefix string) {
|
||||
provider.resource = cty.StringVal(prefix)
|
||||
}
|
||||
|
||||
func (provider *TestProvider) GetResourceKey(id string) string {
|
||||
if !provider.resource.IsNull() && provider.resource.IsKnown() {
|
||||
return path.Join(provider.resource.AsString(), id)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ResourceString() string {
|
||||
return provider.string(provider.ResourcePrefix())
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ResourceCount() int {
|
||||
return provider.count(provider.ResourcePrefix())
|
||||
}
|
||||
|
||||
func (provider *TestProvider) DataSourceString() string {
|
||||
return provider.string(provider.DataPrefix())
|
||||
}
|
||||
|
||||
func (provider *TestProvider) DataSourceCount() int {
|
||||
return provider.count(provider.DataPrefix())
|
||||
}
|
||||
|
||||
func (provider *TestProvider) count(prefix string) int {
|
||||
if len(prefix) == 0 {
|
||||
return len(provider.Store.Data)
|
||||
}
|
||||
|
||||
count := 0
|
||||
for key := range provider.Store.Data {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (provider *TestProvider) string(prefix string) string {
|
||||
var keys []string
|
||||
for key := range provider.Store.Data {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return strings.Join(keys, ", ")
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ConfigureProvider(request providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
|
||||
provider.resource = request.Config.GetAttr("resource_prefix")
|
||||
provider.data = request.Config.GetAttr("data_prefix")
|
||||
return providers.ConfigureProviderResponse{}
|
||||
}
|
||||
|
||||
func (provider *TestProvider) PlanResourceChange(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
if request.ProposedNewState.IsNull() {
|
||||
// Then this is a delete operation.
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: request.ProposedNewState,
|
||||
}
|
||||
}
|
||||
|
||||
resource := request.ProposedNewState
|
||||
if id := resource.GetAttr("id"); !id.IsKnown() || id.IsNull() {
|
||||
vals := resource.AsValueMap()
|
||||
vals["id"] = cty.UnknownVal(cty.String)
|
||||
resource = cty.ObjectVal(vals)
|
||||
}
|
||||
|
||||
return providers.PlanResourceChangeResponse{
|
||||
PlannedState: resource,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ApplyResourceChange(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||
if request.PlannedState.IsNull() {
|
||||
// Then this is a delete operation.
|
||||
provider.Store.Delete(provider.GetResourceKey(request.PriorState.GetAttr("id").AsString()))
|
||||
return providers.ApplyResourceChangeResponse{
|
||||
NewState: request.PlannedState,
|
||||
}
|
||||
}
|
||||
|
||||
resource := request.PlannedState
|
||||
id := resource.GetAttr("id")
|
||||
if !id.IsKnown() {
|
||||
val, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to generate uuid: %v", err))
|
||||
}
|
||||
|
||||
id = cty.StringVal(val)
|
||||
|
||||
vals := resource.AsValueMap()
|
||||
vals["id"] = id
|
||||
resource = cty.ObjectVal(vals)
|
||||
}
|
||||
|
||||
provider.Store.Put(provider.GetResourceKey(id.AsString()), resource)
|
||||
return providers.ApplyResourceChangeResponse{
|
||||
NewState: resource,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ReadResource(request providers.ReadResourceRequest) providers.ReadResourceResponse {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
id := request.PriorState.GetAttr("id").AsString()
|
||||
resource := provider.Store.Get(provider.GetResourceKey(id))
|
||||
if resource == cty.NilVal {
|
||||
diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "not found", fmt.Sprintf("%s does not exist", id)))
|
||||
}
|
||||
|
||||
return providers.ReadResourceResponse{
|
||||
NewState: resource,
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *TestProvider) ReadDataSource(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
id := request.Config.GetAttr("id").AsString()
|
||||
resource := provider.Store.Get(provider.GetDataKey(id))
|
||||
if resource == cty.NilVal {
|
||||
diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "not found", fmt.Sprintf("%s does not exist", id)))
|
||||
}
|
||||
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: resource,
|
||||
Diagnostics: diags,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceStore manages a set of cty.Value resources that can be shared between
|
||||
// TestProvider providers.
|
||||
type ResourceStore struct {
|
||||
Data map[string]cty.Value
|
||||
}
|
||||
|
||||
func (store *ResourceStore) Delete(key string) cty.Value {
|
||||
if resource, ok := store.Data[key]; ok {
|
||||
delete(store.Data, key)
|
||||
return resource
|
||||
}
|
||||
return cty.NilVal
|
||||
}
|
||||
|
||||
func (store *ResourceStore) Get(key string) cty.Value {
|
||||
if resource, ok := store.Data[key]; ok {
|
||||
return resource
|
||||
}
|
||||
return cty.NilVal
|
||||
}
|
||||
|
||||
func (store *ResourceStore) Put(key string, resource cty.Value) cty.Value {
|
||||
old := store.Get(key)
|
||||
store.Data[key] = resource
|
||||
return old
|
||||
}
|
Loading…
Reference in New Issue
Block a user