Skip to content

Commit

Permalink
Tests are now faster (#2165)
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment committed Jun 27, 2020
1 parent 7109c18 commit e756be3
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 136 deletions.
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);

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

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


/**
Expand All @@ -14,6 +14,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 +58,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 +82,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,36 +121,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 = {}) {
// we don't have to pass our console but it's the only way these scripts can talk
// not supplying console here means that all references to `console` inside them will refer to a no-op console
context.console = console;
vm.runInNewContext(fileSource, context);
return context;
let content = fileSourceCache.get(src);
if (content === undefined) {
fileSourceCache.set(src, content = fs.readFileSync(src, "utf8"));
}
return content;
}
};

0 comments on commit e756be3

Please sign in to comment.