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