Migrate completion tests to Cucumber.

Also retire Fish 2.x + macOS test;
installation of custom brew didn't work out anymore.
This commit is contained in:
Raphael Reitzig 2020-05-25 20:17:31 +02:00
parent ed6039e533
commit 0c15f199cd
12 changed files with 364 additions and 236 deletions

17
.idea/runConfigurations/Test.xml generated Normal file
View file

@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="sdkman-for-fish-tests" />
<option name="buildCliOptions" value="" />
<option name="command" value="" />
<option name="containerName" value="" />
<option name="contextFolderPath" value="." />
<option name="entrypoint" value="" />
<option name="commandLineOptions" value="--rm" />
<option name="sourceFilePath" value="test/Dockerfile" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View file

@ -16,13 +16,6 @@ matrix:
- sourceline: "ppa:fish-shell/release-3"
packages:
- fish
- os: osx
env: FISH=2
addons:
homebrew:
packages:
- 'test/Homebrew-Formula-fish-2.7.1.rb'
update: true # TODO: build should be green without, but isn't
- os: osx
env: FISH=3
addons:
@ -33,18 +26,19 @@ matrix:
before_install:
- curl -s "https://get.sdkman.io" | bash
- bash test/prepare_tests.sh
- bundle install --gemfile=test/Gemfile --no-cache
- uname -a; fish --version; sdk version; ruby --version; cucumber --version
install:
- mkdir -p "${HOME}"/.config/fish/{completions,conf.d,functions}
- cp completions/* "${HOME}"/.config/fish/completions/
- cp conf.d/* "${HOME}"/.config/fish/conf.d/
- cp functions/* "${HOME}"/.config/fish/functions/
- uname -a; fish --version
script:
- ruby test/completion.rb
- fish test/wrapper.fish
- (cd test && cucumber)
# TODO: Migrate these to Cucumber:
- bash test/prepare_tests.sh && fish test/wrapper.fish
- fish test/reinitialize.fish
- fish -c "sdk install crash 1.3.0; and sdk uninstall crash 1.3.0" > /dev/null && fish test/check_for_path_zombies.fish
- bash test/remove_sdkman.sh > /dev/null && fish -c "echo 'y' | sdk" > /dev/null && fish -c "sdk version"

View file

@ -29,6 +29,22 @@ _Note:_
It's all in the background; you should be able to run `sdk` and binaries installed
with `sdk` as you would expect.
## Contribute
When you make changes,
please run the tests at least on one platform before creating a pull request.
As the tests may mess up your own setup
-- you have been warned! --
the recommended way is to run the tests in a Docker container:
```bash
docker build -t sdkman-for-fish-tests -f test/Dockerfile .
docker run --rm sdkman-for-fish-tests
```
A run configuration for Jetbrains IDEs is included.
## Acknowledgements
* Completion originally by [Ted Wise](https://github.com/ctwise); see his

33
test/Dockerfile Normal file
View file

@ -0,0 +1,33 @@
FROM ruby:2.5.8-slim-buster
# Install dependencies
RUN apt-get update \
&& apt-get -y install \
fish \
curl \
unzip \
zip \
&& apt-get clean
WORKDIR app
COPY test/Gemfile ./
RUN bundle install \
&& rm Gemfile
# Switch to non-root user for test context
ARG TEST_HOME="/home/test"
RUN groupadd -r test \
&& useradd --no-log-init -r -g test -m -d $TEST_HOME test
USER test
RUN curl -s "https://get.sdkman.io" | bash
# "Install" sdkman-for-fish
RUN mkdir -p $TEST_HOME/.config/fish/
COPY completions $TEST_HOME/.config/fish/completions/
COPY conf.d $TEST_HOME/.config/fish/conf.d/
COPY completions $TEST_HOME/.config/fish/functions/
RUN ls -R $TEST_HOME/.config/fish/
# Run tests
COPY test ./
CMD cucumber

6
test/Gemfile Normal file
View file

@ -0,0 +1,6 @@
source "https://rubygems.org"
group :test do
gem 'cucumber', '~> 3.1.0'
gem 'rspec', '~> 3.7.0'
end

View file

@ -1,60 +0,0 @@
class HomebrewFormulaFish271 < Formula
desc "User-friendly command-line shell for UNIX-like operating systems"
homepage "https://fishshell.com"
url "https://github.com/fish-shell/fish-shell/releases/download/2.7.1/fish-2.7.1.tar.gz"
mirror "https://fishshell.com/files/2.7.1/fish-2.7.1.tar.gz"
sha256 "e42bb19c7586356905a58578190be792df960fa81de35effb1ca5a5a981f0c5a"
bottle do
sha256 "affac16a396410a500241266dbbbd8752562c9b800d9e4f2ef8de279c6fdb6aa" => :high_sierra
sha256 "335538a7ea7f9613474f7321af0a1d519b61b0fc4be97a1744a7ea7bef7ff7e3" => :sierra
sha256 "463cfa8edc0603761f25e0ba4e49524f69a1d856263d370d1de5fb8698dd5ccf" => :el_capitan
end
head do
url "https://github.com/fish-shell/fish-shell.git", :shallow => false
depends_on "autoconf" => :build
depends_on "automake" => :build
depends_on "doxygen" => :build
end
depends_on "pcre2"
def install
system "autoreconf", "--no-recursive" if build.head?
# In Homebrew's 'superenv' sed's path will be incompatible, so
# the correct path is passed into configure here.
args = %W[
--prefix=#{prefix}
--with-extra-functionsdir=#{HOMEBREW_PREFIX}/share/fish/vendor_functions.d
--with-extra-completionsdir=#{HOMEBREW_PREFIX}/share/fish/vendor_completions.d
--with-extra-confdir=#{HOMEBREW_PREFIX}/share/fish/vendor_conf.d
SED=/usr/bin/sed
]
system "./configure", *args
system "make", "install"
end
def caveats; <<~EOS
You will need to add:
#{HOMEBREW_PREFIX}/bin/fish
to /etc/shells.
Then run:
chsh -s #{HOMEBREW_PREFIX}/bin/fish
to make fish your default shell.
EOS
end
def post_install
(pkgshare/"vendor_functions.d").mkpath
(pkgshare/"vendor_completions.d").mkpath
(pkgshare/"vendor_conf.d").mkpath
end
test do
system "#{bin}/fish", "-c", "echo"
end
end

View file

@ -1,162 +0,0 @@
#!/usr/bin/env ruby
# Includes completion examples with the intent to cover
# all sdk commands
# Beware, pasta follows.
test_cases = {
# Basic commands (in the order of `sdk help`):
"i" => ["i", "install"],
"u" => ["u", "ug", "uninstall", "update", "upgrade", "use"],
"r" => ["rm"],
"l" => ["list", "ls"],
"d" => ["d", "default"],
"c" => ["c", "current"],
"v" => ["v", "version"],
"b" => ["b", "broadcast"],
"h" => ["h", "help"],
"o" => ["offline"],
"s" => ["selfupdate"],
"f" => ["flush"],
# Currently uncovered; include to catch new upstream commands:
"a" => [],
"e" => [],
"g" => [],
"j" => [],
"k" => [],
"m" => [],
"n" => [],
"p" => [],
"q" => [],
"t" => [],
"w" => [],
"x" => [],
"y" => [],
"z" => [],
# Second parameters complete correctly
"install an" => ["ant"],
"install xyz" => [],
"install 1." => [],
"uninstall " => ["ant"],
"uninstall a" => ["ant"],
"uninstall j" => [],
"uninstall 1." => [],
"list an" => ["ant"],
"list xyz" => [],
"list 1." => [],
"use " => ["ant"],
"use an" => ["ant"],
"use j" => [],
"use 1." => [],
"default " => ["ant"],
"default an" => ["ant"],
"default j" => [],
"default 1." => [],
"current an" => ["ant"],
"current xyz" => [],
"current 1." => [],
"upgrade " => ["ant"],
"upgrade an" => ["ant"],
"upgrade j" => [],
"upgrade 1." => [],
"version " => [],
"version a" => [],
"broadcast " => [],
"broadcast a" => [],
"help " => [],
"help a" => [],
"offline " => ["enable", "disable"],
"offline a" => [],
"selfupdate " => ["force"],
"selfupdate a" => [],
"update " => [],
"update a" => [],
"flush " => ["broadcast", "archives", "temp"],
"flush x" => [],
# Third parameters complete correctly
#"install ant 1.10." => ["1.10.0", "1.10.1"], # TODO: issue #4
"uninstall ant 1.10." => ["1.10.1"],
"list ant " => [],
"use ant " => ["1.9.9", "1.10.1"],
"default ant " => ["1.9.9", "1.10.1"],
"current ant " => [],
"upgrade ant " => [],
"offline ant " => [],
"selfupdate ant " => [],
"flush ant " => [],
# Fourth parameters complete correctly
"install ant 1.10.2-mine /tm" => ["/tmp/"],
"uninstall ant 1.10.1 " => [],
"use ant 1.10.1 " => [],
"default ant 1.10.1 " => [],
# Fifth parameters complete correctly
"install ant 1.10.2-mine /tmp " => [],
# Lists of all candidates work
"install gr" => ["gradle", "grails", "groovy", "groovyserv"],
"install grad" => ["gradle"],
"install gradk" => [],
# Lists of installed candidates work
"uninstall an" => ["ant"],
"uninstall gr" => [],
"uninstall xyz" => [],
# List of all versions work
# TODO
# List of installed versions work
"uninstall ant 1" => ["1.9.9", "1.10.1"],
"uninstall ant 2" => [],
"uninstall vertx " => [],
"uninstall an " => []
}
def fish_command(prompt)
# Fish errors out if we don't set terminal dimensions
"fish -c 'stty rows 80 columns 80; complete -C\"sdk #{prompt}\"'"
end
def extract(completion_output)
completion_output.split("\n").map { |line|
line.split(/\s+/)[0].strip
}
end
puts "Testing sdk completions"
failures = 0
test_cases.each { |prompt, results|
results.sort!
print " Test: 'sdk #{prompt}'"
completions = extract(`#{fish_command(prompt)}`).sort
if completions != results
puts " -- bad!"
puts " - Expected: #{results}"
puts " - Actual: #{completions}"
failures += 1
else
puts " -- ok!"
end
}
puts "Ran #{test_cases.size} checks each."
puts "#{failures}/#{test_cases.size} checks failed."
exit failures > 0 ? 1 : 0

View file

@ -0,0 +1,186 @@
Feature: Shell Completion
We want to get the correct completion on the CLI.
Background:
Given SDKMAN! candidate list is up to date
And candidate ant is installed at version 1.9.9
And candidate ant is installed at version 1.10.1
And candidate crash is installed
Scenario: Command list correct
When the user enters " " into the prompt
Then completion should propose "b, broadcast, c, current, d, default, flush, h, help, i, install, list, ls, offline, rm, selfupdate, u, ug, uninstall, update, upgrade, use, v, version"
Scenario Outline: Commands complete
When the user enters "<cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| b | b, broadcast |
| c | c, current |
| d | d, default |
| f | flush |
| h | h, help |
| i | i, install |
| in | install |
| l | list, ls |
| o | offline |
| r | rm |
| s | selfupdate |
| u | u, ug, uninstall, update, upgrade, use |
| un | uninstall |
| up | update, upgrade |
| us | use |
| v | v, version |
# Currently uncovered; include to catch new upstream commands:
| a | |
| e | |
| g | |
| j | |
| k | |
| m | |
| n | |
| p | |
| q | |
| t | |
| w | |
| x | |
| y | |
| z | |
Scenario Outline: Completion for 'install'
When the user enters "install <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| an | ant |
| xyz | |
| 1. | |
| gra | gradle, grails |
| grad | gradle |
| gradk | |
# | ant 1.10. | 1.10.0, 1.10.1 | # TODO: list installable versions --> issue #4
| ant 1.10.2-mine /tm | /tmp/ |
| 'ant 1.10.2-mine /tmp ' | |
Scenario Outline: Completion for 'uninstall'
When the user enters "uninstall <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | ant, crash |
| a | ant |
| j | |
| 1. | |
| an | ant | # installed
| gr | | # none installed
| xyz | | # no such candidate
| 'an ' | | # no such candidate installed
| 'ant 1' | 1.10.1, 1.9.9 |
| 'ant 1.10.' | 1.10.1 |
| 'ant 2' | |
| 'ant 1.10.1 ' | |
Scenario Outline: Completion for 'list'
When the user enters "list <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| an | ant |
| xyz | |
| 1. | |
| 'ant ' | |
Scenario Outline: Completion for 'use'
When the user enters "use <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | ant, crash |
| an | ant |
| j | |
| 1. | |
| 'ant ' | 1.10.1, 1.9.9 |
| 'ant 1.10.1 ' | |
Scenario Outline: Completion for 'default'
When the user enters "default <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | ant, crash |
| an | ant |
| j | |
| 1. | |
| 'ant ' | 1.10.1, 1.9.9 |
| 'ant 1.10.1 ' | |
Scenario Outline: Completion for 'current'
When the user enters "current <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| an | ant | # --> installed version
| ja | java | # --> not installed
| xyz | |
| 1. | |
| 'ant ' | |
Scenario Outline: Completion for 'upgrade'
When the user enters "upgrade <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | ant, crash |
| an | ant |
| j | |
| 1. | |
| 'ant ' | |
Scenario Outline: Completion for 'offline'
When the user enters "offline <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | disable, enable |
| e | enable |
| d | disable |
| a | |
| 'enable ' | |
Scenario Outline: Completion for 'selfupdate'
When the user enters "selfupdate <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | force |
| f | force |
| a | |
| 'force ' | |
Scenario Outline: Completion for 'flush'
When the user enters "flush <cmd>" into the prompt
Then completion should propose "<completions>"
Examples:
| cmd | completions |
| | archives, broadcast, temp |
| b | broadcast |
| a | archives |
| t | temp |
| x | |
| 'temp ' | |
Scenario Outline: Completion for commands without parameters
When the user enters "<cmd>" into the prompt
Then completion should propose ""
Examples:
| cmd |
| 'version ' |
| 'version a' |
| 'broadcast ' |
| 'broadcast a' |
| 'help ' |
| 'help a' |
| 'update ' |
| 'update a' |

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'open3'
module CompletionHelper
def complete(cmd)
completions = run_fish_command("complete -C\"sdk #{cmd}\"")
completions.split("\n") \
.map { |line| line.split(/\s+/)[0].strip } \
.sort \
.uniq \
.join(', ')
# TODO: Why do we get duplicates in the Docker container?
# --> remove uniq
end
end
World CompletionHelper
When('the user enters {string} into the prompt') do |cmd|
@response = complete(cmd.gsub(/["']/, ''))
end
Then(/^completion should propose "(.*)"$/) do |completions|
expect(@response).to eq(completions)
end

View file

@ -0,0 +1,31 @@
$index_updated = false # TODO: Hack since Cucumber doesn't have Feature-level hooks
Given(/^SDKMAN! candidate list is up to date$/) do
unless $index_updated
run_bash_command('sdk update')
$index_updated = true
end
end
Given(/^candidate (\w+) is installed at version (\d+(?:\.\d+)*)$/) do |candidate, version|
run_bash_command("sdk install #{candidate} #{version}") unless installed?(candidate, version)
end
Given(/^candidate (\w+) is installed$/) do |candidate|
run_bash_command("sdk install #{candidate}") unless installed?(candidate)
end
# Uninstall all SDKMAN! candidates
# TODO: Run after every scenario, this makes tests very slow.
# Currently, Cucumber doesn't have Feature-level hooks, so we have to work around:
# --> install only if not already installed;
# if the test needs a candidate to _not_ be there, make it explicit.
# --> clean up after _all_ features at least
at_exit do
Dir["#{ENV['HOME']}/.sdkman/candidates/*/*"].each do |candidate_dir|
%r{/([^/]+)/([^/]+)$}.match(candidate_dir) do |match|
candidate = match[1]
version = match[2]
run_bash_command("sdk rm #{candidate} #{version}") unless version == 'current'
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
def list_installed_candidates
candidates = {}
Dir["#{ENV['HOME']}/.sdkman/candidates/*/*"].each do |candidate_dir|
%r{/([^/]+)/([^/]+)$}.match(candidate_dir) do |match|
candidate = match[1]
version = match[2]
candidates[candidate] = [] unless candidates.key?(candidate)
candidates[candidate].push version unless version == 'current'
end
end
candidates
end
def installed?(candidate, version = nil)
candidates = list_installed_candidates
candidates.key?(candidate) && (version.nil? || candidates[candidate].include?(version))
end
def run_bash_command(cmd)
stdout, stderr, status = Open3.capture3("bash -c 'source \"$HOME/.sdkman/bin/sdkman-init.sh\"; #{cmd}'")
unless status.success?
warn(stderr)
raise "Bash command failed: #{stderr}"
end
stdout
end
def run_fish_command(cmd)
# NB: Fish errors out if we don't set terminal dimensions
stdout, stderr, status = Open3.capture3("fish -c 'stty rows 80 columns 80; #{cmd}'")
unless status.success?
warn(stderr)
raise 'Fish command failed'
end
stdout
end

View file

@ -3,8 +3,7 @@
source "${HOME}"/.sdkman/bin/sdkman-init.sh
# Set up an SDK with two installed versions
# --> test of `sdk use` in wrapper.fish
# --> tests in completion.rb
# --> test of `sdk use` in wrapper.fish
sdk install ant 1.9.9
echo "y" | sdk install ant 1.10.1
sdk default ant 1.10.1
sdk default ant 1.10.1