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 abc) def", NewAnd(&Type{Type: TypeRepo, Child: &Substring{Pattern: "abc"}}, &Substring{Pattern: "def"})},
136
137 // errors.
138 {"--", nil},
139 {"\"abc", nil},
140 {"\"a\\", nil},
141 {"case:foo", nil},
142
143 {"sym:", nil},
144 {"abc or", nil},
145 {"or abc", nil},
146 {"def or or abc", nil},
147
148 // unbalanced parentheses
149 {"(", nil},
150 {"((", nil},
151 {"(((", nil},
152 {")", nil},
153 {"))", nil},
154 {")))", nil},
155 {"foo)", nil},
156 {"foo))", nil},
157 {"foo)))", nil},
158 {"(foo", nil},
159 {"((foo", nil},
160 {"(((foo", nil},
161 {"(foo))", nil},
162 {"(((foo))", nil},
163
164 {"", &Const{Value: true}},
165
166 // whitespace
167 {" ( ) ", &Const{Value: true}},
168 {" ( foo ) ", &Substring{Pattern: "foo"}},
169 } {
170 got, err := Parse(c.in)
171 if (c.want == nil) != (err != nil) {
172 t.Errorf("Parse(%q): error %v, want %v", c.in, err, c.want)
173 } else if got != nil {
174 if !reflect.DeepEqual(got, c.want) {
175 t.Errorf("Parse(%s): got %v want %v", c.in, got, c.want)
176 }
177 }
178 }
179}
180
181func TestTokenize(t *testing.T) {
182 type testcase struct {
183 in string
184 typ int
185 text string
186 }
187
188 cases := []testcase{
189 {"file:bla", tokFile, "bla"},
190 {"file:bla ", tokFile, "bla"},
191 {"f:bla ", tokFile, "bla"},
192 {"(abc def) ", tokParenOpen, "("},
193 {"(abcdef)", tokText, "(abcdef)"},
194 {"(abc)(de)", tokText, "(abc)(de)"},
195 {"(ab(c)def) ", tokText, "(ab(c)def)"},
196 {"(ab\\ def) ", tokText, "(ab\\ def)"},
197 {") ", tokParenClose, ")"},
198 {"a(bc))", tokText, "a(bc)"},
199 {"abc) ", tokText, "abc"},
200 {"file:\"bla\"", tokFile, "bla"},
201 {"\"file:bla\"", tokText, "file:bla"},
202 {"\\", tokError, ""},
203 {"o\"r\" bla", tokText, "or"},
204 {"or bla", tokOr, "or"},
205 {"ar bla", tokText, "ar"},
206 }
207 for _, c := range cases {
208 tok, err := nextToken([]byte(c.in))
209 if err != nil {
210 tok = &token{Type: tokError}
211 }
212 if tok.Type != c.typ {
213 t.Errorf("%s: got type %d, want %d", c.in, tok.Type, c.typ)
214 continue
215 }
216
217 if string(tok.Text) != c.text {
218 t.Errorf("%s: got text %q, want %q", c.in, tok.Text, c.text)
219 }
220 }
221}
222
223func TestMetaQueryParsing(t *testing.T) {
224 cases := []struct {
225 input string
226 field string
227 pattern string
228 err bool
229 }{
230 {
231 input: "meta.visibility_level:20",
232 field: "visibility_level",
233 pattern: "20",
234 err: false,
235 },
236 {
237 input: "meta.needle:ha.*stack",
238 field: "needle",
239 pattern: "ha.*stack",
240 err: false,
241 },
242 {
243 input: "meta.public:true",
244 field: "public",
245 pattern: "true",
246 err: false,
247 },
248 {
249 input: "meta.language:go",
250 field: "language",
251 pattern: "go",
252 err: false,
253 },
254 {
255 input: "meta.invalid_field:(",
256 field: "invalid_field",
257 pattern: "(",
258 err: true,
259 },
260 }
261
262 for _, c := range cases {
263 t.Run(c.input, func(t *testing.T) {
264 q, err := Parse(c.input)
265 if c.err {
266 if err == nil {
267 t.Errorf("expected error, got nil")
268 }
269 return
270 }
271
272 if err != nil {
273 t.Errorf("unexpected error: %v", err)
274 }
275
276 meta, ok := q.(*Meta)
277 if !ok || meta == nil {
278 t.Errorf("expected *Meta, got %T", q)
279 return
280 }
281
282 if meta.Field != c.field {
283 t.Errorf("expected field %q, got %q", c.field, meta.Field)
284 }
285 if meta.Value == nil || meta.Value.String() != c.pattern {
286 t.Errorf("expected pattern %q, got %v", c.pattern, meta.Value)
287 }
288 })
289 }
290}