| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package cloud | |
| import ( | |
| "context" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/hashicorp/go-tfe" | |
| "github.com/hashicorp/terraform/internal/command/arguments" | |
| "github.com/hashicorp/terraform/internal/command/jsonformat" | |
| "github.com/hashicorp/terraform/internal/command/views" | |
| "github.com/hashicorp/terraform/internal/configs" | |
| "github.com/hashicorp/terraform/internal/configs/configload" | |
| "github.com/hashicorp/terraform/internal/terminal" | |
| "github.com/hashicorp/terraform/internal/terraform" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| func TestTest(t *testing.T) { | |
| streams, done := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := done(t) | |
| actual := output.All() | |
| expected := `main.tftest.hcl... in progress | |
| defaults... pass | |
| overrides... pass | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... pass | |
| Success! 2 passed, 0 failed. | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| } | |
| func TestTest_Parallelism(t *testing.T) { | |
| streams, _ := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| OperationParallelism: 4, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| if mock.TestRuns.parallelism != 4 { | |
| t.Errorf("expected parallelism to be 4 but was %d", mock.TestRuns.parallelism) | |
| } | |
| } | |
| func TestTest_JSON(t *testing.T) { | |
| streams, done := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: nil, // This should force the logs to render as JSON. | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := done(t) | |
| actual := output.All() | |
| expected := `{"@level":"info","@message":"Terraform 1.6.0-dev","@module":"terraform.ui","@timestamp":"2023-09-12T08:29:27.257413+02:00","terraform":"1.6.0-dev","type":"version","ui":"1.2"} | |
| {"@level":"info","@message":"Found 1 file and 2 run blocks","@module":"terraform.ui","@timestamp":"2023-09-12T08:29:27.268731+02:00","test_abstract":{"main.tftest.hcl":["defaults","overrides"]},"type":"test_abstract"} | |
| {"@level":"info","@message":"main.tftest.hcl... in progress","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-12T08:29:27.268889+02:00","test_file":{"path":"main.tftest.hcl","progress":"starting"},"type":"test_file"} | |
| {"@level":"info","@message":" \"defaults\"... pass","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"defaults","@timestamp":"2023-09-12T08:29:27.710541+02:00","test_run":{"path":"main.tftest.hcl","run":"defaults","progress":"complete","status":"pass"},"type":"test_run"} | |
| {"@level":"info","@message":" \"overrides\"... pass","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"overrides","@timestamp":"2023-09-12T08:29:27.833351+02:00","test_run":{"path":"main.tftest.hcl","run":"overrides","progress":"complete","status":"pass"},"type":"test_run"} | |
| {"@level":"info","@message":"main.tftest.hcl... tearing down","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-12T08:29:27.833375+02:00","test_file":{"path":"main.tftest.hcl","progress":"teardown"},"type":"test_file"} | |
| {"@level":"info","@message":"main.tftest.hcl... pass","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-12T08:29:27.956488+02:00","test_file":{"path":"main.tftest.hcl","progress":"complete","status":"pass"},"type":"test_file"} | |
| {"@level":"info","@message":"Success! 2 passed, 0 failed.","@module":"terraform.ui","@timestamp":"2023-09-12T08:29:27.956510+02:00","test_summary":{"status":"pass","passed":2,"failed":0,"errored":0,"skipped":0},"type":"test_summary"} | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| } | |
| func TestTest_Verbose(t *testing.T) { | |
| directory := "testdata/test-verbose" | |
| loader, close := configload.NewLoaderForTests(t) | |
| defer close() | |
| rootMod, hclDiags := loader.LoadRootModuleWithTests(directory, "tests") | |
| if hclDiags.HasErrors() { | |
| t.Fatalf("failed to load root module: %v", hclDiags.Error()) | |
| } | |
| config, configDiags := terraform.BuildConfigWithGraph( | |
| rootMod, | |
| loader.ModuleWalker(), | |
| nil, | |
| configs.MockDataLoaderFunc(loader.LoadExternalMockData), | |
| ) | |
| if configDiags.HasErrors() { | |
| t.Fatalf("failed to load config: %v", configDiags.Err()) | |
| } | |
| streams, done := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: directory, | |
| TestingDirectory: "tests", | |
| Config: config, | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // The test options don't actually matter, as we just retrieve whatever | |
| // is set in the log file. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := done(t) | |
| actual := output.All() | |
| expected := `main.tftest.hcl... in progress | |
| defaults... pass | |
| Changes to Outputs: | |
| + input = "Hello, world!" | |
| You can apply this plan to save these new output values to the Terraform | |
| state, without changing any real infrastructure. | |
| ╷ | |
| │ Warning: Deprecated | |
| │ | |
| │ with data.null_data_source.values, | |
| │ on main.tf line 7, in data "null_data_source" "values": | |
| │ 7: data "null_data_source" "values" { | |
| │ | |
| │ The null_data_source was historically used to construct intermediate values | |
| │ to re-use elsewhere in configuration, the same can now be achieved using | |
| │ locals | |
| ╵ | |
| ╷ | |
| │ Warning: Deprecated | |
| │ | |
| │ with data.null_data_source.values, | |
| │ on main.tf line 7, in data "null_data_source" "values": | |
| │ 7: data "null_data_source" "values" { | |
| │ | |
| │ The null_data_source was historically used to construct intermediate values | |
| │ to re-use elsewhere in configuration, the same can now be achieved using | |
| │ locals | |
| ╵ | |
| overrides... pass | |
| # data.null_data_source.values: | |
| data "null_data_source" "values" { | |
| has_computed_default = "default" | |
| id = "static" | |
| inputs = { | |
| "data" = "Hello, universe!" | |
| } | |
| outputs = { | |
| "data" = "Hello, universe!" | |
| } | |
| random = "8484833523059069761" | |
| } | |
| Outputs: | |
| input = "Hello, universe!" | |
| ╷ | |
| │ Warning: Deprecated | |
| │ | |
| │ with data.null_data_source.values, | |
| │ on main.tf line 7, in data "null_data_source" "values": | |
| │ 7: data "null_data_source" "values" { | |
| │ | |
| │ The null_data_source was historically used to construct intermediate values | |
| │ to re-use elsewhere in configuration, the same can now be achieved using | |
| │ locals | |
| ╵ | |
| ╷ | |
| │ Warning: Deprecated | |
| │ | |
| │ with data.null_data_source.values, | |
| │ on main.tf line 7, in data "null_data_source" "values": | |
| │ 7: data "null_data_source" "values" { | |
| │ | |
| │ The null_data_source was historically used to construct intermediate values | |
| │ to re-use elsewhere in configuration, the same can now be achieved using | |
| │ locals | |
| ╵ | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... pass | |
| Success! 2 passed, 0 failed. | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| } | |
| func TestTest_Cancel(t *testing.T) { | |
| streams, outputFn := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| module, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }) | |
| if err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| doneContext, done := context.WithCancel(context.Background()) | |
| stopContext, stop := context.WithCancel(context.Background()) | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test-cancel", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: stopContext, | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| // We're only going to be able to finish this if the cancellation calls | |
| // are done correctly. | |
| mock.TestRuns.targetCancels = 1 | |
| var diags tfdiags.Diagnostics | |
| go func() { | |
| defer done() | |
| _, diags = runner.Test(false) | |
| }() | |
| stop() // immediately cancel | |
| // Wait for finish! | |
| <-doneContext.Done() | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := outputFn(t) | |
| actual := output.All() | |
| expected := `main.tftest.hcl... in progress | |
| Interrupt received. | |
| Please wait for Terraform to exit or data loss may occur. | |
| Gracefully shutting down... | |
| defaults... pass | |
| overrides... skip | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... pass | |
| Success! 1 passed, 0 failed, 1 skipped. | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| // We want to make sure the cancel signal actually made it through. | |
| // Luckily we can access the test runs directly in the mock client. | |
| tr := mock.TestRuns.modules[module.ID][0] | |
| if tr.Status != tfe.TestRunCanceled { | |
| t.Errorf("expected test run to have been cancelled but was %s", tr.Status) | |
| } | |
| if mock.TestRuns.cancels != 1 { | |
| t.Errorf("incorrect number of cancels, expected 1 but was %d", mock.TestRuns.cancels) | |
| } | |
| } | |
| // TestTest_DelayedCancel just makes sure that if we trigger the cancellation | |
| // during the log reading stage then it still cancels properly. | |
| func TestTest_DelayedCancel(t *testing.T) { | |
| streams, outputFn := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| module, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }) | |
| if err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| doneContext, done := context.WithCancel(context.Background()) | |
| stopContext, stop := context.WithCancel(context.Background()) | |
| mock.TestRuns.delayedCancel = stop | |
| // We're only going to be able to finish this if the cancellation calls | |
| // are done correctly. | |
| mock.TestRuns.targetCancels = 1 | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test-cancel", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: stopContext, | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| var diags tfdiags.Diagnostics | |
| go func() { | |
| defer done() | |
| _, diags = runner.Test(false) | |
| }() | |
| // Wait for finish! | |
| <-doneContext.Done() | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := outputFn(t) | |
| actual := output.All() | |
| expected := `main.tftest.hcl... in progress | |
| Interrupt received. | |
| Please wait for Terraform to exit or data loss may occur. | |
| Gracefully shutting down... | |
| defaults... pass | |
| overrides... skip | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... pass | |
| Success! 1 passed, 0 failed, 1 skipped. | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| // We want to make sure the cancel signal actually made it through. | |
| // Luckily we can access the test runs directly in the mock client. | |
| tr := mock.TestRuns.modules[module.ID][0] | |
| if tr.Status != tfe.TestRunCanceled { | |
| t.Errorf("expected test run to have been cancelled but was %s", tr.Status) | |
| } | |
| } | |
| func TestTest_ForceCancel(t *testing.T) { | |
| loader, close := configload.NewLoaderForTests(t) | |
| defer close() | |
| rootMod, hclDiags := loader.LoadRootModuleWithTests("testdata/test-force-cancel", "tests") | |
| if hclDiags.HasErrors() { | |
| t.Fatalf("failed to load root module: %v", hclDiags.Error()) | |
| } | |
| config, configDiags := terraform.BuildConfigWithGraph( | |
| rootMod, | |
| loader.ModuleWalker(), | |
| nil, | |
| configs.MockDataLoaderFunc(loader.LoadExternalMockData), | |
| ) | |
| if configDiags.HasErrors() { | |
| t.Fatalf("failed to load config: %v", configDiags.Err()) | |
| } | |
| streams, outputFn := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| module, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }) | |
| if err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| doneContext, done := context.WithCancel(context.Background()) | |
| stopContext, stop := context.WithCancel(context.Background()) | |
| cancelContext, cancel := context.WithCancel(context.Background()) | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test-force-cancel", | |
| TestingDirectory: "tests", | |
| Config: config, | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: stopContext, | |
| CancelledCtx: cancelContext, | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| // We're only going to be able to finish this if the cancellation calls | |
| // are done correctly. | |
| mock.TestRuns.targetCancels = 2 | |
| var diags tfdiags.Diagnostics | |
| go func() { | |
| defer done() | |
| _, diags = runner.Test(false) | |
| }() | |
| stop() | |
| cancel() | |
| // Wait for finish! | |
| <-doneContext.Done() | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := outputFn(t) | |
| expectedErr := `Terraform was interrupted during test execution, and may not have performed | |
| the expected cleanup operations. | |
| Terraform was in the process of creating the following resources for | |
| "overrides" from the module under test, and they may not have been destroyed: | |
| - time_sleep.wait_5_seconds | |
| - tfcoremock_simple_resource.resource | |
| ` | |
| actualErr := output.Stderr() | |
| if diff := cmp.Diff(expectedErr, actualErr); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expectedErr, actualErr, diff) | |
| } | |
| actualOut := output.Stdout() | |
| expectedOut := `main.tftest.hcl... in progress | |
| defaults... pass | |
| Interrupt received. | |
| Please wait for Terraform to exit or data loss may occur. | |
| Gracefully shutting down... | |
| Two interrupts received. Exiting immediately. Note that data loss may have occurred. | |
| overrides... fail | |
| ╷ | |
| │ Error: Test interrupted | |
| │ | |
| │ The test operation could not be completed due to an interrupt signal. | |
| │ Please read the remaining diagnostics carefully for any sign of failed | |
| │ state cleanup or dangling resources. | |
| ╵ | |
| ╷ | |
| │ Error: Create time sleep error | |
| │ | |
| │ with time_sleep.wait_5_seconds, | |
| │ on main.tf line 7, in resource "time_sleep" "wait_5_seconds": | |
| │ 7: resource "time_sleep" "wait_5_seconds" { | |
| │ | |
| │ Original Error: context canceled | |
| ╵ | |
| ╷ | |
| │ Error: execution halted | |
| │ | |
| ╵ | |
| ╷ | |
| │ Error: execution halted | |
| │ | |
| ╵ | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... fail | |
| Failure! 1 passed, 1 failed. | |
| ` | |
| if diff := cmp.Diff(expectedOut, actualOut); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expectedOut, actualOut, diff) | |
| } | |
| // We want to make sure the cancel signal actually made it through. | |
| // Luckily we can access the test runs directly in the mock client. | |
| tr := mock.TestRuns.modules[module.ID][0] | |
| if tr.Status != tfe.TestRunCanceled { | |
| t.Errorf("expected test run to have been cancelled but was %s", tr.Status) | |
| } | |
| if mock.TestRuns.cancels != 2 { | |
| t.Errorf("incorrect number of cancels, expected 2 but was %d", mock.TestRuns.cancels) | |
| } | |
| } | |
| func TestTest_LongRunningTest(t *testing.T) { | |
| streams, done := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test-long-running", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: &jsonformat.Renderer{ | |
| Streams: streams, | |
| Colorize: colorize, | |
| RunningInAutomation: false, | |
| }, | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := done(t) | |
| actual := output.All() | |
| // The long running test logs actually contain additional progress updates, | |
| // but this test should ignore them and just show the usual output. | |
| expected := `main.tftest.hcl... in progress | |
| just_go... pass | |
| main.tftest.hcl... tearing down | |
| main.tftest.hcl... pass | |
| Success! 1 passed, 0 failed. | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| } | |
| func TestTest_LongRunningTestJSON(t *testing.T) { | |
| streams, done := terminal.StreamsForTesting(t) | |
| view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) | |
| colorize := mockColorize() | |
| colorize.Disable = true | |
| mock := NewMockClient() | |
| client := &tfe.Client{ | |
| ConfigurationVersions: mock.ConfigurationVersions, | |
| Organizations: mock.Organizations, | |
| RegistryModules: mock.RegistryModules, | |
| TestRuns: mock.TestRuns, | |
| } | |
| if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ | |
| Name: tfe.String("organisation"), | |
| }); err != nil { | |
| t.Fatalf("failed to create organisation: %v", err) | |
| } | |
| if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ | |
| Name: tfe.String("name"), | |
| Provider: tfe.String("provider"), | |
| RegistryName: "app.terraform.io", | |
| Namespace: "organisation", | |
| }); err != nil { | |
| t.Fatalf("failed to create registry module: %v", err) | |
| } | |
| runner := TestSuiteRunner{ | |
| // Configuration data. | |
| ConfigDirectory: "testdata/test-long-running", | |
| TestingDirectory: "tests", | |
| Config: nil, // We don't need this for this test. | |
| Source: "app.terraform.io/organisation/name/provider", | |
| // Cancellation controls, we won't be doing any cancellations in this | |
| // test. | |
| Stopped: false, | |
| Cancelled: false, | |
| StoppedCtx: context.Background(), | |
| CancelledCtx: context.Background(), | |
| // Test Options, empty for this test. | |
| GlobalVariables: nil, | |
| Verbose: false, | |
| Filters: nil, | |
| // Outputs | |
| Renderer: nil, // This should force the logs to render as JSON. | |
| View: view, | |
| Streams: streams, | |
| // Networking | |
| Services: nil, // Don't need this when the client is overridden. | |
| clientOverride: client, | |
| } | |
| _, diags := runner.Test(false) | |
| if len(diags) > 0 { | |
| t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) | |
| } | |
| output := done(t) | |
| actual := output.All() | |
| // This test should still include the progress updates as we're doing the | |
| // JSON output. | |
| expected := `{"@level":"info","@message":"Terraform 1.7.0-dev","@module":"terraform.ui","@timestamp":"2023-09-28T14:57:09.175210+02:00","terraform":"1.7.0-dev","type":"version","ui":"1.2"} | |
| {"@level":"info","@message":"Found 1 file and 1 run block","@module":"terraform.ui","@timestamp":"2023-09-28T14:57:09.189212+02:00","test_abstract":{"main.tftest.hcl":["just_go"]},"type":"test_abstract"} | |
| {"@level":"info","@message":"main.tftest.hcl... in progress","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-28T14:57:09.189386+02:00","test_file":{"path":"main.tftest.hcl","progress":"starting"},"type":"test_file"} | |
| {"@level":"info","@message":" \"just_go\"... in progress","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:09.189429+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"starting","elapsed":0},"type":"test_run"} | |
| {"@level":"info","@message":" \"just_go\"... in progress","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:11.341278+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"running","elapsed":2152},"type":"test_run"} | |
| {"@level":"info","@message":" \"just_go\"... in progress","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:13.343465+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"running","elapsed":4154},"type":"test_run"} | |
| {"@level":"info","@message":" \"just_go\"... pass","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:14.381552+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"complete","status":"pass"},"type":"test_run"} | |
| {"@level":"info","@message":"main.tftest.hcl... tearing down","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-28T14:57:14.381655+02:00","test_file":{"path":"main.tftest.hcl","progress":"teardown"},"type":"test_file"} | |
| {"@level":"info","@message":" \"just_go\"... tearing down","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:14.381712+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"teardown","elapsed":0},"type":"test_run"} | |
| {"@level":"info","@message":" \"just_go\"... tearing down","@module":"terraform.ui","@testfile":"main.tftest.hcl","@testrun":"just_go","@timestamp":"2023-09-28T14:57:16.477705+02:00","test_run":{"path":"main.tftest.hcl","run":"just_go","progress":"teardown","elapsed":2096},"type":"test_run"} | |
| {"@level":"info","@message":"main.tftest.hcl... pass","@module":"terraform.ui","@testfile":"main.tftest.hcl","@timestamp":"2023-09-28T14:57:17.517309+02:00","test_file":{"path":"main.tftest.hcl","progress":"complete","status":"pass"},"type":"test_file"} | |
| {"@level":"info","@message":"Success! 1 passed, 0 failed.","@module":"terraform.ui","@timestamp":"2023-09-28T14:57:17.517494+02:00","test_summary":{"status":"pass","passed":1,"failed":0,"errored":0,"skipped":0},"type":"test_summary"} | |
| ` | |
| if diff := cmp.Diff(expected, actual); len(diff) > 0 { | |
| t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff) | |
| } | |
| } |