fork of https://github.com/sourcegraph/zoekt
1package zoekt
2
3import (
4 "bytes"
5 "fmt"
6 "testing"
7
8 "github.com/google/go-cmp/cmp"
9)
10
11func TestLimitMatches(t *testing.T) {
12 cases := []struct {
13 // Represents a SearchResult with three dimensions:
14 // 1. outer slice is `Files`
15 // 2. inner slice is `{Chunk,Line}Matches`
16 // 3. value is the length of `Ranges`/`LineFragments`
17 in [][]int
18 limit int
19 expected [][]int
20 }{{
21 in: [][]int{{1, 1, 1}},
22 limit: 1,
23 expected: [][]int{{1}},
24 }, {
25 in: [][]int{{1, 1, 1}},
26 limit: 3,
27 expected: [][]int{{1, 1, 1}},
28 }, {
29 in: [][]int{{1, 1, 1}},
30 limit: 4,
31 expected: [][]int{{1, 1, 1}},
32 }, {
33 in: [][]int{{2, 2, 2}},
34 limit: 4,
35 expected: [][]int{{2, 2}},
36 }, {
37 in: [][]int{{2, 2, 2}},
38 limit: 3,
39 expected: [][]int{{2, 1}},
40 }, {
41 in: [][]int{{2, 2, 2}},
42 limit: 1,
43 expected: [][]int{{1}},
44 }, {
45 in: [][]int{{1}, {1}},
46 limit: 2,
47 expected: [][]int{{1}, {1}},
48 }, {
49 in: [][]int{{1}, {1}},
50 limit: 1,
51 expected: [][]int{{1}},
52 }, {
53 in: [][]int{{1}, {1, 3}},
54 limit: 4,
55 expected: [][]int{{1}, {1, 2}},
56 }, {
57 in: [][]int{{1}, {2, 2}, {3, 3, 3}},
58 limit: 4,
59 expected: [][]int{{1}, {2, 1}},
60 }}
61
62 for _, tc := range cases {
63 t.Run("ChunkMatches", func(t *testing.T) {
64 // Generate a ChunkMatch suitable for testing `LimitChunkMatches`.
65 generateChunkMatch := func(numRanges, lineNumber int) (ChunkMatch, int) {
66 cm := ChunkMatch{SymbolInfo: make([]*Symbol, numRanges)}
67
68 // To simplify testing, we generate Content and the associated
69 // Ranges with fixed logic: each ChunkMatch has 1 line of
70 // context, and each Range spans two lines. It'd probably be
71 // better to do some kind of property-based testing, but this is
72 // alright.
73
74 // 1 line of context.
75 cm.Content = append(cm.Content, []byte("context\n")...)
76 for i := 0; i < numRanges; i += 1 {
77 cm.Ranges = append(cm.Ranges, Range{
78 // We only provide LineNumber as that's all that's
79 // relevant.
80 Start: Location{LineNumber: uint32(lineNumber + (2 * i) + 1)},
81 End: Location{LineNumber: uint32(lineNumber + (2 * i) + 2)},
82 })
83 cm.Content = append(cm.Content, []byte(fmt.Sprintf("range%dStart\nrange%dEnd\n", i, i))...)
84 }
85 // 1 line of context. Content in zoekt notably just does not
86 // contain a trailing newline.
87 cm.Content = append(cm.Content, []byte("context")...)
88
89 // Next Chunk starts two lines past the number of lines we just
90 // added.
91 return cm, lineNumber + (2 * numRanges) + 4
92 }
93
94 res := SearchResult{}
95 for _, file := range tc.in {
96 fm := FileMatch{}
97 lineNumber := 0
98 for _, numRanges := range file {
99 var cm ChunkMatch
100 cm, lineNumber = generateChunkMatch(numRanges, lineNumber)
101 fm.ChunkMatches = append(fm.ChunkMatches, cm)
102 }
103 res.Files = append(res.Files, fm)
104 }
105
106 res.Files = SortAndTruncateFiles(res.Files, &SearchOptions{
107 MaxMatchDisplayCount: tc.limit,
108 ChunkMatches: true,
109 })
110
111 var got [][]int
112 for _, fm := range res.Files {
113 var matches []int
114 for _, cm := range fm.ChunkMatches {
115 if len(cm.Ranges) != len(cm.SymbolInfo) {
116 t.Errorf("Expected Ranges and SymbolInfo to be the same size, but got %d and %d", len(cm.Ranges), len(cm.SymbolInfo))
117 }
118
119 // Using the logic from generateChunkMatch.
120 expectedNewlines := 1 + (len(cm.Ranges) * 2)
121 actualNewlines := bytes.Count(cm.Content, []byte("\n"))
122 if actualNewlines != expectedNewlines {
123 t.Errorf("Expected Content to have %d newlines but got %d", expectedNewlines, actualNewlines)
124 }
125
126 matches = append(matches, len(cm.Ranges))
127 }
128 got = append(got, matches)
129 }
130 if !cmp.Equal(tc.expected, got) {
131 t.Errorf("Expected %v but got %v", tc.expected, got)
132 }
133 })
134
135 t.Run("LineMatches", func(t *testing.T) {
136 res := SearchResult{}
137 for _, file := range tc.in {
138 fm := FileMatch{}
139 for _, numFragments := range file {
140 fm.LineMatches = append(fm.LineMatches, LineMatch{LineFragments: make([]LineFragmentMatch, numFragments)})
141 }
142 res.Files = append(res.Files, fm)
143 }
144
145 res.Files = SortAndTruncateFiles(res.Files, &SearchOptions{
146 MaxMatchDisplayCount: tc.limit,
147 ChunkMatches: false,
148 })
149
150 var got [][]int
151 for _, fm := range res.Files {
152 var matches []int
153 for _, lm := range fm.LineMatches {
154 matches = append(matches, len(lm.LineFragments))
155 }
156 got = append(got, matches)
157 }
158 if !cmp.Equal(tc.expected, got) {
159 t.Errorf("Expected %v but got %v", tc.expected, got)
160 }
161 })
162 }
163}