212 lines
9.2 KiB
Go
212 lines
9.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
)
|
|
|
|
func TestHistorySourceObservationsPreserveRawAndCompositeDefinitions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
baseline := parseDocumentForDecode(t, "defaults: {nullable: null}\n")
|
|
base := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n")
|
|
overlay := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n")
|
|
setHistoryDocumentSource(base, "base")
|
|
setHistoryDocumentSource(overlay, "overlay")
|
|
|
|
baseOrigins := make(map[string]Origin)
|
|
collectOverlayOrigins(baseOrigins, base, Path{}, LayerBase)
|
|
baseApplication := historyApplication{
|
|
layer: LayerBase, value: base,
|
|
origin: overlayOrigin(LayerBase, base.Location),
|
|
}
|
|
composition := &composedDocument{
|
|
root: document.Clone(base), origins: baseOrigins,
|
|
applications: []historyApplication{baseApplication},
|
|
}
|
|
overlayOrigins := make(map[string]Origin)
|
|
collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay)
|
|
assembly := newAssemblyAccumulator(composition)
|
|
overlayApplication := historyApplication{
|
|
layer: LayerOverlay, value: overlay,
|
|
origin: overlayOrigin(LayerOverlay, overlay.Location),
|
|
}
|
|
if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{overlayApplication}, LayerOverlay); len(diagnostics) != 0 {
|
|
t.Fatalf("apply overlay diagnostics = %#v", diagnostics)
|
|
}
|
|
|
|
builder := newHistoryBuilder()
|
|
builder.observeBaseline(baseline)
|
|
builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays)
|
|
model := builder.freeze()
|
|
|
|
defaultPath := Keys("defaults", "nullable")
|
|
baselineSelections := model.selectHistory(defaultPath)
|
|
if len(baselineSelections) != 1 || baselineSelections[0].phase != historyPhaseBaseline || baselineSelections[0].attribution.origin.Layer != LayerDefault || !baselineSelections[0].after {
|
|
t.Fatalf("baseline definition selections = %#v", baselineSelections)
|
|
}
|
|
if selections := model.selectHistory(Keys("defaults", "absent")); len(selections) != 0 {
|
|
t.Fatalf("absent baseline path selections = %#v", selections)
|
|
}
|
|
|
|
sameSelections := model.selectHistory(Keys("section", "same"))
|
|
var rawSources []string
|
|
var replacement bool
|
|
for _, selection := range sameSelections {
|
|
if selection.role == historyRoleContribution && selection.hasAttribution {
|
|
rawSources = append(rawSources, selection.attribution.origin.Locations[0].SourceID)
|
|
}
|
|
if selection.role == historyRoleOperation {
|
|
replacement = selection.before && selection.after
|
|
}
|
|
}
|
|
if !reflect.DeepEqual(rawSources, []string{"base", "overlay"}) || !replacement {
|
|
t.Fatalf("equal raw contributions and replacement = sources %#v, selections %#v", rawSources, sameSelections)
|
|
}
|
|
|
|
emptySelections := model.selectHistory(Keys("section", "empty"))
|
|
var composite *historyAttribution
|
|
for index := range emptySelections {
|
|
if emptySelections[index].role == historyRoleCompositeDefinition {
|
|
composite = &emptySelections[index].attribution
|
|
}
|
|
}
|
|
if composite == nil || !reflect.DeepEqual(locationSourceIDs(composite.origin.Locations), []string{"base", "overlay"}) {
|
|
t.Fatalf("empty mapping composite attribution = %#v; selections %#v", composite, emptySelections)
|
|
}
|
|
var emptySummary *historySourcePayload
|
|
for index := range model.sourcePayloads {
|
|
payload := &model.sourcePayloads[index]
|
|
if payload.kind == historySourceSummaryPayload && sameConcreteHistoryPath(payload.path, Keys("section", "empty")) {
|
|
emptySummary = payload
|
|
break
|
|
}
|
|
}
|
|
if emptySummary == nil || len(emptySummary.contributions) != 2 || emptySummary.contributions[0].id != 0 || emptySummary.contributions[1].id != 1 {
|
|
t.Fatalf("empty mapping summary contribution references = %#v", emptySummary)
|
|
}
|
|
}
|
|
|
|
func TestHistorySourceReplacementTracksConcreteBeforeAndAfterShapes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
base := parseDocumentForDecode(t, "shape: {'0': {leaf: old}}\n")
|
|
overlay := parseDocumentForDecode(t, "shape: [{leaf: new}]\n")
|
|
setHistoryDocumentSource(base, "base")
|
|
setHistoryDocumentSource(overlay, "overlay")
|
|
baseOrigins := make(map[string]Origin)
|
|
collectOverlayOrigins(baseOrigins, base, Path{}, LayerBase)
|
|
overlayOrigins := make(map[string]Origin)
|
|
collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay)
|
|
composition := &composedDocument{
|
|
root: document.Clone(base), origins: baseOrigins,
|
|
applications: []historyApplication{{layer: LayerBase, value: base, origin: overlayOrigin(LayerBase, base.Location)}},
|
|
}
|
|
assembly := newAssemblyAccumulator(composition)
|
|
if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{{layer: LayerOverlay, value: overlay, origin: overlayOrigin(LayerOverlay, overlay.Location)}}, LayerOverlay); len(diagnostics) != 0 {
|
|
t.Fatalf("apply overlay diagnostics = %#v", diagnostics)
|
|
}
|
|
|
|
if len(assembly.current.overlays) != 1 {
|
|
t.Fatalf("replacement records = %#v", assembly.current.overlays)
|
|
}
|
|
record := assembly.current.overlays[0]
|
|
if len(record.displaced) != 3 || len(record.introduced) != 3 {
|
|
t.Fatalf("displaced/introduction path counts = %d/%d", len(record.displaced), len(record.introduced))
|
|
}
|
|
if got := record.introduced[2].path; !sameConcreteHistoryPath(got, Keys("shape").Index(0).Key("leaf")) {
|
|
t.Fatalf("introduced descendant path = %#v", got)
|
|
}
|
|
builder := newHistoryBuilder()
|
|
builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays)
|
|
model := builder.freeze()
|
|
modelIntroduction := model.sourcePayloads[2].overlay.introduced[2]
|
|
|
|
cloned := cloneOverlayRecords(assembly.current.overlays)
|
|
record.introduced[2].path.segments[2].key = "mutated"
|
|
record.introduced[2].origin.Locations[0].SourceID = "mutated"
|
|
if got := cloned[0].introduced[2]; !sameConcreteHistoryPath(got.path, Keys("shape").Index(0).Key("leaf")) || got.origin.Locations[0].SourceID != "overlay" {
|
|
t.Fatalf("cloned introduced evidence changed with producer: %#v", got)
|
|
}
|
|
if !sameConcreteHistoryPath(modelIntroduction.path, Keys("shape").Index(0).Key("leaf")) || modelIntroduction.origin.Locations[0].SourceID != "overlay" {
|
|
t.Fatalf("model-owned introduced evidence changed with producer: %#v", modelIntroduction)
|
|
}
|
|
|
|
oldPath := Keys("shape", "0", "leaf")
|
|
newPath := Keys("shape").Index(0).Key("leaf")
|
|
oldSelections := model.selectHistory(oldPath)
|
|
newSelections := model.selectHistory(newPath)
|
|
if len(oldSelections) < 2 || oldSelections[len(oldSelections)-1].role != historyRoleOperation || !oldSelections[len(oldSelections)-1].before || oldSelections[len(oldSelections)-1].after {
|
|
t.Fatalf("removed key path selections = %#v", oldSelections)
|
|
}
|
|
if len(newSelections) < 2 || newSelections[len(newSelections)-1].role != historyRoleOperation || newSelections[len(newSelections)-1].before || !newSelections[len(newSelections)-1].after {
|
|
t.Fatalf("introduced index path selections = %#v", newSelections)
|
|
}
|
|
parsed, err := ParsePath("/shape/0/leaf")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
parsedSelections := model.selectHistory(parsed)
|
|
if len(parsedSelections) == 0 || !parsedSelections[len(parsedSelections)-1].before || !parsedSelections[len(parsedSelections)-1].after {
|
|
t.Fatalf("parsed path did not combine replacement sides: %#v", parsedSelections)
|
|
}
|
|
}
|
|
|
|
func TestOverlayReplacementCapturesIntroducedPathOrigins(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
existing := parseDocumentForDecode(t, "old: value\n")
|
|
incoming := parseDocumentForDecode(t, "new: value\n")
|
|
setHistoryDocumentSource(existing, "base")
|
|
setHistoryDocumentSource(incoming, "overlay")
|
|
path := Keys("section")
|
|
origins := make(map[string]Origin)
|
|
collectOverlayOrigins(origins, existing, path, LayerBase)
|
|
incomingOrigins := make(map[string]Origin)
|
|
collectOverlayOrigins(incomingOrigins, incoming, path, LayerOverlay)
|
|
result := &composedDocument{
|
|
origins: origins,
|
|
}
|
|
recordOverlayReplacement(result, path, existing, incoming, LayerOverlay, true, incomingOrigins)
|
|
if len(result.overlays) != 1 || len(result.overlays[0].introduced) != 2 {
|
|
t.Fatalf("replacement introduced evidence = %#v", result.overlays)
|
|
}
|
|
introduced := result.overlays[0].introduced[1]
|
|
if !sameConcreteHistoryPath(introduced.path, path.Key("new")) || introduced.origin.Layer != LayerOverlay || introduced.origin.Locations[0].SourceID != "overlay" {
|
|
t.Fatalf("introduced replacement evidence = %#v", introduced)
|
|
}
|
|
}
|
|
|
|
func TestHistorySourceEvidenceLeavesEmptySourcePhaseUnpopulated(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
builder := newHistoryBuilder()
|
|
builder.observeBaseline(parseDocumentForDecode(t, "defaults: {enabled: true}\n"))
|
|
builder.observeSourceEvidence(nil, nil, nil, nil)
|
|
model := builder.freeze()
|
|
if len(model.sourcePayloads) != 0 {
|
|
t.Fatalf("empty source input retained payloads: %#v", model.sourcePayloads)
|
|
}
|
|
for _, observation := range model.observations {
|
|
if observation.phase == historyPhaseSources {
|
|
t.Fatalf("empty source phase has an observation: %#v", observation)
|
|
}
|
|
}
|
|
}
|
|
|
|
func setHistoryDocumentSource(root *document.Node, sourceID string) {
|
|
walkDocument(root, Path{}, func(_ Path, node *document.Node) {
|
|
node.Location.SourceID = sourceID
|
|
})
|
|
}
|
|
|
|
func locationSourceIDs(locations []Location) []string {
|
|
ids := make([]string, len(locations))
|
|
for index, location := range locations {
|
|
ids[index] = location.SourceID
|
|
}
|
|
return ids
|
|
}
|