mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add golden reference test for JSON plan (#31362)
* Add golden JSON test for Terraform plan * Add data source to golden JSON plan * Move output comparison code into shared helper function * Add note for maintainer to contact TFC when UI changes UI changes may potentially impact the behavior of structured run output on TFC. * Add test_data_source to other mock providers
This commit is contained in:
parent
5e000c2741
commit
f30738d965
@ -3,11 +3,9 @@ package command
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -21,7 +19,6 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
@ -29,7 +26,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
@ -2055,81 +2051,7 @@ func TestApply_jsonGoldenReference(t *testing.T) {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
|
||||
// Load the golden reference fixture
|
||||
wantFile, err := os.Open(path.Join(testFixturePath("apply"), "output.jsonlog"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open output file: %s", err)
|
||||
}
|
||||
defer wantFile.Close()
|
||||
wantBytes, err := ioutil.ReadAll(wantFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output file: %s", err)
|
||||
}
|
||||
want := string(wantBytes)
|
||||
|
||||
got := output.Stdout()
|
||||
|
||||
// Split the output and the reference into lines so that we can compare
|
||||
// messages
|
||||
got = strings.TrimSuffix(got, "\n")
|
||||
gotLines := strings.Split(got, "\n")
|
||||
|
||||
want = strings.TrimSuffix(want, "\n")
|
||||
wantLines := strings.Split(want, "\n")
|
||||
|
||||
if len(gotLines) != len(wantLines) {
|
||||
t.Errorf("unexpected number of log lines: got %d, want %d", len(gotLines), len(wantLines))
|
||||
}
|
||||
|
||||
// Verify that the log starts with a version message
|
||||
type versionMessage struct {
|
||||
Level string `json:"@level"`
|
||||
Message string `json:"@message"`
|
||||
Type string `json:"type"`
|
||||
Terraform string `json:"terraform"`
|
||||
UI string `json:"ui"`
|
||||
}
|
||||
var gotVersion versionMessage
|
||||
if err := json.Unmarshal([]byte(gotLines[0]), &gotVersion); err != nil {
|
||||
t.Errorf("failed to unmarshal version line: %s\n%s", err, gotLines[0])
|
||||
}
|
||||
wantVersion := versionMessage{
|
||||
"info",
|
||||
fmt.Sprintf("Terraform %s", tfversion.String()),
|
||||
"version",
|
||||
tfversion.String(),
|
||||
views.JSON_UI_VERSION,
|
||||
}
|
||||
if !cmp.Equal(wantVersion, gotVersion) {
|
||||
t.Errorf("unexpected first message:\n%s", cmp.Diff(wantVersion, gotVersion))
|
||||
}
|
||||
|
||||
// Compare the rest of the lines against the golden reference
|
||||
var gotLineMaps []map[string]interface{}
|
||||
for i, line := range gotLines[1:] {
|
||||
index := i + 1
|
||||
var gotMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &gotMap); err != nil {
|
||||
t.Errorf("failed to unmarshal got line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
if _, ok := gotMap["@timestamp"]; !ok {
|
||||
t.Errorf("missing @timestamp field in log: %s", gotLines[index])
|
||||
}
|
||||
delete(gotMap, "@timestamp")
|
||||
gotLineMaps = append(gotLineMaps, gotMap)
|
||||
}
|
||||
var wantLineMaps []map[string]interface{}
|
||||
for i, line := range wantLines[1:] {
|
||||
index := i + 1
|
||||
var wantMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &wantMap); err != nil {
|
||||
t.Errorf("failed to unmarshal want line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
wantLineMaps = append(wantLineMaps, wantMap)
|
||||
}
|
||||
if diff := cmp.Diff(wantLineMaps, gotLineMaps); diff != "" {
|
||||
t.Errorf("wrong output lines\n%s", diff)
|
||||
}
|
||||
checkGoldenReference(t, output, "apply")
|
||||
}
|
||||
|
||||
func TestApply_warnings(t *testing.T) {
|
||||
|
@ -7,12 +7,14 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -1052,3 +1054,92 @@ func testView(t *testing.T) (*views.View, func(*testing.T) *terminal.TestOutput)
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
return views.NewView(streams), done
|
||||
}
|
||||
|
||||
// checkGoldenReference compares the given test output with a known "golden" output log
|
||||
// located under the specified fixture path.
|
||||
//
|
||||
// If any of these tests fail, please communicate with Terraform Cloud folks before resolving,
|
||||
// as changes to UI output may also affect the behavior of Terraform Cloud's structured run output.
|
||||
func checkGoldenReference(t *testing.T, output *terminal.TestOutput, fixturePathName string) {
|
||||
t.Helper()
|
||||
|
||||
// Load the golden reference fixture
|
||||
wantFile, err := os.Open(path.Join(testFixturePath(fixturePathName), "output.jsonlog"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open output file: %s", err)
|
||||
}
|
||||
defer wantFile.Close()
|
||||
wantBytes, err := ioutil.ReadAll(wantFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output file: %s", err)
|
||||
}
|
||||
want := string(wantBytes)
|
||||
|
||||
got := output.Stdout()
|
||||
|
||||
// Split the output and the reference into lines so that we can compare
|
||||
// messages
|
||||
got = strings.TrimSuffix(got, "\n")
|
||||
gotLines := strings.Split(got, "\n")
|
||||
|
||||
want = strings.TrimSuffix(want, "\n")
|
||||
wantLines := strings.Split(want, "\n")
|
||||
|
||||
if len(gotLines) != len(wantLines) {
|
||||
t.Errorf("unexpected number of log lines: got %d, want %d\n"+
|
||||
"NOTE: This failure may indicate a UI change affecting the behavior of structured run output on TFC.\n"+
|
||||
"Please communicate with Terraform Cloud team before resolving", len(gotLines), len(wantLines))
|
||||
}
|
||||
|
||||
// Verify that the log starts with a version message
|
||||
type versionMessage struct {
|
||||
Level string `json:"@level"`
|
||||
Message string `json:"@message"`
|
||||
Type string `json:"type"`
|
||||
Terraform string `json:"terraform"`
|
||||
UI string `json:"ui"`
|
||||
}
|
||||
var gotVersion versionMessage
|
||||
if err := json.Unmarshal([]byte(gotLines[0]), &gotVersion); err != nil {
|
||||
t.Errorf("failed to unmarshal version line: %s\n%s", err, gotLines[0])
|
||||
}
|
||||
wantVersion := versionMessage{
|
||||
"info",
|
||||
fmt.Sprintf("Terraform %s", version.String()),
|
||||
"version",
|
||||
version.String(),
|
||||
views.JSON_UI_VERSION,
|
||||
}
|
||||
if !cmp.Equal(wantVersion, gotVersion) {
|
||||
t.Errorf("unexpected first message:\n%s", cmp.Diff(wantVersion, gotVersion))
|
||||
}
|
||||
|
||||
// Compare the rest of the lines against the golden reference
|
||||
var gotLineMaps []map[string]interface{}
|
||||
for i, line := range gotLines[1:] {
|
||||
index := i + 1
|
||||
var gotMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &gotMap); err != nil {
|
||||
t.Errorf("failed to unmarshal got line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
if _, ok := gotMap["@timestamp"]; !ok {
|
||||
t.Errorf("missing @timestamp field in log: %s", gotLines[index])
|
||||
}
|
||||
delete(gotMap, "@timestamp")
|
||||
gotLineMaps = append(gotLineMaps, gotMap)
|
||||
}
|
||||
var wantLineMaps []map[string]interface{}
|
||||
for i, line := range wantLines[1:] {
|
||||
index := i + 1
|
||||
var wantMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &wantMap); err != nil {
|
||||
t.Errorf("failed to unmarshal want line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
wantLineMaps = append(wantLineMaps, wantMap)
|
||||
}
|
||||
if diff := cmp.Diff(wantLineMaps, gotLineMaps); diff != "" {
|
||||
t.Errorf("wrong output lines\n%s\n"+
|
||||
"NOTE: This failure may indicate a UI change affecting the behavior of structured run output on TFC.\n"+
|
||||
"Please communicate with Terraform Cloud team before resolving", diff)
|
||||
}
|
||||
}
|
||||
|
@ -700,6 +700,22 @@ func TestPlan_providerArgumentUnset(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_data_source": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"valid": {
|
||||
Type: cty.Bool,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
@ -1382,6 +1398,33 @@ func TestPlan_warnings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlan_jsonGoldenReference(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("plan"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := planFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-json",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
checkGoldenReference(t, output, "plan")
|
||||
}
|
||||
|
||||
// planFixtureSchema returns a schema suitable for processing the
|
||||
// configuration in testdata/plan . This schema should be
|
||||
// assigned to a mock provider named "test".
|
||||
@ -1408,6 +1451,22 @@ func planFixtureSchema() *providers.GetProviderSchemaResponse {
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_data_source": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"valid": {
|
||||
Type: cty.Bool,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1423,6 +1482,14 @@ func planFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@ -1456,6 +1523,14 @@ func planVarsFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@ -1475,6 +1550,14 @@ func planWarningsFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
|
6
internal/command/testdata/plan/main.tf
vendored
6
internal/command/testdata/plan/main.tf
vendored
@ -4,6 +4,10 @@ resource "test_instance" "foo" {
|
||||
# This is here because at some point it caused a test failure
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
||||
|
||||
data "test_data_source" "a" {
|
||||
id = "zzzzz"
|
||||
}
|
||||
|
5
internal/command/testdata/plan/output.jsonlog
vendored
Normal file
5
internal/command/testdata/plan/output.jsonlog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{"@level":"info","@message":"Terraform 1.3.0-dev","@module":"terraform.ui","terraform":"1.3.0-dev","type":"version","ui":"1.0"}
|
||||
{"@level":"info","@message":"data.test_data_source.a: Refreshing...","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read"},"type":"apply_start"}
|
||||
{"@level":"info","@message":"data.test_data_source.a: Refresh complete after 0s [id=zzzzz]","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read","id_key":"id","id_value":"zzzzz","elapsed_seconds":0},"type":"apply_complete"}
|
||||
{"@level":"info","@message":"test_instance.foo: Plan to create","@module":"terraform.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"}
|
||||
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","changes":{"add":1,"change":0,"remove":0,"operation":"plan"},"type":"change_summary"}
|
Loading…
Reference in New Issue
Block a user