rd-net/Test.Lifetimes/Diagnostics/ProcessWatchdogTest.cs (84 lines of code) (raw):

using System; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading.Tasks; using JetBrains.Core; using JetBrains.Diagnostics; using JetBrains.Lifetimes; using JetBrains.Util; using NUnit.Framework; namespace Test.Lifetimes.Diagnostics; public class ProcessWatchdogTest : LifetimesTestBase { [Test] public Task TestWithSleepingProcess() => DoTest(StartSleepingProcess, true); [Test] public async Task TestWithProcessReturning259() { if (!RuntimeInfo.IsRunningUnderWindows) return; await DoTest(() => GetTerminatedProcess(259), false); } private static Task DoTest(Func<Process> processCreator, bool assertAlive) => Lifetime.UsingAsync(async lt => { var process = lt.Bracket( processCreator, p => { if (!p.HasExited) p.Kill(); p.Dispose(); }); var tcs = new TaskCompletionSource<Unit>(); var options = new ProcessWatchdog.Options(process.Id, lt) { BeforeProcessKill = () => tcs.SetResult(Unit.Instance), KillCurrentProcess = () => { } }; ProcessWatchdog.StartWatchdogForPid(options); var timeForReliableDetection = ProcessWatchdog.DELAY_BEFORE_RETRY * 2; var task = tcs.Task; if (assertAlive) { await Task.Delay(timeForReliableDetection, lt); Assert.IsFalse(process.HasExited, "Process should not be exited."); Assert.IsFalse(task.IsCompleted, "Watchdog should not be triggered."); } if (!process.HasExited) process.Kill(); if (await Task.WhenAny(task, Task.Delay(timeForReliableDetection, lt)) != task) { Assert.Fail($"Termination of process {process.Id} wasn't detected during the timeout."); } var exs = Assert.Throws<AggregateException>(TestLogger.ExceptionLogger.ThrowLoggedExceptions).InnerExceptions; Assert.IsTrue( exs.All(e => e.Message.Contains($"Parent process PID:{process.Id} has quit, killing ourselves via Process.Kill")), $"No expected data in some of the exceptions: {string.Join("\n", exs.Select(e => e.Message))}"); }); private Process StartSleepingProcess() { var startInfo = RuntimeInfo.IsRunningUnderWindows ? new ProcessStartInfo("cmd.exe", "/c ping 127.0.0.1 -n 30") : new ProcessStartInfo("sleep", "30"); startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; var logger = Log.GetLog<ProcessWatchdogTest>(); var process = Process.Start(startInfo)!; process.ErrorDataReceived += (_, args) => logger.Warn($"[{process.Id}] {args.Data}"); process.OutputDataReceived += (_, args) => logger.Info($"[{process.Id}] {args.Data}"); process.Exited += (_, _) => logger.Info($"[{process.Id}] Exited with code: {process.ExitCode}"); return process; } private Process GetTerminatedProcess(int exitCode) { var process = RuntimeInfo.IsRunningUnderWindows ? Process.Start(new ProcessStartInfo("cmd.exe", $"/c exit {exitCode.ToString(CultureInfo.InvariantCulture)}") { WindowStyle = ProcessWindowStyle.Hidden }) : Process.Start("/usr/bin/sh", $"-c \"exit {exitCode.ToString(CultureInfo.InvariantCulture)}\""); process!.WaitForExit(); Assert.AreEqual(exitCode, process.ExitCode); return process; } }