diff --git a/app-runner/Private/DeviceProviders/AdbProvider.ps1 b/app-runner/Private/DeviceProviders/AdbProvider.ps1 index b5ea42c..6c61c85 100644 --- a/app-runner/Private/DeviceProviders/AdbProvider.ps1 +++ b/app-runner/Private/DeviceProviders/AdbProvider.ps1 @@ -53,6 +53,7 @@ class AdbProvider : DeviceProvider { # App execution 'launch' = @('adb', '-s {0} shell am start -n {1} {2} -W') 'pidof' = @('adb', '-s {0} shell pidof {1}') + 'force-stop' = @('adb', '-s {0} shell am force-stop {1}') # Logging 'logcat' = @('adb', '-s {0} logcat -d') @@ -218,6 +219,9 @@ class AdbProvider : DeviceProvider { $startTime = Get-Date + # Otherwise `am start` brings a still-running (possibly hung) instance to the front instead of starting a new one. + $this.StopApp($packageName) + # Clear logcat before launch Write-Debug "$($this.Platform): Clearing logcat" $this.InvokeCommand('logcat-clear', @($this.DeviceSerial)) @@ -289,6 +293,10 @@ class AdbProvider : DeviceProvider { $logCache = @($this.InvokeCommand('logcat-pid', @($this.DeviceSerial, $appPID))) Write-Host "Retrieved $($logCache.Count) log lines" -ForegroundColor Cyan + if (-not $processExited) { + $this.StopApp($packageName) + } + Write-Host "Android doesn't report exit codes via adb so exit code is always NULL" $exitCode = $null } @@ -308,6 +316,15 @@ class AdbProvider : DeviceProvider { } } + [void] StopApp([string]$packageName) { + try { + $this.InvokeCommand('force-stop', @($this.DeviceSerial, $packageName)) | Out-Null + } + catch { + Write-Warning "$($this.Platform): Failed to force-stop ${packageName}: $_" + } + } + # Helper method to wait for process with retry [string] WaitForProcess([string]$packageName, [int]$timeoutSeconds) { Write-Debug "$($this.Platform): Waiting for process: $packageName" diff --git a/app-runner/Tests/AdbRunApplication.Tests.ps1 b/app-runner/Tests/AdbRunApplication.Tests.ps1 new file mode 100644 index 0000000..031b1dd --- /dev/null +++ b/app-runner/Tests/AdbRunApplication.Tests.ps1 @@ -0,0 +1,69 @@ +$ErrorActionPreference = 'Stop' + +BeforeAll { + . "$PSScriptRoot\..\Private\DeviceProviders\DeviceProvider.ps1" + . "$PSScriptRoot\..\Private\DeviceProviders\AdbProvider.ps1" + + Invoke-Expression @' +class FakeAdbProvider : AdbProvider { + [System.Collections.Generic.List[string]]$Calls = [System.Collections.Generic.List[string]]::new() + [bool]$Hangs = $false + [bool]$ForceStopFails = $false + [int]$PidofCalls = 0 + + FakeAdbProvider() : base() { + $this.DeviceSerial = 'emulator-5554' + $this.Timeouts['run-timeout'] = 1 + $this.Timeouts['pid-retry'] = 1 + $this.Timeouts['process-check-interval'] = 0 + } + + [object] InvokeCommand([string]$action, [object[]]$parameters) { + $this.Calls.Add($action) + switch ($action) { + 'force-stop' { if ($this.ForceStopFails) { throw 'adb: device offline' } } + 'launch' { return 'Status: ok' } + 'pidof' { + $this.PidofCalls++ + if ($this.Hangs -or $this.PidofCalls -eq 1) { return '4056' } + return $null + } + 'logcat-pid' { return @('10-20 08:14:04.782 4056 4056 I IL2CPP : JNI_OnLoad') } + } + return $null + } +} +'@ +} + +Describe 'AdbProvider.RunApplication' -Tag 'Unit' { + It 'Force-stops the package before launching it' { + $provider = [FakeAdbProvider]::new() + + $provider.RunApplication('com.example.app/.MainActivity', @(), $null, $null) | Out-Null + + $provider.Calls[0] | Should -Be 'force-stop' + $provider.Calls.IndexOf('force-stop') | Should -BeLessThan $provider.Calls.IndexOf('launch') + @($provider.Calls | Where-Object { $_ -eq 'force-stop' }).Count | Should -Be 1 + } + + It 'Force-stops the package after a run timeout, once its logs are read' { + $provider = [FakeAdbProvider]::new() + $provider.Hangs = $true + + $result = $provider.RunApplication('com.example.app/.MainActivity', @(), $null, $null) + + $provider.Calls[-1] | Should -Be 'force-stop' + $provider.Calls[-2] | Should -Be 'logcat-pid' + $result.Output | Should -Contain '10-20 08:14:04.782 4056 4056 I IL2CPP : JNI_OnLoad' + } + + It 'Still launches when force-stop fails' { + $provider = [FakeAdbProvider]::new() + $provider.ForceStopFails = $true + + $provider.RunApplication('com.example.app/.MainActivity', @(), $null, $null) 3>$null | Out-Null + + $provider.Calls | Should -Contain 'launch' + } +}