text: # coding=utf-8
  # flake8: noqa E302
  """
  Unit/functional testing for readline tab completion functions in the cmd2.py module.
  
  These are primarily tests related to readline completer functions which handle tab completion of cmd2/cmd commands,
  file system paths, and shell commands.
  """
  import enum
  import os
  import sys
  from unittest import (
      mock,
  )
  
  import pytest
  
  import cmd2
  from cmd2 import (
      utils,
  )
  from examples.subcommands import (
      SubcommandsExample,
  )
  
  from .conftest import (
      complete_tester,
      normalize,
      run_cmd,
  )
  
  # List of strings used with completion functions
  food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato', 'Cheese "Pizza"']
  sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball']
  delimited_strs = [
      '/home/user/file.txt',
      '/home/user/file space.txt',
      '/home/user/prog.c',
      '/home/other user/maps',
      '/home/other user/tests',
  ]
  
  # Dictionary used with flag based completion functions
  flag_dict = {
      # Tab complete food items after -f and --food flag in command line
      '-f': food_item_strs,
      '--food': food_item_strs,
      # Tab complete sport items after -s and --sport flag in command line
      '-s': sport_item_strs,
      '--sport': sport_item_strs,
  }
  
  # Dictionary used with index based completion functions
  index_dict = {
      1: food_item_strs,  # Tab complete food items at index 1 in command line
      2: sport_item_strs,  # Tab complete sport items at index 2 in command line
  }
  
  
  class CompletionsExample(cmd2.Cmd):
      """
      Example cmd2 application used to exercise tab completion tests
      """
  
      def __init__(self):
          cmd2.Cmd.__init__(self, multiline_commands=['test_multiline'])
          self.foo = 'bar'
          self.add_settable(
              utils.Settable(
                  'foo',
                  str,
                  description="a settable param",
                  settable_object=self,
                  completer=CompletionsExample.complete_foo_val,
              )
          )
  
      def do_test_basic(self, args):
          pass
  
      def complete_test_basic(self, text, line, begidx, endidx):
          return self.basic_complete(text, line, begidx, endidx, food_item_strs)
  
      def do_test_delimited(self, args):
          pass
  
      def complete_test_delimited(self, text, line, begidx, endidx):
          return self.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
  
      def do_test_sort_key(self, args):
          pass
  
      def complete_test_sort_key(self, text, line, begidx, endidx):
          num_strs = ['2', '11', '1']
          return self.basic_complete(text, line, begidx, endidx, num_strs)
  
      def do_test_raise_exception(self, args):
          pass
  
      def complete_test_raise_exception(self, text, line, begidx, endidx):
          raise IndexError("You are out of bounds!!")
  
      def do_test_multiline(self, args):
          pass
  
      def complete_test_multiline(self, text, line, begidx, endidx):
          return self.basic_complete(text, line, begidx, endidx, sport_item_strs)
  
      def do_test_no_completer(self, args):
          """Completing this should result in completedefault() being called"""
          pass
  
      def complete_foo_val(self, text, line, begidx, endidx, arg_tokens):
          """Supports unit testing cmd2.Cmd2.complete_set_val to confirm it passes all tokens in the set command"""
          if 'param' in arg_tokens:
              return ["SUCCESS"]
          else:
              return ["FAIL"]
  
      def completedefault(self, *ignored):
          """Method called to complete an input line when no command-specific
          complete_*() method is available.
  
          """
          return ['default']
  
  
  @pytest.fixture
  def cmd2_app():
      c = CompletionsExample()
      return c
  
  
  def test_cmd2_command_completion_single(cmd2_app):
      text = 'he'
      line = text
      endidx = len(line)
      begidx = endidx - len(text)
      assert cmd2_app.completenames(text, line, begidx, endidx) == ['help']
  
  
  def test_complete_command_single(cmd2_app):
      text = 'he'
      line = text
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == ['help ']
  
  
  def test_complete_empty_arg(cmd2_app):
      text = ''
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = sorted(cmd2_app.get_visible_commands(), key=cmd2_app.default_sort_key)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
  
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_complete_bogus_command(cmd2_app):
      text = ''
      line = 'fizbuzz {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = ['default ']
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_complete_exception(cmd2_app, capsys):
      text = ''
      line = 'test_raise_exception {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      out, err = capsys.readouterr()
  
      assert first_match is None
      assert "IndexError" in err
  
  
  def test_complete_macro(base_app, request):
      # Create the macro
      out, err = run_cmd(base_app, 'macro create fake run_pyscript {1}')
      assert out == normalize("Macro 'fake' created")
  
      # Macros do path completion
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 's')
      line = 'fake {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep]
      first_match = complete_tester(text, line, begidx, endidx, base_app)
      assert first_match is not None and base_app.completion_matches == expected
  
  
  def test_default_sort_key(cmd2_app):
      text = ''
      line = 'test_sort_key {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      # First do alphabetical sorting
      cmd2_app.default_sort_key = cmd2.Cmd.ALPHABETICAL_SORT_KEY
      expected = ['1', '11', '2']
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
      # Now switch to natural sorting
      cmd2_app.default_sort_key = cmd2.Cmd.NATURAL_SORT_KEY
      expected = ['1', '2', '11']
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_cmd2_command_completion_multiple(cmd2_app):
      text = 'h'
      line = text
      endidx = len(line)
      begidx = endidx - len(text)
      assert cmd2_app.completenames(text, line, begidx, endidx) == ['help', 'history']
  
  
  def test_cmd2_command_completion_nomatch(cmd2_app):
      text = 'fakecommand'
      line = text
      endidx = len(line)
      begidx = endidx - len(text)
      assert cmd2_app.completenames(text, line, begidx, endidx) == []
  
  
  def test_cmd2_help_completion_single(cmd2_app):
      text = 'he'
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and cmd2_app.completion_matches == ['help ']
  
  
  def test_cmd2_help_completion_multiple(cmd2_app):
      text = 'h'
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == ['help', 'history']
  
  
  def test_cmd2_help_completion_nomatch(cmd2_app):
      text = 'fakecommand'
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None
  
  
  def test_set_allow_style_completion(cmd2_app):
      """Confirm that completing allow_style presents AllowStyle strings"""
      text = ''
      line = 'set allow_style'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = [val.name.lower() for val in cmd2.ansi.AllowStyle]
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match
      assert cmd2_app.completion_matches == sorted(expected, key=cmd2_app.default_sort_key)
  
  
  def test_set_bool_completion(cmd2_app):
      """Confirm that completing a boolean Settable presents true and false strings"""
      text = ''
      line = 'set debug'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = ['false', 'true']
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match
      assert cmd2_app.completion_matches == sorted(expected, key=cmd2_app.default_sort_key)
  
  
  def test_shell_command_completion_shortcut(cmd2_app):
      # Made sure ! runs a shell command and all matches start with ! since there
      # isn't a space between ! and the shell command. Display matches won't
      # begin with the !.
      if sys.platform == "win32":
          text = '!calc'
          expected = ['!calc.exe ']
          expected_display = ['calc.exe']
      else:
          text = '!egr'
          expected = ['!egrep ']
          expected_display = ['egrep']
  
      line = text
      endidx = len(line)
      begidx = 0
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected and cmd2_app.display_matches == expected_display
  
  
  def test_shell_command_completion_doesnt_match_wildcards(cmd2_app):
      if sys.platform == "win32":
          text = 'c*'
      else:
          text = 'e*'
  
      line = 'shell {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None
  
  
  def test_shell_command_completion_multiple(cmd2_app):
      if sys.platform == "win32":
          text = 'c'
          expected = 'calc.exe'
      else:
          text = 'l'
          expected = 'ls'
  
      line = 'shell {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and expected in cmd2_app.completion_matches
  
  
  def test_shell_command_completion_nomatch(cmd2_app):
      text = 'zzzz'
      line = 'shell {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None
  
  
  def test_shell_command_completion_doesnt_complete_when_just_shell(cmd2_app):
      text = ''
      line = 'shell {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None
  
  
  def test_shell_command_completion_does_path_completion_when_after_command(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'conftest')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == [text + '.py ']
  
  
  def test_shell_commmand_complete_in_path(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 's')
      line = 'shell {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      # Since this will look for directories and executables in the given path,
      # we expect to see the scripts dir among the results
      expected = os.path.join(test_dir, 'scripts' + os.path.sep)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and expected in cmd2_app.completion_matches
  
  
  def test_path_completion_single_end(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'conftest')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.path_complete(text, line, begidx, endidx) == [text + '.py']
  
  
  def test_path_completion_multiple(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 's')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      matches = cmd2_app.path_complete(text, line, begidx, endidx)
      expected = [text + 'cript.py', text + 'cript.txt', text + 'cripts' + os.path.sep]
      assert matches == expected
  
  
  def test_path_completion_nomatch(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'fakepath')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.path_complete(text, line, begidx, endidx) == []
  
  
  def test_default_to_shell_completion(cmd2_app, request):
      cmd2_app.default_to_shell = True
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'conftest')
  
      if sys.platform == "win32":
          command = 'calc.exe'
      else:
          command = 'egrep'
  
      # Make sure the command is on the testing system
      assert command in utils.get_exes_in_path(command)
      line = '{} {}'.format(command, text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == [text + '.py ']
  
  
  def test_path_completion_no_text(cmd2_app):
      # Run path complete with no search text which should show what's in cwd
      text = ''
      line = 'shell ls {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions_no_text = cmd2_app.path_complete(text, line, begidx, endidx)
  
      # Run path complete with path set to the CWD
      text = os.getcwd() + os.path.sep
      line = 'shell ls {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      # We have to strip off the path from the beginning since the matches are entire paths
      completions_cwd = [match.replace(text, '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)]
  
      # Verify that the first test gave results for entries in the cwd
      assert completions_no_text == completions_cwd
      assert completions_cwd
  
  
  def test_path_completion_no_path(cmd2_app):
      # Run path complete with search text that isn't preceded by a path. This should use CWD as the path.
      text = 's'
      line = 'shell ls {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions_no_text = cmd2_app.path_complete(text, line, begidx, endidx)
  
      # Run path complete with path set to the CWD
      text = os.getcwd() + os.path.sep + 's'
      line = 'shell ls {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      # We have to strip off the path from the beginning since the matches are entire paths (Leave the 's')
      completions_cwd = [match.replace(text[:-1], '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)]
  
      # Verify that the first test gave results for entries in the cwd
      assert completions_no_text == completions_cwd
      assert completions_cwd
  
  
  @pytest.mark.skipif(sys.platform == 'win32', reason="this only applies on systems where the root directory is a slash")
  def test_path_completion_cwd_is_root_dir(cmd2_app):
      # Change our CWD to root dir
      cwd = os.getcwd()
      os.chdir(os.path.sep)
  
      text = ''
      line = 'shell ls {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions = cmd2_app.path_complete(text, line, begidx, endidx)
  
      # No match should start with a slash
      assert not any(match.startswith(os.path.sep) for match in completions)
  
      # Restore CWD
      os.chdir(cwd)
  
  
  def test_path_completion_doesnt_match_wildcards(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'c*')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      # Currently path completion doesn't accept wildcards, so will always return empty results
      assert cmd2_app.path_complete(text, line, begidx, endidx) == []
  
  
  def test_path_completion_complete_user(cmd2_app):
      import getpass
  
      user = getpass.getuser()
  
      text = '~{}'.format(user)
      line = 'shell fake {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions = cmd2_app.path_complete(text, line, begidx, endidx)
  
      expected = text + os.path.sep
      assert expected in completions
  
  
  def test_path_completion_user_path_expansion(cmd2_app):
      # Run path with a tilde and a slash
      if sys.platform.startswith('win'):
          cmd = 'dir'
      else:
          cmd = 'ls'
  
      # Use a ~ which will be expanded into the user's home directory
      text = '~{}'.format(os.path.sep)
      line = 'shell {} {}'.format(cmd, text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions_tilde_slash = [match.replace(text, '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)]
  
      # Run path complete on the user's home directory
      text = os.path.expanduser('~') + os.path.sep
      line = 'shell {} {}'.format(cmd, text)
      endidx = len(line)
      begidx = endidx - len(text)
      completions_home = [match.replace(text, '', 1) for match in cmd2_app.path_complete(text, line, begidx, endidx)]
  
      assert completions_tilde_slash == completions_home
  
  
  def test_path_completion_directories_only(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 's')
      line = 'shell cat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = [text + 'cripts' + os.path.sep]
  
      assert cmd2_app.path_complete(text, line, begidx, endidx, path_filter=os.path.isdir) == expected
  
  
  def test_basic_completion_single(cmd2_app):
      text = 'Pi'
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == ['Pizza']
  
  
  def test_basic_completion_multiple(cmd2_app):
      text = ''
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      matches = sorted(cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs))
      assert matches == sorted(food_item_strs)
  
  
  def test_basic_completion_nomatch(cmd2_app):
      text = 'q'
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.basic_complete(text, line, begidx, endidx, food_item_strs) == []
  
  
  def test_delimiter_completion(cmd2_app):
      text = '/home/'
      line = 'run_script {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      cmd2_app.delimiter_complete(text, line, begidx, endidx, delimited_strs, '/')
  
      # Remove duplicates from display_matches and sort it. This is typically done in complete().
      display_list = utils.remove_duplicates(cmd2_app.display_matches)
      display_list = utils.alphabetical_sort(display_list)
  
      assert display_list == ['other user', 'user']
  
  
  def test_flag_based_completion_single(cmd2_app):
      text = 'Pi'
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == ['Pizza']
  
  
  def test_flag_based_completion_multiple(cmd2_app):
      text = ''
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      matches = sorted(cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict))
      assert matches == sorted(food_item_strs)
  
  
  def test_flag_based_completion_nomatch(cmd2_app):
      text = 'q'
      line = 'list_food -f {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == []
  
  
  def test_flag_based_default_completer(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'c')
      line = 'list_food {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict, all_else=cmd2_app.path_complete) == [
          text + 'onftest.py'
      ]
  
  
  def test_flag_based_callable_completer(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'c')
      line = 'list_food -o {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      flag_dict['-o'] = cmd2_app.path_complete
      assert cmd2_app.flag_based_complete(text, line, begidx, endidx, flag_dict) == [text + 'onftest.py']
  
  
  def test_index_based_completion_single(cmd2_app):
      text = 'Foo'
      line = 'command Pizza {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == ['Football']
  
  
  def test_index_based_completion_multiple(cmd2_app):
      text = ''
      line = 'command Pizza {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      matches = sorted(cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict))
      assert matches == sorted(sport_item_strs)
  
  
  def test_index_based_completion_nomatch(cmd2_app):
      text = 'q'
      line = 'command {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
      assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == []
  
  
  def test_index_based_default_completer(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'c')
      line = 'command Pizza Bat Computer {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict, all_else=cmd2_app.path_complete) == [
          text + 'onftest.py'
      ]
  
  
  def test_index_based_callable_completer(cmd2_app, request):
      test_dir = os.path.dirname(request.module.__file__)
  
      text = os.path.join(test_dir, 'c')
      line = 'command Pizza Bat {}'.format(text)
  
      endidx = len(line)
      begidx = endidx - len(text)
  
      index_dict[3] = cmd2_app.path_complete
      assert cmd2_app.index_based_complete(text, line, begidx, endidx, index_dict) == [text + 'onftest.py']
  
  
  def test_tokens_for_completion_quoted(cmd2_app):
      text = 'Pi'
      line = 'list_food "{}"'.format(text)
      endidx = len(line)
      begidx = endidx
  
      expected_tokens = ['list_food', 'Pi', '']
      expected_raw_tokens = ['list_food', '"Pi"', '']
  
      tokens, raw_tokens = cmd2_app.tokens_for_completion(line, begidx, endidx)
      assert expected_tokens == tokens
      assert expected_raw_tokens == raw_tokens
  
  
  def test_tokens_for_completion_unclosed_quote(cmd2_app):
      text = 'Pi'
      line = 'list_food "{}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_tokens = ['list_food', 'Pi']
      expected_raw_tokens = ['list_food', '"Pi']
  
      tokens, raw_tokens = cmd2_app.tokens_for_completion(line, begidx, endidx)
      assert expected_tokens == tokens
      assert expected_raw_tokens == raw_tokens
  
  
  def test_tokens_for_completion_punctuation(cmd2_app):
      """Test that redirectors and terminators are word delimiters"""
      text = 'file'
      line = 'command | < ;>>{}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_tokens = ['command', '|', '<', ';', '>>', 'file']
      expected_raw_tokens = ['command', '|', '<', ';', '>>', 'file']
  
      tokens, raw_tokens = cmd2_app.tokens_for_completion(line, begidx, endidx)
      assert expected_tokens == tokens
      assert expected_raw_tokens == raw_tokens
  
  
  def test_tokens_for_completion_quoted_punctuation(cmd2_app):
      """Test that quoted punctuation characters are not word delimiters"""
      text = '>file'
      line = 'command "{}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_tokens = ['command', '>file']
      expected_raw_tokens = ['command', '">file']
  
      tokens, raw_tokens = cmd2_app.tokens_for_completion(line, begidx, endidx)
      assert expected_tokens == tokens
      assert expected_raw_tokens == raw_tokens
  
  
  def test_add_opening_quote_basic_no_text(cmd2_app):
      text = ''
      line = 'test_basic {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      # The whole list will be returned with no opening quotes added
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == sorted(food_item_strs, key=cmd2_app.default_sort_key)
  
  
  def test_add_opening_quote_basic_nothing_added(cmd2_app):
      text = 'P'
      line = 'test_basic {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == ['Pizza', 'Potato']
  
  
  def test_add_opening_quote_basic_quote_added(cmd2_app):
      text = 'Ha'
      line = 'test_basic {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = sorted(['"Ham', '"Ham Sandwich'], key=cmd2_app.default_sort_key)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_add_opening_quote_basic_single_quote_added(cmd2_app):
      text = 'Ch'
      line = 'test_basic {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = ["'Cheese \"Pizza\"' "]
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_add_opening_quote_basic_text_is_common_prefix(cmd2_app):
      # This tests when the text entered is the same as the common prefix of the matches
      text = 'Ham'
      line = 'test_basic {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = sorted(['"Ham', '"Ham Sandwich'], key=cmd2_app.default_sort_key)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_add_opening_quote_delimited_no_text(cmd2_app):
      text = ''
      line = 'test_delimited {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      # The whole list will be returned with no opening quotes added
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == sorted(delimited_strs, key=cmd2_app.default_sort_key)
  
  
  def test_add_opening_quote_delimited_nothing_added(cmd2_app):
      text = '/ho'
      line = 'test_delimited {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_matches = sorted(delimited_strs, key=cmd2_app.default_sort_key)
      expected_display = sorted(['other user', 'user'], key=cmd2_app.default_sort_key)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert (
          first_match is not None
          and cmd2_app.completion_matches == expected_matches
          and cmd2_app.display_matches == expected_display
      )
  
  
  def test_add_opening_quote_delimited_quote_added(cmd2_app):
      text = '/home/user/fi'
      line = 'test_delimited {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_common_prefix = '"/home/user/file'
      expected_display = sorted(['file.txt', 'file space.txt'], key=cmd2_app.default_sort_key)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert (
          first_match is not None
          and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix
          and cmd2_app.display_matches == expected_display
      )
  
  
  def test_add_opening_quote_delimited_text_is_common_prefix(cmd2_app):
      # This tests when the text entered is the same as the common prefix of the matches
      text = '/home/user/file'
      line = 'test_delimited {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_common_prefix = '"/home/user/file'
      expected_display = sorted(['file.txt', 'file space.txt'], key=cmd2_app.default_sort_key)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert (
          first_match is not None
          and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix
          and cmd2_app.display_matches == expected_display
      )
  
  
  def test_add_opening_quote_delimited_space_in_prefix(cmd2_app):
      # This test when a space appears before the part of the string that is the display match
      text = '/home/oth'
      line = 'test_delimited {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected_common_prefix = '"/home/other user/'
      expected_display = ['maps', 'tests']
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert (
          first_match is not None
          and os.path.commonprefix(cmd2_app.completion_matches) == expected_common_prefix
          and cmd2_app.display_matches == expected_display
      )
  
  
  def test_no_completer(cmd2_app):
      text = ''
      line = 'test_no_completer {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = ['default ']
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_wordbreak_in_command(cmd2_app):
      text = ''
      line = '"{}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None and not cmd2_app.completion_matches
  
  
  def test_complete_multiline_on_single_line(cmd2_app):
      text = ''
      line = 'test_multiline {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = sorted(sport_item_strs, key=cmd2_app.default_sort_key)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
  
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  def test_complete_multiline_on_multiple_lines(cmd2_app):
      # Set the same variables _complete_statement() sets when a user is entering data at a continuation prompt
      cmd2_app._at_continuation_prompt = True
      cmd2_app._multiline_in_progress = "test_multiline\n"
  
      text = 'Ba'
      line = '{}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      expected = sorted(['Bat', 'Basket', 'Basketball'], key=cmd2_app.default_sort_key)
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
  
      assert first_match is not None and cmd2_app.completion_matches == expected
  
  
  # Used by redirect_complete tests
  class RedirCompType(enum.Enum):
      SHELL_CMD = (1,)
      PATH = (2,)
      DEFAULT = (3,)
      NONE = 4
  
  
  @pytest.mark.parametrize(
      'line, comp_type',
      [
          ('fake', RedirCompType.DEFAULT),
          ('fake arg', RedirCompType.DEFAULT),
          ('fake |', RedirCompType.SHELL_CMD),
          ('fake | grep', RedirCompType.PATH),
          ('fake | grep arg', RedirCompType.PATH),
          ('fake | grep >', RedirCompType.PATH),
          ('fake | grep > >', RedirCompType.NONE),
          ('fake | grep > file', RedirCompType.NONE),
          ('fake | grep > file >', RedirCompType.NONE),
          ('fake | grep > file |', RedirCompType.SHELL_CMD),
          ('fake | grep > file | grep', RedirCompType.PATH),
          ('fake | |', RedirCompType.NONE),
          ('fake | >', RedirCompType.NONE),
          ('fake >', RedirCompType.PATH),
          ('fake >>', RedirCompType.PATH),
          ('fake > >', RedirCompType.NONE),
          ('fake > |', RedirCompType.SHELL_CMD),
          ('fake >> file |', RedirCompType.SHELL_CMD),
          ('fake >> file | grep', RedirCompType.PATH),
          ('fake > file', RedirCompType.NONE),
          ('fake > file >', RedirCompType.NONE),
          ('fake > file >>', RedirCompType.NONE),
      ],
  )
  def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type):
      # Test both cases of allow_redirection
      cmd2_app.allow_redirection = True
      for count in range(2):
          shell_cmd_complete_mock = mock.MagicMock(name='shell_cmd_complete')
          monkeypatch.setattr("cmd2.Cmd.shell_cmd_complete", shell_cmd_complete_mock)
  
          path_complete_mock = mock.MagicMock(name='path_complete')
          monkeypatch.setattr("cmd2.Cmd.path_complete", path_complete_mock)
  
          default_complete_mock = mock.MagicMock(name='fake_completer')
  
          text = ''
          line = '{} {}'.format(line, text)
          endidx = len(line)
          begidx = endidx - len(text)
  
          cmd2_app._redirect_complete(text, line, begidx, endidx, default_complete_mock)
  
          if comp_type == RedirCompType.SHELL_CMD:
              shell_cmd_complete_mock.assert_called_once()
          elif comp_type == RedirCompType.PATH:
              path_complete_mock.assert_called_once()
          elif comp_type == RedirCompType.DEFAULT:
              default_complete_mock.assert_called_once()
          else:
              shell_cmd_complete_mock.assert_not_called()
              path_complete_mock.assert_not_called()
              default_complete_mock.assert_not_called()
  
          # Do the next test with allow_redirection as False
          cmd2_app.allow_redirection = False
          if comp_type != RedirCompType.DEFAULT:
              comp_type = RedirCompType.NONE
  
  
  def test_complete_set_value(cmd2_app):
      text = ''
      line = 'set foo {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match == "SUCCESS "
      assert cmd2_app.completion_hint == "Hint:\n  value  a settable param\n"
  
  
  def test_complete_set_value_invalid_settable(cmd2_app, capsys):
      text = ''
      line = 'set fake {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
      assert first_match is None
  
      out, err = capsys.readouterr()
      assert "fake is not a settable parameter" in out
  
  
  @pytest.fixture
  def sc_app():
      c = SubcommandsExample()
      c.stdout = utils.StdSim(c.stdout)
      return c
  
  
  def test_cmd2_subcommand_completion_single_end(sc_app):
      text = 'f'
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and sc_app.completion_matches == ['foo ']
  
  
  def test_cmd2_subcommand_completion_multiple(sc_app):
      text = ''
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
      assert first_match is not None and sc_app.completion_matches == ['bar', 'foo', 'sport']
  
  
  def test_cmd2_subcommand_completion_nomatch(sc_app):
      text = 'z'
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
      assert first_match is None
  
  
  def test_help_subcommand_completion_single(sc_app):
      text = 'base'
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and sc_app.completion_matches == ['base ']
  
  
  def test_help_subcommand_completion_multiple(sc_app):
      text = ''
      line = 'help base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
      assert first_match is not None and sc_app.completion_matches == ['bar', 'foo', 'sport']
  
  
  def test_help_subcommand_completion_nomatch(sc_app):
      text = 'z'
      line = 'help base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
      assert first_match is None
  
  
  def test_subcommand_tab_completion(sc_app):
      # This makes sure the correct completer for the sport subcommand is called
      text = 'Foot'
      line = 'base sport {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and sc_app.completion_matches == ['Football ']
  
  
  def test_subcommand_tab_completion_with_no_completer(sc_app):
      # This tests what happens when a subcommand has no completer
      # In this case, the foo subcommand has no completer defined
      text = 'Foot'
      line = 'base foo {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
      assert first_match is None
  
  
  def test_subcommand_tab_completion_space_in_text(sc_app):
      text = 'B'
      line = 'base sport "Space {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, sc_app)
  
      assert first_match is not None and sc_app.completion_matches == ['Ball" '] and sc_app.display_matches == ['Space Ball']
  
  
  ####################################################
  
  
  class SubcommandsWithUnknownExample(cmd2.Cmd):
      """
      Example cmd2 application where we a base command which has a couple subcommands
      and the "sport" subcommand has tab completion enabled.
      """
  
      def __init__(self):
          cmd2.Cmd.__init__(self)
  
      # subcommand functions for the base command
      def base_foo(self, args):
          """foo subcommand of base command"""
          self.poutput(args.x * args.y)
  
      def base_bar(self, args):
          """bar subcommand of base command"""
          self.poutput('((%s))' % args.z)
  
      def base_sport(self, args):
          """sport subcommand of base command"""
          self.poutput('Sport is {}'.format(args.sport))
  
      # create the top-level parser for the base command
      base_parser = cmd2.Cmd2ArgumentParser()
      base_subparsers = base_parser.add_subparsers(title='subcommands', help='subcommand help')
  
      # create the parser for the "foo" subcommand
      parser_foo = base_subparsers.add_parser('foo', help='foo help')
      parser_foo.add_argument('-x', type=int, default=1, help='integer')
      parser_foo.add_argument('y', type=float, help='float')
      parser_foo.set_defaults(func=base_foo)
  
      # create the parser for the "bar" subcommand
      parser_bar = base_subparsers.add_parser('bar', help='bar help')
      parser_bar.add_argument('z', help='string')
      parser_bar.set_defaults(func=base_bar)
  
      # create the parser for the "sport" subcommand
      parser_sport = base_subparsers.add_parser('sport', help='sport help')
      sport_arg = parser_sport.add_argument('sport', help='Enter name of a sport', choices=sport_item_strs)
  
      @cmd2.with_argparser(base_parser, with_unknown_args=True)
      def do_base(self, args):
          """Base command help"""
          func = getattr(args, 'func', None)
          if func is not None:
              # Call whatever subcommand function was selected
              func(self, args)
          else:
              # No subcommand was provided, so call help
              self.do_help('base')
  
  
  @pytest.fixture
  def scu_app():
      """Declare test fixture for with_argparser decorator"""
      app = SubcommandsWithUnknownExample()
      return app
  
  
  def test_subcmd_with_unknown_completion_single_end(scu_app):
      text = 'f'
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
  
      print('first_match: {}'.format(first_match))
  
      # It is at end of line, so extra space is present
      assert first_match is not None and scu_app.completion_matches == ['foo ']
  
  
  def test_subcmd_with_unknown_completion_multiple(scu_app):
      text = ''
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport']
  
  
  def test_subcmd_with_unknown_completion_nomatch(scu_app):
      text = 'z'
      line = 'base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is None
  
  
  def test_help_subcommand_completion_single_scu(scu_app):
      text = 'base'
      line = 'help {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and scu_app.completion_matches == ['base ']
  
  
  def test_help_subcommand_completion_multiple_scu(scu_app):
      text = ''
      line = 'help base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport']
  
  
  def test_help_subcommand_completion_with_flags_before_command(scu_app):
      text = ''
      line = 'help -h -v base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is not None and scu_app.completion_matches == ['bar', 'foo', 'sport']
  
  
  def test_complete_help_subcommands_with_blank_command(scu_app):
      text = ''
      line = 'help "" {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is None and not scu_app.completion_matches
  
  
  def test_help_subcommand_completion_nomatch_scu(scu_app):
      text = 'z'
      line = 'help base {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match == None
  
  
  def test_subcommand_tab_completion_scu(scu_app):
      # This makes sure the correct completer for the sport subcommand is called
      text = 'Foot'
      line = 'base sport {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
  
      # It is at end of line, so extra space is present
      assert first_match is not None and scu_app.completion_matches == ['Football ']
  
  
  def test_subcommand_tab_completion_with_no_completer_scu(scu_app):
      # This tests what happens when a subcommand has no completer
      # In this case, the foo subcommand has no completer defined
      text = 'Foot'
      line = 'base foo {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
      assert first_match is None
  
  
  def test_subcommand_tab_completion_space_in_text_scu(scu_app):
      text = 'B'
      line = 'base sport "Space {}'.format(text)
      endidx = len(line)
      begidx = endidx - len(text)
  
      first_match = complete_tester(text, line, begidx, endidx, scu_app)
  
      assert first_match is not None and scu_app.completion_matches == ['Ball" '] and scu_app.display_matches == ['Space Ball']
  