Expose resolution input provenance
This commit is contained in:
@@ -280,6 +280,83 @@ func TestResolutionRejectsInvalidProjectedValuesAndSuppressesReturnedErrors(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionEntryQueriesExposeDetachedLoadInput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type config struct {
|
||||
Declared string `yaml:"declared"`
|
||||
Default string `yaml:"default"`
|
||||
Optional *string `yaml:"optional"`
|
||||
}
|
||||
filename := writeImportFile(t, t.TempDir(), "config.yml", "declared: file\noptional: null\n")
|
||||
var savedField FieldInfo
|
||||
var savedSource SourceInfo
|
||||
result, err := Load[config](File(filename), Options[config]{Definition: Definition[config]{
|
||||
Defaults: func() config { return config{Default: "retained"} },
|
||||
Resolve: func(_ *config, resolution *Resolution) error {
|
||||
for _, path := range []Path{Keys("declared"), Keys("default"), Keys("optional")} {
|
||||
info, found := resolution.InputInfo(path)
|
||||
if !found || info.Explicit != resolution.IsExplicit(path) {
|
||||
return errors.New("entry metadata does not agree with explicitness")
|
||||
}
|
||||
}
|
||||
defaultInfo, _ := resolution.InputInfo(Keys("default"))
|
||||
optionalInfo, _ := resolution.InputInfo(Keys("optional"))
|
||||
if defaultInfo.Explicit || defaultInfo.Origin.Layer != LayerDefault || !optionalInfo.Explicit || !optionalInfo.Null {
|
||||
return errors.New("entry metadata lost default or null state")
|
||||
}
|
||||
if _, found := resolution.InputInfo(Keys("missing")); found {
|
||||
return errors.New("missing entry path was found")
|
||||
}
|
||||
declared, _ := resolution.InputInfo(Keys("declared"))
|
||||
if len(declared.Origin.Locations) != 1 {
|
||||
return errors.New("declared source location is missing")
|
||||
}
|
||||
source, found := resolution.Source(declared.Origin.Locations[0].SourceID)
|
||||
if !found || source.Path != canonicalTestPath(t, filename) || source.ID != "source-1" {
|
||||
return errors.New("declared source record is missing")
|
||||
}
|
||||
if _, found := resolution.Source(""); found {
|
||||
return errors.New("empty source identity was found")
|
||||
}
|
||||
if _, found := resolution.Source("unknown"); found {
|
||||
return errors.New("unknown source identity was found")
|
||||
}
|
||||
savedField, savedSource = declared, source
|
||||
return nil
|
||||
},
|
||||
}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Config.Declared != "file" || result.Config.Default != "retained" || result.Config.Optional != nil {
|
||||
t.Fatalf("configuration = %#v", result.Config)
|
||||
}
|
||||
savedField.Origin.Locations[0].SourceID = "mutated"
|
||||
savedSource.Label = "mutated"
|
||||
field, _ := result.Metadata().Lookup(Keys("declared"))
|
||||
if field.Origin.Locations[0].SourceID != "source-1" || result.Sources()[0].Label == "mutated" {
|
||||
t.Fatal("saved resolution records alias a completed result")
|
||||
}
|
||||
|
||||
noSource, err := Load[config](NoSource(), Options[config]{Definition: Definition[config]{
|
||||
Defaults: func() config { return config{Default: "retained"} },
|
||||
Resolve: func(_ *config, resolution *Resolution) error {
|
||||
info, found := resolution.InputInfo(Keys("default"))
|
||||
if !found || info.Explicit || resolution.IsExplicit(Keys("default")) {
|
||||
return errors.New("defaults-only entry metadata is wrong")
|
||||
}
|
||||
if _, found := resolution.Source("source-1"); found {
|
||||
return errors.New("defaults-only load invented a source")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}})
|
||||
if err != nil || noSource.Config.Default != "retained" {
|
||||
t.Fatalf("defaults-only load = %#v, %v", noSource, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionContextPanicsAfterCallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -301,6 +378,45 @@ func TestResolutionContextPanicsAfterCallback(t *testing.T) {
|
||||
retained.IsExplicit(Keys("value"))
|
||||
}
|
||||
|
||||
func TestResolutionEntryQueriesRequireAnActiveCallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type config struct {
|
||||
Value int `yaml:"value"`
|
||||
}
|
||||
requireClosed := func(t *testing.T, call func()) {
|
||||
t.Helper()
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != closedCallbackContextMessage {
|
||||
t.Fatalf("panic = %#v", recovered)
|
||||
}
|
||||
}()
|
||||
call()
|
||||
}
|
||||
requireClosed(t, func() { (*Resolution)(nil).InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { (&Resolution{}).Source("source-1") })
|
||||
|
||||
var successful *Resolution
|
||||
if _, err := Load(NoSource(), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
successful = resolution
|
||||
return nil
|
||||
}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
requireClosed(t, func() { successful.InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { successful.Source("source-1") })
|
||||
|
||||
var failed *Resolution
|
||||
if _, err := Load(NoSource(), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
failed = resolution
|
||||
return errors.New("resolver failed")
|
||||
}}}); err == nil {
|
||||
t.Fatal("failed resolver succeeded")
|
||||
}
|
||||
requireClosed(t, func() { failed.InputInfo(Keys("value")) })
|
||||
requireClosed(t, func() { failed.Source("source-1") })
|
||||
}
|
||||
|
||||
func TestValidationContextPanicsAfterVersionCallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user