StartAsync awaited the static launch-gate semaphore with no timeout, so one hung launch (slow disk, AV scanning claude.exe, a Porta.Pty/ConPTY hiccup) blocked every subsequent pane open behind it indefinitely, including Retry. Extracted the wait into WaitForLaunchGateAsync with a 30s timeout that throws TimeoutException before the try/finally (never releasing a gate it didn't acquire); the exception flows through StartCoreAsync's existing catch into StartError, so the pane shows an error + Retry instead of hanging. StartError renders ex.Message directly (no locale key involved), so no locale changes were needed. PtyTerminalSession.StartAsync itself needs a real TerminalControl and stays impractical to unit-test directly, so tests target the extracted WaitForLaunchGateAsync helper against a plain SemaphoreSlim: timeout throws, timeout never acquires the gate, and success still acquires it.
39 lines
1.3 KiB
C#
39 lines
1.3 KiB
C#
using System.Threading;
|
|
using ClaudeDo.Ui.Services;
|
|
using Xunit;
|
|
|
|
namespace ClaudeDo.Ui.Tests.Services;
|
|
|
|
public class PtyTerminalSessionTests
|
|
{
|
|
[Fact]
|
|
public async Task WaitForLaunchGateAsync_ThrowsTimeoutException_WhenGateStaysHeld()
|
|
{
|
|
using var gate = new SemaphoreSlim(0, 1); // never released — simulates a hung launch
|
|
|
|
await Assert.ThrowsAsync<TimeoutException>(() =>
|
|
PtyTerminalSession.WaitForLaunchGateAsync(gate, TimeSpan.FromMilliseconds(50), CancellationToken.None));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WaitForLaunchGateAsync_DoesNotReleaseGate_ItNeverAcquired()
|
|
{
|
|
using var gate = new SemaphoreSlim(0, 1);
|
|
|
|
await Assert.ThrowsAsync<TimeoutException>(() =>
|
|
PtyTerminalSession.WaitForLaunchGateAsync(gate, TimeSpan.FromMilliseconds(50), CancellationToken.None));
|
|
|
|
Assert.Equal(0, gate.CurrentCount); // still held; a bad fix would Release() what it never acquired
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WaitForLaunchGateAsync_AcquiresGate_WhenAvailable()
|
|
{
|
|
using var gate = new SemaphoreSlim(1, 1);
|
|
|
|
await PtyTerminalSession.WaitForLaunchGateAsync(gate, TimeSpan.FromSeconds(5), CancellationToken.None);
|
|
|
|
Assert.Equal(0, gate.CurrentCount); // acquired
|
|
}
|
|
}
|