forked from mirrors/gecko-dev
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
218 lines
6.1 KiB
JavaScript
218 lines
6.1 KiB
JavaScript
/* 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 <http://mozilla.org/MPL/2.0/>. */
|
|
|
|
"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
|
|
);
|
|
}
|