// Copyright 2012-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 using System.Runtime.InteropServices; using Shouldly; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.Internal; /// /// Tests for SignalHandler — mirrors server/signal_test.go. /// public sealed class SignalHandlerTests : IDisposable { public SignalHandlerTests() { SignalHandler.ResetTestHooks(); SignalHandler.SetProcessName("nats-server"); } public void Dispose() { SignalHandler.ResetTestHooks(); SignalHandler.SetProcessName("nats-server"); } [Fact] // T:3158 public void CommandToUnixSignal_ShouldMapCorrectly() { SignalHandler.CommandToUnixSignal(ServerCommand.Stop).ShouldBe(UnixSignal.SigKill); SignalHandler.CommandToUnixSignal(ServerCommand.Quit).ShouldBe(UnixSignal.SigInt); SignalHandler.CommandToUnixSignal(ServerCommand.Reopen).ShouldBe(UnixSignal.SigUsr1); SignalHandler.CommandToUnixSignal(ServerCommand.Reload).ShouldBe(UnixSignal.SigHup); SignalHandler.CommandToUnixSignal(ServerCommand.Term).ShouldBe(UnixSignal.SigTerm); SignalHandler.CommandToUnixSignal(ServerCommand.LameDuckMode).ShouldBe(UnixSignal.SigUsr2); } [Fact] // T:3155 public void SetProcessName_ShouldNotThrow() { Should.NotThrow(() => SignalHandler.SetProcessName("test-server")); } [Fact] // T:3149 public void IsWindowsService_ShouldReturnFalse() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.IsWindowsService().ShouldBeFalse(); } [Fact] // T:3148 public void Run_ShouldInvokeStartAction() { var called = false; SignalHandler.Run(() => called = true); called.ShouldBeTrue(); } [Fact] // T:3157 public void ProcessSignal_InvalidPid_ShouldReturnError() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "not-a-pid"); err.ShouldNotBeNull(); } [Fact] // T:2919 public void ProcessSignalInvalidCommand_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var err = SignalHandler.ProcessSignal((ServerCommand)99, "123"); err.ShouldNotBeNull(); err!.Message.ShouldContain("unknown signal"); } [Fact] // T:2920 public void ProcessSignalInvalidPid_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "abc"); err.ShouldNotBeNull(); err!.Message.ShouldBe("invalid pid: abc"); } [Fact] // T:2913 public void ProcessSignalMultipleProcesses_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.ResolvePidsHandler = () => [123, 456]; var err = SignalHandler.ProcessSignal(ServerCommand.Stop, ""); err.ShouldNotBeNull(); err!.Message.ShouldBe("multiple nats-server processes running:\n123\n456"); } [Fact] // T:2914 public void ProcessSignalMultipleProcessesGlob_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.ResolvePidsHandler = () => [123, 456]; SignalHandler.SendSignalHandler = static (_, _) => new InvalidOperationException("mock"); var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "*"); err.ShouldNotBeNull(); var lines = err!.Message.Split('\n'); lines.Length.ShouldBe(3); lines[0].ShouldBe(string.Empty); lines[1].ShouldStartWith("signal \"stop\" 123:"); lines[2].ShouldStartWith("signal \"stop\" 456:"); } [Fact] // T:2915 public void ProcessSignalMultipleProcessesGlobPartial_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.ResolvePidsHandler = () => [123, 124, 456]; SignalHandler.SendSignalHandler = static (_, _) => new InvalidOperationException("mock"); var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "12*"); err.ShouldNotBeNull(); var lines = err!.Message.Split('\n'); lines.Length.ShouldBe(3); lines[0].ShouldBe(string.Empty); lines[1].ShouldStartWith("signal \"stop\" 123:"); lines[2].ShouldStartWith("signal \"stop\" 124:"); } [Fact] // T:2916 public void ProcessSignalPgrepError_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.ResolvePidsHandler = static () => throw new InvalidOperationException("unable to resolve pid, try providing one"); var err = SignalHandler.ProcessSignal(ServerCommand.Stop, ""); err.ShouldNotBeNull(); err!.Message.ShouldBe("unable to resolve pid, try providing one"); } [Fact] // T:2917 public void ProcessSignalPgrepMangled_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; SignalHandler.ResolvePidsHandler = static () => throw new InvalidOperationException("unable to resolve pid, try providing one"); var err = SignalHandler.ProcessSignal(ServerCommand.Stop, ""); err.ShouldNotBeNull(); err!.Message.ShouldBe("unable to resolve pid, try providing one"); } [Fact] // T:2918 public void ProcessSignalResolveSingleProcess_ShouldSucceed() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var called = false; SignalHandler.ResolvePidsHandler = () => [123]; SignalHandler.SendSignalHandler = (pid, signal) => { called = true; pid.ShouldBe(123); signal.ShouldBe(UnixSignal.SigKill); return null; }; var err = SignalHandler.ProcessSignal(ServerCommand.Stop, ""); err.ShouldBeNull(); called.ShouldBeTrue(); } [Fact] // T:2921 public void ProcessSignalQuitProcess_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Quit, UnixSignal.SigInt, "123"); } [Fact] // T:2922 public void ProcessSignalTermProcess_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123"); } [Fact] // T:2923 public void ProcessSignalReopenProcess_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Reopen, UnixSignal.SigUsr1, "123"); } [Fact] // T:2924 public void ProcessSignalReloadProcess_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Reload, UnixSignal.SigHup, "123"); } [Fact] // T:2925 public void ProcessSignalLameDuckMode_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.LameDuckMode, UnixSignal.SigUsr2, "123"); } [Fact] // T:2926 public void ProcessSignalTermDuringLameDuckMode_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123"); } [Fact] // T:2927 public void SignalInterruptHasSuccessfulExit_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Quit, UnixSignal.SigInt, "123"); } [Fact] // T:2928 public void SignalTermHasSuccessfulExit_ShouldSucceed() { ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123"); } private static void ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand command, UnixSignal expectedSignal, string pid) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; var called = false; SignalHandler.SendSignalHandler = (resolvedPid, signal) => { called = true; resolvedPid.ShouldBe(123); signal.ShouldBe(expectedSignal); return null; }; var err = SignalHandler.ProcessSignal(command, pid); err.ShouldBeNull(); called.ShouldBeTrue(); } }