From bca9bafc66515e3d69ec5f61701252f4d271ef37 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 31 Mar 2017 15:10:57 +0200 Subject: [PATCH] [JENKINS-42744] - Improve the ProcessHelperTes, add RunawayProcessKillerTest for the affected logic --- .../RunawayProcessKillerExtension.cs | 14 +++-- .../Extensions/RunawayProcessKillerTest.cs | 61 +++++++++++++++++++ src/Test/winswTests/Util/ConfigXmlBuilder.cs | 30 +++++++++ .../winswTests/Util/FilesystemTestHelper.cs | 13 +++- src/Test/winswTests/Util/ProcessHelperTest.cs | 9 ++- 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs index 0677549..562b17a 100644 --- a/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs +++ b/src/Plugins/RunawayProcessKiller/RunawayProcessKillerExtension.cs @@ -38,9 +38,11 @@ namespace winsw.Plugins.RunawayProcessKiller // Default initializer } - public RunawayProcessKillerExtension(String pidfile) + public RunawayProcessKillerExtension(String pidfile, int stopTimeoutMs = 5000, bool stopParentFirst = false) { this.Pidfile = pidfile; + this.StopTimeout = TimeSpan.FromMilliseconds(5000); + this.StopParentProcessFirst = stopParentFirst; } public override void Configure(ServiceDescriptor descriptor, XmlNode node) @@ -89,6 +91,7 @@ namespace winsw.Plugins.RunawayProcessKiller } // Now check the process + Logger.DebugFormat("Checking the potentially runaway process with PID={0}", pid); Process proc; try { @@ -113,11 +116,12 @@ namespace winsw.Plugins.RunawayProcessKiller else { Logger.Warn("The process " + pid + " has no " + expectedEnvVarName + " environment variable defined. " - + "The process has not been started by this service, hence it won't be terminated."); + + "The process has not been started by WinSW, hence it won't be terminated."); if (Logger.IsDebugEnabled) { - foreach (string key in previousProcessEnvVars.Keys) { - Logger.Debug("Env var of " + pid + ": " + key + "=" + previousProcessEnvVars[key]); - } + //TODO replace by String.Join() in .NET 4 + String[] keys = new String[previousProcessEnvVars.Count]; + previousProcessEnvVars.Keys.CopyTo(keys, 0); + Logger.DebugFormat("Env vars of the process with PID={0}: {1}", new Object[] {pid, String.Join(",", keys)}); } return; } diff --git a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs index 0ea5022..efa56cc 100644 --- a/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs +++ b/src/Test/winswTests/Extensions/RunawayProcessKillerTest.cs @@ -3,6 +3,12 @@ using NUnit.Framework; using winsw.Extensions; using winsw.Plugins.SharedDirectoryMapper; using winsw.Plugins.RunawayProcessKiller; +using winswTests.Util; +using System.IO; +using System.Diagnostics; +using winsw.Util; +using System; +using System.Collections.Generic; namespace winswTests.Extensions { @@ -58,5 +64,60 @@ namespace winswTests.Extensions manager.FireOnWrapperStarted(); manager.FireBeforeWrapperStopped(); } + + [Test] + public void ShouldKillTheSpawnedProcess() + { + var winswId = "myAppWithRunaway"; + var extensionId = "runawayProcessKiller"; + var tmpDir = FilesystemTestHelper.CreateTmpDirectory(); + + // Prepare the env var + String varName = WinSWSystem.ENVVAR_NAME_SERVICE_ID; + var env = new Dictionary(); + env.Add("varName", winswId); + + // Spawn the test process + var scriptFile = Path.Combine(tmpDir, "dosleep.bat"); + var envFile = Path.Combine(tmpDir, "env.txt"); + File.WriteAllText(scriptFile, "set > " + envFile + "\nsleep 100500"); + Process proc = new Process(); + var ps = proc.StartInfo; + ps.FileName = scriptFile; + ProcessHelper.StartProcessAndCallbackForExit(proc, envVars: env); + + try + { + // Generate extension and ensure that the roundtrip is correct + var pidfile = Path.Combine(tmpDir, "process.pid"); + var sd = ConfigXmlBuilder.create(id: winswId).WithRunawayProcessKiller(new RunawayProcessKillerExtension(pidfile), extensionId).ToServiceDescriptor(); + WinSWExtensionManager manager = new WinSWExtensionManager(sd); + manager.LoadExtensions(); + var extension = manager.Extensions[extensionId] as RunawayProcessKillerExtension; + Assert.IsNotNull(extension, "RunawayProcessKillerExtension should be loaded"); + Assert.AreEqual(pidfile, extension.Pidfile, "PidFile should have been retained during the config roundtrip"); + + // Inject PID + File.WriteAllText(pidfile, proc.Id.ToString()); + + // Try to terminate + Assert.That(!proc.HasExited, "Process " + proc + " has exited before the RunawayProcessKiller extension invocation"); + extension.OnWrapperStarted(); + Assert.That(proc.HasExited, "Process " + proc + " should have been terminated by RunawayProcessKiller"); + } + finally + { + if (!proc.HasExited) + { + Console.Error.WriteLine("Test: Killing runaway process with ID=" + proc.Id); + ProcessHelper.StopProcessAndChildren(proc.Id, TimeSpan.FromMilliseconds(100), false); + if (!proc.HasExited) + { + // The test is failed here anyway, but we add additional diagnostics info + Console.Error.WriteLine("Test: ProcessHelper failed to properly terminate process with ID=" + proc.Id); + } + } + } + } } } diff --git a/src/Test/winswTests/Util/ConfigXmlBuilder.cs b/src/Test/winswTests/Util/ConfigXmlBuilder.cs index 15c3818..9416a29 100644 --- a/src/Test/winswTests/Util/ConfigXmlBuilder.cs +++ b/src/Test/winswTests/Util/ConfigXmlBuilder.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Text; using winsw; +using winsw.Plugins.RunawayProcessKiller; +using winswTests.Extensions; namespace winswTests.Util { @@ -16,6 +18,7 @@ namespace winswTests.Util public string Executable { get; set; } public bool PrintXMLVersion { get; set; } public string XMLComment { get; set; } + public List ExtensionXmls { get; private set; } private List configEntries; @@ -23,6 +26,7 @@ namespace winswTests.Util private ConfigXmlBuilder() { configEntries = new List(); + ExtensionXmls = new List(); } public static ConfigXmlBuilder create(string id = null, string name = null, @@ -63,6 +67,18 @@ namespace winswTests.Util // We do not care much about pretty formatting here str.AppendFormat(" {0}\n", entry); } + + // Extensions + if (ExtensionXmls.Count > 0) + { + str.Append(" \n"); + foreach (string xml in ExtensionXmls) + { + str.Append(xml); + } + str.Append(" \n"); + } + str.Append("\n"); string res = str.ToString(); if (dumpConfig) @@ -88,5 +104,19 @@ namespace winswTests.Util { return WithRawEntry(String.Format("<{0}>{1}", tagName, value)); } + + public ConfigXmlBuilder WithRunawayProcessKiller(RunawayProcessKillerExtension ext, string extensionId = "killRunawayProcess", bool enabled = true) + { + var fullyQualifiedExtensionName = ExtensionTestBase.getExtensionClassNameWithAssembly(typeof(RunawayProcessKillerExtension)); + StringBuilder str = new StringBuilder(); + str.AppendFormat(" \n", new Object[] { enabled, fullyQualifiedExtensionName, extensionId}); + str.AppendFormat(" {0}\n", ext.Pidfile); + str.AppendFormat(" {0}\n", ext.StopTimeout.TotalMilliseconds); + str.AppendFormat(" {0}\n", ext.StopParentProcessFirst); + str.Append( " \n"); + ExtensionXmls.Add(str.ToString()); + + return this; + } } } diff --git a/src/Test/winswTests/Util/FilesystemTestHelper.cs b/src/Test/winswTests/Util/FilesystemTestHelper.cs index 63c1fb3..00bf373 100644 --- a/src/Test/winswTests/Util/FilesystemTestHelper.cs +++ b/src/Test/winswTests/Util/FilesystemTestHelper.cs @@ -1,4 +1,5 @@ -using System; +using NUnit.Framework; +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -29,7 +30,15 @@ namespace winswTests.Util Dictionary res = new Dictionary(); var lines = File.ReadAllLines(filePath); foreach(var line in lines) { - line.Split("=".ToCharArray(), 2); + var parsed = line.Split("=".ToCharArray(), 2); + if (parsed.Length == 2) + { + res.Add(parsed[0], parsed[1]); + } + else + { + Assert.Fail("Wrong line in the parsed Set output file: " + line); + } } return res; } diff --git a/src/Test/winswTests/Util/ProcessHelperTest.cs b/src/Test/winswTests/Util/ProcessHelperTest.cs index d537e26..9bff216 100644 --- a/src/Test/winswTests/Util/ProcessHelperTest.cs +++ b/src/Test/winswTests/Util/ProcessHelperTest.cs @@ -36,9 +36,12 @@ namespace winswTests.Util // Check several veriables, which are expected to be in Uppercase var envVars = FilesystemTestHelper.parseSetOutput(envFile); - Assert.That(envVars.ContainsKey("PROCESSOR_ARCHITECTURE"), "No PROCESSOR_ARCHITECTURE in the injected vars"); - Assert.That(envVars.ContainsKey("COMPUTERNAME"), "No COMPUTERNAME in the injected vars"); - Assert.That(envVars.ContainsKey("PATHEXT"), "No PATHEXT in the injected vars"); + String[] keys = new String[envVars.Count]; + envVars.Keys.CopyTo(keys, 0); + String availableVars = "[" + String.Join(",", keys) + "]"; + Assert.That(envVars.ContainsKey("PROCESSOR_ARCHITECTURE"), "No PROCESSOR_ARCHITECTURE in the injected vars: " + availableVars); + Assert.That(envVars.ContainsKey("COMPUTERNAME"), "No COMPUTERNAME in the injected vars: " + availableVars); + Assert.That(envVars.ContainsKey("PATHEXT"), "No PATHEXT in the injected vars: " + availableVars); // And just ensure that the parsing logic is case-sensitive Assert.That(!envVars.ContainsKey("computername"), "Test error: the environment parsing logic is case-insensitive");