Verify mounted diagnostic paths and locations
This commit is contained in:
@@ -284,7 +284,7 @@ ownership and lifecycle timing are preserved.
|
|||||||
|
|
||||||
## Stage 6 — Reconcile diagnostic coordinate systems
|
## Stage 6 — Reconcile diagnostic coordinate systems
|
||||||
|
|
||||||
Status: planned. Scope: useful paths without fictional source locations.
|
Status: complete. Scope: useful paths without fictional source locations.
|
||||||
|
|
||||||
1. Audit early acquisition, import, fragment-version, and composition failures.
|
1. Audit early acquisition, import, fragment-version, and composition failures.
|
||||||
Use one narrow path-prefix translation where diagnostics genuinely identify
|
Use one narrow path-prefix translation where diagnostics genuinely identify
|
||||||
@@ -300,6 +300,14 @@ Status: planned. Scope: useful paths without fictional source locations.
|
|||||||
nil results, not whole messages or callback choreography.
|
nil results, not whole messages or callback choreography.
|
||||||
|
|
||||||
Validate: destination/diagnostic/import/version/decode tests and everyday check.
|
Validate: destination/diagnostic/import/version/decode tests and everyday check.
|
||||||
|
Validated with `go test ./internal/engine -run 'LayeredDestinationDiagnostics'`,
|
||||||
|
`go test ./internal/engine -run 'Destination|Diagnostic|Import|Version|Decode'`,
|
||||||
|
and `./scripts/check.sh` (go1.26.8 linux/amd64); all passed. The mounted
|
||||||
|
conflict, decode, and imported-version assertions use effective application
|
||||||
|
paths with authored source locations; invalid destinations have no physical
|
||||||
|
location; malformed source-root directives retain their authored path and
|
||||||
|
location. No path-prefix translation was needed because value diagnostics are
|
||||||
|
already produced from placed trees and directive diagnostics from raw trees.
|
||||||
Done: diagnostics consistently distinguish application paths from source syntax.
|
Done: diagnostics consistently distinguish application paths from source syntax.
|
||||||
|
|
||||||
## Stage 7 — Complete shared placement accounting
|
## Stage 7 — Complete shared placement accounting
|
||||||
|
|||||||
142
internal/engine/destination_diagnostic_test.go
Normal file
142
internal/engine/destination_diagnostic_test.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLayeredDestinationDiagnosticsUseEffectivePathsAndAuthoredLocations(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("strict conflict retains both source locations", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loaded, err := LoadLayered(
|
||||||
|
Input{Source: NoSource()},
|
||||||
|
testSecretBinding(),
|
||||||
|
LayeredOptions[map[string]any]{Underlays: []Underlay{{Inputs: []Input{
|
||||||
|
{Source: Bytes("left", []byte("section:\n value: left\n")), Destination: Keys("application")},
|
||||||
|
{Source: Bytes("right", []byte("section:\n value: right\n")), Destination: Keys("application")},
|
||||||
|
}}}},
|
||||||
|
)
|
||||||
|
if loaded != nil {
|
||||||
|
t.Fatalf("conflicting load returned a result: %#v", loaded)
|
||||||
|
}
|
||||||
|
diagnostic := firstDestinationDiagnostic(t, err)
|
||||||
|
if diagnostic.Code != CodeComposeConflict || diagnostic.Path.String() != "/application/section/value" {
|
||||||
|
t.Fatalf("conflict diagnostic = %#v", diagnostic)
|
||||||
|
}
|
||||||
|
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" || diagnostic.Primary.Line != 2 {
|
||||||
|
t.Fatalf("primary location = %#v", diagnostic.Primary)
|
||||||
|
}
|
||||||
|
if len(diagnostic.Related) != 1 || diagnostic.Related[0].SourceID != "source-2" || diagnostic.Related[0].Line != 2 {
|
||||||
|
t.Fatalf("related locations = %#v", diagnostic.Related)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("decode failure keeps effective path and content line", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
type section struct {
|
||||||
|
Count int `yaml:"count"`
|
||||||
|
}
|
||||||
|
type config struct {
|
||||||
|
Application struct {
|
||||||
|
Section section `yaml:"section"`
|
||||||
|
} `yaml:"application"`
|
||||||
|
}
|
||||||
|
loaded, err := LoadLayered(
|
||||||
|
Input{Source: Bytes("main", []byte("section:\n count: invalid\n")), Destination: Keys("application")},
|
||||||
|
testSecretBinding(),
|
||||||
|
LayeredOptions[config]{},
|
||||||
|
)
|
||||||
|
if loaded != nil {
|
||||||
|
t.Fatalf("invalid load returned a result: %#v", loaded)
|
||||||
|
}
|
||||||
|
diagnostic := firstDestinationDiagnostic(t, err)
|
||||||
|
if diagnostic.Code != CodeDecodeType || diagnostic.Path.String() != "/application/section/count" {
|
||||||
|
t.Fatalf("decode diagnostic = %#v", diagnostic)
|
||||||
|
}
|
||||||
|
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" || diagnostic.Primary.Line != 2 {
|
||||||
|
t.Fatalf("decode location = %#v", diagnostic.Primary)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("imported version failure uses effective path and import source", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
directory := t.TempDir()
|
||||||
|
writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: fragment.yml}]}\n")
|
||||||
|
writeImportFile(t, directory, "fragment.yml", "version: forbidden\n")
|
||||||
|
rule := &VersionRule{Path: Keys("application", "version"), Check: func(Version, *Validation) error { return nil }}
|
||||||
|
loaded, err := LoadLayered(
|
||||||
|
Input{Source: NoSource()},
|
||||||
|
testSecretBinding(),
|
||||||
|
LayeredOptions[map[string]any]{
|
||||||
|
Definition: Definition[map[string]any]{Version: rule},
|
||||||
|
Underlays: []Underlay{{Inputs: []Input{{
|
||||||
|
Source: File(filepath.Join(directory, "main.yml")), Imports: true, Destination: Keys("application"),
|
||||||
|
}}}},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if loaded != nil {
|
||||||
|
t.Fatalf("invalid import returned a result: %#v", loaded)
|
||||||
|
}
|
||||||
|
diagnostic := firstDestinationDiagnostic(t, err)
|
||||||
|
if diagnostic.Code != CodeImportVersion || diagnostic.Path.String() != "/application/version" {
|
||||||
|
t.Fatalf("import version diagnostic = %#v", diagnostic)
|
||||||
|
}
|
||||||
|
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-2" || diagnostic.Primary.Line != 1 {
|
||||||
|
t.Fatalf("import version location = %#v", diagnostic.Primary)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid destination has no fictional source location", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loaded, err := LoadLayered(
|
||||||
|
Input{Source: Bytes("main", []byte("value: present\n")), Destination: Keys("configkit")},
|
||||||
|
testSecretBinding(),
|
||||||
|
LayeredOptions[map[string]any]{},
|
||||||
|
)
|
||||||
|
if loaded != nil {
|
||||||
|
t.Fatalf("invalid destination returned a result: %#v", loaded)
|
||||||
|
}
|
||||||
|
diagnostic := firstDestinationDiagnostic(t, err)
|
||||||
|
if diagnostic.Code != CodeOptionsInvalid || diagnostic.Path.String() != "/configkit" || diagnostic.Primary != nil {
|
||||||
|
t.Fatalf("invalid destination diagnostic = %#v", diagnostic)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("malformed source directive keeps its local path and location", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
filesystem := NewFilesystem("bundle", fstest.MapFS{
|
||||||
|
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [true]}\nvalue: present\n")},
|
||||||
|
})
|
||||||
|
loaded, err := LoadLayered(
|
||||||
|
Input{Source: filesystem.File("main.yml"), Imports: true, Destination: Keys("application")},
|
||||||
|
testSecretBinding(),
|
||||||
|
LayeredOptions[map[string]any]{},
|
||||||
|
)
|
||||||
|
if loaded != nil {
|
||||||
|
t.Fatalf("malformed directive returned a result: %#v", loaded)
|
||||||
|
}
|
||||||
|
diagnostic := firstDestinationDiagnostic(t, err)
|
||||||
|
if diagnostic.Code != CodeImportInvalid || diagnostic.Path.String() != "/configkit/imports/0" {
|
||||||
|
t.Fatalf("malformed directive diagnostic = %#v", diagnostic)
|
||||||
|
}
|
||||||
|
if diagnostic.Primary == nil || diagnostic.Primary.SourceID != "source-1" || diagnostic.Primary.Line != 1 {
|
||||||
|
t.Fatalf("malformed directive location = %#v", diagnostic.Primary)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstDestinationDiagnostic(t *testing.T, err error) Diagnostic {
|
||||||
|
t.Helper()
|
||||||
|
loadError, ok := err.(*LoadError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("load error = %#v", err)
|
||||||
|
}
|
||||||
|
diagnostics := loadError.Diagnostics()
|
||||||
|
if len(diagnostics) == 0 {
|
||||||
|
t.Fatal("load error has no diagnostics")
|
||||||
|
}
|
||||||
|
return diagnostics[0]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user