xref: /freebsd/contrib/libedit/TEST/test_filecompletion.c (revision 5ca8e32633c4ffbbcd6762e5888b6a4ba0708c6c)
1 /*	$NetBSD: test_filecompletion.c,v 1.5 2019/09/08 05:50:58 abhinav Exp $	*/
2 
3 /*-
4  * Copyright (c) 2017 Abhinav Upadhyay <abhinav@NetBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include "config.h"
33 
34 #include <assert.h>
35 #include <err.h>
36 #include <stdio.h>
37 #include <histedit.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <wchar.h>
41 
42 #include "filecomplete.h"
43 #include "el.h"
44 
45 typedef struct {
46 	const wchar_t *user_typed_text; /* The actual text typed by the user on the terminal */
47 	const char *completion_function_input ; /*the text received by fn_filename_completion_function */
48 	const char *expanded_text[2]; /* the value to which completion_function_input should be expanded */
49 	const wchar_t *escaped_output; /* expected escaped value of expanded_text */
50 } test_input;
51 
52 static test_input inputs[] = {
53 	{
54 		/* simple test for escaping angular brackets */
55 		L"ls ang",
56 		"ang",
57 		{"ang<ular>test", NULL},
58 		L"ls ang\\<ular\\>test "
59 	},
60 	{
61 		/* test angular bracket inside double quotes: ls "dq_ang */
62 		L"ls \"dq_ang",
63 		"dq_ang",
64 		{"dq_ang<ular>test", NULL},
65 		L"ls \"dq_ang<ular>test\""
66 	},
67 	{
68 		/* test angular bracket inside singlq quotes: ls "sq_ang */
69 		L"ls 'sq_ang",
70 		"sq_ang",
71 		{"sq_ang<ular>test", NULL},
72 		L"ls 'sq_ang<ular>test'"
73 	},
74 	{
75 		/* simple test for backslash */
76 		L"ls back",
77 		"back",
78 		{"backslash\\test", NULL},
79 		L"ls backslash\\\\test "
80 	},
81 	{
82 		/* backslash inside single quotes */
83 		L"ls 'sback",
84 		"sback",
85 		{"sbackslash\\test", NULL},
86 		L"ls 'sbackslash\\test'"
87 	},
88 	{
89 		/* backslash inside double quotes */
90 		L"ls \"dback",
91 		"dback",
92 		{"dbackslash\\test", NULL},
93 		L"ls \"dbackslash\\\\test\""
94 	},
95 	{
96 		/* test braces */
97 		L"ls br",
98 		"br",
99 		{"braces{test}", NULL},
100 		L"ls braces\\{test\\} "
101 	},
102 	{
103 		/* test braces inside single quotes */
104 		L"ls 'sbr",
105 		"sbr",
106 		{"sbraces{test}", NULL},
107 		L"ls 'sbraces{test}'"
108 	},
109 	{
110 		/* test braces inside double quotes */
111 		L"ls \"dbr",
112 		"dbr",
113 		{"dbraces{test}", NULL},
114 		L"ls \"dbraces{test}\""
115 	},
116 	{
117 		/* test dollar */
118 		L"ls doll",
119 		"doll",
120 		{"doll$artest", NULL},
121 		L"ls doll\\$artest "
122 	},
123 	{
124 		/* test dollar inside single quotes */
125 		L"ls 'sdoll",
126 		"sdoll",
127 		{"sdoll$artest", NULL},
128 		L"ls 'sdoll$artest'"
129 	},
130 	{
131 		/* test dollar inside double quotes */
132 		L"ls \"ddoll",
133 		"ddoll",
134 		{"ddoll$artest", NULL},
135 		L"ls \"ddoll\\$artest\""
136 	},
137 	{
138 		/* test equals */
139 		L"ls eq",
140 		"eq",
141 		{"equals==test", NULL},
142 		L"ls equals\\=\\=test "
143 	},
144 	{
145 		/* test equals inside sinqle quotes */
146 		L"ls 'seq",
147 		"seq",
148 		{"sequals==test", NULL},
149 		L"ls 'sequals==test'"
150 	},
151 	{
152 		/* test equals inside double quotes */
153 		L"ls \"deq",
154 		"deq",
155 		{"dequals==test", NULL},
156 		L"ls \"dequals==test\""
157 	},
158 	{
159 		/* test \n */
160 		L"ls new",
161 		"new",
162 		{"new\\nline", NULL},
163 		L"ls new\\\\nline "
164 	},
165 	{
166 		/* test \n inside single quotes */
167 		L"ls 'snew",
168 		"snew",
169 		{"snew\nline", NULL},
170 		L"ls 'snew\nline'"
171 	},
172 	{
173 		/* test \n inside double quotes */
174 		L"ls \"dnew",
175 		"dnew",
176 		{"dnew\nline", NULL},
177 		L"ls \"dnew\nline\""
178 	},
179 	{
180 		/* test single space */
181 		L"ls spac",
182 		"spac",
183 		{"space test", NULL},
184 		L"ls space\\ test "
185 	},
186 	{
187 		/* test single space inside singlq quotes */
188 		L"ls 's_spac",
189 		"s_spac",
190 		{"s_space test", NULL},
191 		L"ls 's_space test'"
192 	},
193 	{
194 		/* test single space inside double quotes */
195 		L"ls \"d_spac",
196 		"d_spac",
197 		{"d_space test", NULL},
198 		L"ls \"d_space test\""
199 	},
200 	{
201 		/* test multiple spaces */
202 		L"ls multi",
203 		"multi",
204 		{"multi space  test", NULL},
205 		L"ls multi\\ space\\ \\ test "
206 	},
207 	{
208 		/* test multiple spaces inside single quotes */
209 		L"ls 's_multi",
210 		"s_multi",
211 		{"s_multi space  test", NULL},
212 		L"ls 's_multi space  test'"
213 	},
214 	{
215 		/* test multiple spaces inside double quotes */
216 		L"ls \"d_multi",
217 		"d_multi",
218 		{"d_multi space  test", NULL},
219 		L"ls \"d_multi space  test\""
220 	},
221 	{
222 		/* test double quotes */
223 		L"ls doub",
224 		"doub",
225 		{"doub\"quotes", NULL},
226 		L"ls doub\\\"quotes "
227 	},
228 	{
229 		/* test double quotes inside single quotes */
230 		L"ls 's_doub",
231 		"s_doub",
232 		{"s_doub\"quotes", NULL},
233 		L"ls 's_doub\"quotes'"
234 	},
235 	{
236 		/* test double quotes inside double quotes */
237 		L"ls \"d_doub",
238 		"d_doub",
239 		{"d_doub\"quotes", NULL},
240 		L"ls \"d_doub\\\"quotes\""
241 	},
242 	{
243 		/* test multiple double quotes */
244 		L"ls mud",
245 		"mud",
246 		{"mud\"qu\"otes\"", NULL},
247 		L"ls mud\\\"qu\\\"otes\\\" "
248 	},
249 	{
250 		/* test multiple double quotes inside single quotes */
251 		L"ls 'smud",
252 		"smud",
253 		{"smud\"qu\"otes\"", NULL},
254 		L"ls 'smud\"qu\"otes\"'"
255 	},
256 	{
257 		/* test multiple double quotes inside double quotes */
258 		L"ls \"dmud",
259 		"dmud",
260 		{"dmud\"qu\"otes\"", NULL},
261 		L"ls \"dmud\\\"qu\\\"otes\\\"\""
262 	},
263 	{
264 		/* test one single quote */
265 		L"ls sing",
266 		"sing",
267 		{"single'quote", NULL},
268 		L"ls single\\'quote "
269 	},
270 	{
271 		/* test one single quote inside single quote */
272 		L"ls 'ssing",
273 		"ssing",
274 		{"ssingle'quote", NULL},
275 		L"ls 'ssingle'\\''quote'"
276 	},
277 	{
278 		/* test one single quote inside double quote */
279 		L"ls \"dsing",
280 		"dsing",
281 		{"dsingle'quote", NULL},
282 		L"ls \"dsingle'quote\""
283 	},
284 	{
285 		/* test multiple single quotes */
286 		L"ls mu_sing",
287 		"mu_sing",
288 		{"mu_single''quotes''", NULL},
289 		L"ls mu_single\\'\\'quotes\\'\\' "
290 	},
291 	{
292 		/* test multiple single quotes inside single quote */
293 		L"ls 'smu_sing",
294 		"smu_sing",
295 		{"smu_single''quotes''", NULL},
296 		L"ls 'smu_single'\\'''\\''quotes'\\\'''\\'''"
297 	},
298 	{
299 		/* test multiple single quotes inside double quote */
300 		L"ls \"dmu_sing",
301 		"dmu_sing",
302 		{"dmu_single''quotes''", NULL},
303 		L"ls \"dmu_single''quotes''\""
304 	},
305 	{
306 		/* test parenthesis */
307 		L"ls paren",
308 		"paren",
309 		{"paren(test)", NULL},
310 		L"ls paren\\(test\\) "
311 	},
312 	{
313 		/* test parenthesis inside single quote */
314 		L"ls 'sparen",
315 		"sparen",
316 		{"sparen(test)", NULL},
317 		L"ls 'sparen(test)'"
318 	},
319 	{
320 		/* test parenthesis inside double quote */
321 		L"ls \"dparen",
322 		"dparen",
323 		{"dparen(test)", NULL},
324 		L"ls \"dparen(test)\""
325 	},
326 	{
327 		/* test pipe */
328 		L"ls pip",
329 		"pip",
330 		{"pipe|test", NULL},
331 		L"ls pipe\\|test "
332 	},
333 	{
334 		/* test pipe inside single quote */
335 		L"ls 'spip",
336 		"spip",
337 		{"spipe|test", NULL},
338 		L"ls 'spipe|test'",
339 	},
340 	{
341 		/* test pipe inside double quote */
342 		L"ls \"dpip",
343 		"dpip",
344 		{"dpipe|test", NULL},
345 		L"ls \"dpipe|test\""
346 	},
347 	{
348 		/* test tab */
349 		L"ls ta",
350 		"ta",
351 		{"tab\ttest", NULL},
352 		L"ls tab\\\ttest "
353 	},
354 	{
355 		/* test tab inside single quote */
356 		L"ls 'sta",
357 		"sta",
358 		{"stab\ttest", NULL},
359 		L"ls 'stab\ttest'"
360 	},
361 	{
362 		/* test tab inside double quote */
363 		L"ls \"dta",
364 		"dta",
365 		{"dtab\ttest", NULL},
366 		L"ls \"dtab\ttest\""
367 	},
368 	{
369 		/* test back tick */
370 		L"ls tic",
371 		"tic",
372 		{"tick`test`", NULL},
373 		L"ls tick\\`test\\` "
374 	},
375 	{
376 		/* test back tick inside single quote */
377 		L"ls 'stic",
378 		"stic",
379 		{"stick`test`", NULL},
380 		L"ls 'stick`test`'"
381 	},
382 	{
383 		/* test back tick inside double quote */
384 		L"ls \"dtic",
385 		"dtic",
386 		{"dtick`test`", NULL},
387 		L"ls \"dtick\\`test\\`\""
388 	},
389 	{
390 		/* test for @ */
391 		L"ls at",
392 		"at",
393 		{"atthe@rate", NULL},
394 		L"ls atthe\\@rate "
395 	},
396 	{
397 		/* test for @ inside single quote */
398 		L"ls 'sat",
399 		"sat",
400 		{"satthe@rate", NULL},
401 		L"ls 'satthe@rate'"
402 	},
403 	{
404 		/* test for @ inside double quote */
405 		L"ls \"dat",
406 		"dat",
407 		{"datthe@rate", NULL},
408 		L"ls \"datthe@rate\""
409 	},
410 	{
411 		/* test ; */
412 		L"ls semi",
413 		"semi",
414 		{"semi;colon;test", NULL},
415 		L"ls semi\\;colon\\;test "
416 	},
417 	{
418 		/* test ; inside single quote */
419 		L"ls 'ssemi",
420 		"ssemi",
421 		{"ssemi;colon;test", NULL},
422 		L"ls 'ssemi;colon;test'"
423 	},
424 	{
425 		/* test ; inside double quote */
426 		L"ls \"dsemi",
427 		"dsemi",
428 		{"dsemi;colon;test", NULL},
429 		L"ls \"dsemi;colon;test\""
430 	},
431 	{
432 		/* test & */
433 		L"ls amp",
434 		"amp",
435 		{"ampers&and", NULL},
436 		L"ls ampers\\&and "
437 	},
438 	{
439 		/* test & inside single quote */
440 		L"ls 'samp",
441 		"samp",
442 		{"sampers&and", NULL},
443 		L"ls 'sampers&and'"
444 	},
445 	{
446 		/* test & inside double quote */
447 		L"ls \"damp",
448 		"damp",
449 		{"dampers&and", NULL},
450 		L"ls \"dampers&and\""
451 	},
452 	{
453 		/* test completion when cursor at \ */
454 		L"ls foo\\",
455 		"foo",
456 		{"foo bar", NULL},
457 		L"ls foo\\ bar "
458 	},
459 	{
460 		/* test completion when cursor at single quote */
461 		L"ls foo'",
462 		"foo'",
463 		{"foo bar", NULL},
464 		L"ls foo\\ bar "
465 	},
466 	{
467 		/* test completion when cursor at double quote */
468 		L"ls foo\"",
469 		"foo\"",
470 		{"foo bar", NULL},
471 		L"ls foo\\ bar "
472 	},
473 	{
474 		/* test multiple completion matches */
475 		L"ls fo",
476 		"fo",
477 		{"foo bar", "foo baz"},
478 		L"ls foo\\ ba"
479 	},
480 	{
481 		L"ls ba",
482 		"ba",
483 		{"bar <bar>", "bar <baz>"},
484 		L"ls bar\\ \\<ba"
485 	}
486 };
487 
488 static const wchar_t break_chars[] = L" \t\n\"\\'`@$><=;|&{(";
489 
490 /*
491  * Custom completion function passed to fn_complet, NULLe.
492  * The function returns hardcoded completion matches
493  * based on the test cases present in inputs[] (above)
494  */
495 static char *
496 mycomplet_func(const char *text, int index)
497 {
498 	static int last_index = 0;
499 	size_t i = 0;
500 	if (last_index == 2) {
501 		last_index = 0;
502 		return NULL;
503 	}
504 
505 	for (i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) {
506 		if (strcmp(text, inputs[i].completion_function_input) == 0) {
507 			if (inputs[i].expanded_text[last_index] != NULL)
508 				return strdup(inputs[i].expanded_text[last_index++]);
509 			else {
510 				last_index = 0;
511 				return NULL;
512 			}
513 		}
514 	}
515 
516 	return NULL;
517 }
518 
519 int
520 main(int argc, char **argv)
521 {
522 	EditLine *el = el_init(argv[0], stdin, stdout, stderr);
523 	size_t i;
524 	size_t input_len;
525 	el_line_t line;
526 	wchar_t *buffer = malloc(64 * sizeof(*buffer));
527 	if (buffer == NULL)
528 		err(EXIT_FAILURE, "malloc failed");
529 
530 	for (i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++) {
531 		memset(buffer, 0, 64 * sizeof(*buffer));
532 		input_len = wcslen(inputs[i].user_typed_text);
533 		wmemcpy(buffer, inputs[i].user_typed_text, input_len);
534 		buffer[input_len] = 0;
535 		line.buffer = buffer;
536 		line.cursor = line.buffer + input_len ;
537 		line.lastchar = line.cursor - 1;
538 		line.limit = line.buffer + 64 * sizeof(*buffer);
539 		el->el_line = line;
540 		fn_complete(el, mycomplet_func, NULL, break_chars, NULL, NULL, 10, NULL, NULL, NULL, NULL);
541 
542 		/*
543 		 * fn_complete would have expanded and escaped the input in el->el_line.buffer.
544 		 * We need to assert that it matches with the expected value in our test data
545 		 */
546 		printf("User input: %ls\t Expected output: %ls\t Generated output: %ls\n",
547 				inputs[i].user_typed_text, inputs[i].escaped_output, el->el_line.buffer);
548 		assert(wcscmp(el->el_line.buffer, inputs[i].escaped_output) == 0);
549 	}
550 	el_end(el);
551 	return 0;
552 
553 }
554