diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0dda7bf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[**/*.bats] +indent_size = 4 +indent_style = space \ No newline at end of file diff --git a/.gitignore b/.gitignore index 852b572..aaf0a69 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ npm-debug.log .DS_Store current + +*~ diff --git a/package.json b/package.json index 18d203e..b38fb6d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test/slow": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=slow test-$shell", "test/install_script": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=install_script test-$shell", "test/installation": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=installation test-$shell", - "test/sourcing": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=sourcing test-$shell" + "test/sourcing": "shell=$(basename -- $(ps -o comm= $(ps -o ppid= -p $PPID)) | sed 's/^-//'); make TEST_SUITE=sourcing test-$shell", + "bats": "bats test/bats" }, "repository": { "type": "git", @@ -30,6 +31,7 @@ }, "homepage": "https://github.com/creationix/nvm", "devDependencies": { + "bats": "git://github.com/sstephenson/bats", "replace": "~0.3.0", "semver": "~4.2.0", "urchin": "~0.0.5" diff --git a/test/bats/aliases-circular.bats b/test/bats/aliases-circular.bats new file mode 100644 index 0000000..e10fadc --- /dev/null +++ b/test/bats/aliases-circular.bats @@ -0,0 +1,58 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." + +setup() { + echo 'setup' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias + + echo loopback > alias/loopback + echo two > alias/one + echo three > alias/two + echo one > alias/three + + echo two > alias/four +} + +teardown() { + echo 'teardown' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test './Aliases/circular/nvm_resolve_alias' { + load "${NVM_SRC_DIR}/nvm.sh" + + die () { echo $@ ; exit 1; } + + run nvm_resolve_alias loopback + assert_success "∞" + run nvm alias loopback + assert_success "loopback -> loopback (-> ∞)" + + run nvm_resolve_alias one + assert_success "∞" + run nvm alias one + assert_success "one -> two (-> ∞)" + + run nvm_resolve_alias two + assert_success "∞" + run nvm alias two + assert_success "two -> three (-> ∞)" + + run nvm_resolve_alias three + assert_success "∞" + run nvm alias three + assert_success "three -> one (-> ∞)" + + run nvm_resolve_alias four + assert_success "∞" + run nvm alias four + assert_success "four -> two (-> ∞)" +} diff --git a/test/bats/aliases-listall.bats b/test/bats/aliases-listall.bats new file mode 100644 index 0000000..1104d72 --- /dev/null +++ b/test/bats/aliases-listall.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." +NVM_DIR="${BATS_TMPDIR}" +load "${NVM_SRC_DIR}/nvm.sh" + +setup() { + echo 'setup' >&2 + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias + for i in 1 2 3 4 5 6 7 8 9 10 + do + echo 0.0.$i > alias/test-stable-$i + mkdir -p v0.0.$i + echo 0.1.$i > alias/test-unstable-$i + mkdir -p v0.1.$i + done +} + +teardown() { + echo 'teardown' >&2 + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test './Aliases/Running "nvm alias" should list all aliases.' { + run nvm alias + + assert_line 0 'test-stable-1 -> 0.0.1 (-> v0.0.1)' "did not find test-stable-1 alias" + assert_line 1 'test-stable-10 -> 0.0.10 (-> v0.0.10)' "did not find test-stable-10 alias" + assert_line 2 'test-stable-2 -> 0.0.2 (-> v0.0.2)' "did not find test-stable-2 alias" + assert_line 3 'test-stable-3 -> 0.0.3 (-> v0.0.3)' "did not find test-stable-3 alias" + assert_line 4 'test-stable-4 -> 0.0.4 (-> v0.0.4)' "did not find test-stable-4 alias" + assert_line 5 'test-stable-5 -> 0.0.5 (-> v0.0.5)' "did not find test-stable-5 alias" + assert_line 6 'test-stable-6 -> 0.0.6 (-> v0.0.6)' "did not find test-stable-6 alias" + assert_line 7 'test-stable-7 -> 0.0.7 (-> v0.0.7)' "did not find test-stable-7 alias" + assert_line 8 'test-stable-8 -> 0.0.8 (-> v0.0.8)' "did not find test-stable-8 alias" + assert_line 9 'test-stable-9 -> 0.0.9 (-> v0.0.9)' "did not find test-stable-9 alias" + assert_line 10 'test-unstable-1 -> 0.1.1 (-> v0.1.1)' "did not find test-unstable-1 alias" + assert_line 11 'test-unstable-10 -> 0.1.10 (-> v0.1.10)' "did not find test-unstable-10 alias" + assert_line 12 'test-unstable-2 -> 0.1.2 (-> v0.1.2)' "did not find test-unstable-2 alias" + assert_line 13 'test-unstable-3 -> 0.1.3 (-> v0.1.3)' "did not find test-unstable-3 alias" + assert_line 14 'test-unstable-4 -> 0.1.4 (-> v0.1.4)' "did not find test-unstable-4 alias" + assert_line 15 'test-unstable-5 -> 0.1.5 (-> v0.1.5)' "did not find test-unstable-5 alias" + assert_line 16 'test-unstable-6 -> 0.1.6 (-> v0.1.6)' "did not find test-unstable-6 alias" + assert_line 17 'test-unstable-7 -> 0.1.7 (-> v0.1.7)' "did not find test-unstable-7 alias" + assert_line 18 'test-unstable-8 -> 0.1.8 (-> v0.1.8)' "did not find test-unstable-8 alias" + assert_line 19 'test-unstable-9 -> 0.1.9 (-> v0.1.9)' "did not find test-unstable-9 alias" + +} + diff --git a/test/bats/aliases.bats b/test/bats/aliases.bats new file mode 100644 index 0000000..ca6c98a --- /dev/null +++ b/test/bats/aliases.bats @@ -0,0 +1,85 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." +NVM_DIR="${BATS_TMPDIR}" +load "${NVM_SRC_DIR}/nvm.sh" + +setup() { + echo 'setup' >&2 + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias + for i in 1 2 3 4 5 6 7 8 9 10 + do + echo 0.0.$i > alias/test-stable-$i + mkdir -p v0.0.$i + echo 0.1.$i > alias/test-unstable-$i + mkdir -p v0.1.$i + done +} + +teardown() { + echo 'teardown' >&2 + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test './Aliases/nvm_resolve_alias' { + + run nvm_resolve_alias + [ "$status" -eq 1 ] + + for i in $(seq 1 10) + do + run nvm_resolve_alias test-stable-$i + assert_success "v0.0.$i" "nvm_resolve_alias test-stable-$i" + + run nvm_resolve_alias test-unstable-$i + assert_success "v0.1.$i" "nvm_resolve_alias test-unstable-$i" + done + + run nvm_resolve_alias nonexistent + assert_failure + + run nvm_resolve_alias stable + assert_success "v0.0.10" "'nvm_resolve_alias stable' was not v0.0.10" + + run nvm_resolve_alias unstable + assert_success "v0.1.10" "'nvm_resolve_alias unstable' was not v0.1.10" +} + +@test './Aliases/Running "nvm alias " should list but one alias.' { + run nvm alias test-stable-1 + assert_success + + local num_lines="${#lines[@]}" + assert_equal $num_lines 2 +} + +@test './Aliases/Running "nvm alias" lists implicit aliases when they do not exist' { + run nvm alias + + assert_line 20 "stable -> 0.0 (-> v0.0.10) (default)" "nvm alias did not contain the default local stable node version" + assert_line 21 "unstable -> 0.1 (-> v0.1.10) (default)" "nvm alias did not contain the default local unstable node version" +} + +@test './Aliases/Running "nvm alias" lists manual aliases instead of implicit aliases when present' { + mkdir v0.8.1 + mkdir v0.9.1 + + stable="$(nvm_print_implicit_alias local stable)" + unstable="$(nvm_print_implicit_alias local unstable)" + + assert_unequal $stable $unstable "stable and unstable versions are the same!" + + run nvm alias stable $unstable + run nvm alias unstable $stable + + run nvm alias + + assert_line 0 "stable -> 0.9 (-> v0.9.1)" "nvm alias did not contain the overridden 'stable' alias" + assert_line 21 "unstable -> 0.8 (-> v0.8.1)" "nvm alias did not contain the overridden 'unstable' alias" +} + diff --git a/test/bats/fast.bats b/test/bats/fast.bats new file mode 100644 index 0000000..9185ea5 --- /dev/null +++ b/test/bats/fast.bats @@ -0,0 +1,222 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." +source "${NVM_SRC_DIR}/nvm.sh" + +test_debug() { + false # set to 'true' to get setup/teardown in test stderr +} + +test_implementing() { + false # set to 'false' to run all tests; set to 'true' to skip implemented tests + # only used while porting to bats: remove afterward +} + +setup() { + test_debug && echo 'setup' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias +} + +teardown() { + test_debug && echo 'teardown' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test "'nvm' command defined in environment" { + test_implementing && skip + run nvm + assert_equal "$status" "0" "nvm command defined" +} + +@test "Running 'nvm alias' should create a file in the alias directory." { + test_implementing && skip + run nvm alias test v0.1.2 + [ "$status" -eq 0 ] + result=$(cat "alias/test") + assert_equal "$result" "v0.1.2" "expected new alias" +} + +@test 'Running "nvm current" should display current nvm environment.' { + test_implementing && skip + + run nvm deactivate + + run nvm current + assert_match "$output" 'system|none' "expected 'none' or 'system' when deactivated" +} + +@test 'Running "nvm deactivate" should unset the nvm environment variables.' { + test_implementing && skip + + mkdir -p v0.2.3 + + # v0.2.3 should not be active + assert_nomatch "$PATH" ".*v0.2.3/.*/bin" "v0.2.3 should not be active yet" + [ `expr $PATH : ".*v0.2.3/.*/bin"` = 0 ] + + # can't use 'run' -- sets up new env, PATH is lost + nvm use 0.2.3 + + assert_match $PATH "v0.2.3/.*/bin" "PATH should contain v0.2.3" + assert_nomatch $NODE_PATH "v0.2.3/lib/node_modules" "NODE_PATH should not contain v0.2.3" + + nvm deactivate + + assert_nomatch $PATH "v0.2.3/.*/bin" "PATH should be cleaned of v0.2.3" + assert_nomatch $NODE_PATH "v0.2.3/lib/node_modules" "NODE_PATH should not contain v0.2.3" +} + +@test 'Running "nvm install" with "--reinstall-packages-from" requires a valid version' { + test_implementing && skip + + mkdir -p v0.10.4 + + nvm deactivate + + run nvm install v0.10.5 --reinstall-packages-from=0.11 + assert_equal 5 "$status" "should exit with code 5, invalid version" + assert_match "$output" "If --reinstall-packages-from is provided, it must point to an installed version of node." + + run nvm install v0.10.5 --reinstall-packages-from=0.10.5 + assert_equal 4 "$status" "should exit with code 4, same version" + assert_match "$output" "You can't reinstall global packages from the same version of node you're installing." + +} + +@test 'Running "nvm install" with an invalid version fails nicely' { + test_implementing && skip + + run nvm install invalid.invalid + assert_equal "3" "$status" "Invalid version, exit with status 3" + assert_match "$output" "Version 'invalid.invalid' not found - try \`nvm ls-remote\` to browse available versions." "nvm installing an invalid version did not print a nice error message" +} + +@test 'Running "nvm unalias" should remove the alias file.' { + test_implementing && skip + + echo v0.1.2 > alias/test + + run nvm unalias test + ! [ -e alias/test ] +} + +@test 'Running "nvm uninstall" should remove the appropriate directory.' { + test_implementing && skip + + mkdir -p v0.0.1 src/node-v0.0.1 + + nvm uninstall v0.0.1 + ! [ -d 'v0.0.1' ] + ! [ -d 'src/node-v0.0.1' ] +} + +@test 'Running "nvm unload" should unset all function and variables.' { + test_implementing && skip + + run type nvm + assert_equal 0 "$status" "nvm not loaded" + + nvm unload + + run type nvm + assert_equal 1 "$status" "nvm should have unloaded" +} + +@test 'Running "nvm use foo" where "foo" is circular aborts' { + test_implementing && skip + + echo 'foo' > alias/foo + + run nvm use foo + assert_equal 8 "$status" "Expected exit code 8 (infinite alias loop)" + assert_match "$output" 'The alias "foo" leads to an infinite loop. Aborting.' +} + +@test 'Running "nvm use system" should work as expected' { + test_implementing && skip + + # NOTE: undocumented assumption of this test is + # that node is installed on the test machine! + # TODO: fix that, by using a mock + + nvm_has_system_node() { return 0; } + run nvm use system + assert_equal 0 "$status" "Expect success in using system node" + assert_match "$output" 'Now using system version of node:' + + nvm_has_system_node() { return 1; } + run nvm use system + assert_equal 127 "$status" "Expect failure when no system node" + assert_match "$output" 'System version of node not found.' +} + +@test 'Running "nvm use x" should create and change the "current" symlink' { + test_implementing && skip + + export NVM_SYMLINK_CURRENT=true + + mkdir -p v0.10.29 + nvm use 0.10.29 + rmdir v0.10.29 + + # TODO make this a proper assert + [ -L current ] # "expected 'current' symlink to be created" + + oldLink="$(readlink current)" + + assert_equal "$(basename $oldLink)" "v0.10.29" "Expected 'current' to point to v0.10.29 but was $oldLink" + + mkdir v0.11.13 + nvm use 0.11.13 + rmdir v0.11.13 + + newlink="$(readlink current)" + + assert_equal "$(basename $newlink)" "v0.11.13" "Expected 'current' to point to v0.11.13 but was $newLink" +} + +@test 'Running "nvm use x" should not create the "current" symlink if $NVM_SYMLINK_CURRENT is false' { + test_implementing && skip + + test_symlink_made() { + local arg="$1" + + mkdir v0.10.29 + + if [ "$arg" = "undef" ] + then + unset NVM_SYMLINK_CURRENT + else + NVM_SYMLINK_CURRENT="$arg" + fi + + run nvm use 0.10.29 + run [ -L current ] + result="$status" + + rm -f current + rmdir v0.10.29 + + return $result + } + + test_symlink_made 'true' + + ! test_symlink_made 'false' + ! test_symlink_made 'garbagevalue' + ! test_symlink_made 0 + ! test_symlink_made 1 + ! test_symlink_made 'undef' +} + +@test 'Sourcing nvm.sh should make the nvm command available.' { + + nvm +} diff --git a/test/bats/listing-versions.bats b/test/bats/listing-versions.bats new file mode 100644 index 0000000..f209df8 --- /dev/null +++ b/test/bats/listing-versions.bats @@ -0,0 +1,158 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." + +setup() { + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias + load "${NVM_SRC_DIR}/nvm.sh" +} + +teardown() { + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test 'Listing versions/Running "nvm ls 0.0.2" should display only version 0.0.2.' { + + mkdir -p v0.0.2 + mkdir -p v0.0.20 + + run nvm ls 0.0.2 + assert_match ${lines[0]} "v0.0.2" "nvm ls 0.0.2 must contain v0.0.2" + + run nvm ls 0.0.2 + assert_nomatch "$output" "v0.0.20" "nvm ls 0.0.2 must NOT contain v0.0.20" +} + +@test 'Listing versions/Running "nvm ls 0.2" should display only 0.2.x versions.' { + + mkdir -p v0.1.3 + mkdir -p v0.2.3 + mkdir -p v0.20.3 + + run nvm ls 0.1 + assert_nomatch "$output" "v0.2.3" "nvm ls 0.1 should not contain v0.2.3" + assert_nomatch "$output" "v0.20.3" "nvm ls 0.1 should not contain v0.20.3" + assert_match "$output" "v0.1.3" "nvm ls 0.1 should contain v0.1.3" + + run nvm ls 0.2 + assert_match "$output" "v0.2.3" "nvm ls 0.2 should contain v0.2.3" + assert_nomatch "$output" "v0.20.3" "nvm ls 0.2 should not contain v0.20.3" + assert_nomatch "$output" "v0.1.3" "nvm ls 0.2 should not contain v0.1.3" +} + +@test 'Listing versions/Running "nvm ls foo" should return a nonzero exit code when not found' { + + run nvm ls nonexistent_version + assert_equal 3 "$status" +} + +@test 'Listing versions/Running "nvm ls node" should return a nonzero exit code when not found' { + + run nvm ls node + assert_equal 3 "$status" +} + +@test 'Listing versions/Running "nvm ls stable" and "nvm ls unstable" should return the appropriate implicit alias' { + + mkdir -p v0.2.3 + mkdir -p v0.3.3 + + run nvm ls stable + assert_match "$output" "0.2.3" "expected 'nvm ls stable' to give STABLE_VERSION" + + run nvm ls unstable + assert_match "$output" "0.3.3" "expected 'nvm ls unstable' to give UNSTABLE_VERSION" + mkdir -p v0.1.2 + nvm alias stable 0.1 + + run nvm ls stable + assert_nomatch "$output" "0.2.3" "expected 'nvm ls stable' to not give old STABLE_VERSION" + assert_match "$output" "v0.1.2" "expected 'nvm ls stable' to give new STABLE_VERSION" +} + +## merged two tests here +# `nvm ls` and `nvm ls system` +@test 'Listing versions/Running "nvm ls [system]" should include "system" when appropriate' { + + mkdir -p v0.{0,3}.{1,3,9} + + nvm_has_system_node() { return 0; } + run nvm ls system + assert_match "$output" system '"nvm ls system" did not contain "system" when system node is present' + + run nvm ls + assert_match "$output" system '"nvm ls" did not contain "system" when system node is present' + + nvm_has_system_node() { return 1; } + run nvm ls system + assert_nomatch "$output" system '"nvm ls system" contained "system" when system node is NOT present' + + run nvm ls + assert_nomatch "$output" system '"nvm ls" contained "system" when system node is NOT present' +} + +@test 'Listing versions/Running "nvm ls" should display all installed versions.' { + + mkdir -p v0.{0,3}.{1,3,9} + + run nvm ls + assert_match "$output" v0.0.1 + assert_match "$output" v0.0.3 + assert_match "$output" v0.0.9 + assert_match "$output" v0.3.1 + assert_match "$output" v0.3.3 + assert_match "$output" v0.3.9 +} + +@test 'Listing versions/Running "nvm ls" should filter out ".nvm"' { + + mkdir -p v0.{1,2}.3 + + run nvm ls + assert_nomatch "$output" "^ *\." "running 'nvm ls' should filter out dotfiles" +} + +@test 'Listing versions/Running "nvm ls" should filter out "versions"' { + + mkdir -p v0.{1,2}.3 versions + run nvm ls + assert_nomatch "$output" versions "running 'nvm ls' should filter out 'versions'" +} + +@test 'Listing versions/Running "nvm ls" should list versions both in and out of the "versions" directory' { + + mkdir -p versions/v0.12.1 + mkdir -p v0.1.3 + + run nvm ls 0.12 + assert_match "$output" "v0.12.1" "'nvm ls' did not list a version in versions/" + + run nvm ls 0.1 + assert_match "$output" "v0.1.3" "'nvm ls' did not list a version NOT in versions/" + +} + +@test 'Listing versions/Running "nvm ls" with node-like versioning vx.x.x should only list a matched version' { + + mkdir -p v0.1.2 + + run nvm ls v0.1 + assert_match "$output" "v0.1.2" + + run nvm ls v0.1.2 + assert_match "$output" "v0.1.2" + + run nvm ls v0.1. + assert_match "$output" "v0.1.2" + + run nvm ls v0.1.1 + assert_nomatch "$output" "v0.1.2" + assert_match "$output" "N/A" +} diff --git a/test/bats/test_helper.bash b/test/bats/test_helper.bash new file mode 100644 index 0000000..9a2f2aa --- /dev/null +++ b/test/bats/test_helper.bash @@ -0,0 +1,152 @@ +# test_helper.bash stolen from rbenv +# +#unset RBENV_VERSION +#unset RBENV_DIR + +if enable -f "${BATS_TEST_DIRNAME}"/../libexec/rbenv-realpath.dylib realpath 2>/dev/null; then + RBENV_TEST_DIR="$(realpath "$BATS_TMPDIR")/rbenv" +else + if [ -n "$RBENV_NATIVE_EXT" ]; then + echo "rbenv: failed to load \`realpath' builtin" >&2 + exit 1 + fi + RBENV_TEST_DIR="${BATS_TMPDIR}/rbenv" +fi + +# guard against executing this block twice due to bats internals +if [ "$RBENV_ROOT" != "${RBENV_TEST_DIR}/root" ]; then + export RBENV_ROOT="${RBENV_TEST_DIR}/root" + + PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin + PATH="${BATS_TEST_DIRNAME}/../libexec:$PATH" + PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" + export PATH +fi + +teardown() { + rm -rf "$RBENV_TEST_DIR" +} + +flunk() { + { if [ "$#" -eq 0 ]; then cat - + else echo "$@" + fi + } | sed "s:${RBENV_TEST_DIR}:TEST_DIR:g" >&2 + return 1 +} + +assert_success() { + if [ "$status" -ne 0 ]; then + flunk "command failed with exit status $status" + elif [ "$#" -gt 0 ]; then + assert_output "$1" + fi +} + +assert_failure() { + if [ "$status" -eq 0 ]; then + flunk "expected failed exit status" + elif [ "$#" -gt 0 ]; then + assert_output "$1" + fi +} + +assert_equal() { + if [ "$1" != "$2" ]; then + { [ -z "$3" ] || echo "$3" + echo "expected: $1" + echo "actual: $2" + } | flunk + fi +} + +assert_match() { + if [[ ! "$1" =~ $2 ]]; then + { [ -z "$3" ] || echo "$3" + echo "expected: $1" + echo "to match: $2" + } | flunk + fi +} + +assert_nomatch() { + if [[ "$1" =~ $2 ]]; then + { [ -z "$3" ] || echo "$3" + echo "expected : $1" + echo "to not match: $2" + } | flunk + fi +} + +assert_unequal() { + if [ "$1" = "$2" ]; then + { [ -z "$3" ] || echo "$3" + echo "expected: $1" + echo "unequal to: $2" + } | flunk + fi +} + +assert_output() { + local expected + if [ $# -eq 0 ]; then expected="$(cat -)" + else expected="$1" + fi + assert_equal "$expected" "$output" +} + +assert_line() { + if [ "$1" -ge 0 ] 2>/dev/null; then + assert_equal "$2" "${lines[$1]}" "$3" + else + local line + for line in "${lines[@]}"; do + if [ "$line" = "$1" ]; then return 0; fi + done + flunk "expected line \`$1'" + fi +} + +refute_line() { + if [ "$1" -ge 0 ] 2>/dev/null; then + local num_lines="${#lines[@]}" + if [ "$1" -lt "$num_lines" ]; then + flunk "output has $num_lines lines" + fi + else + local line + for line in "${lines[@]}"; do + if [ "$line" = "$1" ]; then + flunk "expected to not find line \`$line'" + fi + done + fi +} + +assert() { + if ! "$@"; then + flunk "failed: $@" + fi +} + +# Output a modified PATH that ensures that the given executable is not present, +# but in which system utils necessary for rbenv operation are still available. +path_without() { + local exe="$1" + local path="${PATH}:" + local found alt util + for found in $(which -a "$exe"); do + found="${found%/*}" + if [ "$found" != "${RBENV_ROOT}/shims" ]; then + alt="${RBENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')" + mkdir -p "$alt" + for util in bash head cut readlink greadlink; do + if [ -x "${found}/$util" ]; then + ln -s "${found}/$util" "${alt}/$util" + fi + done + path="${path/${found}:/${alt}:}" + fi + done + echo "${path%:}" +} diff --git a/test/bats/unit-tests.bats b/test/bats/unit-tests.bats new file mode 100644 index 0000000..1a070d1 --- /dev/null +++ b/test/bats/unit-tests.bats @@ -0,0 +1,387 @@ +#!/usr/bin/env bats + +load test_helper + +NVM_SRC_DIR="${BATS_TEST_DIRNAME}/../.." +source "${NVM_SRC_DIR}/nvm.sh" + +FIXTURES_DIR="${NVM_SRC_DIR}/test/fixtures" + +test_debug() { + false # set to 'true' to get setup/teardown in test stderr +} + +test_implementing() { + false # set to 'false' to run all tests; set to 'true' to skip implemented tests + # only used while porting to bats: remove afterward +} + +setup() { + test_debug && echo 'setup' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* + mkdir src alias + load "${NVM_SRC_DIR}/nvm.sh" +} + +teardown() { + test_debug && echo 'teardown' >&2 + NVM_DIR="${BATS_TMPDIR}" + cd "${NVM_DIR}" + rm -Rf src alias v* +} + +@test './Unit tests/nvm_alias' { + test_implementing && skip + + run nvm_alias + assert_equal 1 "$status" "nvm_alias should exit with code 1 for missing" + assert_match "$output" "An alias is required." + + run nvm_alias nonexistent + assert_equal 2 "$status" "nvm_alias should exit with code 2 for nonexistent" + assert_match "$output" "Alias does not exist." + + run nvm alias test "0.10" + run nvm_alias test + assert_match "$output" "0.10" "nvm_alias test produced wrong output" +} + +@test './Unit tests/nvm_checksum' { + test_implementing && skip + + mkdir -p tmp + touch tmp/emptyfile + echo -n "test" > tmp/testfile + + run nvm_checksum tmp/emptyfile "da39a3ee5e6b4b0d3255bfef95601890afd80709" + assert_equal 0 "$status" "nvm_checksum on empty file did not match SHA1 of empty string" + + run nvm_checksum tmp/testfile "da39a3ee5e6b4b0d3255bfef95601890afd80709" + assert_equal 1 "$status" "nvm_checksum allowed a bad checksum" +} + +@test './Unit tests/nvm_find_up' { + test_implementing && skip + + mkdir -p tmp_nvm_find_up/a/b/c/d + touch tmp_nvm_find_up/test + touch tmp_nvm_find_up/a/b/c/test + + output="$(PWD=${PWD}/tmp_nvm_find_up/a nvm_find_up 'test')" + assert_equal "${PWD}/tmp_nvm_find_up" "$output" "failed to find 1 dir up" + + output="$(PWD=${PWD}/tmp_nvm_find_up/a/b nvm_find_up 'test')" + assert_equal "${PWD}/tmp_nvm_find_up" "$output" "failed to find 2 dirs up" + + output="$(PWD=${PWD}/tmp_nvm_find_up/a/b/c nvm_find_up 'test')" + assert_equal "${PWD}/tmp_nvm_find_up/a/b/c" "$output" "failed to find in current dir" + + output="$(PWD=${PWD}/tmp_nvm_find_up/a/b/c/d nvm_find_up 'test')" + assert_equal "${PWD}/tmp_nvm_find_up/a/b/c" "$output" "failed to find 1 up from current dir" + +} + +@test './Unit tests/nvm_format_version' { + test_implementing && skip + + run nvm_format_version 0.1.2 + assert_equal "v0.1.2" "$output" + + run nvm_format_version 0.1 + assert_equal "v0.1.0" "$output" +} + +@test './Unit tests/nvm_has' { + test_implementing && skip + + nvm_has cat + type cat + + ! nvm_has foobarbaz + ! type foobarbaz +} + +@test './Unit tests/nvm_has_system_node' { + test_implementing && skip + + mkdir v0.1.2 + touch v0.1.2/node + + nvm use 0.1.2 + + if command -v node; then + nvm_has_system_node + else + ! nvm_has_system_node + fi + + nvm deactivate + + if command -v node; then + nvm_has_system_node + else + ! nvm_has_system_node + fi +} + +@test './Unit tests/nvm_ls_current' { + test_implementing && skip + + nvm deactivate + run nvm_ls_current + assert_match "$output" "system" "when deactivated did not return 'system'" + + rm -fr nvm_ls_current_tmp + mkdir -p nvm_ls_current_tmp + TDIR="${PWD}/nvm_ls_current_tmp" + + ln -s "$(which which)" "$TDIR/which" + ln -s "$(which dirname)" "$TDIR/dirname" + + output="$(PATH=${TDIR} nvm_ls_current || true)" + assert_match "$output" "none" "when node not installed, nvm_ls_current should return 'none'" + + echo "#!/bin/bash" > node + echo "echo 'VERSION FOO!'" >> node + chmod a+x node + + nvm_tree_contains_path() { + return 0 + } + output="$(PATH=${TDIR} nvm_ls_current || true)" + assert_match "$output" "none" "when activated 'nvm_ls_current' should return 'noe'" +} + +@test './Unit tests/nvm_ls_remote' { + test_implementing && skip + + nvm_download() { + cat ${FIXTURES_DIR}/download.txt + } + + run nvm_ls_remote foo + assert_equal 3 "$status" + assert_match "N/A" "$output" "nonexistent version should report N/A" + + run nvm_ls_remote + assert_equal 0 "$status" + assert_line 0 "v0.1.14" + assert_line 45 "v0.3.3" + assert_line 199 "v0.11.14" + + run nvm_ls_remote 0.3 + assert_equal 0 "$status" + assert_line 0 "v0.3.0" + assert_line 1 "v0.3.1" + assert_line 2 "v0.3.2" + assert_line 3 "v0.3.3" + assert_line 4 "v0.3.4" + assert_line 5 "v0.3.5" + assert_line 6 "v0.3.6" + assert_line 7 "v0.3.7" + assert_line 8 "v0.3.8" + + run nvm_print_implicit_alias remote stable + assert_match "$output" "0.10" + + run nvm_print_implicit_alias remote unstable + assert_match "$output" "0.11" + + run nvm_ls_remote stable + assert_match "$output" "0.10.32" + + run nvm_ls_remote unstable + assert_match "$output" "0.11.14" +} + + +@test './Unit tests/nvm_num_version_groups' { + test_implementing && skip + + assert_equal "0" "$(nvm_num_version_groups)" + + assert_equal "1" "$(nvm_num_version_groups a)" + + assert_equal "1" "$(nvm_num_version_groups 1)" "1 should give 1" + assert_equal "1" "$(nvm_num_version_groups v1)" "v1 should give 1" + assert_equal "1" "$(nvm_num_version_groups v1.)" "v1. should give 1" + + assert_equal "2" "$(nvm_num_version_groups 1.2)" "1.2 should give 2" + assert_equal "2" "$(nvm_num_version_groups v1.2)" "v1.2 should give 2" + assert_equal "2" "$(nvm_num_version_groups v1.2.)" "v1.2. should give 2" + + assert_equal "3" "$(nvm_num_version_groups 1.2.3)" "1.2.3 should give 3" + assert_equal "3" "$(nvm_num_version_groups v1.2.3)" "v1.2.3 should give 3" + assert_equal "3" "$(nvm_num_version_groups v1.2.3.)" "v1.2.3. should give 3" +} + +@test './Unit tests/nvm_prepend_path' { + test_implementing && skip + + TEST_PATH=/usr/bin:/usr/local/bin + NEW_PATH=`nvm_prepend_path "$TEST_PATH" "$NVM_DIR/v0.2.5/bin"` + + assert_equal "$NVM_DIR/v0.2.5/bin:/usr/bin:/usr/local/bin" "$NEW_PATH" "Not correctly prepended: $NEW_PATH " + + EMPTY_PATH= + NEW_PATH=`nvm_prepend_path "$EMPTY_PATH" "$NVM_DIR/v0.2.5/bin"` + + assert_equal "$NVM_DIR/v0.2.5/bin" "$NEW_PATH" "Not correctly prepended: $NEW_PATH " +} + +@test './Unit tests/nvm_print_implicit_alias errors' { + test_implementing && skip + + run nvm_print_implicit_alias + assert_match "$output" "nvm_print_implicit_alias must be specified with local or remote as the first argument." "nvm_print_implicit_alias did not require local|remote as first argument" + + run nvm_print_implicit_alias foo + assert_match "$output" "nvm_print_implicit_alias must be specified with local or remote as the first argument." "nvm_print_implicit_alias did not require local|remote as first argument" + assert_equal "1" "$status" + + run nvm_print_implicit_alias local + assert_match "$output" "Only implicit aliases 'stable' and 'unstable' are supported." "nvm_print_implicit_alias did not require stable|unstable as second argument" + + run nvm_print_implicit_alias local foo + assert_match "$output" "Only implicit aliases 'stable' and 'unstable' are supported." "nvm_print_implicit_alias did not require stable|unstable as second argument" + assert_equal "2" "$status" +} + +@test './Unit tests/nvm_print_implicit_alias success' { + test_implementing && skip + + mkdir -p v0.2.3 v0.3.4 v0.4.6 v0.5.7 v0.7.7 + + run nvm_print_implicit_alias local stable + assert_match "$output" "0.4" + + run nvm_print_implicit_alias local unstable + assert_match "$output" "0.7" + + nvm_ls_remote() { + echo "v0.4.3" + echo "v0.5.4" + echo "v0.6.6" + echo "v0.7.7" + echo "v0.9.7" + echo "v0.4.3" + echo "v0.5.4" + echo "v0.6.6" + echo "v0.7.7" + echo "v0.9.7" + } + + run nvm_print_implicit_alias remote stable + assert_match "$output" "0.6" + + run nvm_print_implicit_alias remote unstable + assert_match "$output" "0.9" +} + +@test './Unit tests/nvm_remote_version' { + test_implementing && skip + + nvm_ls_remote() { + echo "N/A" + } + + run nvm_remote_version foo + assert_match "$output" "N/A" + assert_equal "3" "$status" + + nvm_ls_remote() { + echo "test output" + echo "more test output" + echo "pattern received: _$1_" + } + + run nvm_remote_version foo + assert_match "$output" "pattern received: _foo_" "nvm_remote_version foo did not return last line only of nvm_ls_remote foo; got $OUTPUT" + assert_equal "0" "$status" +} + +@test './Unit tests/nvm_strip_path' { + test_implementing && skip + + TEST_PATH=$NVM_DIR/v0.10.5/bin:/usr/bin:$NVM_DIR/v0.11.5/bin:$NVM_DIR/v0.9.5/bin:/usr/local/bin:$NVM_DIR/v0.2.5/bin + + STRIPPED_PATH=`nvm_strip_path "$TEST_PATH" "/bin"` + + assert_equal "/usr/bin:/usr/local/bin" "$STRIPPED_PATH" "Not correctly stripped: $STRIPPED_PATH " + +} + +@test './Unit tests/nvm_tree_contains_path' { + test_implementing && skip + + mkdir -p tmp tmp2 + touch tmp/node tmp2/node + + run nvm_tree_contains_path + assert_equal "2" "$status" + assert_match "$output" "both the tree and the node path are required" "expected error messge with no args" + + nvm_tree_contains_path tmp tmp/node + ! nvm_tree_contains_path tmp tmp2/node + ! nvm_tree_contains_path tmp2 tmp/node + nvm_tree_contains_path tmp2 tmp2/node +} + +@test './Unit tests/nvm_validate_implicit_alias' { + test_implementing && skip + + run nvm_validate_implicit_alias + assert_match "$output" "Only implicit aliases 'stable' and 'unstable' are supported." "nvm_validate_implicit_alias did not require stable|unstable" + run nvm_validate_implicit_alias foo + assert_match "$output" "Only implicit aliases 'stable' and 'unstable' are supported." "nvm_validate_implicit_alias did not require stable|unstable" + assert_equal "1" "$status" + + nvm_validate_implicit_alias stable + nvm_validate_implicit_alias unstable +} + +@test './Unit tests/nvm_version_dir' { +# test_implementing && skip + + run nvm_version_dir + assert_equal "$NVM_DIR/versions" "$output" + + run nvm_version_dir new + assert_equal "$NVM_DIR/versions" "$output" + + run nvm_version_dir old + assert_equal "$NVM_DIR" "$output" + + run nvm_version_dir foo + assert_match "$output" "unknown version dir" + assert_unequal "0" "$status" +} + +@test './Unit tests/nvm_version_greater' { +# test_implementing && skip + + nvm_version_greater 0.10.0 0.2.12 + + ! nvm_version_greater 0.10.0 0.20.12 + + ! nvm_version_greater 0.10.0 0.10.0 +} + +@test './Unit tests/nvm_version_path' { +# test_implementing && skip + + run nvm_version_path foo + assert_match "$output" "$NVM_DIR/foo" + + run nvm_version_path + assert_unequal "0" "$status" + + run nvm_version_path v0.11.0 + assert_match "$output" "$NVM_DIR/v0.11.0" + + run nvm_version_path v0.12.0 + assert_match "$output" "$NVM_DIR/versions/v0.12.0" +} + diff --git a/test/fixtures/download.txt b/test/fixtures/download.txt new file mode 100644 index 0000000..58535a2 --- /dev/null +++ b/test/fixtures/download.txt @@ -0,0 +1,430 @@ +foo "v0.10.0 +foo "v0.10.0 +foo "v0.10.1 +foo "v0.10.1 +foo "v0.10.10 +foo "v0.10.10 +foo "v0.10.11 +foo "v0.10.11 +foo "v0.10.12 +foo "v0.10.12 +foo "v0.10.13 +foo "v0.10.13 +foo "v0.10.14 +foo "v0.10.14 +foo "v0.10.15 +foo "v0.10.15 +foo "v0.10.16 +foo "v0.10.16 +foo "v0.10.16 +foo "v0.10.16 +foo "v0.10.17 +foo "v0.10.17 +foo "v0.10.18 +foo "v0.10.18 +foo "v0.10.19 +foo "v0.10.19 +foo "v0.10.2 +foo "v0.10.2 +foo "v0.10.20 +foo "v0.10.20 +foo "v0.10.21 +foo "v0.10.21 +foo "v0.10.22 +foo "v0.10.22 +foo "v0.10.23 +foo "v0.10.23 +foo "v0.10.24 +foo "v0.10.24 +foo "v0.10.25 +foo "v0.10.25 +foo "v0.10.26 +foo "v0.10.26 +foo "v0.10.27 +foo "v0.10.27 +foo "v0.10.28 +foo "v0.10.28 +foo "v0.10.29 +foo "v0.10.29 +foo "v0.10.3 +foo "v0.10.3 +foo "v0.10.30 +foo "v0.10.30 +foo "v0.10.31 +foo "v0.10.31 +foo "v0.10.32 +foo "v0.10.32 +foo "v0.10.4 +foo "v0.10.4 +foo "v0.10.5 +foo "v0.10.5 +foo "v0.10.6 +foo "v0.10.6 +foo "v0.10.7 +foo "v0.10.7 +foo "v0.10.8 +foo "v0.10.8 +foo "v0.10.9 +foo "v0.10.9 +foo "v0.11.0 +foo "v0.11.0 +foo "v0.11.1 +foo "v0.11.1 +foo "v0.11.10 +foo "v0.11.10 +foo "v0.11.11 +foo "v0.11.11 +foo "v0.11.12 +foo "v0.11.12 +foo "v0.11.13 +foo "v0.11.13 +foo "v0.11.14 +foo "v0.11.14 +foo "v0.11.2 +foo "v0.11.2 +foo "v0.11.3 +foo "v0.11.3 +foo "v0.11.4 +foo "v0.11.4 +foo "v0.11.5 +foo "v0.11.5 +foo "v0.11.6 +foo "v0.11.6 +foo "v0.11.7 +foo "v0.11.7 +foo "v0.11.8 +foo "v0.11.8 +foo "v0.11.9 +foo "v0.11.9 +foo "v0.5.1 +foo "v0.5.1 +foo "v0.5.10 +foo "v0.5.10 +foo "v0.5.2 +foo "v0.5.2 +foo "v0.5.3 +foo "v0.5.3 +foo "v0.5.4 +foo "v0.5.4 +foo "v0.5.5 +foo "v0.5.5 +foo "v0.5.6 +foo "v0.5.6 +foo "v0.5.7 +foo "v0.5.7 +foo "v0.5.8 +foo "v0.5.8 +foo "v0.5.9 +foo "v0.5.9 +foo "v0.6.0 +foo "v0.6.0 +foo "v0.6.1 +foo "v0.6.1 +foo "v0.6.10 +foo "v0.6.10 +foo "v0.6.11 +foo "v0.6.11 +foo "v0.6.12 +foo "v0.6.12 +foo "v0.6.13 +foo "v0.6.13 +foo "v0.6.14 +foo "v0.6.14 +foo "v0.6.15 +foo "v0.6.15 +foo "v0.6.16 +foo "v0.6.16 +foo "v0.6.17 +foo "v0.6.17 +foo "v0.6.18 +foo "v0.6.18 +foo "v0.6.19 +foo "v0.6.19 +foo "v0.6.2 +foo "v0.6.2 +foo "v0.6.20 +foo "v0.6.20 +foo "v0.6.21 +foo "v0.6.21 +foo "v0.6.3 +foo "v0.6.3 +foo "v0.6.4 +foo "v0.6.4 +foo "v0.6.5 +foo "v0.6.5 +foo "v0.6.6 +foo "v0.6.6 +foo "v0.6.7 +foo "v0.6.7 +foo "v0.6.8 +foo "v0.6.8 +foo "v0.6.9 +foo "v0.6.9 +foo "v0.7.0 +foo "v0.7.0 +foo "v0.7.1 +foo "v0.7.1 +foo "v0.7.10 +foo "v0.7.10 +foo "v0.7.11 +foo "v0.7.11 +foo "v0.7.12 +foo "v0.7.12 +foo "v0.7.2 +foo "v0.7.2 +foo "v0.7.3 +foo "v0.7.3 +foo "v0.7.4 +foo "v0.7.4 +foo "v0.7.5 +foo "v0.7.5 +foo "v0.7.6 +foo "v0.7.6 +foo "v0.7.7 +foo "v0.7.7 +foo "v0.7.8 +foo "v0.7.8 +foo "v0.7.9 +foo "v0.7.9 +foo "v0.8.0 +foo "v0.8.0 +foo "v0.8.1 +foo "v0.8.1 +foo "v0.8.10 +foo "v0.8.10 +foo "v0.8.11 +foo "v0.8.11 +foo "v0.8.12 +foo "v0.8.12 +foo "v0.8.13 +foo "v0.8.13 +foo "v0.8.14 +foo "v0.8.14 +foo "v0.8.15 +foo "v0.8.15 +foo "v0.8.16 +foo "v0.8.16 +foo "v0.8.17 +foo "v0.8.17 +foo "v0.8.18 +foo "v0.8.18 +foo "v0.8.19 +foo "v0.8.19 +foo "v0.8.2 +foo "v0.8.2 +foo "v0.8.20 +foo "v0.8.20 +foo "v0.8.21 +foo "v0.8.21 +foo "v0.8.22 +foo "v0.8.22 +foo "v0.8.23 +foo "v0.8.23 +foo "v0.8.24 +foo "v0.8.24 +foo "v0.8.25 +foo "v0.8.25 +foo "v0.8.26 +foo "v0.8.26 +foo "v0.8.27 +foo "v0.8.27 +foo "v0.8.28 +foo "v0.8.28 +foo "v0.8.3 +foo "v0.8.3 +foo "v0.8.4 +foo "v0.8.4 +foo "v0.8.5 +foo "v0.8.5 +foo "v0.8.6 +foo "v0.8.6 +foo "v0.8.7 +foo "v0.8.7 +foo "v0.8.8 +foo "v0.8.8 +foo "v0.8.9 +foo "v0.8.9 +foo "v0.9.0 +foo "v0.9.0 +foo "v0.9.1 +foo "v0.9.1 +foo "v0.9.10 +foo "v0.9.10 +foo "v0.9.11 +foo "v0.9.11 +foo "v0.9.12 +foo "v0.9.12 +foo "v0.9.2 +foo "v0.9.2 +foo "v0.9.3 +foo "v0.9.3 +foo "v0.9.4 +foo "v0.9.4 +foo "v0.9.5 +foo "v0.9.5 +foo "v0.9.6 +foo "v0.9.6 +foo "v0.9.7 +foo "v0.9.7 +foo "v0.9.8 +foo "v0.9.8 +foo "v0.9.9 +foo "v0.9.9 +foo "v0.1.100 +foo "v0.1.100 +foo "v0.1.101 +foo "v0.1.101 +foo "v0.1.102 +foo "v0.1.102 +foo "v0.1.103 +foo "v0.1.103 +foo "v0.1.104 +foo "v0.1.104 +foo "v0.1.14 +foo "v0.1.14 +foo "v0.1.15 +foo "v0.1.15 +foo "v0.1.16 +foo "v0.1.16 +foo "v0.1.17 +foo "v0.1.17 +foo "v0.1.18 +foo "v0.1.18 +foo "v0.1.19 +foo "v0.1.19 +foo "v0.1.20 +foo "v0.1.20 +foo "v0.1.21 +foo "v0.1.21 +foo "v0.1.22 +foo "v0.1.22 +foo "v0.1.23 +foo "v0.1.23 +foo "v0.1.24 +foo "v0.1.24 +foo "v0.1.25 +foo "v0.1.25 +foo "v0.1.26 +foo "v0.1.26 +foo "v0.1.27 +foo "v0.1.27 +foo "v0.1.28 +foo "v0.1.28 +foo "v0.1.29 +foo "v0.1.29 +foo "v0.1.30 +foo "v0.1.30 +foo "v0.1.31 +foo "v0.1.31 +foo "v0.1.32 +foo "v0.1.32 +foo "v0.1.33 +foo "v0.1.33 +foo "v0.1.90 +foo "v0.1.90 +foo "v0.1.91 +foo "v0.1.91 +foo "v0.1.92 +foo "v0.1.92 +foo "v0.1.93 +foo "v0.1.93 +foo "v0.1.94 +foo "v0.1.94 +foo "v0.1.95 +foo "v0.1.95 +foo "v0.1.96 +foo "v0.1.96 +foo "v0.1.97 +foo "v0.1.97 +foo "v0.1.98 +foo "v0.1.98 +foo "v0.1.99 +foo "v0.1.99 +foo "v0.10.14 +foo "v0.10.14 +foo "v0.2.0 +foo "v0.2.0 +foo "v0.2.1 +foo "v0.2.1 +foo "v0.2.2 +foo "v0.2.2 +foo "v0.2.3 +foo "v0.2.3 +foo "v0.2.4 +foo "v0.2.4 +foo "v0.2.5 +foo "v0.2.5 +foo "v0.2.6 +foo "v0.2.6 +foo "v0.3.0 +foo "v0.3.0 +foo "v0.3.1 +foo "v0.3.1 +foo "v0.3.2 +foo "v0.3.2 +foo "v0.3.3 +foo "v0.3.3 +foo "v0.3.4 +foo "v0.3.4 +foo "v0.3.5 +foo "v0.3.5 +foo "v0.3.6 +foo "v0.3.6 +foo "v0.3.7 +foo "v0.3.7 +foo "v0.3.8 +foo "v0.3.8 +foo "v0.4.0 +foo "v0.4.0 +foo "v0.4.1 +foo "v0.4.1 +foo "v0.4.10 +foo "v0.4.10 +foo "v0.4.11 +foo "v0.4.11 +foo "v0.4.12 +foo "v0.4.12 +foo "v0.4.2 +foo "v0.4.2 +foo "v0.4.3 +foo "v0.4.3 +foo "v0.4.4 +foo "v0.4.4 +foo "v0.4.5 +foo "v0.4.5 +foo "v0.4.6 +foo "v0.4.6 +foo "v0.4.7 +foo "v0.4.7 +foo "v0.4.8 +foo "v0.4.8 +foo "v0.4.9 +foo "v0.4.9 +foo "v0.5.0 +foo "v0.5.0 +foo "v0.6.1 +foo "v0.6.1 +foo "v0.6.10 +foo "v0.6.10 +foo "v0.6.11 +foo "v0.6.11 +foo "v0.6.12 +foo "v0.6.12 +foo "v0.6.13 +foo "v0.6.13 +foo "v0.6.2 +foo "v0.6.2 +foo "v0.6.3 +foo "v0.6.3 +foo "v0.6.4 +foo "v0.6.4 +foo "v0.6.5 +foo "v0.6.5 +foo "v0.6.6 +foo "v0.6.6 +foo "v0.6.7 +foo "v0.6.7 +foo "v0.6.8 +foo "v0.6.8 +foo "v0.6.9 +foo "v0.6.9