diff --git a/go.mod b/go.mod index 2d304ebc0..21793b7ec 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/hashicorp/terraform-exec v0.24.0 github.com/hashicorp/terraform-json v0.27.2 github.com/hashicorp/terraform-plugin-go v0.29.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 github.com/mitchellh/go-testing-interface v1.14.1 github.com/zclconf/go-cty v1.17.0 diff --git a/go.sum b/go.sum index 4a9a91b75..84193f33f 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoK github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= -github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= -github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= diff --git a/helper/logging/logging.go b/helper/logging/logging.go deleted file mode 100644 index c13453e49..000000000 --- a/helper/logging/logging.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package logging - -import ( - "fmt" - "io" - "log" - "os" - "strings" - "syscall" - - "github.com/hashicorp/logutils" - "github.com/mitchellh/go-testing-interface" -) - -// These are the environmental variables that determine if we log, and if -// we log whether or not the log should go to a file. -const ( - EnvLog = "TF_LOG" // See ValidLevels - EnvLogFile = "TF_LOG_PATH" // Set to a file - EnvAccLogFile = "TF_ACC_LOG_PATH" // Set to a file - // EnvLogPathMask splits test log files by name. - EnvLogPathMask = "TF_LOG_PATH_MASK" -) - -var ValidLevels = []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"} - -// LogOutput determines where we should send logs (if anywhere) and the log -// level. This only effects this log.Print* functions called in the provider -// under test. Dependency providers for the provider under test will have their -// logging controlled by Terraform itself and managed with the TF_ACC_LOG_PATH -// environment variable. Calls to tflog.* will have their output managed by the -// tfsdklog sink. -func LogOutput(t testing.T) (logOutput io.Writer, err error) { - logOutput = io.Discard - - logLevel := LogLevel() - if logLevel == "" { - if os.Getenv(EnvAccLogFile) != "" { - // plugintest defaults to TRACE when TF_ACC_LOG_PATH is - // set for Terraform and dependency providers of the - // provider under test. We should do the same for the - // provider under test. - logLevel = "TRACE" - } else { - return - } - } - - logOutput = os.Stderr - if logPath := os.Getenv(EnvLogFile); logPath != "" { - var err error - logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) - if err != nil { - return nil, err - } - } - - if logPath := os.Getenv(EnvAccLogFile); logPath != "" { - var err error - logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) - if err != nil { - return nil, err - } - } - - if logPathMask := os.Getenv(EnvLogPathMask); logPathMask != "" { - // Escape special characters which may appear if we have subtests - testName := strings.Replace(t.Name(), "/", "__", -1) - - logPath := fmt.Sprintf(logPathMask, testName) - var err error - logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) - if err != nil { - return nil, err - } - } - - // This was the default since the beginning - logOutput = &logutils.LevelFilter{ - Levels: ValidLevels, - MinLevel: logutils.LogLevel(logLevel), - Writer: logOutput, - } - - return -} - -// SetOutput checks for a log destination with LogOutput, and calls -// log.SetOutput with the result. If LogOutput returns nil, SetOutput uses -// io.Discard. Any error from LogOutout is fatal. -func SetOutput(t testing.T) { - out, err := LogOutput(t) - if err != nil { - log.Fatal(err) - } - - if out == nil { - out = io.Discard - } - - log.SetOutput(out) -} - -// LogLevel returns the current log level string based the environment vars -func LogLevel() string { - envLevel := os.Getenv(EnvLog) - if envLevel == "" { - return "" - } - - logLevel := "TRACE" - if isValidLogLevel(envLevel) { - // allow following for better ux: info, Info or INFO - logLevel = strings.ToUpper(envLevel) - } else { - log.Printf("[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v", - envLevel, ValidLevels) - } - - return logLevel -} - -// IsDebugOrHigher returns whether or not the current log level is debug or trace -func IsDebugOrHigher() bool { - level := LogLevel() - return level == "DEBUG" || level == "TRACE" -} - -func isValidLogLevel(level string) bool { - for _, l := range ValidLevels { - if strings.ToUpper(level) == string(l) { - return true - } - } - - return false -} diff --git a/internal/logging/context.go b/internal/logging/context.go index 5a3108451..35f416061 100644 --- a/internal/logging/context.go +++ b/internal/logging/context.go @@ -21,7 +21,7 @@ import ( func InitTestContext(ctx context.Context, t testing.T) context.Context { helperlogging.SetOutput(t) - ctx = tfsdklog.RegisterTestSink(ctx, t) + ctx = tfsdklog.ContextWithTestLogging(ctx, t.Name()) ctx = tfsdklog.NewRootSDKLogger(ctx, tfsdklog.WithLevelFromEnv(EnvTfLogSdk)) ctx = tfsdklog.NewSubsystem(ctx, SubsystemHelperResource, // All calls are through the HelperResource* helper functions diff --git a/internal/testing/hack/t.go b/internal/testing/hack/t.go new file mode 100644 index 000000000..d41213fdd --- /dev/null +++ b/internal/testing/hack/t.go @@ -0,0 +1,205 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package hack + +import ( + "context" + "fmt" + "io" + "testing" + "time" +) + +// BaseT is a replacement for github.com/mitchellh/go-testing-interface.T. +// +// RuntimeT and StandardT are two implementations of BaseT. MetaT can be used +// to test a testing.T-based test framework without the side effects of +// stopping goroutine execution. StandardT can be used as an adapter around a +// standard testing.T. +// +// Precedent for StandardT: the unexported tshim type in +// github.com/rogpeppe/go-internal/testscript. +type BaseT interface { + Cleanup(func()) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fail() + FailNow() + Failed() bool + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Helper() + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Name() string + Parallel() + Skip(args ...interface{}) + SkipNow() + Skipf(format string, args ...interface{}) + Skipped() bool + + // Not in mitchellh/go-testing-interface + Attr(key, value string) + Chdir(dir string) + Context() context.Context + Deadline() (deadline time.Time, ok bool) + Output() io.Writer + Setenv(key, value string) + TempDir() string +} + +var _ BaseT = MetaT{} + +type MetaT struct { + fail bool + skip bool +} + +// Attr satisfies [BaseT] and does nothing. +func (t MetaT) Attr(key string, value string) { +} + +// Chdir satisfies [BaseT] and does nothing. +func (r MetaT) Chdir(dir string) { +} + +// Cleanup satisfies [BaseT] and does nothing. +func (r MetaT) Cleanup(_ func()) { +} + +// Context satisfies [BaseT] and returns context.TODO() +func (r MetaT) Context() context.Context { + return context.TODO() +} + +// Deadline satisfies [BaseT] and returns zero-values. +func (r MetaT) Deadline() (deadline time.Time, ok bool) { + return time.Time{}, false +} + +// Error is equivalent to Log followed by Fail. +func (t MetaT) Error(args ...interface{}) { + t.Log(args) + t.Fail() +} + +// Errorf is equivalent to Logf followed by Fail. +func (t MetaT) Errorf(format string, args ...interface{}) { + t.Logf(format, args...) + t.Fail() +} + +// Fail marks the function as having failed but continues execution. +func (t MetaT) Fail() { + t.fail = true +} + +// Failed reports whether the function has failed. +func (t MetaT) Failed() bool { + return t.fail +} + +// FailNow marks the function as having failed and stops its execution by +// calling panic(). +// +// For compatibility, it mimics the string argument from +// mitchellg/go-testing-interface. +func (t MetaT) FailNow() { + panic("testing.T failed, see logs for output") +} + +// Fatal is equivalent to Log followed by FailNow. +func (t MetaT) Fatal(args ...interface{}) { + t.Log(args) + t.FailNow() +} + +// Fatalf is equivalent to Logf followed by FailNow. +func (t MetaT) Fatalf(format string, args ...interface{}) { + t.Logf(format, args...) + t.FailNow() +} + +// Log formats its arguments using default formatting, analogous to +// fmt.Println,, and records the text to standard output. +func (t MetaT) Log(args ...interface{}) { + fmt.Println(fmt.Sprintf("%v", args)) +} + +// Logf formats its arguments according to the format, analogous to fmt.Printf, +// and records the text to standard output. +func (t MetaT) Logf(format string, args ...interface{}) { + fmt.Println(fmt.Sprintf(format, args...)) +} + +// Helper satisfies [BaseT] and does nothing. +func (r MetaT) Helper() { +} + +// Name satisfies [BaseT] and returns an empty string. +func (r MetaT) Name() string { + return "" +} + +// Output satisfied [BaseT] and returns io.Discard. +func (r MetaT) Output() io.Writer { + return io.Discard +} + +// Parallel satisfies [BaseT] and does nothing. +func (r MetaT) Parallel() { + panic("parallel not implemented") // TODO: Implement +} + +// Setenv satisfies [BaseT] and does nothing. +func (r MetaT) Setenv(key string, value string) { +} + +// SkipNow marks the test as having been skipped. +// +// As a practical consideration, this does not stop execution in the way that +// [testing.T.SkipNow] does -- RuntimeT.Run does not run its function in a +// separate goroutine. +func (t MetaT) SkipNow() { + t.Skip() +} + +// Skipf is equivalent to Logf followed by SkipNow. +func (t MetaT) Skipf(format string, args ...interface{}) { + t.Logf(format, args...) + t.Skip() +} + +// TempDir satisfies [BaseT] and returns "/dev/null". +func (r MetaT) TempDir() string { + return "/dev/null" +} + +// Skip is equivalent to Log followed by SkipNow. +func (t MetaT) Skip(args ...interface{}) { + t.Log(args) + t.skip = true +} + +// Skipped reports whether the test was skipped. +func (t MetaT) Skipped() bool { + return t.skip +} + +var _ BaseT = StandardT{} + +// StandardT embeds a [testing.T] and satisfies [BaseT]. +type StandardT struct { + *testing.T +} + +// Run runs f as a subtest of t. +// +// As practical consideration, this does nsot run its function in a separate +// goroutine and the name is not used. +func (t StandardT) Run(name string, f func(BaseT)) bool { + return t.T.Run(name, func(t *testing.T) { + f(StandardT{t}) + }) +} diff --git a/tfversion/any_test.go b/tfversion/any_test.go index 76eacaa01..8b0f1ef48 100644 --- a/tfversion/any_test.go +++ b/tfversion/any_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-go/tfprotov6" - testinginterface "github.com/mitchellh/go-testing-interface" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/hack" r "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" @@ -74,7 +74,7 @@ func Test_Any_Error(t *testing.T) { //nolint:paralleltest t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.1.0") plugintest.TestExpectTFatal(t, func() { - r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{ + r.UnitTest(&hack.RuntimeT{}, r.TestCase{ ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "test": func() (tfprotov6.ProviderServer, error) { //nolint:unparam // required signature return nil, nil