diff --git a/.github/workflows/studio-mac-update-smoke.yml b/.github/workflows/studio-mac-update-smoke.yml index 07d26b9ab3..cfa192b470 100644 --- a/.github/workflows/studio-mac-update-smoke.yml +++ b/.github/workflows/studio-mac-update-smoke.yml @@ -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 diff --git a/.github/workflows/studio-update-smoke.yml b/.github/workflows/studio-update-smoke.yml index 1c353e933a..b28e2bf0bd 100644 --- a/.github/workflows/studio-update-smoke.yml +++ b/.github/workflows/studio-update-smoke.yml @@ -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 diff --git a/.github/workflows/studio-windows-update-smoke.yml b/.github/workflows/studio-windows-update-smoke.yml index 157874d404..b412d60921 100644 --- a/.github/workflows/studio-windows-update-smoke.yml +++ b/.github/workflows/studio-windows-update-smoke.yml @@ -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