Support multiple locations for diagnostics (#1610)

## Changes
This PR changes `diag.Diagnostics` to allow including multiple locations
associated with the diagnostic message. The diagnostics that now return
multiple locations with this PR are:
1. Warning for unknown keys in config.
2. Use of experimental.run_as
3. Accidental sync.exludes that exclude all files.

## Tests
Existing unit tests pass. New unit test case to assert on error message
when multiple locations are included.

Example output:
```
➜  bundle-playground-2 ~/cli2/cli/cli bundle validate              
Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.
  at experimental.use_legacy_run_as
  in resources.yml:10:22
     databricks.yml:13:22

Name: fix run_if
Target: default
Workspace:
  User: shreyas.goenka@databricks.com
  Path: /Users/shreyas.goenka@databricks.com/.bundle/fix run_if/default

Found 1 warning
```
This commit is contained in:
shreyas-goenka 2024-07-23 22:50:11 +05:30 committed by GitHub
parent 52ca599cd5
commit 4bf88b4209
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 276 additions and 218 deletions

View File

@ -55,11 +55,17 @@ func parsePythonDiagnostics(input io.Reader) (diag.Diagnostics, error) {
return nil, fmt.Errorf("failed to parse path: %s", err)
}
var locations []dyn.Location
location := convertPythonLocation(parsedLine.Location)
if location != (dyn.Location{}) {
locations = append(locations, location)
}
diag := diag.Diagnostic{
Severity: severity,
Summary: parsedLine.Summary,
Detail: parsedLine.Detail,
Location: convertPythonLocation(parsedLine.Location),
Locations: locations,
Path: path,
}

View File

@ -39,7 +39,8 @@ func TestParsePythonDiagnostics(t *testing.T) {
{
Severity: diag.Error,
Summary: "error summary",
Location: dyn.Location{
Locations: []dyn.Location{
{
File: "src/examples/file.py",
Line: 1,
Column: 2,
@ -47,6 +48,7 @@ func TestParsePythonDiagnostics(t *testing.T) {
},
},
},
},
{
name: "short error with path",
input: `{"severity": "error", "summary": "error summary", "path": "resources.jobs.job0.name"}`,

View File

@ -97,11 +97,14 @@ func TestPythonMutator_load(t *testing.T) {
assert.Equal(t, 1, len(diags))
assert.Equal(t, "job doesn't have any tasks", diags[0].Summary)
assert.Equal(t, dyn.Location{
assert.Equal(t, []dyn.Location{
{
File: "src/examples/file.py",
Line: 10,
Column: 5,
}, diags[0].Location)
},
}, diags[0].Locations)
}
func TestPythonMutator_load_disallowed(t *testing.T) {

View File

@ -181,7 +181,7 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics {
Severity: diag.Warning,
Summary: "You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.",
Path: dyn.MustPathFromString("experimental.use_legacy_run_as"),
Location: b.Config.GetLocation("experimental.use_legacy_run_as"),
Locations: b.Config.GetLocations("experimental.use_legacy_run_as"),
},
}
}

View File

@ -524,6 +524,17 @@ func (r Root) GetLocation(path string) dyn.Location {
return v.Location()
}
// Get all locations of the configuration value at the specified path. We need both
// this function and it's singular version (GetLocation) because some diagnostics just need
// the primary location and some need all locations associated with a configuration value.
func (r Root) GetLocations(path string) []dyn.Location {
v, err := dyn.Get(r.value, path)
if err != nil {
return []dyn.Location{}
}
return v.Locations()
}
// Value returns the dynamic configuration value of the root object. This value
// is the source of truth and is kept in sync with values in the typed configuration.
func (r Root) Value() dyn.Value {

View File

@ -45,7 +45,9 @@ func (v *filesToSync) Apply(ctx context.Context, rb bundle.ReadOnlyBundle) diag.
diags = diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: "There are no files to sync, please check your .gitignore and sync.exclude configuration",
Location: loc.Location(),
// Show all locations where sync.exclude is defined, since merging
// sync.exclude is additive.
Locations: loc.Locations(),
Path: loc.Path(),
})
}

View File

@ -6,6 +6,7 @@ import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn"
)
func JobClusterKeyDefined() bundle.ReadOnlyMutator {
@ -41,7 +42,10 @@ func (v *jobClusterKeyDefined) Apply(ctx context.Context, rb bundle.ReadOnlyBund
diags = diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("job_cluster_key %s is not defined", task.JobClusterKey),
Location: loc.Location(),
// Show only the location where the job_cluster_key is defined.
// Other associated locations are not relevant since they are
// overridden during merging.
Locations: []dyn.Location{loc.Location()},
Path: loc.Path(),
})
}

