github.com/scorpionis/hub@v2.2.1+incompatible/features/support/completion.rb (about)

     1  # Driver for completion tests executed via a separate tmux pane in which we
     2  # spawn an interactive shell, send keystrokes to and inspect the outcome of
     3  # tab-completion.
     4  #
     5  # Prerequisites:
     6  # - tmux
     7  # - bash
     8  # - zsh
     9  # - git
    10  
    11  require 'fileutils'
    12  require 'rspec/expectations'
    13  require 'pathname'
    14  
    15  tmpdir = Pathname.new(ENV.fetch('TMPDIR', '/tmp')) + 'hub-test'
    16  cpldir = tmpdir + 'completion'
    17  zsh_completion = File.expand_path('../../../etc/hub.zsh_completion', __FILE__)
    18  bash_completion = File.expand_path('../../../etc/hub.bash_completion.sh', __FILE__)
    19  
    20  _git_prefix = nil
    21  
    22  git_prefix = lambda {
    23    _git_prefix ||= begin
    24      git_core = Pathname.new(`git --exec-path`.chomp)
    25      git_core.dirname.dirname
    26    end
    27  }
    28  
    29  git_distributed_zsh_completion = lambda {
    30    [ git_prefix.call + 'share/git-core/contrib/completion/git-completion.zsh',
    31      git_prefix.call + 'share/zsh/site-functions/_git',
    32    ].detect {|p| p.exist? }
    33  }
    34  
    35  git_distributed_bash_completion = lambda {
    36    [ git_prefix.call + 'share/git-core/contrib/completion/git-completion.bash',
    37      git_prefix.call + 'etc/bash_completion.d/git-completion.bash',
    38      Pathname.new('/etc/bash_completion.d/git'),
    39      Pathname.new('/usr/share/bash-completion/completions/git'),
    40      Pathname.new('/usr/share/bash-completion/git'),
    41    ].detect {|p| p.exist? }
    42  }
    43  
    44  link_completion = Proc.new { |from, name|
    45    raise ArgumentError, from.to_s unless File.exist?(from)
    46    FileUtils.ln_s(from, cpldir + name, :force => true)
    47  }
    48  
    49  setup_tmp_home = lambda { |shell|
    50    FileUtils.rm_rf(tmpdir)
    51    FileUtils.mkdir_p(cpldir)
    52  
    53    case shell
    54    when 'zsh'
    55      File.open(File.join(tmpdir, '.zshrc'), 'w') do |zshrc|
    56        zshrc.write <<-SH
    57          PS1='$ '
    58          for site_fn in /usr/{local/,}share/zsh/site-functions; do
    59            fpath=(${fpath#\$site_fn})
    60          done
    61          fpath=('#{cpldir}' $fpath)
    62          alias git=hub
    63          autoload -U compinit
    64          compinit -i
    65        SH
    66      end
    67    when 'bash'
    68      File.open(File.join(tmpdir, '.bashrc'), 'w') do |bashrc|
    69        bashrc.write <<-SH
    70          PS1='$ '
    71          alias git=hub
    72          . '#{git_distributed_bash_completion.call}'
    73          . '#{bash_completion}'
    74        SH
    75      end
    76    end
    77  }
    78  
    79  $tmux = nil
    80  
    81  Before('@completion') do
    82    unless $tmux
    83      $tmux = %w[tmux -L hub-test]
    84      system(*($tmux + %w[new-session -ds hub]))
    85      at_exit do
    86        system(*($tmux + %w[kill-server]))
    87      end
    88    end
    89  end
    90  
    91  After('@completion') do
    92    tmux_kill_pane
    93  end
    94  
    95  World Module.new {
    96    attr_reader :shell
    97  
    98    def set_shell(shell)
    99      @shell = shell
   100    end
   101  
   102    define_method(:tmux_pane) do
   103      return @tmux_pane if tmux_pane?
   104      Dir.chdir(tmpdir) do
   105        @tmux_pane = `#{$tmux.join(' ')} new-window -dP -n test 'env HOME="#{tmpdir}" #{shell}'`.chomp
   106      end
   107    end
   108  
   109    def tmux_pane?
   110      defined?(@tmux_pane) && @tmux_pane
   111    end
   112  
   113    def tmux_pane_contents
   114      system(*($tmux + ['capture-pane', '-t', tmux_pane]))
   115      `#{$tmux.join(' ')} show-buffer`.rstrip
   116    end
   117  
   118    def tmux_send_keys(*keys)
   119      system(*($tmux + ['send-keys', '-t', tmux_pane, *keys]))
   120    end
   121  
   122    def tmux_send_tab
   123      @last_pane_contents = tmux_pane_contents
   124      tmux_send_keys('Tab')
   125    end
   126  
   127    def tmux_kill_pane
   128      system(*($tmux + ['kill-pane', '-t', tmux_pane])) if tmux_pane?
   129    end
   130  
   131    def tmux_wait_for_prompt
   132      num_waited = 0
   133      while tmux_pane_contents !~ /\$\Z/
   134        raise "timeout while waiting for shell prompt" if num_waited > 300
   135        sleep 0.01
   136        num_waited += 1
   137      end
   138    end
   139  
   140    def tmux_wait_for_completion
   141      num_waited = 0
   142      raise "tmux_send_tab not called first" unless defined? @last_pane_contents
   143      while tmux_pane_contents == @last_pane_contents
   144        if num_waited > 300
   145          if block_given? then return yield
   146          else
   147            raise "timeout while waiting for completions to expand"
   148          end
   149        end
   150        sleep 0.01
   151        num_waited += 1
   152      end
   153    end
   154  
   155    def tmux_completion_menu
   156      tmux_wait_for_completion
   157      hash = {}
   158      tmux_pane_contents.split("\n").grep(/^[^\$].+ -- /).each { |line|
   159        item, description = line.split(/ +-- +/, 2)
   160        hash[item] = description
   161      }
   162      hash
   163    end
   164  
   165    def tmux_completion_menu_basic
   166      tmux_wait_for_completion
   167      tmux_pane_contents.split("\n").grep(/^[^\$]/).map {|line|
   168        line.split(/\s+/)
   169      }.flatten
   170    end
   171  }
   172  
   173  Given(/^my shell is (\w+)$/) do |shell|
   174    set_shell(shell)
   175    setup_tmp_home.call(shell)
   176  end
   177  
   178  Given(/^I'm using ((?:zsh|git)-distributed) base git completions$/) do |type|
   179    link_completion.call(zsh_completion, '_hub')
   180    case type
   181    when 'zsh-distributed'
   182      raise "this combination makes no sense!" if 'bash' == shell
   183      expect((cpldir + '_git')).to_not be_exist
   184    when 'git-distributed'
   185      if 'zsh' == shell
   186        if git_zsh_completion = git_distributed_zsh_completion.call
   187          link_completion.call(git_zsh_completion, '_git')
   188          link_completion.call(git_distributed_bash_completion.call, 'git-completion.bash')
   189        else
   190          warn "warning: git-distributed zsh completion wasn't found; using zsh-distributed instead"
   191        end
   192      end
   193    else
   194      raise ArgumentError, type
   195    end
   196  end
   197  
   198  When(/^I type "(.+?)" and press <Tab>$/) do |string|
   199    tmux_wait_for_prompt
   200    @last_command = string
   201    tmux_send_keys(string)
   202    tmux_send_tab
   203  end
   204  
   205  When(/^I press <Tab> again$/) do
   206    tmux_send_tab
   207  end
   208  
   209  Then(/^the completion menu should offer "([^"]+?)"( unsorted)?$/) do |items, unsorted|
   210    menu = tmux_completion_menu_basic
   211    if unsorted
   212      menu.sort!
   213      items = items.split(' ').sort.join(' ')
   214    end
   215    expect(menu.join(' ')).to eq(items)
   216  end
   217  
   218  Then(/^the completion menu should offer "(.+?)" with description "(.+?)"$/) do |item, description|
   219    menu = tmux_completion_menu
   220    expect(menu.keys).to include(item)
   221    expect(menu[item]).to eq(description)
   222  end
   223  
   224  Then(/^the completion menu should offer:/) do |table|
   225    menu = tmux_completion_menu
   226    expect(menu).to eq(table.rows_hash)
   227  end
   228  
   229  Then(/^the command should expand to "(.+?)"$/) do |cmd|
   230    tmux_wait_for_completion
   231    expect(tmux_pane_contents).to match(/^\$ #{cmd}$/)
   232  end
   233  
   234  Then(/^the command should not expand$/) do
   235    tmux_wait_for_completion { false }
   236    expect(tmux_pane_contents).to match(/^\$ #{@last_command}$/)
   237  end