From 6fef34dbfc384e7ef12739f23eeabd2f62861b07 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 4 May 2022 13:47:38 -0400 Subject: [PATCH] internal: Introduce logging unit testing Creates internal/testing/emptyprovider so it can be shared for multiple packages, then uses it to verify the logging setup with `(internal/proto6server.Server).GetProviderSchema()`. This could be expanded to other RPC handlers, if desired and the testing setup is trivial enough. Also added unit testing within internal/logging to ensure it is fully covered. --- internal/logging/context_test.go | 49 +++++++ internal/logging/framework_test.go | 159 +++++++++++++++++++++ internal/proto6server/serve.go | 2 +- internal/proto6server/serve_test.go | 66 +++++++++ internal/testing/emptyprovider/provider.go | 34 +++++ providerserver/provider_test.go | 31 ---- providerserver/providerserver_test.go | 5 +- 7 files changed, 312 insertions(+), 34 deletions(-) create mode 100644 internal/logging/context_test.go create mode 100644 internal/logging/framework_test.go create mode 100644 internal/testing/emptyprovider/provider.go delete mode 100644 providerserver/provider_test.go diff --git a/internal/logging/context_test.go b/internal/logging/context_test.go new file mode 100644 index 000000000..290eec983 --- /dev/null +++ b/internal/logging/context_test.go @@ -0,0 +1,49 @@ +package logging_test + +import ( + "bytes" + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-log/tfsdklog" + "github.com/hashicorp/terraform-plugin-log/tfsdklogtest" +) + +func TestInitContext(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + + // Simulate root logger fields that would have been associated by + // terraform-plugin-go prior to the InitContext() call. + ctx = tfsdklog.With(ctx, "tf_rpc", "GetProviderSchema") + ctx = tfsdklog.With(ctx, "tf_req_id", "123-testing-123") + + ctx = logging.InitContext(ctx) + + logging.FrameworkTrace(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "trace", + "@message": "test message", + "@module": "sdk.framework", + "tf_rpc": "GetProviderSchema", + "tf_req_id": "123-testing-123", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/internal/logging/framework_test.go b/internal/logging/framework_test.go new file mode 100644 index 000000000..1f9c09978 --- /dev/null +++ b/internal/logging/framework_test.go @@ -0,0 +1,159 @@ +package logging_test + +import ( + "bytes" + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-log/tfsdklogtest" +) + +func TestFrameworkDebug(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + logging.FrameworkDebug(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "debug", + "@message": "test message", + "@module": "sdk.framework", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} + +func TestFrameworkError(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + logging.FrameworkError(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "error", + "@message": "test message", + "@module": "sdk.framework", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} + +func TestFrameworkTrace(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + logging.FrameworkTrace(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "trace", + "@message": "test message", + "@module": "sdk.framework", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} + +func TestFrameworkWarn(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + logging.FrameworkWarn(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "warn", + "@message": "test message", + "@module": "sdk.framework", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} + +func TestFrameworkWithAttributePath(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + ctx = logging.FrameworkWithAttributePath(ctx, tftypes.NewAttributePath().WithAttributeName("test_attr").String()) + logging.FrameworkTrace(ctx, "test message") + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "trace", + "@message": "test message", + "@module": "sdk.framework", + "tf_attribute_path": "AttributeName(\"test_attr\")", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/internal/proto6server/serve.go b/internal/proto6server/serve.go index a906f7d8c..5d2225add 100644 --- a/internal/proto6server/serve.go +++ b/internal/proto6server/serve.go @@ -197,7 +197,7 @@ func (s *Server) getProviderSchema(ctx context.Context, resp *getProviderSchemaR // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/299 logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources") dataSourceSchemas, diags := s.Provider.GetDataSources(ctx) - logging.FrameworkDebug(ctx, "Calling provider defined Provider GetDataSources") + logging.FrameworkDebug(ctx, "Called provider defined Provider GetDataSources") resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/internal/proto6server/serve_test.go b/internal/proto6server/serve_test.go index f0cc78cda..8cc4249ce 100644 --- a/internal/proto6server/serve_test.go +++ b/internal/proto6server/serve_test.go @@ -1,6 +1,7 @@ package proto6server import ( + "bytes" "context" "encoding/json" "strings" @@ -11,10 +12,13 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-log/tfsdklogtest" ) func TestServerCancelInFlightContexts(t *testing.T) { @@ -300,6 +304,68 @@ func TestServerGetProviderSchema(t *testing.T) { } } +func TestServerGetProviderSchema_logging(t *testing.T) { + t.Parallel() + + var output bytes.Buffer + + ctx := tfsdklogtest.RootLogger(context.Background(), &output) + ctx = logging.InitContext(ctx) + + testServer := &Server{ + Provider: &emptyprovider.Provider{}, + } + + _, err := testServer.GetProviderSchema(ctx, new(tfprotov6.GetProviderSchemaRequest)) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + entries, err := tfsdklogtest.MultilineJSONDecode(&output) + + if err != nil { + t.Fatalf("unable to read multiple line JSON: %s", err) + } + + expectedEntries := []map[string]interface{}{ + { + "@level": "debug", + "@message": "Calling provider defined Provider GetSchema", + "@module": "sdk.framework", + }, + { + "@level": "debug", + "@message": "Called provider defined Provider GetSchema", + "@module": "sdk.framework", + }, + { + "@level": "debug", + "@message": "Calling provider defined Provider GetResources", + "@module": "sdk.framework", + }, + { + "@level": "debug", + "@message": "Called provider defined Provider GetResources", + "@module": "sdk.framework", + }, + { + "@level": "debug", + "@message": "Calling provider defined Provider GetDataSources", + "@module": "sdk.framework", + }, + { + "@level": "debug", + "@message": "Called provider defined Provider GetDataSources", + "@module": "sdk.framework", + }, + } + + if diff := cmp.Diff(entries, expectedEntries); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} + func TestServerGetProviderSchemaWithProviderMeta(t *testing.T) { t.Parallel() diff --git a/internal/testing/emptyprovider/provider.go b/internal/testing/emptyprovider/provider.go new file mode 100644 index 000000000..964b3c3fb --- /dev/null +++ b/internal/testing/emptyprovider/provider.go @@ -0,0 +1,34 @@ +package emptyprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +var _ tfsdk.Provider = &Provider{} + +// tfsdk.Provider that is completely empty, e.g. +// +// - No Schema +// - No DataSources +// - No Resources +// +type Provider struct{} + +func (t *Provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{}, nil +} + +func (t *Provider) Configure(_ context.Context, _ tfsdk.ConfigureProviderRequest, _ *tfsdk.ConfigureProviderResponse) { + // intentionally empty +} + +func (t *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + return map[string]tfsdk.DataSourceType{}, nil +} + +func (t *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{}, nil +} diff --git a/providerserver/provider_test.go b/providerserver/provider_test.go deleted file mode 100644 index 7272bde52..000000000 --- a/providerserver/provider_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package providerserver - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" -) - -var _ tfsdk.Provider = &testProvider{} - -// Provider type for testing package functionality. -// -// This is separate from tfsdk.testServeProvider to avoid changing that. -type testProvider struct{} - -func (t *testProvider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{}, nil -} - -func (t *testProvider) Configure(_ context.Context, _ tfsdk.ConfigureProviderRequest, _ *tfsdk.ConfigureProviderResponse) { - // intentionally empty -} - -func (t *testProvider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { - return map[string]tfsdk.DataSourceType{}, nil -} - -func (t *testProvider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { - return map[string]tfsdk.ResourceType{}, nil -} diff --git a/providerserver/providerserver_test.go b/providerserver/providerserver_test.go index 14a479fd4..611fe9015 100644 --- a/providerserver/providerserver_test.go +++ b/providerserver/providerserver_test.go @@ -4,11 +4,12 @@ import ( "context" "testing" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/emptyprovider" "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) func TestNewProtocol6(t *testing.T) { - provider := &testProvider{} + provider := &emptyprovider.Provider{} providerServerFunc := NewProtocol6(provider) providerServer := providerServerFunc() @@ -22,7 +23,7 @@ func TestNewProtocol6(t *testing.T) { } func TestNewProtocol6WithError(t *testing.T) { - provider := &testProvider{} + provider := &emptyprovider.Provider{} providerServer, err := NewProtocol6WithError(provider)()