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

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