diff --git a/bundle/tests/suggest_target_test.go b/bundle/tests/suggest_target_test.go index 8fb13040..02905d77 100644 --- a/bundle/tests/suggest_target_test.go +++ b/bundle/tests/suggest_target_test.go @@ -1,22 +1,22 @@ package config_tests import ( - "path/filepath" + "context" "testing" - "github.com/databricks/cli/cmd/root" - assert "github.com/databricks/cli/libs/dyn/dynassert" - - "github.com/databricks/cli/internal" + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config/mutator" + "github.com/stretchr/testify/require" ) func TestSuggestTargetIfWrongPassed(t *testing.T) { - t.Setenv("BUNDLE_ROOT", filepath.Join("target_overrides", "workspace")) - stdoutBytes, _, err := internal.RequireErrorRun(t, "bundle", "validate", "-e", "incorrect") - stdout := stdoutBytes.String() + b := load(t, "target_overrides/workspace") - assert.Error(t, root.ErrAlreadyPrinted, err) - assert.Contains(t, stdout, "Available targets:") - assert.Contains(t, stdout, "development") - assert.Contains(t, stdout, "staging") + ctx := context.Background() + diags := bundle.Apply(ctx, b, mutator.SelectTarget("incorrect")) + err := diags.Error() + require.Error(t, err) + require.Contains(t, err.Error(), "Available targets:") + require.Contains(t, err.Error(), "development") + require.Contains(t, err.Error(), "staging") } diff --git a/cmd/labs/installed_test.go b/cmd/labs/installed_test.go index 00692f79..0418e5a0 100644 --- a/cmd/labs/installed_test.go +++ b/cmd/labs/installed_test.go @@ -4,14 +4,14 @@ import ( "context" "testing" - "github.com/databricks/cli/internal" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/env" ) func TestListsInstalledProjects(t *testing.T) { ctx := context.Background() ctx = env.WithUserHomeDir(ctx, "project/testdata/installed-in-home") - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "installed") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "installed") r.RunAndExpectOutput(` Name Description Version blueprint Blueprint Project v0.3.15 diff --git a/cmd/labs/list_test.go b/cmd/labs/list_test.go index 925b984a..eec942d4 100644 --- a/cmd/labs/list_test.go +++ b/cmd/labs/list_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/databricks/cli/internal" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/env" "github.com/stretchr/testify/require" ) @@ -12,7 +12,7 @@ import ( func TestListingWorks(t *testing.T) { ctx := context.Background() ctx = env.WithUserHomeDir(ctx, "project/testdata/installed-in-home") - c := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "list") + c := testcli.NewRunnerWithContext(t, ctx, "labs", "list") stdout, _, err := c.Run() require.NoError(t, err) require.Contains(t, stdout.String(), "ucx") diff --git a/cmd/labs/project/command_test.go b/cmd/labs/project/command_test.go index 20021879..f6326b2d 100644 --- a/cmd/labs/project/command_test.go +++ b/cmd/labs/project/command_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/databricks/cli/internal" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/python" "github.com/databricks/databricks-sdk-go" @@ -30,7 +30,7 @@ func devEnvContext(t *testing.T) context.Context { func TestRunningBlueprintEcho(t *testing.T) { ctx := devEnvContext(t) - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "blueprint", "echo") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "blueprint", "echo") var out echoOut r.RunAndParseJSON(&out) assert.Equal(t, "echo", out.Command) @@ -41,14 +41,14 @@ func TestRunningBlueprintEcho(t *testing.T) { func TestRunningBlueprintEchoProfileWrongOverride(t *testing.T) { ctx := devEnvContext(t) - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "blueprint", "echo", "--profile", "workspace-profile") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "blueprint", "echo", "--profile", "workspace-profile") _, _, err := r.Run() assert.ErrorIs(t, err, databricks.ErrNotAccountClient) } func TestRunningCommand(t *testing.T) { ctx := devEnvContext(t) - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "blueprint", "foo") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "blueprint", "foo") r.WithStdin() defer r.CloseStdin() @@ -60,7 +60,7 @@ func TestRunningCommand(t *testing.T) { func TestRenderingTable(t *testing.T) { ctx := devEnvContext(t) - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "blueprint", "table") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "blueprint", "table") r.RunAndExpectOutput(` Key Value First Second diff --git a/cmd/labs/project/installer_test.go b/cmd/labs/project/installer_test.go index 63c984bc..367daccb 100644 --- a/cmd/labs/project/installer_test.go +++ b/cmd/labs/project/installer_test.go @@ -19,7 +19,7 @@ import ( "github.com/databricks/cli/cmd/labs/github" "github.com/databricks/cli/cmd/labs/project" - "github.com/databricks/cli/internal" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/process" "github.com/databricks/cli/libs/python" @@ -236,7 +236,7 @@ func TestInstallerWorksForReleases(t *testing.T) { // │ │ │ └── site-packages // │ │ │ ├── ... // │ │ │ ├── distutils-precedence.pth - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "install", "blueprint", "--debug") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "install", "blueprint", "--debug") r.RunAndExpectOutput("setting up important infrastructure") } @@ -356,7 +356,7 @@ account_id = abc // └── databrickslabs-blueprint-releases.json // `databricks labs install .` means "verify this installer i'm developing does work" - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "install", ".") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "install", ".") r.WithStdin() defer r.CloseStdin() @@ -426,7 +426,7 @@ func TestUpgraderWorksForReleases(t *testing.T) { ctx = env.Set(ctx, "DATABRICKS_CLUSTER_ID", "installer-cluster") ctx = env.Set(ctx, "DATABRICKS_WAREHOUSE_ID", "installer-warehouse") - r := internal.NewCobraTestRunnerWithContext(t, ctx, "labs", "upgrade", "blueprint") + r := testcli.NewRunnerWithContext(t, ctx, "labs", "upgrade", "blueprint") r.RunAndExpectOutput("setting up important infrastructure") // Check if the stub was called with the 'python -m pip install' command diff --git a/internal/alerts_test.go b/internal/alerts_test.go index ebb7a209..d7be16d8 100644 --- a/internal/alerts_test.go +++ b/internal/alerts_test.go @@ -3,6 +3,7 @@ package internal import ( "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/stretchr/testify/assert" ) @@ -10,6 +11,6 @@ import ( func TestAccAlertsCreateErrWhenNoArguments(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - _, _, err := RequireErrorRun(t, "alerts-legacy", "create") + _, _, err := testcli.RequireErrorRun(t, "alerts-legacy", "create") assert.Equal(t, "please provide command input in JSON format by specifying the --json flag", err.Error()) } diff --git a/internal/api_test.go b/internal/api_test.go index fbbc5b07..1c7312dd 100644 --- a/internal/api_test.go +++ b/internal/api_test.go @@ -11,13 +11,14 @@ import ( "github.com/stretchr/testify/require" _ "github.com/databricks/cli/cmd/api" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" ) func TestAccApiGet(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - stdout, _ := RequireSuccessfulRun(t, "api", "get", "/api/2.0/preview/scim/v2/Me") + stdout, _ := testcli.RequireSuccessfulRun(t, "api", "get", "/api/2.0/preview/scim/v2/Me") // Deserialize SCIM API response. var out map[string]any @@ -44,11 +45,11 @@ func TestAccApiPost(t *testing.T) { // Post to mkdir { - RequireSuccessfulRun(t, "api", "post", "--json=@"+requestPath, "/api/2.0/dbfs/mkdirs") + testcli.RequireSuccessfulRun(t, "api", "post", "--json=@"+requestPath, "/api/2.0/dbfs/mkdirs") } // Post to delete { - RequireSuccessfulRun(t, "api", "post", "--json=@"+requestPath, "/api/2.0/dbfs/delete") + testcli.RequireSuccessfulRun(t, "api", "post", "--json=@"+requestPath, "/api/2.0/dbfs/delete") } } diff --git a/internal/auth_describe_test.go b/internal/auth_describe_test.go index dfbc95a1..e0b335c5 100644 --- a/internal/auth_describe_test.go +++ b/internal/auth_describe_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go" "github.com/stretchr/testify/require" @@ -13,7 +14,7 @@ import ( func TestAuthDescribeSuccess(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - stdout, _ := RequireSuccessfulRun(t, "auth", "describe") + stdout, _ := testcli.RequireSuccessfulRun(t, "auth", "describe") outStr := stdout.String() w, err := databricks.NewWorkspaceClient(&databricks.Config{}) @@ -34,7 +35,7 @@ func TestAuthDescribeSuccess(t *testing.T) { func TestAuthDescribeFailure(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - stdout, _ := RequireSuccessfulRun(t, "auth", "describe", "--profile", "nonexistent") + stdout, _ := testcli.RequireSuccessfulRun(t, "auth", "describe", "--profile", "nonexistent") outStr := stdout.String() require.NotEmpty(t, outStr) diff --git a/internal/bundle/artifacts_test.go b/internal/bundle/artifacts_test.go index d0773df0..8d4b0ae1 100644 --- a/internal/bundle/artifacts_test.go +++ b/internal/bundle/artifacts_test.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/internal" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/compute" @@ -257,7 +258,7 @@ func TestAccUploadArtifactFileToVolumeThatDoesNotExist(t *testing.T) { require.NoError(t, err) t.Setenv("BUNDLE_ROOT", bundleRoot) - stdout, stderr, err := internal.RequireErrorRun(t, "bundle", "deploy") + stdout, stderr, err := testcli.RequireErrorRun(t, "bundle", "deploy") assert.Error(t, err) assert.Equal(t, fmt.Sprintf(`Error: volume /Volumes/main/%s/doesnotexist does not exist: Not Found @@ -294,7 +295,7 @@ func TestAccUploadArtifactToVolumeNotYetDeployed(t *testing.T) { require.NoError(t, err) t.Setenv("BUNDLE_ROOT", bundleRoot) - stdout, stderr, err := internal.RequireErrorRun(t, "bundle", "deploy") + stdout, stderr, err := testcli.RequireErrorRun(t, "bundle", "deploy") assert.Error(t, err) assert.Equal(t, fmt.Sprintf(`Error: volume /Volumes/main/%s/my_volume does not exist: Not Found diff --git a/internal/bundle/bind_resource_test.go b/internal/bundle/bind_resource_test.go index c6bafdfc..0d545878 100644 --- a/internal/bundle/bind_resource_test.go +++ b/internal/bundle/bind_resource_test.go @@ -8,6 +8,7 @@ import ( "github.com/databricks/cli/internal" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -39,7 +40,7 @@ func TestAccBindJobToExistingJob(t *testing.T) { }) t.Setenv("BUNDLE_ROOT", bundleRoot) - c := internal.NewCobraTestRunner(t, "bundle", "deployment", "bind", "foo", fmt.Sprint(jobId), "--auto-approve") + c := testcli.NewRunner(t, "bundle", "deployment", "bind", "foo", fmt.Sprint(jobId), "--auto-approve") _, _, err = c.Run() require.NoError(t, err) @@ -61,7 +62,7 @@ func TestAccBindJobToExistingJob(t *testing.T) { require.Equal(t, job.Settings.Name, fmt.Sprintf("test-job-basic-%s", uniqueId)) require.Contains(t, job.Settings.Tasks[0].SparkPythonTask.PythonFile, "hello_world.py") - c = internal.NewCobraTestRunner(t, "bundle", "deployment", "unbind", "foo") + c = testcli.NewRunner(t, "bundle", "deployment", "unbind", "foo") _, _, err = c.Run() require.NoError(t, err) @@ -107,7 +108,7 @@ func TestAccAbortBind(t *testing.T) { // Bind should fail because prompting is not possible. t.Setenv("BUNDLE_ROOT", bundleRoot) t.Setenv("TERM", "dumb") - c := internal.NewCobraTestRunner(t, "bundle", "deployment", "bind", "foo", fmt.Sprint(jobId)) + c := testcli.NewRunner(t, "bundle", "deployment", "bind", "foo", fmt.Sprint(jobId)) // Expect error suggesting to use --auto-approve _, _, err = c.Run() @@ -157,7 +158,7 @@ func TestAccGenerateAndBind(t *testing.T) { }) t.Setenv("BUNDLE_ROOT", bundleRoot) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "generate", "job", + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "generate", "job", "--key", "test_job_key", "--existing-job-id", fmt.Sprint(jobId), "--config-dir", filepath.Join(bundleRoot, "resources"), @@ -173,7 +174,7 @@ func TestAccGenerateAndBind(t *testing.T) { require.Len(t, matches, 1) - c = internal.NewCobraTestRunner(t, "bundle", "deployment", "bind", "test_job_key", fmt.Sprint(jobId), "--auto-approve") + c = testcli.NewRunner(t, "bundle", "deployment", "bind", "test_job_key", fmt.Sprint(jobId), "--auto-approve") _, _, err = c.Run() require.NoError(t, err) diff --git a/internal/bundle/deploy_test.go b/internal/bundle/deploy_test.go index 283b05f7..244cfecc 100644 --- a/internal/bundle/deploy_test.go +++ b/internal/bundle/deploy_test.go @@ -13,6 +13,7 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/internal" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/env" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" @@ -120,7 +121,7 @@ func TestAccBundleDeployUcSchemaFailsWithoutAutoApprove(t *testing.T) { // Redeploy the bundle t.Setenv("BUNDLE_ROOT", bundleRoot) t.Setenv("TERM", "dumb") - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock") stdout, stderr, err := c.Run() assert.EqualError(t, err, root.ErrAlreadyPrinted.Error()) @@ -164,7 +165,7 @@ func TestAccBundlePipelineDeleteWithoutAutoApprove(t *testing.T) { // Redeploy the bundle. Expect it to fail because deleting the pipeline requires --auto-approve. t.Setenv("BUNDLE_ROOT", bundleRoot) t.Setenv("TERM", "dumb") - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock") stdout, stderr, err := c.Run() assert.EqualError(t, err, root.ErrAlreadyPrinted.Error()) @@ -203,7 +204,7 @@ func TestAccBundlePipelineRecreateWithoutAutoApprove(t *testing.T) { // Redeploy the bundle, pointing the DLT pipeline to a different UC catalog. t.Setenv("BUNDLE_ROOT", bundleRoot) t.Setenv("TERM", "dumb") - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock", "--var=\"catalog=whatever\"") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock", "--var=\"catalog=whatever\"") stdout, stderr, err := c.Run() assert.EqualError(t, err, root.ErrAlreadyPrinted.Error()) @@ -284,7 +285,7 @@ func TestAccDeployUcVolume(t *testing.T) { // Recreation of the volume without --auto-approve should fail since prompting is not possible t.Setenv("TERM", "dumb") t.Setenv("BUNDLE_ROOT", bundleRoot) - stdout, stderr, err := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--var=schema_name=${resources.schemas.schema2.name}").Run() + stdout, stderr, err := testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--var=schema_name=${resources.schemas.schema2.name}").Run() assert.Error(t, err) assert.Contains(t, stderr.String(), `This action will result in the deletion or recreation of the following volumes. For managed volumes, the files stored in the volume are also deleted from your @@ -296,7 +297,7 @@ is removed from the catalog, but the underlying files are not deleted: // Successfully recreate the volume with --auto-approve t.Setenv("TERM", "dumb") t.Setenv("BUNDLE_ROOT", bundleRoot) - _, _, err = internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--var=schema_name=${resources.schemas.schema2.name}", "--auto-approve").Run() + _, _, err = testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--var=schema_name=${resources.schemas.schema2.name}", "--auto-approve").Run() assert.NoError(t, err) // Assert the volume is updated successfully diff --git a/internal/bundle/generate_job_test.go b/internal/bundle/generate_job_test.go index 7fd265fd..9e2f927a 100644 --- a/internal/bundle/generate_job_test.go +++ b/internal/bundle/generate_job_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/internal" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" @@ -36,7 +37,7 @@ func TestAccGenerateFromExistingJobAndDeploy(t *testing.T) { }) t.Setenv("BUNDLE_ROOT", bundleRoot) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "generate", "job", + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "generate", "job", "--existing-job-id", fmt.Sprint(jobId), "--config-dir", filepath.Join(bundleRoot, "resources"), "--source-dir", filepath.Join(bundleRoot, "src")) diff --git a/internal/bundle/generate_pipeline_test.go b/internal/bundle/generate_pipeline_test.go index 589cb2a6..194f6d30 100644 --- a/internal/bundle/generate_pipeline_test.go +++ b/internal/bundle/generate_pipeline_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/internal" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" @@ -35,7 +36,7 @@ func TestAccGenerateFromExistingPipelineAndDeploy(t *testing.T) { }) t.Setenv("BUNDLE_ROOT", bundleRoot) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "generate", "pipeline", + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "generate", "pipeline", "--existing-pipeline-id", fmt.Sprint(pipelineId), "--config-dir", filepath.Join(bundleRoot, "resources"), "--source-dir", filepath.Join(bundleRoot, "src")) diff --git a/internal/bundle/helpers.go b/internal/bundle/helpers.go index d06a2a72..0512de87 100644 --- a/internal/bundle/helpers.go +++ b/internal/bundle/helpers.go @@ -12,7 +12,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/cmd/root" - "github.com/databricks/cli/internal" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" @@ -65,7 +65,7 @@ func writeConfigFile(t testutil.TestingT, config map[string]any) (string, error) func validateBundle(t testutil.TestingT, ctx context.Context, path string) ([]byte, error) { ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "validate", "--output", "json") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "validate", "--output", "json") stdout, _, err := c.Run() return stdout.Bytes(), err } @@ -85,7 +85,7 @@ func unmarshalConfig(t testutil.TestingT, data []byte) *bundle.Bundle { func deployBundle(t testutil.TestingT, ctx context.Context, path string) error { ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock", "--auto-approve") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "deploy", "--force-lock", "--auto-approve") _, _, err := c.Run() return err } @@ -93,7 +93,7 @@ func deployBundle(t testutil.TestingT, ctx context.Context, path string) error { func deployBundleWithArgs(t testutil.TestingT, ctx context.Context, path string, args ...string) (string, string, error) { ctx = env.Set(ctx, "BUNDLE_ROOT", path) args = append([]string{"bundle", "deploy"}, args...) - c := internal.NewCobraTestRunnerWithContext(t, ctx, args...) + c := testcli.NewRunnerWithContext(t, ctx, args...) stdout, stderr, err := c.Run() return stdout.String(), stderr.String(), err } @@ -102,7 +102,7 @@ func deployBundleWithFlags(t testutil.TestingT, ctx context.Context, path string ctx = env.Set(ctx, "BUNDLE_ROOT", path) args := []string{"bundle", "deploy", "--force-lock"} args = append(args, flags...) - c := internal.NewCobraTestRunnerWithContext(t, ctx, args...) + c := testcli.NewRunnerWithContext(t, ctx, args...) _, _, err := c.Run() return err } @@ -111,7 +111,7 @@ func runResource(t testutil.TestingT, ctx context.Context, path, key string) (st ctx = env.Set(ctx, "BUNDLE_ROOT", path) ctx = cmdio.NewContext(ctx, cmdio.Default()) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "run", key) + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "run", key) stdout, _, err := c.Run() return stdout.String(), err } @@ -123,14 +123,14 @@ func runResourceWithParams(t testutil.TestingT, ctx context.Context, path, key s args := make([]string, 0) args = append(args, "bundle", "run", key) args = append(args, params...) - c := internal.NewCobraTestRunnerWithContext(t, ctx, args...) + c := testcli.NewRunnerWithContext(t, ctx, args...) stdout, _, err := c.Run() return stdout.String(), err } func destroyBundle(t testutil.TestingT, ctx context.Context, path string) error { ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := internal.NewCobraTestRunnerWithContext(t, ctx, "bundle", "destroy", "--auto-approve") + c := testcli.NewRunnerWithContext(t, ctx, "bundle", "destroy", "--auto-approve") _, _, err := c.Run() return err } diff --git a/internal/clusters_test.go b/internal/clusters_test.go index 9828d240..cc1b2aa0 100644 --- a/internal/clusters_test.go +++ b/internal/clusters_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go/listing" "github.com/databricks/databricks-sdk-go/service/compute" @@ -16,7 +17,7 @@ import ( func TestAccClustersList(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - stdout, stderr := RequireSuccessfulRun(t, "clusters", "list") + stdout, stderr := testcli.RequireSuccessfulRun(t, "clusters", "list") outStr := stdout.String() assert.Contains(t, outStr, "ID") assert.Contains(t, outStr, "Name") @@ -32,14 +33,14 @@ func TestAccClustersGet(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) clusterId := findValidClusterID(t) - stdout, stderr := RequireSuccessfulRun(t, "clusters", "get", clusterId) + stdout, stderr := testcli.RequireSuccessfulRun(t, "clusters", "get", clusterId) outStr := stdout.String() assert.Contains(t, outStr, fmt.Sprintf(`"cluster_id":"%s"`, clusterId)) assert.Equal(t, "", stderr.String()) } func TestClusterCreateErrorWhenNoArguments(t *testing.T) { - _, _, err := RequireErrorRun(t, "clusters", "create") + _, _, err := testcli.RequireErrorRun(t, "clusters", "create") assert.Contains(t, err.Error(), "accepts 1 arg(s), received 0") } diff --git a/internal/completer_test.go b/internal/completer_test.go index b2c93688..52a1f6c0 100644 --- a/internal/completer_test.go +++ b/internal/completer_test.go @@ -7,6 +7,7 @@ import ( "testing" _ "github.com/databricks/cli/cmd/fs" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/filer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,7 +22,7 @@ func TestAccFsCompletion(t *testing.T) { f, tmpDir := setupDbfsFiler(t) setupCompletionFile(t, f) - stdout, _ := RequireSuccessfulRun(t, "__complete", "fs", "ls", tmpDir+"/") + stdout, _ := testcli.RequireSuccessfulRun(t, "__complete", "fs", "ls", tmpDir+"/") expectedOutput := fmt.Sprintf("%s/dir1/\n:2\n", tmpDir) assert.Equal(t, expectedOutput, stdout.String()) } diff --git a/internal/fs_cat_test.go b/internal/fs_cat_test.go index c625d885..1d9a0dbe 100644 --- a/internal/fs_cat_test.go +++ b/internal/fs_cat_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" @@ -27,7 +28,7 @@ func TestAccFsCat(t *testing.T) { err := f.Write(context.Background(), "hello.txt", strings.NewReader("abcd"), filer.CreateParentDirectories) require.NoError(t, err) - stdout, stderr := RequireSuccessfulRun(t, "fs", "cat", path.Join(tmpDir, "hello.txt")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "cat", path.Join(tmpDir, "hello.txt")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "abcd", stdout.String()) }) @@ -47,7 +48,7 @@ func TestAccFsCatOnADir(t *testing.T) { err := f.Mkdir(context.Background(), "dir1") require.NoError(t, err) - _, _, err = RequireErrorRun(t, "fs", "cat", path.Join(tmpDir, "dir1")) + _, _, err = testcli.RequireErrorRun(t, "fs", "cat", path.Join(tmpDir, "dir1")) assert.ErrorAs(t, err, &filer.NotAFile{}) }) } @@ -64,7 +65,7 @@ func TestAccFsCatOnNonExistentFile(t *testing.T) { _, tmpDir := tc.setupFiler(t) - _, _, err := RequireErrorRun(t, "fs", "cat", path.Join(tmpDir, "non-existent-file")) + _, _, err := testcli.RequireErrorRun(t, "fs", "cat", path.Join(tmpDir, "non-existent-file")) assert.ErrorIs(t, err, fs.ErrNotExist) }) } @@ -73,7 +74,7 @@ func TestAccFsCatOnNonExistentFile(t *testing.T) { func TestAccFsCatForDbfsInvalidScheme(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - _, _, err := RequireErrorRun(t, "fs", "cat", "dab:/non-existent-file") + _, _, err := testcli.RequireErrorRun(t, "fs", "cat", "dab:/non-existent-file") assert.ErrorContains(t, err, "invalid scheme: dab") } @@ -92,6 +93,6 @@ func TestAccFsCatDoesNotSupportOutputModeJson(t *testing.T) { err = f.Write(ctx, "hello.txt", strings.NewReader("abc")) require.NoError(t, err) - _, _, err = RequireErrorRun(t, "fs", "cat", "dbfs:"+path.Join(tmpDir, "hello.txt"), "--output=json") + _, _, err = testcli.RequireErrorRun(t, "fs", "cat", "dbfs:"+path.Join(tmpDir, "hello.txt"), "--output=json") assert.ErrorContains(t, err, "json output not supported") } diff --git a/internal/fs_cp_test.go b/internal/fs_cp_test.go index 41c2a44b..16318d5c 100644 --- a/internal/fs_cp_test.go +++ b/internal/fs_cp_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/stretchr/testify/assert" @@ -134,7 +135,7 @@ func TestAccFsCpDir(t *testing.T) { targetFiler, targetDir := tc.setupTarget(t) setupSourceDir(t, context.Background(), sourceFiler) - RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive") + testcli.RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive") assertTargetDir(t, context.Background(), targetFiler) }) @@ -154,7 +155,7 @@ func TestAccFsCpFileToFile(t *testing.T) { targetFiler, targetDir := tc.setupTarget(t) setupSourceFile(t, context.Background(), sourceFiler) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "foo.txt"), path.Join(targetDir, "bar.txt")) + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "foo.txt"), path.Join(targetDir, "bar.txt")) assertTargetFile(t, context.Background(), targetFiler, "bar.txt") }) @@ -174,7 +175,7 @@ func TestAccFsCpFileToDir(t *testing.T) { targetFiler, targetDir := tc.setupTarget(t) setupSourceFile(t, context.Background(), sourceFiler) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "foo.txt"), targetDir) + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "foo.txt"), targetDir) assertTargetFile(t, context.Background(), targetFiler, "foo.txt") }) @@ -193,7 +194,7 @@ func TestAccFsCpFileToDirForWindowsPaths(t *testing.T) { windowsPath := filepath.Join(filepath.FromSlash(sourceDir), "foo.txt") - RequireSuccessfulRun(t, "fs", "cp", windowsPath, targetDir) + testcli.RequireSuccessfulRun(t, "fs", "cp", windowsPath, targetDir) assertTargetFile(t, ctx, targetFiler, "foo.txt") } @@ -214,7 +215,7 @@ func TestAccFsCpDirToDirFileNotOverwritten(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/hello.txt", strings.NewReader("this should not be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive") + testcli.RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive") assertFileContent(t, context.Background(), targetFiler, "a/b/c/hello.txt", "this should not be overwritten") assertFileContent(t, context.Background(), targetFiler, "query.sql", "SELECT 1") assertFileContent(t, context.Background(), targetFiler, "pyNb.py", "# Databricks notebook source\nprint(123)") @@ -239,7 +240,7 @@ func TestAccFsCpFileToDirFileNotOverwritten(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/hello.txt", strings.NewReader("this should not be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c")) + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c")) assertFileContent(t, context.Background(), targetFiler, "a/b/c/hello.txt", "this should not be overwritten") }) } @@ -262,7 +263,7 @@ func TestAccFsCpFileToFileFileNotOverwritten(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/dontoverwrite.txt", strings.NewReader("this should not be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c/dontoverwrite.txt")) + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c/dontoverwrite.txt")) assertFileContent(t, context.Background(), targetFiler, "a/b/c/dontoverwrite.txt", "this should not be overwritten") }) } @@ -285,7 +286,7 @@ func TestAccFsCpDirToDirWithOverwriteFlag(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/hello.txt", strings.NewReader("this should be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive", "--overwrite") + testcli.RequireSuccessfulRun(t, "fs", "cp", sourceDir, targetDir, "--recursive", "--overwrite") assertTargetDir(t, context.Background(), targetFiler) }) } @@ -308,7 +309,7 @@ func TestAccFsCpFileToFileWithOverwriteFlag(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/overwritten.txt", strings.NewReader("this should be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c/overwritten.txt"), "--overwrite") + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c/overwritten.txt"), "--overwrite") assertFileContent(t, context.Background(), targetFiler, "a/b/c/overwritten.txt", "hello, world\n") }) } @@ -331,7 +332,7 @@ func TestAccFsCpFileToDirWithOverwriteFlag(t *testing.T) { err := targetFiler.Write(context.Background(), "a/b/c/hello.txt", strings.NewReader("this should be overwritten"), filer.CreateParentDirectories) require.NoError(t, err) - RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c"), "--overwrite") + testcli.RequireSuccessfulRun(t, "fs", "cp", path.Join(sourceDir, "a/b/c/hello.txt"), path.Join(targetDir, "a/b/c"), "--overwrite") assertFileContent(t, context.Background(), targetFiler, "a/b/c/hello.txt", "hello, world\n") }) } @@ -348,7 +349,7 @@ func TestAccFsCpErrorsWhenSourceIsDirWithoutRecursiveFlag(t *testing.T) { _, tmpDir := tc.setupFiler(t) - _, _, err := RequireErrorRun(t, "fs", "cp", path.Join(tmpDir), path.Join(tmpDir, "foobar")) + _, _, err := testcli.RequireErrorRun(t, "fs", "cp", path.Join(tmpDir), path.Join(tmpDir, "foobar")) r := regexp.MustCompile("source path .* is a directory. Please specify the --recursive flag") assert.Regexp(t, r, err.Error()) }) @@ -358,7 +359,7 @@ func TestAccFsCpErrorsWhenSourceIsDirWithoutRecursiveFlag(t *testing.T) { func TestAccFsCpErrorsOnInvalidScheme(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - _, _, err := RequireErrorRun(t, "fs", "cp", "dbfs:/a", "https:/b") + _, _, err := testcli.RequireErrorRun(t, "fs", "cp", "dbfs:/a", "https:/b") assert.Equal(t, "invalid scheme: https", err.Error()) } @@ -379,7 +380,7 @@ func TestAccFsCpSourceIsDirectoryButTargetIsFile(t *testing.T) { err := targetFiler.Write(context.Background(), "my_target", strings.NewReader("I'll block any attempts to recursively copy"), filer.CreateParentDirectories) require.NoError(t, err) - _, _, err = RequireErrorRun(t, "fs", "cp", sourceDir, path.Join(targetDir, "my_target"), "--recursive") + _, _, err = testcli.RequireErrorRun(t, "fs", "cp", sourceDir, path.Join(targetDir, "my_target"), "--recursive") assert.Error(t, err) }) } diff --git a/internal/fs_ls_test.go b/internal/fs_ls_test.go index 999d9afe..d56fc8bb 100644 --- a/internal/fs_ls_test.go +++ b/internal/fs_ls_test.go @@ -10,6 +10,7 @@ import ( "testing" _ "github.com/databricks/cli/cmd/fs" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/stretchr/testify/assert" @@ -51,7 +52,7 @@ func TestAccFsLs(t *testing.T) { f, tmpDir := tc.setupFiler(t) setupLsFiles(t, f) - stdout, stderr := RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json") + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json") assert.Equal(t, "", stderr.String()) var parsedStdout []map[string]any @@ -84,7 +85,7 @@ func TestAccFsLsWithAbsolutePaths(t *testing.T) { f, tmpDir := tc.setupFiler(t) setupLsFiles(t, f) - stdout, stderr := RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json", "--absolute") + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json", "--absolute") assert.Equal(t, "", stderr.String()) var parsedStdout []map[string]any @@ -116,7 +117,7 @@ func TestAccFsLsOnFile(t *testing.T) { f, tmpDir := tc.setupFiler(t) setupLsFiles(t, f) - _, _, err := RequireErrorRun(t, "fs", "ls", path.Join(tmpDir, "a", "hello.txt"), "--output=json") + _, _, err := testcli.RequireErrorRun(t, "fs", "ls", path.Join(tmpDir, "a", "hello.txt"), "--output=json") assert.Regexp(t, regexp.MustCompile("not a directory: .*/a/hello.txt"), err.Error()) assert.ErrorAs(t, err, &filer.NotADirectory{}) }) @@ -134,7 +135,7 @@ func TestAccFsLsOnEmptyDir(t *testing.T) { _, tmpDir := tc.setupFiler(t) - stdout, stderr := RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json") + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "ls", tmpDir, "--output=json") assert.Equal(t, "", stderr.String()) var parsedStdout []map[string]any err := json.Unmarshal(stdout.Bytes(), &parsedStdout) @@ -157,7 +158,7 @@ func TestAccFsLsForNonexistingDir(t *testing.T) { _, tmpDir := tc.setupFiler(t) - _, _, err := RequireErrorRun(t, "fs", "ls", path.Join(tmpDir, "nonexistent"), "--output=json") + _, _, err := testcli.RequireErrorRun(t, "fs", "ls", path.Join(tmpDir, "nonexistent"), "--output=json") assert.ErrorIs(t, err, fs.ErrNotExist) assert.Regexp(t, regexp.MustCompile("no such directory: .*/nonexistent"), err.Error()) }) @@ -169,6 +170,6 @@ func TestAccFsLsWithoutScheme(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - _, _, err := RequireErrorRun(t, "fs", "ls", "/path-without-a-dbfs-scheme", "--output=json") + _, _, err := testcli.RequireErrorRun(t, "fs", "ls", "/path-without-a-dbfs-scheme", "--output=json") assert.ErrorIs(t, err, fs.ErrNotExist) } diff --git a/internal/fs_mkdir_test.go b/internal/fs_mkdir_test.go index 9191f614..197f5d4b 100644 --- a/internal/fs_mkdir_test.go +++ b/internal/fs_mkdir_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/filer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,7 +25,7 @@ func TestAccFsMkdir(t *testing.T) { f, tmpDir := tc.setupFiler(t) // create directory "a" - stdout, stderr := RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) @@ -49,7 +50,7 @@ func TestAccFsMkdirCreatesIntermediateDirectories(t *testing.T) { f, tmpDir := tc.setupFiler(t) // create directory "a/b/c" - stdout, stderr := RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a", "b", "c")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a", "b", "c")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) @@ -90,7 +91,7 @@ func TestAccFsMkdirWhenDirectoryAlreadyExists(t *testing.T) { require.NoError(t, err) // assert run is successful without any errors - stdout, stderr := RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "mkdir", path.Join(tmpDir, "a")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) }) @@ -110,7 +111,7 @@ func TestAccFsMkdirWhenFileExistsAtPath(t *testing.T) { require.NoError(t, err) // assert mkdir fails - _, _, err = RequireErrorRun(t, "fs", "mkdir", path.Join(tmpDir, "hello")) + _, _, err = testcli.RequireErrorRun(t, "fs", "mkdir", path.Join(tmpDir, "hello")) // Different cloud providers or cloud configurations return different errors. regex := regexp.MustCompile(`(^|: )Path is a file: .*$|(^|: )Cannot create directory .* because .* is an existing file\.$|(^|: )mkdirs\(hadoopPath: .*, permission: rwxrwxrwx\): failed$|(^|: )"The specified path already exists.".*$`) @@ -127,7 +128,7 @@ func TestAccFsMkdirWhenFileExistsAtPath(t *testing.T) { require.NoError(t, err) // assert mkdir fails - _, _, err = RequireErrorRun(t, "fs", "mkdir", path.Join(tmpDir, "hello")) + _, _, err = testcli.RequireErrorRun(t, "fs", "mkdir", path.Join(tmpDir, "hello")) assert.ErrorAs(t, err, &filer.FileAlreadyExistsError{}) }) diff --git a/internal/fs_rm_test.go b/internal/fs_rm_test.go index d49813a3..1af779fd 100644 --- a/internal/fs_rm_test.go +++ b/internal/fs_rm_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/libs/filer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -31,7 +32,7 @@ func TestAccFsRmFile(t *testing.T) { assert.NoError(t, err) // Run rm command - stdout, stderr := RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "hello.txt")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "hello.txt")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) @@ -61,7 +62,7 @@ func TestAccFsRmEmptyDir(t *testing.T) { assert.NoError(t, err) // Run rm command - stdout, stderr := RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "a")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "a")) assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) @@ -95,7 +96,7 @@ func TestAccFsRmNonEmptyDirectory(t *testing.T) { assert.NoError(t, err) // Run rm command - _, _, err = RequireErrorRun(t, "fs", "rm", path.Join(tmpDir, "a")) + _, _, err = testcli.RequireErrorRun(t, "fs", "rm", path.Join(tmpDir, "a")) assert.ErrorIs(t, err, fs.ErrInvalid) assert.ErrorAs(t, err, &filer.DirectoryNotEmptyError{}) }) @@ -114,7 +115,7 @@ func TestAccFsRmForNonExistentFile(t *testing.T) { _, tmpDir := tc.setupFiler(t) // Expect error if file does not exist - _, _, err := RequireErrorRun(t, "fs", "rm", path.Join(tmpDir, "does-not-exist")) + _, _, err := testcli.RequireErrorRun(t, "fs", "rm", path.Join(tmpDir, "does-not-exist")) assert.ErrorIs(t, err, fs.ErrNotExist) }) } @@ -144,7 +145,7 @@ func TestAccFsRmDirRecursively(t *testing.T) { assert.NoError(t, err) // Run rm command - stdout, stderr := RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "a"), "--recursive") + stdout, stderr := testcli.RequireSuccessfulRun(t, "fs", "rm", path.Join(tmpDir, "a"), "--recursive") assert.Equal(t, "", stderr.String()) assert.Equal(t, "", stdout.String()) diff --git a/internal/git_fetch_test.go b/internal/git_fetch_test.go index 797bc2e3..e692970c 100644 --- a/internal/git_fetch_test.go +++ b/internal/git_fetch_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/dbr" "github.com/databricks/cli/libs/git" @@ -44,9 +45,9 @@ func TestAccFetchRepositoryInfoAPI_FromRepo(t *testing.T) { require.NoError(t, err) targetPath := testutil.RandomName(path.Join("/Workspace/Users", me.UserName, "/testing-clone-bundle-examples-")) - stdout, stderr := RequireSuccessfulRun(t, "repos", "create", examplesRepoUrl, examplesRepoProvider, "--path", targetPath) + stdout, stderr := testcli.RequireSuccessfulRun(t, "repos", "create", examplesRepoUrl, examplesRepoProvider, "--path", targetPath) t.Cleanup(func() { - RequireSuccessfulRun(t, "repos", "delete", targetPath) + testcli.RequireSuccessfulRun(t, "repos", "delete", targetPath) }) assert.Empty(t, stderr.String()) @@ -71,9 +72,9 @@ func TestAccFetchRepositoryInfoAPI_FromNonRepo(t *testing.T) { require.NoError(t, err) rootPath := testutil.RandomName(path.Join("/Workspace/Users", me.UserName, "testing-nonrepo-")) - _, stderr := RequireSuccessfulRun(t, "workspace", "mkdirs", path.Join(rootPath, "a/b/c")) + _, stderr := testcli.RequireSuccessfulRun(t, "workspace", "mkdirs", path.Join(rootPath, "a/b/c")) t.Cleanup(func() { - RequireSuccessfulRun(t, "workspace", "delete", "--recursive", rootPath) + testcli.RequireSuccessfulRun(t, "workspace", "delete", "--recursive", rootPath) }) assert.Empty(t, stderr.String()) diff --git a/internal/helpers.go b/internal/helpers.go index b67ea889..f6cb199e 100644 --- a/internal/helpers.go +++ b/internal/helpers.go @@ -1,30 +1,18 @@ package internal import ( - "bufio" - "bytes" "context" - "encoding/json" "errors" "fmt" - "io" "net/http" "os" "path" "path/filepath" - "reflect" "strings" - "sync" - "time" - "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/internal/acc" "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/flags" - "github.com/databricks/cli/cmd" - _ "github.com/databricks/cli/cmd/version" - "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" @@ -33,305 +21,9 @@ import ( "github.com/databricks/databricks-sdk-go/service/files" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/workspace" - "github.com/spf13/cobra" - "github.com/spf13/pflag" "github.com/stretchr/testify/require" - - _ "github.com/databricks/cli/cmd/workspace" ) -// Helper for running the root command in the background. -// It ensures that the background goroutine terminates upon -// test completion through cancelling the command context. -type cobraTestRunner struct { - testutil.TestingT - - args []string - stdout bytes.Buffer - stderr bytes.Buffer - stdinR *io.PipeReader - stdinW *io.PipeWriter - - ctx context.Context - - // Line-by-line output. - // Background goroutines populate these channels by reading from stdout/stderr pipes. - stdoutLines <-chan string - stderrLines <-chan string - - errch <-chan error -} - -func consumeLines(ctx context.Context, wg *sync.WaitGroup, r io.Reader) <-chan string { - ch := make(chan string, 30000) - wg.Add(1) - go func() { - defer close(ch) - defer wg.Done() - scanner := bufio.NewScanner(r) - for scanner.Scan() { - // We expect to be able to always send these lines into the channel. - // If we can't, it means the channel is full and likely there is a problem - // in either the test or the code under test. - select { - case <-ctx.Done(): - return - case ch <- scanner.Text(): - continue - default: - panic("line buffer is full") - } - } - }() - return ch -} - -func (t *cobraTestRunner) registerFlagCleanup(c *cobra.Command) { - // Find target command that will be run. Example: if the command run is `databricks fs cp`, - // target command corresponds to `cp` - targetCmd, _, err := c.Find(t.args) - if err != nil && strings.HasPrefix(err.Error(), "unknown command") { - // even if command is unknown, we can proceed - require.NotNil(t, targetCmd) - } else { - require.NoError(t, err) - } - - // Force initialization of default flags. - // These are initialized by cobra at execution time and would otherwise - // not be cleaned up by the cleanup function below. - targetCmd.InitDefaultHelpFlag() - targetCmd.InitDefaultVersionFlag() - - // Restore flag values to their original value on test completion. - targetCmd.Flags().VisitAll(func(f *pflag.Flag) { - v := reflect.ValueOf(f.Value) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - // Store copy of the current flag value. - reset := reflect.New(v.Type()).Elem() - reset.Set(v) - t.Cleanup(func() { - v.Set(reset) - }) - }) -} - -// Like [cobraTestRunner.Eventually], but more specific -func (t *cobraTestRunner) WaitForTextPrinted(text string, timeout time.Duration) { - t.Eventually(func() bool { - currentStdout := t.stdout.String() - return strings.Contains(currentStdout, text) - }, timeout, 50*time.Millisecond) -} - -func (t *cobraTestRunner) WaitForOutput(text string, timeout time.Duration) { - require.Eventually(t, func() bool { - currentStdout := t.stdout.String() - currentErrout := t.stderr.String() - return strings.Contains(currentStdout, text) || strings.Contains(currentErrout, text) - }, timeout, 50*time.Millisecond) -} - -func (t *cobraTestRunner) WithStdin() { - reader, writer := io.Pipe() - t.stdinR = reader - t.stdinW = writer -} - -func (t *cobraTestRunner) CloseStdin() { - if t.stdinW == nil { - panic("no standard input configured") - } - t.stdinW.Close() -} - -func (t *cobraTestRunner) SendText(text string) { - if t.stdinW == nil { - panic("no standard input configured") - } - _, err := t.stdinW.Write([]byte(text + "\n")) - if err != nil { - panic("Failed to to write to t.stdinW") - } -} - -func (t *cobraTestRunner) RunBackground() { - var stdoutR, stderrR io.Reader - var stdoutW, stderrW io.WriteCloser - stdoutR, stdoutW = io.Pipe() - stderrR, stderrW = io.Pipe() - ctx := cmdio.NewContext(t.ctx, &cmdio.Logger{ - Mode: flags.ModeAppend, - Reader: bufio.Reader{}, - Writer: stderrW, - }) - - cli := cmd.New(ctx) - cli.SetOut(stdoutW) - cli.SetErr(stderrW) - cli.SetArgs(t.args) - if t.stdinW != nil { - cli.SetIn(t.stdinR) - } - - // Register cleanup function to restore flags to their original values - // once test has been executed. This is needed because flag values reside - // in a global singleton data-structure, and thus subsequent tests might - // otherwise interfere with each other - t.registerFlagCleanup(cli) - - errch := make(chan error) - ctx, cancel := context.WithCancel(ctx) - - // Tee stdout/stderr to buffers. - stdoutR = io.TeeReader(stdoutR, &t.stdout) - stderrR = io.TeeReader(stderrR, &t.stderr) - - // Consume stdout/stderr line-by-line. - var wg sync.WaitGroup - t.stdoutLines = consumeLines(ctx, &wg, stdoutR) - t.stderrLines = consumeLines(ctx, &wg, stderrR) - - // Run command in background. - go func() { - err := root.Execute(ctx, cli) - if err != nil { - t.Logf("Error running command: %s", err) - } - - // Close pipes to signal EOF. - stdoutW.Close() - stderrW.Close() - - // Wait for the [consumeLines] routines to finish now that - // the pipes they're reading from have closed. - wg.Wait() - - if t.stdout.Len() > 0 { - // Make a copy of the buffer such that it remains "unread". - scanner := bufio.NewScanner(bytes.NewBuffer(t.stdout.Bytes())) - for scanner.Scan() { - t.Logf("[databricks stdout]: %s", scanner.Text()) - } - } - - if t.stderr.Len() > 0 { - // Make a copy of the buffer such that it remains "unread". - scanner := bufio.NewScanner(bytes.NewBuffer(t.stderr.Bytes())) - for scanner.Scan() { - t.Logf("[databricks stderr]: %s", scanner.Text()) - } - } - - // Reset context on command for the next test. - // These commands are globals so we have to clean up to the best of our ability after each run. - // See https://github.com/spf13/cobra/blob/a6f198b635c4b18fff81930c40d464904e55b161/command.go#L1062-L1066 - //nolint:staticcheck // cobra sets the context and doesn't clear it - cli.SetContext(nil) - - // Make caller aware of error. - errch <- err - close(errch) - }() - - // Ensure command terminates upon test completion (success or failure). - t.Cleanup(func() { - // Signal termination of command. - cancel() - // Wait for goroutine to finish. - <-errch - }) - - t.errch = errch -} - -func (t *cobraTestRunner) Run() (bytes.Buffer, bytes.Buffer, error) { - t.RunBackground() - err := <-t.errch - return t.stdout, t.stderr, err -} - -// Like [require.Eventually] but errors if the underlying command has failed. -func (c *cobraTestRunner) Eventually(condition func() bool, waitFor, tick time.Duration, msgAndArgs ...any) { - ch := make(chan bool, 1) - - timer := time.NewTimer(waitFor) - defer timer.Stop() - - ticker := time.NewTicker(tick) - defer ticker.Stop() - - // Kick off condition check immediately. - go func() { ch <- condition() }() - - for tick := ticker.C; ; { - select { - case err := <-c.errch: - require.Fail(c, "Command failed", err) - return - case <-timer.C: - require.Fail(c, "Condition never satisfied", msgAndArgs...) - return - case <-tick: - tick = nil - go func() { ch <- condition() }() - case v := <-ch: - if v { - return - } - tick = ticker.C - } - } -} - -func (t *cobraTestRunner) RunAndExpectOutput(heredoc string) { - stdout, _, err := t.Run() - require.NoError(t, err) - require.Equal(t, cmdio.Heredoc(heredoc), strings.TrimSpace(stdout.String())) -} - -func (t *cobraTestRunner) RunAndParseJSON(v any) { - stdout, _, err := t.Run() - require.NoError(t, err) - err = json.Unmarshal(stdout.Bytes(), &v) - require.NoError(t, err) -} - -func NewCobraTestRunner(t testutil.TestingT, args ...string) *cobraTestRunner { - return &cobraTestRunner{ - TestingT: t, - - ctx: context.Background(), - args: args, - } -} - -func NewCobraTestRunnerWithContext(t testutil.TestingT, ctx context.Context, args ...string) *cobraTestRunner { - return &cobraTestRunner{ - TestingT: t, - - ctx: ctx, - args: args, - } -} - -func RequireSuccessfulRun(t testutil.TestingT, args ...string) (bytes.Buffer, bytes.Buffer) { - t.Logf("run args: [%s]", strings.Join(args, ", ")) - c := NewCobraTestRunner(t, args...) - stdout, stderr, err := c.Run() - require.NoError(t, err) - return stdout, stderr -} - -func RequireErrorRun(t testutil.TestingT, args ...string) (bytes.Buffer, bytes.Buffer, error) { - c := NewCobraTestRunner(t, args...) - stdout, stderr, err := c.Run() - require.Error(t, err) - return stdout, stderr, err -} - func GenerateNotebookTasks(notebookPath string, versions []string, nodeTypeId string) []jobs.SubmitTask { tasks := make([]jobs.SubmitTask, 0) for i := 0; i < len(versions); i++ { diff --git a/internal/init_test.go b/internal/init_test.go index e219e265..e2d96b3e 100644 --- a/internal/init_test.go +++ b/internal/init_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/iamutil" "github.com/databricks/databricks-sdk-go" @@ -22,7 +23,7 @@ func TestAccBundleInitErrorOnUnknownFields(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) tmpDir := t.TempDir() - _, _, err := RequireErrorRun(t, "bundle", "init", "./testdata/init/field-does-not-exist", "--output-dir", tmpDir) + _, _, err := testcli.RequireErrorRun(t, "bundle", "init", "./testdata/init/field-does-not-exist", "--output-dir", tmpDir) assert.EqualError(t, err, "failed to compute file content for bar.tmpl. variable \"does_not_exist\" not defined") } @@ -63,7 +64,7 @@ func TestAccBundleInitOnMlopsStacks(t *testing.T) { // Run bundle init assert.NoFileExists(t, filepath.Join(tmpDir2, "repo_name", projectName, "README.md")) - RequireSuccessfulRun(t, "bundle", "init", "mlops-stacks", "--output-dir", tmpDir2, "--config-file", filepath.Join(tmpDir1, "config.json")) + testcli.RequireSuccessfulRun(t, "bundle", "init", "mlops-stacks", "--output-dir", tmpDir2, "--config-file", filepath.Join(tmpDir1, "config.json")) // Assert that the README.md file was created assert.FileExists(t, filepath.Join(tmpDir2, "repo_name", projectName, "README.md")) @@ -71,17 +72,17 @@ func TestAccBundleInitOnMlopsStacks(t *testing.T) { // Validate the stack testutil.Chdir(t, filepath.Join(tmpDir2, "repo_name", projectName)) - RequireSuccessfulRun(t, "bundle", "validate") + testcli.RequireSuccessfulRun(t, "bundle", "validate") // Deploy the stack - RequireSuccessfulRun(t, "bundle", "deploy") + testcli.RequireSuccessfulRun(t, "bundle", "deploy") t.Cleanup(func() { // Delete the stack - RequireSuccessfulRun(t, "bundle", "destroy", "--auto-approve") + testcli.RequireSuccessfulRun(t, "bundle", "destroy", "--auto-approve") }) // Get summary of the bundle deployment - stdout, _ := RequireSuccessfulRun(t, "bundle", "summary", "--output", "json") + stdout, _ := testcli.RequireSuccessfulRun(t, "bundle", "summary", "--output", "json") summary := &config.Root{} err = json.Unmarshal(stdout.Bytes(), summary) require.NoError(t, err) @@ -159,7 +160,7 @@ func TestAccBundleInitHelpers(t *testing.T) { require.NoError(t, err) // Run bundle init. - RequireSuccessfulRun(t, "bundle", "init", tmpDir, "--output-dir", tmpDir2) + testcli.RequireSuccessfulRun(t, "bundle", "init", tmpDir, "--output-dir", tmpDir2) // Assert that the helper function was correctly computed. assertLocalFileContents(t, filepath.Join(tmpDir2, "foo.txt"), test.expected) diff --git a/internal/jobs_test.go b/internal/jobs_test.go index e9132cea..2781323e 100644 --- a/internal/jobs_test.go +++ b/internal/jobs_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,10 +18,10 @@ func TestAccCreateJob(t *testing.T) { if env != "azure" { t.Skipf("Not running test on cloud %s", env) } - stdout, stderr := RequireSuccessfulRun(t, "jobs", "create", "--json", "@testjsons/create_job_without_workers.json", "--log-level=debug") + stdout, stderr := testcli.RequireSuccessfulRun(t, "jobs", "create", "--json", "@testjsons/create_job_without_workers.json", "--log-level=debug") assert.Empty(t, stderr.String()) var output map[string]int err := json.Unmarshal(stdout.Bytes(), &output) require.NoError(t, err) - RequireSuccessfulRun(t, "jobs", "delete", fmt.Sprint(output["job_id"]), "--log-level=debug") + testcli.RequireSuccessfulRun(t, "jobs", "delete", fmt.Sprint(output["job_id"]), "--log-level=debug") } diff --git a/internal/repos_test.go b/internal/repos_test.go index b681ff7e..a89217fe 100644 --- a/internal/repos_test.go +++ b/internal/repos_test.go @@ -6,6 +6,7 @@ import ( "strconv" "testing" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" @@ -52,7 +53,7 @@ func TestAccReposCreateWithProvider(t *testing.T) { require.NoError(t, err) repoPath := synthesizeTemporaryRepoPath(t, w, ctx) - _, stderr := RequireSuccessfulRun(t, "repos", "create", repoUrl, "gitHub", "--path", repoPath) + _, stderr := testcli.RequireSuccessfulRun(t, "repos", "create", repoUrl, "gitHub", "--path", repoPath) assert.Equal(t, "", stderr.String()) // Confirm the repo was created. @@ -69,7 +70,7 @@ func TestAccReposCreateWithoutProvider(t *testing.T) { require.NoError(t, err) repoPath := synthesizeTemporaryRepoPath(t, w, ctx) - _, stderr := RequireSuccessfulRun(t, "repos", "create", repoUrl, "--path", repoPath) + _, stderr := testcli.RequireSuccessfulRun(t, "repos", "create", repoUrl, "--path", repoPath) assert.Equal(t, "", stderr.String()) // Confirm the repo was created. @@ -88,22 +89,22 @@ func TestAccReposGet(t *testing.T) { repoId, repoPath := createTemporaryRepo(t, w, ctx) // Get by ID - byIdOutput, stderr := RequireSuccessfulRun(t, "repos", "get", strconv.FormatInt(repoId, 10), "--output=json") + byIdOutput, stderr := testcli.RequireSuccessfulRun(t, "repos", "get", strconv.FormatInt(repoId, 10), "--output=json") assert.Equal(t, "", stderr.String()) // Get by path - byPathOutput, stderr := RequireSuccessfulRun(t, "repos", "get", repoPath, "--output=json") + byPathOutput, stderr := testcli.RequireSuccessfulRun(t, "repos", "get", repoPath, "--output=json") assert.Equal(t, "", stderr.String()) // Output should be the same assert.Equal(t, byIdOutput.String(), byPathOutput.String()) // Get by path fails - _, stderr, err = RequireErrorRun(t, "repos", "get", repoPath+"-doesntexist", "--output=json") + _, stderr, err = testcli.RequireErrorRun(t, "repos", "get", repoPath+"-doesntexist", "--output=json") assert.ErrorContains(t, err, "failed to look up repo") // Get by path resolves to something other than a repo - _, stderr, err = RequireErrorRun(t, "repos", "get", "/Repos", "--output=json") + _, stderr, err = testcli.RequireErrorRun(t, "repos", "get", "/Repos", "--output=json") assert.ErrorContains(t, err, "is not a repo") } @@ -117,11 +118,11 @@ func TestAccReposUpdate(t *testing.T) { repoId, repoPath := createTemporaryRepo(t, w, ctx) // Update by ID - byIdOutput, stderr := RequireSuccessfulRun(t, "repos", "update", strconv.FormatInt(repoId, 10), "--branch", "ide") + byIdOutput, stderr := testcli.RequireSuccessfulRun(t, "repos", "update", strconv.FormatInt(repoId, 10), "--branch", "ide") assert.Equal(t, "", stderr.String()) // Update by path - byPathOutput, stderr := RequireSuccessfulRun(t, "repos", "update", repoPath, "--branch", "ide") + byPathOutput, stderr := testcli.RequireSuccessfulRun(t, "repos", "update", repoPath, "--branch", "ide") assert.Equal(t, "", stderr.String()) // Output should be the same @@ -138,7 +139,7 @@ func TestAccReposDeleteByID(t *testing.T) { repoId, _ := createTemporaryRepo(t, w, ctx) // Delete by ID - stdout, stderr := RequireSuccessfulRun(t, "repos", "delete", strconv.FormatInt(repoId, 10)) + stdout, stderr := testcli.RequireSuccessfulRun(t, "repos", "delete", strconv.FormatInt(repoId, 10)) assert.Equal(t, "", stdout.String()) assert.Equal(t, "", stderr.String()) @@ -157,7 +158,7 @@ func TestAccReposDeleteByPath(t *testing.T) { repoId, repoPath := createTemporaryRepo(t, w, ctx) // Delete by path - stdout, stderr := RequireSuccessfulRun(t, "repos", "delete", repoPath) + stdout, stderr := testcli.RequireSuccessfulRun(t, "repos", "delete", repoPath) assert.Equal(t, "", stdout.String()) assert.Equal(t, "", stderr.String()) diff --git a/internal/secrets_test.go b/internal/secrets_test.go index d780654b..0ad9cece 100644 --- a/internal/secrets_test.go +++ b/internal/secrets_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/stretchr/testify/assert" @@ -14,7 +15,7 @@ import ( ) func TestSecretsCreateScopeErrWhenNoArguments(t *testing.T) { - _, _, err := RequireErrorRun(t, "secrets", "create-scope") + _, _, err := testcli.RequireErrorRun(t, "secrets", "create-scope") assert.Contains(t, err.Error(), "accepts 1 arg(s), received 0") } @@ -68,7 +69,7 @@ func TestAccSecretsPutSecretStringValue(tt *testing.T) { key := "test-key" value := "test-value\nwith-newlines\n" - stdout, stderr := RequireSuccessfulRun(t, "secrets", "put-secret", scope, key, "--string-value", value) + stdout, stderr := testcli.RequireSuccessfulRun(t, "secrets", "put-secret", scope, key, "--string-value", value) assert.Empty(t, stdout) assert.Empty(t, stderr) @@ -82,7 +83,7 @@ func TestAccSecretsPutSecretBytesValue(tt *testing.T) { key := "test-key" value := []byte{0x00, 0x01, 0x02, 0x03} - stdout, stderr := RequireSuccessfulRun(t, "secrets", "put-secret", scope, key, "--bytes-value", string(value)) + stdout, stderr := testcli.RequireSuccessfulRun(t, "secrets", "put-secret", scope, key, "--bytes-value", string(value)) assert.Empty(t, stdout) assert.Empty(t, stderr) diff --git a/internal/storage_credentials_test.go b/internal/storage_credentials_test.go index e937a9e7..b7fe1aa5 100644 --- a/internal/storage_credentials_test.go +++ b/internal/storage_credentials_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/stretchr/testify/assert" ) @@ -14,7 +15,7 @@ func TestAccStorageCredentialsListRendersResponse(t *testing.T) { // Check if metastore is assigned for the workspace, otherwise test will fail t.Log(testutil.GetEnvOrSkipTest(t, "TEST_METASTORE_ID")) - stdout, stderr := RequireSuccessfulRun(t, "storage-credentials", "list") + stdout, stderr := testcli.RequireSuccessfulRun(t, "storage-credentials", "list") assert.NotEmpty(t, stdout) assert.Empty(t, stderr) } diff --git a/internal/sync_test.go b/internal/sync_test.go index 3699ce37..848c60ce 100644 --- a/internal/sync_test.go +++ b/internal/sync_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - _ "github.com/databricks/cli/cmd/sync" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/databricks/cli/libs/sync" @@ -64,7 +64,7 @@ func setupRepo(t *testing.T, wsc *databricks.WorkspaceClient, ctx context.Contex type syncTest struct { t *testing.T - c *cobraTestRunner + c *testcli.Runner w *databricks.WorkspaceClient f filer.Filer localRoot string @@ -89,7 +89,7 @@ func setupSyncTest(t *testing.T, args ...string) *syncTest { "json", }, args...) - c := NewCobraTestRunner(t, args...) + c := testcli.NewRunner(t, args...) c.RunBackground() return &syncTest{ @@ -110,7 +110,7 @@ func (s *syncTest) waitForCompletionMarker() { select { case <-ctx.Done(): s.t.Fatal("timed out waiting for sync to complete") - case line := <-s.c.stdoutLines: + case line := <-s.c.StdoutLines: var event sync.EventBase err := json.Unmarshal([]byte(line), &event) require.NoError(s.t, err) diff --git a/internal/testcli/README.md b/internal/testcli/README.md new file mode 100644 index 00000000..b37ae3bc --- /dev/null +++ b/internal/testcli/README.md @@ -0,0 +1,7 @@ +# testcli + +This package provides a way to run the CLI from tests as if it were a separate process. +By running the CLI inline we can still set breakpoints and step through execution. + +It transitively imports pretty much this entire repository, which is why we +intentionally keep this package _separate_ from `testutil`. diff --git a/internal/testcli/runner.go b/internal/testcli/runner.go new file mode 100644 index 00000000..55e3faca --- /dev/null +++ b/internal/testcli/runner.go @@ -0,0 +1,315 @@ +package testcli + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "io" + "reflect" + "strings" + "sync" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/databricks/cli/cmd" + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/internal/testutil" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" +) + +// Helper for running the root command in the background. +// It ensures that the background goroutine terminates upon +// test completion through cancelling the command context. +type Runner struct { + testutil.TestingT + + args []string + stdout bytes.Buffer + stderr bytes.Buffer + stdinR *io.PipeReader + stdinW *io.PipeWriter + + ctx context.Context + + // Line-by-line output. + // Background goroutines populate these channels by reading from stdout/stderr pipes. + StdoutLines <-chan string + StderrLines <-chan string + + errch <-chan error +} + +func consumeLines(ctx context.Context, wg *sync.WaitGroup, r io.Reader) <-chan string { + ch := make(chan string, 30000) + wg.Add(1) + go func() { + defer close(ch) + defer wg.Done() + scanner := bufio.NewScanner(r) + for scanner.Scan() { + // We expect to be able to always send these lines into the channel. + // If we can't, it means the channel is full and likely there is a problem + // in either the test or the code under test. + select { + case <-ctx.Done(): + return + case ch <- scanner.Text(): + continue + default: + panic("line buffer is full") + } + } + }() + return ch +} + +func (r *Runner) registerFlagCleanup(c *cobra.Command) { + // Find target command that will be run. Example: if the command run is `databricks fs cp`, + // target command corresponds to `cp` + targetCmd, _, err := c.Find(r.args) + if err != nil && strings.HasPrefix(err.Error(), "unknown command") { + // even if command is unknown, we can proceed + require.NotNil(r, targetCmd) + } else { + require.NoError(r, err) + } + + // Force initialization of default flags. + // These are initialized by cobra at execution time and would otherwise + // not be cleaned up by the cleanup function below. + targetCmd.InitDefaultHelpFlag() + targetCmd.InitDefaultVersionFlag() + + // Restore flag values to their original value on test completion. + targetCmd.Flags().VisitAll(func(f *pflag.Flag) { + v := reflect.ValueOf(f.Value) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + // Store copy of the current flag value. + reset := reflect.New(v.Type()).Elem() + reset.Set(v) + r.Cleanup(func() { + v.Set(reset) + }) + }) +} + +// Like [Runner.Eventually], but more specific +func (r *Runner) WaitForTextPrinted(text string, timeout time.Duration) { + r.Eventually(func() bool { + currentStdout := r.stdout.String() + return strings.Contains(currentStdout, text) + }, timeout, 50*time.Millisecond) +} + +func (r *Runner) WaitForOutput(text string, timeout time.Duration) { + require.Eventually(r, func() bool { + currentStdout := r.stdout.String() + currentErrout := r.stderr.String() + return strings.Contains(currentStdout, text) || strings.Contains(currentErrout, text) + }, timeout, 50*time.Millisecond) +} + +func (r *Runner) WithStdin() { + reader, writer := io.Pipe() + r.stdinR = reader + r.stdinW = writer +} + +func (r *Runner) CloseStdin() { + if r.stdinW == nil { + panic("no standard input configured") + } + r.stdinW.Close() +} + +func (r *Runner) SendText(text string) { + if r.stdinW == nil { + panic("no standard input configured") + } + _, err := r.stdinW.Write([]byte(text + "\n")) + if err != nil { + panic("Failed to to write to t.stdinW") + } +} + +func (r *Runner) RunBackground() { + var stdoutR, stderrR io.Reader + var stdoutW, stderrW io.WriteCloser + stdoutR, stdoutW = io.Pipe() + stderrR, stderrW = io.Pipe() + ctx := cmdio.NewContext(r.ctx, &cmdio.Logger{ + Mode: flags.ModeAppend, + Reader: bufio.Reader{}, + Writer: stderrW, + }) + + cli := cmd.New(ctx) + cli.SetOut(stdoutW) + cli.SetErr(stderrW) + cli.SetArgs(r.args) + if r.stdinW != nil { + cli.SetIn(r.stdinR) + } + + // Register cleanup function to restore flags to their original values + // once test has been executed. This is needed because flag values reside + // in a global singleton data-structure, and thus subsequent tests might + // otherwise interfere with each other + r.registerFlagCleanup(cli) + + errch := make(chan error) + ctx, cancel := context.WithCancel(ctx) + + // Tee stdout/stderr to buffers. + stdoutR = io.TeeReader(stdoutR, &r.stdout) + stderrR = io.TeeReader(stderrR, &r.stderr) + + // Consume stdout/stderr line-by-line. + var wg sync.WaitGroup + r.StdoutLines = consumeLines(ctx, &wg, stdoutR) + r.StderrLines = consumeLines(ctx, &wg, stderrR) + + // Run command in background. + go func() { + err := root.Execute(ctx, cli) + if err != nil { + r.Logf("Error running command: %s", err) + } + + // Close pipes to signal EOF. + stdoutW.Close() + stderrW.Close() + + // Wait for the [consumeLines] routines to finish now that + // the pipes they're reading from have closed. + wg.Wait() + + if r.stdout.Len() > 0 { + // Make a copy of the buffer such that it remains "unread". + scanner := bufio.NewScanner(bytes.NewBuffer(r.stdout.Bytes())) + for scanner.Scan() { + r.Logf("[databricks stdout]: %s", scanner.Text()) + } + } + + if r.stderr.Len() > 0 { + // Make a copy of the buffer such that it remains "unread". + scanner := bufio.NewScanner(bytes.NewBuffer(r.stderr.Bytes())) + for scanner.Scan() { + r.Logf("[databricks stderr]: %s", scanner.Text()) + } + } + + // Reset context on command for the next test. + // These commands are globals so we have to clean up to the best of our ability after each run. + // See https://github.com/spf13/cobra/blob/a6f198b635c4b18fff81930c40d464904e55b161/command.go#L1062-L1066 + //nolint:staticcheck // cobra sets the context and doesn't clear it + cli.SetContext(nil) + + // Make caller aware of error. + errch <- err + close(errch) + }() + + // Ensure command terminates upon test completion (success or failure). + r.Cleanup(func() { + // Signal termination of command. + cancel() + // Wait for goroutine to finish. + <-errch + }) + + r.errch = errch +} + +func (r *Runner) Run() (bytes.Buffer, bytes.Buffer, error) { + r.RunBackground() + err := <-r.errch + return r.stdout, r.stderr, err +} + +// Like [require.Eventually] but errors if the underlying command has failed. +func (r *Runner) Eventually(condition func() bool, waitFor, tick time.Duration, msgAndArgs ...any) { + ch := make(chan bool, 1) + + timer := time.NewTimer(waitFor) + defer timer.Stop() + + ticker := time.NewTicker(tick) + defer ticker.Stop() + + // Kick off condition check immediately. + go func() { ch <- condition() }() + + for tick := ticker.C; ; { + select { + case err := <-r.errch: + require.Fail(r, "Command failed", err) + return + case <-timer.C: + require.Fail(r, "Condition never satisfied", msgAndArgs...) + return + case <-tick: + tick = nil + go func() { ch <- condition() }() + case v := <-ch: + if v { + return + } + tick = ticker.C + } + } +} + +func (r *Runner) RunAndExpectOutput(heredoc string) { + stdout, _, err := r.Run() + require.NoError(r, err) + require.Equal(r, cmdio.Heredoc(heredoc), strings.TrimSpace(stdout.String())) +} + +func (r *Runner) RunAndParseJSON(v any) { + stdout, _, err := r.Run() + require.NoError(r, err) + err = json.Unmarshal(stdout.Bytes(), &v) + require.NoError(r, err) +} + +func NewRunner(t testutil.TestingT, args ...string) *Runner { + return &Runner{ + TestingT: t, + + ctx: context.Background(), + args: args, + } +} + +func NewRunnerWithContext(t testutil.TestingT, ctx context.Context, args ...string) *Runner { + return &Runner{ + TestingT: t, + + ctx: ctx, + args: args, + } +} + +func RequireSuccessfulRun(t testutil.TestingT, args ...string) (bytes.Buffer, bytes.Buffer) { + t.Logf("run args: [%s]", strings.Join(args, ", ")) + r := NewRunner(t, args...) + stdout, stderr, err := r.Run() + require.NoError(t, err) + return stdout, stderr +} + +func RequireErrorRun(t testutil.TestingT, args ...string) (bytes.Buffer, bytes.Buffer, error) { + r := NewRunner(t, args...) + stdout, stderr, err := r.Run() + require.Error(t, err) + return stdout, stderr, err +} diff --git a/internal/unknown_command_test.go b/internal/unknown_command_test.go index 62b84027..96cb4fa5 100644 --- a/internal/unknown_command_test.go +++ b/internal/unknown_command_test.go @@ -3,11 +3,12 @@ package internal import ( "testing" + "github.com/databricks/cli/internal/testcli" assert "github.com/databricks/cli/libs/dyn/dynassert" ) func TestUnknownCommand(t *testing.T) { - stdout, stderr, err := RequireErrorRun(t, "unknown-command") + stdout, stderr, err := testcli.RequireErrorRun(t, "unknown-command") assert.Error(t, err, "unknown command", `unknown command "unknown-command" for "databricks"`) assert.Equal(t, "", stdout.String()) diff --git a/internal/version_test.go b/internal/version_test.go index 7dba63cd..5c5c3a10 100644 --- a/internal/version_test.go +++ b/internal/version_test.go @@ -6,31 +6,32 @@ import ( "testing" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/internal/testcli" "github.com/stretchr/testify/assert" ) var expectedVersion = fmt.Sprintf("Databricks CLI v%s\n", build.GetInfo().Version) func TestVersionFlagShort(t *testing.T) { - stdout, stderr := RequireSuccessfulRun(t, "-v") + stdout, stderr := testcli.RequireSuccessfulRun(t, "-v") assert.Equal(t, expectedVersion, stdout.String()) assert.Equal(t, "", stderr.String()) } func TestVersionFlagLong(t *testing.T) { - stdout, stderr := RequireSuccessfulRun(t, "--version") + stdout, stderr := testcli.RequireSuccessfulRun(t, "--version") assert.Equal(t, expectedVersion, stdout.String()) assert.Equal(t, "", stderr.String()) } func TestVersionCommand(t *testing.T) { - stdout, stderr := RequireSuccessfulRun(t, "version") + stdout, stderr := testcli.RequireSuccessfulRun(t, "version") assert.Equal(t, expectedVersion, stdout.String()) assert.Equal(t, "", stderr.String()) } func TestVersionCommandWithJSONOutput(t *testing.T) { - stdout, stderr := RequireSuccessfulRun(t, "version", "--output", "json") + stdout, stderr := testcli.RequireSuccessfulRun(t, "version", "--output", "json") assert.NotEmpty(t, stdout.String()) assert.Equal(t, "", stderr.String()) diff --git a/internal/workspace_test.go b/internal/workspace_test.go index e159fc1d..7ff9b0bb 100644 --- a/internal/workspace_test.go +++ b/internal/workspace_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/databricks/cli/internal/acc" + "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" @@ -23,7 +24,7 @@ import ( func TestAccWorkspaceList(t *testing.T) { t.Log(testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - stdout, stderr := RequireSuccessfulRun(t, "workspace", "list", "/") + stdout, stderr := testcli.RequireSuccessfulRun(t, "workspace", "list", "/") outStr := stdout.String() assert.Contains(t, outStr, "ID") assert.Contains(t, outStr, "Type") @@ -33,12 +34,12 @@ func TestAccWorkspaceList(t *testing.T) { } func TestWorkpaceListErrorWhenNoArguments(t *testing.T) { - _, _, err := RequireErrorRun(t, "workspace", "list") + _, _, err := testcli.RequireErrorRun(t, "workspace", "list") assert.Contains(t, err.Error(), "accepts 1 arg(s), received 0") } func TestWorkpaceGetStatusErrorWhenNoArguments(t *testing.T) { - _, _, err := RequireErrorRun(t, "workspace", "get-status") + _, _, err := testcli.RequireErrorRun(t, "workspace", "get-status") assert.Contains(t, err.Error(), "accepts 1 arg(s), received 0") } @@ -57,7 +58,7 @@ func TestAccWorkpaceExportPrintsContents(t *testing.T) { require.NoError(t, err) // Run export - stdout, stderr := RequireSuccessfulRun(t, "workspace", "export", path.Join(tmpdir, "file-a")) + stdout, stderr := testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(tmpdir, "file-a")) assert.Equal(t, contents, stdout.String()) assert.Equal(t, "", stderr.String()) } @@ -125,7 +126,7 @@ func TestAccExportDir(t *testing.T) { }, "\n") // Run Export - stdout, stderr := RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir) + stdout, stderr := testcli.RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir) assert.Equal(t, expectedLogs, stdout.String()) assert.Equal(t, "", stderr.String()) @@ -153,7 +154,7 @@ func TestAccExportDirDoesNotOverwrite(t *testing.T) { require.NoError(t, err) // Run Export - RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir) + testcli.RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir) // Assert file is not overwritten assertLocalFileContents(t, filepath.Join(targetDir, "file-a"), "local content") @@ -174,7 +175,7 @@ func TestAccExportDirWithOverwriteFlag(t *testing.T) { require.NoError(t, err) // Run Export - RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir, "--overwrite") + testcli.RequireSuccessfulRun(t, "workspace", "export-dir", sourceDir, targetDir, "--overwrite") // Assert file has been overwritten assertLocalFileContents(t, filepath.Join(targetDir, "file-a"), "content from workspace") @@ -182,7 +183,7 @@ func TestAccExportDirWithOverwriteFlag(t *testing.T) { func TestAccImportDir(t *testing.T) { ctx, workspaceFiler, targetDir := setupWorkspaceImportExportTest(t) - stdout, stderr := RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir, "--log-level=debug") + stdout, stderr := testcli.RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir, "--log-level=debug") expectedLogs := strings.Join([]string{ fmt.Sprintf("Importing files from %s", "./testdata/import_dir"), @@ -223,7 +224,7 @@ func TestAccImportDirDoesNotOverwrite(t *testing.T) { assertFilerFileContents(t, ctx, workspaceFiler, "file-a", "old file") assertFilerFileContents(t, ctx, workspaceFiler, "pyNotebook", "# Databricks notebook source\nprint(\"old notebook\")") - RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir) + testcli.RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir) // Assert files are imported assertFilerFileContents(t, ctx, workspaceFiler, "a/b/c/file-b", "file-in-dir") @@ -251,7 +252,7 @@ func TestAccImportDirWithOverwriteFlag(t *testing.T) { assertFilerFileContents(t, ctx, workspaceFiler, "file-a", "old file") assertFilerFileContents(t, ctx, workspaceFiler, "pyNotebook", "# Databricks notebook source\nprint(\"old notebook\")") - RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir, "--overwrite") + testcli.RequireSuccessfulRun(t, "workspace", "import-dir", "./testdata/import_dir", targetDir, "--overwrite") // Assert files are imported assertFilerFileContents(t, ctx, workspaceFiler, "a/b/c/file-b", "file-in-dir") @@ -273,7 +274,7 @@ func TestAccExport(t *testing.T) { // Export vanilla file err = f.Write(ctx, "file-a", strings.NewReader("abc")) require.NoError(t, err) - stdout, _ := RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "file-a")) + stdout, _ := testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "file-a")) b, err := io.ReadAll(&stdout) require.NoError(t, err) assert.Equal(t, "abc", string(b)) @@ -281,13 +282,13 @@ func TestAccExport(t *testing.T) { // Export python notebook err = f.Write(ctx, "pyNotebook.py", strings.NewReader("# Databricks notebook source")) require.NoError(t, err) - stdout, _ = RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook")) + stdout, _ = testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook")) b, err = io.ReadAll(&stdout) require.NoError(t, err) assert.Equal(t, "# Databricks notebook source\n", string(b)) // Export python notebook as jupyter - stdout, _ = RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--format", "JUPYTER") + stdout, _ = testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--format", "JUPYTER") b, err = io.ReadAll(&stdout) require.NoError(t, err) assert.Contains(t, string(b), `"cells":`, "jupyter notebooks contain the cells field") @@ -303,7 +304,7 @@ func TestAccExportWithFileFlag(t *testing.T) { // Export vanilla file err = f.Write(ctx, "file-a", strings.NewReader("abc")) require.NoError(t, err) - stdout, _ := RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "file-a"), "--file", filepath.Join(localTmpDir, "file.txt")) + stdout, _ := testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "file-a"), "--file", filepath.Join(localTmpDir, "file.txt")) b, err := io.ReadAll(&stdout) require.NoError(t, err) // Expect nothing to be printed to stdout @@ -313,14 +314,14 @@ func TestAccExportWithFileFlag(t *testing.T) { // Export python notebook err = f.Write(ctx, "pyNotebook.py", strings.NewReader("# Databricks notebook source")) require.NoError(t, err) - stdout, _ = RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--file", filepath.Join(localTmpDir, "pyNb.py")) + stdout, _ = testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--file", filepath.Join(localTmpDir, "pyNb.py")) b, err = io.ReadAll(&stdout) require.NoError(t, err) assert.Equal(t, "", string(b)) assertLocalFileContents(t, filepath.Join(localTmpDir, "pyNb.py"), "# Databricks notebook source\n") // Export python notebook as jupyter - stdout, _ = RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--format", "JUPYTER", "--file", filepath.Join(localTmpDir, "jupyterNb.ipynb")) + stdout, _ = testcli.RequireSuccessfulRun(t, "workspace", "export", path.Join(sourceDir, "pyNotebook"), "--format", "JUPYTER", "--file", filepath.Join(localTmpDir, "jupyterNb.ipynb")) b, err = io.ReadAll(&stdout) require.NoError(t, err) assert.Equal(t, "", string(b)) @@ -332,13 +333,13 @@ func TestAccImportFileUsingContentFormatSource(t *testing.T) { ctx, workspaceFiler, targetDir := setupWorkspaceImportExportTest(t) // Content = `print(1)`. Uploaded as a notebook by default - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyScript"), + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyScript"), "--content", base64.StdEncoding.EncodeToString([]byte("print(1)")), "--language=PYTHON") assertFilerFileContents(t, ctx, workspaceFiler, "pyScript", "print(1)") assertWorkspaceFileType(t, ctx, workspaceFiler, "pyScript", workspace.ObjectTypeNotebook) // Import with content = `# Databricks notebook source\nprint(1)`. Uploaded as a notebook with the content just being print(1) - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyNb"), + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyNb"), "--content", base64.StdEncoding.EncodeToString([]byte("`# Databricks notebook source\nprint(1)")), "--language=PYTHON") assertFilerFileContents(t, ctx, workspaceFiler, "pyNb", "print(1)") @@ -349,19 +350,19 @@ func TestAccImportFileUsingContentFormatAuto(t *testing.T) { ctx, workspaceFiler, targetDir := setupWorkspaceImportExportTest(t) // Content = `# Databricks notebook source\nprint(1)`. Upload as file if path has no extension. - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-file"), + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-file"), "--content", base64.StdEncoding.EncodeToString([]byte("`# Databricks notebook source\nprint(1)")), "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "py-nb-as-file", "# Databricks notebook source\nprint(1)") assertWorkspaceFileType(t, ctx, workspaceFiler, "py-nb-as-file", workspace.ObjectTypeFile) // Content = `# Databricks notebook source\nprint(1)`. Upload as notebook if path has py extension - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-notebook.py"), + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-notebook.py"), "--content", base64.StdEncoding.EncodeToString([]byte("`# Databricks notebook source\nprint(1)")), "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "py-nb-as-notebook", "# Databricks notebook source\nprint(1)") assertWorkspaceFileType(t, ctx, workspaceFiler, "py-nb-as-notebook", workspace.ObjectTypeNotebook) // Content = `print(1)`. Upload as file if content is not notebook (even if path has .py extension) - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "not-a-notebook.py"), "--content", + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "not-a-notebook.py"), "--content", base64.StdEncoding.EncodeToString([]byte("print(1)")), "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "not-a-notebook.py", "print(1)") assertWorkspaceFileType(t, ctx, workspaceFiler, "not-a-notebook.py", workspace.ObjectTypeFile) @@ -369,15 +370,15 @@ func TestAccImportFileUsingContentFormatAuto(t *testing.T) { func TestAccImportFileFormatSource(t *testing.T) { ctx, workspaceFiler, targetDir := setupWorkspaceImportExportTest(t) - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyNotebook"), "--file", "./testdata/import_dir/pyNotebook.py", "--language=PYTHON") + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "pyNotebook"), "--file", "./testdata/import_dir/pyNotebook.py", "--language=PYTHON") assertFilerFileContents(t, ctx, workspaceFiler, "pyNotebook", "# Databricks notebook source\nprint(\"python\")") assertWorkspaceFileType(t, ctx, workspaceFiler, "pyNotebook", workspace.ObjectTypeNotebook) - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "scalaNotebook"), "--file", "./testdata/import_dir/scalaNotebook.scala", "--language=SCALA") + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "scalaNotebook"), "--file", "./testdata/import_dir/scalaNotebook.scala", "--language=SCALA") assertFilerFileContents(t, ctx, workspaceFiler, "scalaNotebook", "// Databricks notebook source\nprintln(\"scala\")") assertWorkspaceFileType(t, ctx, workspaceFiler, "scalaNotebook", workspace.ObjectTypeNotebook) - _, _, err := RequireErrorRun(t, "workspace", "import", path.Join(targetDir, "scalaNotebook"), "--file", "./testdata/import_dir/scalaNotebook.scala") + _, _, err := testcli.RequireErrorRun(t, "workspace", "import", path.Join(targetDir, "scalaNotebook"), "--file", "./testdata/import_dir/scalaNotebook.scala") assert.ErrorContains(t, err, "The zip file may not be valid or may be an unsupported version. Hint: Objects imported using format=SOURCE are expected to be zip encoded databricks source notebook(s) by default. Please specify a language using the --language flag if you are trying to import a single uncompressed notebook") } @@ -385,18 +386,18 @@ func TestAccImportFileFormatAuto(t *testing.T) { ctx, workspaceFiler, targetDir := setupWorkspaceImportExportTest(t) // Upload as file if path has no extension - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-file"), "--file", "./testdata/import_dir/pyNotebook.py", "--format=AUTO") + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-file"), "--file", "./testdata/import_dir/pyNotebook.py", "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "py-nb-as-file", "# Databricks notebook source") assertFilerFileContents(t, ctx, workspaceFiler, "py-nb-as-file", "print(\"python\")") assertWorkspaceFileType(t, ctx, workspaceFiler, "py-nb-as-file", workspace.ObjectTypeFile) // Upload as notebook if path has extension - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-notebook.py"), "--file", "./testdata/import_dir/pyNotebook.py", "--format=AUTO") + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "py-nb-as-notebook.py"), "--file", "./testdata/import_dir/pyNotebook.py", "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "py-nb-as-notebook", "# Databricks notebook source\nprint(\"python\")") assertWorkspaceFileType(t, ctx, workspaceFiler, "py-nb-as-notebook", workspace.ObjectTypeNotebook) // Upload as file if content is not notebook (even if path has .py extension) - RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "not-a-notebook.py"), "--file", "./testdata/import_dir/file-a", "--format=AUTO") + testcli.RequireSuccessfulRun(t, "workspace", "import", path.Join(targetDir, "not-a-notebook.py"), "--file", "./testdata/import_dir/file-a", "--format=AUTO") assertFilerFileContents(t, ctx, workspaceFiler, "not-a-notebook.py", "hello, world") assertWorkspaceFileType(t, ctx, workspaceFiler, "not-a-notebook.py", workspace.ObjectTypeFile) }