package engine import ( "io/fs" "reflect" "slices" "testing" "testing/fstest" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestInheritedOriginsKeepSourcePathsPresenceAndIndependentReplacement(t *testing.T) { parent := parseCompositionDocument(t, "source-1", `parent: enabled: false count: 0 optional: null items: [{file: message.txt}] settings: {shared: parent} `) child := parseCompositionDocument(t, "source-2", `child: extends: parent count: 3 settings: {own: child} `) composed, diagnostics := composeDocuments(parent, child) if len(diagnostics) != 0 { t.Fatal(diagnostics) } result, diagnostics := expandInheritanceWithOrigins(composed.root, composed.origins, defaultProjectionLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } for _, key := range []string{"enabled", "optional"} { path := Keys("child", key) value, found := documentAtPath(result.root, path) origin, hasOrigin := result.origins[path.String()] if !found || !hasOrigin || value == nil || len(origin.Locations) != 1 || origin.Locations[0].SourceID != "source-1" || len(projectInheritanceSteps(origin)) != 1 { t.Fatalf("inherited %s = %#v, %#v", key, value, origin) } } count := result.origins["/child/count"] if len(count.Locations) != 1 || count.Locations[0].SourceID != "source-2" || len(projectInheritanceSteps(count)) != 0 { t.Fatalf("direct replacement origin = %#v", count) } if len(result.replacements) != 1 || result.replacements[0].before.Text != "0" || result.replacements[0].after.Text != "3" { t.Fatalf("replacement accounting values = %#v", result.replacements) } settings := result.origins["/child/settings"] if len(settings.Locations) != 2 || settings.Locations[0].SourceID != "source-1" || settings.Locations[1].SourceID != "source-2" || len(projectInheritanceSteps(settings)) != 1 { t.Fatalf("composite mapping origin = %#v", settings) } if originContainedBy(result.origins["/child/enabled"], result.origins["/parent/enabled"]) || originContainedBy(result.origins["/parent/enabled"], result.origins["/child/enabled"]) { t.Fatal("direct and inherited evidence collapsed despite matching source coordinates") } original := result.origins["/child/enabled"] detached := cloneOrigin(original) detached.Locations[0].SourceID = "changed" detached.contributors[0].source.segments[0].key = "changed" stepsCopy := projectInheritanceSteps(original) stepsCopy[0].Locations[0].SourceID = "changed" if original.Locations[0].SourceID != "source-1" || original.contributors[0].source.String() != "/parent/enabled" || projectInheritanceSteps(original)[0].Locations[0].SourceID != "source-2" { t.Fatal("cloned origin or projected steps changed retained evidence") } resource := result.origins["/child/items/0/file"] steps := projectInheritanceSteps(resource) if len(steps) != 1 || steps[0].From.String() != "/parent/items/0/file" || steps[0].To.String() != "/child/items/0/file" || steps[0].Reference.String() != "/child/extends" || len(resource.Locations) != 1 || resource.Locations[0].SourceID != "source-1" { t.Fatalf("sequence resource origin = %#v, steps = %#v", resource, steps) } contexts := map[string]fs.FS{ "source-1": fstest.MapFS{"message.txt": &fstest.MapFile{Data: []byte("from parent")}}, "source-2": fstest.MapFS{"message.txt": &fstest.MapFile{Data: []byte("from child")}}, } data, err := fs.ReadFile(contexts[resource.Locations[0].SourceID], "message.txt") if err != nil || string(data) != "from parent" { t.Fatalf("inherited resource = %q, %v", data, err) } } func TestOriginMembershipPreservesLocationAndSourceCoverageRules(t *testing.T) { location := Location{SourceID: "source", Line: 4, Column: 2} otherCoordinates := location otherCoordinates.Line++ container := Origin{ Layer: LayerOverlay, Operation: ChangeNormalization, Locations: []Location{location}, } if !originContainedBy(container, container) { t.Fatal("identical location evidence was not contained") } coordinateDifference := container coordinateDifference.Locations = []Location{otherCoordinates} if originContainedBy(coordinateDifference, container) { t.Fatal("location containment ignored differing coordinates") } parent := Keys("parent") child := parent.Key("child") change := Change{Path: parent, Origin: container} descendantOrigin := Origin{ Layer: container.Layer, Operation: container.Operation, Locations: []Location{otherCoordinates}, } if !originRepresentedByChange(child, descendantOrigin, change) { t.Fatal("descendant source coverage rejected matching SourceID with different coordinates") } descendantOrigin.Locations[0].SourceID = "another-source" if originRepresentedByChange(child, descendantOrigin, change) { t.Fatal("descendant source coverage accepted an unrecorded SourceID") } withContributor := container withContributor.contributors = []inheritanceContributor{{source: parent, location: location}} if originContainedBy(withContributor, container) { t.Fatal("exact location containment ignored the contributor-empty mismatch") } if !originRepresentedByChange(child, withContributor, change) { t.Fatal("descendant source coverage was conflated with contributor containment") } } 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) { first := parseCompositionDocument(t, "source-1", `first: enabled: false settings: primary: {value: original} secondary: {extends: primary} `) second := parseCompositionDocument(t, "source-2", `first: {other: present} middle: {extends: first} last: extends: middle settings: primary: {value: replaced} `) composed, diagnostics := composeDocuments(first, second) if len(diagnostics) != 0 { t.Fatal(diagnostics) } result, diagnostics := expandInheritanceWithOrigins(composed.root, composed.origins, defaultProjectionLimits()) if len(diagnostics) != 0 { t.Fatal(diagnostics) } chain := projectInheritanceSteps(result.origins["/last/enabled"]) if len(chain) != 2 || chain[0].From.String() != "/first/enabled" || chain[0].To.String() != "/middle/enabled" || chain[1].From.String() != "/middle/enabled" || chain[1].To.String() != "/last/enabled" { t.Fatalf("chain = %#v", chain) } composite := result.origins["/last"] if len(composite.Locations) < 2 || len(composite.contributors) != len(composite.Locations) { t.Fatalf("composite contributors = %#v", composite) } seenSources := make(map[string]bool) for _, contributor := range composite.contributors { seenSources[contributor.location.SourceID] = true } if !seenSources["source-1"] || !seenSources["source-2"] { t.Fatalf("same-layer source contributors = %#v", composite.contributors) } nested := projectInheritanceSteps(result.origins["/last/settings/secondary/value"]) if len(nested) != 1 || nested[0].From.String() != "/last/settings/primary/value" || nested[0].To.String() != "/last/settings/secondary/value" { t.Fatalf("destination-specific nested transfer = %#v", nested) } if got := result.origins["/last/settings/secondary/value"].Locations[0].SourceID; got != "source-2" { t.Fatalf("nested replacement source = %q", got) } if original, found := documentAtPath(first, Keys("first", "settings", "primary", "value")); !found || original.Text != "original" { t.Fatal("expansion changed its input") } if inherited, found := documentAtPath(result.root, Keys("last", "settings", "secondary", "value")); !found || inherited.Kind != document.String || inherited.Text != "replaced" { t.Fatalf("nested inherited value = %#v, %v", inherited, found) } }