UNPKG

gpii-windows

Version:

Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™

433 lines (346 loc) 15.8 kB
/* * 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 */ "use strict"; 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); });