mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
fix(launcher): extract startup subsystem and harden IPC detection
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
46
LanMountainDesktop.Tests/HostActivationPolicyTests.cs
Normal file
46
LanMountainDesktop.Tests/HostActivationPolicyTests.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Startup;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class HostActivationPolicyTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("launch", "normal", true)]
|
||||
[InlineData("launch", "restart", false)]
|
||||
[InlineData("apply-update", "normal", false)]
|
||||
public void ShouldProbeExistingHostBeforeLaunch_RespectsLaunchSource(
|
||||
string command,
|
||||
string launchSource,
|
||||
bool expected)
|
||||
{
|
||||
var context = CommandContext.FromArgs([command, "--launch-source", launchSource]);
|
||||
Assert.Equal(expected, HostActivationPolicy.ShouldProbeExistingHostBeforeLaunch(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsRecoverableActivationFailure_AllowsStartupPendingWhenIpcReady()
|
||||
{
|
||||
var activation = new PublicShellActivationResult(
|
||||
false,
|
||||
"startup_pending",
|
||||
"pending",
|
||||
new PublicShellStatus(
|
||||
ProcessId: 1,
|
||||
StartedAtUtc: DateTimeOffset.UtcNow,
|
||||
LaunchSource: "normal",
|
||||
ShellState: "initializing",
|
||||
MainWindowCreated: false,
|
||||
MainWindowVisible: false,
|
||||
MainWindowOpened: false,
|
||||
DesktopVisible: false,
|
||||
PublicIpcReady: true,
|
||||
Tray: new PublicTrayStatus("Unavailable", false, false, false, false, 0),
|
||||
Taskbar: new PublicTaskbarStatus(false, false, false, false, false, false)));
|
||||
|
||||
Assert.True(HostActivationPolicy.IsRecoverableActivationFailure(activation));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Services;
|
||||
using LanMountainDesktop.Launcher.Startup;
|
||||
using LanMountainDesktop.Models;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
@@ -22,7 +22,7 @@ public sealed class LauncherMultiInstancePolicyTests
|
||||
{
|
||||
var context = CommandContext.FromArgs(["launch"]);
|
||||
|
||||
Assert.True(LauncherFlowCoordinator.ShouldProbeExistingHostBeforeLaunch(context));
|
||||
Assert.True(HostActivationPolicy.ShouldProbeExistingHostBeforeLaunch(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -33,16 +33,16 @@ public sealed class LauncherMultiInstancePolicyTests
|
||||
$"--{LauncherIpcConstants.LaunchSourceOptionName}=restart"
|
||||
]);
|
||||
|
||||
Assert.False(LauncherFlowCoordinator.ShouldProbeExistingHostBeforeLaunch(context));
|
||||
Assert.False(HostActivationPolicy.ShouldProbeExistingHostBeforeLaunch(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActivationExitCodes_AreClassifiedSeparatelyFromEarlyHostExit()
|
||||
{
|
||||
Assert.True(LauncherFlowCoordinator.IsSuccessfulActivationExitCode(HostExitCodes.SecondaryActivationSucceeded));
|
||||
Assert.True(LauncherFlowCoordinator.IsFailedActivationExitCode(HostExitCodes.SecondaryActivationFailed));
|
||||
Assert.True(LauncherFlowCoordinator.IsFailedActivationExitCode(HostExitCodes.RestartLockNotAcquired));
|
||||
Assert.False(LauncherFlowCoordinator.IsFailedActivationExitCode(1));
|
||||
Assert.True(HostActivationPolicy.IsSuccessfulActivationExitCode(HostExitCodes.SecondaryActivationSucceeded));
|
||||
Assert.True(HostActivationPolicy.IsFailedActivationExitCode(HostExitCodes.SecondaryActivationFailed));
|
||||
Assert.True(HostActivationPolicy.IsFailedActivationExitCode(HostExitCodes.RestartLockNotAcquired));
|
||||
Assert.False(HostActivationPolicy.IsFailedActivationExitCode(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,7 +57,7 @@ public sealed class LauncherMultiInstancePolicyTests
|
||||
mainWindowOpened: false,
|
||||
desktopVisible: false));
|
||||
|
||||
Assert.True(LauncherFlowCoordinator.IsRecoverableActivationFailure(activation));
|
||||
Assert.True(HostActivationPolicy.IsRecoverableActivationFailure(activation));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,18 +72,18 @@ public sealed class LauncherMultiInstancePolicyTests
|
||||
mainWindowOpened: false,
|
||||
desktopVisible: false));
|
||||
|
||||
Assert.False(LauncherFlowCoordinator.IsRecoverableActivationFailure(activation));
|
||||
Assert.False(HostActivationPolicy.IsRecoverableActivationFailure(activation));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsExistingHostReadyForLauncherDecision_RequiresPublicIpcReady()
|
||||
{
|
||||
Assert.False(LauncherFlowCoordinator.IsExistingHostReadyForLauncherDecision(null));
|
||||
Assert.False(LauncherFlowCoordinator.IsExistingHostReadyForLauncherDecision(CreateShellStatus(
|
||||
Assert.False(HostActivationPolicy.IsExistingHostReadyForLauncherDecision(null));
|
||||
Assert.False(HostActivationPolicy.IsExistingHostReadyForLauncherDecision(CreateShellStatus(
|
||||
publicIpcReady: false,
|
||||
mainWindowOpened: true,
|
||||
desktopVisible: true)));
|
||||
Assert.True(LauncherFlowCoordinator.IsExistingHostReadyForLauncherDecision(CreateShellStatus(
|
||||
Assert.True(HostActivationPolicy.IsExistingHostReadyForLauncherDecision(CreateShellStatus(
|
||||
publicIpcReady: true,
|
||||
mainWindowOpened: true,
|
||||
desktopVisible: true)));
|
||||
|
||||
@@ -7,11 +7,10 @@ public sealed class LauncherStartupTimeoutPolicyTests
|
||||
[Fact]
|
||||
public void LauncherStartupTimeouts_MatchSlowStartupContract()
|
||||
{
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.Launcher", "Services", "LauncherFlowCoordinator.cs");
|
||||
var source = ReadRepositoryFile("LanMountainDesktop.Launcher", "Startup", "StartupTimeoutPolicy.cs");
|
||||
|
||||
Assert.Contains("StartupSoftTimeout = TimeSpan.FromSeconds(30)", source);
|
||||
Assert.Contains("StartupHardTimeout = TimeSpan.FromSeconds(120)", source);
|
||||
Assert.DoesNotContain("StartupHardTimeout = TimeSpan.FromSeconds(30)", source);
|
||||
Assert.Contains("SoftTimeout = TimeSpan.FromSeconds(30)", source);
|
||||
Assert.Contains("HardTimeout = TimeSpan.FromSeconds(120)", source);
|
||||
}
|
||||
|
||||
private static string ReadRepositoryFile(params string[] pathParts)
|
||||
|
||||
56
LanMountainDesktop.Tests/StartupSuccessTrackerTests.cs
Normal file
56
LanMountainDesktop.Tests/StartupSuccessTrackerTests.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using LanMountainDesktop.Launcher;
|
||||
using LanMountainDesktop.Launcher.Startup;
|
||||
using LanMountainDesktop.Shared.Contracts.Launcher;
|
||||
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace LanMountainDesktop.Tests;
|
||||
|
||||
public sealed class StartupSuccessTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryResolve_DesktopVisibleStage_SucceedsForForegroundLaunch()
|
||||
{
|
||||
var tracker = new StartupSuccessTracker(CreateContext("normal"));
|
||||
Assert.True(tracker.TryResolve(StartupStage.DesktopVisible, out var state));
|
||||
Assert.Equal("ok", state.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryResolve_ShellStatusWithMainWindowOpened_Succeeds()
|
||||
{
|
||||
var tracker = new StartupSuccessTracker(CreateContext("normal"));
|
||||
var status = new PublicShellStatus(
|
||||
ProcessId: 1234,
|
||||
StartedAtUtc: DateTimeOffset.UtcNow,
|
||||
LaunchSource: "normal",
|
||||
ShellState: "opened",
|
||||
MainWindowCreated: true,
|
||||
MainWindowVisible: false,
|
||||
MainWindowOpened: true,
|
||||
DesktopVisible: false,
|
||||
PublicIpcReady: true,
|
||||
Tray: new PublicTrayStatus("Unavailable", false, false, false, false, 0),
|
||||
Taskbar: new PublicTaskbarStatus(false, true, false, false, false, true));
|
||||
|
||||
Assert.True(tracker.TryResolve(status, out var state));
|
||||
Assert.Equal(StartupStage.Ready, state.Stage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryResolve_RestartTrayPolicy_RequiresTrayAndBackground()
|
||||
{
|
||||
var tracker = new StartupSuccessTracker(CreateContext("restart", "--restart-presentation", "tray"));
|
||||
Assert.False(tracker.TryResolve(StartupStage.TrayReady, out _));
|
||||
Assert.True(tracker.TryResolve(StartupStage.BackgroundReady, out _));
|
||||
Assert.True(tracker.TryResolve(StartupStage.TrayReady, out var final));
|
||||
Assert.Equal("background_ready", final.Code);
|
||||
}
|
||||
|
||||
private static CommandContext CreateContext(string launchSource, params string[] extraArgs)
|
||||
{
|
||||
var args = new List<string> { "launch", "--launch-source", launchSource };
|
||||
args.AddRange(extraArgs);
|
||||
return CommandContext.FromArgs(args.ToArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user