From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../tests/xpcshell/test_TaskScheduler.js | 55 ++++ .../tests/xpcshell/test_TaskSchedulerMacOSImpl.js | 108 ++++++++ .../tests/xpcshell/test_TaskSchedulerWinImpl.js | 291 +++++++++++++++++++++ .../tests/xpcshell/test_WinTaskSchedulerService.js | 183 +++++++++++++ .../taskscheduler/tests/xpcshell/xpcshell.toml | 20 ++ 5 files changed, 657 insertions(+) create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/test_TaskScheduler.js create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js create mode 100644 toolkit/components/taskscheduler/tests/xpcshell/xpcshell.toml (limited to 'toolkit/components/taskscheduler/tests/xpcshell') 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..8c4aa8c454 --- /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.importESModule( + "resource://gre/modules/TaskScheduler.sys.mjs" +); + +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..d087c77446 --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerMacOSImpl.js @@ -0,0 +1,108 @@ +/* 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.importESModule( + "resource://gre/modules/TaskScheduler.sys.mjs" +); +const { MacOSImpl } = ChromeUtils.importESModule( + "resource://gre/modules/TaskSchedulerMacOSImpl.sys.mjs" +); + +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..ed1be3e49b --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/test_TaskSchedulerWinImpl.js @@ -0,0 +1,291 @@ +/* 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.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` + ); + } +}); 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..d84fe0901c --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/test_WinTaskSchedulerService.js @@ -0,0 +1,183 @@ +/* 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 = ` + + + false + + + + xyz123.exe + + +`; + +// Missing actions +const gInvalidTaskXML = ` + + + false + +`; + +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.toml b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..9551b1c877 --- /dev/null +++ b/toolkit/components/taskscheduler/tests/xpcshell/xpcshell.toml @@ -0,0 +1,20 @@ +[DEFAULT] + +["test_TaskScheduler.js"] +skip-if = [ + "apple_silicon", # bug 1707753 + "apple_catalina", # Bug 1713329 +] + +["test_TaskSchedulerMacOSImpl.js"] +run-if = ["os == 'mac'"] # Test of macOS backend +skip-if = [ + "apple_silicon", # bug 1707753 + "apple_catalina", # Bug 1713329 +] + +["test_TaskSchedulerWinImpl.js"] +run-if = ["os == 'win'"] # Test of Windows backend + +["test_WinTaskSchedulerService.js"] +run-if = ["os == 'win'"] # Test of Windows only service -- cgit v1.2.3