/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { ExtensionPermissions } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionPermissions.sys.mjs" ); /* import-globals-from ../../../ml/tests/browser/head.js */ loadTestSubscript("../../../ml/tests/browser/head.js"); async function happyPath() { const options = { taskName: "summarization", modelId: "test-echo", modelRevision: "main", }; await browser.trial.ml.createEngine(options); const data = ["This gets echoed."]; browser.test.sendMessage("model_created"); const inferencePromise = browser.trial.ml.runEngine({ args: data, }); browser.test.sendMessage("promise_created"); const res = (await inferencePromise).output; // The `test-echo` task does not load a real model but // creates a fully functional worker in the infefence engine that returns // the same values abd the options it received. browser.test.assertDeepEq( res, data, "The text get echoed exercising the whole flow." ); browser.test.sendMessage("inference_finished"); } async function disabledFeature() { const options = { taskName: "summarization", modelId: "test-echo", modelRevision: "main", }; try { await browser.trial.ml.createEngine(options); throw Error("Should fail"); } catch (err) {} browser.test.sendMessage("model_created"); browser.test.sendMessage("promise_created"); browser.test.sendMessage("inference_finished"); } function createExtension(background) { const id = Services.uuid.generateUUID().number; ExtensionPermissions.add(id, { permissions: ["trialML"], origins: [] }); return ExtensionTestUtils.loadExtension({ isPrivileged: true, manifest: { optional_permissions: ["trialML"], background: { scripts: ["background.js"] }, browser_specific_settings: { gecko: { id } }, }, background, }); } function createMlExtensionTest({ testName, backgroundFunction = happyPath, prefs = [ ["extensions.experiments.enabled", true], ["extensions.ml.enabled", true], ], }) { const func = async function () { const { cleanup, remoteClients } = await setup({ prefs }); let extension = createExtension(backgroundFunction); await extension.startup(); try { await extension.awaitMessage("model_created"); await extension.awaitMessage("promise_created"); await remoteClients["ml-onnx-runtime"].resolvePendingDownloads(1); await extension.awaitMessage("inference_finished"); } finally { await extension.unload(); await EngineProcess.destroyMLEngine(); await cleanup(); await SpecialPowers.popPrefEnv(); } }; Object.defineProperty(func, "name", { value: testName }); return func; } /** * Testing that the API won't work if the preferences are not set */ add_task( createMlExtensionTest({ testName: "no_pref", backgroundFunction: disabledFeature, prefs: [ ["extensions.experiments.enabled", true], ["extensions.ml.enabled", false], ], }) ); /** * Testing the happy path. */ add_task(createMlExtensionTest({ testName: "happy_path" })); /** * Testing errors when options are not valid */ add_task( createMlExtensionTest({ testName: "options_error", backgroundFunction: async function backgroundScript() { const options = { taskName: "summari@#zation", modelId: "test-echo", modelRevision: "main", }; try { await browser.trial.ml.createEngine(options); browser.test.fail("Bad options should be caught"); } catch (err) { browser.test.assertTrue( err.message.startsWith("Unsupported task summari@#zation") ); } browser.test.sendMessage("model_created"); browser.test.sendMessage("promise_created"); browser.test.sendMessage("inference_finished"); }, }) ); add_task( createMlExtensionTest({ testName: "options_error_2", backgroundFunction: async function backgroundScript() { const options = { taskName: "summarization", modelId: "test-ec@ho", modelRevision: "main", }; try { await browser.trial.ml.createEngine(options); browser.test.fail("Bad options should be caught"); } catch (err) { browser.test.assertTrue(err.message.startsWith("Invalid value")); } browser.test.sendMessage("model_created"); browser.test.sendMessage("promise_created"); browser.test.sendMessage("inference_finished"); }, }) ); /** * Test re-creating the engine after the idle timeout drops it. */ add_task(async function test_idle_timeout() { const { cleanup, remoteClients } = await setup({ prefs: [ ["extensions.experiments.enabled", true], ["extensions.ml.enabled", true], ], }); let extension = createExtension(async function background() { const options = { taskName: "summarization", modelId: "test-echo", modelRevision: "main", }; await browser.trial.ml.createEngine(options); browser.test.sendMessage("model_created"); browser.test.onMessage.addListener(async (_msg, data) => { const inferencePromise = browser.trial.ml.runEngine({ args: data }); browser.test.sendMessage("promise_created"); const res = (await inferencePromise).output; browser.test.assertDeepEq( res, data, "The text get echoed exercising the whole flow." ); browser.test.sendMessage("inference_finished"); }); }); await extension.startup(); try { await extension.awaitMessage("model_created"); // Run inference the first time. extension.sendMessage("run", "Marco"); await extension.awaitMessage("promise_created"); await remoteClients["ml-onnx-runtime"].resolvePendingDownloads(1); await extension.awaitMessage("inference_finished"); // Simulate the engine getting destroyed after an idle timeout. await EngineProcess.destroyMLEngine(); ok(EngineProcess.areAllEnginesTerminated(), "Nothing is running."); // Run inference without calling createEngine again. extension.sendMessage("run", "Polo"); await extension.awaitMessage("promise_created"); await remoteClients["ml-onnx-runtime"].resolvePendingDownloads(1); await extension.awaitMessage("inference_finished"); } finally { await extension.unload(); await EngineProcess.destroyMLEngine(); await cleanup(); } }); add_task(async function test_deleteCachedModels() { const { cleanup } = await setup({ prefs: [ ["extensions.experiments.enabled", true], ["extensions.ml.enabled", true], ], }); const extension = createExtension(async function background() { await browser.trial.ml.deleteCachedModels(); browser.test.sendMessage("cached-models-deleted"); }); await extension.startup(); await extension.awaitMessage("cached-models-deleted"); await extension.unload(); await EngineProcess.destroyMLEngine(); await cleanup(); });