Add internal engine relocation candidate
This commit is contained in:
510
internal/engine/provenance_test.go
Normal file
510
internal/engine/provenance_test.go
Normal file
@@ -0,0 +1,510 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type layeredItem struct {
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
type layeredConfig struct {
|
||||
Shared struct {
|
||||
Equal string `yaml:"equal"`
|
||||
Imported string `yaml:"imported"`
|
||||
Profile string `yaml:"profile"`
|
||||
} `yaml:"shared"`
|
||||
Values []int `yaml:"values"`
|
||||
Items map[string]layeredItem `yaml:"items"`
|
||||
Value any `yaml:"value"`
|
||||
Nullable *string `yaml:"nullable"`
|
||||
Derived int `yaml:"derived"`
|
||||
Empty map[string]string `yaml:"empty"`
|
||||
}
|
||||
|
||||
func TestLayeredProvenanceAgreesWithResolvedSnapshot(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
imports: [{path: imported.yml}]
|
||||
shared: {equal: same}
|
||||
values: [1, 2]
|
||||
items:
|
||||
keep: {value: 'yes'}
|
||||
remove: {value: 'no'}
|
||||
value: {child: base}
|
||||
nullable: base
|
||||
empty: {}
|
||||
`)
|
||||
importPath := writeImportFile(t, directory, "imported.yml", "shared: {imported: base}\n")
|
||||
profilePath := writeImportFile(t, directory, "profiles/testing.yml", `shared: {profile: selected}
|
||||
values: [3, 4]
|
||||
value: selected
|
||||
nullable: null
|
||||
empty: {}
|
||||
`)
|
||||
defaultNullable := "default"
|
||||
definition := Definition[layeredConfig]{
|
||||
Defaults: func() layeredConfig {
|
||||
defaults := layeredConfig{
|
||||
Values: []int{0, 0}, Value: map[string]any{"child": "default"},
|
||||
Nullable: &defaultNullable, Empty: map[string]string{"retained": "default"},
|
||||
}
|
||||
defaults.Shared.Equal = "same"
|
||||
return defaults
|
||||
},
|
||||
Resolve: func(configuration *layeredConfig, resolution *Resolution) error {
|
||||
Normalize(resolution, Keys("values"), &configuration.Values, []int{5},
|
||||
"bounded values", Keys("shared", "imported"), Keys("shared", "imported"))
|
||||
Normalize(resolution, Keys("values"), &configuration.Values, []int{5},
|
||||
"bounded values", Keys("shared", "imported"))
|
||||
delete(configuration.Items, "remove")
|
||||
resolution.Record(Keys("items"), ChangeNormalization, "removed disabled items")
|
||||
Default(resolution, Keys("derived"), &configuration.Derived, 7,
|
||||
"derived from equal", Keys("shared", "equal"), Keys("shared", "equal"))
|
||||
resolution.Warning(Keys("values"), "values were bounded", Keys("shared", "imported"))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
result, err := Load[layeredConfig](File(mainPath), Options[layeredConfig]{
|
||||
Imports: true, Profile: "testing", Definition: definition,
|
||||
})
|
||||
if err != nil {
|
||||
var loadError *LoadError
|
||||
_ = errors.As(err, &loadError)
|
||||
t.Fatalf("%v: %#v", err, loadError.Diagnostics())
|
||||
}
|
||||
wantSources := []string{canonicalTestPath(t, mainPath), canonicalTestPath(t, importPath), canonicalTestPath(t, profilePath)}
|
||||
sources := result.Sources()
|
||||
gotSources := make([]string, len(sources))
|
||||
for index, source := range sources {
|
||||
gotSources[index] = source.Path
|
||||
}
|
||||
if !reflect.DeepEqual(gotSources, wantSources) || sources[2].Layer != LayerProfile {
|
||||
t.Fatalf("sources = %#v", sources)
|
||||
}
|
||||
if result.Config.Shared.Equal != "same" || result.Config.Shared.Imported != "base" || result.Config.Shared.Profile != "selected" ||
|
||||
!reflect.DeepEqual(result.Config.Values, []int{5}) || result.Config.Nullable != nil || result.Config.Derived != 7 ||
|
||||
result.Config.Empty["retained"] != "default" {
|
||||
t.Fatalf("configuration = %#v", result.Config)
|
||||
}
|
||||
|
||||
metadata := result.Metadata()
|
||||
equal, _ := metadata.Lookup(Keys("shared", "equal"))
|
||||
derived, _ := metadata.Lookup(Keys("derived"))
|
||||
nullable, _ := metadata.Lookup(Keys("nullable"))
|
||||
empty, _ := metadata.Lookup(Keys("empty"))
|
||||
firstValue, _ := metadata.Lookup(Keys("values").Index(0))
|
||||
if !equal.Explicit || equal.Origin.Layer != LayerBase || derived.Explicit || derived.Origin.Operation != ChangeDerivedDefault ||
|
||||
!nullable.Explicit || !nullable.Null || nullable.Origin.Layer != LayerProfile ||
|
||||
!empty.Explicit || empty.Origin.Layer != LayerProfile || len(empty.Origin.Locations) != 2 ||
|
||||
!firstValue.Explicit || firstValue.Origin.Operation != ChangeNormalization {
|
||||
t.Fatalf("metadata = equal %#v derived %#v nullable %#v empty %#v value %#v", equal, derived, nullable, empty, firstValue)
|
||||
}
|
||||
if _, ok := metadata.Lookup(Keys("value", "child")); ok {
|
||||
t.Fatal("profile-removed child remains effective")
|
||||
}
|
||||
if _, ok := metadata.Lookup(Keys("values").Index(1)); ok {
|
||||
t.Fatal("resolution-removed sequence index remains effective")
|
||||
}
|
||||
if _, ok := metadata.Lookup(Keys("items", "remove", "value")); ok {
|
||||
t.Fatal("resolution-removed mapping child remains effective")
|
||||
}
|
||||
|
||||
assertHistoryLayersAndPaths(t, metadata.History(Keys("values").Index(1)),
|
||||
[]Layer{LayerDefault, LayerBase, LayerProfile, LayerResolution},
|
||||
[]string{"/values/1", "/values/1", "/values", "/values"})
|
||||
assertHistoryLayersAndPaths(t, metadata.History(Keys("value", "child")),
|
||||
[]Layer{LayerDefault, LayerBase, LayerProfile},
|
||||
[]string{"/value/child", "/value/child", "/value"})
|
||||
removedHistory := metadata.History(Keys("items", "remove", "value"))
|
||||
assertHistoryLayersAndPaths(t, removedHistory,
|
||||
[]Layer{LayerBase, LayerResolution},
|
||||
[]string{"/items/remove/value", "/items"})
|
||||
if !reflect.DeepEqual(removedHistory[1].Reasons, []string{"removed disabled items"}) {
|
||||
t.Fatalf("removed history = %#v", removedHistory)
|
||||
}
|
||||
valuesHistory := metadata.History(Keys("values").Index(0))
|
||||
lastValue := valuesHistory[len(valuesHistory)-1]
|
||||
if !reflect.DeepEqual(lastValue.Reasons, []string{"bounded values"}) || len(lastValue.Dependencies) != 1 || lastValue.Dependencies[0].String() != "/shared/imported" {
|
||||
t.Fatalf("canonical resolution history = %#v", lastValue)
|
||||
}
|
||||
|
||||
schema, issue := buildSchema(reflect.TypeFor[layeredConfig]())
|
||||
if issue != nil {
|
||||
t.Fatal(issue)
|
||||
}
|
||||
projected, projectionIssue := projectConfiguration(schema, reflect.ValueOf(result.Config), defaultProjectionLimits())
|
||||
if projectionIssue != nil || !configurationValuesEqual(projected, result.final) {
|
||||
t.Fatalf("typed projection and private snapshot disagree: %#v", projectionIssue)
|
||||
}
|
||||
|
||||
metadata.history[Keys("values").Index(0).String()][0].Origin.Layer = "mutated"
|
||||
sources[1].ImportedFrom.SourceID = "mutated"
|
||||
diagnostics := result.Diagnostics()
|
||||
diagnostics[0].Related[0].SourceID = "mutated"
|
||||
result.Config.Values[0] = 99
|
||||
freshHistory := result.Metadata().History(Keys("values").Index(0))
|
||||
if freshHistory[0].Origin.Layer == "mutated" || result.Sources()[1].ImportedFrom.SourceID == "mutated" ||
|
||||
result.Diagnostics()[0].Related[0].SourceID == "mutated" {
|
||||
t.Fatal("returned inspection records alias private state")
|
||||
}
|
||||
snapshotValue, ok := documentAtPath(result.final, Keys("values").Index(0))
|
||||
if !ok || snapshotValue.Text != "5" {
|
||||
t.Fatal("caller configuration mutation changed the private final snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedProfileLoadsRemainIndependentConcurrently(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", "values: [1, 2]\n")
|
||||
writeImportFile(t, directory, "profiles/testing.yml", "values: [3, 4]\n")
|
||||
options := Options[layeredConfig]{Profile: "testing"}
|
||||
|
||||
const loads = 16
|
||||
results := make(chan *Result[layeredConfig], loads)
|
||||
errors := make(chan error, loads)
|
||||
var group sync.WaitGroup
|
||||
for index := 0; index < loads; index++ {
|
||||
group.Add(1)
|
||||
go func() {
|
||||
defer group.Done()
|
||||
result, err := Load[layeredConfig](File(mainPath), options)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
results <- result
|
||||
}()
|
||||
}
|
||||
group.Wait()
|
||||
close(results)
|
||||
close(errors)
|
||||
for err := range errors {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var collected []*Result[layeredConfig]
|
||||
for result := range results {
|
||||
collected = append(collected, result)
|
||||
}
|
||||
if len(collected) != loads {
|
||||
t.Fatalf("completed loads = %d", len(collected))
|
||||
}
|
||||
collected[0].Config.Values[0] = 99
|
||||
collected[0].metadata.fields[Keys("values").String()] = FieldInfo{}
|
||||
for _, result := range collected[1:] {
|
||||
if !reflect.DeepEqual(result.Config.Values, []int{3, 4}) {
|
||||
t.Fatalf("concurrent result contaminated = %#v", result.Config.Values)
|
||||
}
|
||||
if info, ok := result.Metadata().Lookup(Keys("values")); !ok || info.Origin.Layer != LayerProfile {
|
||||
t.Fatalf("concurrent metadata contaminated = %#v, %v", info, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionInputKeepsLayeredOriginsAndSources(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
mainPath := writeImportFile(t, directory, "main.yml", `configkit:
|
||||
imports: [{path: first.yml}, {path: second.yml}]
|
||||
main: main
|
||||
equal: same
|
||||
cli_equal: same
|
||||
composite: {main: main}
|
||||
`)
|
||||
firstPath := writeImportFile(t, directory, "first.yml", `imported: imported
|
||||
sequence: [first, second]
|
||||
composite: {first: first}
|
||||
`)
|
||||
secondPath := writeImportFile(t, directory, "second.yml", "composite: {second: second}\n")
|
||||
profilePath := writeImportFile(t, directory, "profiles/testing.yml", "equal: same\nprofile: profile\n")
|
||||
compositeSources := map[string]bool{
|
||||
canonicalTestPath(t, mainPath): true,
|
||||
canonicalTestPath(t, firstPath): true,
|
||||
canonicalTestPath(t, secondPath): true,
|
||||
}
|
||||
type config struct {
|
||||
Main string `yaml:"main"`
|
||||
Imported string `yaml:"imported"`
|
||||
Sequence []string `yaml:"sequence"`
|
||||
Composite map[string]string `yaml:"composite"`
|
||||
Equal string `yaml:"equal"`
|
||||
Profile string `yaml:"profile"`
|
||||
CLIEqual string `yaml:"cli_equal"`
|
||||
}
|
||||
var callbackErr error
|
||||
result, err := Load[config](File(mainPath), Options[config]{
|
||||
Imports: true, Profile: "testing", Overrides: []Override{{Path: Keys("cli_equal"), Value: "same", Flag: "--cli-equal"}},
|
||||
Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
check := func(ok bool, message string) {
|
||||
if !ok && callbackErr == nil {
|
||||
callbackErr = errors.New(message)
|
||||
}
|
||||
}
|
||||
fileOwner := func(path Path, want string) FieldInfo {
|
||||
info, found := resolution.InputInfo(path)
|
||||
check(found && len(info.Origin.Locations) == 1, "scalar has no exact origin")
|
||||
if !found || len(info.Origin.Locations) != 1 {
|
||||
return FieldInfo{}
|
||||
}
|
||||
source, exists := resolution.Source(info.Origin.Locations[0].SourceID)
|
||||
check(exists && source.Path == want, "scalar source does not match its declaration")
|
||||
return info
|
||||
}
|
||||
fileOwner(Keys("main"), canonicalTestPath(t, mainPath))
|
||||
fileOwner(Keys("imported"), canonicalTestPath(t, firstPath))
|
||||
fileOwner(Keys("sequence").Index(1), canonicalTestPath(t, firstPath))
|
||||
profile := fileOwner(Keys("equal"), canonicalTestPath(t, profilePath))
|
||||
fileOwner(Keys("profile"), canonicalTestPath(t, profilePath))
|
||||
check(profile.Origin.Layer == LayerProfile, "profile replacement retained base ownership")
|
||||
cli, found := resolution.InputInfo(Keys("cli_equal"))
|
||||
check(found && cli.Origin.Layer == LayerCLI && len(cli.Origin.Locations) == 1, "CLI replacement lost ownership")
|
||||
if found && len(cli.Origin.Locations) == 1 {
|
||||
source, exists := resolution.Source(cli.Origin.Locations[0].SourceID)
|
||||
check(exists && source.Layer == LayerCLI && source.Path == "", "CLI source has a file path or is missing")
|
||||
}
|
||||
|
||||
composite, found := resolution.InputInfo(Keys("composite"))
|
||||
check(found && len(composite.Origin.Locations) == 3, "composite mapping did not retain every contributor")
|
||||
for _, location := range composite.Origin.Locations {
|
||||
source, exists := resolution.Source(location.SourceID)
|
||||
check(exists && compositeSources[source.Path], "composite location has no expected source record")
|
||||
delete(compositeSources, source.Path)
|
||||
}
|
||||
check(len(compositeSources) == 0, "composite sources are incomplete")
|
||||
|
||||
imported, _ := resolution.InputInfo(Keys("imported"))
|
||||
importedSourceID := imported.Origin.Locations[0].SourceID
|
||||
imported.Origin.Locations[0].SourceID = "mutated"
|
||||
importSource, _ := resolution.Source(importedSourceID)
|
||||
check(importSource.ImportedFrom != nil, "import source is missing its import location")
|
||||
if importSource.ImportedFrom != nil {
|
||||
importSource.ImportedFrom.SourceID = "mutated"
|
||||
}
|
||||
again, _ := resolution.InputInfo(Keys("imported"))
|
||||
againSource, _ := resolution.Source(importedSourceID)
|
||||
check(again.Origin.Locations[0].SourceID == importedSourceID, "input record is aliased")
|
||||
check(againSource.ImportedFrom != nil && againSource.ImportedFrom.SourceID == "source-1", "source record is aliased")
|
||||
return callbackErr
|
||||
}},
|
||||
})
|
||||
if callbackErr != nil {
|
||||
t.Fatal(callbackErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Config.Main != "main" || result.Config.Imported != "imported" || !reflect.DeepEqual(result.Config.Sequence, []string{"first", "second"}) ||
|
||||
result.Config.Equal != "same" || result.Config.Profile != "profile" || result.Config.CLIEqual != "same" {
|
||||
t.Fatalf("configuration = %#v", result.Config)
|
||||
}
|
||||
|
||||
resultField, _ := result.Metadata().Lookup(Keys("imported"))
|
||||
resultSource := result.Sources()[1]
|
||||
if resultField.Origin.Locations[0].SourceID != "source-2" || resultSource.ImportedFrom == nil || resultSource.ImportedFrom.SourceID != "source-1" {
|
||||
t.Fatal("callback record mutation changed the completed result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionInputUsesEntryShapesAcrossTypedAndDynamicValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type Embedded struct {
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
type config struct {
|
||||
Embedded `yaml:",inline"`
|
||||
Mappings map[string]string `yaml:"mappings"`
|
||||
Array [2]string `yaml:"array"`
|
||||
Dynamic any `yaml:"dynamic"`
|
||||
}
|
||||
var callbackErr error
|
||||
_, err := Load[config](Bytes("shapes.yml", []byte("name: inlined\nmappings: {'-0': key, 'a/b~': escaped}\narray: [zero, one]\ndynamic: {nested: [dynamic]}\n")), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
check := func(path Path, want bool) {
|
||||
_, found := resolution.InputInfo(path)
|
||||
if found != want && callbackErr == nil {
|
||||
callbackErr = errors.New("entry shape lookup disagrees with its document container")
|
||||
}
|
||||
}
|
||||
parsedMapping, _ := ParsePath("/mappings/-0")
|
||||
escapedMapping, _ := ParsePath("/mappings/a~1b~0")
|
||||
check(Keys("name"), true)
|
||||
check(parsedMapping, true)
|
||||
check(escapedMapping, true)
|
||||
check(Keys("mappings").Index(0), false)
|
||||
check(Keys("array").Index(1), true)
|
||||
check(Keys("array", "1"), false)
|
||||
check(Keys("array").Index(2), false)
|
||||
check(Keys("dynamic", "nested").Index(0), true)
|
||||
check(Keys("dynamic", "nested", "0"), false)
|
||||
negative, _ := ParsePath("/array/-1")
|
||||
check(negative, false)
|
||||
return callbackErr
|
||||
}}})
|
||||
if callbackErr != nil {
|
||||
t.Fatal(callbackErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionInputDistinguishesNonFileRootsAndImportedFiles(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
importPath := writeImportFile(t, directory, "fragment.yml", "imported: file\n")
|
||||
type config struct {
|
||||
Root string `yaml:"root"`
|
||||
Imported string `yaml:"imported"`
|
||||
Default string `yaml:"default"`
|
||||
}
|
||||
input := "configkit: {imports: [{path: fragment.yml}]}\nroot: memory\n"
|
||||
context := &FilesystemContext{BaseDir: directory, Root: directory}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
source func() Source
|
||||
}{
|
||||
{name: "bytes", source: func() Source { return Bytes("memory://config.yml", []byte(input)) }},
|
||||
{name: "reader", source: func() Source { return Reader("memory://config.yml", strings.NewReader(input)) }},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var callbackErr error
|
||||
_, err := Load[config](test.source(), Options[config]{Imports: true, Filesystem: context, Definition: Definition[config]{
|
||||
Defaults: func() config { return config{Default: "retained"} },
|
||||
Resolve: func(_ *config, resolution *Resolution) error {
|
||||
root, _ := resolution.InputInfo(Keys("root"))
|
||||
imported, _ := resolution.InputInfo(Keys("imported"))
|
||||
defaulted, _ := resolution.InputInfo(Keys("default"))
|
||||
rootSource, rootFound := resolution.Source(root.Origin.Locations[0].SourceID)
|
||||
importSource, importFound := resolution.Source(imported.Origin.Locations[0].SourceID)
|
||||
if !rootFound || rootSource.Path != "" || !importFound || importSource.Path != canonicalTestPath(t, importPath) || defaulted.Explicit {
|
||||
callbackErr = errors.New("non-file root or imported file provenance is wrong")
|
||||
}
|
||||
if _, found := resolution.Source("unknown"); found {
|
||||
callbackErr = errors.New("unknown source was found")
|
||||
}
|
||||
return callbackErr
|
||||
},
|
||||
}})
|
||||
if callbackErr != nil {
|
||||
t.Fatal(callbackErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var callbackErr error
|
||||
_, err := Load[config](Bytes("memory://standalone.yml", []byte("root: memory\n")), Options[config]{Definition: Definition[config]{
|
||||
Defaults: func() config { return config{Default: "retained"} },
|
||||
Resolve: func(_ *config, resolution *Resolution) error {
|
||||
root, rootFound := resolution.InputInfo(Keys("root"))
|
||||
defaulted, defaultFound := resolution.InputInfo(Keys("default"))
|
||||
source, sourceFound := resolution.Source(root.Origin.Locations[0].SourceID)
|
||||
if !rootFound || !defaultFound || !sourceFound || source.Path != "" || defaulted.Explicit {
|
||||
callbackErr = errors.New("standalone non-file provenance is wrong")
|
||||
}
|
||||
return callbackErr
|
||||
},
|
||||
}})
|
||||
if callbackErr != nil {
|
||||
t.Fatal(callbackErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionInputRemainsFixedAcrossRecordedMutations(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type config struct {
|
||||
Existing string `yaml:"existing"`
|
||||
Values map[string]string `yaml:"values"`
|
||||
}
|
||||
var callbackErr error
|
||||
result, err := Load[config](Bytes("mutations.yml", []byte("existing: original\nvalues: {original: move}\n")), Options[config]{Definition: Definition[config]{Resolve: func(value *config, resolution *Resolution) error {
|
||||
original, originalFound := resolution.InputInfo(Keys("values", "original"))
|
||||
_, destinationFound := resolution.InputInfo(Keys("values", "destination"))
|
||||
existing, existingFound := resolution.InputInfo(Keys("existing"))
|
||||
Normalize(resolution, Keys("existing"), &value.Existing, "normalized", "normalized existing value")
|
||||
value.Values["destination"] = value.Values["original"]
|
||||
delete(value.Values, "original")
|
||||
resolution.Record(Keys("values"), ChangeNormalization, "moved configured value")
|
||||
againOriginal, againOriginalFound := resolution.InputInfo(Keys("values", "original"))
|
||||
againDestination, againDestinationFound := resolution.InputInfo(Keys("values", "destination"))
|
||||
againExisting, againExistingFound := resolution.InputInfo(Keys("existing"))
|
||||
if !originalFound || destinationFound || !existingFound || !againOriginalFound || againDestinationFound || !againExistingFound ||
|
||||
!reflect.DeepEqual(original, againOriginal) || !reflect.DeepEqual(existing, againExisting) || !reflect.DeepEqual(againDestination, FieldInfo{}) {
|
||||
callbackErr = errors.New("entry view changed during resolution")
|
||||
}
|
||||
return callbackErr
|
||||
}}})
|
||||
if callbackErr != nil {
|
||||
t.Fatal(callbackErr)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Config.Existing != "normalized" || result.Config.Values["destination"] != "move" {
|
||||
t.Fatalf("configuration = %#v", result.Config)
|
||||
}
|
||||
if _, found := result.Metadata().Lookup(Keys("values", "original")); found {
|
||||
t.Fatal("final metadata retained a removed path")
|
||||
}
|
||||
destination, found := result.Metadata().Lookup(Keys("values", "destination"))
|
||||
existing, existingFound := result.Metadata().Lookup(Keys("existing"))
|
||||
if !found || destination.Origin.Layer != LayerResolution || !existingFound || existing.Origin.Layer != LayerResolution {
|
||||
t.Fatalf("final metadata = destination %#v existing %#v", destination, existing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolutionInputQueriesRemainIndependentAcrossLoads(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type config struct {
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
const loads = 16
|
||||
failures := make(chan error, loads)
|
||||
var group sync.WaitGroup
|
||||
for index := 0; index < loads; index++ {
|
||||
group.Add(1)
|
||||
go func() {
|
||||
defer group.Done()
|
||||
_, err := Load[config](Bytes("parallel.yml", []byte("value: stable\n")), Options[config]{Definition: Definition[config]{Resolve: func(_ *config, resolution *Resolution) error {
|
||||
info, found := resolution.InputInfo(Keys("value"))
|
||||
source, sourceFound := resolution.Source(info.Origin.Locations[0].SourceID)
|
||||
if !found || !sourceFound || source.ID != "source-1" {
|
||||
return errors.New("independent entry lookup failed")
|
||||
}
|
||||
info.Origin.Locations[0].SourceID = "mutated"
|
||||
source.Label = "mutated"
|
||||
return nil
|
||||
}}})
|
||||
if err != nil {
|
||||
failures <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
group.Wait()
|
||||
close(failures)
|
||||
for err := range failures {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertHistoryLayersAndPaths(t *testing.T, history []Change, layers []Layer, paths []string) {
|
||||
t.Helper()
|
||||
if len(history) != len(layers) || len(history) != len(paths) {
|
||||
t.Fatalf("history = %#v", history)
|
||||
}
|
||||
for index, change := range history {
|
||||
if change.Origin.Layer != layers[index] || change.Path.String() != paths[index] {
|
||||
t.Fatalf("history[%d] = %#v", index, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user