Skip to content

Commit b4a6b8e

Browse files
tablackburnclaude
andauthored
ci: Add integration test timeouts and download progress monitoring (#37)
* ci: Add integration test timeouts and download progress monitoring Add job-level (90 min) and step-level (75 min) timeouts to prevent integration tests from hanging indefinitely when Plex server downloads stall on ubuntu runners. Add Start-SyncProgressMonitor/Stop-SyncProgressMonitor helpers that poll the destination directory every 30 seconds and emit file count and total MB to CI logs, giving visibility into download progress. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Use explicit SourceIdentifier for event subscription cleanup Register-ObjectEvent -Action returns a PSEventJob whose .Name is the job name, not the SourceIdentifier needed by Unregister-Event. Use an explicit -SourceIdentifier with a unique GUID and store that instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f9982e7 commit b4a6b8e

3 files changed

Lines changed: 100 additions & 5 deletions

File tree

.github/workflows/CI.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ jobs:
152152
integration-tests:
153153
name: Integration Tests (${{ matrix.os }})
154154
runs-on: ${{ matrix.os }}
155+
timeout-minutes: 90
155156
# Only run for pushes to main or PRs from the same repo (has access to secrets)
156157
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
157158
needs: [unit-tests, unit-tests-ps51]
@@ -237,6 +238,7 @@ jobs:
237238
238239
- name: Run Integration Tests
239240
if: steps.check-secrets.outputs.secrets_configured == 'true'
241+
timeout-minutes: 75
240242
shell: pwsh
241243
env:
242244
PLEX_SERVER_URI: ${{ secrets.PLEX_SERVER_URI }}

tests/Integration/IntegrationTestHelpers.psm1

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,87 @@ function Remove-IntegrationTestServers {
249249
}
250250
}
251251

252+
function Start-SyncProgressMonitor {
253+
<#
254+
.SYNOPSIS
255+
Starts a background timer that reports download progress to CI logs
256+
257+
.PARAMETER Path
258+
The destination directory to monitor for downloaded files
259+
260+
.PARAMETER IntervalSeconds
261+
How often to report progress (default: 30 seconds)
262+
263+
.OUTPUTS
264+
PSCustomObject
265+
Returns a monitor object to pass to Stop-SyncProgressMonitor
266+
#>
267+
[CmdletBinding()]
268+
param(
269+
[Parameter(Mandatory = $true)]
270+
[string]$Path,
271+
272+
[Parameter(Mandatory = $false)]
273+
[int]$IntervalSeconds = 30
274+
)
275+
276+
$timer = [System.Timers.Timer]::new($IntervalSeconds * 1000)
277+
$timer.AutoReset = $true
278+
279+
$startTime = [DateTime]::UtcNow
280+
$sourceId = "SyncProgressMonitor-$([Guid]::NewGuid())"
281+
$eventAction = Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier $sourceId -MessageData @{
282+
Path = $Path
283+
StartTime = $startTime
284+
} -Action {
285+
$destination = $Event.MessageData.Path
286+
$elapsed = [DateTime]::UtcNow - $Event.MessageData.StartTime
287+
$elapsedFormatted = '{0}m{1}s' -f [math]::Floor($elapsed.TotalMinutes), $elapsed.Seconds
288+
289+
if (Test-Path $destination) {
290+
$files = Get-ChildItem -Path $destination -Recurse -File -ErrorAction SilentlyContinue
291+
$totalBytes = ($files | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
292+
$totalMB = [math]::Round($totalBytes / 1MB, 1)
293+
Write-Host " [SYNC PROGRESS] $elapsedFormatted elapsed | $($files.Count) files | $totalMB MB downloaded"
294+
}
295+
else {
296+
Write-Host " [SYNC PROGRESS] $elapsedFormatted elapsed | Waiting for first file..."
297+
}
298+
}
299+
300+
$timer.Start()
301+
302+
return [PSCustomObject]@{
303+
Timer = $timer
304+
EventSubscription = $sourceId
305+
StartTime = $startTime
306+
}
307+
}
308+
309+
function Stop-SyncProgressMonitor {
310+
<#
311+
.SYNOPSIS
312+
Stops a progress monitor started by Start-SyncProgressMonitor
313+
314+
.PARAMETER Monitor
315+
The monitor object returned by Start-SyncProgressMonitor
316+
#>
317+
[CmdletBinding()]
318+
param(
319+
[Parameter(Mandatory = $true)]
320+
[PSCustomObject]$Monitor
321+
)
322+
323+
$Monitor.Timer.Stop()
324+
Unregister-Event -SourceIdentifier $Monitor.EventSubscription -ErrorAction SilentlyContinue
325+
$Monitor.Timer.Dispose()
326+
327+
$elapsed = [DateTime]::UtcNow - $Monitor.StartTime
328+
$elapsedFormatted = '{0}m{1}s' -f [math]::Floor($elapsed.TotalMinutes), $elapsed.Seconds
329+
Write-Host " [SYNC COMPLETE] Total time: $elapsedFormatted"
330+
}
331+
252332
Export-ModuleMember -Function Test-IntegrationPrerequisites, Get-IntegrationTestContext, `
253333
Get-IntegrationConfigPath, Invoke-IntegrationTestWithRetry, Backup-ServerConfiguration, `
254-
Restore-ServerConfiguration, Remove-IntegrationTestServers
334+
Restore-ServerConfiguration, Remove-IntegrationTestServers, Start-SyncProgressMonitor, `
335+
Stop-SyncProgressMonitor

tests/Integration/Public/MediaSync.Integration.tests.ps1

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,14 @@ Describe 'Sync-PatMedia Integration Tests' -Skip:(-not $script:integrationEnable
255255
It 'Downloads media files from playlist' {
256256
if (-not $script:hasItems) { Set-ItResult -Skipped -Because 'Travel playlist has no items' }
257257

258-
# This actually downloads! Only runs if playlist has items
259-
$result = Sync-PatMedia -Destination $script:syncDestination -PassThru -Confirm:$false
258+
# Monitor download progress in CI logs (every 30s)
259+
$monitor = Start-SyncProgressMonitor -Path $script:syncDestination
260+
try {
261+
$result = Sync-PatMedia -Destination $script:syncDestination -PassThru -Confirm:$false
262+
}
263+
finally {
264+
Stop-SyncProgressMonitor -Monitor $monitor
265+
}
260266

261267
$result | Should -Not -BeNullOrEmpty
262268

@@ -273,8 +279,14 @@ Describe 'Sync-PatMedia Integration Tests' -Skip:(-not $script:integrationEnable
273279
It 'Is idempotent - second run downloads nothing' {
274280
if (-not $script:hasItems) { Set-ItResult -Skipped -Because 'Travel playlist has no items' }
275281

276-
# Run sync again - should detect existing files and skip
277-
$result = Sync-PatMedia -Destination $script:syncDestination -PassThru -Confirm:$false
282+
# Monitor for visibility even though this should be fast
283+
$monitor = Start-SyncProgressMonitor -Path $script:syncDestination -IntervalSeconds 10
284+
try {
285+
$result = Sync-PatMedia -Destination $script:syncDestination -PassThru -Confirm:$false
286+
}
287+
finally {
288+
Stop-SyncProgressMonitor -Monitor $monitor
289+
}
278290

279291
# ItemsToAdd should be 0 since files already exist
280292
$result.ItemsToAdd | Should -Be 0

0 commit comments

Comments
 (0)