Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / search / compiler / __tests__ / PhutilSearchQueryCompilerTestCase.php
blob4dc41d9734f1737226632bc5d5da5c3329203336
1 <?php
3 final class PhutilSearchQueryCompilerTestCase
4 extends PhutilTestCase {
6 public function testCompileQueries() {
7 $tests = array(
8 '' => null,
9 'cat dog' => '+"cat" +"dog"',
10 'cat -dog' => '+"cat" -"dog"',
11 'cat-dog' => '+"cat-dog"',
13 // Double quotes serve as delimiters even if there is no whitespace
14 // between terms.
15 '"cat"dog' => '+"cat" +"dog"',
17 // This query is too long.
18 str_repeat('x', 2048) => false,
20 // Multiple operators are not permitted.
21 '++cat' => false,
22 '+-cat' => false,
23 '--cat' => false,
25 // Stray operators are not permitted.
26 '+' => false,
27 'cat +' => false,
29 // Double quotes must be paired.
30 '"' => false,
31 'cat "' => false,
32 '"cat' => false,
33 'A"' => false,
34 'A"B"' => '+"A" +"B"',
36 // Trailing whitespace should be discarded.
37 'a b ' => '+"a" +"b"',
39 // Tokens must have search text.
40 '""' => false,
41 '-' => false,
43 // Previously, we permitted spaces to appear inside or after operators.
45 // Now that "title:-" is now a valid construction meaning "title is
46 // absent", this had to be tightened. We want "title:- duck" to mean
47 // "title is absent, and any other field matches 'duck'".
48 'cat - dog' => false,
51 $this->assertCompileQueries($tests);
53 // Test that we compile queries correctly if the operators have been
54 // swapped to use "AND" by default.
55 $operator_tests = array(
56 'cat dog' => '"cat" "dog"',
57 'cat -dog' => '"cat" -"dog"',
59 $this->assertCompileQueries($operator_tests, ' |-><()~*:""&\'');
62 // Test that we compile queries correctly if the quote operators have
63 // been swapped to differ.
64 $quote_tests = array(
65 'cat dog' => '+[cat] +[dog]',
66 'cat -dog' => '+[cat] -[dog]',
68 $this->assertCompileQueries($quote_tests, '+ -><()~*:[]&|');
71 public function testCompileQueriesWithStemming() {
72 $stemming_tests = array(
73 'cat dog' => array(
74 null,
75 '+"cat" +"dog"',
77 'cats dogs' => array(
78 null,
79 '+"cat" +"dog"',
81 'cats "dogs"' => array(
82 '+"dogs"',
83 '+"cat"',
85 '"blessed blade" of the windseeker' => array(
86 '+"blessed blade"',
87 '+"of" +"the" +"windseek"',
89 'mailing users for mentions on tasks' => array(
90 null,
91 '+"mail" +"user" +"for" +"mention" +"on" +"task"',
95 $stemmer = new PhutilSearchStemmer();
96 $this->assertCompileQueries($stemming_tests, null, $stemmer);
99 public function testCompileQueriesWithFunctions() {
100 $op_and = PhutilSearchQueryCompiler::OPERATOR_AND;
101 $op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
102 $op_exact = PhutilSearchQueryCompiler::OPERATOR_EXACT;
103 $op_present = PhutilSearchQueryCompiler::OPERATOR_PRESENT;
104 $op_absent = PhutilSearchQueryCompiler::OPERATOR_ABSENT;
106 $mao = "\xE7\x8C\xAB";
108 $function_tests = array(
109 'cat' => array(
110 array(null, $op_and, 'cat'),
112 ':cat' => array(
113 array(null, $op_and, 'cat'),
115 'title:cat' => array(
116 array('title', $op_and, 'cat'),
118 'title:cat:dog' => array(
119 array('title', $op_and, 'cat:dog'),
121 'title:~cat' => array(
122 array('title', $op_sub, 'cat'),
124 'cat title:="Meow Meow"' => array(
125 array(null, $op_and, 'cat'),
126 array('title', $op_exact, 'Meow Meow'),
128 'title:cat title:dog' => array(
129 array('title', $op_and, 'cat'),
130 array('title', $op_and, 'dog'),
132 '~"core and seven years ag"' => array(
133 array(null, $op_sub, 'core and seven years ag'),
135 $mao => array(
136 array(null, $op_sub, $mao),
138 '+'.$mao => array(
139 array(null, $op_and, $mao),
141 '~'.$mao => array(
142 array(null, $op_sub, $mao),
144 '"'.$mao.'"' => array(
145 array(null, $op_and, $mao),
147 'title:' => false,
148 'title:+' => false,
149 'title:+""' => false,
150 'title:""' => false,
152 'title:~' => array(
153 array('title', $op_present, null),
156 'title:-' => array(
157 array('title', $op_absent, null),
160 '~' => false,
161 '-' => false,
163 // Functions like "title:" apply to following terms if their term is
164 // not specified with double quotes.
165 'title:x y' => array(
166 array('title', $op_and, 'x'),
167 array('title', $op_and, 'y'),
169 'title: x y' => array(
170 array('title', $op_and, 'x'),
171 array('title', $op_and, 'y'),
173 'title:"x" y' => array(
174 array('title', $op_and, 'x'),
175 array(null, $op_and, 'y'),
178 // The "present" and "absent" functions are not sticky.
179 'title:~ x' => array(
180 array('title', $op_present, null),
181 array(null, $op_and, 'x'),
183 'title:- x' => array(
184 array('title', $op_absent, null),
185 array(null, $op_and, 'x'),
188 // Functions like "title:" continue to stick across quotes if the
189 // quotes aren't the initial argument.
190 'title:a "b c" d' => array(
191 array('title', $op_and, 'a'),
192 array('title', $op_and, 'b c'),
193 array('title', $op_and, 'd'),
196 // These queries require a field be both present and absent, which is
197 // impossible.
198 'title:- title:x' => false,
199 'title:- title:~' => false,
201 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ:xyz' => array(
202 array(
203 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ',
204 $op_and,
205 'xyz',
209 // See T12995. Interpret CJK tokens as substring queries since these
210 // languages do not use spaces as word separators.
211 "\xE7\x8C\xAB" => array(
212 array(null, $op_sub, "\xE7\x8C\xAB"),
215 // See T13632. Interpret tokens that begin with "_" as substring tokens
216 // if no function is specified.
217 '_x _y_ "_z_"' => array(
218 array(null, $op_sub, '_x'),
219 array(null, $op_sub, '_y_'),
220 array(null, $op_and, '_z_'),
224 $this->assertCompileFunctionQueries($function_tests);
227 private function assertCompileQueries(
228 array $tests,
229 $operators = null,
230 PhutilSearchStemmer $stemmer = null) {
231 foreach ($tests as $input => $expect) {
232 $caught = null;
234 $query = null;
235 $literal_query = null;
236 $stemmed_query = null;
238 try {
239 $compiler = new PhutilSearchQueryCompiler();
241 if ($operators !== null) {
242 $compiler->setOperators($operators);
245 if ($stemmer !== null) {
246 $compiler->setStemmer($stemmer);
249 $tokens = $compiler->newTokens($input);
251 if ($stemmer) {
252 $literal_query = $compiler->compileLiteralQuery($tokens);
253 $stemmed_query = $compiler->compileStemmedQuery($tokens);
254 } else {
255 $query = $compiler->compileQuery($tokens);
257 } catch (PhutilSearchQueryCompilerSyntaxException $ex) {
258 $caught = $ex;
261 if ($caught !== null) {
262 $query = false;
263 $literal_query = false;
264 $stemmed_query = false;
267 if (!$stemmer) {
268 $this->assertEqual(
269 $expect,
270 $query,
271 pht('Compilation of query: %s', $input));
272 } else {
273 $this->assertEqual(
274 $expect,
275 ($literal_query === false)
276 ? false
277 : array($literal_query, $stemmed_query),
278 pht('Stemmed compilation of query: %s', $input));
283 private function assertCompileFunctionQueries(array $tests) {
284 foreach ($tests as $input => $expect) {
285 $compiler = id(new PhutilSearchQueryCompiler())
286 ->setEnableFunctions(true);
288 try {
289 $tokens = $compiler->newTokens($input);
291 $result = array();
292 foreach ($tokens as $token) {
293 $result[] = array(
294 $token->getFunction(),
295 $token->getOperator(),
296 $token->getValue(),
299 } catch (PhutilSearchQueryCompilerSyntaxException $ex) {
300 $result = false;
303 $this->assertEqual(
304 $expect,
305 $result,
306 pht('Function compilation of query: %s', $input));