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