From e954c570c96bba3c66db3795a5bc8ed643abd8f7 Mon Sep 17 00:00:00 2001 From: Nicolas Chevobbe Date: Thu, 3 Mar 2022 16:16:50 +0000 Subject: [PATCH] Bug 1757552 - [devtools] Generate actor-less reps stubs.r=bomsy. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is introducing the machinery to automatically generate/check some stubs used by Reps. We're focusing on stubs that shouldn't be represented by a front as it's easier to deal with; we should then have follow up and incremental patches for each stubs. Some data can't be retrieved after being serialized/deserialized (`-0`, unsafe int, …), and in such case the associated test was modified to directly pass the object. Differential Revision: https://phabricator.services.mozilla.com/D139933 --- .eslintignore | 1 + devtools/client/shared/components/moz.build | 5 +- .../components/test/browser/browser.ini | 1 + .../test/browser/browser_reps_stubs.js | 218 ++++++++++++++++++ .../test/node/components/reps/number.test.js | 7 +- .../test/node/stubs/reps/browser_dummy.js | 11 + .../test/node/stubs/reps/infinity.js | 8 +- .../components/test/node/stubs/reps/nan.js | 5 +- .../components/test/node/stubs/reps/null.js | 5 +- .../components/test/node/stubs/reps/number.js | 17 +- .../components/test/node/stubs/reps/stubs.ini | 12 + .../test/node/stubs/reps/undefined.js | 5 +- 12 files changed, 278 insertions(+), 17 deletions(-) create mode 100644 devtools/client/shared/components/test/browser/browser_reps_stubs.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js create mode 100644 devtools/client/shared/components/test/node/stubs/reps/stubs.ini diff --git a/.eslintignore b/.eslintignore index 4d710e107b55..0cc0e1b0f0a7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -74,6 +74,7 @@ devtools/client/preferences/ devtools/shared/css/generated/properties-db.js devtools/client/webconsole/test/node/fixtures/stubs/*.js !devtools/client/webconsole/test/node/fixtures/stubs/index.js +devtools/client/shared/components/test/node/stubs/reps/*.js # Ignore devtools files testing sourcemaps / code style devtools/client/framework/test/code_* diff --git a/devtools/client/shared/components/moz.build b/devtools/client/shared/components/moz.build index 2dc4c579f30b..a515d57c0d20 100644 --- a/devtools/client/shared/components/moz.build +++ b/devtools/client/shared/components/moz.build @@ -34,4 +34,7 @@ DevToolsModules( ) MOCHITEST_CHROME_MANIFESTS += ["test/chrome/chrome.ini"] -BROWSER_CHROME_MANIFESTS += ["test/browser/browser.ini"] +BROWSER_CHROME_MANIFESTS += [ + "test/browser/browser.ini", + "test/node/stubs/reps/stubs.ini", +] diff --git a/devtools/client/shared/components/test/browser/browser.ini b/devtools/client/shared/components/test/browser/browser.ini index a4797d206080..619662db8f88 100644 --- a/devtools/client/shared/components/test/browser/browser.ini +++ b/devtools/client/shared/components/test/browser/browser.ini @@ -6,3 +6,4 @@ support-files = !/devtools/client/shared/test/telemetry-test-helpers.js [browser_notification_box_basic.js] +[browser_reps_stubs.js] diff --git a/devtools/client/shared/components/test/browser/browser_reps_stubs.js b/devtools/client/shared/components/test/browser/browser_reps_stubs.js new file mode 100644 index 000000000000..e0bc303fd52e --- /dev/null +++ b/devtools/client/shared/components/test/browser/browser_reps_stubs.js @@ -0,0 +1,218 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from ../../../../shared/test/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); + +const TEST_URI = "data:text/html;charset=utf-8,stub generation"; +/** + * A Map keyed by filename, and for which the value is also a Map, with the key being the + * label for the stub, and the value the expression to evaluate to get the stub. + */ +const EXPRESSIONS_BY_FILE = { + "infinity.js": new Map([ + ["Infinity", `Infinity`], + ["NegativeInfinity", `-Infinity`], + ]), + "nan.js": new Map([["NaN", `2 * document`]]), + "null.js": new Map([["Null", `null`]]), + "number.js": new Map([ + ["Int", `2 + 3`], + ["True", `true`], + ["False", `false`], + ["NegZeroGrip", `1 / -Infinity`], + ]), + "undefined.js": new Map([["Undefined", `undefined`]]), + // XXX: File a bug blocking Bug 1671400 for enabling automatic generation for one of + // the following file. + // "accessible.js", + // "accessor.js", + // "attribute.js", + // "big-int.js", + // "comment-node.js", + // "date-time.js", + // "document-type.js", + // "document.js", + // "element-node.js", + // "error.js", + // "event.js", + // "failure.js", + // "function.js", + // "grip-array.js", + // "grip-map-entry.js", + // "grip-map.js", + // "grip.js", + // "long-string.js", + // "object-with-text.js", + // "object-with-url.js", + // "promise.js", + // "regexp.js", + // "stylesheet.js", + // "symbol.js", + // "text-node.js", + // "window.js", +}; + +add_task(async function() { + const isStubsUpdate = env.get(STUBS_UPDATE_ENV) == "true"; + + const tab = await addTab(TEST_URI); + const { + CommandsFactory, + } = require("devtools/shared/commands/commands-factory"); + const commands = await CommandsFactory.forTab(tab); + await commands.targetCommand.startListening(); + + let failed = false; + for (const stubFile of Object.keys(EXPRESSIONS_BY_FILE)) { + info(`${isStubsUpdate ? "Update" : "Check"} ${stubFile}`); + + const generatedStubs = await generateStubs(commands, stubFile); + if (isStubsUpdate) { + await writeStubsToFile(env, stubFile, generatedStubs); + ok(true, `${stubFile} was updated`); + continue; + } + + const existingStubs = getStubFile(stubFile); + if (generatedStubs.size !== existingStubs.size) { + failed = true; + continue; + } + + for (const [key, packet] of generatedStubs) { + const packetStr = getSerializedPacket(packet, { sortKeys: true }); + const grip = getSerializedPacket(existingStubs.get(key), { + sortKeys: true, + }); + is(packetStr, grip, `"${key}" packet has expected value`); + failed = failed || packetStr !== grip; + } + } + + if (failed) { + ok( + false, + "The reps stubs need to be updated by running `" + + `mach test ${getCurrentTestFilePath()} --headless --setenv STUBS_UPDATE=true` + + "`" + ); + } else { + ok(true, "Stubs are up to date"); + } + + await removeTab(tab); +}); + +async function generateStubs(commands, stubFile) { + const stubs = new Map(); + + for (const [key, expression] of EXPRESSIONS_BY_FILE[stubFile]) { + const { result } = await commands.scriptCommand.execute(expression); + stubs.set(key, getCleanedPacket(key, result)); + } + + return stubs; +} + +function getCleanedPacket(key, packet) { + // TODO: Remove / normalize any data that is not stable + return packet; +} + +// HELPER + +const CHROME_PREFIX = "chrome://mochitests/content/browser/"; +const STUBS_FOLDER = "devtools/client/shared/components/test/node/stubs/reps/"; +const STUBS_UPDATE_ENV = "STUBS_UPDATE"; + +/** + * Write stubs to a given file + * + * @param {Object} env + * @param {String} fileName: The file to write the stubs in. + * @param {Map} packets: A Map of the packets. + */ +async function writeStubsToFile(env, fileName, packets) { + const mozRepo = env.get("MOZ_DEVELOPER_REPO_DIR"); + const filePath = `${mozRepo}/${STUBS_FOLDER + fileName}`; + + const stubs = Array.from(packets.entries()).map(([key, packet]) => { + const stringifiedPacket = getSerializedPacket(packet); + return `stubs.set(\`${key}\`, ${stringifiedPacket});`; + }); + + const fileContent = `/* 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 . */ + +"use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ + +const stubs = new Map(); +${stubs.join("\n\n")} + +module.exports = stubs; +`; + + const textEncoder = new TextEncoder(); + await IOUtils.write(filePath, textEncoder.encode(fileContent)); +} + +function getStubFile(fileName) { + return require(CHROME_PREFIX + STUBS_FOLDER + fileName); +} + +function sortObjectKeys(obj) { + const isArray = Array.isArray(obj); + const isObject = Object.prototype.toString.call(obj) === "[object Object]"; + const isFront = obj?._grip; + + if (isObject && !isFront) { + // Reorder keys for objects, but skip fronts to avoid infinite recursion. + const sortedKeys = Object.keys(obj).sort((k1, k2) => k1.localeCompare(k2)); + const withSortedKeys = {}; + sortedKeys.forEach(k => { + withSortedKeys[k] = k !== "stacktrace" ? sortObjectKeys(obj[k]) : obj[k]; + }); + return withSortedKeys; + } else if (isArray) { + return obj.map(item => sortObjectKeys(item)); + } + return obj; +} + +/** + * @param {Object} packet + * The packet to serialize. + * @param {Object} + * - {Boolean} sortKeys: pass true to sort all keys alphabetically in the + * packet before serialization. For instance stub comparison should not + * fail if the order of properties changed. + */ +function getSerializedPacket(packet, { sortKeys = false } = {}) { + if (sortKeys) { + packet = sortObjectKeys(packet); + } + + return JSON.stringify( + packet, + function(_, value) { + // The message can have fronts that we need to serialize + if (value && value._grip) { + return { _grip: value._grip, actorID: value.actorID }; + } + + return value; + }, + 2 + ); +} diff --git a/devtools/client/shared/components/test/node/components/reps/number.test.js b/devtools/client/shared/components/test/node/components/reps/number.test.js index 86754a208942..1d87498f1512 100644 --- a/devtools/client/shared/components/test/node/components/reps/number.test.js +++ b/devtools/client/shared/components/test/node/components/reps/number.test.js @@ -67,7 +67,7 @@ describe("Boolean", () => { describe("Negative Zero", () => { const stubNegativeZeroGrip = stubs.get("NegZeroGrip"); - const stubNegativeZeroValue = stubs.get("NegZeroValue"); + const stubNegativeZeroValue = -0; it("correctly selects Number Rep for negative zero grip", () => { expect(getRep(stubNegativeZeroGrip)).toBe(Number.rep); @@ -121,12 +121,11 @@ describe("Zero", () => { }); describe("Unsafe Int", () => { - const stub = stubs.get("UnsafeInt"); - it("renders with expected test content for a long number", () => { const renderedComponent = shallow( Rep({ - object: stub, + // eslint-disable-next-line no-loss-of-precision + object: 900719925474099122, shouldRenderTooltip: true, }) ); diff --git a/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js b/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js new file mode 100644 index 000000000000..8a9353cd7e3a --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/browser_dummy.js @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This file is a fake test so we can have support files in the stubs.ini, which are then +// referenced as support files in the webconsole mochitest ini file. + +"use strict"; + +add_task(function() { + ok(true, "this is not a test"); +}); diff --git a/devtools/client/shared/components/test/node/stubs/reps/infinity.js b/devtools/client/shared/components/test/node/stubs/reps/infinity.js index b7483f4ea85e..06deb944dc3b 100644 --- a/devtools/client/shared/components/test/node/stubs/reps/infinity.js +++ b/devtools/client/shared/components/test/node/stubs/reps/infinity.js @@ -3,12 +3,16 @@ * file, You can obtain one at . */ "use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ const stubs = new Map(); -stubs.set("Infinity", { +stubs.set(`Infinity`, { type: "Infinity", }); -stubs.set("NegativeInfinity", { + +stubs.set(`NegativeInfinity`, { type: "-Infinity", }); diff --git a/devtools/client/shared/components/test/node/stubs/reps/nan.js b/devtools/client/shared/components/test/node/stubs/reps/nan.js index 67d188022e23..a0b8b7ab1d26 100644 --- a/devtools/client/shared/components/test/node/stubs/reps/nan.js +++ b/devtools/client/shared/components/test/node/stubs/reps/nan.js @@ -3,9 +3,12 @@ * file, You can obtain one at . */ "use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ const stubs = new Map(); -stubs.set("NaN", { +stubs.set(`NaN`, { type: "NaN", }); diff --git a/devtools/client/shared/components/test/node/stubs/reps/null.js b/devtools/client/shared/components/test/node/stubs/reps/null.js index 20b9985e000a..1af8d8ee1807 100644 --- a/devtools/client/shared/components/test/node/stubs/reps/null.js +++ b/devtools/client/shared/components/test/node/stubs/reps/null.js @@ -3,9 +3,12 @@ * file, You can obtain one at . */ "use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ const stubs = new Map(); -stubs.set("Null", { +stubs.set(`Null`, { type: "null", }); diff --git a/devtools/client/shared/components/test/node/stubs/reps/number.js b/devtools/client/shared/components/test/node/stubs/reps/number.js index f942a7e89715..05c7c6dd7ef6 100644 --- a/devtools/client/shared/components/test/node/stubs/reps/number.js +++ b/devtools/client/shared/components/test/node/stubs/reps/number.js @@ -3,16 +3,19 @@ * file, You can obtain one at . */ "use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ const stubs = new Map(); -stubs.set("Int", 5); -stubs.set("True", true); -stubs.set("False", false); -stubs.set("NegZeroValue", -0); -stubs.set("NegZeroGrip", { +stubs.set(`Int`, 5); + +stubs.set(`True`, true); + +stubs.set(`False`, false); + +stubs.set(`NegZeroGrip`, { type: "-0", }); -// eslint-disable-next-line no-loss-of-precision -stubs.set("UnsafeInt", 900719925474099122); module.exports = stubs; diff --git a/devtools/client/shared/components/test/node/stubs/reps/stubs.ini b/devtools/client/shared/components/test/node/stubs/reps/stubs.ini new file mode 100644 index 000000000000..e0909160efb4 --- /dev/null +++ b/devtools/client/shared/components/test/node/stubs/reps/stubs.ini @@ -0,0 +1,12 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + infinity.js + nan.js + null.js + number.js + undefined.js + +[browser_dummy.js] +skip-if=true #This is only here so we can expose the support files in other ini files. diff --git a/devtools/client/shared/components/test/node/stubs/reps/undefined.js b/devtools/client/shared/components/test/node/stubs/reps/undefined.js index f07d62db712e..922dccafa546 100644 --- a/devtools/client/shared/components/test/node/stubs/reps/undefined.js +++ b/devtools/client/shared/components/test/node/stubs/reps/undefined.js @@ -3,9 +3,12 @@ * file, You can obtain one at . */ "use strict"; +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. RUN browser_reps_stubs.js with STUBS_UPDATE=true env TO UPDATE. + */ const stubs = new Map(); -stubs.set("Undefined", { +stubs.set(`Undefined`, { type: "undefined", });