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