gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
433 lines (346 loc) • 15.8 kB
JavaScript
/*
* Windows Kill Process Unit Tests
*
* Copyright 2015 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The research leading to these results has received funding from the European Union's
* Seventh Framework Programme (FP7/2007-2013)
* under grant agreement no. 289016.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
;
var fluid = require("gpii-universal");
var jqUnit = fluid.require("node-jqunit");
var gpii = fluid.registerNamespace("gpii");
var shelljs = require("shelljs"),
path = require("path"),
os = require("os"),
fs = require("fs"),
child_process = require("child_process");
require("../processHandling.js");
fluid.registerNamespace("gpii.tests.windows.processHandling");
var waitExe = "gpii-process-handling-test.exe";
var waitExePath = null;
var teardowns = [];
jqUnit.module("gpii.tests.windows.processHandling", {
setup: function () {
// Take a copy of the built-in "waitfor" command, to ensure a unique process name.
// This command is a glorified "sleep" that is also able to be terminated "nicely".
waitExePath = path.join(process.env.TEMP, waitExe);
shelljs.cp(path.join(process.env.SystemRoot, "/System32/waitfor.exe"), waitExePath);
},
teardown: function () {
while (teardowns.length) {
teardowns.pop()();
}
if (waitExePath !== null) {
gpii.windows.killProcessByName(waitExe);
shelljs.rm(waitExePath);
}
}
});
jqUnit.test("Testing isProcessRunning", function () {
// Check a PID that isn't running.
var running = gpii.windows.isProcessRunning(-1);
jqUnit.assertFalse("Process should not have been found running", running);
// Check a PID that is running.
running = gpii.windows.isProcessRunning(process.pid);
jqUnit.assertTrue("Process should have been found running", running);
// Check an exe file that isn't running.
running = gpii.windows.isProcessRunning("a non-matching process.exe");
jqUnit.assertFalse("Process should not have been found running", running);
// Check an exe file that is running.
running = gpii.windows.isProcessRunning("node.exe");
jqUnit.assertTrue("Process should have been found running", running);
// Check multiple processes. There's always more than one svchost.exe running on Windows.
running = gpii.windows.isProcessRunning("svchost.exe", true);
jqUnit.assertTrue("Process should have been found running (multiple processes)", running);
});
jqUnit.asyncTest("Testing timeout waiting for processes start", function () {
jqUnit.expect(1);
var exe = "a non-matching process.exe";
gpii.windows.waitForProcessStart(exe, { timeout: 100 })
.then(function () {
jqUnit.fail("The process '" + exe + "' should not be running");
}, function () {
jqUnit.assert("Should have timed out");
jqUnit.start();
});
});
jqUnit.asyncTest("Testing timeout waiting for processes termination", function () {
jqUnit.expect(1);
var exe = "node.exe";
gpii.windows.waitForProcessTermination(exe, { timeout: 100 })
.then(function () {
jqUnit.fail("The process '" + exe + "' should not have terminated");
}, function () {
jqUnit.assert("Should have timed out");
jqUnit.start();
});
});
jqUnit.asyncTest("Testing waiting for processes start and end", function () {
jqUnit.expect(3);
var running = gpii.processReporter.find(waitExe);
jqUnit.assertFalse("The process should not already be running.", running);
// Timeout waiting for the process start/end after 10 seconds.
var options = { timeout: 10000 };
// Wait for it to start.
gpii.windows.waitForProcessStart(waitExe, options)
.then(function () {
jqUnit.assert("We just started the new process.");
// Wait for it to die
gpii.windows.waitForProcessTermination(waitExe, options)
.then(function () {
jqUnit.assert("Child process terminated.");
jqUnit.start();
}, function () {
jqUnit.fail("Failed to detect process termination.");
});
// Tell the process to stop now.
child_process.execSync("waitfor /SI waitForProcessTerminationTest");
}, function () {
jqUnit.fail("Failed to detect process start.");
});
// Create the process, with a 5 second timeout
child_process.exec(waitExePath + " waitForProcessTerminationTest /T 5 > nul");
});
jqUnit.asyncTest("Testing Killing Processes", function () {
jqUnit.expect(3);
var running = gpii.processReporter.find(waitExe);
jqUnit.assertFalse("The process should not already be running.", running);
// On the call below, async is true because if it is false shelljs will
// wait around until it is manually killed before continuing with the
// rest of the tests.
var command = waitExePath + " killProcessByNameTest /T 30";
fluid.log("Executing " + command);
child_process.exec(command, function (error, stdout, stderr) {
fluid.log("Exit code:", error.code);
jqUnit.assertEquals("Process should have terminated with code SIGKILL", 9, error.code);
fluid.log("Program output:", stdout);
fluid.log("Program stderr:", stderr);
jqUnit.start();
});
// Kill the process when it starts
gpii.windows.waitForProcessStart(waitExe)
.then(function () {
jqUnit.assert("The process has started");
gpii.windows.killProcessByName(waitExe);
});
});
jqUnit.asyncTest("Testing closeProcessByName", function () {
jqUnit.expect(3);
var exitCode = 5;
// Start a process that creates a window.
var exeName = "test-window.exe";
var exePath = path.join(__dirname, exeName);
var command = exePath + " -window";
fluid.log("Executing " + command);
var child = child_process.exec(command, function (error) {
fluid.log("Exit code:", error.code);
jqUnit.assertEquals("Exit code", exitCode, error.code);
// If our exitCode is not as expected, we are unlikely to get to the next set of checks and might as well start the clock for failure.
if (exitCode !== error.code) { jqUnit.start(); }
});
child.stdout.on("data", function (data) {
// Wait for the window to be created
if (data.match("Window created")) {
jqUnit.assert("Window is created");
gpii.windows.closeProcessByName(exeName, {cleanOnly: true, timeout: 1000, exitCode: exitCode})
.then(function (clean) {
jqUnit.assertTrue("Should have closed cleanly", clean);
jqUnit.start();
}, function (e) {
fluid.log(e.message);
jqUnit.fail("Process should have closed.");
gpii.windows.killProcessByName(exeName);
});
}
});
});
jqUnit.asyncTest("Testing closeProcessByName (window-less process)", function () {
jqUnit.expect(3);
// Start a process that does not have a window.
gpii.windows.waitForProcessStart(waitExe)
.then(function () {
jqUnit.assert("Process started");
// The process doesn't have any windows, so this should fail to terminate it.
gpii.windows.closeProcessByName(waitExe, {cleanOnly: true})
.then(function () {
jqUnit.fail("Process should not have closed.");
}, function (e) {
jqUnit.assert("Process not closed");
fluid.log(e.message);
// Retry with force
gpii.windows.closeProcessByName(waitExe, {cleanOnly: false})
.then(function () {
jqUnit.assert("Process terminated");
jqUnit.start();
});
});
});
var command = waitExePath + " closeProcessByNameTest /T 30";
fluid.log("Executing " + command);
child_process.exec(command);
});
jqUnit.test("Testing getServiceState", function () {
// Local Session Manager will always be running.
var state = gpii.windows.getServiceState("LSM");
jqUnit.assertEquals("LSM service should be running", "running", state);
// There's a chance it might be running, but who sends faxes anymore?
state = gpii.windows.getServiceState("Fax");
jqUnit.assertEquals("Fax service should be stopped", "stopped", state);
state = gpii.windows.getServiceState("gpii-unknown");
jqUnit.assertEquals("gpii-unknown service should be unknown", "unknown", state);
});
// Check if getExplorerProcess returns an explorer.exe pid.
jqUnit.test("Testing getExplorerProcess", function () {
var explorerPid = gpii.windows.getExplorerProcess();
jqUnit.assertFalse("explorer PID must be a number", isNaN(explorerPid));
var isRunning = gpii.windows.isProcessRunning(explorerPid);
jqUnit.assertTrue("explorer pid must be a running process", isRunning);
var processes = gpii.processes.windows.getProcessList(explorerPid);
jqUnit.assertEquals("getProcessList should return 1 process", 1, processes.length);
jqUnit.assertEquals("process name should be explorer.exe", "explorer.exe", processes[0].command.toLowerCase());
});
jqUnit.asyncTest("Testing stopExplorer", function () {
jqUnit.expect(5);
// Instead of killing the real explorer, start the test window which will accept the same message that the tasktray
// window does.
// The test window will record the message, but will remain so the more forceful termination can also be tested.
// Start a process that creates a window.
var exeName = "test-window.exe";
var exePath = path.join(__dirname, exeName);
var command = exePath + " -window";
var stopExplorerPromise = fluid.promise();
var tasktrayClosePromise = fluid.promise();
fluid.log("Executing " + command);
var child = child_process.exec(command, function (error) {
fluid.log("Exit code:", error.code);
jqUnit.assertEquals("Exit code", 5, error.code);
if (error.code !== 5) {
jqUnit.fail();
} else {
// This test shouldn't have stopped the real explorer.
jqUnit.assertTrue("Explorer should still be running.", gpii.windows.getExplorerProcess());
fluid.promise.sequence([stopExplorerPromise, tasktrayClosePromise]).then(jqUnit.start);
}
});
child.stdout.on("data", function (data) {
fluid.log(data);
// Wait for the window to be created
var match = data.match(/Window created.*hwnd=([0-9]+)/);
if (match) {
// Window was created - get the handle.
var testWindow = match[1];
var p = gpii.windows.stopExplorer({
trayWindow: testWindow,
timeout: 1000,
ignoreFolders: true,
explorerExe: exeName
});
jqUnit.assertTrue("stopExplorer should return a promise", fluid.isPromise(p));
p.then(function () {
jqUnit.assert("stopExplorer resolved");
stopExplorerPromise.resolve();
}, fluid.fail);
} else if (data.indexOf("tasktray close") >= 0) {
// The "tasktray close" message was received.
jqUnit.assert("stopExplorer attempted clean shutdown");
tasktrayClosePromise.resolve();
}
});
});
jqUnit.asyncTest("Testing restartExplorer", function () {
jqUnit.expect(4);
var explorerPid = gpii.windows.getExplorerProcess();
jqUnit.assertTrue("Explorer should be running", explorerPid);
var exeName = "test-window.exe";
var exePath = path.join(__dirname, exeName);
jqUnit.assertFalse("There shouldn't be a test-window process running.", gpii.windows.isProcessRunning(exeName));
teardowns.push(function () {
gpii.windows.killProcessByName(exeName);
});
// Setting trayWindow to INVALID_HANDLE_VALUE will prevent restartExplorer from hiding the real taskbar.
var INVALID_HANDLE_VALUE = 0xffffffff;
var restartPromise = gpii.windows.restartExplorer({
trayWindow: INVALID_HANDLE_VALUE,
explorer: exePath,
args: [ "-window" ],
ignoreFolders: true
});
jqUnit.assertTrue("restartExplorer should return a promise", fluid.isPromise(restartPromise));
restartPromise.then(null, jqUnit.fail);
// restartExplorer could resolve either before or after the test process is started, because it detects real
// explorer (which is already running).
fluid.promise.sequence([
restartPromise,
gpii.windows.waitForProcessStart(exeName)
]).then(function () {
// This test shouldn't have touched explorer.
jqUnit.assertEquals("Explorer should still be running.", explorerPid, gpii.windows.getExplorerProcess());
jqUnit.start();
});
});
jqUnit.test("Testing getProcessPath", function () {
var pid = gpii.windows.getExplorerProcess();
var path = gpii.windows.getProcessPath(pid);
jqUnit.assertEquals("Path for explorer be correct", "c:\\windows\\explorer.exe", path.toLowerCase());
// Try the "System" process.
var path2 = gpii.windows.getProcessPath(4);
jqUnit.assertEquals("Path for explorer be correct", "system", path2.toLowerCase());
});
// Test startProcess starts the process with the correct window state.
jqUnit.asyncTest("Testing startProcess", function () {
var exeName = "test-window.exe";
var exePath = path.join(__dirname, exeName);
var tests = [
{
windowState: "hide",
expect: gpii.windows.API_constants.SW_HIDE
}, {
windowState: "normal",
expect: gpii.windows.API_constants.SW_SHOWNORMAL
}, {
windowState: "minimized",
expect: gpii.windows.API_constants.SW_SHOWMINIMIZED
}, {
windowState: "maximized",
expect: gpii.windows.API_constants.SW_SHOWMAXIMIZED
}, {
windowState: "noactivate",
expect: gpii.windows.API_constants.SW_SHOWNOACTIVATE
}
];
jqUnit.expect(tests.length * 3);
var tempFiles = [];
teardowns.push(function () {
shelljs.rm(tempFiles);
});
var runTest = function (testIndex) {
var test = tests[testIndex];
if (!test) {
jqUnit.start();
return;
}
var tempFile = path.join(os.tmpdir(), "gpii-startprocess" + Math.random());
tempFiles.push(tempFile);
var p = gpii.windows.startProcess(exePath, ["-windowState", tempFile], {windowState: test.windowState});
jqUnit.assertEquals("startProcess should return a number", "number", typeof(p));
jqUnit.assertTrue("startProcess should return a process ID", p > 0);
gpii.windows.waitForProcessTermination(p, 15000).then(function (result) {
if (result === "timeout") {
jqUnit.fail("Timeout waiting for test-window.exe to finish");
}
var actualWindowState = parseInt(fs.readFileSync(tempFile, "utf8"));
jqUnit.assertEquals("wShowWindow in the child window must be the expected value",
test.expect, actualWindowState);
runTest(testIndex + 1);
}, jqUnit.fail);
};
runTest(0);
});