View File

@ -20,6 +20,10 @@ func (l location) Location() dyn.Location {
return l.rb.Config().GetLocation(l.path)
}
func (l location) Locations() []dyn.Location {
return l.rb.Config().GetLocations(l.path)
}
func (l location) Path() dyn.Path {
return dyn.MustPathFromString(l.path)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/libs/diag"
"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/fileset"
"golang.org/x/sync/errgroup"
)
@ -66,7 +67,7 @@ func checkPatterns(patterns []string, path string, rb bundle.ReadOnlyBundle) (di
diags = diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("Pattern %s does not match any files", p),
Location: loc.Location(),
Locations: []dyn.Location{loc.Location()},
Path: loc.Path(),
})
mu.Unlock()

View File

@ -32,8 +32,8 @@ const errorTemplate = `{{ "Error" | red }}: {{ .Summary }}
{{- if .Path.String }}
{{ "at " }}{{ .Path.String | green }}
{{- end }}
{{- if .Location.File }}
{{ "in " }}{{ .Location.String | cyan }}
{{- range $index, $element := .Locations }}
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
{{- end }}
{{- if .Detail }}
@ -46,8 +46,8 @@ const warningTemplate = `{{ "Warning" | yellow }}: {{ .Summary }}
{{- if .Path.String }}
{{ "at " }}{{ .Path.String | green }}
{{- end }}
{{- if .Location.File }}
{{ "in " }}{{ .Location.String | cyan }}
{{- range $index, $element := .Locations }}
{{ if eq $index 0 }}in {{else}} {{ end}}{{ $element.String | cyan }}
{{- end }}
{{- if .Detail }}
@ -141,12 +141,18 @@ func renderDiagnostics(out io.Writer, b *bundle.Bundle, diags diag.Diagnostics)
t = warningT
}
// Make file relative to bundle root
if d.Location.File != "" && b != nil {
out, err := filepath.Rel(b.RootPath, d.Location.File)
for i := range d.Locations {
if b == nil {
break
}
// Make location relative to bundle root
if d.Locations[i].File != "" {
out, err := filepath.Rel(b.RootPath, d.Locations[i].File)
// if we can't relativize the path, just use path as-is
if err == nil {
d.Location.File = out
d.Locations[i].File = out
}
}
}

View File

@ -91,31 +91,19 @@ func TestRenderTextOutput(t *testing.T) {
Severity: diag.Error,
Summary: "error (1)",
Detail: "detail (1)",
Location: dyn.Location{
File: "foo.py",
Line: 1,
Column: 1,
},
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
},
diag.Diagnostic{
Severity: diag.Error,
Summary: "error (2)",
Detail: "detail (2)",
Location: dyn.Location{
File: "foo.py",
Line: 2,
Column: 1,
},
Locations: []dyn.Location{{File: "foo.py", Line: 2, Column: 1}},
},
diag.Diagnostic{
Severity: diag.Warning,
Summary: "warning (3)",
Detail: "detail (3)",
Location: dyn.Location{
File: "foo.py",
Line: 3,
Column: 1,
},
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
},
},
opts: RenderOptions{RenderSummaryTable: true},
@ -177,21 +165,13 @@ func TestRenderTextOutput(t *testing.T) {
Severity: diag.Error,
Summary: "error (1)",
Detail: "detail (1)",
Location: dyn.Location{
File: "foo.py",
Line: 1,
Column: 1,
},
Locations: []dyn.Location{{File: "foo.py", Line: 1, Column: 1}},
},
diag.Diagnostic{
Severity: diag.Warning,
Summary: "warning (2)",
Detail: "detail (2)",
Location: dyn.Location{
File: "foo.py",
Line: 3,
Column: 1,
},
Locations: []dyn.Location{{File: "foo.py", Line: 3, Column: 1}},
},
},
opts: RenderOptions{RenderSummaryTable: false},
@ -252,17 +232,42 @@ func TestRenderDiagnostics(t *testing.T) {
Severity: diag.Error,
Summary: "failed to load xxx",
Detail: "'name' is required",
Location: dyn.Location{
Locations: []dyn.Location{{
File: "foo.yaml",
Line: 1,
Column: 2,
},
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{

View File

@ -9,6 +9,7 @@ import (
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config/validate"
"github.com/databricks/cli/libs/diag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -21,11 +22,13 @@ func TestSyncIncludeExcludeNoMatchesTest(t *testing.T) {
require.Equal(t, diags[0].Severity, diag.Warning)
require.Equal(t, diags[0].Summary, "Pattern dist does not match any files")
require.Equal(t, diags[0].Location.File, filepath.Join("sync", "override", "databricks.yml"))
require.Equal(t, diags[0].Location.Line, 17)
require.Equal(t, diags[0].Location.Column, 11)
require.Equal(t, diags[0].Path.String(), "sync.exclude[0]")
assert.Len(t, diags[0].Locations, 1)
require.Equal(t, diags[0].Locations[0].File, filepath.Join("sync", "override", "databricks.yml"))
require.Equal(t, diags[0].Locations[0].Line, 17)
require.Equal(t, diags[0].Locations[0].Column, 11)
summaries := []string{
fmt.Sprintf("Pattern %s does not match any files", filepath.Join("src", "*")),
fmt.Sprintf("Pattern %s does not match any files", filepath.Join("tests", "*")),

View File

@ -17,9 +17,9 @@ type Diagnostic struct {
// This may be multiple lines and may be nil.
Detail string
// Location is a source code location associated with the diagnostic message.
// It may be zero if there is no associated location.
Location dyn.Location
// Locations are the source code locations associated with the diagnostic message.
// It may be empty if there are no associated locations.
Locations []dyn.Location
// Path is a path to the value in a configuration tree that the diagnostic is associated with.
// It may be nil if there is no associated path.

View File

@ -67,7 +67,7 @@ func nullWarning(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnosti
return diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("expected a %s value, found null", expected),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
}
}
@ -76,7 +76,7 @@ func typeMismatch(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnost
return diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("expected %s, found %s", expected, src.Kind()),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
}
}
@ -98,7 +98,8 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
diags = diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("unknown field: %s", pk.MustString()),
Location: pk.Location(),
// Show all locations the unknown field is defined at.
Locations: pk.Locations(),
Path: path,
})
}
@ -322,7 +323,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf(`cannot accurately represent "%g" as integer due to precision loss`, src.MustFloat()),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
})
}
@ -338,7 +339,7 @@ func (n normalizeOptions) normalizeInt(typ reflect.Type, src dyn.Value, path dyn
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("cannot parse %q as an integer", src.MustString()),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
})
}
@ -365,7 +366,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf(`cannot accurately represent "%d" as floating point number due to precision loss`, src.MustInt()),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
})
}
@ -381,7 +382,7 @@ func (n normalizeOptions) normalizeFloat(typ reflect.Type, src dyn.Value, path d
return dyn.InvalidValue, diags.Append(diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("cannot parse %q as a floating point number", src.MustString()),
Location: src.Location(),
Locations: []dyn.Location{src.Location()},
Path: path,
})
}

