diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Test.xml
new file mode 100644
index 0000000..700aee0
--- /dev/null
+++ b/.idea/runConfigurations/Test.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 9ae19b0..7a17061 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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"
diff --git a/README.md b/README.md
index 38e6031..56b3490 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/test/Dockerfile b/test/Dockerfile
new file mode 100644
index 0000000..09f8669
--- /dev/null
+++ b/test/Dockerfile
@@ -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
diff --git a/test/Gemfile b/test/Gemfile
new file mode 100644
index 0000000..70d69fe
--- /dev/null
+++ b/test/Gemfile
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+group :test do
+ gem 'cucumber', '~> 3.1.0'
+ gem 'rspec', '~> 3.7.0'
+end
diff --git a/test/Homebrew-Formula-fish-2.7.1.rb b/test/Homebrew-Formula-fish-2.7.1.rb
deleted file mode 100644
index 3a463db..0000000
--- a/test/Homebrew-Formula-fish-2.7.1.rb
+++ /dev/null
@@ -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
diff --git a/test/completion.rb b/test/completion.rb
deleted file mode 100644
index 8ddf73d..0000000
--- a/test/completion.rb
+++ /dev/null
@@ -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
\ No newline at end of file
diff --git a/test/features/completions.feature b/test/features/completions.feature
new file mode 100644
index 0000000..74ee349
--- /dev/null
+++ b/test/features/completions.feature
@@ -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 "" into the prompt
+ Then completion should propose ""
+ 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 " into the prompt
+ Then completion should propose ""
+ 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 " into the prompt
+ Then completion should propose ""
+ 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 " into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd | completions |
+ | an | ant |
+ | xyz | |
+ | 1. | |
+ | 'ant ' | |
+
+ Scenario Outline: Completion for 'use'
+ When the user enters "use " into the prompt
+ Then completion should propose ""
+ 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 " into the prompt
+ Then completion should propose ""
+ 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 " into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd | completions |
+ | an | ant | # --> installed version
+ | ja | java | # --> not installed
+ | xyz | |
+ | 1. | |
+ | 'ant ' | |
+
+ Scenario Outline: Completion for 'upgrade'
+ When the user enters "upgrade " into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd | completions |
+ | | ant, crash |
+ | an | ant |
+ | j | |
+ | 1. | |
+ | 'ant ' | |
+
+ Scenario Outline: Completion for 'offline'
+ When the user enters "offline " into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd | completions |
+ | | disable, enable |
+ | e | enable |
+ | d | disable |
+ | a | |
+ | 'enable ' | |
+
+ Scenario Outline: Completion for 'selfupdate'
+ When the user enters "selfupdate " into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd | completions |
+ | | force |
+ | f | force |
+ | a | |
+ | 'force ' | |
+
+ Scenario Outline: Completion for 'flush'
+ When the user enters "flush " into the prompt
+ Then completion should propose ""
+ 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 "" into the prompt
+ Then completion should propose ""
+ Examples:
+ | cmd |
+ | 'version ' |
+ | 'version a' |
+ | 'broadcast ' |
+ | 'broadcast a' |
+ | 'help ' |
+ | 'help a' |
+ | 'update ' |
+ | 'update a' |
diff --git a/test/features/step_definitions/completion.rb b/test/features/step_definitions/completion.rb
new file mode 100644
index 0000000..69f8cea
--- /dev/null
+++ b/test/features/step_definitions/completion.rb
@@ -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
diff --git a/test/features/step_definitions/setup.rb b/test/features/step_definitions/setup.rb
new file mode 100644
index 0000000..0609b47
--- /dev/null
+++ b/test/features/step_definitions/setup.rb
@@ -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
diff --git a/test/features/support/helpers.rb b/test/features/support/helpers.rb
new file mode 100644
index 0000000..f6096d8
--- /dev/null
+++ b/test/features/support/helpers.rb
@@ -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
diff --git a/test/prepare_tests.sh b/test/prepare_tests.sh
index 981d4e0..6305b42 100644
--- a/test/prepare_tests.sh
+++ b/test/prepare_tests.sh
@@ -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
\ No newline at end of file
+sdk default ant 1.10.1