mirror of https://github.com/databricks/cli.git
591 lines
14 KiB
Go
591 lines
14 KiB
Go
package render
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/databricks/cli/bundle"
|
|
"github.com/databricks/cli/bundle/config"
|
|
"github.com/databricks/cli/bundle/config/resources"
|
|
"github.com/databricks/cli/libs/diag"
|
|
"github.com/databricks/cli/libs/dyn"
|
|
"github.com/databricks/databricks-sdk-go/service/catalog"
|
|
"github.com/databricks/databricks-sdk-go/service/iam"
|
|
"github.com/databricks/databricks-sdk-go/service/jobs"
|
|
"github.com/databricks/databricks-sdk-go/service/pipelines"
|
|
"github.com/databricks/databricks-sdk-go/service/serving"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type renderTestOutputTestCase struct {
|
|
name string
|
|
bundle *bundle.Bundle
|
|
diags diag.Diagnostics
|
|
opts RenderOptions
|
|
expected string
|
|
}
|
|
|
|
func TestRenderTextOutput(t *testing.T) {
|
|
loadingBundle := &bundle.Bundle{
|
|
Config: config.Root{
|
|
Bundle: config.Bundle{
|
|
Name: "test-bundle",
|
|
Target: "test-target",
|
|
},
|
|
},
|
|
}
|
|
|
|
testCases := []renderTestOutputTestCase{
|
|
{
|
|
name: "nil bundle and 1 error",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Summary: "failed to load xxx",
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Error: failed to load xxx\n" +
|
|
"\n" +
|
|
"Found 1 error\n",
|
|
},
|
|
{
|
|
name: "nil bundle and 1 recommendation",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Recommendation,
|
|
Summary: "recommendation",
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Recommendation: recommendation\n" +
|
|
"\n" +
|
|
"Found 1 recommendation\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 1 error",
|
|
bundle: loadingBundle,
|
|
diags: diag.Errorf("failed to load bundle"),
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Error: failed to load bundle\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 1 error\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 1 warning",
|
|
bundle: loadingBundle,
|
|
diags: diag.Warningf("failed to load bundle"),
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Warning: failed to load bundle\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 1 warning\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 2 warnings",
|
|
bundle: loadingBundle,
|
|
diags: diag.Warningf("warning (1)").Extend(diag.Warningf("warning (2)")),
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Warning: warning (1)\n" +
|
|
"\n" +
|
|
"Warning: warning (2)\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 2 warnings\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 2 errors, 1 warning and 1 recommendation with details",
|
|
bundle: loadingBundle,
|
|
diags: diag.Diagnostics{
|
|
diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: "error (1)",
|
|
Detail: "detail (1)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: "error (2)",
|
|
Detail: "detail (2)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: "warning (3)",
|
|
Detail: "detail (3)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Recommendation,
|
|
Summary: "recommendation (4)",
|
|
Detail: "detail (4)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 4, Column: 1}},
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Error: error (1)\n" +
|
|
" in foo.py:1:1\n" +
|
|
"\n" +
|
|
"detail (1)\n" +
|
|
"\n" +
|
|
"Error: error (2)\n" +
|
|
" in foo.py:2:1\n" +
|
|
"\n" +
|
|
"detail (2)\n" +
|
|
"\n" +
|
|
"Warning: warning (3)\n" +
|
|
" in foo.py:3:1\n" +
|
|
"\n" +
|
|
"detail (3)\n" +
|
|
"\n" +
|
|
"Recommendation: recommendation (4)\n" +
|
|
" in foo.py:4:1\n" +
|
|
"\n" +
|
|
"detail (4)\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 2 errors, 1 warning, and 1 recommendation\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 1 error and 1 warning",
|
|
bundle: loadingBundle,
|
|
diags: diag.Diagnostics{
|
|
diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: "error (1)",
|
|
Detail: "detail (1)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: "warning (2)",
|
|
Detail: "detail (2)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Error: error (1)\n" +
|
|
" in foo.py:1:1\n" +
|
|
"\n" +
|
|
"detail (1)\n" +
|
|
"\n" +
|
|
"Warning: warning (2)\n" +
|
|
" in foo.py:2:1\n" +
|
|
"\n" +
|
|
"detail (2)\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 1 error and 1 warning\n",
|
|
},
|
|
{
|
|
name: "bundle during 'load' and 1 errors, 2 warning and 2 recommendations with details",
|
|
bundle: loadingBundle,
|
|
diags: diag.Diagnostics{
|
|
diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: "error (1)",
|
|
Detail: "detail (1)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: "warning (2)",
|
|
Detail: "detail (2)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: "warning (3)",
|
|
Detail: "detail (3)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Recommendation,
|
|
Summary: "recommendation (4)",
|
|
Detail: "detail (4)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 4, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Recommendation,
|
|
Summary: "recommendation (5)",
|
|
Detail: "detail (5)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 5, Column: 1}},
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Error: error (1)\n" +
|
|
" in foo.py:1:1\n" +
|
|
"\n" +
|
|
"detail (1)\n" +
|
|
"\n" +
|
|
"Warning: warning (2)\n" +
|
|
" in foo.py:2:1\n" +
|
|
"\n" +
|
|
"detail (2)\n" +
|
|
"\n" +
|
|
"Warning: warning (3)\n" +
|
|
" in foo.py:3:1\n" +
|
|
"\n" +
|
|
"detail (3)\n" +
|
|
"\n" +
|
|
"Recommendation: recommendation (4)\n" +
|
|
" in foo.py:4:1\n" +
|
|
"\n" +
|
|
"detail (4)\n" +
|
|
"\n" +
|
|
"Recommendation: recommendation (5)\n" +
|
|
" in foo.py:5:1\n" +
|
|
"\n" +
|
|
"detail (5)\n" +
|
|
"\n" +
|
|
"Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"\n" +
|
|
"Found 1 error, 2 warnings, and 2 recommendations\n",
|
|
},
|
|
{
|
|
name: "bundle during 'init'",
|
|
bundle: &bundle.Bundle{
|
|
Config: config.Root{
|
|
Bundle: config.Bundle{
|
|
Name: "test-bundle",
|
|
Target: "test-target",
|
|
},
|
|
Workspace: config.Workspace{
|
|
Host: "https://localhost/",
|
|
CurrentUser: &config.User{
|
|
User: &iam.User{
|
|
UserName: "test-user",
|
|
},
|
|
},
|
|
RootPath: "/Users/test-user@databricks.com/.bundle/examples/test-target",
|
|
},
|
|
},
|
|
},
|
|
diags: nil,
|
|
opts: RenderOptions{RenderSummaryTable: true},
|
|
expected: "Name: test-bundle\n" +
|
|
"Target: test-target\n" +
|
|
"Workspace:\n" +
|
|
" Host: https://localhost/\n" +
|
|
" User: test-user\n" +
|
|
" Path: /Users/test-user@databricks.com/.bundle/examples/test-target\n" +
|
|
"\n" +
|
|
"Validation OK!\n",
|
|
},
|
|
{
|
|
name: "nil bundle without summary with 1 error, 1 warning and 1 recommendation",
|
|
bundle: nil,
|
|
diags: diag.Diagnostics{
|
|
diag.Diagnostic{
|
|
Severity: diag.Error,
|
|
Summary: "error (1)",
|
|
Detail: "detail (1)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Warning,
|
|
Summary: "warning (2)",
|
|
Detail: "detail (2)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
|
|
},
|
|
diag.Diagnostic{
|
|
Severity: diag.Recommendation,
|
|
Summary: "recommendation (3)",
|
|
Detail: "detail (3)",
|
|
Locations: []dyn.Location{{File: "foo.py", Line: 5, Column: 1}},
|
|
},
|
|
},
|
|
opts: RenderOptions{RenderSummaryTable: false},
|
|
expected: "Error: error (1)\n" +
|
|
" in foo.py:1:1\n" +
|
|
"\n" +
|
|
"detail (1)\n" +
|
|
"\n" +
|
|
"Warning: warning (2)\n" +
|
|
" in foo.py:3:1\n" +
|
|
"\n" +
|
|
"detail (2)\n" +
|
|
"\n" +
|
|
"Recommendation: recommendation (3)\n" +
|
|
" in foo.py:5:1\n" +
|
|
"\n" +
|
|
"detail (3)\n" +
|
|
"\n",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
writer := &bytes.Buffer{}
|
|
|
|
err := RenderDiagnostics(writer, tc.bundle, tc.diags, tc.opts)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tc.expected, writer.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
type renderDiagnosticsTestCase struct {
|
|
name string
|
|
diags diag.Diagnostics
|
|
expected string
|
|
}
|
|
|
|
func TestRenderDiagnostics(t *testing.T) {
|
|
bundle := &bundle.Bundle{}
|
|
|
|
testCases := []renderDiagnosticsTestCase{
|
|
{
|
|
name: "empty diagnostics",
|
|
diags: diag.Diagnostics{},
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "error with short summary",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Summary: "failed to load xxx",
|
|
},
|
|
},
|
|
expected: "Error: failed to load xxx\n\n",
|
|
},
|
|
{
|
|
name: "error with source location",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Summary: "failed to load xxx",
|
|
Detail: "'name' is required",
|
|
Locations: []dyn.Location{{
|
|
File: "foo.yaml",
|
|
Line: 1,
|
|
Column: 2}},
|
|
},
|
|
},
|
|
expected: "Error: failed to load xxx\n" +
|
|
" in foo.yaml:1:2\n\n" +
|
|
"'name' is required\n\n",
|
|
},
|
|
{
|
|
name: "error with multiple source locations",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Summary: "failed to load xxx",
|
|
Detail: "'name' is required",
|
|
Locations: []dyn.Location{
|
|
{
|
|
File: "foo.yaml",
|
|
Line: 1,
|
|
Column: 2,
|
|
},
|
|
{
|
|
File: "bar.yaml",
|
|
Line: 3,
|
|
Column: 4,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: "Error: failed to load xxx\n" +
|
|
" in foo.yaml:1:2\n" +
|
|
" bar.yaml:3:4\n\n" +
|
|
"'name' is required\n\n",
|
|
},
|
|
{
|
|
name: "error with path",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Detail: "'name' is required",
|
|
Summary: "failed to load xxx",
|
|
Paths: []dyn.Path{dyn.MustPathFromString("resources.jobs.xxx")},
|
|
},
|
|
},
|
|
expected: "Error: failed to load xxx\n" +
|
|
" at resources.jobs.xxx\n" +
|
|
"\n" +
|
|
"'name' is required\n\n",
|
|
},
|
|
{
|
|
name: "error with multiple paths",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Error,
|
|
Detail: "'name' is required",
|
|
Summary: "failed to load xxx",
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.xxx"),
|
|
dyn.MustPathFromString("resources.jobs.yyy"),
|
|
dyn.MustPathFromString("resources.jobs.zzz"),
|
|
},
|
|
},
|
|
},
|
|
expected: "Error: failed to load xxx\n" +
|
|
" at resources.jobs.xxx\n" +
|
|
" resources.jobs.yyy\n" +
|
|
" resources.jobs.zzz\n" +
|
|
"\n" +
|
|
"'name' is required\n\n",
|
|
},
|
|
{
|
|
name: "recommendation with multiple paths and locations",
|
|
diags: diag.Diagnostics{
|
|
{
|
|
Severity: diag.Recommendation,
|
|
Summary: "summary",
|
|
Detail: "detail",
|
|
Paths: []dyn.Path{
|
|
dyn.MustPathFromString("resources.jobs.xxx"),
|
|
dyn.MustPathFromString("resources.jobs.yyy"),
|
|
},
|
|
Locations: []dyn.Location{
|
|
{File: "foo.yaml", Line: 1, Column: 2},
|
|
{File: "bar.yaml", Line: 3, Column: 4},
|
|
},
|
|
},
|
|
},
|
|
expected: "Recommendation: summary\n" +
|
|
" at resources.jobs.xxx\n" +
|
|
" resources.jobs.yyy\n" +
|
|
" in foo.yaml:1:2\n" +
|
|
" bar.yaml:3:4\n\n" +
|
|
"detail\n\n",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
writer := &bytes.Buffer{}
|
|
|
|
err := renderDiagnosticsOnly(writer, bundle, tc.diags)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tc.expected, writer.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderSummaryTemplate_nilBundle(t *testing.T) {
|
|
writer := &bytes.Buffer{}
|
|
|
|
err := renderSummaryHeaderTemplate(writer, nil)
|
|
require.NoError(t, err)
|
|
|
|
io.WriteString(writer, buildTrailer(nil))
|
|
|
|
assert.Equal(t, "Validation OK!\n", writer.String())
|
|
}
|
|
|
|
func TestRenderSummary(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create a mock bundle with various resources
|
|
b := &bundle.Bundle{
|
|
Config: config.Root{
|
|
Bundle: config.Bundle{
|
|
Name: "test-bundle",
|
|
Target: "test-target",
|
|
},
|
|
Workspace: config.Workspace{
|
|
Host: "https://mycompany.databricks.com/",
|
|
},
|
|
Resources: config.Resources{
|
|
Jobs: map[string]*resources.Job{
|
|
"job1": {
|
|
ID: "1",
|
|
URL: "https://url1",
|
|
JobSettings: &jobs.JobSettings{Name: "job1-name"},
|
|
},
|
|
"job2": {
|
|
ID: "2",
|
|
URL: "https://url2",
|
|
JobSettings: &jobs.JobSettings{Name: "job2-name"},
|
|
},
|
|
},
|
|
Pipelines: map[string]*resources.Pipeline{
|
|
"pipeline2": {
|
|
ID: "4",
|
|
// no URL
|
|
PipelineSpec: &pipelines.PipelineSpec{Name: "pipeline2-name"},
|
|
},
|
|
"pipeline1": {
|
|
ID: "3",
|
|
URL: "https://url3",
|
|
PipelineSpec: &pipelines.PipelineSpec{Name: "pipeline1-name"},
|
|
},
|
|
},
|
|
Schemas: map[string]*resources.Schema{
|
|
"schema1": {
|
|
ID: "catalog.schema",
|
|
CreateSchema: &catalog.CreateSchema{
|
|
Name: "schema",
|
|
},
|
|
// no URL
|
|
},
|
|
},
|
|
ModelServingEndpoints: map[string]*resources.ModelServingEndpoint{
|
|
"endpoint1": {
|
|
ID: "7",
|
|
CreateServingEndpoint: &serving.CreateServingEndpoint{
|
|
Name: "my_serving_endpoint",
|
|
},
|
|
URL: "https://url4",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
writer := &bytes.Buffer{}
|
|
err := RenderSummary(ctx, writer, b)
|
|
require.NoError(t, err)
|
|
|
|
expectedSummary := `Name: test-bundle
|
|
Target: test-target
|
|
Workspace:
|
|
Host: https://mycompany.databricks.com/
|
|
Resources:
|
|
Jobs:
|
|
job1:
|
|
Name: job1-name
|
|
URL: https://url1
|
|
job2:
|
|
Name: job2-name
|
|
URL: https://url2
|
|
Model Serving Endpoints:
|
|
endpoint1:
|
|
Name: my_serving_endpoint
|
|
URL: https://url4
|
|
Pipelines:
|
|
pipeline1:
|
|
Name: pipeline1-name
|
|
URL: https://url3
|
|
pipeline2:
|
|
Name: pipeline2-name
|
|
URL: (not deployed)
|
|
Schemas:
|
|
schema1:
|
|
Name: schema
|
|
URL: (not deployed)
|
|
`
|
|
assert.Equal(t, expectedSummary, writer.String())
|
|
}
|