Skip to content

Commit e756be3

Browse files
authoredJun 27, 2020
Tests are now faster (#2165)
1 parent 7109c18 commit e756be3

File tree

5 files changed

+102
-136
lines changed

5 files changed

+102
-136
lines changed
 

‎tests/checks/extend.js

-20
This file was deleted.

‎tests/checks/insert-before.js

-32
This file was deleted.

‎tests/helper/check-functionality.js

-13
This file was deleted.

‎tests/helper/checks.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use strict";
2+
3+
function testFunction(name, object, tester) {
4+
const func = object[name];
5+
6+
object[name] = function () {
7+
tester.apply(this, arguments);
8+
return func.apply(this, arguments);
9+
};
10+
}
11+
12+
module.exports = (Prism) => {
13+
14+
function extendTest(id, redef) {
15+
const details = `\nextend("${id}", ${redef})`;
16+
17+
// type checks
18+
if (typeof id !== 'string') {
19+
throw new TypeError(`The id argument has to be a 'string'.` + details);
20+
}
21+
if (typeof redef !== 'object') {
22+
throw new TypeError(`The redef argument has to be an 'object'.` + details);
23+
}
24+
25+
26+
if (!(id in Prism.languages)) {
27+
throw new Error(`Cannot extend '${id}' because it is not defined in Prism.languages.`);
28+
}
29+
}
30+
31+
function insertBeforeTest(inside, before, insert, root) {
32+
const details = `\ninsertBefore("${inside}", "${before}", ${insert}, ${root})`;
33+
34+
// type checks
35+
if (typeof inside !== 'string') {
36+
throw new TypeError(`The inside argument has to be a 'string'.` + details);
37+
}
38+
if (typeof before !== 'string') {
39+
throw new TypeError(`The before argument has to be a 'string'.` + details);
40+
}
41+
if (typeof insert !== 'object') {
42+
throw new TypeError(`The insert argument has to be an 'object'.` + details);
43+
}
44+
if (root && typeof root !== 'object') {
45+
throw new TypeError(`The root argument has to be an 'object' if defined.` + details);
46+
}
47+
48+
49+
root = root || Prism.languages;
50+
var grammar = root[inside];
51+
52+
if (typeof grammar !== 'object') {
53+
throw new Error(`The grammar "${inside}" has to be an 'object' not '${typeof grammar}'.`);
54+
}
55+
if (!(before in grammar)) {
56+
throw new Error(`"${before}" has to be a key of the grammar "${inside}".`);
57+
}
58+
}
59+
60+
testFunction('extend', Prism.languages, extendTest);
61+
testFunction('insertBefore', Prism.languages, insertBeforeTest);
62+
63+
};

‎tests/helper/prism-loader.js

+39-71
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"use strict";
22

33
const fs = require("fs");
4-
const vm = require("vm");
54
const { getAllFiles } = require("./test-discovery");
65
const components = require("../../components.json");
76
const getLoader = require("../../dependencies");
87
const languagesCatalog = components.languages;
8+
const coreChecks = require('./checks');
99

1010

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

17+
/** @type {Map<string, string>} */
18+
const fileSourceCache = new Map();
19+
/** @type {() => any} */
20+
let coreSupplierFunction = null;
21+
/** @type {Map<string, (Prism: any) => void>} */
22+
const languageCache = new Map();
23+
1724
module.exports = {
1825

1926
/**
@@ -51,9 +58,15 @@ module.exports = {
5158
throw new Error(`Language '${id}' not found.`);
5259
}
5360

54-
// load the language itself
55-
const languageSource = this.loadComponentSource(id);
56-
context.Prism = this.runFileWithContext(languageSource, { Prism: context.Prism }).Prism;
61+
// get the function which adds the language from cache
62+
let languageFunction = languageCache.get(id);
63+
if (languageFunction === undefined) {
64+
// make a function from the code which take "Prism" as an argument, so the language grammar
65+
// references the function argument
66+
const func = new Function('Prism', this.loadComponentSource(id));
67+
languageCache.set(id, languageFunction = (Prism) => func(Prism));
68+
}
69+
languageFunction(context.Prism);
5770

5871
context.loaded.add(id);
5972
});
@@ -69,45 +82,26 @@ module.exports = {
6982
* @returns {Prism}
7083
*/
7184
createEmptyPrism() {
72-
const coreSource = this.loadComponentSource("core");
73-
const context = this.runFileWithContext(coreSource);
74-
75-
for (const testSource of this.getChecks().map(src => this.loadFileSource(src))) {
76-
context.Prism = this.runFileWithContext(testSource, {
77-
Prism: context.Prism,
78-
/**
79-
* A pseudo require function for the checks.
80-
*
81-
* This function will behave like the regular `require` in real modules when called form a check file.
82-
*
83-
* @param {string} id The id of relative path to require.
84-
*/
85-
require(id) {
86-
if (id.startsWith('./')) {
87-
// We have to rewrite relative paths starting with './'
88-
return require('./../checks/' + id.substr(2));
89-
} else {
90-
// This might be an id like 'mocha' or 'fs' or a relative path starting with '../'.
91-
// In both cases we don't have to change anything.
92-
return require(id);
93-
}
94-
}
95-
}).Prism;
85+
if (!coreSupplierFunction) {
86+
const source = this.loadComponentSource("core");
87+
// Core exports itself in 2 ways:
88+
// 1) it uses `module.exports = Prism` which what we'll use
89+
// 2) it uses `global.Prism = Prism` which we want to sabotage to prevent leaking
90+
const func = new Function('module', 'global', source);
91+
coreSupplierFunction = () => {
92+
const module = {
93+
// that's all we care about
94+
exports: {}
95+
};
96+
func(module, {});
97+
return module.exports;
98+
};
9699
}
97-
98-
return context.Prism;
100+
const Prism = coreSupplierFunction();
101+
coreChecks(Prism);
102+
return Prism;
99103
},
100104

101-
102-
/**
103-
* Cached file sources, to prevent massive HDD work
104-
*
105-
* @private
106-
* @type {Object.<string, string>}
107-
*/
108-
fileSourceCache: {},
109-
110-
111105
/**
112106
* Loads the given component's file source as string
113107
*
@@ -127,36 +121,10 @@ module.exports = {
127121
* @returns {string}
128122
*/
129123
loadFileSource(src) {
130-
return this.fileSourceCache[src] = this.fileSourceCache[src] || fs.readFileSync(src, "utf8");
131-
},
132-
133-
134-
checkCache: null,
135-
136-
/**
137-
* Returns a list of files which add additional checks to Prism functions.
138-
*
139-
* @returns {ReadonlyArray<string>}
140-
*/
141-
getChecks() {
142-
return this.checkCache = this.checkCache || getAllFiles(__dirname + "/../checks");
143-
},
144-
145-
146-
/**
147-
* Runs a VM for a given file source with the given context
148-
*
149-
* @private
150-
* @param {string} fileSource
151-
* @param {*} [context={}]
152-
*
153-
* @returns {*}
154-
*/
155-
runFileWithContext(fileSource, context = {}) {
156-
// we don't have to pass our console but it's the only way these scripts can talk
157-
// not supplying console here means that all references to `console` inside them will refer to a no-op console
158-
context.console = console;
159-
vm.runInNewContext(fileSource, context);
160-
return context;
124+
let content = fileSourceCache.get(src);
125+
if (content === undefined) {
126+
fileSourceCache.set(src, content = fs.readFileSync(src, "utf8"));
127+
}
128+
return content;
161129
}
162130
};

0 commit comments

Comments
 (0)
Please sign in to comment.