Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster tests #2165

Merged
merged 2 commits into from Jun 27, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 0 additions & 20 deletions tests/checks/extend.js

This file was deleted.

32 changes: 0 additions & 32 deletions tests/checks/insert-before.js

This file was deleted.

13 changes: 0 additions & 13 deletions tests/helper/check-functionality.js

This file was deleted.

63 changes: 63 additions & 0 deletions tests/helper/checks.js
@@ -0,0 +1,63 @@
"use strict";

function testFunction(name, object, tester) {
const func = object[name];

object[name] = function () {
tester.apply(this, arguments);
return func.apply(this, arguments);
};
}

module.exports = (Prism) => {

function extendTest(id, redef) {
const details = `\nextend("${id}", ${redef})`;

// type checks
if (typeof id !== 'string') {
throw new TypeError(`The id argument has to be a 'string'.` + details);
}
if (typeof redef !== 'object') {
throw new TypeError(`The redef argument has to be an 'object'.` + details);
}


if (!(id in Prism.languages)) {
throw new Error(`Cannot extend '${id}' because it is not defined in Prism.languages.`);
}
}

function insertBeforeTest(inside, before, insert, root) {
const details = `\ninsertBefore("${inside}", "${before}", ${insert}, ${root})`;

// type checks
if (typeof inside !== 'string') {
throw new TypeError(`The inside argument has to be a 'string'.` + details);
}
if (typeof before !== 'string') {
throw new TypeError(`The before argument has to be a 'string'.` + details);
}
if (typeof insert !== 'object') {
throw new TypeError(`The insert argument has to be an 'object'.` + details);
}
if (root && typeof root !== 'object') {
throw new TypeError(`The root argument has to be an 'object' if defined.` + details);
}


root = root || Prism.languages;
var grammar = root[inside];

if (typeof grammar !== 'object') {
throw new Error(`The grammar "${inside}" has to be an 'object' not '${typeof grammar}'.`);
}
if (!(before in grammar)) {
throw new Error(`"${before}" has to be a key of the grammar "${inside}".`);
}
}

testFunction('extend', Prism.languages, extendTest);
testFunction('insertBefore', Prism.languages, insertBeforeTest);

};
108 changes: 39 additions & 69 deletions tests/helper/prism-loader.js
@@ -1,11 +1,10 @@
"use strict";

const fs = require("fs");
const vm = require("vm");
const { getAllFiles } = require("./test-discovery");
const components = require("../../components");
const getLoader = require("../../dependencies");
const languagesCatalog = components.languages;
const coreChecks = require('./checks');


/**
Expand All @@ -14,6 +13,13 @@ const languagesCatalog = components.languages;
* @property {Set<string>} loaded A set of loaded components.
*/

/** @type {Map<string, string>} */
const fileSourceCache = new Map();
/** @type {() => any} */
let coreSupplierFunction = null;
/** @type {Map<string, (Prism: any) => void>} */
const languageCache = new Map();

module.exports = {

/**
Expand Down Expand Up @@ -51,9 +57,15 @@ module.exports = {
throw new Error(`Language '${id}' not found.`);
}

// load the language itself
const languageSource = this.loadComponentSource(id);
context.Prism = this.runFileWithContext(languageSource, { Prism: context.Prism }).Prism;
// get the function which adds the language from cache
let languageFunction = languageCache.get(id);
if (languageFunction === undefined) {
// make a function from the code which take "Prism" as an argument, so the language grammar
// references the function argument
const func = new Function('Prism', this.loadComponentSource(id));
languageCache.set(id, languageFunction = (Prism) => func(Prism));
}
languageFunction(context.Prism);

context.loaded.add(id);
});
Expand All @@ -69,45 +81,26 @@ module.exports = {
* @returns {Prism}
*/
createEmptyPrism() {
const coreSource = this.loadComponentSource("core");
const context = this.runFileWithContext(coreSource);

for (const testSource of this.getChecks().map(src => this.loadFileSource(src))) {
context.Prism = this.runFileWithContext(testSource, {
Prism: context.Prism,
/**
* A pseudo require function for the checks.
*
* This function will behave like the regular `require` in real modules when called form a check file.
*
* @param {string} id The id of relative path to require.
*/
require(id) {
if (id.startsWith('./')) {
// We have to rewrite relative paths starting with './'
return require('./../checks/' + id.substr(2));
} else {
// This might be an id like 'mocha' or 'fs' or a relative path starting with '../'.
// In both cases we don't have to change anything.
return require(id);
}
}
}).Prism;
if (!coreSupplierFunction) {
const source = this.loadComponentSource("core");
// Core exports itself in 2 ways:
// 1) it uses `module.exports = Prism` which what we'll use
// 2) it uses `global.Prism = Prism` which we want to sabotage to prevent leaking
const func = new Function('module', 'global', source);
coreSupplierFunction = () => {
const module = {
// that's all we care about
exports: {}
};
func(module, {});
return module.exports;
};
}

return context.Prism;
const Prism = coreSupplierFunction();
coreChecks(Prism);
return Prism;
},


/**
* Cached file sources, to prevent massive HDD work
*
* @private
* @type {Object.<string, string>}
*/
fileSourceCache: {},


/**
* Loads the given component's file source as string
*
Expand All @@ -127,33 +120,10 @@ module.exports = {
* @returns {string}
*/
loadFileSource(src) {
return this.fileSourceCache[src] = this.fileSourceCache[src] || fs.readFileSync(src, "utf8");
},


checkCache: null,

/**
* Returns a list of files which add additional checks to Prism functions.
*
* @returns {ReadonlyArray<string>}
*/
getChecks() {
return this.checkCache = this.checkCache || getAllFiles(__dirname + "/../checks");
},


/**
* Runs a VM for a given file source with the given context
*
* @private
* @param {string} fileSource
* @param {*} [context={}]
*
* @returns {*}
*/
runFileWithContext(fileSource, context = {}) {
vm.runInNewContext(fileSource, context);
return context;
let content = fileSourceCache.get(src);
if (content === undefined) {
fileSourceCache.set(src, content = fs.readFileSync(src, "utf8"));
}
return content;
}
};