Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
*/
"use strict";
// Unit tests for Windows scheduled task generation.
const { updateAppInfo } = ChromeUtils.importESModule(
);
updateAppInfo();
const { TaskScheduler } = ChromeUtils.importESModule(
"resource://gre/modules/TaskScheduler.sys.mjs"
);
const { WinImpl } = ChromeUtils.importESModule(
"resource://gre/modules/TaskSchedulerWinImpl.sys.mjs"
);
const WinSvc = Cc["@mozilla.org/win-task-scheduler-service;1"].getService(
Ci.nsIWinTaskSchedulerService
);
const uuidGenerator = Services.uuid;
function randomName() {
return (
"moz-taskschd-test-" + uuidGenerator.generateUUID().toString().slice(1, -1)
);
}
const gFolderName = randomName();
// Override task folder name, to prevent colliding with other tests.
WinImpl._taskFolderName = function () {
return gFolderName;
};
WinImpl._taskFolderNameParts = function () {
return {
parentName: "\\",
subName: gFolderName,
};
};
registerCleanupFunction(async () => {
await TaskScheduler.deleteAllTasks();
});
add_task(async function test_create() {
const taskName = "test-task-1";
const rawTaskName = WinImpl._formatTaskName(taskName);
const folderName = WinImpl._taskFolderName();
const exePath = "C:\\Program Files\\XYZ\\123.exe";
const workingDir = "C:\\Program Files\\XYZ";
const argsIn = [
"x.txt",
"c:\\x.txt",
'C:\\"HELLO WORLD".txt',
"only space.txt",
];
const expectedArgsOutStr = [
"x.txt",
"c:\\x.txt",
'"C:\\\\\\"HELLO WORLD\\".txt"',
'"only space.txt"',
].join(" ");
const description = "Entities: < &. Non-ASCII: abc😀def.";
const intervalSecsIn = 2 * 60 * 60; // 2 hours
const expectedIntervalOutWin10 = "PT2H"; // Windows 10 regroups by hours and minutes
const expectedIntervalOutWin7 = `PT${intervalSecsIn}S`; // Windows 7 doesn't regroup
await TaskScheduler.registerTask(taskName, exePath, intervalSecsIn, {
disabled: true,
args: argsIn,
description,
workingDirectory: workingDir,
});
// Read back the task
const readBackXML = WinSvc.getTaskXML(folderName, rawTaskName);
const parser = new DOMParser();
const doc = parser.parseFromString(readBackXML, "text/xml");
Assert.equal(doc.documentElement.tagName, "Task");
// Check for the values set above
Assert.equal(doc.querySelector("Actions Exec Command").textContent, exePath);
Assert.equal(
doc.querySelector("Actions Exec WorkingDirectory").textContent,
workingDir
);
Assert.equal(
doc.querySelector("Actions Exec Arguments").textContent,
expectedArgsOutStr
);
Assert.equal(
doc.querySelector("RegistrationInfo Description").textContent,
description
);
Assert.equal(
doc.querySelector("RegistrationInfo Author").textContent,
Services.appinfo.vendor
);
Assert.equal(doc.querySelector("Settings Enabled").textContent, "false");
// Note: It's a little too tricky to check for a specific StartBoundary value reliably here, given
// that it gets set relative to Date.now(), so I'm skipping that.
const intervalOut = doc.querySelector(
"Triggers TimeTrigger Repetition Interval"
).textContent;
Assert.ok(
intervalOut == expectedIntervalOutWin7 ||
intervalOut == expectedIntervalOutWin10
);
// Validate the XML
WinSvc.validateTaskDefinition(readBackXML);
// Update
const updatedExePath = "C:\\Program Files (x86)\\ABC\\foo.exe";
const updatedIntervalSecsIn = 3 * 60 * 60; // 3 hours
const expectedUpdatedIntervalOutWin10 = "PT3H";
const expectedUpdatedIntervalOutWin7 = `PT${updatedIntervalSecsIn}S`;
await TaskScheduler.registerTask(
taskName,
updatedExePath,
updatedIntervalSecsIn,
{
disabled: true,
args: argsIn,
description,
workingDirectory: workingDir,
}
);
// Read back the updated task
const readBackUpdatedXML = WinSvc.getTaskXML(folderName, rawTaskName);
const updatedDoc = parser.parseFromString(readBackUpdatedXML, "text/xml");
Assert.equal(updatedDoc.documentElement.tagName, "Task");
// Check for updated values
Assert.equal(
updatedDoc.querySelector("Actions Exec Command").textContent,
updatedExePath
);
Assert.notEqual(
doc.querySelector("Triggers TimeTrigger StartBoundary").textContent,
updatedDoc.querySelector("Triggers TimeTrigger StartBoundary").textContent
);
const updatedIntervalOut = updatedDoc.querySelector(
"Triggers TimeTrigger Repetition Interval"
).textContent;
Assert.ok(
updatedIntervalOut == expectedUpdatedIntervalOutWin7 ||
updatedIntervalOut == expectedUpdatedIntervalOutWin10
);
// Check that the folder really was there
{
const { parentName, subName } = WinImpl._taskFolderNameParts();
let threw;
try {
WinSvc.deleteFolder(parentName, subName);
} catch (ex) {
threw = ex;
}
Assert.equal(threw.result, Cr.NS_ERROR_FILE_DIR_NOT_EMPTY);
}
// Delete
await TaskScheduler.deleteAllTasks();
// Check that the folder is gone
{
const { parentName, subName } = WinImpl._taskFolderNameParts();
let threw;
try {
WinSvc.deleteFolder(parentName, subName);
} catch (ex) {
threw = ex;
}
Assert.equal(threw.result, Cr.NS_ERROR_FILE_NOT_FOUND);
}
// Format and validate the XML with the task not disabled
const enabledXML = WinImpl._formatTaskDefinitionXML(exePath, intervalSecsIn, {
args: argsIn,
description,
workingDirectory: workingDir,
});
Assert.equal(WinSvc.validateTaskDefinition(enabledXML), 0 /* S_OK */);
// Format and validate with no options
const basicXML = WinImpl._formatTaskDefinitionXML(
"foo",
TaskScheduler.MIN_INTERVAL_SECONDS
);
Assert.equal(WinSvc.validateTaskDefinition(basicXML), 0 /* S_OK */);
});
add_task(async function test_migrate() {
// Create task name with nameVersion1
const taskName = "test-task-1";
const rawTaskNameV1 = WinImpl._formatTaskName(taskName, { nameVersion: 1 });
const rawTaskNameV2 = WinImpl._formatTaskName(taskName, { nameVersion: 2 });
const folderName = WinImpl._taskFolderName();
const exePath = "C:\\Program Files\\XYZ\\123.exe";
const workingDir = "C:\\Program Files\\XYZ";
const argsIn = [
"x.txt",
"c:\\x.txt",
'C:\\"HELLO WORLD".txt',
"only space.txt",
];
const expectedArgsOutStr = [
"x.txt",
"c:\\x.txt",
'"C:\\\\\\"HELLO WORLD\\".txt"',
'"only space.txt"',
].join(" ");
const description = "Entities: < &. Non-ASCII: abc😀def.";
const intervalSecsIn = 2 * 60 * 60; // 2 hours
const expectedIntervalOut = "PT2H"; // 2 hours
const queries = [
["Actions Exec Command", exePath],
["Actions Exec WorkingDirectory", workingDir],
["Actions Exec Arguments", expectedArgsOutStr],
["RegistrationInfo Description", description],
["RegistrationInfo Author", Services.appinfo.vendor],
["Settings Enabled", "false"],
["Triggers TimeTrigger Repetition Interval", expectedIntervalOut],
];
await TaskScheduler.registerTask(taskName, exePath, intervalSecsIn, {
disabled: true,
args: argsIn,
description,
workingDirectory: workingDir,
nameVersion: 1,
});
ok(
WinImpl.taskExists(taskName, { nameVersion: 1 }),
"Task exists with nameVersion1"
);
const originalTaskXML = WinSvc.getTaskXML(folderName, rawTaskNameV1);
const parser = new DOMParser();
const docV1 = parser.parseFromString(originalTaskXML, "text/xml");
Assert.equal(docV1.documentElement.tagName, "Task");
// Check for the values set above
for (let [sel, expected] of queries) {
Assert.equal(
docV1.querySelector(sel).textContent,
expected,
`Task V1 ${sel} had expected textContent`
);
}
// Update task name format to nameVersion2
WinImpl._updateTaskNameFormat(taskName);
ok(
WinImpl.taskExists(taskName, { nameVersion: 2 }),
"Task exists with nameVersion2"
);
ok(
!WinImpl.taskExists(taskName, { nameVersion: 1 }),
"Task with nameVersion1 successfully deleted"
);
// Check that the new task XML is still valid
const newTaskXML = WinSvc.getTaskXML(folderName, rawTaskNameV2);
Assert.equal(WinSvc.validateTaskDefinition(newTaskXML), 0 /* S_OK */);
const docV2 = parser.parseFromString(newTaskXML, "text/xml");
Assert.equal(docV2.documentElement.tagName, "Task");
// Check that the updated values still match the provided ones.
for (let [sel, expected] of queries) {
Assert.equal(
docV2.querySelector(sel).textContent,
expected,
`Task V2 ${sel} had expected textContent`
);
}
});