Cover search exhaustion and fallback selection
This commit is contained in:
@@ -74,6 +74,8 @@ sequence-item, and budget checks to verify the extraction.
|
||||
|
||||
## Finding 3 — Exhaustion and lazy fallback lack focused regression coverage
|
||||
|
||||
**Status** — Complete. Public tests cover search exhaustion, root-only main context, and missing fallbacks when they win or are shadowed.
|
||||
|
||||
**Priority P3.** The context-selection test uses an existing fallback when
|
||||
checking that main derivation wins. It would still pass if the loader
|
||||
prematurely opened that fallback. The no-base tests use `NoSource()`, and
|
||||
|
||||
@@ -122,3 +122,76 @@ func TestMainRelativeSourceVersionEvidence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMainRelativeOptionalSearchExhaustion(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.Mkdir(filepath.Join(root, "overlays"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, content := range map[string]string{
|
||||
"fragment.yml": "settings: {companion: loaded}\n",
|
||||
"overlays/live.yml": "count: 9\n",
|
||||
} {
|
||||
if err := os.WriteFile(filepath.Join(root, name), []byte(content), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
search := configkit.SearchConfig("", configkit.SearchPaths(filepath.Join(root, "absent.yml")))
|
||||
missing := filepath.Join(root, "missing-fallback")
|
||||
fallback := &configkit.FilesystemContext{Root: missing, BaseDir: missing}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
mainContext *configkit.FilesystemContext
|
||||
bindingFallback *configkit.FilesystemContext
|
||||
overlayFallback *configkit.FilesystemContext
|
||||
overlay string
|
||||
wantCount int
|
||||
wantSources int
|
||||
}{
|
||||
{name: "skip without context", wantCount: 3, wantSources: 1},
|
||||
{
|
||||
name: "root-only main context wins", mainContext: &configkit.FilesystemContext{Root: root},
|
||||
bindingFallback: fallback, overlayFallback: fallback, overlay: "live", wantCount: 9, wantSources: 3,
|
||||
},
|
||||
{name: "selected missing companion fallback", bindingFallback: fallback},
|
||||
{name: "selected missing overlay fallback", overlay: "live", overlayFallback: fallback},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
underlay := configkit.Input{
|
||||
Source: configkit.Bytes("companion", []byte("count: 3\nconfig: {imports: [{path: fragment.yml}]}\n")),
|
||||
Imports: true, MainRelative: &configkit.MainRelativeContext{Fallback: tc.bindingFallback},
|
||||
}
|
||||
result, err := configkit.LoadLayered(
|
||||
configkit.Input{Source: search, Filesystem: tc.mainContext},
|
||||
configkit.LayeredOptions[layeredFacadeConfig]{
|
||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{underlay}}},
|
||||
Overlay: tc.overlay, OverlayFallback: tc.overlayFallback,
|
||||
},
|
||||
)
|
||||
if tc.wantSources == 0 {
|
||||
if err == nil {
|
||||
t.Fatal("missing selected fallback succeeded")
|
||||
}
|
||||
diagnostics := err.(*configkit.LoadError).Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeImportInvalid {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sources := result.Sources()
|
||||
if result.Config.Count != tc.wantCount || len(sources) != tc.wantSources {
|
||||
t.Fatalf("config = %#v, sources = %#v", result.Config, sources)
|
||||
}
|
||||
if sources[0].Layer != configkit.LayerUnderlay || len(sources[0].EmptyImportSelections) != 0 {
|
||||
t.Fatalf("declaring source = %#v", sources[0])
|
||||
}
|
||||
if tc.mainContext != nil && (result.Config.Settings["companion"] != "loaded" || sources[2].Layer != configkit.LayerOverlay) {
|
||||
t.Fatalf("config = %#v, sources = %#v", result.Config, sources)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user