From 09c31624f49e9303c596e29fb4b780d7b3a94080 Mon Sep 17 00:00:00 2001 From: macayu17 Date: Fri, 3 Jul 2026 10:46:45 +0530 Subject: [PATCH] pyenv-binary: test dependency parsing and tighten the version guard Add tests that feed realistic ldd and otool listings through save and check only the libraries resolving outside the prefix end up in the metadata, since that filtering is the fiddliest part of the command. Narrow the version guard to reject `.' and `..' rather than any name containing a dot-dot, now that a slash is already refused, and note why the find still matches *.so.* even though CPython does not produce them. --- .../pyenv-binary/libexec/pyenv-binary-save | 9 ++- plugins/pyenv-binary/test/save.bats | 72 ++++++++++++++++++- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/plugins/pyenv-binary/libexec/pyenv-binary-save b/plugins/pyenv-binary/libexec/pyenv-binary-save index fb818fcf..e4273681 100755 --- a/plugins/pyenv-binary/libexec/pyenv-binary-save +++ b/plugins/pyenv-binary/libexec/pyenv-binary-save @@ -27,10 +27,10 @@ if [ -z "$version" ]; then exit 1 fi -# A version is a single directory name under versions/. Reject a name with a -# slash or a dot-dot component so it cannot point outside there. +# A version is a single directory name under versions/. With no slash allowed, +# the only remaining names that could point elsewhere are `.' and `..'. case "$version" in -*/* | *..* ) +*/* | .. | . ) echo "pyenv-binary: invalid version name \`${version}'" >&2 exit 1 ;; @@ -69,6 +69,9 @@ system_deps() { for f in "${prefix}"/bin/python*; do [ -e "$f" ] && printf '%s\n' "$f" done + # CPython ships its extension modules as *.so (and *.dylib on macOS). A + # bare *.so.* is unusual for CPython itself, but the odd build carries a + # versioned copy alongside, so match it too rather than miss a dependency. find "${prefix}" -type f \( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \) } | sort -u | while IFS= read -r f; do if [ "$os" = "Darwin" ]; then diff --git a/plugins/pyenv-binary/test/save.bats b/plugins/pyenv-binary/test/save.bats index b2986d8e..1e1188ef 100644 --- a/plugins/pyenv-binary/test/save.bats +++ b/plugins/pyenv-binary/test/save.bats @@ -25,9 +25,9 @@ platform() { assert_failure "pyenv-binary: invalid version name \`foo/bar'" } -@test "rejects a version name that walks out of versions/" { - run pyenv-binary-save "../../etc" - assert_failure "pyenv-binary: invalid version name \`../../etc'" +@test "rejects the parent directory reference" { + run pyenv-binary-save ".." + assert_failure "pyenv-binary: invalid version name \`..'" } @test "packages an installed version" { @@ -49,3 +49,69 @@ platform() { assert_output_contains "platform=$(platform)" assert_output_contains "archive=3.12.7-$(platform).tar.gz" } + +# Put an executable on PATH that stands in for a system tool during the test. +stub() { + local name="$1" + local dir="${BATS_TEST_TMPDIR}/stubs" + mkdir -p "$dir" + { echo "#!/usr/bin/env bash"; cat -; } > "${dir}/${name}" + chmod +x "${dir}/${name}" + export PATH="${dir}:$PATH" +} + +@test "records only the libraries ldd resolves outside the prefix" { + create_version "3.12.7" + touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12" + + # A realistic ldd listing: the vdso and the loader have no `=>' mapping, + # libpython resolves inside the prefix, and libc/libm are external. + stub ldd <<'STUB' +prefix="${PYENV_ROOT}/versions/3.12.7" +cat < ${prefix}/lib/libpython3.12.so.1.0 (0x00007f4a3c000000) + libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4a3bc00000) + libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a3b800000) + /lib64/ld-linux-x86-64.so.2 (0x00007f4a3c200000) +EOF +STUB + + run pyenv-binary-save "3.12.7" "${BATS_TEST_TMPDIR}/dist" + assert_success + + run grep '^dep=' "${BATS_TEST_TMPDIR}/dist/"*.meta + assert_output "dep=libc.so.6 +dep=libm.so.6" +} + +@test "records only the libraries otool resolves outside the prefix" { + create_version "3.12.7" + touch "${PYENV_ROOT}/versions/3.12.7/bin/python3.12" + + # Take the macOS path by faking the platform, then feed a realistic otool + # listing: the first line names the file, @rpath and in-prefix entries are + # bundled, and libSystem is the one external dependency. + stub uname <<'STUB' +case "$1" in + -s) echo Darwin ;; + -m) echo arm64 ;; + *) exec /usr/bin/uname "$@" ;; +esac +STUB + stub otool <<'STUB' +prefix="${PYENV_ROOT}/versions/3.12.7" +cat <