Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/repo-guards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
run: python -m pip install pyqt6 requests flask pyinstaller
- name: Run test suites on Windows
run: python tests/run_all.py
- name: Test stale-binary detector (check_stale.ps1)
shell: pwsh
run: ./tests/test_check_stale.ps1
- name: Build hyperwall.exe (no mpv DLL needed for a build-only smoke test)
shell: pwsh
run: |
Expand Down
18 changes: 12 additions & 6 deletions launch.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ if not exist "hyperwall.exe" (
exit /b %errorlevel%
)

:: Stale binary check: if exe is older than hyperwall.py, warn
for %%F in (hyperwall.exe) do set EXE_TS=%%~tF
for %%F in (hyperwall.py) do set SRC_TS=%%~tF
echo === Hyperwall ===

:: Simple check: if hyperwall.py is newer than today, assume it changed
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set NOW=%%I
:: Stale-binary check: warn (don't block) if hyperwall.py is newer than the exe.
:: Delegated to scripts\check_stale.ps1 (exit 2 = stale) so the comparison is
:: real and unit-tested in CI, rather than computing timestamps and ignoring them.
if exist "scripts\check_stale.ps1" (
powershell -NoProfile -ExecutionPolicy Bypass -File "scripts\check_stale.ps1" ^
-Source "hyperwall.py" -Exe "hyperwall.exe"
if errorlevel 2 (
echo Continuing with the existing exe. Press Ctrl+C to abort and rebuild.
timeout /t 3 >nul
)
)

echo === Hyperwall ===
echo Launching hyperwall.exe...
echo.

Expand Down
41 changes: 41 additions & 0 deletions scripts/check_stale.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<#
check_stale.ps1 — Hyperwall stale-binary detector.

Compares the last-write time of the source entry point against the built exe.
Used by launch.bat so an out-of-date exe (source edited, forgot to rebuild)
is flagged instead of silently launched.

Exit codes (so callers can branch):
0 = exe is current (or exe/source missing → nothing to warn about here;
launch.bat handles the missing-exe case on its own)
2 = exe is STALE (source is newer than exe) — a warning is printed

Params let the tests point at fixture files.
#>
[CmdletBinding()]
param(
[string]$Source = "hyperwall.py",
[string]$Exe = "hyperwall.exe"
)

if (-not (Test-Path -LiteralPath $Exe)) {
# No exe to compare — not this script's job to complain.
exit 0
}
if (-not (Test-Path -LiteralPath $Source)) {
# No source next to the exe (e.g. a frozen-only deployment) — can't compare.
exit 0
}

$srcTime = (Get-Item -LiteralPath $Source).LastWriteTimeUtc
$exeTime = (Get-Item -LiteralPath $Exe).LastWriteTimeUtc

if ($srcTime -gt $exeTime) {
$msg = "hyperwall.exe is STALE: $Source (modified $($srcTime.ToString('u'))) " +
"is newer than the exe (built $($exeTime.ToString('u'))). " +
"Rebuild with build.bat to pick up your latest changes."
Write-Warning $msg
exit 2
}

exit 0
61 changes: 61 additions & 0 deletions tests/test_check_stale.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<#
test_check_stale.ps1 — verifies scripts/check_stale.ps1 exit codes.

No Pester dependency: creates temp fixture files with controlled write times
and asserts the detector's exit code. Run on the Windows CI job.

Exit 0 = all cases pass; exit 1 = a failure.
#>
$ErrorActionPreference = "Stop"
$script = Join-Path $PSScriptRoot "..\scripts\check_stale.ps1"
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("hwstale_" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $tmp | Out-Null

$fails = 0
function Check($name, $expected, $actual) {
if ($expected -eq $actual) {
Write-Host " PASS $name"
} else {
Write-Host " FAIL $name (expected exit $expected, got $actual)"
$script:fails++
}
}

try {
$src = Join-Path $tmp "hyperwall.py"
$exe = Join-Path $tmp "hyperwall.exe"

# Case 1: exe newer than source → current → exit 0
Set-Content -LiteralPath $src -Value "src"
Start-Sleep -Milliseconds 50
Set-Content -LiteralPath $exe -Value "exe"
& powershell -NoProfile -ExecutionPolicy Bypass -File $script -Source $src -Exe $exe
Check "fresh_exe_exit0" 0 $LASTEXITCODE

# Case 2: source newer than exe → stale → exit 2
Start-Sleep -Milliseconds 50
Set-Content -LiteralPath $src -Value "src-edited"
& powershell -NoProfile -ExecutionPolicy Bypass -File $script -Source $src -Exe $exe
Check "stale_exe_exit2" 2 $LASTEXITCODE

# Case 3: exe missing → nothing to warn → exit 0
Remove-Item -LiteralPath $exe
& powershell -NoProfile -ExecutionPolicy Bypass -File $script -Source $src -Exe $exe
Check "missing_exe_exit0" 0 $LASTEXITCODE

# Case 4: source missing → can't compare → exit 0
Set-Content -LiteralPath $exe -Value "exe"
Remove-Item -LiteralPath $src
& powershell -NoProfile -ExecutionPolicy Bypass -File $script -Source $src -Exe $exe
Check "missing_src_exit0" 0 $LASTEXITCODE
}
finally {
Remove-Item -Recurse -Force -LiteralPath $tmp -ErrorAction SilentlyContinue
}

if ($fails -gt 0) {
Write-Host "`n$fails failed."
exit 1
}
Write-Host "`nAll stale-detector cases passed."
exit 0
Loading