summaryrefslogtreecommitdiffstats
path: root/toolkit/components/taskscheduler/tests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/taskscheduler/tests')
-rw-r--r--toolkit/components/taskscheduler/tests/xpcshell/test_TaskScheduler.js55
-rw-r--r--toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js112
-rw-r--r--toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js206
-rw-r--r--toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js187
-rw-r--r--toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini13
5 files changed, 573 insertions, 0 deletions
diff --git a/toolkit/components/taskscheduler/tests/xpcshell/test_TaskScheduler.js b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskScheduler.js
new file mode 100644
index 0000000000..74ad1bf197
--- /dev/null
+++ b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskScheduler.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+// Cross-platform task scheduler tests.
+//
+// There's not much that can be done here without allowing the task to run, so this
+// only touches on the basics of argument checking. On platforms without a task
+// scheduler implementation, these interfaces currently do nothing else.
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+const { TaskScheduler } = ChromeUtils.import(
+ "resource://gre/modules/TaskScheduler.jsm"
+);
+
+registerCleanupFunction(async () => {
+ await TaskScheduler.deleteAllTasks();
+});
+
+add_task(async function test_gen() {
+ await TaskScheduler.registerTask(
+ "FOO",
+ "xyz",
+ TaskScheduler.MIN_INTERVAL_SECONDS,
+ {
+ disabled: true,
+ }
+ );
+
+ Assert.equal(
+ await TaskScheduler.taskExists("FOO"),
+ true,
+ "Task should exist after we created it."
+ );
+
+ await TaskScheduler.deleteTask("FOO");
+
+ Assert.equal(
+ await TaskScheduler.taskExists("FOO"),
+ false,
+ "Task should not exist after we deleted it."
+ );
+
+ await Assert.rejects(
+ TaskScheduler.registerTask("BAR", "123", 1, {
+ disabled: true,
+ }),
+ /Interval is too short/
+ );
+});
diff --git a/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js
new file mode 100644
index 0000000000..921f91207e
--- /dev/null
+++ b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+// Unit tests for macOS scheduled task generation.
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+const { TaskScheduler } = ChromeUtils.import(
+ "resource://gre/modules/TaskScheduler.jsm"
+);
+const { MacOSImpl } = ChromeUtils.import(
+ "resource://gre/modules/TaskSchedulerMacOSImpl.jsm"
+);
+
+function getFirefoxExecutableFilename() {
+ if (AppConstants.platform === "win") {
+ return AppConstants.MOZ_APP_NAME + ".exe";
+ }
+ return AppConstants.MOZ_APP_NAME;
+}
+
+// Returns a nsIFile to the firefox.exe (really, application) executable file.
+function getFirefoxExecutableFile() {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+
+ file.append(getFirefoxExecutableFilename());
+ return file;
+}
+
+const uuidGenerator = Services.uuid;
+
+function randomName() {
+ return (
+ "moz-taskschd-test-" +
+ uuidGenerator
+ .generateUUID()
+ .toString()
+ .slice(1, -1)
+ );
+}
+
+add_task(async function test_all() {
+ let labels;
+ Assert.notEqual(await MacOSImpl._uid(), 0, "Should not be running as root");
+
+ let id1 = randomName();
+ let id2 = randomName();
+ Assert.notEqual(id1, id2, "Random labels should not collide");
+
+ await MacOSImpl.registerTask(
+ id1,
+ getFirefoxExecutableFile().path,
+ TaskScheduler.MIN_INTERVAL_SECONDS,
+ { disabled: true }
+ );
+
+ await MacOSImpl.registerTask(
+ id2,
+ getFirefoxExecutableFile().path,
+ TaskScheduler.MIN_INTERVAL_SECONDS,
+ { disabled: true }
+ );
+
+ let label1 = MacOSImpl._formatLabelForThisApp(id1);
+ let label2 = MacOSImpl._formatLabelForThisApp(id2);
+
+ // We don't assert equality because there may be existing tasks, concurrent
+ // tests, etc. This also means we can't reasonably tests `deleteAllTasks()`.
+ labels = await MacOSImpl._listAllLabelsForThisApp();
+ Assert.ok(
+ labels.includes(label1),
+ `Task ${label1} should have been registered in ${JSON.stringify(labels)}`
+ );
+ Assert.ok(
+ labels.includes(label2),
+ `Task ${label2} should have been registered in ${JSON.stringify(labels)}`
+ );
+
+ Assert.ok(await MacOSImpl.deleteTask(id1));
+
+ labels = await MacOSImpl._listAllLabelsForThisApp();
+ Assert.ok(
+ !labels.includes(label1),
+ `Task ${label1} should no longer be registered in ${JSON.stringify(labels)}`
+ );
+ Assert.ok(
+ labels.includes(label2),
+ `Task ${label2} should still be registered in ${JSON.stringify(labels)}`
+ );
+
+ Assert.ok(await MacOSImpl.deleteTask(id2));
+
+ labels = await MacOSImpl._listAllLabelsForThisApp();
+ Assert.ok(
+ !labels.includes(label1),
+ `Task ${label1} should no longer be registered in ${JSON.stringify(labels)}`
+ );
+ Assert.ok(
+ !labels.includes(label2),
+ `Task ${label2} should no longer be registered in ${JSON.stringify(labels)}`
+ );
+});
diff --git a/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js
new file mode 100644
index 0000000000..438c5d397f
--- /dev/null
+++ b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+// Unit tests for Windows scheduled task generation.
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+const { TaskScheduler } = ChromeUtils.import(
+ "resource://gre/modules/TaskScheduler.jsm"
+);
+
+const { WinImpl } = ChromeUtils.import(
+ "resource://gre/modules/TaskSchedulerWinImpl.jsm"
+);
+
+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 */);
+});
diff --git a/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js b/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js
new file mode 100644
index 0000000000..ac0ccad670
--- /dev/null
+++ b/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js
@@ -0,0 +1,187 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+// Unit tests for access to the Windows Task Scheduler via nsIWinTaskSchedulerService.
+
+const svc = Cc["@mozilla.org/win-task-scheduler-service;1"].getService(
+ Ci.nsIWinTaskSchedulerService
+);
+
+function randomName() {
+ return (
+ "moz-taskschd-test-" +
+ Services.uuid
+ .generateUUID()
+ .toString()
+ .slice(1, -1)
+ );
+}
+
+const gParentFolderName = randomName();
+const gParentFolderPath = `\\${gParentFolderName}`;
+const gSubFolderName = randomName();
+const gSubFolderPath = `\\${gParentFolderName}\\${gSubFolderName}`;
+// This folder will not be created
+const gMissingFolderName = randomName();
+const gMissingFolderPath = `\\${gParentFolderName}\\${gMissingFolderName}`;
+
+const gValidTaskXML = `<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
+ <Triggers />
+ <Settings>
+ <Enabled>false</Enabled>
+ </Settings>
+ <Actions>
+ <Exec>
+ <Command>xyz123.exe</Command>
+ </Exec>
+ </Actions>
+</Task>`;
+
+// Missing actions
+const gInvalidTaskXML = `<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
+ <Triggers />
+ <Settings>
+ <Enabled>false</Enabled>
+ </Settings>
+</Task>`;
+
+function cleanup() {
+ let tasksToDelete = svc.getFolderTasks(gSubFolderPath);
+
+ for (const task of tasksToDelete) {
+ svc.deleteTask(gSubFolderPath, task);
+ }
+
+ svc.deleteFolder(gParentFolderPath, gSubFolderName);
+
+ svc.deleteFolder("\\", gParentFolderPath);
+}
+
+registerCleanupFunction(() => {
+ try {
+ cleanup();
+ } catch (_ex) {
+ // Folders may not exist
+ }
+});
+
+add_task(async function test_svc() {
+ /***** FOLDERS *****/
+
+ // Try creating subfolder before parent folder exists
+ Assert.throws(
+ () => svc.createFolder(gParentFolderPath, gSubFolderName),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ // Create parent folder
+ svc.createFolder("\\", gParentFolderName);
+
+ // Create subfolder
+ svc.createFolder(gParentFolderPath, gSubFolderName);
+
+ // Try creating existing folder
+ Assert.throws(
+ () => svc.createFolder(gParentFolderPath, gSubFolderName),
+ /NS_ERROR_FILE_ALREADY_EXISTS/
+ );
+
+ // Try deleting nonexistent subfolder
+ Assert.throws(
+ () => svc.deleteFolder(gParentFolderPath, gMissingFolderName),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ /***** TASKS *****/
+ const taskNames = [randomName(), randomName(), randomName()];
+
+ // Try enumerating nonexistent subfolder
+ Assert.throws(
+ () => svc.getFolderTasks(gMissingFolderPath),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ // List empty subfolder
+ Assert.deepEqual(svc.getFolderTasks(gSubFolderPath), []);
+
+ // Try to create task in nonexistent subfolder
+ Assert.throws(
+ () => svc.registerTask(gMissingFolderPath, taskNames[0], gValidTaskXML),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ // Create task 0
+
+ svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML);
+
+ // Try to recreate task 0
+ Assert.throws(
+ () => svc.registerTask(gSubFolderPath, taskNames[0], gValidTaskXML),
+ /NS_ERROR_FILE_ALREADY_EXISTS/
+ );
+
+ // Update task 0
+ svc.registerTask(
+ gSubFolderPath,
+ taskNames[0],
+ gValidTaskXML,
+ true /* aUpdateExisting */
+ );
+
+ // Read back XML
+ Assert.ok(svc.getTaskXML(gSubFolderPath, taskNames[0]));
+
+ // Create remaining tasks
+ for (const task of taskNames.slice(1)) {
+ svc.registerTask(gSubFolderPath, task, gValidTaskXML);
+ }
+
+ // Try to create with invalid XML
+ Assert.throws(
+ () => svc.registerTask(gSubFolderPath, randomName(), gInvalidTaskXML),
+ /NS_ERROR_FAILURE/
+ );
+
+ // Validate XML
+ Assert.equal(svc.validateTaskDefinition(gValidTaskXML), 0 /* S_OK */);
+
+ // Try to validate invalid XML
+ Assert.notEqual(svc.validateTaskDefinition(gInvalidTaskXML), 0 /* S_OK */);
+
+ // Test enumeration
+ {
+ let foundTasks = svc.getFolderTasks(gSubFolderPath);
+ foundTasks.sort();
+
+ let allTasks = taskNames.slice();
+ allTasks.sort();
+
+ Assert.deepEqual(foundTasks, allTasks);
+ }
+
+ // Try deleting non-empty folder
+ Assert.throws(
+ () => svc.deleteFolder(gParentFolderPath, gSubFolderName),
+ /NS_ERROR_FILE_DIR_NOT_EMPTY/
+ );
+
+ const missingTaskName = randomName();
+
+ // Try deleting non-existent task
+ Assert.throws(
+ () => svc.deleteTask(gSubFolderName, missingTaskName),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ // Try reading non-existent task
+ Assert.throws(
+ () => svc.getTaskXML(gSubFolderPath, missingTaskName),
+ /NS_ERROR_FILE_NOT_FOUND/
+ );
+
+ /***** Cleanup *****/
+ // Explicitly call cleanup() to test that it removes the folder without error.
+ cleanup();
+});
diff --git a/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..ede5742015
--- /dev/null
+++ b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,13 @@
+[test_TaskSchedulerMacOSImpl.js]
+run-if = os == "mac" # Test of macOS backend
+skip-if =
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329
+[test_WinTaskSchedulerService.js]
+run-if = os == "win" # Test of Windows only service
+[test_TaskSchedulerWinImpl.js]
+run-if = os == "win" # Test of Windows backend
+[test_TaskScheduler.js]
+skip-if =
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329