Files
ConfigKit/internal/engine/inheritance_history_test.go

364 lines
15 KiB
Go

package engine
import (
"bytes"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestNestedInheritanceHistoryRetainsUnresolvedTemplates(t *testing.T) {
input := `presets:
base:
settings:
primary: {width: 10}
secondary: {extends: primary}
custom:
extends: base
settings:
primary: {width: 20}
grandchild:
extends: custom
settings:
primary: {width: 30}
`
loaded, err := Load[map[string]any](Bytes("nested", []byte(input)), testSecretBinding())
if err != nil {
t.Fatal(err)
}
for name, width := range map[string]string{"custom": "20", "grandchild": "30"} {
t.Run(name, func(t *testing.T) {
root := Keys("presets", name)
value, _ := loaded.Inspection.Explain(root.Key("settings").Key("secondary").Key("width"))
introduced := false
for _, change := range value.History {
if change.Origin.Operation == ChangeInheritance && !change.Before.Present && change.After.YAML == width+"\n" {
introduced = true
}
}
if !introduced || value.Effective.YAML != width+"\n" {
t.Fatalf("missing nested introduction: %#v", value)
}
controlPath := root.Key("settings").Key("secondary").Key("extends")
control, _ := loaded.Inspection.Explain(controlPath)
arrived, consumed := false, false
for _, change := range control.History {
arrived = arrived || change.After.YAML == "primary\n"
consumed = consumed || (change.Before.YAML == "primary\n" && !change.After.Present)
}
if !arrived || !consumed || control.Effective.Present {
t.Fatalf("missing control history: %#v", control)
}
container, _ := loaded.Inspection.Explain(root)
unresolved := false
for _, change := range container.History {
if change.Origin.Operation == ChangeInheritance && strings.Contains(change.After.YAML, "extends: primary") {
unresolved = true
}
}
if !unresolved || strings.Contains(container.Effective.YAML, "extends:") {
t.Fatalf("invalid enclosing snapshot: %#v", container)
}
if _, present := loaded.Inspection.Metadata().Lookup(controlPath); present {
t.Fatal("control is effective")
}
if len(loaded.Inspection.Metadata().History(controlPath)) != len(control.History) {
t.Fatal("metadata and explanation disagree")
}
})
}
}
func TestNestedInheritanceHistoryInsideSequence(t *testing.T) {
input := `base:
items:
- first: {value: inherited}
second: {extends: first}
child: {extends: base}
`
loaded, err := Load[map[string]any](Bytes("sequence", []byte(input)), testSecretBinding())
if err != nil {
t.Fatal(err)
}
path := Keys("child", "items").Index(0).Key("second")
control, _ := loaded.Inspection.Explain(path.Key("extends"))
consumed := false
for _, change := range control.History {
consumed = consumed || (change.Before.YAML == "first\n" && !change.After.Present)
}
if !consumed || control.Effective.Present {
t.Fatalf("sequence control history = %#v", control)
}
value, _ := loaded.Inspection.Explain(path.Key("value"))
introduced := false
for _, change := range value.History {
introduced = introduced || (!change.Before.Present && change.After.YAML == "inherited\n")
}
if !introduced || value.Effective.YAML != "inherited\n" {
t.Fatalf("sequence value history = %#v", value)
}
}
func inheritanceInspectionForTest(t *testing.T, sources ...*document.Node) (*Inspection, *inheritanceScope) {
t.Helper()
composed, diagnostics := composeDocuments(sources...)
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
return inheritanceInspectionFromComposition(t, composed)
}
func inheritanceInspectionFromComposition(t *testing.T, composed *composedDocument) (*Inspection, *inheritanceScope) {
t.Helper()
expanded, diagnostics := expandInheritanceWithOrigins(composed.root, composed.origins, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
baseline := &document.Node{Kind: document.Mapping}
presence := make(map[string]inputPresence)
collectPresence(expanded.root, Path{}, presence)
decoded := decodedConfiguration{
baseline: baseline, final: expanded.root, presence: presence, origins: expanded.origins,
}
metadata := metadataFromDecoded(decoded)
applyInheritanceMetadata(&metadata, composed.root, composed.origins, composed.overlays, expanded)
finalizeMetadataPaths(&metadata, expanded.root, composed.applications, baseline, composed.root)
return &Inspection{
metadata: metadata, baseline: baseline, input: expanded.root,
inputOrigins: expanded.origins, final: expanded.root,
applications: composed.applications, inheritance: expanded.operations,
}, expanded
}
func TestInheritanceHistoryFollowsFinalParentAfterSourceOverlay(t *testing.T) {
base, diagnostics := composeDocuments(parseCompositionDocument(t, "source-1", "parent: {value: original}\nchild: {extends: parent}\n"))
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
assembly := newAssemblyAccumulator(base)
if diagnostics := assembly.applyOverlay(parseCompositionDocument(t, "source-2", "parent: {value: latest}\n"), nil, nil, LayerOverlay); len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
inspection, _ := inheritanceInspectionFromComposition(t, assembly.current)
path := Keys("child", "value")
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.YAML != "latest\n" ||
len(explanation.History) == 0 || explanation.History[len(explanation.History)-1].Origin.Locations[0].SourceID != "source-2" {
t.Fatalf("overlaid parent contribution = %#v, %v", explanation, found)
}
parentHistory := inspection.Metadata().History(Keys("parent", "value"))
if len(parentHistory) < 2 || parentHistory[0].Origin.Layer != LayerBase ||
parentHistory[len(parentHistory)-1].Origin.Layer != LayerOverlay {
t.Fatalf("parent source history = %#v", parentHistory)
}
}
func TestInheritanceHistoryPreservesCancellationAndSupersededDirective(t *testing.T) {
base, diagnostics := composeDocuments(parseCompositionDocument(t, "source-1", "parent: {value: one}\nchild: {extends: missing, value: own}\n"))
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
assembly := newAssemblyAccumulator(base)
if diagnostics := assembly.applyOverlay(parseCompositionDocument(t, "source-2", "child: {extends: null}\n"), nil, nil, LayerOverlay); len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
inspection, _ := inheritanceInspectionFromComposition(t, assembly.current)
control := Keys("child", inheritanceKey)
history := inspection.Metadata().History(control)
if len(history) < 3 || history[0].Origin.Locations[0].SourceID != "source-1" ||
history[1].Origin.Locations[0].SourceID != "source-2" ||
history[len(history)-1].Origin.Operation != ChangeInheritance {
t.Fatalf("superseded and cancelled directive history = %#v", history)
}
explanation, found := inspection.Explain(control)
if !found || explanation.Effective.Present || len(explanation.History) < 3 ||
explanation.History[len(explanation.History)-1].After.Present {
t.Fatalf("cancelled directive explanation = %#v, %v", explanation, found)
}
value, found := inspection.Explain(Keys("child", "value"))
if !found || value.Effective.YAML != "own\n" {
t.Fatalf("cancelled relationship changed own value = %#v, %v", value, found)
}
}
func TestInheritanceHistoryProjectsNestedDestinationValues(t *testing.T) {
inspection, _ := inheritanceInspectionForTest(t, parseCompositionDocument(t, "source-1", `base:
settings:
primary: {value: old}
secondary: {extends: primary}
custom:
extends: base
settings:
primary: {value: new}
`))
path := Keys("custom", "settings", "secondary", "value")
history := inspection.Metadata().History(path)
structural := 0
for _, change := range history {
if change.Origin.Operation == ChangeInheritance {
structural++
}
}
if structural == 0 {
t.Fatalf("nested destination history = %#v", history)
}
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.YAML != "new\n" ||
len(explanation.Origin.Inheritance) == 0 ||
explanation.Origin.Inheritance[len(explanation.Origin.Inheritance)-1].To.String() != path.String() {
t.Fatalf("nested destination explanation = %#v, %v", explanation, found)
}
for _, change := range explanation.History {
if change.Origin.Operation == ChangeDecoding {
t.Fatalf("nested inheritance was attributed to decoding: %#v", explanation.History)
}
}
}
func TestInheritanceHistoryShowsParentAndEqualChildContribution(t *testing.T) {
inspection, _ := inheritanceInspectionForTest(t,
parseCompositionDocument(t, "source-1", "parent: {value: same}\n"),
parseCompositionDocument(t, "source-2", "child: {extends: parent, value: same}\n"))
path := Keys("child", "value")
history := inspection.Metadata().History(path)
var structural []Change
for _, change := range history {
if change.Origin.Operation == ChangeInheritance {
structural = append(structural, change)
}
}
if len(structural) != 2 || structural[0].Origin.Locations[0].SourceID != "source-1" ||
structural[1].Origin.Locations[0].SourceID != "source-2" ||
len(structural[0].Dependencies) != 2 || structural[0].Dependencies[0].String() != "/parent" ||
len(structural[0].Origin.Inheritance) != 1 {
t.Fatalf("equal-valued structural history = %#v", history)
}
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.YAML != "same\n" {
t.Fatalf("explanation = %#v, %v", explanation, found)
}
var equalEvents int
for _, change := range explanation.History {
if change.Origin.Operation == ChangeDecoding {
t.Fatalf("inheritance was attributed to decoding: %#v", explanation.History)
}
if change.Origin.Operation == ChangeInheritance && change.Before.YAML == change.After.YAML {
equalEvents++
}
}
if equalEvents != 2 {
t.Fatalf("equal parent and child operations = %#v", explanation.History)
}
parentHistory := inspection.Metadata().History(Keys("parent", "value"))
for _, change := range parentHistory {
if change.Origin.Operation == ChangeInheritance {
t.Fatalf("source definition acquired a destination operation: %#v", parentHistory)
}
}
}
func TestInheritanceHistoryRetainsControlsAndDisplacedPaths(t *testing.T) {
inspection, _ := inheritanceInspectionForTest(t,
parseCompositionDocument(t, "source-1", "parent: {branch: {detail: inherited}}\n"),
parseCompositionDocument(t, "source-2", "child: {extends: parent, branch: replaced}\n"))
control := Keys("child", inheritanceKey)
controlHistory := inspection.Metadata().History(control)
if len(controlHistory) < 2 || controlHistory[len(controlHistory)-1].Origin.Operation != ChangeInheritance {
t.Fatalf("control history = %#v", controlHistory)
}
controlExplanation, found := inspection.Explain(control)
if !found || controlExplanation.Effective.Present ||
controlExplanation.History[len(controlExplanation.History)-1].After.Present {
t.Fatalf("consumed control = %#v, %v", controlExplanation, found)
}
path := Keys("child", "branch", "detail")
if _, found := inspection.Metadata().Lookup(path); found {
t.Fatal("displaced descendant remained effective")
}
listed := false
for _, historical := range inspection.Metadata().HistoricalPaths() {
if historical.String() == path.String() {
listed = true
}
}
if !listed {
t.Fatal("displaced inherited descendant was not enumerable")
}
history := inspection.Metadata().History(path)
if len(history) < 2 || history[len(history)-1].Origin.Operation != ChangeInheritance {
t.Fatalf("displaced descendant history = %#v", history)
}
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.Present {
t.Fatalf("displaced descendant explanation = %#v, %v", explanation, found)
}
var sawInherited, sawRemoval bool
for _, change := range explanation.History {
if change.Origin.Operation != ChangeInheritance {
continue
}
sawInherited = sawInherited || change.After.Present
sawRemoval = sawRemoval || (change.Before.Present && !change.After.Present)
}
if !sawInherited || !sawRemoval {
t.Fatalf("displaced value sequence = %#v", explanation.History)
}
var rendered bytes.Buffer
if err := inspection.WriteExplanation(&rendered, path); err != nil ||
!strings.Contains(rendered.String(), "structural inheritance") {
t.Fatalf("rendered structural explanation = %q, %v", rendered.String(), err)
}
}
func TestInheritanceHistoryPrecedesApplicationResolution(t *testing.T) {
inspection, _ := inheritanceInspectionForTest(t,
parseCompositionDocument(t, "source-1", "parent: {value: original}\nchild: {extends: parent}\n"))
path := Keys("child", "value")
replacement := &document.Node{Kind: document.String, Text: "derived"}
snapshot := resolutionSnapshot{
change: Change{Path: path, Origin: Origin{Layer: LayerResolution, Operation: ChangeNormalization}},
after: replacement,
}
final := document.Clone(inspection.final)
replaceHistoryPath(&final, path, replacement)
inspection.final = final
inspection.resolution = []resolutionSnapshot{snapshot}
inspection.metadata.history[path.String()] = append(inspection.metadata.history[path.String()], snapshot.change)
inspection.metadata.fields[path.String()] = FieldInfo{Explicit: true, Origin: snapshot.change.Origin}
history := inspection.Metadata().History(path)
if len(history) < 2 || history[len(history)-2].Origin.Operation != ChangeInheritance ||
history[len(history)-1].Origin.Operation != ChangeNormalization {
t.Fatalf("resolution order = %#v", history)
}
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.YAML != "derived\n" ||
explanation.History[len(explanation.History)-1].Origin.Operation != ChangeNormalization {
t.Fatalf("resolved explanation = %#v, %v", explanation, found)
}
}
func TestHistoryNodeBudgetCountsSharedInheritanceEvidenceOnce(t *testing.T) {
shared := &document.Node{Kind: document.String, Text: "shared"}
extra := &document.Node{Kind: document.String, Text: "extra"}
expansion := &inheritanceScope{
root: shared,
operations: []inheritanceOperation{{parent: shared, effective: shared}},
replacements: []inheritanceReplacement{{before: shared, after: extra}},
}
if !historyNodesWithin(2, nil, nil, nil, nil, nil, expansion) ||
historyNodesWithin(1, nil, nil, nil, nil, nil, expansion) {
t.Fatal("shared evidence node accounting is incorrect")
}
}
func TestInheritanceExpansionEnforcesRetainedHistoryBudget(t *testing.T) {
input := parseCompositionDocument(t, "source-1", "parent: {value: original}\nchild: {extends: parent, value: replacement}\n")
if _, diagnostics := expandInheritanceWithBudgets(input, nil, defaultProjectionLimits(), 2); len(diagnostics) != 1 || diagnostics[0].Code != CodeExplainLimitExceeded {
t.Fatalf("retained history limit = %#v", diagnostics)
}
if _, diagnostics := expandInheritanceWithBudgets(input, nil, defaultProjectionLimits(), 100); len(diagnostics) != 0 {
t.Fatalf("sufficient retained history budget = %#v", diagnostics)
}
}