fork of https://github.com/sourcegraph/zoekt
1// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package query
16
17import (
18 "log"
19 "reflect"
20 "regexp/syntax"
21 "testing"
22
23 "github.com/grafana/regexp"
24)
25
26func mustParseRE(s string) *syntax.Regexp {
27 r, err := syntax.Parse(s, regexpFlags)
28 if err != nil {
29 log.Panicf("parsing %q: %v", s, err)
30 }
31 return r
32}
33
34func TestParseQuery(t *testing.T) {
35 type testcase struct {
36 in string
37 want Q
38 }
39
40 for _, c := range []testcase{
41 {`\bword\b`, &Regexp{Regexp: mustParseRE(`\bword\b`)}},
42 {"fi\"le:bla\"", &Substring{Pattern: "file:bla"}},
43 {"abc or def", NewOr(&Substring{Pattern: "abc"}, &Substring{Pattern: "def"})},
44 {"(abc or def)", NewOr(&Substring{Pattern: "abc"}, &Substring{Pattern: "def"})},
45 {"(ppp qqq or rrr sss)", NewOr(
46 NewAnd(&Substring{Pattern: "ppp"}, &Substring{Pattern: "qqq"}),
47 NewAnd(&Substring{Pattern: "rrr"}, &Substring{Pattern: "sss"}))},
48 {"((x) ora b(z(d)))", NewAnd(
49 &Substring{Pattern: "x"},
50 &Substring{Pattern: "ora"},
51 &Substring{Pattern: "bzd"})},
52 {"( )", &Const{Value: true}},
53 {"(abc)(de)", &Substring{Pattern: "abcde"}},
54 {"sub-pixel", &Substring{Pattern: "sub-pixel"}},
55 {"abc", &Substring{Pattern: "abc"}},
56 {"ABC", &Substring{Pattern: "ABC", CaseSensitive: true}},
57 {"\"abc bcd\"", &Substring{Pattern: "abc bcd"}},
58 {"abc bcd", NewAnd(
59 &Substring{Pattern: "abc"},
60 &Substring{Pattern: "bcd"})},
61 {"f:fs", &Substring{Pattern: "fs", FileName: true}},
62 {"fs", &Substring{Pattern: "fs"}},
63 {"-abc", &Not{&Substring{Pattern: "abc"}}},
64 {"abccase:yes", &Substring{Pattern: "abccase:yes"}},
65 {"file:abc", &Substring{Pattern: "abc", FileName: true}},
66 {"branch:pqr", &Branch{Pattern: "pqr"}},
67 {"((x|y) )", &Regexp{Regexp: mustParseRE("[xy]")}},
68 {"archived:yes", RawConfig(RcOnlyArchived)},
69 {"archived:no", RawConfig(RcNoArchived)},
70 {"fork:yes", RawConfig(RcOnlyForks)},
71 {"fork:no", RawConfig(RcNoForks)},
72 {"public:yes", RawConfig(RcOnlyPublic)},
73 {"public:no", RawConfig(RcOnlyPrivate)},
74 {"file:helpers\\.go byte", NewAnd(
75 &Substring{Pattern: "helpers.go", FileName: true},
76 &Substring{Pattern: "byte"})},
77 {"(abc def)", NewAnd(
78 &Substring{Pattern: "abc"},
79 &Substring{Pattern: "def"})},
80 {"(abc def", nil},
81 {"regex:abc[p-q]", &Regexp{Regexp: mustParseRE("abc[p-q]")}},
82 {"aBc[p-q]", &Regexp{Regexp: mustParseRE("aBc[p-q]"), CaseSensitive: true}},
83 {"aBc[p-q] case:auto", &Regexp{Regexp: mustParseRE("aBc[p-q]"), CaseSensitive: true}},
84 {"repo:go", &Repo{regexp.MustCompile("go")}},
85 {"repo:.*", &Repo{Regexp: regexp.MustCompile(".*")}},
86
87 {"file:\"\"", &Const{true}},
88 {"abc.*def", &Regexp{Regexp: mustParseRE("abc.*def")}},
89 {"abc\\.\\*def", &Substring{Pattern: "abc.*def"}},
90 {"(abc)", &Substring{Pattern: "abc"}},
91
92 {"c:abc", &Substring{Pattern: "abc", Content: true}},
93 {"content:abc", &Substring{Pattern: "abc", Content: true}},
94
95 {"lang:c++", &Language{"C++"}},
96 {"lang:cpp", &Language{"C++"}},
97 {"sym:pqr", &Symbol{&Substring{Pattern: "pqr"}}},
98 {"sym:Pqr", &Symbol{&Substring{Pattern: "Pqr", CaseSensitive: true}}},
99 {"sym:.*", &Symbol{&Regexp{Regexp: mustParseRE(".*")}}},
100 {"sym:a(b|d)e", &Symbol{&Regexp{Regexp: mustParseRE("a[bd]e")}}},
101
102 // case
103 {"abc case:yes", &Substring{Pattern: "abc", CaseSensitive: true}},
104 {"abc case:auto", &Substring{Pattern: "abc", CaseSensitive: false}},
105 {"ABC case:auto", &Substring{Pattern: "ABC", CaseSensitive: true}},
106 {"ABC case:\"auto\"", &Substring{Pattern: "ABC", CaseSensitive: true}},
107 {"abc -f:def case:yes", NewAnd(
108 &Substring{Pattern: "abc", CaseSensitive: true},
109 &Not{Child: &Substring{Pattern: "def", FileName: true, CaseSensitive: true}},
110 )},
111 {"(foo case:yes) bar", NewAnd(
112 &Substring{Pattern: "foo", CaseSensitive: true},
113 &Substring{Pattern: "bar"},
114 )},
115 {"(case:yes foo) bar", NewAnd(
116 &Substring{Pattern: "foo", CaseSensitive: true},
117 &Substring{Pattern: "bar"},
118 )},
119 {"(case:yes foo (bar))", NewAnd(
120 &Substring{Pattern: "foo", CaseSensitive: true},
121 &Substring{Pattern: "bar", CaseSensitive: true},
122 )},
123 {"case:auto (foo case:yes) bar", NewAnd(
124 &Substring{Pattern: "foo", CaseSensitive: true},
125 &Substring{Pattern: "bar"},
126 )},
127 {"case:yes (foo case:no) bar", NewAnd(
128 &Substring{Pattern: "foo"},
129 &Substring{Pattern: "bar", CaseSensitive: true},
130 )},
131
132 // type
133 {"type:repo abc", &Type{Type: TypeRepo, Child: &Substring{Pattern: "abc"}}},
134 {"type:file abc def", &Type{Type: TypeFileName, Child: NewAnd(&Substring{Pattern: "abc"}, &Substring{Pattern: "def"})}},
135 {"type:repo foo or bar", &Type{Type: TypeRepo, Child: NewOr(&Substring{Pattern: "foo"}, &Substring{Pattern: "bar"})}},
136 {"(type:repo abc) def", NewAnd(&Type{Type: TypeRepo, Child: &Substring{Pattern: "abc"}}, &Substring{Pattern: "def"})},
137
138 // errors.
139 {"--", nil},
140 {"\"abc", nil},
141 {"\"a\\", nil},
142 {"case:foo", nil},
143
144 {"sym:", nil},
145 {"abc or", nil},
146 {"or abc", nil},
147 {"def or or abc", nil},
148 {"type:repo or", nil},
149 {"or type:repo", nil},
150
151 // unbalanced parentheses
152 {"(", nil},
153 {"((", nil},
154 {"(((", nil},
155 {")", nil},
156 {"))", nil},
157 {")))", nil},
158 {"foo)", nil},
159 {"foo))", nil},
160 {"foo)))", nil},
161 {"(foo", nil},
162 {"((foo", nil},
163 {"(((foo", nil},
164 {"(foo))", nil},
165 {"(((foo))", nil},
166
167 {"", &Const{Value: true}},
168
169 // whitespace
170 {" ( ) ", &Const{Value: true}},
171 {" ( foo ) ", &Substring{Pattern: "foo"}},
172 } {
173 got, err := Parse(c.in)
174 if (c.want == nil) != (err != nil) {
175 t.Errorf("Parse(%q): error %v, want %v", c.in, err, c.want)
176 } else if got != nil {
177 if !reflect.DeepEqual(got, c.want) {
178 t.Errorf("Parse(%s): got %v want %v", c.in, got, c.want)
179 }
180 }
181 }
182}
183
184func TestTokenize(t *testing.T) {
185 type testcase struct {
186 in string
187 typ int
188 text string
189 }
190
191 cases := []testcase{
192 {"file:bla", tokFile, "bla"},
193 {"file:bla ", tokFile, "bla"},
194 {"f:bla ", tokFile, "bla"},
195 {"(abc def) ", tokParenOpen, "("},
196 {"(abcdef)", tokText, "(abcdef)"},
197 {"(abc)(de)", tokText, "(abc)(de)"},
198 {"(ab(c)def) ", tokText, "(ab(c)def)"},
199 {"(ab\\ def) ", tokText, "(ab\\ def)"},
200 {") ", tokParenClose, ")"},
201 {"a(bc))", tokText, "a(bc)"},
202 {"abc) ", tokText, "abc"},
203 {"file:\"bla\"", tokFile, "bla"},
204 {"\"file:bla\"", tokText, "file:bla"},
205 {"\\", tokError, ""},
206 {"o\"r\" bla", tokText, "or"},
207 {"or bla", tokOr, "or"},
208 {"ar bla", tokText, "ar"},
209 }
210 for _, c := range cases {
211 tok, err := nextToken([]byte(c.in))
212 if err != nil {
213 tok = &token{Type: tokError}
214 }
215 if tok.Type != c.typ {
216 t.Errorf("%s: got type %d, want %d", c.in, tok.Type, c.typ)
217 continue
218 }
219
220 if string(tok.Text) != c.text {
221 t.Errorf("%s: got text %q, want %q", c.in, tok.Text, c.text)
222 }
223 }
224}
225
226func TestMetaQueryParsing(t *testing.T) {
227 cases := []struct {
228 input string
229 field string
230 pattern string
231 err bool
232 }{
233 {
234 input: "meta.visibility_level:20",
235 field: "visibility_level",
236 pattern: "20",
237 err: false,
238 },
239 {
240 input: "meta.needle:ha.*stack",
241 field: "needle",
242 pattern: "ha.*stack",
243 err: false,
244 },
245 {
246 input: "meta.public:true",
247 field: "public",
248 pattern: "true",
249 err: false,
250 },
251 {
252 input: "meta.language:go",
253 field: "language",
254 pattern: "go",
255 err: false,
256 },
257 {
258 input: "meta.invalid_field:(",
259 field: "invalid_field",
260 pattern: "(",
261 err: true,
262 },
263 }
264
265 for _, c := range cases {
266 t.Run(c.input, func(t *testing.T) {
267 q, err := Parse(c.input)
268 if c.err {
269 if err == nil {
270 t.Errorf("expected error, got nil")
271 }
272 return
273 }
274
275 if err != nil {
276 t.Errorf("unexpected error: %v", err)
277 }
278
279 meta, ok := q.(*Meta)
280 if !ok || meta == nil {
281 t.Errorf("expected *Meta, got %T", q)
282 return
283 }
284
285 if meta.Field != c.field {
286 t.Errorf("expected field %q, got %q", c.field, meta.Field)
287 }
288 if meta.Value == nil || meta.Value.String() != c.pattern {
289 t.Errorf("expected pattern %q, got %v", c.pattern, meta.Value)
290 }
291 })
292 }
293}