diff --git a/spec/segment.go b/spec/segment.go index e737471..0e9dad9 100644 --- a/spec/segment.go +++ b/spec/segment.go @@ -60,6 +60,19 @@ func (s *Segment) Select(current, root any) []any { return ret } +// SelectLocated selects and returns values as [LocatedNode]s from current or +// root for each of seg's selectors. Defined by the [Selector] interface. +func (s *Segment) SelectLocated(current, root any, parent NormalizedPath) []*LocatedNode { + ret := []*LocatedNode{} + for _, sel := range s.selectors { + ret = append(ret, sel.SelectLocated(current, root, parent)...) + } + if s.descendant { + ret = append(ret, s.descendLocated(current, root, parent)...) + } + return ret +} + // descend recursively executes seg.Select for each value in current and/or // root and returns the results. func (s *Segment) descend(current, root any) []any { @@ -77,6 +90,23 @@ func (s *Segment) descend(current, root any) []any { return ret } +// descend recursively executes seg.Select for each value in current and/or +// root and returns the results. +func (s *Segment) descendLocated(current, root any, parent NormalizedPath) []*LocatedNode { + ret := []*LocatedNode{} + switch val := current.(type) { + case []any: + for i, v := range val { + ret = append(ret, s.SelectLocated(v, root, append(parent, Index(i)))...) + } + case map[string]any: + for k, v := range val { + ret = append(ret, s.SelectLocated(v, root, append(parent, Name(k)))...) + } + } + return ret +} + // isSingular returns true if the segment selects at most one node. Defined by // the [Selector] interface. func (s *Segment) isSingular() bool { diff --git a/spec/segment_test.go b/spec/segment_test.go index a6a6b0d..dff89d3 100644 --- a/spec/segment_test.go +++ b/spec/segment_test.go @@ -88,7 +88,7 @@ func TestSegmentString(t *testing.T) { } } -func TestSegmentQuery(t *testing.T) { +func TestSegmentSelect(t *testing.T) { t.Parallel() a := assert.New(t) @@ -97,6 +97,7 @@ func TestSegmentQuery(t *testing.T) { seg *Segment src any exp []any + loc []*LocatedNode rand bool sing bool }{ @@ -105,12 +106,16 @@ func TestSegmentQuery(t *testing.T) { seg: Child(), src: []any{1, 3}, exp: []any{}, + loc: []*LocatedNode{}, }, { name: "name", seg: Child(Name("hi")), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + }, sing: true, }, { @@ -118,6 +123,10 @@ func TestSegmentQuery(t *testing.T) { seg: Child(Name("hi"), Name("go")), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go")}, Node: 98.6}, + }, rand: true, }, { @@ -125,26 +134,41 @@ func TestSegmentQuery(t *testing.T) { seg: Child(Name("hi"), Name("hi")), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42, 42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("hi")}, Node: 42}, + }, rand: true, }, { name: "three_names", seg: Child(Name("hi"), Name("go"), Name("x")), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, - rand: true, exp: []any{42, 98.6, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go")}, Node: 98.6}, + {Path: NormalizedPath{Name("x")}, Node: true}, + }, + rand: true, }, { name: "name_and_others", seg: Child(Name("hi"), Index(1), Slice()), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + }, }, { name: "index", seg: Child(Index(1)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + }, sing: true, }, { @@ -152,72 +176,137 @@ func TestSegmentQuery(t *testing.T) { seg: Child(Index(1), Index(4)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "x"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(4)}, Node: "x"}, + }, }, { name: "dupe_index", seg: Child(Index(1), Index(1)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, 42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + }, }, { name: "three_indexes", seg: Child(Index(1), Index(4), Index(0)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "x", "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(4)}, Node: "x"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + }, }, { name: "index_and_name", seg: Child(Name("hi"), Index(2)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{"go"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(2)}, Node: "go"}, + }, }, { name: "slice", seg: Child(Slice(1, 3)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "go"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + }, }, { name: "two_slices", seg: Child(Slice(1, 3), Slice(0, 1)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "go", "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + }, }, { name: "overlapping_slices", seg: Child(Slice(1, 3), Slice(0, 3)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "go", "hi", 42, "go"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + }, }, { name: "slice_plus_index", seg: Child(Slice(1, 3), Index(0)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "go", "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + }, }, { name: "slice_plus_overlapping_indexes", seg: Child(Slice(1, 3), Index(0), Index(1)), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "go", "hi", 42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + }, }, { name: "slice_and_others", seg: Child(Name("hi"), Index(1), Slice()), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{42, "hi", 42, "go", 98.6, "x", true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(3)}, Node: 98.6}, + {Path: NormalizedPath{Index(4)}, Node: "x"}, + {Path: NormalizedPath{Index(5)}, Node: true}, + }, }, { name: "wildcard_array", seg: Child(Wildcard), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{"hi", 42, "go", 98.6, "x", true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(3)}, Node: 98.6}, + {Path: NormalizedPath{Index(4)}, Node: "x"}, + {Path: NormalizedPath{Index(5)}, Node: true}, + }, }, { name: "wildcard_object", seg: Child(Wildcard), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42, 98.6, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go")}, Node: 98.6}, + {Path: NormalizedPath{Name("x")}, Node: true}, + }, rand: true, }, { @@ -225,12 +314,29 @@ func TestSegmentQuery(t *testing.T) { seg: Child(Wildcard, Slice(1, 3), Index(0), Name("go")), src: []any{"hi", 42, "go", 98.6, "x", true}, exp: []any{"hi", 42, "go", 98.6, "x", true, 42, "go", "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(3)}, Node: 98.6}, + {Path: NormalizedPath{Index(4)}, Node: "x"}, + {Path: NormalizedPath{Index(5)}, Node: true}, + {Path: NormalizedPath{Index(1)}, Node: 42}, + {Path: NormalizedPath{Index(2)}, Node: "go"}, + {Path: NormalizedPath{Index(0)}, Node: "hi"}, + }, }, { name: "wildcard_others_object", seg: Child(Wildcard, Slice(1, 3), Index(0), Name("go")), src: map[string]any{"hi": 42, "go": 98.6, "x": true}, exp: []any{42, 98.6, true, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go")}, Node: 98.6}, + {Path: NormalizedPath{Name("x")}, Node: true}, + {Path: NormalizedPath{Name("go")}, Node: 98.6}, + }, rand: true, }, } { @@ -241,14 +347,16 @@ func TestSegmentQuery(t *testing.T) { a.Equal(tc.seg.descendant, tc.seg.IsDescendant()) if tc.rand { a.ElementsMatch(tc.exp, tc.seg.Select(tc.src, nil)) + a.ElementsMatch(tc.loc, tc.seg.SelectLocated(tc.src, nil, NormalizedPath{})) } else { a.Equal(tc.exp, tc.seg.Select(tc.src, nil)) + a.Equal(tc.loc, tc.seg.SelectLocated(tc.src, nil, NormalizedPath{})) } }) } } -func TestDescendantSegmentQuery(t *testing.T) { +func TestDescendantSegmentSelect(t *testing.T) { t.Parallel() a := assert.New(t) @@ -257,6 +365,7 @@ func TestDescendantSegmentQuery(t *testing.T) { seg *Segment src any exp []any + loc []*LocatedNode rand bool }{ { @@ -264,12 +373,16 @@ func TestDescendantSegmentQuery(t *testing.T) { seg: Descendant(), src: []any{1, 3, []any{3, 5, []any{42, 98.6, true}}}, exp: []any{}, + loc: []*LocatedNode{}, }, { name: "root_name", seg: Descendant(Name("hi")), src: map[string]any{"hi": 42, "go": map[string]any{"x": 98.6}}, exp: []any{42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + }, rand: true, }, { @@ -280,7 +393,12 @@ func TestDescendantSegmentQuery(t *testing.T) { "hi": 98.6, "x": map[string]any{"hi": true}, }, }, - exp: []any{42, 98.6, true}, + exp: []any{42, 98.6, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go"), Name("hi")}, Node: 98.6}, + {Path: NormalizedPath{Name("go"), Name("x"), Name("hi")}, Node: true}, + }, rand: true, }, { @@ -291,7 +409,12 @@ func TestDescendantSegmentQuery(t *testing.T) { "hi": map[string]any{"hi": true}, }, }, - exp: []any{42, map[string]any{"hi": true}, true}, + exp: []any{42, map[string]any{"hi": true}, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go"), Name("hi")}, Node: map[string]any{"hi": true}}, + {Path: NormalizedPath{Name("go"), Name("hi"), Name("hi")}, Node: true}, + }, rand: true, }, { @@ -302,7 +425,11 @@ func TestDescendantSegmentQuery(t *testing.T) { "hi": 98.6, "x": map[string]any{"hi": true}, }, }, - exp: []any{98.6, true}, + exp: []any{98.6, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0), Name("hi")}, Node: 98.6}, + {Path: NormalizedPath{Index(0), Name("x"), Name("hi")}, Node: true}, + }, rand: true, }, { @@ -314,7 +441,15 @@ func TestDescendantSegmentQuery(t *testing.T) { }, "x": map[string]any{"x": 99}, }, - exp: []any{42, 98.6, true, 12, map[string]any{"x": 99}, 99}, + exp: []any{42, 98.6, true, 12, map[string]any{"x": 99}, 99}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("hi")}, Node: 42}, + {Path: NormalizedPath{Name("go"), Name("hi")}, Node: 98.6}, + {Path: NormalizedPath{Name("go"), Name("go"), Name("hi")}, Node: true}, + {Path: NormalizedPath{Name("go"), Name("go"), Name("x")}, Node: 12}, + {Path: NormalizedPath{Name("x")}, Node: map[string]any{"x": 99}}, + {Path: NormalizedPath{Name("x"), Name("x")}, Node: 99}, + }, rand: true, }, { @@ -322,39 +457,68 @@ func TestDescendantSegmentQuery(t *testing.T) { seg: Descendant(Index(1)), src: []any{1, 3, []any{3}}, exp: []any{3}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + }, }, { name: "index", seg: Descendant(Index(1)), src: []any{1, 3, []any{3, 5, []any{42, 98.6, true}}}, exp: []any{3, 5, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2), Index(1)}, Node: 5}, + {Path: NormalizedPath{Index(2), Index(2), Index(1)}, Node: 98.6}, + }, }, { name: "two_indexes", seg: Descendant(Index(1), Index(0)), src: []any{1, 3, []any{3, 5, []any{42, 98.6, true}}}, exp: []any{3, 1, 5, 3, 98.6, 42}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(2), Index(1)}, Node: 5}, + {Path: NormalizedPath{Index(2), Index(0)}, Node: 3}, + {Path: NormalizedPath{Index(2), Index(2), Index(1)}, Node: 98.6}, + {Path: NormalizedPath{Index(2), Index(2), Index(0)}, Node: 42}, + }, }, { name: "index_under_object", seg: Descendant(Index(1)), src: map[string]any{"x": 1, "y": 3, "z": []any{3, 5, []any{42, 98.6, true}}}, exp: []any{5, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("z"), Index(1)}, Node: 5}, + {Path: NormalizedPath{Name("z"), Index(2), Index(1)}, Node: 98.6}, + }, }, { name: "root_slice", seg: Descendant(Slice(1, 3)), src: []any{1, 3, 4, []any{3}}, exp: []any{3, 4}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: 4}, + }, }, { name: "slice", seg: Descendant(Slice(1, 2)), src: []any{1, 3, 4, []any{3, 5, "x", []any{42, 98.6, "y", "z", true}}}, exp: []any{3, 5, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(3), Index(1)}, Node: 5}, + {Path: NormalizedPath{Index(3), Index(3), Index(1)}, Node: 98.6}, + }, }, { - name: "two_slices", + name: "two_more_slices", seg: Descendant(Slice(1, 2), Slice(3, 4)), src: []any{1, 3, 4, []any{3, 5, "x", []any{42, 98.6, "y", "z", true}}}, exp: []any{ @@ -365,6 +529,14 @@ func TestDescendantSegmentQuery(t *testing.T) { 98.6, "z", }, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(3)}, Node: []any{3, 5, "x", []any{42, 98.6, "y", "z", true}}}, + {Path: NormalizedPath{Index(3), Index(1)}, Node: 5}, + {Path: NormalizedPath{Index(3), Index(3)}, Node: []any{42, 98.6, "y", "z", true}}, + {Path: NormalizedPath{Index(3), Index(3), Index(1)}, Node: 98.6}, + {Path: NormalizedPath{Index(3), Index(3), Index(3)}, Node: "z"}, + }, }, { name: "slice_and_index", @@ -378,24 +550,45 @@ func TestDescendantSegmentQuery(t *testing.T) { 98.6, "z", }, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(3)}, Node: []any{3, 5, "x", []any{42, 98.6, "y", "z", true}}}, + {Path: NormalizedPath{Index(3), Index(1)}, Node: 5}, + {Path: NormalizedPath{Index(3), Index(3)}, Node: []any{42, 98.6, "y", "z", true}}, + {Path: NormalizedPath{Index(3), Index(3), Index(1)}, Node: 98.6}, + {Path: NormalizedPath{Index(3), Index(3), Index(3)}, Node: "z"}, + }, }, { name: "slice_under_object", seg: Descendant(Slice(1, 2)), src: map[string]any{"x": []any{3, 5, "x", []any{42, 98.6, "y", "z", true}}}, exp: []any{5, 98.6}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("x"), Index(1)}, Node: 5}, + {Path: NormalizedPath{Name("x"), Index(3), Index(1)}, Node: 98.6}, + }, }, { name: "root_wildcard_array", seg: Descendant(Wildcard), src: []any{1, 3, 4}, exp: []any{1, 3, 4}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: 4}, + }, }, { name: "root_wildcard_object", seg: Descendant(Wildcard), src: map[string]any{"x": 42, "y": true}, exp: []any{42, true}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("x")}, Node: 42}, + {Path: NormalizedPath{Name("y")}, Node: true}, + }, rand: true, }, { @@ -403,12 +596,24 @@ func TestDescendantSegmentQuery(t *testing.T) { seg: Descendant(Wildcard), src: []any{1, 3, []any{4, 5}}, exp: []any{1, 3, []any{4, 5}, 4, 5}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: []any{4, 5}}, + {Path: NormalizedPath{Index(2), Index(0)}, Node: 4}, + {Path: NormalizedPath{Index(2), Index(1)}, Node: 5}, + }, }, { name: "wildcard_nested_object", seg: Descendant(Wildcard), src: map[string]any{"x": 42, "y": map[string]any{"z": "hi"}}, exp: []any{42, map[string]any{"z": "hi"}, "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Name("x")}, Node: 42}, + {Path: NormalizedPath{Name("y")}, Node: map[string]any{"z": "hi"}}, + {Path: NormalizedPath{Name("y"), Name("z")}, Node: "hi"}, + }, rand: true, }, { @@ -416,6 +621,12 @@ func TestDescendantSegmentQuery(t *testing.T) { seg: Descendant(Wildcard), src: []any{1, 3, map[string]any{"z": "hi"}}, exp: []any{1, 3, map[string]any{"z": "hi"}, "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: map[string]any{"z": "hi"}}, + {Path: NormalizedPath{Index(2), Name("z")}, Node: "hi"}, + }, rand: true, }, { @@ -423,12 +634,27 @@ func TestDescendantSegmentQuery(t *testing.T) { seg: Descendant(Wildcard, Index(0)), src: []any{1, 3, map[string]any{"z": "hi"}}, exp: []any{1, 3, map[string]any{"z": "hi"}, 1, "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: map[string]any{"z": "hi"}}, + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(2), Name("z")}, Node: "hi"}, + }, }, { name: "wildcard_mixed_name", seg: Descendant(Wildcard, Name("z")), src: []any{1, 3, map[string]any{"z": "hi", "y": "x"}}, exp: []any{1, 3, map[string]any{"z": "hi", "y": "x"}, "hi", "x", "hi"}, + loc: []*LocatedNode{ + {Path: NormalizedPath{Index(0)}, Node: 1}, + {Path: NormalizedPath{Index(1)}, Node: 3}, + {Path: NormalizedPath{Index(2)}, Node: map[string]any{"z": "hi", "y": "x"}}, + {Path: NormalizedPath{Index(2), Name("z")}, Node: "hi"}, + {Path: NormalizedPath{Index(2), Name("y")}, Node: "x"}, + {Path: NormalizedPath{Index(2), Name("z")}, Node: "hi"}, + }, rand: true, }, } { @@ -438,8 +664,10 @@ func TestDescendantSegmentQuery(t *testing.T) { a.True(tc.seg.IsDescendant()) if tc.rand { a.ElementsMatch(tc.exp, tc.seg.Select(tc.src, nil)) + a.ElementsMatch(tc.loc, tc.seg.SelectLocated(tc.src, nil, NormalizedPath{})) } else { a.Equal(tc.exp, tc.seg.Select(tc.src, nil)) + a.Equal(tc.loc, tc.seg.SelectLocated(tc.src, nil, NormalizedPath{})) } }) }