@@ -7,6 +7,7 @@ $ProgressPreference = "SilentlyContinue"
77
88$repoRoot = [IO.Path ]::GetFullPath((Join-Path $PSScriptRoot " ..\.." ))
99$testRoot = Join-Path $PSScriptRoot " .tmp-$PID "
10+ $testUpdaterVersion = (Get-Content - LiteralPath (Join-Path $repoRoot " updater\updater.version" ) - Raw).Trim()
1011$requiredPreserve = @ (
1112 " models/**" ,
1213 " webui/voice/**" ,
@@ -33,6 +34,7 @@ function New-TestBundle {
3334 param ([string ]$Name )
3435 $root = Join-Path $testRoot $Name
3536 New-Item - ItemType Directory - Path (Join-Path $root " updater" ) - Force | Out-Null
37+ Copy-Item - LiteralPath (Join-Path $repoRoot " update.bat" ) - Destination (Join-Path $root " update.bat" )
3638 Copy-Item - LiteralPath (Join-Path $repoRoot " updater\updater.ps1" ) - Destination (Join-Path $root " updater\updater.ps1" )
3739 Copy-Item - LiteralPath (Join-Path $repoRoot " updater\apply-update.ps1" ) - Destination (Join-Path $root " updater\apply-update.ps1" )
3840 Copy-Item - LiteralPath (Join-Path $repoRoot " updater\updater.version" ) - Destination (Join-Path $root " updater\updater.version" )
@@ -50,7 +52,7 @@ function New-TestBundle {
5052 channel = " stable"
5153 platform = " windows-x64"
5254 python = " 3.11"
53- components = [ordered ]@ { app = " 0.2.0" ; core_cpu = " 0.2.0" ; core_cuda = " 0.2.0" ; python_env = " 0.2.0" ; updater = " 1.1.0 " }
55+ components = [ordered ]@ { app = " 0.2.0" ; core_cpu = " 0.2.0" ; core_cuda = " 0.2.0" ; python_env = " 0.2.0" ; updater = $testUpdaterVersion }
5456 }
5557 Write-Json $version (Join-Path $root " version.json" )
5658 return $root
@@ -132,7 +134,7 @@ function New-CustomManifest {
132134 param (
133135 [string ]$Directory ,
134136 [object []]$Components ,
135- [string ]$MinimumUpdater = " 1.1.0 " ,
137+ [string ]$MinimumUpdater = $testUpdaterVersion ,
136138 [string []]$HealthChecks = @ ()
137139 )
138140 $value = [ordered ]@ {
@@ -271,6 +273,17 @@ function Invoke-TestUpdater {
271273 return [pscustomobject ]@ { ExitCode = $exitCode ; Output = ($output -join " `n " ) }
272274}
273275
276+ function Invoke-TestBatchUpdater {
277+ param (
278+ [string ]$Root ,
279+ [string []]$Arguments
280+ )
281+ $batchPath = Join-Path $Root " update.bat"
282+ $output = @ (& $batchPath @Arguments 2>&1 )
283+ $exitCode = $LASTEXITCODE
284+ return [pscustomobject ]@ { ExitCode = $exitCode ; Output = ($output -join " `n " ) }
285+ }
286+
274287function Test-CheckMode {
275288 $root = New-TestBundle " check"
276289 $assets = Join-Path $root " assets"
@@ -282,6 +295,30 @@ function Test-CheckMode {
282295 Assert-True ((Read-JsonFile (Join-Path $root " version.json" )).version -eq " 0.2.0" ) " check mode changed version.json"
283296}
284297
298+ function Test-BatchStableChannelCheck {
299+ $root = New-TestBundle " batch entry with spaces"
300+ $assets = Join-Path $root " release assets"
301+ New-Item - ItemType Directory - Path $assets - Force | Out-Null
302+ $archive = New-TestArchive $assets " app.zip" " webui/managed.txt"
303+ $manifest = New-TestManifest $assets $archive
304+ $stable = Join-Path $assets " stable.json"
305+ Write-Json ([ordered ]@ {
306+ schema = 1
307+ channel = " stable"
308+ version = " 0.2.1"
309+ manifest_url = $manifest
310+ signature_url = " $manifest .minisig"
311+ notes_url = " https://example.invalid/audio.cpp/v0.2.1"
312+ }) $stable
313+ $result = Invoke-TestBatchUpdater $root @ (
314+ " --check" , " -StableUrl" , $stable ,
315+ " -MinisignPath" , (Join-Path $root " updater\test-minisign.cmd" ),
316+ " -PublicKeyPath" , (Join-Path $root " updater\test-public-key.txt" )
317+ )
318+ Assert-True ($result.ExitCode -eq 0 ) " update.bat stable-channel check failed: $ ( $result.Output ) "
319+ Assert-True ($result.Output -match " Target version\s+: 0\.2\.1" ) " update.bat did not report the stable target version"
320+ }
321+
285322function Read-JsonFile {
286323 param ([string ]$Path )
287324 return Get-Content - LiteralPath $Path - Raw - Encoding UTF8 | ConvertFrom-Json
@@ -344,10 +381,20 @@ function Test-ProtectedPathAndLock {
344381 Assert-True ($result.ExitCode -ne 0 ) " updater accepted a protected payload path"
345382
346383 New-Item - ItemType Directory - Path (Join-Path $root " _update" ) - Force | Out-Null
347- Set-Content - LiteralPath (Join-Path $root " _update\update.lock" ) - Value " owned by another process" - Encoding ASCII
384+ $lockPath = Join-Path $root " _update\update.lock"
385+ $lockStream = [IO.File ]::Open($lockPath , [IO.FileMode ]::CreateNew, [IO.FileAccess ]::Write, [IO.FileShare ]::None)
386+ try {
387+ $result = Invoke-TestUpdater $root " --check" $manifest
388+ Assert-True ($result.ExitCode -ne 0 ) " updater ignored an active lock"
389+ Assert-True (Test-Path - LiteralPath $lockPath ) " updater removed another process's active lock"
390+ } finally {
391+ $lockStream.Dispose ()
392+ }
393+
394+ Set-Content - LiteralPath $lockPath - Value " stale interrupted update" - Encoding ASCII
348395 $result = Invoke-TestUpdater $root " --check" $manifest
349- Assert-True ($result.ExitCode -ne 0 ) " updater ignored an existing lock"
350- Assert-True (Test-Path - LiteralPath ( Join-Path $root " _update\update.lock " )) " updater removed another process's lock"
396+ Assert-True ($result.ExitCode -eq 0 ) " updater did not recover from a stale lock: $ ( $result .Output ) "
397+ Assert-True (-not ( Test-Path - LiteralPath $lockPath )) " updater left the recovered stale lock behind "
351398}
352399
353400function Test-PythonDependencyUpdateAndRollback {
@@ -466,7 +513,7 @@ function Test-ReleaseBuilder {
466513 " audiocpp-core-cpu-win-x64-v0.2.1.zip" ,
467514 " audiocpp-core-cuda-win-x64-v0.2.1.zip" ,
468515 " audiocpp-python-deps-py311-win-x64-v0.2.1.zip" ,
469- " audiocpp-updater-v1.1.0 .zip" ,
516+ " audiocpp-updater-v $testUpdaterVersion .zip" ,
470517 " manifest-v0.2.1.json" ,
471518 " manifest-v0.2.1.json.minisig" ,
472519 " stable.json" ,
@@ -479,6 +526,7 @@ function Test-ReleaseBuilder {
479526 $manifest = Read-JsonFile (Join-Path $output " manifest-v0.2.1.json" )
480527 Assert-True (@ ($manifest.components ).Count -eq 5 ) " release manifest does not contain all five component types"
481528 Assert-True (@ ($manifest.preserve ) -contains " webui/voice/**" ) " release manifest omitted voice preservation"
529+ Assert-True ([string ]$manifest.supported_from -eq " >=0.2.0" ) " release manifest does not keep bootstrap installs eligible for later versions"
482530 $stable = Read-JsonFile (Join-Path $output " stable.json" )
483531 Assert-True ([string ]$stable.manifest_url -match ' /v0\.2\.1-windows-prebuilt/manifest-v0\.2\.1\.json$' ) " stable.json points at the wrong manifest"
484532
@@ -494,13 +542,16 @@ function Test-ReleaseBuilder {
494542 foreach ($relative in @ (" update.bat" , " version.json" , " updater\updater.ps1" , " updater\apply-update.ps1" , " updater\updater.version" , " updater\public-key.txt" , " updater\minisign.exe" )) {
495543 Assert-True (Test-Path - LiteralPath (Join-Path $bootstrapExpanded $relative ) - PathType Leaf) " bootstrap ZIP omitted $relative "
496544 }
545+ $bootstrapVersion = Read-JsonFile (Join-Path $bootstrapExpanded " version.json" )
546+ Assert-True ([string ]$bootstrapVersion.components.updater -eq $testUpdaterVersion ) " bootstrap version.json does not match updater.version"
497547}
498548
499549try {
500550 if (Test-Path - LiteralPath $testRoot ) { Remove-Item - LiteralPath $testRoot - Recurse - Force }
501551 New-Item - ItemType Directory - Path $testRoot - Force | Out-Null
502552 $tests = @ (
503553 " Test-CheckMode" ,
554+ " Test-BatchStableChannelCheck" ,
504555 " Test-DryRunAndHashValidation" ,
505556 " Test-ApplySuccessAndPreserve" ,
506557 " Test-RollbackOnHealthFailure" ,
0 commit comments