using System.Diagnostics; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Startup; /// /// #618 — a host that fails to start must END the process with a non-zero exit code and a Fatal line, /// not linger as a half-started zombie. Spawns the built host assembly as a real child process. /// public sealed class FailStartExitTests { [Fact] public async Task A_failed_start_exits_non_zero_and_logs_fatal() { var hostDll = typeof(ZB.MOM.WW.OtOpcUa.Host.Program).Assembly.Location; var dir = Path.GetDirectoryName(hostDll)!; var psi = new ProcessStartInfo("dotnet", $"\"{hostDll}\"") { WorkingDirectory = dir, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, }; // An invalid Akka seed address makes the cluster wiring throw before the host can ever run; // no external service is touched. psi.Environment["Cluster__Roles"] = "driver"; psi.Environment["Cluster__SeedNodes__0"] = "not-a-valid-address"; using var proc = Process.Start(psi)!; var stdout = proc.StandardOutput.ReadToEndAsync(); var stderr = proc.StandardError.ReadToEndAsync(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); try { await proc.WaitForExitAsync(cts.Token); } catch (OperationCanceledException) { proc.Kill(entireProcessTree: true); Assert.Fail("host did not exit within 60 s of a failed start (#618): " + await stdout + await stderr); } var output = await stdout + await stderr; proc.ExitCode.ShouldNotBe(0, output); output.ShouldContain("terminated during startup", Case.Insensitive, "the failure must be logged as Fatal"); } }