Isolated exec tests (#11531)
* Run exec tests in fresh contexts * Reevaluate modules in every context * Cache module code when running tests * Eliminate weakmap accesses as much as possible * Remove old multiline usage * Using bundled polyfill to significantly increase performance The individual requires for each file were the part that was sooooo slow. * Drop LRU cache size * Fixes * Fix test Co-authored-by: Huáng Jùnliàng <jlhwung@gmail.com>
This commit is contained in:
parent
3bff1ce35a
commit
a5bc48661b
@ -1,8 +1,8 @@
|
||||
var code = multiline([
|
||||
"function foo() {",
|
||||
" var a = a ? a : a;",
|
||||
"}",
|
||||
]);
|
||||
var code = `
|
||||
function foo() {
|
||||
var a = a ? a : a;
|
||||
}
|
||||
`;
|
||||
|
||||
transform(code, {
|
||||
plugins: [
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
var code = multiline([
|
||||
"(function() {",
|
||||
" var bar = 'lol';",
|
||||
" function foo(b){",
|
||||
" b === bar;",
|
||||
" foo(b);",
|
||||
" }",
|
||||
"})();",
|
||||
]);
|
||||
var code = `
|
||||
(function() {
|
||||
var bar = 'lol';
|
||||
function foo(b){
|
||||
b === bar
|
||||
foo(b);
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
transform(code, {
|
||||
plugins: [
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"jest": "^24.8.0",
|
||||
"jest-diff": "^24.8.0",
|
||||
"lodash": "^4.17.19",
|
||||
"quick-lru": "5.1.0",
|
||||
"resolve": "^1.3.2",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
|
||||
@ -14,34 +14,100 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import vm from "vm";
|
||||
import checkDuplicatedNodes from "babel-check-duplicated-nodes";
|
||||
import QuickLRU from "quick-lru";
|
||||
|
||||
import diff from "jest-diff";
|
||||
|
||||
const moduleCache = {};
|
||||
const testContext = vm.createContext({
|
||||
...helpers,
|
||||
process: process,
|
||||
transform: babel.transform,
|
||||
setTimeout: setTimeout,
|
||||
setImmediate: setImmediate,
|
||||
expect,
|
||||
});
|
||||
testContext.global = testContext;
|
||||
const cachedScripts = new QuickLRU({ maxSize: 10 });
|
||||
const contextModuleCache = new WeakMap();
|
||||
const sharedTestContext = createContext();
|
||||
|
||||
// Initialize the test context with the polyfill, and then freeze the global to prevent implicit
|
||||
// global creation in tests, which could cause things to bleed between tests.
|
||||
runModuleInTestContext("@babel/polyfill", __filename);
|
||||
function createContext() {
|
||||
const context = vm.createContext({
|
||||
...helpers,
|
||||
process: process,
|
||||
transform: babel.transform,
|
||||
setTimeout: setTimeout,
|
||||
setImmediate: setImmediate,
|
||||
expect,
|
||||
});
|
||||
context.global = context;
|
||||
|
||||
// Populate the "babelHelpers" global with Babel's helper utilities.
|
||||
runCodeInTestContext(buildExternalHelpers(), {
|
||||
filename: path.join(__dirname, "babel-helpers-in-memory.js"),
|
||||
});
|
||||
const moduleCache = Object.create(null);
|
||||
contextModuleCache.set(context, moduleCache);
|
||||
|
||||
// Initialize the test context with the polyfill, and then freeze the global to prevent implicit
|
||||
// global creation in tests, which could cause things to bleed between tests.
|
||||
runModuleInTestContext(
|
||||
"@babel/polyfill/dist/polyfill.min",
|
||||
__filename,
|
||||
context,
|
||||
moduleCache,
|
||||
);
|
||||
|
||||
// Populate the "babelHelpers" global with Babel's helper utilities.
|
||||
runCacheableScriptInTestContext(
|
||||
path.join(__dirname, "babel-helpers-in-memory.js"),
|
||||
buildExternalHelpers,
|
||||
context,
|
||||
moduleCache,
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function runCacheableScriptInTestContext(
|
||||
filename: string,
|
||||
srcFn: () => string,
|
||||
context: Context,
|
||||
moduleCache: Object,
|
||||
) {
|
||||
let cached = cachedScripts.get(filename);
|
||||
if (!cached) {
|
||||
const code = `(function (exports, require, module, __filename, __dirname) {\n${srcFn()}\n});`;
|
||||
cached = {
|
||||
code,
|
||||
cachedData: undefined,
|
||||
};
|
||||
cachedScripts.set(filename, cached);
|
||||
}
|
||||
|
||||
const script = new vm.Script(cached.code, {
|
||||
filename,
|
||||
displayErrors: true,
|
||||
lineOffset: -1,
|
||||
cachedData: cached.cachedData,
|
||||
produceCachedData: true,
|
||||
});
|
||||
|
||||
if (script.cachedDataProduced) {
|
||||
cached.cachedData = script.cachedData;
|
||||
}
|
||||
|
||||
const module = {
|
||||
id: filename,
|
||||
exports: {},
|
||||
};
|
||||
const req = id => runModuleInTestContext(id, filename, context, moduleCache);
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
script
|
||||
.runInContext(context)
|
||||
.call(module.exports, module.exports, req, module, filename, dirname);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic implementation of CommonJS so we can execute `@babel/polyfill` inside our test context.
|
||||
* This allows us to run our unittests
|
||||
*/
|
||||
function runModuleInTestContext(id: string, relativeFilename: string) {
|
||||
function runModuleInTestContext(
|
||||
id: string,
|
||||
relativeFilename: string,
|
||||
context: Context,
|
||||
moduleCache: Object,
|
||||
) {
|
||||
const filename = resolve.sync(id, {
|
||||
basedir: path.dirname(relativeFilename),
|
||||
});
|
||||
@ -50,23 +116,17 @@ function runModuleInTestContext(id: string, relativeFilename: string) {
|
||||
// the context's global scope.
|
||||
if (filename === id) return require(id);
|
||||
|
||||
// Modules can only evaluate once per context, so the moduleCache is a
|
||||
// stronger cache guarantee than the LRU's Script cache.
|
||||
if (moduleCache[filename]) return moduleCache[filename].exports;
|
||||
|
||||
const module = (moduleCache[filename] = {
|
||||
id: filename,
|
||||
exports: {},
|
||||
});
|
||||
const dirname = path.dirname(filename);
|
||||
const req = id => runModuleInTestContext(id, filename);
|
||||
|
||||
const src = fs.readFileSync(filename, "utf8");
|
||||
const code = `(function (exports, require, module, __filename, __dirname) {\n${src}\n});`;
|
||||
|
||||
vm.runInContext(code, testContext, {
|
||||
const module = runCacheableScriptInTestContext(
|
||||
filename,
|
||||
displayErrors: true,
|
||||
lineOffset: -1,
|
||||
}).call(module.exports, module.exports, req, module, filename, dirname);
|
||||
() => fs.readFileSync(filename, "utf8"),
|
||||
context,
|
||||
moduleCache,
|
||||
);
|
||||
moduleCache[filename] = module;
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
@ -76,10 +136,15 @@ function runModuleInTestContext(id: string, relativeFilename: string) {
|
||||
*
|
||||
* Exposed for unit tests, not for use as an API.
|
||||
*/
|
||||
export function runCodeInTestContext(code: string, opts: { filename: string }) {
|
||||
export function runCodeInTestContext(
|
||||
code: string,
|
||||
opts: { filename: string },
|
||||
context = sharedTestContext,
|
||||
) {
|
||||
const filename = opts.filename;
|
||||
const dirname = path.dirname(filename);
|
||||
const req = id => runModuleInTestContext(id, filename);
|
||||
const moduleCache = contextModuleCache.get(context);
|
||||
const req = id => runModuleInTestContext(id, filename, context, moduleCache);
|
||||
|
||||
const module = {
|
||||
id: filename,
|
||||
@ -94,7 +159,7 @@ export function runCodeInTestContext(code: string, opts: { filename: string }) {
|
||||
// Note: This isn't doing .call(module.exports, ...) because some of our tests currently
|
||||
// rely on 'this === global'.
|
||||
const src = `(function(exports, require, module, __filename, __dirname, opts) {\n${code}\n});`;
|
||||
return vm.runInContext(src, testContext, {
|
||||
return vm.runInContext(src, context, {
|
||||
filename,
|
||||
displayErrors: true,
|
||||
lineOffset: -1,
|
||||
@ -183,13 +248,14 @@ function run(task) {
|
||||
let resultExec;
|
||||
|
||||
if (execCode) {
|
||||
const context = createContext();
|
||||
const execOpts = getOpts(exec);
|
||||
result = babel.transform(execCode, execOpts);
|
||||
checkDuplicatedNodes(babel, result.ast);
|
||||
execCode = result.code;
|
||||
|
||||
try {
|
||||
resultExec = runCodeInTestContext(execCode, execOpts);
|
||||
resultExec = runCodeInTestContext(execCode, execOpts, context);
|
||||
} catch (err) {
|
||||
// Pass empty location to include the whole file in the output.
|
||||
err.message =
|
||||
|
||||
@ -1,27 +1,25 @@
|
||||
"use strict";
|
||||
const NOSET = `NOSET${__filename}`;
|
||||
const NOWRITE = `NOWRITE${__filename}`;
|
||||
|
||||
Object.defineProperty(Object.prototype, NOSET, {
|
||||
Object.defineProperty(Object.prototype, 'NOSET', {
|
||||
get(value) {
|
||||
// noop
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(Object.prototype, NOWRITE, {
|
||||
Object.defineProperty(Object.prototype, 'NOWRITE', {
|
||||
writable: false,
|
||||
value: 'abc',
|
||||
});
|
||||
|
||||
const obj = { [NOSET]: 123 };
|
||||
const obj = { 'NOSET': 123 };
|
||||
// this won't work as expected if transformed as Object.assign (or equivalent)
|
||||
// because those trigger object setters (spread don't)
|
||||
expect(() => {
|
||||
const objSpread = { ...obj };
|
||||
}).toThrow();
|
||||
|
||||
const obj2 = { [NOWRITE]: 456 };
|
||||
// this throws `TypeError: Cannot assign to read only property 'NOWRITE'`
|
||||
const obj2 = { 'NOWRITE': 456 };
|
||||
// this throws `TypeError: Cannot assign to read only property 'NOWRITE'`
|
||||
// if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties
|
||||
// (spread defines them)
|
||||
expect(() => {
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
"use strict";
|
||||
const NOSET = `NOSET${__filename}`;
|
||||
const NOWRITE = `NOWRITE${__filename}`;
|
||||
|
||||
Object.defineProperty(Object.prototype, NOSET, {
|
||||
Object.defineProperty(Object.prototype, 'NOSET', {
|
||||
get(value) {
|
||||
// noop
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(Object.prototype, NOWRITE, {
|
||||
Object.defineProperty(Object.prototype, 'NOWRITE', {
|
||||
writable: false,
|
||||
value: 'abc',
|
||||
});
|
||||
|
||||
const obj = { [NOSET]: 123 };
|
||||
const obj = { 'NOSET': 123 };
|
||||
// this won't work as expected if transformed as Object.assign (or equivalent)
|
||||
// because those trigger object setters (spread don't)
|
||||
expect(() => {
|
||||
const objSpread = { ...obj };
|
||||
}).toThrow();
|
||||
|
||||
const obj2 = { [NOWRITE]: 456 };
|
||||
// this throws `TypeError: Cannot assign to read only property 'NOWRITE'`
|
||||
const obj2 = { 'NOWRITE': 456 };
|
||||
// this throws `TypeError: Cannot assign to read only property 'NOWRITE'`
|
||||
// if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties
|
||||
// (spread defines them)
|
||||
expect(() => {
|
||||
|
||||
@ -1,31 +1,27 @@
|
||||
"use strict";
|
||||
const NOSET = `NOSET${__filename}`;
|
||||
const NOWRITE = `NOWRITE${__filename}`;
|
||||
|
||||
Object.defineProperty(Object.prototype, NOSET, {
|
||||
set(value) {
|
||||
Object.defineProperty(Object.prototype, 'NOSET', {
|
||||
get(value) {
|
||||
// noop
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(Object.prototype, NOWRITE, {
|
||||
Object.defineProperty(Object.prototype, 'NOWRITE', {
|
||||
writable: false,
|
||||
value: 'abc',
|
||||
});
|
||||
|
||||
const obj = { [NOSET]: 123 };
|
||||
const obj = { NOSET: 123 };
|
||||
// this wouldn't work as expected if transformed as Object.assign (or equivalent)
|
||||
// because those trigger object setters (spread don't)
|
||||
const objSpread = { ...obj };
|
||||
expect(objSpread).toHaveProperty('NOSET', 123);
|
||||
|
||||
const obj2 = { NOSET: 123, [NOWRITE]: 456 };
|
||||
const obj2 = { NOWRITE: 456 };
|
||||
// this line would throw `TypeError: Cannot assign to read only property 'NOWRITE'`
|
||||
// if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties
|
||||
// (spread defines them)
|
||||
const obj2Spread = { ...obj2 };
|
||||
|
||||
expect(objSpread).toEqual(obj);
|
||||
expect(obj2Spread).toEqual(obj2);
|
||||
expect(obj2Spread).toHaveProperty('NOWRITE', 456);
|
||||
|
||||
const KEY = Symbol('key');
|
||||
const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' };
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
const code = multiline([
|
||||
"for (const {foo, ...bar} of { bar: [] }) {",
|
||||
"() => foo;",
|
||||
"const [qux] = bar;",
|
||||
"try {} catch (e) {",
|
||||
"let quux = qux;",
|
||||
"}",
|
||||
"}"
|
||||
]);
|
||||
const code = `
|
||||
for (const {foo, ...bar} of { bar: [] }) {
|
||||
() => foo;
|
||||
const [qux] = bar;
|
||||
try {} catch (e) {
|
||||
let quux = qux;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let programPath;
|
||||
let forOfPath;
|
||||
|
||||
@ -1,26 +1,15 @@
|
||||
const oldReflect = this.Reflect;
|
||||
const oldHTMLElement = this.HTMLElement;
|
||||
// Pretend that `Reflect.construct` isn't supported.
|
||||
global.Reflect = undefined;
|
||||
|
||||
try {
|
||||
// Pretend that `Reflect.construct` isn't supported.
|
||||
this.Reflect = undefined;
|
||||
global.HTMLElement = function() {
|
||||
// Here, `this.HTMLElement` is this function, not the original HTMLElement
|
||||
// constructor. `this.constructor` should be this function too, but isn't.
|
||||
constructor = this.constructor;
|
||||
};
|
||||
|
||||
this.HTMLElement = function() {
|
||||
// Here, `this.HTMLElement` is this function, not the original HTMLElement
|
||||
// constructor. `this.constructor` should be this function too, but isn't.
|
||||
constructor = this.constructor;
|
||||
};
|
||||
|
||||
var constructor;
|
||||
|
||||
class CustomElement extends HTMLElement {};
|
||||
new CustomElement();
|
||||
|
||||
expect(constructor).toBe(CustomElement);
|
||||
} finally {
|
||||
// Restore original env
|
||||
this.Reflect = oldReflect;
|
||||
this.HTMLElement = oldHTMLElement;
|
||||
}
|
||||
var constructor;
|
||||
|
||||
class CustomElement extends HTMLElement {}
|
||||
new CustomElement();
|
||||
|
||||
expect(constructor).toBe(CustomElement);
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
var a = (() => [1, 2, 3])();
|
||||
|
||||
// Simulate old environment
|
||||
let _Symbol = Symbol;
|
||||
Symbol = void 0;
|
||||
try {
|
||||
var [first, ...rest] = a;
|
||||
global.Symbol = void 0;
|
||||
|
||||
expect(first).toBe(1);
|
||||
expect(rest).toEqual([2, 3]);
|
||||
} finally {
|
||||
Symbol = _Symbol;
|
||||
}
|
||||
var [first, ...rest] = a;
|
||||
|
||||
expect(first).toBe(1);
|
||||
expect(rest).toEqual([2, 3]);
|
||||
|
||||
@ -8,15 +8,8 @@ expect(
|
||||
() => [foo, bar] = {}
|
||||
).toThrow(/destructure non-iterable/);
|
||||
|
||||
// Simulate old browser
|
||||
let _Symbol = Symbol;
|
||||
Symbol = void 0;
|
||||
try {
|
||||
global.Symbol = void 0;
|
||||
|
||||
expect(
|
||||
() => [foo, bar] = {}
|
||||
).toThrow(/destructure non-iterable/);
|
||||
|
||||
} finally {
|
||||
Symbol = _Symbol;
|
||||
}
|
||||
expect(
|
||||
() => [foo, bar] = {}
|
||||
).toThrow(/destructure non-iterable/);
|
||||
|
||||
@ -3,13 +3,13 @@ var actual = transform(
|
||||
Object.assign({}, opts, { filename: '/fake/path/mock.js' })
|
||||
).code;
|
||||
|
||||
var expected = multiline([
|
||||
'var _jsxFileName = "/fake/path/mock.js";',
|
||||
'var x = <sometag __source={{',
|
||||
' fileName: _jsxFileName,',
|
||||
' lineNumber: 1,',
|
||||
' columnNumber: 9',
|
||||
'}} />;',
|
||||
]);
|
||||
var expected = `
|
||||
var _jsxFileName = "/fake/path/mock.js";
|
||||
var x = <sometag __source={{
|
||||
fileName: _jsxFileName,
|
||||
lineNumber: 1,
|
||||
columnNumber: 9
|
||||
}} />;
|
||||
`.trim();
|
||||
|
||||
expect(actual).toBe(expected);
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
var a = (() => [2, 3])();
|
||||
|
||||
// Simulate old environment
|
||||
let _Symbol = Symbol;
|
||||
Symbol = void 0;
|
||||
try {
|
||||
expect([1, ...a]).toEqual([1, 2, 3]);
|
||||
} finally {
|
||||
Symbol = _Symbol;
|
||||
}
|
||||
global.Symbol = void 0;
|
||||
|
||||
expect([1, ...a]).toEqual([1, 2, 3]);
|
||||
|
||||
@ -5,6 +5,6 @@ expect(() => [...undefined]).toThrow(/spread non-iterable/);
|
||||
expect(() => [...o]).toThrow(/spread non-iterable/);
|
||||
|
||||
// Simulate old browser
|
||||
Symbol = void 0;
|
||||
global.Symbol = void 0;
|
||||
|
||||
expect(() => [...o]).toThrow(/spread non-iterable/);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user