View File

@ -42,7 +42,7 @@ func TestNormalizeStructElementDiagnostic(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.NewPath(dyn.Key("bar")),
}, err[0])
@ -58,23 +58,33 @@ func TestNormalizeStructUnknownField(t *testing.T) {
}
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"bar": dyn.V("baz"),
})
m := dyn.NewMapping()
m.Set(dyn.V("foo"), dyn.V("val-foo"))
// Set the unknown field, with location information.
m.Set(dyn.NewValue("bar", []dyn.Location{
{File: "hello.yaml", Line: 1, Column: 1},
{File: "world.yaml", Line: 2, Column: 2},
}), dyn.V("var-bar"))
vin := dyn.V(m)
vout, err := Normalize(typ, vin)
assert.Len(t, err, 1)
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `unknown field: bar`,
Location: vin.Get("foo").Location(),
// Assert location of the unknown field is included in the diagnostic.
Locations: []dyn.Location{
{File: "hello.yaml", Line: 1, Column: 1},
{File: "world.yaml", Line: 2, Column: 2},
},
Path: dyn.EmptyPath,
}, err[0])
// The field that can be mapped to the struct field is retained.
assert.Equal(t, map[string]any{
"foo": "bar",
"foo": "val-foo",
}, vout.AsAny())
}
@ -102,7 +112,7 @@ func TestNormalizeStructError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Get("foo").Location(),
Locations: []dyn.Location{vin.Get("foo").Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -247,7 +257,7 @@ func TestNormalizeStructRandomStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -264,7 +274,7 @@ func TestNormalizeStructIntError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found int`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -293,7 +303,7 @@ func TestNormalizeMapElementDiagnostic(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.NewPath(dyn.Key("bar")),
}, err[0])
@ -319,7 +329,7 @@ func TestNormalizeMapError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -374,7 +384,7 @@ func TestNormalizeMapRandomStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -387,7 +397,7 @@ func TestNormalizeMapIntError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected map, found int`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -417,7 +427,7 @@ func TestNormalizeSliceElementDiagnostic(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.NewPath(dyn.Index(2)),
}, err[0])
@ -441,7 +451,7 @@ func TestNormalizeSliceError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected sequence, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -496,7 +506,7 @@ func TestNormalizeSliceRandomStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected sequence, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -509,7 +519,7 @@ func TestNormalizeSliceIntError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected sequence, found int`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -530,7 +540,7 @@ func TestNormalizeStringNil(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a string value, found null`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -567,7 +577,7 @@ func TestNormalizeStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected string, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.EmptyPath,
}, err[0])
}
@ -588,7 +598,7 @@ func TestNormalizeBoolNil(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a bool value, found null`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -630,7 +640,7 @@ func TestNormalizeBoolFromStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected bool, found string`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -643,7 +653,7 @@ func TestNormalizeBoolError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected bool, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.EmptyPath,
}, err[0])
}
@ -664,7 +674,7 @@ func TestNormalizeIntNil(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a int value, found null`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -685,7 +695,7 @@ func TestNormalizeIntFromFloatError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot accurately represent "1.5" as integer due to precision loss`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -714,7 +724,7 @@ func TestNormalizeIntFromStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot parse "abc" as an integer`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -727,7 +737,7 @@ func TestNormalizeIntError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected int, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.EmptyPath,
}, err[0])
}
@ -748,7 +758,7 @@ func TestNormalizeFloatNil(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected a float value, found null`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -773,7 +783,7 @@ func TestNormalizeFloatFromIntError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot accurately represent "9007199254740993" as floating point number due to precision loss`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -802,7 +812,7 @@ func TestNormalizeFloatFromStringError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `cannot parse "abc" as a floating point number`,
Location: vin.Location(),
Locations: []dyn.Location{vin.Location()},
Path: dyn.EmptyPath,
}, err[0])
}
@ -815,7 +825,7 @@ func TestNormalizeFloatError(t *testing.T) {
assert.Equal(t, diag.Diagnostic{
Severity: diag.Warning,
Summary: `expected float, found map`,
Location: dyn.Location{},
Locations: []dyn.Location{{}},
Path: dyn.EmptyPath,
}, err[0])
}