Merge pull request #3375 from native-api/fix_inf_loop_shim_different_location

Fix an infinite loop if a shim is symlinked to and called from a different location
This commit is contained in:
native-api 2025-12-20 11:24:41 +03:00 committed by GitHub
commit 22993a239e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 251 additions and 179 deletions

View File

@ -29,6 +29,14 @@ if [ -z "$PYENV_COMMAND" ]; then
exit 1 exit 1
fi fi
if [[ -n $_PYENV_SHIM_PATH ]]; then
PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
NR_PYENV_SHIM_PATHS_PROGRAM="_PYENV_SHIM_PATHS_${PROGRAM}"
export -- "$NR_PYENV_SHIM_PATHS_PROGRAM=$_PYENV_SHIM_PATH${!NR_PYENV_SHIM_PATHS_PROGRAM:+:${!NR_PYENV_SHIM_PATHS_PROGRAM}}"
unset PROGRAM NR_PYENV_SHIM_PATHS_PROGRAM
unset _PYENV_SHIM_PATH
fi
PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")" PYENV_COMMAND_PATH="$(pyenv-which "$PYENV_COMMAND")"
PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}" PYENV_BIN_PATH="${PYENV_COMMAND_PATH%/*}"
export PYENV_VERSION export PYENV_VERSION

View File

@ -82,6 +82,10 @@ set -e
program="\${0##*/}" program="\${0##*/}"
export PYENV_ROOT="$PYENV_ROOT" export PYENV_ROOT="$PYENV_ROOT"
SHIM_PATH=\${0%/*}
if [[ \$SHIM_PATH != "$PYENV_ROOT/shims" ]]; then
export _PYENV_SHIM_PATH="\$SHIM_PATH"
fi
exec "$(command -v pyenv)" exec "\$program" "\$@" exec "$(command -v pyenv)" exec "\$program" "\$@"
SH SH
chmod +x "$PROTOTYPE_SHIM_PATH" chmod +x "$PROTOTYPE_SHIM_PATH"

View File

@ -42,15 +42,19 @@ done
remove_from_path() { remove_from_path() {
local path_to_remove="$1" local -a paths_to_remove
local path_before IFS=: paths_to_remove=($1)
local path_to_remove path_before
local result=":${PATH//\~/$HOME}:" local result=":${PATH//\~/$HOME}:"
while [ "$path_before" != "$result" ]; do for path_to_remove in "${paths_to_remove[@]}"; do
path_before="$result" while true; do
result="${result//:$path_to_remove:/:}" path_before="$result"
result="${result//:$path_to_remove:/:}"
if [[ ${#path_before} == "${#result}" ]]; then break; fi
done
done done
result="${result%:}" result="${result:1:${#result}-2}"
echo "${result#:}" echo "$result"
} }
if [ -z "$PYENV_COMMAND" ]; then if [ -z "$PYENV_COMMAND" ]; then
@ -66,8 +70,10 @@ declare -a nonexistent_versions
for version in "${versions[@]}" "$system"; do for version in "${versions[@]}" "$system"; do
if [ "$version" = "system" ]; then if [ "$version" = "system" ]; then
PATH="$(remove_from_path "${PYENV_ROOT}/shims")" PROGRAM="$(echo "$PYENV_COMMAND" | tr a-z- A-Z_ | sed 's/[^A-Z0-9_]/_/g')"
PYENV_COMMAND_PATH="$(command -v "$PYENV_COMMAND" || true)" NR_CUSTOM_SHIM_PATHS="_PYENV_SHIM_PATHS_$PROGRAM"
SEARCH_PATH="$(remove_from_path "${PYENV_ROOT}/shims${!NR_CUSTOM_SHIM_PATHS:+:${!NR_CUSTOM_SHIM_PATHS}}")"
PYENV_COMMAND_PATH="$(PATH="$SEARCH_PATH" command -v "$PYENV_COMMAND" || true)"
else else
# $version may be a prefix to be resolved by pyenv-latest # $version may be a prefix to be resolved by pyenv-latest
version_path="$(pyenv-prefix "${version}" 2>/dev/null)" || \ version_path="$(pyenv-prefix "${version}" 2>/dev/null)" || \

View File

@ -2,18 +2,6 @@
load test_helper load test_helper
create_executable() {
name="${1?}"
shift 1
bin="${PYENV_ROOT}/versions/${PYENV_VERSION}/bin"
mkdir -p "$bin"
{ if [ $# -eq 0 ]; then cat -
else printf '%s\n' "$@"
fi
} | sed -Ee '1s/^ +//' > "${bin}/$name"
chmod +x "${bin}/$name"
}
@test "fails with invalid version" { @test "fails with invalid version" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
export PYENV_VERSION="3.4" export PYENV_VERSION="3.4"
@ -38,8 +26,8 @@ EOF
@test "completes with names of executables" { @test "completes with names of executables" {
export PYENV_VERSION="3.4" export PYENV_VERSION="3.4"
create_executable "fab" "#!/bin/sh" create_alt_executable "fab"
create_executable "python" "#!/bin/sh" create_alt_executable "python"
pyenv-rehash pyenv-rehash
run pyenv-completions exec run pyenv-completions exec
@ -65,7 +53,7 @@ SH
@test "forwards all arguments" { @test "forwards all arguments" {
export PYENV_VERSION="3.4" export PYENV_VERSION="3.4"
create_executable "python" <<SH create_alt_executable "python" <<SH
#!$BASH #!$BASH
echo \$0 echo \$0
for arg; do for arg; do
@ -87,16 +75,13 @@ OUT
} }
@test "sys.executable with system version (#98)" { @test "sys.executable with system version (#98)" {
export PATH="${PYENV_ROOT}/versions/bin:${PATH}" create_path_executable "python3" "echo system"
create_executable "python3" <<SH
#!$BASH system_python="$(python3 </dev/null)"
echo system
SH
system_python="$(python3)"
assert_equal "${system_python}" "system" assert_equal "${system_python}" "system"
export PYENV_VERSION="custom" export PYENV_VERSION="custom"
create_executable "python3" "#!/bin/sh" "echo custom" create_alt_executable "python3" "echo custom"
pyenv-rehash pyenv-rehash
@ -105,19 +90,16 @@ SH
} }
@test 'PATH is not modified with system Python' { @test 'PATH is not modified with system Python' {
export PATH="${PYENV_TEST_DIR}:${PATH}"
# Create a wrapper executable that verifies PATH. # Create a wrapper executable that verifies PATH.
PYENV_VERSION="custom" create_alt_executable_in_version "custom" "python3" <<!
create_executable "python3" '[[ "$PATH" == "${PYENV_TEST_DIR}/root/versions/custom/bin:"* ]] || { echo "unexpected:$PATH"; exit 2;}' [[ \$PATH == "\${PYENV_ROOT}/versions/custom/bin:"* ]] \
unset PYENV_VERSION || { echo "unexpected:\$PATH"; exit 2;}
!
pyenv-rehash pyenv-rehash
# Path is not modified with system Python. # Path is not modified with system Python.
cat > "${PYENV_TEST_DIR}/python3" <<SH create_path_executable "python3" "echo \$PATH"
#!$BASH
echo \$PATH
SH
chmod +x "${PYENV_TEST_DIR}/python3"
pyenv-rehash pyenv-rehash
run pyenv-exec python3 run pyenv-exec python3
assert_success "$PATH" assert_success "$PATH"
@ -134,3 +116,26 @@ SH
PYENV_VERSION=system:custom run pyenv-exec python3 PYENV_VERSION=system:custom run pyenv-exec python3
assert_success "$PATH" assert_success "$PATH"
} }
@test "sets/adds to _PYENV_SHIM_PATHS_{PROGRAM} when _PYENV_SHIM_PATH is set, unsets _PYENV_SHIM_PATH" {
progname='123;wacky-prog.name ^%$#'
envvarname="_PYENV_SHIM_PATHS_123_WACKY_PROG_NAME_____"
create_path_executable "$progname" <<!
echo $envvarname="\$$envvarname"
echo _PYENV_SHIM_PATH="\$_PYENV_SHIM_PATH"
!
_PYENV_SHIM_PATH=/unusual/shim/location run pyenv-exec "$progname"
assert_success
assert_output <<!
$envvarname=/unusual/shim/location
_PYENV_SHIM_PATH=
!
eval "export ${envvarname}=/another/shim/location"
_PYENV_SHIM_PATH=/unusual/shim/location run pyenv-exec "$progname"
assert_success
assert_output <<!
$envvarname=/unusual/shim/location:/another/shim/location
_PYENV_SHIM_PATH=
!
}

View File

@ -2,18 +2,6 @@
load test_helper load test_helper
_setup() {
export PATH="${PYENV_TEST_DIR}/bin:$PATH"
}
create_executable() {
local name="$1"
local bin="${PYENV_TEST_DIR}/bin"
mkdir -p "$bin"
sed -Ee '1s/^ +//' > "${bin}/$name"
chmod +x "${bin}/$name"
}
@test "creates shims and versions directories" { @test "creates shims and versions directories" {
assert [ ! -d "${PYENV_ROOT}/shims" ] assert [ ! -d "${PYENV_ROOT}/shims" ]
assert [ ! -d "${PYENV_ROOT}/versions" ] assert [ ! -d "${PYENV_ROOT}/versions" ]
@ -179,16 +167,14 @@ echo "\$PATH"
} }
@test "outputs sh-compatible case syntax" { @test "outputs sh-compatible case syntax" {
create_executable pyenv-commands <<! create_stub pyenv-commands <<!
#!$BASH
echo -e 'activate\ndeactivate\nrehash\nshell' echo -e 'activate\ndeactivate\nrehash\nshell'
! !
run pyenv-init - bash run pyenv-init - bash
assert_success assert_success
assert_line ' activate|deactivate|rehash|shell)' assert_line ' activate|deactivate|rehash|shell)'
create_executable pyenv-commands <<! create_stub pyenv-commands <<!
#!$BASH
echo echo
! !
run pyenv-init - bash run pyenv-init - bash

View File

@ -2,21 +2,8 @@
load test_helper load test_helper
_setup() {
export PATH="${PYENV_TEST_DIR}/bin:$PATH"
}
create_executable() {
local name="$1"
local bin="${PYENV_TEST_DIR}/bin"
mkdir -p "$bin"
sed -Ee '1s/^ +//' > "${bin}/$name"
chmod +x "${bin}/$name"
}
@test "read from installed" { @test "read from installed" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo 4.5.6 echo 4.5.6
! !
run pyenv-latest 4 run pyenv-latest 4
@ -27,8 +14,7 @@ echo 4.5.6
} }
@test "read from known" { @test "read from known" {
create_executable python-build <<! create_stub python-build <<!
#!$BASH
echo 4.5.6 echo 4.5.6
! !
run pyenv-latest -k 4 run pyenv-latest -k 4
@ -39,8 +25,7 @@ echo 4.5.6
} }
@test "installed version not found" { @test "installed version not found" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo 3.5.6 echo 3.5.6
echo 3.10.8 echo 3.10.8
! !
@ -52,8 +37,7 @@ pyenv: no installed versions match the prefix \`3.8'
} }
@test "known version not found" { @test "known version not found" {
create_executable python-build <<! create_stub python-build <<!
#!$BASH
echo 3.5.6 echo 3.5.6
echo 3.10.8 echo 3.10.8
! !
@ -65,8 +49,7 @@ pyenv: no known versions match the prefix \`3.8'
} }
@test "complete name resolves to itself" { @test "complete name resolves to itself" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo foo echo foo
echo foo.bar echo foo.bar
! !
@ -80,8 +63,7 @@ foo
} }
@test "sort CPython" { @test "sort CPython" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo 2.7.18 echo 2.7.18
echo 3.5.6 echo 3.5.6
echo 3.10.8 echo 3.10.8
@ -95,8 +77,7 @@ echo 3.10.6
} }
@test "ignores rolling releases, branch tips, alternative srcs, prereleases, virtualenvs; 't' versions if prefix without 't'" { @test "ignores rolling releases, branch tips, alternative srcs, prereleases, virtualenvs; 't' versions if prefix without 't'" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo 3.8.5-dev echo 3.8.5-dev
echo 3.8.5-src echo 3.8.5-src
echo 3.8.5-latest echo 3.8.5-latest
@ -117,8 +98,7 @@ echo 3.8.1/envs/foo
} }
@test "resolves to a 't' version if prefix has 't'" { @test "resolves to a 't' version if prefix has 't'" {
create_executable pyenv-versions <<! create_stub pyenv-versions <<!
#!$BASH
echo 3.13.2t echo 3.13.2t
echo 3.13.5 echo 3.13.5
echo 3.13.5t echo 3.13.5t
@ -132,9 +112,7 @@ echo 3.14.6
} }
@test "falls back to argument with -b" { @test "falls back to argument with -b" {
create_executable pyenv-versions <<! create_stub pyenv-versions
#!$BASH
!
run pyenv-latest -b nonexistent run pyenv-latest -b nonexistent
assert_failure assert_failure
assert_output <<! assert_output <<!
@ -143,9 +121,7 @@ nonexistent
} }
@test "falls back to argument and succeeds with -f" { @test "falls back to argument and succeeds with -f" {
create_executable pyenv-versions <<! create_stub pyenv-versions
#!$BASH
!
run pyenv-latest -f nonexistent run pyenv-latest -f nonexistent
assert_success assert_success
assert_output <<! assert_output <<!

View File

@ -2,13 +2,6 @@
load test_helper load test_helper
create_executable() {
local bin="${PYENV_ROOT}/versions/${PYENV_VERSION}/bin"
mkdir -p "$bin"
echo "#!/bin/sh" > "${bin}/$1"
chmod +x "${bin}/$1"
}
copy_src_pyenvd() { copy_src_pyenvd() {
mkdir -p "${PYENV_ROOT}" mkdir -p "${PYENV_ROOT}"
cp -r "${BATS_TEST_DIRNAME}/../pyenv.d" "${PYENV_ROOT}" cp -r "${BATS_TEST_DIRNAME}/../pyenv.d" "${PYENV_ROOT}"
@ -16,8 +9,8 @@ copy_src_pyenvd() {
@test "pip-rehash triggered when using 'pip'" { @test "pip-rehash triggered when using 'pip'" {
export PYENV_VERSION="3.7.14" export PYENV_VERSION="3.7.14"
create_executable "example" create_alt_executable "example"
create_executable "pip" create_alt_executable "pip"
copy_src_pyenvd copy_src_pyenvd
run command -v example 2> /dev/null run command -v example 2> /dev/null
assert_failure assert_failure
@ -29,8 +22,8 @@ copy_src_pyenvd() {
@test "pip-rehash triggered when using 'pip3'" { @test "pip-rehash triggered when using 'pip3'" {
export PYENV_VERSION="3.7.14" export PYENV_VERSION="3.7.14"
create_executable "example" create_alt_executable "example"
create_executable "pip3" create_alt_executable "pip3"
copy_src_pyenvd copy_src_pyenvd
run command -v example 2> /dev/null run command -v example 2> /dev/null
assert_failure assert_failure
@ -42,8 +35,8 @@ copy_src_pyenvd() {
@test "pip-rehash triggered when using 'pip3.x'" { @test "pip-rehash triggered when using 'pip3.x'" {
export PYENV_VERSION="3.7.14" export PYENV_VERSION="3.7.14"
create_executable "example" create_alt_executable "example"
create_executable "pip3.7" create_alt_executable "pip3.7"
copy_src_pyenvd copy_src_pyenvd
run command -v example 2> /dev/null run command -v example 2> /dev/null
assert_failure assert_failure
@ -55,8 +48,8 @@ copy_src_pyenvd() {
@test "pip-rehash triggered when using 'python -m pip install'" { @test "pip-rehash triggered when using 'python -m pip install'" {
export PYENV_VERSION="3.7.14" export PYENV_VERSION="3.7.14"
create_executable "example" create_alt_executable "example"
create_executable "python" create_alt_executable "python"
copy_src_pyenvd copy_src_pyenvd
run command -v example 2> /dev/null run command -v example 2> /dev/null
assert_failure assert_failure

View File

@ -2,13 +2,6 @@
load test_helper load test_helper
create_executable() {
local bin="${PYENV_ROOT}/versions/${1}/bin"
mkdir -p "$bin"
touch "${bin}/$2"
chmod +x "${bin}/$2"
}
@test "empty rehash" { @test "empty rehash" {
assert [ ! -d "${PYENV_ROOT}/shims" ] assert [ ! -d "${PYENV_ROOT}/shims" ]
run pyenv-rehash run pyenv-rehash
@ -42,10 +35,10 @@ create_executable() {
} }
@test "creates shims" { @test "creates shims" {
create_executable "2.7" "python" create_alt_executable_in_version "2.7" "python"
create_executable "2.7" "fab" create_alt_executable_in_version "2.7" "fab"
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
assert [ ! -e "${PYENV_ROOT}/shims/fab" ] assert [ ! -e "${PYENV_ROOT}/shims/fab" ]
assert [ ! -e "${PYENV_ROOT}/shims/python" ] assert [ ! -e "${PYENV_ROOT}/shims/python" ]
@ -68,8 +61,8 @@ OUT
touch "${PYENV_ROOT}/shims/oldshim1" touch "${PYENV_ROOT}/shims/oldshim1"
chmod +x "${PYENV_ROOT}/shims/oldshim1" chmod +x "${PYENV_ROOT}/shims/oldshim1"
create_executable "3.4" "fab" create_alt_executable_in_version "3.4" "fab"
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
run pyenv-rehash run pyenv-rehash
assert_success "" assert_success ""
@ -78,8 +71,8 @@ OUT
} }
@test "binary install locations containing spaces" { @test "binary install locations containing spaces" {
create_executable "dirname1 p247" "python" create_alt_executable_in_version "dirname1 p247" "python"
create_executable "dirname2 preview1" "py.test" create_alt_executable_in_version "dirname2 preview1" "py.test"
assert [ ! -e "${PYENV_ROOT}/shims/python" ] assert [ ! -e "${PYENV_ROOT}/shims/python" ]
assert [ ! -e "${PYENV_ROOT}/shims/py.test" ] assert [ ! -e "${PYENV_ROOT}/shims/py.test" ]
@ -108,29 +101,54 @@ SH
} }
@test "sh-rehash in bash" { @test "sh-rehash in bash" {
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
PYENV_SHELL=bash run pyenv-sh-rehash PYENV_SHELL=bash run pyenv-sh-rehash
assert_success "command pyenv rehash assert_success "command pyenv rehash
hash -r 2>/dev/null || true" hash -r 2>/dev/null || true"
} }
@test "sh-rehash in bash (integration)" { @test "sh-rehash in bash (integration)" {
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
run eval "$(pyenv-sh-rehash)" run eval "$(pyenv-sh-rehash)"
assert_success assert_success
assert [ -x "${PYENV_ROOT}/shims/python" ] assert [ -x "${PYENV_ROOT}/shims/python" ]
} }
@test "sh-rehash in fish" { @test "sh-rehash in fish" {
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
PYENV_SHELL=fish run pyenv-sh-rehash PYENV_SHELL=fish run pyenv-sh-rehash
assert_success "command pyenv rehash" assert_success "command pyenv rehash"
} }
@test "sh-rehash in fish (integration)" { @test "sh-rehash in fish (integration)" {
command -v fish >/dev/null || skip "-- fish not installed" command -v fish >/dev/null || skip "-- fish not installed"
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
run fish -Nc "eval (pyenv-sh-rehash)" run fish -Nc "eval (pyenv-sh-rehash)"
assert_success assert_success
assert [ -x "${PYENV_ROOT}/shims/python" ] assert [ -x "${PYENV_ROOT}/shims/python" ]
} }
@test "shim sets _PYENV_SHIM_PATH when linked from elsewhere" {
export PYENV_VERSION="custom"
create_alt_executable python3
#must stub pyenv before rehash 'cuz the path is hardcoded into shims
create_stub pyenv <<!
[[ \$1 == 'exec' ]] && \
echo _PYENV_SHIM_PATH="\$_PYENV_SHIM_PATH"
!
pyenv-rehash
mkdir -p "${PYENV_TEST_DIR}/alt-shim"
ln -s "${PYENV_ROOT}/shims/python3" "${PYENV_TEST_DIR}/alt-shim/python3"
run "${PYENV_TEST_DIR}/alt-shim/python3"
assert_success
assert_output <<!
_PYENV_SHIM_PATH=${PYENV_TEST_DIR}/alt-shim
!
run python3
assert_success
assert_output <<!
_PYENV_SHIM_PATH=
!
}

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bats
load test_helper
@test "shims linked from elsewhere are chained, other programs at the same paths are unaffected (integration)" {
bats_require_minimum_version 1.5.0
progname=shimmed_program
called_progname=another_program
create_alt_executable_in_version "custom" "$progname" <<!
echo "called from \$0"
pyenv-exec "$called_progname"
!
pyenv-rehash
PATH="$(path_without "$progname" "$called_progname")"
for disguised_shim_path in "$BATS_TEST_TMPDIR/"{weird-location,even/weirder/location}; do
mkdir -p "$disguised_shim_path"
ln -s "${PYENV_ROOT}/shims/$progname" "$disguised_shim_path/$progname"
PATH="$PATH:$disguised_shim_path"
done
create_executable "$disguised_shim_path" "$called_progname" <<!
echo "convoluted call success!"
!
real_path="$BATS_TEST_TMPDIR/real-location"
mkdir -p "$real_path"
ln -s "${PYENV_ROOT}/versions/custom/bin/$progname" "$real_path/$progname"
PATH="$PATH:$real_path"
run "$progname"
assert_success
assert_output <<!
called from $real_path/$progname
convoluted call success!
!
rm "$real_path/$progname"
run -127 "$progname"
assert_failure
assert_line 0 "pyenv: shimmed_program: command not found"
}

View File

@ -2,6 +2,7 @@ unset PYENV_VERSION
unset PYENV_DIR unset PYENV_DIR
setup() { setup() {
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
if ! enable -f "${BATS_TEST_DIRNAME}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then if ! enable -f "${BATS_TEST_DIRNAME}"/../libexec/pyenv-realpath.dylib realpath 2>/dev/null; then
if [ -n "$PYENV_NATIVE_EXT" ]; then if [ -n "$PYENV_NATIVE_EXT" ]; then
echo "pyenv: failed to load \`realpath' builtin" >&2 echo "pyenv: failed to load \`realpath' builtin" >&2
@ -27,7 +28,7 @@ setup() {
PATH="${BATS_TEST_DIRNAME%/*}/libexec:$PATH" PATH="${BATS_TEST_DIRNAME%/*}/libexec:$PATH"
PATH="${BATS_TEST_DIRNAME}/libexec:$PATH" PATH="${BATS_TEST_DIRNAME}/libexec:$PATH"
PATH="${PYENV_ROOT}/shims:$PATH" PATH="${PYENV_ROOT}/shims:$PATH"
export PATH PATH="${BATS_TEST_TMPDIR}/stubs:$PATH"
for xdg_var in `env 2>/dev/null | grep ^XDG_ | cut -d= -f1`; do unset "$xdg_var"; done for xdg_var in `env 2>/dev/null | grep ^XDG_ | cut -d= -f1`; do unset "$xdg_var"; done
unset xdg_var unset xdg_var
@ -127,7 +128,7 @@ path_without() {
if [ "$found" != "${PYENV_ROOT}/shims" ]; then if [ "$found" != "${PYENV_ROOT}/shims" ]; then
alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')" alt="${PYENV_TEST_DIR}/$(echo "${found#/}" | tr '/' '-')"
mkdir -p "$alt" mkdir -p "$alt"
for util in bash head cut readlink greadlink; do for util in bash head cut readlink greadlink tr sed; do
if [ -x "${found}/$util" ]; then if [ -x "${found}/$util" ]; then
ln -s "${found}/$util" "${alt}/$util" ln -s "${found}/$util" "${alt}/$util"
fi fi
@ -141,6 +142,43 @@ path_without() {
echo "$path" echo "$path"
} }
create_path_executable() {
create_executable "${PYENV_TEST_DIR}/bin" "$@"
}
create_alt_executable() {
create_alt_executable_in_version "${PYENV_VERSION}" "$@"
}
create_alt_executable_in_version() {
local version="${1:?}"
shift 1
create_executable "${PYENV_ROOT}/versions/$version/bin" "$@"
}
create_stub() {
create_executable "${BATS_TEST_TMPDIR}/stubs" "$@"
}
create_executable() {
local bin="${1:?}"
local name="${2:?}"
shift 2
mkdir -p "$bin"
local payload
# Bats doesn't redirect stdin
if [[ $# -eq 0 && ! -t 0 ]]; then
payload="$(cat -)"
else
payload="$(printf '%s\n' "$@")"
fi
if [[ $payload != "#!/"* ]]; then
payload="#!$BASH"$'\n'"$payload"
fi
echo "$payload" > "${bin}/$name"
chmod +x "${bin}/$name"
}
create_hook() { create_hook() {
mkdir -p "${PYENV_HOOK_PATH}/$1" mkdir -p "${PYENV_HOOK_PATH}/$1"
touch "${PYENV_HOOK_PATH}/$1/$2" touch "${PYENV_HOOK_PATH}/$1/$2"

View File

@ -22,14 +22,6 @@ stub_system_python() {
touch "$stub" && chmod +x "$stub" touch "$stub" && chmod +x "$stub"
} }
create_executable() {
local name="$1"
local bin="${PYENV_TEST_DIR}/bin"
mkdir -p "$bin"
sed -Ee '1s/^ +//' > "${bin}/$name"
chmod +x "${bin}/$name"
}
@test "no versions installed" { @test "no versions installed" {
stub_system_python stub_system_python
assert [ ! -d "${PYENV_ROOT}/versions" ] assert [ ! -d "${PYENV_ROOT}/versions" ]
@ -199,7 +191,7 @@ OUT
create_version "1.9.0" create_version "1.9.0"
create_version "1.53.0" create_version "1.53.0"
create_version "1.218.0" create_version "1.218.0"
create_executable sort <<SH create_path_executable sort <<SH
#!$BASH #!$BASH
cat >/dev/null cat >/dev/null
if [ "\$1" == "--version-sort" ]; then if [ "\$1" == "--version-sort" ]; then
@ -223,7 +215,7 @@ OUT
create_version "1.9.0" create_version "1.9.0"
create_version "1.53.0" create_version "1.53.0"
create_version "1.218.0" create_version "1.218.0"
create_executable sort <<SH create_path_executable sort <<SH
#!$BASH #!$BASH
exit 1 exit 1
SH SH

View File

@ -2,18 +2,11 @@
load test_helper load test_helper
create_executable() {
local bin="${PYENV_ROOT}/versions/${1}/bin"
mkdir -p "$bin"
touch "${bin}/$2"
chmod +x "${bin}/$2"
}
@test "finds versions where present" { @test "finds versions where present" {
create_executable "2.7" "python" create_alt_executable_in_version "2.7" "python"
create_executable "2.7" "fab" create_alt_executable_in_version "2.7" "fab"
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
run pyenv-whence python run pyenv-whence python
assert_success assert_success

View File

@ -2,19 +2,9 @@
load test_helper load test_helper
create_executable() {
local bin
if [[ $1 == */* ]]; then bin="$1"
else bin="${PYENV_ROOT}/versions/${1}/bin"
fi
mkdir -p "$bin"
touch "${bin}/$2"
chmod +x "${bin}/$2"
}
@test "outputs path to executable" { @test "outputs path to executable" {
create_executable "2.7" "python" create_alt_executable_in_version "2.7" "python"
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
PYENV_VERSION=2.7 run pyenv-which python PYENV_VERSION=2.7 run pyenv-which python
assert_success "${PYENV_ROOT}/versions/2.7/bin/python" assert_success "${PYENV_ROOT}/versions/2.7/bin/python"
@ -27,7 +17,7 @@ create_executable() {
} }
@test "searches PATH for system version" { @test "searches PATH for system version" {
create_executable "${PYENV_TEST_DIR}/bin" "kill-all-humans" create_path_executable "kill-all-humans"
create_executable "${PYENV_ROOT}/shims" "kill-all-humans" create_executable "${PYENV_ROOT}/shims" "kill-all-humans"
PYENV_VERSION=system run pyenv-which kill-all-humans PYENV_VERSION=system run pyenv-which kill-all-humans
@ -35,7 +25,7 @@ create_executable() {
} }
@test "searches PATH for system version (shims prepended)" { @test "searches PATH for system version (shims prepended)" {
create_executable "${PYENV_TEST_DIR}/bin" "kill-all-humans" create_path_executable "kill-all-humans"
create_executable "${PYENV_ROOT}/shims" "kill-all-humans" create_executable "${PYENV_ROOT}/shims" "kill-all-humans"
PATH="${PYENV_ROOT}/shims:$PATH" PYENV_VERSION=system run pyenv-which kill-all-humans PATH="${PYENV_ROOT}/shims:$PATH" PYENV_VERSION=system run pyenv-which kill-all-humans
@ -43,7 +33,7 @@ create_executable() {
} }
@test "searches PATH for system version (shims appended)" { @test "searches PATH for system version (shims appended)" {
create_executable "${PYENV_TEST_DIR}/bin" "kill-all-humans" create_path_executable "kill-all-humans"
create_executable "${PYENV_ROOT}/shims" "kill-all-humans" create_executable "${PYENV_ROOT}/shims" "kill-all-humans"
PATH="$PATH:${PYENV_ROOT}/shims" PYENV_VERSION=system run pyenv-which kill-all-humans PATH="$PATH:${PYENV_ROOT}/shims" PYENV_VERSION=system run pyenv-which kill-all-humans
@ -51,7 +41,7 @@ create_executable() {
} }
@test "searches PATH for system version (shims spread)" { @test "searches PATH for system version (shims spread)" {
create_executable "${PYENV_TEST_DIR}/bin" "kill-all-humans" create_path_executable "kill-all-humans"
create_executable "${PYENV_ROOT}/shims" "kill-all-humans" create_executable "${PYENV_ROOT}/shims" "kill-all-humans"
PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/shims:/tmp/non-existent:$PATH:${PYENV_ROOT}/shims" \ PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/shims:/tmp/non-existent:$PATH:${PYENV_ROOT}/shims" \
@ -61,17 +51,15 @@ create_executable() {
@test "doesn't include current directory in PATH search" { @test "doesn't include current directory in PATH search" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
mkdir -p "$PYENV_TEST_DIR" create_executable "$PYENV_TEST_DIR" kill-all-humans
cd "$PYENV_TEST_DIR" cd "$PYENV_TEST_DIR"
touch kill-all-humans
chmod +x kill-all-humans
PATH="$(path_without "kill-all-humans")" PYENV_VERSION=system run -127 pyenv-which kill-all-humans PATH="$(path_without "kill-all-humans")" PYENV_VERSION=system run -127 pyenv-which kill-all-humans
assert_failure "pyenv: kill-all-humans: command not found" assert_failure "pyenv: kill-all-humans: command not found"
} }
@test "version not installed" { @test "version not installed" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
PYENV_VERSION=3.3 run -127 pyenv-which py.test PYENV_VERSION=3.3 run -127 pyenv-which py.test
assert_failure <<OUT assert_failure <<OUT
pyenv: version \`3.3' is not installed (set by PYENV_VERSION environment variable) pyenv: version \`3.3' is not installed (set by PYENV_VERSION environment variable)
@ -87,7 +75,7 @@ OUT
@test "versions not installed" { @test "versions not installed" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
PYENV_VERSION=2.7:3.3 run -127 pyenv-which py.test PYENV_VERSION=2.7:3.3 run -127 pyenv-which py.test
assert_failure <<OUT assert_failure <<OUT
pyenv: version \`2.7' is not installed (set by PYENV_VERSION environment variable) pyenv: version \`2.7' is not installed (set by PYENV_VERSION environment variable)
@ -104,7 +92,7 @@ OUT
@test "no executable found" { @test "no executable found" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
create_executable "2.7" "py.test" create_alt_executable_in_version "2.7" "py.test"
PYENV_VERSION=2.7 run -127 pyenv-which fab PYENV_VERSION=2.7 run -127 pyenv-which fab
assert_failure "pyenv: fab: command not found" assert_failure "pyenv: fab: command not found"
} }
@ -117,9 +105,9 @@ OUT
@test "executable found in other versions" { @test "executable found in other versions" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
create_executable "2.7" "python" create_alt_executable_in_version "2.7" "python"
create_executable "3.3" "py.test" create_alt_executable_in_version "3.3" "py.test"
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
PYENV_VERSION=2.7 run -127 pyenv-which py.test PYENV_VERSION=2.7 run -127 pyenv-which py.test
assert_failure assert_failure
@ -150,7 +138,7 @@ SH
@test "discovers version from pyenv-version-name" { @test "discovers version from pyenv-version-name" {
mkdir -p "$PYENV_ROOT" mkdir -p "$PYENV_ROOT"
cat > "${PYENV_ROOT}/version" <<<"3.4" cat > "${PYENV_ROOT}/version" <<<"3.4"
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
mkdir -p "$PYENV_TEST_DIR" mkdir -p "$PYENV_TEST_DIR"
cd "$PYENV_TEST_DIR" cd "$PYENV_TEST_DIR"
@ -165,7 +153,7 @@ SH
2.7 2.7
3.4 3.4
EOF EOF
create_executable "3.4" "python" create_alt_executable_in_version "3.4" "python"
mkdir -p "$PYENV_TEST_DIR" mkdir -p "$PYENV_TEST_DIR"
cd "$PYENV_TEST_DIR" cd "$PYENV_TEST_DIR"
@ -175,7 +163,7 @@ EOF
} }
@test "resolves pyenv-latest prefixes" { @test "resolves pyenv-latest prefixes" {
create_executable "3.4.2" "python" create_alt_executable_in_version "3.4.2" "python"
PYENV_VERSION=3.4 run pyenv-which python PYENV_VERSION=3.4 run pyenv-which python
assert_success "${PYENV_ROOT}/versions/3.4.2/bin/python" assert_success "${PYENV_ROOT}/versions/3.4.2/bin/python"
@ -187,7 +175,7 @@ echo version=\$version
exit exit
! !
create_executable "3.4.2" "python" create_alt_executable_in_version "3.4.2" "python"
PYENV_VERSION=3.4 run pyenv-which python PYENV_VERSION=3.4 run pyenv-which python
assert_success "version=3.4.2" assert_success "version=3.4.2"
@ -195,9 +183,9 @@ exit
@test "skip advice supresses error messages" { @test "skip advice supresses error messages" {
bats_require_minimum_version 1.5.0 bats_require_minimum_version 1.5.0
create_executable "2.7" "python" create_alt_executable_in_version "2.7" "python"
create_executable "3.3" "py.test" create_alt_executable_in_version "3.3" "py.test"
create_executable "3.4" "py.test" create_alt_executable_in_version "3.4" "py.test"
PYENV_VERSION=2.7 run -127 pyenv-which py.test --skip-advice PYENV_VERSION=2.7 run -127 pyenv-which py.test --skip-advice
assert_failure assert_failure
@ -205,3 +193,29 @@ exit
pyenv: py.test: command not found pyenv: py.test: command not found
OUT OUT
} }
@test "excludes paths in _PYENV_SHIM_PATHS_{PROGRAM} from search only for PROGRAM" {
progname='123;wacky-prog.name ^%$#'
envvarname="_PYENV_SHIM_PATHS_123_WACKY_PROG_NAME_____"
create_path_executable "$progname"
for dir_ in "$BATS_TEST_TMPDIR/alt-path"{1,2}; do
mkdir -p "$dir_"
ln -s "${PYENV_TEST_DIR}/bin/$progname" "$dir_/$progname"
eval 'export '"$envvarname"'="$dir_${'"$envvarname"':+:$'"$envvarname"'}"'
PATH="$dir_:$PATH"
done
create_executable "$dir_" "normal_program"
run pyenv-which "$progname"
assert_success
assert_output <<!
$PYENV_TEST_DIR/bin/$progname
!
run pyenv-which "normal_program"
assert_success
assert_output <<!
$dir_/normal_program
!
}