Before this commit, it was possible to deploy services more than once. This happened in case the root widget was about to deploy and start the services, and more than one service was registering itself during a next tick. Services must be deployed and started only once. This commit ensures that if many services are added in this scenario, the services are deployed and started only once. Part-of: odoo/odoo#92786
245 lines
6.5 KiB
JavaScript
245 lines
6.5 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { makeEnv, startServices } from "@web/env";
|
|
import { registry } from "@web/core/registry";
|
|
import {
|
|
clearRegistryWithCleanup,
|
|
clearServicesMetadataWithCleanup,
|
|
makeTestEnv,
|
|
} from "./helpers/mock_env";
|
|
import { makeDeferred, nextTick, patchWithCleanup } from "./helpers/utils";
|
|
|
|
const serviceRegistry = registry.category("services");
|
|
|
|
QUnit.module("env");
|
|
|
|
QUnit.test("can start a service", async (assert) => {
|
|
serviceRegistry.add("test", {
|
|
start() {
|
|
return 17;
|
|
},
|
|
});
|
|
const env = await makeTestEnv();
|
|
assert.strictEqual(env.services.test, 17);
|
|
});
|
|
|
|
QUnit.test("properly handle crash in service start", async (assert) => {
|
|
patchWithCleanup(console, {
|
|
error: () => assert.step("log"),
|
|
});
|
|
serviceRegistry.add("test", {
|
|
start() {
|
|
return 17;
|
|
},
|
|
});
|
|
serviceRegistry.add("ouch", {
|
|
start() {
|
|
throw new Error("boom");
|
|
},
|
|
});
|
|
const env = await makeTestEnv();
|
|
assert.strictEqual(env.services.test, 17);
|
|
assert.ok(env.services.ouch instanceof Error);
|
|
assert.verifySteps(["log"]);
|
|
});
|
|
|
|
QUnit.test("properly handle crash in async service start", async (assert) => {
|
|
patchWithCleanup(console, {
|
|
error: () => assert.step("log"),
|
|
});
|
|
serviceRegistry.add("test", {
|
|
start() {
|
|
return 17;
|
|
},
|
|
});
|
|
serviceRegistry.add("ouch", {
|
|
async start() {
|
|
throw new Error("boom");
|
|
},
|
|
});
|
|
const env = await makeTestEnv();
|
|
assert.strictEqual(env.services.test, 17);
|
|
assert.ok(env.services.ouch instanceof Error);
|
|
assert.verifySteps(["log"]);
|
|
});
|
|
|
|
QUnit.test("can start an asynchronous service", async (assert) => {
|
|
const def = makeDeferred();
|
|
serviceRegistry.add("test", {
|
|
async start() {
|
|
assert.step("before");
|
|
const result = await def;
|
|
assert.step("after");
|
|
return result;
|
|
},
|
|
});
|
|
const prom = makeTestEnv();
|
|
await Promise.resolve(); // Wait for startServices
|
|
assert.verifySteps(["before"]);
|
|
def.resolve(15);
|
|
const env = await prom;
|
|
assert.verifySteps(["after"]);
|
|
assert.strictEqual(env.services.test, 15);
|
|
});
|
|
|
|
QUnit.test("can start two sequentially dependant asynchronous services", async (assert) => {
|
|
const def1 = makeDeferred();
|
|
const def2 = makeDeferred();
|
|
serviceRegistry.add("test2", {
|
|
dependencies: ["test1"],
|
|
start() {
|
|
assert.step("test2");
|
|
return def2;
|
|
},
|
|
});
|
|
serviceRegistry.add("test1", {
|
|
start() {
|
|
assert.step("test1");
|
|
return def1;
|
|
},
|
|
});
|
|
serviceRegistry.add("test3", {
|
|
dependencies: ["test2"],
|
|
start() {
|
|
assert.step("test3");
|
|
},
|
|
});
|
|
const promise = makeTestEnv();
|
|
await nextTick();
|
|
assert.verifySteps(["test1"]);
|
|
def2.resolve();
|
|
await nextTick();
|
|
assert.verifySteps([]);
|
|
def1.resolve();
|
|
await nextTick();
|
|
assert.verifySteps(["test2", "test3"]);
|
|
await promise;
|
|
});
|
|
|
|
QUnit.test("can start two independant asynchronous services in parallel", async (assert) => {
|
|
const def1 = makeDeferred();
|
|
const def2 = makeDeferred();
|
|
serviceRegistry.add("test1", {
|
|
start() {
|
|
assert.step("test1");
|
|
return def1;
|
|
},
|
|
});
|
|
serviceRegistry.add("test2", {
|
|
start() {
|
|
assert.step("test2");
|
|
return def2;
|
|
},
|
|
});
|
|
serviceRegistry.add("test3", {
|
|
dependencies: ["test1", "test2"],
|
|
start() {
|
|
assert.step("test3");
|
|
},
|
|
});
|
|
const promise = makeTestEnv();
|
|
await nextTick();
|
|
assert.verifySteps(["test1", "test2"]);
|
|
def1.resolve();
|
|
await nextTick();
|
|
assert.verifySteps([]);
|
|
def2.resolve();
|
|
await nextTick();
|
|
assert.verifySteps(["test3"]);
|
|
await promise;
|
|
});
|
|
|
|
QUnit.test("can start a service with a dependency", async (assert) => {
|
|
serviceRegistry.add("aang", {
|
|
dependencies: ["appa"],
|
|
start() {
|
|
assert.step("aang");
|
|
},
|
|
});
|
|
serviceRegistry.add("appa", {
|
|
start() {
|
|
assert.step("appa");
|
|
},
|
|
});
|
|
await makeTestEnv();
|
|
assert.verifySteps(["appa", "aang"]);
|
|
});
|
|
|
|
QUnit.test("get an object containing dependencies as second arg", async (assert) => {
|
|
serviceRegistry.add("aang", {
|
|
dependencies: ["appa"],
|
|
start(env, deps) {
|
|
assert.deepEqual(deps, { appa: "flying bison" });
|
|
},
|
|
});
|
|
serviceRegistry.add("appa", {
|
|
start() {
|
|
return "flying bison";
|
|
},
|
|
});
|
|
await makeTestEnv();
|
|
});
|
|
|
|
QUnit.test(
|
|
"startServices: throws if all dependencies are not met in the same microtick as the call",
|
|
async function (assert) {
|
|
assert.expect(3);
|
|
clearRegistryWithCleanup(serviceRegistry);
|
|
clearServicesMetadataWithCleanup();
|
|
|
|
const serviceA = {
|
|
start() {
|
|
return "a";
|
|
},
|
|
};
|
|
const serviceB = {
|
|
dependencies: ["a"],
|
|
start() {
|
|
return "b";
|
|
},
|
|
};
|
|
const env = makeEnv();
|
|
|
|
serviceRegistry.add("b", serviceB);
|
|
const prom = startServices(env);
|
|
await Promise.resolve();
|
|
await assert.rejects(prom, "Some services could not be started: b. Missing dependencies: a");
|
|
assert.deepEqual(env.services, {});
|
|
|
|
serviceRegistry.add("a", serviceA);
|
|
await startServices(env);
|
|
assert.deepEqual(env.services, { a: "a", b: "b" });
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
"startServices: waits for all synchronous code before attempting to start services",
|
|
async function (assert) {
|
|
assert.expect(1);
|
|
clearRegistryWithCleanup(serviceRegistry);
|
|
clearServicesMetadataWithCleanup();
|
|
|
|
const serviceA = {
|
|
start() {
|
|
return "a";
|
|
},
|
|
};
|
|
const serviceB = {
|
|
dependencies: ["a"],
|
|
start() {
|
|
return "b";
|
|
},
|
|
};
|
|
|
|
const env = makeEnv();
|
|
serviceRegistry.add("b", serviceB);
|
|
const prom = startServices(env);
|
|
// Dependency added in the same microtick doesn't cause startServices to throw even if it was added after the call
|
|
// (eg, a module is defined after main.js)
|
|
serviceRegistry.add("a", serviceA);
|
|
|
|
await prom;
|
|
assert.deepEqual(env.services, { a: "a", b: "b" });
|
|
}
|
|
);
|