Studio update CI: round-trip install -> update -> uninstall (#5536)

* Studio update CI: round-trip install -> update -> uninstall

Adds an "Uninstall and verify clean" step to the three existing
studio-{,-mac-,-windows-}update-smoke.yml workflows so each one ends by
running uninstall.sh / uninstall.ps1 against the install it just
produced, then asserting that the install dir, launcher data dir,
desktop shortcut, CLI shim (and on Mac, the .app bundle) are all gone.
Two trailing reruns confirm idempotency. The uninstall log is added to
the existing artifact bundle.

Catches regressions where install.sh / install.ps1 starts writing to a
new path (registry key, Start Menu entry, %APPDATA% subdir, etc.) and
uninstall.{sh,ps1} has not been updated to match. Safety-guard
scenarios (refuse-\$HOME, refuse-non-Studio, tilde expansion, etc.) are
intentionally NOT exercised here -- those belong in a dedicated fast
smoke job that does not have to wait on a 5-15 min install.

Wall-clock overhead is ~30-45 s on each runner. Path filters extended
to include uninstall.sh / uninstall.ps1 so a pure uninstaller change
also triggers the round-trip check.

* Skip round-trip step when uninstall.{sh,ps1} are not in tree

---------

Co-authored-by: Daniel Han <info@unsloth.ai>
This commit is contained in:
Daniel Han 2026-05-18 02:11:52 -07:00 committed by GitHub
commit 9b8ee6c773
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 107 additions and 1 deletions

View file

@ -21,6 +21,7 @@ on:
pull_request:
paths:
- 'install.sh'
- 'uninstall.sh'
- 'studio/setup.sh'
- 'studio/install_python_stack.py'
- 'studio/install_llama_prebuilt.py'
@ -137,6 +138,38 @@ jobs:
kill "$PID" 2>/dev/null || true
echo "post-update Studio /api/health OK"
- name: Uninstall and verify clean
# Round-trip through uninstall.sh on real macOS. As a side effect
# this exercises the macOS-only .app bundle + Launch Services
# removal path (~/Applications/Unsloth Studio.app, lsregister -u)
# which is not testable from a Linux runner. Skips gracefully if
# uninstall.sh has not landed yet (lets this workflow merge
# before #5497).
run: |
set -o pipefail
if [ ! -f uninstall.sh ]; then
echo "uninstall.sh not present in this tree; skipping round-trip"
: > logs/uninstall.log
exit 0
fi
sh uninstall.sh 2>&1 | tee logs/uninstall.log
leak=0
for p in \
"$HOME/.unsloth/studio" \
"$HOME/.local/share/unsloth" \
"$HOME/Applications/Unsloth Studio.app" \
"$HOME/Desktop/Unsloth Studio.app" \
"$HOME/.local/bin/unsloth"; do
if [ -e "$p" ] || [ -L "$p" ]; then
echo "::error::leak: $p"
leak=$((leak + 1))
fi
done
[ "$leak" -eq 0 ] || exit 1
sh uninstall.sh 2>&1 | tail -5
sh uninstall.sh 2>&1 | tail -5
echo "PASS: mac install -> update -> uninstall round-trip clean"
- name: Upload update logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@ -147,4 +180,5 @@ jobs:
logs/update.log
logs/update2.log
logs/studio.log
logs/uninstall.log
retention-days: 7

View file

@ -15,6 +15,7 @@ on:
pull_request:
paths:
- 'install.sh'
- 'uninstall.sh'
- 'studio/setup.sh'
- 'studio/install_python_stack.py'
- 'studio/install_llama_prebuilt.py'
@ -139,9 +140,44 @@ jobs:
kill "$PID" 2>/dev/null || true
echo "post-update Studio /api/health OK"
- name: Uninstall and verify clean
# Round-trip the installer through uninstall.sh: confirms the
# uninstaller actually finds and removes everything install.sh +
# update wrote. Safety-guard scenarios (refuse-$HOME etc.) belong
# in a separate fast smoke job; this is the happy-path cleanup
# assertion that catches regressions where install.sh starts
# writing to a new location and uninstall.sh hasn't caught up.
# Skips gracefully if uninstall.sh has not landed yet (lets this
# workflow merge before #5497).
run: |
set -o pipefail
if [ ! -f uninstall.sh ]; then
echo "uninstall.sh not present in this tree; skipping round-trip"
: > logs/uninstall.log
exit 0
fi
sh uninstall.sh 2>&1 | tee logs/uninstall.log
leak=0
for p in \
"$HOME/.unsloth/studio" \
"$HOME/.local/share/unsloth" \
"$HOME/Desktop/Unsloth Studio.desktop" \
"$HOME/.local/bin/unsloth"; do
if [ -e "$p" ] || [ -L "$p" ]; then
echo "::error::leak: $p"
ls -la "$p" 2>&1 | head -3
leak=$((leak + 1))
fi
done
[ "$leak" -eq 0 ] || exit 1
# Idempotent: re-runs exit 0 on an empty $HOME.
sh uninstall.sh 2>&1 | tail -5
sh uninstall.sh 2>&1 | tail -5
echo "PASS: install -> update -> uninstall round-trip clean"
- name: Upload update logs
# Always upload so a green run still leaves the install + two
# update logs reviewable.
# update logs + uninstall log reviewable.
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
@ -151,4 +187,5 @@ jobs:
logs/update.log
logs/update2.log
logs/studio.log
logs/uninstall.log
retention-days: 7

View file

@ -23,6 +23,7 @@ on:
pull_request:
paths:
- 'install.ps1'
- 'uninstall.ps1'
- 'studio/setup.ps1'
- 'studio/setup.bat'
- 'studio/install_python_stack.py'
@ -266,6 +267,39 @@ jobs:
kill "$PID" 2>/dev/null || true
echo "post-update Studio /api/health OK"
- name: Uninstall and verify clean
# Round-trip through uninstall.ps1 against the default install
# tree at %USERPROFILE%\.unsloth\studio. Catches regressions
# where install.ps1 starts writing under a new key (registry,
# Start Menu, %APPDATA%) and uninstall.ps1 has not been updated
# to match. Skips gracefully if uninstall.ps1 has not landed yet
# (lets this workflow merge before #5513).
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path logs | Out-Null
if (-not (Test-Path "$PWD\uninstall.ps1")) {
Write-Host "uninstall.ps1 not present in this tree; skipping round-trip"
"" | Set-Content logs/uninstall.log
exit 0
}
pwsh -NoProfile -File "$PWD\uninstall.ps1" *>&1 | Tee-Object -FilePath logs/uninstall.log
$leak = 0
foreach ($p in @(
"$env:USERPROFILE\.unsloth\studio",
"$env:USERPROFILE\.unsloth\studio\unsloth_studio",
"$env:USERPROFILE\.unsloth\studio\bin\unsloth.exe"
)) {
if (Test-Path -LiteralPath $p) {
Write-Host "::error::leak: $p"
$leak++
}
}
if ($leak -gt 0) { exit 1 }
# Idempotency.
pwsh -NoProfile -File "$PWD\uninstall.ps1" *>&1 | Select-Object -Last 5
pwsh -NoProfile -File "$PWD\uninstall.ps1" *>&1 | Select-Object -Last 5
Write-Host "PASS: windows install -> update -> uninstall round-trip clean"
- name: Upload update logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@ -276,4 +310,5 @@ jobs:
logs/update.log
logs/update2.log
logs/studio.log
logs/uninstall.log
retention-days: 7