OILS / core / completion_test.py View on Github | oilshell.org

827 lines, 455 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
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"""completion_test.py: Tests for completion.py."""
9from __future__ import print_function
10
11import os
12import unittest
13import sys
14
15from _devbuild.gen.option_asdl import option_i
16from _devbuild.gen.runtime_asdl import comp_action_e
17from _devbuild.gen.syntax_asdl import proc_sig
18from _devbuild.gen.value_asdl import (value, value_e)
19from core import completion # module under test
20from core import comp_ui
21from core import sh_init
22from core import state
23from core import test_lib
24from core import util
25from mycpp.mylib import log
26from frontend import flag_def # side effect: flags are defined!
27
28_ = flag_def
29from frontend import parse_lib
30from testdata.completion import bash_oracle
31
32# guard some tests that fail on Darwin
33IS_DARWIN = sys.platform == 'darwin'
34
35A1 = completion.TestAction(['foo.py', 'foo', 'bar.py'])
36U1 = completion.UserSpec([A1], [], [], completion.DefaultPredicate(), '', '')
37
38BASE_OPTS = {}
39
40FIRST = completion.TestAction(['grep', 'sed', 'test'])
41U2 = completion.UserSpec([FIRST], [], [], completion.DefaultPredicate(), '',
42 '')
43
44OPT_ARRAY = [False] * option_i.ARRAY_SIZE
45
46
47def MockApi(line):
48 """Match readline's get_begidx() / get_endidx()."""
49 return completion.Api(line=line, begin=0, end=len(line))
50
51
52def _MakeRootCompleter(parse_ctx=None, comp_lookup=None):
53 compopt_state = completion.OptionState()
54 comp_ui_state = comp_ui.State()
55 comp_lookup = comp_lookup or completion.Lookup()
56
57 mem = state.Mem('', [], None, [], {})
58 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
59 mem.exec_opts = exec_opts
60
61 sh_init.InitDefaultVars(mem)
62 mutable_opts.Init()
63
64 if not parse_ctx:
65 parse_ctx = test_lib.InitParseContext(parse_opts=parse_opts,
66 do_lossless=True)
67 parse_ctx.Init_Trail(parse_lib.Trail())
68
69 if 1: # enable for details
70 debug_f = util.DebugFile(sys.stdout)
71 else:
72 debug_f = util.NullDebugFile()
73
74 ev = test_lib.InitWordEvaluator(exec_opts=exec_opts)
75 return completion.RootCompleter(ev, mem, comp_lookup, compopt_state,
76 comp_ui_state, parse_ctx, debug_f)
77
78
79class FunctionsTest(unittest.TestCase):
80
81 def testAdjustArg(self):
82 AdjustArg = completion.AdjustArg
83 out = []
84 AdjustArg(':foo:=bar:', [':', '='], out)
85 self.assertEqual(out, [':', 'foo', ':=', 'bar', ':'])
86
87 out = []
88 AdjustArg('==::==', [':', '='], out)
89 self.assertEqual(out, ['==::=='])
90
91 out = []
92 AdjustArg('==::==', [':'], out)
93 self.assertEqual(out, ['==', '::', '=='])
94
95 # This is like if you get [""] somehow, it should be [""].
96 out = []
97 AdjustArg('', [':', '='], out)
98 self.assertEqual(out, [''])
99
100
101class CompletionTest(unittest.TestCase):
102
103 def _CompApi(self, partial_argv, index, to_complete):
104 comp = completion.Api('', 0, 0)
105 comp.Update('', to_complete, '', index, partial_argv)
106 return comp
107
108 def testLookup(self):
109 c = completion.Lookup()
110 c.RegisterName('grep', BASE_OPTS, U1)
111
112 _, user_spec = c.GetSpecForName('grep')
113 self.assertEqual(1, len(user_spec.actions))
114
115 _, user_spec = c.GetSpecForName('/usr/bin/grep')
116 self.assertEqual(1, len(user_spec.actions))
117
118 c.RegisterGlob('*.py', BASE_OPTS, U1)
119 base_opts, comp = c.GetSpecForName('/usr/bin/foo.py')
120 print('py', comp)
121 # NOTE: This is an implementation detail
122 self.assertEqual(1, len(comp.actions))
123
124 comp_rb = c.GetSpecForName('foo.rb')
125 print('rb', comp_rb)
126
127 def testExternalCommandAction(self):
128 mem = state.Mem('dummy', [], None, [], {})
129 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
130 mem.exec_opts = exec_opts
131
132 a = completion.ExternalCommandAction(mem)
133 comp = self._CompApi([], 0, 'f')
134 print(list(a.Matches(comp)))
135
136 # TODO: This should set up the file system and $PATH and assert that only
137 # executable files are accessed!
138
139 def testFileSystemAction(self):
140 CASES = [
141 # Dirs and files
142 ('m', ['metrics', 'mycpp']),
143 ('opy/doc', ['opy/doc']),
144 ]
145
146 a = completion.FileSystemAction(False, False, False)
147 for prefix, expected in CASES:
148 log('')
149 log('-- PREFIX %r', prefix)
150 comp = self._CompApi([], 0, prefix)
151 self.assertEqual(expected, sorted(a.Matches(comp)))
152
153 os.system('mkdir -p /tmp/oil_comp_test')
154 os.system('bash -c "touch /tmp/oil_comp_test/{one,two,three}"')
155
156 # This test depends on actual file system content. But we choose things
157 # that shouldn't go away.
158 ADD_SLASH_CASES = [
159 # Dirs and files
160 ('o', ['oil-version.txt', 'opy/', 'osh/']),
161 ('nonexistent/', []),
162 ('README.', ['README.md']),
163 # Directory should be completed to core/ ?
164 ('core', ['core/']),
165 ('asdl/R', ['asdl/README.md']),
166 ('opy/doc', ['opy/doc/']),
167 ('opy/doc/', ['opy/doc/opcodes.md']),
168 ('/bi', ['/bin/']),
169 ('/tmp/oil_comp_test/', [
170 '/tmp/oil_comp_test/one',
171 '/tmp/oil_comp_test/three',
172 '/tmp/oil_comp_test/two',
173 ]),
174 ('./o', ['./oil-version.txt', './opy/', './osh/']),
175 ]
176
177 a = completion.FileSystemAction(False, False, True)
178 for prefix, expected in ADD_SLASH_CASES:
179 log('')
180 log('-- PREFIX %s', prefix)
181 comp = self._CompApi([], 0, prefix)
182 self.assertEqual(expected, sorted(a.Matches(comp)))
183
184 # A bunch of repos in oilshell
185 comp = completion.Api('', 0, 0)
186 comp.Update('', '../o', '', 0, None)
187 print(list(a.Matches(comp)))
188
189 EXEC_ONLY_CASES = [('i', ['install'])]
190
191 a = completion.FileSystemAction(False, True, False)
192 for prefix, expected in EXEC_ONLY_CASES:
193 log('')
194 log('-- PREFIX %s', prefix)
195 comp = self._CompApi([], 0, prefix)
196 self.assertEqual(expected, sorted(a.Matches(comp)))
197
198 def testShellFuncExecution(self):
199 arena = test_lib.MakeArena('testShellFuncExecution')
200 c_parser = test_lib.InitCommandParser("""\
201 f() {
202 COMPREPLY=(f1 f2)
203 }
204 """,
205 arena=arena)
206 node = c_parser.ParseLogicalLine()
207
208 cmd_ev = test_lib.InitCommandEvaluator(arena=arena)
209
210 frame = cmd_ev.mem.var_stack[0]
211 assert frame is not None
212 proc = value.Proc(node.name, node.name_tok, proc_sig.Open, node.body,
213 [], True, frame)
214
215 comp_lookup = completion.Lookup()
216 a = completion.ShellFuncAction(cmd_ev, proc, comp_lookup)
217 comp = self._CompApi(['f'], 0, 'f')
218 matches = list(a.Matches(comp))
219 self.assertEqual(['f1', 'f2'], matches)
220
221 def testUserSpec(self):
222 comp = self._CompApi(['f'], 0, 'f')
223 matches = list(U1.AllMatches(comp))
224 self.assertEqual([('foo.py', comp_action_e.Other),
225 ('foo', comp_action_e.Other)], matches)
226
227 predicate = completion.GlobPredicate(False, '*.py')
228 c2 = completion.UserSpec([A1], [], [], predicate, '', '')
229 comp = self._CompApi(['f'], 0, 'f')
230 matches = list(c2.AllMatches(comp))
231 self.assertEqual([('foo.py', comp_action_e.Other)], matches)
232
233
234class RootCompleterTest(unittest.TestCase):
235
236 def testCompletesWords(self):
237 comp_lookup = completion.Lookup()
238
239 comp_lookup.RegisterName('grep', BASE_OPTS, U1)
240 comp_lookup.RegisterName('__first', BASE_OPTS, U2)
241 r = _MakeRootCompleter(comp_lookup=comp_lookup)
242
243 comp = MockApi('grep f')
244 m = list(r.Matches(comp))
245 self.assertEqual(['grep foo.py ', 'grep foo '], m)
246
247 comp = MockApi('grep g')
248 m = list(r.Matches(comp))
249 self.assertEqual([], m)
250
251 # Complete first word
252 m = list(r.Matches(MockApi('g')))
253 self.assertEqual(['grep '], m)
254
255 # Empty completer
256 m = list(r.Matches(MockApi('')))
257 self.assertEqual(['grep ', 'sed ', 'test '], m)
258
259 # Test compound commands. These PARSE
260 m = list(r.Matches(MockApi('echo hi || grep f')))
261 m = list(r.Matches(MockApi('echo hi; grep f')))
262
263 # Brace -- does NOT parse
264 m = list(r.Matches(MockApi('{ echo hi; grep f')))
265 # TODO: Test if/for/while/case/etc.
266
267 m = list(r.Matches(MockApi('var=$v')))
268 m = list(r.Matches(MockApi('local var=$v')))
269
270 def testCompletesHomeDirs(self):
271 r = _MakeRootCompleter()
272
273 comp = MockApi(line='echo ~r')
274 print(comp)
275 m = list(r.Matches(comp))
276
277 # This test isn't hermetic, but I think root should be on all systems.
278 self.assert_('echo ~root/' in m, 'Got %s' % m)
279
280 comp = MockApi(line='echo ~')
281 print(comp)
282 m = list(r.Matches(comp))
283 self.assert_('echo ~root/' in m, 'Got %s' % m)
284
285 # Don't be overly aggressive!
286 comp = MockApi(line='echo a~')
287 m = list(r.Matches(comp))
288 self.assertEqual(0, len(m))
289
290 def testCompletesVarNames(self):
291 r = _MakeRootCompleter()
292
293 # Complete ALL variables
294 comp = MockApi('echo $')
295 self.assertEqual(0, comp.begin) # what readline does
296 self.assertEqual(6, comp.end)
297 print(comp)
298 m = list(r.Matches(comp))
299
300 # Just test for a subset
301 self.assert_('echo $HOSTNAME' in m, m)
302 self.assert_('echo $IFS' in m, m)
303
304 # Now it has a prefix
305 comp = MockApi(line='echo $P')
306 self.assertEqual(0, comp.begin) # what readline does
307 self.assertEqual(7, comp.end)
308 print(comp)
309 m = list(r.Matches(comp))
310 self.assert_('echo $PPID' in m, 'Got %s' % m)
311 self.assert_('echo $PS4' in m, 'Got %s' % m)
312
313 #
314 # BracedVarSub
315 #
316
317 # Complete ALL variables
318 comp = MockApi(line='echo _${')
319 print(comp)
320 m = list(r.Matches(comp))
321 # Just test for a subset
322 self.assert_('echo _${HOSTNAME' in m, 'Got %s' % m)
323 self.assert_('echo _${IFS' in m, 'Got %s' % m)
324
325 # Now it has a prefix
326 comp = MockApi(line='echo ${P')
327 print(comp)
328 m = list(r.Matches(comp))
329 self.assert_('echo ${PPID' in m, 'Got %s' % m)
330 self.assert_('echo ${PS4' in m, 'Got %s' % m)
331
332 # Odd word break
333 # NOTE: We use VSub_Name both for $FOO and ${FOO. Might be bad?
334 comp = MockApi(line='echo ${undef:-$P')
335 print(comp)
336 m = list(r.Matches(comp))
337 self.assert_('echo ${undef:-$PPID' in m, 'Got %s' % m)
338 self.assert_('echo ${undef:-$PS4' in m, 'Got %s' % m)
339
340 comp = MockApi(line='echo ${undef:-$')
341 print(comp)
342 m = list(r.Matches(comp))
343 self.assert_('echo ${undef:-$HOSTNAME' in m, 'Got %s' % m)
344 self.assert_('echo ${undef:-$IFS' in m, 'Got %s' % m)
345
346 #
347 # Double Quoted
348 #
349 # NOTE: GNU readline seems to complete closing quotes? We don't want that.
350
351 comp = MockApi(line='echo "$')
352 print(comp)
353 m = list(r.Matches(comp))
354 self.assert_('echo "$HOSTNAME' in m, 'Got %s' % m)
355 self.assert_('echo "$IFS' in m, 'Got %s' % m)
356
357 comp = MockApi(line='echo "$P')
358 print(comp)
359 m = list(r.Matches(comp))
360 self.assert_('echo "$PPID' in m, 'Got %s' % m)
361 self.assert_('echo "$PS4' in m, 'Got %s' % m)
362
363 #
364 # Prefix operator
365 #
366
367 if 0: # Here you need to handle VSub_Pound
368 comp = MockApi(line='echo ${#')
369 print(comp)
370 m = list(r.Matches(comp))
371 self.assert_('${#HOSTNAME' in m, 'Got %s' % m)
372 self.assert_('${#IFS' in m, 'Got %s' % m)
373
374 comp = MockApi(line='echo "${#P')
375 print(comp)
376 m = list(r.Matches(comp))
377 self.assert_('echo "${#PPID' in m, 'Got %s' % m)
378 self.assert_('echo "${#PS4' in m, 'Got %s' % m)
379
380 #
381 # Arithmetic Context
382 #
383
384 comp = MockApi(line='echo "$((PWD +P') # bare word
385 print(comp)
386 m = list(r.Matches(comp))
387 self.assert_('echo "$((PWD +PPID' in m, 'Got %s' % m)
388 self.assert_('echo "$((PWD +PS4' in m, 'Got %s' % m)
389
390 comp = MockApi(line='echo "$(( $P')
391 print(comp)
392 m = list(r.Matches(comp))
393 self.assert_('echo "$(( $PPID' in m, 'Got %s' % m) # word with $
394 self.assert_('echo "$(( $PS4' in m, 'Got %s' % m)
395
396 def testCompletesCommandSubs(self):
397 comp_lookup = completion.Lookup()
398 comp_lookup.RegisterName('grep', BASE_OPTS, U1)
399 comp_lookup.RegisterName('__first', BASE_OPTS, U2)
400 r = _MakeRootCompleter(comp_lookup=comp_lookup)
401
402 # Normal completion
403 comp = MockApi('gre')
404 m = list(r.Matches(comp))
405 self.assertEqual(['grep '], m)
406
407 # $(command sub)
408 comp = MockApi('echo $(gre')
409 m = list(r.Matches(comp))
410 self.assertEqual(['echo $(grep '], m)
411
412 # `backticks`
413 comp = MockApi('echo `gre')
414 m = list(r.Matches(comp))
415 self.assertEqual(['echo `grep '], m)
416
417 # Args inside `backticks
418 comp = MockApi('echo `grep f')
419 m = list(r.Matches(comp))
420 self.assertEqual(['echo `grep foo.py ', 'echo `grep foo '], m)
421
422 def testCompletesRedirectArguments(self):
423 r = _MakeRootCompleter()
424
425 comp = MockApi('cat < b')
426 m = list(r.Matches(comp))
427 # Some B subdirs of the repo!
428 self.assert_('cat < bin/' in m, 'Got %s' % m)
429 self.assert_('cat < build/' in m, 'Got %s' % m)
430 self.assert_('cat < benchmarks/' in m, 'Got %s' % m)
431
432 # This redirect does NOT take a path argument!
433 comp = MockApi('echo >&')
434 m = list(r.Matches(comp))
435 self.assertEqual(0, len(m))
436
437 def testRunsUserDefinedFunctions(self):
438 # This is here because it's hard to test readline with the spec tests.
439 with open('testdata/completion/osh-unit.bash') as f:
440 code_str = f.read()
441
442 parse_ctx = test_lib.InitParseContext()
443 parse_ctx.Init_Trail(parse_lib.Trail())
444
445 comp_lookup = completion.Lookup()
446 cmd_ev = test_lib.EvalCode(code_str,
447 parse_ctx,
448 comp_lookup=comp_lookup)
449
450 r = _MakeRootCompleter(comp_lookup=comp_lookup)
451
452 # By default, we get a space on the end.
453 m = list(r.Matches(MockApi('mywords t')))
454 self.assertEqual(['mywords three ', 'mywords two '], sorted(m))
455
456 # No space
457 m = list(r.Matches(MockApi('mywords_nospace t')))
458 self.assertEqual(['mywords_nospace three', 'mywords_nospace two'],
459 sorted(m))
460
461 # next 3 fail on darwin
462 if not IS_DARWIN:
463 # Filtered out two and bin
464 m = list(r.Matches(MockApi('flagX ')))
465 self.assertEqual(['flagX one ', 'flagX three '], sorted(m))
466
467 # Filter out everything EXCEPT two and bin
468 m = list(r.Matches(MockApi('flagX_bang ')))
469 self.assertEqual(['flagX_bang bin ', 'flagX_bang two '], sorted(m))
470
471 # -X with -P
472 m = list(r.Matches(MockApi('flagX_prefix ')))
473 self.assertEqual(['flagX_prefix __one ', 'flagX_prefix __three '],
474 sorted(m))
475
476 # -P with plusdirs
477 m = list(r.Matches(MockApi('prefix_plusdirs b')))
478 self.assertEqual([
479 'prefix_plusdirs __bin ',
480 'prefix_plusdirs benchmarks/',
481 'prefix_plusdirs bin/',
482 'prefix_plusdirs build/',
483 'prefix_plusdirs builtin/',
484 ], sorted(m))
485
486 if not IS_DARWIN:
487 # -X with plusdirs. We're filtering out bin/, and then it's added back by
488 # plusdirs. The filter doesn't kill it.
489 m = list(r.Matches(MockApi('flagX_plusdirs b')))
490 self.assertEqual([
491 'flagX_plusdirs benchmarks/',
492 'flagX_plusdirs bin/',
493 'flagX_plusdirs build/',
494 'flagX_plusdirs builtin/',
495 ], sorted(m))
496
497 # -P with dirnames. -P is NOT respected.
498 m = list(r.Matches(MockApi('prefix_dirnames b')))
499 self.assertEqual([
500 'prefix_dirnames benchmarks/',
501 'prefix_dirnames bin/',
502 'prefix_dirnames build/',
503 'prefix_dirnames builtin/',
504 ], sorted(m))
505
506 def testCompletesAliases(self):
507 # I put some aliases in this file.
508 with open('testdata/completion/osh-unit.bash') as f:
509 code_str = f.read()
510 aliases = {}
511 parse_ctx = test_lib.InitParseContext(aliases=aliases)
512 parse_ctx.Init_Trail(parse_lib.Trail())
513 comp_lookup = completion.Lookup()
514
515 cmd_ev = test_lib.EvalCode(code_str,
516 parse_ctx,
517 comp_lookup=comp_lookup,
518 aliases=aliases)
519
520 r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup)
521
522 # The original command
523 m = list(r.Matches(MockApi('ls ')))
524 self.assertEqual(['ls one ', 'ls two '], sorted(m))
525
526 # Alias for the command
527 m = list(r.Matches(MockApi('ll ')))
528 self.assertEqual(['ll one ', 'll two '], sorted(m))
529
530 # DOUBLE alias expansion goes back to original
531 m = list(r.Matches(MockApi('ll_classify ')))
532 self.assertEqual(['ll_classify one ', 'll_classify two '], sorted(m))
533
534 # Trailing space
535 m = list(r.Matches(MockApi('ll_trailing ')))
536 self.assertEqual(['ll_trailing one ', 'll_trailing two '], sorted(m))
537
538 # It should NOT clobber completio registered for aliases
539 m = list(r.Matches(MockApi('ll_own_completion ')))
540 self.assertEqual(
541 ['ll_own_completion own ', 'll_own_completion words '], sorted(m))
542
543 def testNoInfiniteLoop(self):
544 # This was ONE place where we got an infinite loop.
545
546 with open('testdata/completion/return-124.bash') as f:
547 code_str = f.read()
548 parse_ctx = test_lib.InitParseContext()
549 parse_ctx.Init_Trail(parse_lib.Trail())
550
551 comp_lookup = completion.Lookup()
552 cmd_ev = test_lib.EvalCode(code_str,
553 parse_ctx,
554 comp_lookup=comp_lookup)
555
556 r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup)
557
558 m = list(r.Matches(MockApi('bad ')))
559 self.assertEqual([], sorted(m))
560
561 # Error: spec not changed
562 m = list(r.Matches(MockApi('both ')))
563 self.assertEqual([], sorted(m))
564
565 # Redefines completions
566 m = list(r.Matches(MockApi('both2 ')))
567 self.assertEqual(['both2 b1 ', 'both2 b2 '], sorted(m))
568
569 def testCompletesShAssignment(self):
570 # OSH doesn't do this. Here is noticed about bash --norc (which is
571 # undoubtedly different from bash_completion):
572 #
573 # foo=/ho<TAB> completes directory
574 # foo=/home/:/ho<TAB> completes directory
575 #
576 # foo='/ho<TAB> completes directory
577 # foo='/home/:/ho<TAB> does NOT complete
578 #
579 # Ditto for ". The first path is completed, but nothing after :.
580 #
581 # Ditto for echo foo=/ho
582 # echo foo='/ho
583 # echo foo="/ho
584 #
585 # It doesn't distinguish by position.
586 #
587 # TODO:
588 # - test with an image created with debootstrap
589 # - test with an Alpine image
590 return
591
592
593_INIT_TEMPLATE = """
594argv() {
595 python -c 'import sys; print(sys.argv[1:])' "$@"
596}
597
598fail() {
599 echo "Non-fatal assertion failed: $@" >&2
600}
601
602arrays_equal() {
603 local n=$1
604 shift
605
606 # Copy to avoid silly ${@ : : } semantics
607 local argv=("$@")
608
609 local left=( "${argv[@]: 0 : n}" )
610 local right=( "${argv[@]: n : 2*n - 1}" )
611
612 for (( i = 0; i < n; i++ )); do
613 if [[ ${left[i]} != ${right[i]} ]]; then
614 echo -n 'left : '; argv "${left[@]}"
615 echo -n 'right: '; argv "${right[@]}"
616 fail "Word $i differed: ${left[i]} != ${right[i]}"
617 return 1
618 fi
619 done
620 return 0
621}
622
623_init_completion() {
624 compadjust "$@" cur prev words cword
625}
626
627my_complete() {
628 local cur prev words cword split
629
630 # Test this function
631 if arrays_equal 2 a b a b; then
632 echo ok
633 else
634 echo failed
635 return
636 fi
637
638 PASSED=()
639
640 # no quotes with [[
641 if [[ $COMP_LINE == $ORACLE_COMP_LINE ]]; then
642 PASSED+=(COMP_LINE)
643 fi
644 if [[ $COMP_POINT == $ORACLE_COMP_POINT ]]; then
645 PASSED+=(COMP_POINT)
646 fi
647
648 if [[ ${#COMP_WORDS[@]} == ${#ORACLE_COMP_WORDS[@]} ]]; then
649 local n=${#COMP_WORDS[@]}
650 if arrays_equal "$n" "${COMP_WORDS[@]}" "${ORACLE_COMP_WORDS[@]}"; then
651 PASSED+=(COMP_WORDS)
652 fi
653 else
654 fail "COMP_WORDS: Expected ${ORACLE_COMP_WORDS[@]}, got ${COMP_WORDS[@]}"
655 fi
656
657 # This doesn't pass because COMP_WORDS and COMP_CWORD are different.
658 if [[ $COMP_CWORD == $ORACLE_COMP_CWORD ]]; then
659 #echo "passed: COMP_CWORD = $COMP_CWORD"
660 PASSED+=(COMP_CWORD)
661 else
662 fail "COMP_CWORD: Expected $ORACLE_COMP_CWORD, got $COMP_CWORD"
663 fi
664
665 #
666 # Now run _init_completion
667 #
668 _init_completion %(flags)s
669
670 if [[ ${#words[@]} == ${#ORACLE_words[@]} ]]; then
671 local n=${#words[@]}
672 if arrays_equal "$n" "${words[@]}" "${ORACLE_words[@]}"; then
673 PASSED+=(words)
674 fi
675 else
676 fail "COMP_WORDS: Expected ${ORACLE_words[@]}, got ${words[@]}"
677 fi
678
679 if [[ $cur == $ORACLE_cur ]]; then
680 PASSED+=(cur)
681 else
682 fail "cur: Expected $ORACLE_cur, got $cur"
683 fi
684 if [[ $prev == $ORACLE_prev ]]; then
685 PASSED+=(prev)
686 else
687 fail "prev: Expected $ORACLE_prev, got $prev"
688 fi
689 if [[ $cword == $ORACLE_cword ]]; then
690 PASSED+=(cword)
691 else
692 fail "cword: Expected $ORACLE_cword, got $cword"
693 fi
694 if [[ $split == $ORACLE_split ]]; then
695 PASSED+=(split)
696 else
697 fail "split: Expected $ORACLE_split, got $split"
698 fi
699
700 COMPREPLY=(dummy)
701}
702complete -F my_complete %(command)s
703"""
704
705
706class InitCompletionTest(unittest.TestCase):
707
708 def testMatchesOracle(self):
709 for i, case in enumerate(bash_oracle.CASES): # generated data
710 flags = case.get('_init_completion_flags')
711 if flags is None:
712 continue
713
714 # This was input
715 code_str = case['code']
716 assert code_str.endswith('\t')
717
718 log('')
719 log('--- Case %d: %r with flags %s', i, code_str, flags)
720 log('')
721 #print(case)
722
723 oracle_comp_words = case['COMP_WORDS']
724 oracle_comp_cword = case['COMP_CWORD']
725 oracle_comp_line = case['COMP_LINE']
726 oracle_comp_point = case['COMP_POINT']
727
728 # Init completion data
729 oracle_words = case['words']
730 oracle_cur = case['cur']
731 oracle_prev = case['prev']
732 oracle_cword = case['cword']
733 oracle_split = case['split']
734
735 #
736 # First test some invariants on the oracle's data.
737 #
738
739 self.assertEqual(code_str[:-1], oracle_comp_line)
740 # weird invariant that always holds. So isn't COMP_CWORD useless?
741 self.assertEqual(int(oracle_comp_cword),
742 len(oracle_comp_words) - 1)
743 # Another weird invariant. Note this is from the bash ORACLE, not from
744 # our mocks.
745 self.assertEqual(int(oracle_comp_point), len(code_str) - 1)
746
747 #
748 # Now run a piece of code that compares OSH's actual data against the
749 # oracle.
750 #
751
752 init_code = _INIT_TEMPLATE % {
753 'flags': ' '.join(flags),
754 'command': oracle_comp_words[0]
755 }
756
757 arena = test_lib.MakeArena('<InitCompletionTest>')
758 parse_ctx = test_lib.InitParseContext(arena=arena)
759 mem = state.Mem('', [], arena, [], {})
760 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, {}, None)
761 mem.exec_opts = exec_opts
762
763 mutable_opts.Init()
764
765 #
766 # Allow our code to access oracle data
767 #
768 state.SetGlobalArray(mem, 'ORACLE_COMP_WORDS', oracle_comp_words)
769 state.SetGlobalString(mem, 'ORACLE_COMP_CWORD', oracle_comp_cword)
770 state.SetGlobalString(mem, 'ORACLE_COMP_LINE', oracle_comp_line)
771 state.SetGlobalString(mem, 'ORACLE_COMP_POINT', oracle_comp_point)
772
773 state.SetGlobalArray(mem, 'ORACLE_words', oracle_words)
774 state.SetGlobalString(mem, 'ORACLE_cur', oracle_cur)
775 state.SetGlobalString(mem, 'ORACLE_prev', oracle_prev)
776 state.SetGlobalString(mem, 'ORACLE_cword', oracle_cword)
777 state.SetGlobalString(mem, 'ORACLE_split', oracle_split)
778
779 comp_lookup = completion.Lookup()
780 cmd_ev = test_lib.EvalCode(init_code,
781 parse_ctx,
782 comp_lookup=comp_lookup,
783 mem=mem)
784
785 r = _MakeRootCompleter(comp_lookup=comp_lookup)
786 comp = MockApi(code_str[:-1])
787 m = list(r.Matches(comp))
788 log('matches = %s', m)
789
790 # Unterminated quote in case 5. Nothing to complete.
791 # TODO: use a label
792 if i == 5:
793 continue
794
795 # Our test shell script records what passed in an array.
796 val = mem.GetValue('PASSED')
797 self.assertEqual(value_e.BashArray, val.tag(),
798 "[case %d] Expected array, got %s" % (i, val))
799 actually_passed = val.strs
800
801 should_pass = [
802 'COMP_WORDS',
803 'COMP_CWORD',
804 'COMP_LINE',
805 'COMP_POINT', # old API
806 'words',
807 'cur',
808 'prev',
809 'cword',
810 'split' # new API
811 ]
812
813 if i == 4:
814 should_pass.remove('COMP_WORDS')
815 should_pass.remove('COMP_CWORD')
816 should_pass.remove('cword')
817 should_pass.remove('words') # double quotes aren't the same
818
819 for t in should_pass:
820 self.assert_(t in actually_passed,
821 "%r was expected to pass (case %d)" % (t, i))
822
823 log('Ran %d cases', len(bash_oracle.CASES))
824
825
826if __name__ == '__main__':
827 unittest.main()