Index inherited step membership
This commit is contained in:
@@ -394,7 +394,7 @@ Roadmap candidate 7. Owners: publication, inspection, document/snapshot/history
|
|||||||
Validate: `go test ./internal/engine -run 'Path|History|Explain|Effective|Independent|Parallel'`,
|
Validate: `go test ./internal/engine -run 'Path|History|Explain|Effective|Independent|Parallel'`,
|
||||||
engine race tests, and the everyday check.
|
engine race tests, and the everyday check.
|
||||||
|
|
||||||
## Stage 16: Index materialized inheritance-step membership
|
## Stage 16: Index materialized inheritance-step membership — Complete
|
||||||
|
|
||||||
Roadmap candidate 8. Owner: engine `inheritance_origin.go`.
|
Roadmap candidate 8. Owner: engine `inheritance_origin.go`.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||||
)
|
)
|
||||||
@@ -74,10 +75,44 @@ func copyInheritanceSubtree(target, incoming map[string]Origin, node *document.N
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type inheritanceStepKey struct {
|
||||||
|
from string
|
||||||
|
to string
|
||||||
|
reference string
|
||||||
|
locations string
|
||||||
|
}
|
||||||
|
|
||||||
|
func inheritanceStepKeyForPaths(from, to, reference Path, locations []Location) inheritanceStepKey {
|
||||||
|
return inheritanceStepKey{
|
||||||
|
from: from.String(), to: to.String(), reference: reference.String(),
|
||||||
|
locations: inheritanceLocationsKey(locations),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func inheritanceLocationsKey(locations []Location) string {
|
||||||
|
encoded := strconv.AppendInt(nil, int64(len(locations)), 10)
|
||||||
|
encoded = append(encoded, ';')
|
||||||
|
for _, location := range locations {
|
||||||
|
encoded = strconv.AppendInt(encoded, int64(len(location.SourceID)), 10)
|
||||||
|
encoded = append(encoded, ':')
|
||||||
|
encoded = append(encoded, location.SourceID...)
|
||||||
|
encoded = append(encoded, ';')
|
||||||
|
encoded = strconv.AppendInt(encoded, int64(location.Line), 10)
|
||||||
|
encoded = append(encoded, ';')
|
||||||
|
encoded = strconv.AppendInt(encoded, int64(location.Column), 10)
|
||||||
|
encoded = append(encoded, ';')
|
||||||
|
}
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
// Materialize concrete steps only for an inspection record. The linked private
|
// Materialize concrete steps only for an inspection record. The linked private
|
||||||
// traces remain compact when one parent supplies many descendant values.
|
// traces remain compact when one parent supplies many descendant values.
|
||||||
func projectInheritanceSteps(origin Origin) []InheritanceStep {
|
func projectInheritanceSteps(origin Origin) []InheritanceStep {
|
||||||
steps := cloneInheritanceSteps(origin.Inheritance)
|
steps := cloneInheritanceSteps(origin.Inheritance)
|
||||||
|
seen := make(map[inheritanceStepKey]struct{}, len(steps))
|
||||||
|
for _, step := range steps {
|
||||||
|
seen[inheritanceStepKeyForPaths(step.From, step.To, step.Reference, step.Locations)] = struct{}{}
|
||||||
|
}
|
||||||
for _, contributor := range origin.contributors {
|
for _, contributor := range origin.contributors {
|
||||||
var reverse []*inheritanceTrace
|
var reverse []*inheritanceTrace
|
||||||
for link := contributor.trace; link != nil; link = link.previous {
|
for link := contributor.trace; link != nil; link = link.previous {
|
||||||
@@ -91,17 +126,13 @@ func projectInheritanceSteps(origin Origin) []InheritanceStep {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
next := appendInheritancePath(transfer.to, relative)
|
next := appendInheritancePath(transfer.to, relative)
|
||||||
step := InheritanceStep{
|
key := inheritanceStepKeyForPaths(current, next, transfer.reference, transfer.locations)
|
||||||
From: current.clone(), To: next.clone(), Reference: transfer.reference.clone(),
|
if _, found := seen[key]; !found {
|
||||||
Locations: cloneLocations(transfer.locations),
|
seen[key] = struct{}{}
|
||||||
}
|
steps = append(steps, InheritanceStep{
|
||||||
if !slices.ContainsFunc(steps, func(existing InheritanceStep) bool {
|
From: current.clone(), To: next.clone(), Reference: transfer.reference.clone(),
|
||||||
return existing.From.String() == step.From.String() &&
|
Locations: cloneLocations(transfer.locations),
|
||||||
existing.To.String() == step.To.String() &&
|
})
|
||||||
existing.Reference.String() == step.Reference.String() &&
|
|
||||||
slices.Equal(existing.Locations, step.Locations)
|
|
||||||
}) {
|
|
||||||
steps = append(steps, step)
|
|
||||||
}
|
}
|
||||||
current = next
|
current = next
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
|
||||||
@@ -85,6 +87,120 @@ func TestInheritedOriginsKeepSourcePathsPresenceAndIndependentReplacement(t *tes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProjectedInheritanceStepsMatchScanAndRetainOwnership(t *testing.T) {
|
||||||
|
sourcePath := Keys("source", "a/b", "01")
|
||||||
|
firstLocations := []Location{{SourceID: "first;:source", Line: -1, Column: 2}}
|
||||||
|
secondLocations := []Location{
|
||||||
|
{SourceID: "second:;source", Line: 3, Column: -4},
|
||||||
|
{SourceID: "", Line: -5, Column: 6},
|
||||||
|
}
|
||||||
|
firstTransfer := inheritanceTransfer{
|
||||||
|
from: Keys("source"), to: Keys("middle"), reference: Keys("middle", "extends"), locations: firstLocations,
|
||||||
|
}
|
||||||
|
secondTransfer := inheritanceTransfer{
|
||||||
|
from: Keys("middle"), to: Keys("target"), reference: Keys("target", "extends"), locations: secondLocations,
|
||||||
|
}
|
||||||
|
firstTrace := &inheritanceTrace{transfer: &firstTransfer}
|
||||||
|
chain := &inheritanceTrace{previous: firstTrace, transfer: &secondTransfer}
|
||||||
|
firstStep := InheritanceStep{
|
||||||
|
From: sourcePath, To: Keys("middle", "a/b", "01"), Reference: firstTransfer.reference, Locations: firstLocations,
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicateFirstTransfer := firstTransfer
|
||||||
|
duplicateSecondTransfer := secondTransfer
|
||||||
|
duplicateChain := &inheritanceTrace{
|
||||||
|
previous: &inheritanceTrace{transfer: &duplicateFirstTransfer},
|
||||||
|
transfer: &duplicateSecondTransfer,
|
||||||
|
}
|
||||||
|
reversedLocations := []Location{secondLocations[1], secondLocations[0]}
|
||||||
|
reorderedSecondTransfer := secondTransfer
|
||||||
|
reorderedSecondTransfer.locations = reversedLocations
|
||||||
|
reorderedChain := &inheritanceTrace{
|
||||||
|
previous: &inheritanceTrace{transfer: &duplicateFirstTransfer},
|
||||||
|
transfer: &reorderedSecondTransfer,
|
||||||
|
}
|
||||||
|
origin := Origin{
|
||||||
|
Inheritance: []InheritanceStep{firstStep, firstStep},
|
||||||
|
contributors: []inheritanceContributor{
|
||||||
|
{source: sourcePath, trace: chain},
|
||||||
|
{source: sourcePath, trace: duplicateChain},
|
||||||
|
{source: sourcePath, trace: reorderedChain},
|
||||||
|
{source: sourcePath, trace: chain},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
want := projectInheritanceStepsByScan(origin)
|
||||||
|
got := projectInheritanceSteps(origin)
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("projected steps differ from ordered scan:\n got: %#v\nwant: %#v", got, want)
|
||||||
|
}
|
||||||
|
if len(got) != 4 || got[0].From.String() != "/source/a~1b/01" || got[0].To.String() != "/middle/a~1b/01" ||
|
||||||
|
!reflect.DeepEqual(got[0], got[1]) || got[2].To.String() != "/target/a~1b/01" ||
|
||||||
|
!reflect.DeepEqual(got[2].Locations, secondLocations) || !reflect.DeepEqual(got[3].Locations, reversedLocations) {
|
||||||
|
t.Fatalf("unexpected duplicate/order/escape projection: %#v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
got[0].From.segments[0].key = "mutated"
|
||||||
|
got[2].Locations[0].SourceID = "mutated"
|
||||||
|
if repeated := projectInheritanceSteps(origin); !reflect.DeepEqual(repeated, want) {
|
||||||
|
t.Fatalf("returned steps mutated retained evidence:\n got: %#v\nwant: %#v", repeated, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInheritanceLocationsKeyEncodesOrderedLengthPrefixedValues(t *testing.T) {
|
||||||
|
if inheritanceLocationsKey(nil) != inheritanceLocationsKey([]Location{}) {
|
||||||
|
t.Fatal("nil and empty location lists should encode equally")
|
||||||
|
}
|
||||||
|
values := [][]Location{
|
||||||
|
{{SourceID: "a;1;-2;", Line: -3, Column: 4}},
|
||||||
|
{{SourceID: "a", Line: 1, Column: -2}, {SourceID: ";1;-2;", Line: -3, Column: 4}},
|
||||||
|
{{SourceID: ";1;-2;", Line: -3, Column: 4}, {SourceID: "a", Line: 1, Column: -2}},
|
||||||
|
{{SourceID: "é", Line: -3, Column: 4}},
|
||||||
|
{{SourceID: "e", Line: -3, Column: 4}},
|
||||||
|
}
|
||||||
|
keys := make(map[string]int, len(values))
|
||||||
|
for index, locations := range values {
|
||||||
|
key := inheritanceLocationsKey(locations)
|
||||||
|
if previous, found := keys[key]; found {
|
||||||
|
t.Fatalf("location lists %d and %d encoded identically", previous, index)
|
||||||
|
}
|
||||||
|
keys[key] = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectInheritanceStepsByScan(origin Origin) []InheritanceStep {
|
||||||
|
steps := cloneInheritanceSteps(origin.Inheritance)
|
||||||
|
for _, contributor := range origin.contributors {
|
||||||
|
var reverse []*inheritanceTrace
|
||||||
|
for link := contributor.trace; link != nil; link = link.previous {
|
||||||
|
reverse = append(reverse, link)
|
||||||
|
}
|
||||||
|
current := contributor.source
|
||||||
|
for index := len(reverse) - 1; index >= 0; index-- {
|
||||||
|
transfer := reverse[index].transfer
|
||||||
|
relative, ok := relativeHistoryPath(transfer.from, current)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
next := appendInheritancePath(transfer.to, relative)
|
||||||
|
step := InheritanceStep{
|
||||||
|
From: current.clone(), To: next.clone(), Reference: transfer.reference.clone(),
|
||||||
|
Locations: cloneLocations(transfer.locations),
|
||||||
|
}
|
||||||
|
if !slices.ContainsFunc(steps, func(existing InheritanceStep) bool {
|
||||||
|
return existing.From.String() == step.From.String() &&
|
||||||
|
existing.To.String() == step.To.String() &&
|
||||||
|
existing.Reference.String() == step.Reference.String() &&
|
||||||
|
slices.Equal(existing.Locations, step.Locations)
|
||||||
|
}) {
|
||||||
|
steps = append(steps, step)
|
||||||
|
}
|
||||||
|
current = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
func TestInheritedOriginsRetainChainsAndNestedDestinations(t *testing.T) {
|
func TestInheritedOriginsRetainChainsAndNestedDestinations(t *testing.T) {
|
||||||
first := parseCompositionDocument(t, "source-1", `first:
|
first := parseCompositionDocument(t, "source-1", `first:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
Reference in New Issue
Block a user