365 lines
15 KiB
Go
365 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.resolutionReplay = coalescedResolutionSnapshots(inspection.resolution)
|
|
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)
|
|
}
|
|
}
|