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

823 lines, 452 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.InitMem(mem, {}, '0.1')
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 proc = value.Proc(node.name, node.name_tok, proc_sig.Open, node.body,
207 [], True)
208
209 cmd_ev = test_lib.InitCommandEvaluator(arena=arena)
210
211 comp_lookup = completion.Lookup()
212 a = completion.ShellFuncAction(cmd_ev, proc, comp_lookup)
213 comp = self._CompApi(['f'], 0, 'f')
214 matches = list(a.Matches(comp))
215 self.assertEqual(['f1', 'f2'], matches)
216
217 def testUserSpec(self):
218 comp = self._CompApi(['f'], 0, 'f')
219 matches = list(U1.AllMatches(comp))
220 self.assertEqual([('foo.py', comp_action_e.Other),
221 ('foo', comp_action_e.Other)], matches)
222
223 predicate = completion.GlobPredicate(False, '*.py')
224 c2 = completion.UserSpec([A1], [], [], predicate, '', '')
225 comp = self._CompApi(['f'], 0, 'f')
226 matches = list(c2.AllMatches(comp))
227 self.assertEqual([('foo.py', comp_action_e.Other)], matches)
228
229
230class RootCompleterTest(unittest.TestCase):
231
232 def testCompletesWords(self):
233 comp_lookup = completion.Lookup()
234
235 comp_lookup.RegisterName('grep', BASE_OPTS, U1)
236 comp_lookup.RegisterName('__first', BASE_OPTS, U2)
237 r = _MakeRootCompleter(comp_lookup=comp_lookup)
238
239 comp = MockApi('grep f')
240 m = list(r.Matches(comp))
241 self.assertEqual(['grep foo.py ', 'grep foo '], m)
242
243 comp = MockApi('grep g')
244 m = list(r.Matches(comp))
245 self.assertEqual([], m)
246
247 # Complete first word
248 m = list(r.Matches(MockApi('g')))
249 self.assertEqual(['grep '], m)
250
251 # Empty completer
252 m = list(r.Matches(MockApi('')))
253 self.assertEqual(['grep ', 'sed ', 'test '], m)
254
255 # Test compound commands. These PARSE
256 m = list(r.Matches(MockApi('echo hi || grep f')))
257 m = list(r.Matches(MockApi('echo hi; grep f')))
258
259 # Brace -- does NOT parse
260 m = list(r.Matches(MockApi('{ echo hi; grep f')))
261 # TODO: Test if/for/while/case/etc.
262
263 m = list(r.Matches(MockApi('var=$v')))
264 m = list(r.Matches(MockApi('local var=$v')))
265
266 def testCompletesHomeDirs(self):
267 r = _MakeRootCompleter()
268
269 comp = MockApi(line='echo ~r')
270 print(comp)
271 m = list(r.Matches(comp))
272
273 # This test isn't hermetic, but I think root should be on all systems.
274 self.assert_('echo ~root/' in m, 'Got %s' % m)
275
276 comp = MockApi(line='echo ~')
277 print(comp)
278 m = list(r.Matches(comp))
279 self.assert_('echo ~root/' in m, 'Got %s' % m)
280
281 # Don't be overly aggressive!
282 comp = MockApi(line='echo a~')
283 m = list(r.Matches(comp))
284 self.assertEqual(0, len(m))
285
286 def testCompletesVarNames(self):
287 r = _MakeRootCompleter()
288
289 # Complete ALL variables
290 comp = MockApi('echo $')
291 self.assertEqual(0, comp.begin) # what readline does
292 self.assertEqual(6, comp.end)
293 print(comp)
294 m = list(r.Matches(comp))
295
296 # Just test for a subset
297 self.assert_('echo $HOSTNAME' in m, m)
298 self.assert_('echo $IFS' in m, m)
299
300 # Now it has a prefix
301 comp = MockApi(line='echo $P')
302 self.assertEqual(0, comp.begin) # what readline does
303 self.assertEqual(7, comp.end)
304 print(comp)
305 m = list(r.Matches(comp))
306 self.assert_('echo $PWD' in m, 'Got %s' % m)
307 self.assert_('echo $PS4' in m, 'Got %s' % m)
308
309 #
310 # BracedVarSub
311 #
312
313 # Complete ALL variables
314 comp = MockApi(line='echo _${')
315 print(comp)
316 m = list(r.Matches(comp))
317 # Just test for a subset
318 self.assert_('echo _${HOSTNAME' in m, 'Got %s' % m)
319 self.assert_('echo _${IFS' in m, 'Got %s' % m)
320
321 # Now it has a prefix
322 comp = MockApi(line='echo ${P')
323 print(comp)
324 m = list(r.Matches(comp))
325 self.assert_('echo ${PWD' in m, 'Got %s' % m)
326 self.assert_('echo ${PS4' in m, 'Got %s' % m)
327
328 # Odd word break
329 # NOTE: We use VSub_Name both for $FOO and ${FOO. Might be bad?
330 comp = MockApi(line='echo ${undef:-$P')
331 print(comp)
332 m = list(r.Matches(comp))
333 self.assert_('echo ${undef:-$PWD' in m, 'Got %s' % m)
334 self.assert_('echo ${undef:-$PS4' in m, 'Got %s' % m)
335
336 comp = MockApi(line='echo ${undef:-$')
337 print(comp)
338 m = list(r.Matches(comp))
339 self.assert_('echo ${undef:-$HOSTNAME' in m, 'Got %s' % m)
340 self.assert_('echo ${undef:-$IFS' in m, 'Got %s' % m)
341
342 #
343 # Double Quoted
344 #
345 # NOTE: GNU readline seems to complete closing quotes? We don't want that.
346
347 comp = MockApi(line='echo "$')
348 print(comp)
349 m = list(r.Matches(comp))
350 self.assert_('echo "$HOSTNAME' in m, 'Got %s' % m)
351 self.assert_('echo "$IFS' in m, 'Got %s' % m)
352
353 comp = MockApi(line='echo "$P')
354 print(comp)
355 m = list(r.Matches(comp))
356 self.assert_('echo "$PWD' in m, 'Got %s' % m)
357 self.assert_('echo "$PS4' in m, 'Got %s' % m)
358
359 #
360 # Prefix operator
361 #
362
363 if 0: # Here you need to handle VSub_Pound
364 comp = MockApi(line='echo ${#')
365 print(comp)
366 m = list(r.Matches(comp))
367 self.assert_('${#HOSTNAME' in m, 'Got %s' % m)
368 self.assert_('${#IFS' in m, 'Got %s' % m)
369
370 comp = MockApi(line='echo "${#P')
371 print(comp)
372 m = list(r.Matches(comp))
373 self.assert_('echo "${#PWD' in m, 'Got %s' % m)
374 self.assert_('echo "${#PS4' in m, 'Got %s' % m)
375
376 #
377 # Arithmetic Context
378 #
379
380 comp = MockApi(line='echo "$((PWD +P') # bare word
381 print(comp)
382 m = list(r.Matches(comp))
383 self.assert_('echo "$((PWD +PWD' in m, 'Got %s' % m)
384 self.assert_('echo "$((PWD +PS4' in m, 'Got %s' % m)
385
386 comp = MockApi(line='echo "$(( $P')
387 print(comp)
388 m = list(r.Matches(comp))
389 self.assert_('echo "$(( $PWD' in m, 'Got %s' % m) # word with $
390 self.assert_('echo "$(( $PS4' in m, 'Got %s' % m)
391
392 def testCompletesCommandSubs(self):
393 comp_lookup = completion.Lookup()
394 comp_lookup.RegisterName('grep', BASE_OPTS, U1)
395 comp_lookup.RegisterName('__first', BASE_OPTS, U2)
396 r = _MakeRootCompleter(comp_lookup=comp_lookup)
397
398 # Normal completion
399 comp = MockApi('gre')
400 m = list(r.Matches(comp))
401 self.assertEqual(['grep '], m)
402
403 # $(command sub)
404 comp = MockApi('echo $(gre')
405 m = list(r.Matches(comp))
406 self.assertEqual(['echo $(grep '], m)
407
408 # `backticks`
409 comp = MockApi('echo `gre')
410 m = list(r.Matches(comp))
411 self.assertEqual(['echo `grep '], m)
412
413 # Args inside `backticks
414 comp = MockApi('echo `grep f')
415 m = list(r.Matches(comp))
416 self.assertEqual(['echo `grep foo.py ', 'echo `grep foo '], m)
417
418 def testCompletesRedirectArguments(self):
419 r = _MakeRootCompleter()
420
421 comp = MockApi('cat < b')
422 m = list(r.Matches(comp))
423 # Some B subdirs of the repo!
424 self.assert_('cat < bin/' in m, 'Got %s' % m)
425 self.assert_('cat < build/' in m, 'Got %s' % m)
426 self.assert_('cat < benchmarks/' in m, 'Got %s' % m)
427
428 # This redirect does NOT take a path argument!
429 comp = MockApi('echo >&')
430 m = list(r.Matches(comp))
431 self.assertEqual(0, len(m))
432
433 def testRunsUserDefinedFunctions(self):
434 # This is here because it's hard to test readline with the spec tests.
435 with open('testdata/completion/osh-unit.bash') as f:
436 code_str = f.read()
437
438 parse_ctx = test_lib.InitParseContext()
439 parse_ctx.Init_Trail(parse_lib.Trail())
440
441 comp_lookup = completion.Lookup()
442 cmd_ev = test_lib.EvalCode(code_str,
443 parse_ctx,
444 comp_lookup=comp_lookup)
445
446 r = _MakeRootCompleter(comp_lookup=comp_lookup)
447
448 # By default, we get a space on the end.
449 m = list(r.Matches(MockApi('mywords t')))
450 self.assertEqual(['mywords three ', 'mywords two '], sorted(m))
451
452 # No space
453 m = list(r.Matches(MockApi('mywords_nospace t')))
454 self.assertEqual(['mywords_nospace three', 'mywords_nospace two'],
455 sorted(m))
456
457 # next 3 fail on darwin
458 if not IS_DARWIN:
459 # Filtered out two and bin
460 m = list(r.Matches(MockApi('flagX ')))
461 self.assertEqual(['flagX one ', 'flagX three '], sorted(m))
462
463 # Filter out everything EXCEPT two and bin
464 m = list(r.Matches(MockApi('flagX_bang ')))
465 self.assertEqual(['flagX_bang bin ', 'flagX_bang two '], sorted(m))
466
467 # -X with -P
468 m = list(r.Matches(MockApi('flagX_prefix ')))
469 self.assertEqual(['flagX_prefix __one ', 'flagX_prefix __three '],
470 sorted(m))
471
472 # -P with plusdirs
473 m = list(r.Matches(MockApi('prefix_plusdirs b')))
474 self.assertEqual([
475 'prefix_plusdirs __bin ',
476 'prefix_plusdirs benchmarks/',
477 'prefix_plusdirs bin/',
478 'prefix_plusdirs build/',
479 'prefix_plusdirs builtin/',
480 ], sorted(m))
481
482 if not IS_DARWIN:
483 # -X with plusdirs. We're filtering out bin/, and then it's added back by
484 # plusdirs. The filter doesn't kill it.
485 m = list(r.Matches(MockApi('flagX_plusdirs b')))
486 self.assertEqual([
487 'flagX_plusdirs benchmarks/',
488 'flagX_plusdirs bin/',
489 'flagX_plusdirs build/',
490 'flagX_plusdirs builtin/',
491 ], sorted(m))
492
493 # -P with dirnames. -P is NOT respected.
494 m = list(r.Matches(MockApi('prefix_dirnames b')))
495 self.assertEqual([
496 'prefix_dirnames benchmarks/',
497 'prefix_dirnames bin/',
498 'prefix_dirnames build/',
499 'prefix_dirnames builtin/',
500 ], sorted(m))
501
502 def testCompletesAliases(self):
503 # I put some aliases in this file.
504 with open('testdata/completion/osh-unit.bash') as f:
505 code_str = f.read()
506 aliases = {}
507 parse_ctx = test_lib.InitParseContext(aliases=aliases)
508 parse_ctx.Init_Trail(parse_lib.Trail())
509 comp_lookup = completion.Lookup()
510
511 cmd_ev = test_lib.EvalCode(code_str,
512 parse_ctx,
513 comp_lookup=comp_lookup,
514 aliases=aliases)
515
516 r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup)
517
518 # The original command
519 m = list(r.Matches(MockApi('ls ')))
520 self.assertEqual(['ls one ', 'ls two '], sorted(m))
521
522 # Alias for the command
523 m = list(r.Matches(MockApi('ll ')))
524 self.assertEqual(['ll one ', 'll two '], sorted(m))
525
526 # DOUBLE alias expansion goes back to original
527 m = list(r.Matches(MockApi('ll_classify ')))
528 self.assertEqual(['ll_classify one ', 'll_classify two '], sorted(m))
529
530 # Trailing space
531 m = list(r.Matches(MockApi('ll_trailing ')))
532 self.assertEqual(['ll_trailing one ', 'll_trailing two '], sorted(m))
533
534 # It should NOT clobber completio registered for aliases
535 m = list(r.Matches(MockApi('ll_own_completion ')))
536 self.assertEqual(
537 ['ll_own_completion own ', 'll_own_completion words '], sorted(m))
538
539 def testNoInfiniteLoop(self):
540 # This was ONE place where we got an infinite loop.
541
542 with open('testdata/completion/return-124.bash') as f:
543 code_str = f.read()
544 parse_ctx = test_lib.InitParseContext()
545 parse_ctx.Init_Trail(parse_lib.Trail())
546
547 comp_lookup = completion.Lookup()
548 cmd_ev = test_lib.EvalCode(code_str,
549 parse_ctx,
550 comp_lookup=comp_lookup)
551
552 r = _MakeRootCompleter(parse_ctx=parse_ctx, comp_lookup=comp_lookup)
553
554 m = list(r.Matches(MockApi('bad ')))
555 self.assertEqual([], sorted(m))
556
557 # Error: spec not changed
558 m = list(r.Matches(MockApi('both ')))
559 self.assertEqual([], sorted(m))
560
561 # Redefines completions
562 m = list(r.Matches(MockApi('both2 ')))
563 self.assertEqual(['both2 b1 ', 'both2 b2 '], sorted(m))
564
565 def testCompletesShAssignment(self):
566 # OSH doesn't do this. Here is noticed about bash --norc (which is
567 # undoubtedly different from bash_completion):
568 #
569 # foo=/ho<TAB> completes directory
570 # foo=/home/:/ho<TAB> completes directory
571 #
572 # foo='/ho<TAB> completes directory
573 # foo='/home/:/ho<TAB> does NOT complete
574 #
575 # Ditto for ". The first path is completed, but nothing after :.
576 #
577 # Ditto for echo foo=/ho
578 # echo foo='/ho
579 # echo foo="/ho
580 #
581 # It doesn't distinguish by position.
582 #
583 # TODO:
584 # - test with an image created with debootstrap
585 # - test with an Alpine image
586 return
587
588
589_INIT_TEMPLATE = """
590argv() {
591 python -c 'import sys; print(sys.argv[1:])' "$@"
592}
593
594fail() {
595 echo "Non-fatal assertion failed: $@" >&2
596}
597
598arrays_equal() {
599 local n=$1
600 shift
601
602 # Copy to avoid silly ${@ : : } semantics
603 local argv=("$@")
604
605 local left=( "${argv[@]: 0 : n}" )
606 local right=( "${argv[@]: n : 2*n - 1}" )
607
608 for (( i = 0; i < n; i++ )); do
609 if [[ ${left[i]} != ${right[i]} ]]; then
610 echo -n 'left : '; argv "${left[@]}"
611 echo -n 'right: '; argv "${right[@]}"
612 fail "Word $i differed: ${left[i]} != ${right[i]}"
613 return 1
614 fi
615 done
616 return 0
617}
618
619_init_completion() {
620 compadjust "$@" cur prev words cword
621}
622
623my_complete() {
624 local cur prev words cword split
625
626 # Test this function
627 if arrays_equal 2 a b a b; then
628 echo ok
629 else
630 echo failed
631 return
632 fi
633
634 PASSED=()
635
636 # no quotes with [[
637 if [[ $COMP_LINE == $ORACLE_COMP_LINE ]]; then
638 PASSED+=(COMP_LINE)
639 fi
640 if [[ $COMP_POINT == $ORACLE_COMP_POINT ]]; then
641 PASSED+=(COMP_POINT)
642 fi
643
644 if [[ ${#COMP_WORDS[@]} == ${#ORACLE_COMP_WORDS[@]} ]]; then
645 local n=${#COMP_WORDS[@]}
646 if arrays_equal "$n" "${COMP_WORDS[@]}" "${ORACLE_COMP_WORDS[@]}"; then
647 PASSED+=(COMP_WORDS)
648 fi
649 else
650 fail "COMP_WORDS: Expected ${ORACLE_COMP_WORDS[@]}, got ${COMP_WORDS[@]}"
651 fi
652
653 # This doesn't pass because COMP_WORDS and COMP_CWORD are different.
654 if [[ $COMP_CWORD == $ORACLE_COMP_CWORD ]]; then
655 #echo "passed: COMP_CWORD = $COMP_CWORD"
656 PASSED+=(COMP_CWORD)
657 else
658 fail "COMP_CWORD: Expected $ORACLE_COMP_CWORD, got $COMP_CWORD"
659 fi
660
661 #
662 # Now run _init_completion
663 #
664 _init_completion %(flags)s
665
666 if [[ ${#words[@]} == ${#ORACLE_words[@]} ]]; then
667 local n=${#words[@]}
668 if arrays_equal "$n" "${words[@]}" "${ORACLE_words[@]}"; then
669 PASSED+=(words)
670 fi
671 else
672 fail "COMP_WORDS: Expected ${ORACLE_words[@]}, got ${words[@]}"
673 fi
674
675 if [[ $cur == $ORACLE_cur ]]; then
676 PASSED+=(cur)
677 else
678 fail "cur: Expected $ORACLE_cur, got $cur"
679 fi
680 if [[ $prev == $ORACLE_prev ]]; then
681 PASSED+=(prev)
682 else
683 fail "prev: Expected $ORACLE_prev, got $prev"
684 fi
685 if [[ $cword == $ORACLE_cword ]]; then
686 PASSED+=(cword)
687 else
688 fail "cword: Expected $ORACLE_cword, got $cword"
689 fi
690 if [[ $split == $ORACLE_split ]]; then
691 PASSED+=(split)
692 else
693 fail "split: Expected $ORACLE_split, got $split"
694 fi
695
696 COMPREPLY=(dummy)
697}
698complete -F my_complete %(command)s
699"""
700
701
702class InitCompletionTest(unittest.TestCase):
703
704 def testMatchesOracle(self):
705 for i, case in enumerate(bash_oracle.CASES): # generated data
706 flags = case.get('_init_completion_flags')
707 if flags is None:
708 continue
709
710 # This was input
711 code_str = case['code']
712 assert code_str.endswith('\t')
713
714 log('')
715 log('--- Case %d: %r with flags %s', i, code_str, flags)
716 log('')
717 #print(case)
718
719 oracle_comp_words = case['COMP_WORDS']
720 oracle_comp_cword = case['COMP_CWORD']
721 oracle_comp_line = case['COMP_LINE']
722 oracle_comp_point = case['COMP_POINT']
723
724 # Init completion data
725 oracle_words = case['words']
726 oracle_cur = case['cur']
727 oracle_prev = case['prev']
728 oracle_cword = case['cword']
729 oracle_split = case['split']
730
731 #
732 # First test some invariants on the oracle's data.
733 #
734
735 self.assertEqual(code_str[:-1], oracle_comp_line)
736 # weird invariant that always holds. So isn't COMP_CWORD useless?
737 self.assertEqual(int(oracle_comp_cword),
738 len(oracle_comp_words) - 1)
739 # Another weird invariant. Note this is from the bash ORACLE, not from
740 # our mocks.
741 self.assertEqual(int(oracle_comp_point), len(code_str) - 1)
742
743 #
744 # Now run a piece of code that compares OSH's actual data against the
745 # oracle.
746 #
747
748 init_code = _INIT_TEMPLATE % {
749 'flags': ' '.join(flags),
750 'command': oracle_comp_words[0]
751 }
752
753 arena = test_lib.MakeArena('<InitCompletionTest>')
754 parse_ctx = test_lib.InitParseContext(arena=arena)
755 mem = state.Mem('', [], arena, [])
756 parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, None)
757 mem.exec_opts = exec_opts
758
759 mutable_opts.Init()
760
761 #
762 # Allow our code to access oracle data
763 #
764 state.SetGlobalArray(mem, 'ORACLE_COMP_WORDS', oracle_comp_words)
765 state.SetGlobalString(mem, 'ORACLE_COMP_CWORD', oracle_comp_cword)
766 state.SetGlobalString(mem, 'ORACLE_COMP_LINE', oracle_comp_line)
767 state.SetGlobalString(mem, 'ORACLE_COMP_POINT', oracle_comp_point)
768
769 state.SetGlobalArray(mem, 'ORACLE_words', oracle_words)
770 state.SetGlobalString(mem, 'ORACLE_cur', oracle_cur)
771 state.SetGlobalString(mem, 'ORACLE_prev', oracle_prev)
772 state.SetGlobalString(mem, 'ORACLE_cword', oracle_cword)
773 state.SetGlobalString(mem, 'ORACLE_split', oracle_split)
774
775 comp_lookup = completion.Lookup()
776 cmd_ev = test_lib.EvalCode(init_code,
777 parse_ctx,
778 comp_lookup=comp_lookup,
779 mem=mem)
780
781 r = _MakeRootCompleter(comp_lookup=comp_lookup)
782 comp = MockApi(code_str[:-1])
783 m = list(r.Matches(comp))
784 log('matches = %s', m)
785
786 # Unterminated quote in case 5. Nothing to complete.
787 # TODO: use a label
788 if i == 5:
789 continue
790
791 # Our test shell script records what passed in an array.
792 val = mem.GetValue('PASSED')
793 self.assertEqual(value_e.BashArray, val.tag(),
794 "[case %d] Expected array, got %s" % (i, val))
795 actually_passed = val.strs
796
797 should_pass = [
798 'COMP_WORDS',
799 'COMP_CWORD',
800 'COMP_LINE',
801 'COMP_POINT', # old API
802 'words',
803 'cur',
804 'prev',
805 'cword',
806 'split' # new API
807 ]
808
809 if i == 4:
810 should_pass.remove('COMP_WORDS')
811 should_pass.remove('COMP_CWORD')
812 should_pass.remove('cword')
813 should_pass.remove('words') # double quotes aren't the same
814
815 for t in should_pass:
816 self.assert_(t in actually_passed,
817 "%r was expected to pass (case %d)" % (t, i))
818
819 log('Ran %d cases', len(bash_oracle.CASES))
820
821
822if __name__ == '__main__':
823 unittest.main()