[JENKINS-42744] - Improve the ProcessHelperTes, add RunawayProcessKillerTest for the affected logic

pull/202/head
Oleg Nenashev 2017-03-31 15:10:57 +02:00
parent 9fc518a3d0
commit bca9bafc66
5 changed files with 117 additions and 10 deletions

View File

@ -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;
}

View File

@ -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<string, string>();
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);
}
}
}
}
}
}

View File

@ -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<string> ExtensionXmls { get; private set; }
private List<String> configEntries;
@ -23,6 +26,7 @@ namespace winswTests.Util
private ConfigXmlBuilder()
{
configEntries = new List<string>();
ExtensionXmls = new List<string>();
}
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(" <extensions>\n");
foreach (string xml in ExtensionXmls)
{
str.Append(xml);
}
str.Append(" </extensions>\n");
}
str.Append("</service>\n");
string res = str.ToString();
if (dumpConfig)
@ -88,5 +104,19 @@ namespace winswTests.Util
{
return WithRawEntry(String.Format("<{0}>{1}</{0}>", 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(" <extension enabled=\"{0}\" className=\"{1}\" id=\"{2}\">\n", new Object[] { enabled, fullyQualifiedExtensionName, extensionId});
str.AppendFormat(" <pidfile>{0}</pidfile>\n", ext.Pidfile);
str.AppendFormat(" <stopTimeout>{0}</stopTimeout>\n", ext.StopTimeout.TotalMilliseconds);
str.AppendFormat(" <stopParentFirst>{0}</stopParentFirst>\n", ext.StopParentProcessFirst);
str.Append( " </extension>\n");
ExtensionXmls.Add(str.ToString());
return this;
}
}
}

View File

@ -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<string, string> res = new Dictionary<string, string>();
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;
}

View File

@ -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");