package configkit 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) } } }