* Installer: restore interrupted updates and clean stale rollback environments * CI: run POSIX rollback lifecycle tests on Linux
142 lines
5.9 KiB
Bash
142 lines
5.9 KiB
Bash
#!/bin/bash
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
# Tests for the torch-trio --overrides guard on the Step-2 unsloth installs in
|
|
# install.sh. A released unsloth wheel can pin an older torch (2026.7.2 declares
|
|
# torch<2.11.0); without the overrides file a with-deps PyPI resolve downgrades
|
|
# the trio Step 1 installed, and the flavor guard misses it (PyPI's torch 2.10
|
|
# default is itself cu128-flavored). Same assertion pattern as test_torch_constraint.sh.
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
INSTALL_SH="$SCRIPT_DIR/../../install.sh"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
assert_true() {
|
|
_label="$1"; _ok="$2"
|
|
if [ "$_ok" = "0" ]; then
|
|
echo " PASS: $_label"
|
|
PASS=$((PASS + 1))
|
|
else
|
|
echo " FAIL: $_label"
|
|
FAIL=$((FAIL + 1))
|
|
fi
|
|
}
|
|
|
|
echo "=== test_unsloth_torch_override ==="
|
|
|
|
# 1. Every with-deps unsloth install carries the overrides expansion (local,
|
|
# generic, migrated); the --no-deps no-torch paths need no guard.
|
|
_local_block=$(grep -A2 '"install unsloth (local)"' "$INSTALL_SH")
|
|
printf '%s' "$_local_block" | grep -q -- '--overrides "\$_UNSLOTH_TORCH_OVERRIDES"'
|
|
assert_true "local (with-deps) unsloth install passes --overrides" "$?"
|
|
|
|
_generic_block=$(grep -A2 '"install unsloth" uv pip install' "$INSTALL_SH")
|
|
printf '%s' "$_generic_block" | grep -q -- '--overrides "\$_UNSLOTH_TORCH_OVERRIDES"'
|
|
assert_true "generic (with-deps) unsloth install passes --overrides" "$?"
|
|
|
|
_migrated_block=$(grep -A3 '"install unsloth (migrated)"' "$INSTALL_SH")
|
|
printf '%s' "$_migrated_block" | grep -q -- '--overrides "\$_UNSLOTH_TORCH_OVERRIDES"'
|
|
assert_true "migrated (with-deps) unsloth install passes --overrides" "$?"
|
|
|
|
_no_torch_block=$(grep -A2 '"install unsloth (no-torch)"' "$INSTALL_SH")
|
|
if printf '%s' "$_no_torch_block" | grep -q -- '--overrides'; then _rc=1; else _rc=0; fi
|
|
assert_true "no-torch (--no-deps) unsloth install has no overrides" "$_rc"
|
|
|
|
_migrated_nt_block=$(grep -A2 '"install unsloth (migrated no-torch)"' "$INSTALL_SH")
|
|
if printf '%s' "$_migrated_nt_block" | grep -q -- '--overrides'; then _rc=1; else _rc=0; fi
|
|
assert_true "migrated no-torch (--no-deps) unsloth install has no overrides" "$_rc"
|
|
|
|
# 2. The overrides file is only built when SKIP_TORCH=false.
|
|
grep -B2 '_torch_trio_pins=\$(' "$INSTALL_SH" | grep -q 'SKIP_TORCH" = false'
|
|
assert_true "overrides file build is gated on SKIP_TORCH=false" "$?"
|
|
|
|
# 3. The pin-collection snippet emits exact ==pins for the installed trio (run
|
|
# the embedded python against this test's interpreter).
|
|
_snippet=$(sed -n '/_torch_trio_pins=\$("\$_VENV_PY" -c "/,/^" 2>\/dev\/null)/p' "$INSTALL_SH" \
|
|
| sed '1s/.*-c "//' | sed '$d')
|
|
_out=$(python3 -c "$_snippet" 2>&1) || true
|
|
# torch may or may not be importable on the test host; the snippet must not
|
|
# crash and every line it does emit must be an exact pkg==version pin.
|
|
if [ -n "$_out" ]; then
|
|
printf '%s\n' "$_out" | grep -vqE '^(torch|torchvision|torchaudio)==.+$' && _rc=1 || _rc=0
|
|
else
|
|
_rc=0
|
|
fi
|
|
assert_true "pin snippet emits only exact trio ==pins (or nothing)" "$_rc"
|
|
|
|
# 4. The temp overrides file is cleaned up after Step 2.
|
|
grep -q 'rm -f "\$_UNSLOTH_TORCH_OVERRIDES"' "$INSTALL_SH"
|
|
assert_true "overrides temp file is removed after the unsloth installs" "$?"
|
|
|
|
# 5. Any UV_OVERRIDE env file is folded in (the CLI --overrides flag would
|
|
# otherwise replace it, dropping e.g. the macOS arm64 darwin overrides).
|
|
grep -q 'for _ov_file in \${UV_OVERRIDE:-}' "$INSTALL_SH"
|
|
assert_true "UV_OVERRIDE env files are merged into the overrides file" "$?"
|
|
|
|
# 6. Exit and signal traps share cleanup, so a failed or interrupted Step 2
|
|
# cannot leak the overrides file.
|
|
sed -n '/_on_install_exit() {/,/^}/p' "$INSTALL_SH" | grep -q '_cleanup_install_temporaries'
|
|
_exit_cleanup_rc=$?
|
|
sed -n '/_on_install_signal() {/,/^}/p' "$INSTALL_SH" | grep -q '_cleanup_install_temporaries'
|
|
_signal_cleanup_rc=$?
|
|
sed -n '/_cleanup_install_temporaries() {/,/^}/p' "$INSTALL_SH" \
|
|
| grep -q 'rm -f "\$_UNSLOTH_TORCH_OVERRIDES"'
|
|
_cleanup_body_rc=$?
|
|
if [ "$_exit_cleanup_rc" -eq 0 ] && [ "$_signal_cleanup_rc" -eq 0 ] \
|
|
&& [ "$_cleanup_body_rc" -eq 0 ]; then
|
|
_rc=0
|
|
else
|
|
_rc=1
|
|
fi
|
|
assert_true "exit and signal traps remove the overrides temp file" "$_rc"
|
|
|
|
# 7. The UV_OVERRIDE fold filters inherited files instead of cat-ing them (run
|
|
# the extracted awk program on sample files): (a) inherited torch-trio lines
|
|
# are dropped so the generated exact pins win (uv intersects duplicates);
|
|
# (b) every line is newline-terminated so an unterminated file cannot join
|
|
# two requirements into one.
|
|
_awk_prog=$(sed -n "s/.*awk '\(.*\)' \"\$_ov_file\".*/\1/p" "$INSTALL_SH")
|
|
[ -n "$_awk_prog" ]
|
|
assert_true "UV_OVERRIDE fold uses the trio-filtering awk program" "$?"
|
|
|
|
_ov_dir=$(mktemp -d)
|
|
printf '%s' 'transformers>=4.57.6' > "$_ov_dir/ov1.txt" # no trailing newline
|
|
cat > "$_ov_dir/ov2.txt" <<'EOF'
|
|
# comment survives
|
|
torch<2.11.0
|
|
torchvision==0.25.0
|
|
torchaudio!=2.11.0
|
|
torchmetrics==1.0
|
|
anyio<4.14.0
|
|
EOF
|
|
_merged="$_ov_dir/merged.txt"
|
|
printf '%s\n' 'torch==2.11.0+cu128' > "$_merged"
|
|
for _f in "$_ov_dir/ov1.txt" "$_ov_dir/ov2.txt"; do
|
|
awk "$_awk_prog" "$_f" >> "$_merged"
|
|
done
|
|
|
|
grep -qx 'transformers>=4.57.6' "$_merged"
|
|
assert_true "no-trailing-newline override stays a separate requirement line" "$?"
|
|
|
|
if grep -qx 'torchmetrics==1.0' "$_merged" && grep -qx 'anyio<4.14.0' "$_merged"; then
|
|
_rc=0
|
|
else
|
|
_rc=1
|
|
fi
|
|
assert_true "unrelated inherited overrides are preserved" "$_rc"
|
|
|
|
if grep -qE '^(torch|torchvision|torchaudio)([[:space:]<>=!~;@[]|$)' "$_merged" \
|
|
&& [ "$(grep -cE '^(torch|torchvision|torchaudio)([[:space:]<>=!~;@[]|$)' "$_merged")" != "1" ]; then
|
|
_rc=1
|
|
else
|
|
_rc=0
|
|
fi
|
|
grep -qx 'torch==2.11.0+cu128' "$_merged" || _rc=1
|
|
assert_true "inherited torch-trio lines are dropped; generated pin wins" "$_rc"
|
|
rm -rf "$_ov_dir"
|
|
|
|
echo ""
|
|
echo "Results: $PASS passed, $FAIL failed"
|
|
[ "$FAIL" -eq 0 ] || exit 1
|