Index combined diff sensitivity
This commit is contained in:
48
internal/engine/diff_sensitivity.go
Normal file
48
internal/engine/diff_sensitivity.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package engine
|
||||
|
||||
type diffSensitivity struct {
|
||||
prefixes pathPrefixIndex
|
||||
pointers []string
|
||||
}
|
||||
|
||||
func newDiffSensitivity(left, right map[string]bool) *diffSensitivity {
|
||||
sensitivity := &diffSensitivity{}
|
||||
seen := make(map[string]struct{}, len(left)+len(right))
|
||||
for _, classifications := range []map[string]bool{left, right} {
|
||||
for pointer, classified := range classifications {
|
||||
if !classified {
|
||||
continue
|
||||
}
|
||||
if _, found := seen[pointer]; found {
|
||||
continue
|
||||
}
|
||||
seen[pointer] = struct{}{}
|
||||
id := len(sensitivity.pointers)
|
||||
sensitivity.pointers = append(sensitivity.pointers, pointer)
|
||||
sensitivity.prefixes.insert(pointer, id)
|
||||
}
|
||||
}
|
||||
return sensitivity
|
||||
}
|
||||
|
||||
func (sensitivity *diffSensitivity) isSensitiveAtOrAbove(pointer string) bool {
|
||||
if sensitivity == nil {
|
||||
return false
|
||||
}
|
||||
found := false
|
||||
sensitivity.prefixes.visitAncestors(pointer, func(int) { found = true })
|
||||
return found
|
||||
}
|
||||
|
||||
func (sensitivity *diffSensitivity) hasStrictDescendant(pointer string) bool {
|
||||
if sensitivity == nil {
|
||||
return false
|
||||
}
|
||||
found := false
|
||||
sensitivity.prefixes.visitDescendants(pointer, func(id int) {
|
||||
if sensitivity.pointers[id] != pointer {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
90
internal/engine/diff_sensitivity_test.go
Normal file
90
internal/engine/diff_sensitivity_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDiffSensitivityCombinesClassificationsWithoutMutation(t *testing.T) {
|
||||
left := map[string]bool{
|
||||
"/a": true,
|
||||
"/a/b": true,
|
||||
"/historical/removed": true,
|
||||
"/cross~1key": true,
|
||||
"/literal~01slash": true,
|
||||
"/items/01": true,
|
||||
"/items/+1": false,
|
||||
"/false-left": false,
|
||||
}
|
||||
right := map[string]bool{
|
||||
"/a/b": true,
|
||||
"/right-only": true,
|
||||
"/false-right": false,
|
||||
}
|
||||
leftBefore, rightBefore := maps.Clone(left), maps.Clone(right)
|
||||
sensitivity := newDiffSensitivity(left, right)
|
||||
|
||||
for _, test := range []struct {
|
||||
pointer string
|
||||
want bool
|
||||
}{
|
||||
{pointer: "/a", want: true},
|
||||
{pointer: "/a/b", want: true},
|
||||
{pointer: "/a/b/c", want: true},
|
||||
{pointer: "/ab", want: false},
|
||||
{pointer: "/ab/c", want: false},
|
||||
{pointer: "/historical/removed", want: true},
|
||||
{pointer: "/historical/removed/child", want: true},
|
||||
{pointer: "/historical", want: false},
|
||||
{pointer: "/right-only", want: true},
|
||||
{pointer: "/false-left", want: false},
|
||||
{pointer: "/false-right", want: false},
|
||||
{pointer: "/cross~1key", want: true},
|
||||
{pointer: "/cross/key", want: false},
|
||||
{pointer: "/literal~01slash", want: true},
|
||||
{pointer: "/literal~1slash", want: false},
|
||||
{pointer: "/items/01", want: true},
|
||||
{pointer: "/items/1", want: false},
|
||||
{pointer: "/items/+1", want: false},
|
||||
} {
|
||||
if got := sensitivity.isSensitiveAtOrAbove(test.pointer); got != test.want {
|
||||
t.Errorf("isSensitiveAtOrAbove(%q) = %v, want %v", test.pointer, got, test.want)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
pointer string
|
||||
want bool
|
||||
}{
|
||||
{pointer: "", want: true},
|
||||
{pointer: "/a", want: true},
|
||||
{pointer: "/a/b", want: false},
|
||||
{pointer: "/ab", want: false},
|
||||
{pointer: "/historical", want: true},
|
||||
{pointer: "/right-only", want: false},
|
||||
{pointer: "/cross~1key", want: false},
|
||||
} {
|
||||
if got := sensitivity.hasStrictDescendant(test.pointer); got != test.want {
|
||||
t.Errorf("hasStrictDescendant(%q) = %v, want %v", test.pointer, got, test.want)
|
||||
}
|
||||
}
|
||||
|
||||
if !maps.Equal(left, leftBefore) || !maps.Equal(right, rightBefore) {
|
||||
t.Fatal("classification maps were modified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffSensitivityDistinguishesRootAndEmptyKey(t *testing.T) {
|
||||
root := newDiffSensitivity(map[string]bool{"": true}, nil)
|
||||
if !root.isSensitiveAtOrAbove("") || !root.isSensitiveAtOrAbove("/empty") ||
|
||||
root.hasStrictDescendant("") {
|
||||
t.Fatal("root classification did not remain distinct from strict descendants")
|
||||
}
|
||||
|
||||
emptyKey := newDiffSensitivity(map[string]bool{"/": true}, nil)
|
||||
if emptyKey.isSensitiveAtOrAbove("") || !emptyKey.isSensitiveAtOrAbove("/") ||
|
||||
emptyKey.isSensitiveAtOrAbove("/other") || !emptyKey.hasStrictDescendant("") ||
|
||||
emptyKey.hasStrictDescendant("/") {
|
||||
t.Fatal("empty-key classification was treated as the root or an unrelated path")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user