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