forked from evanw/esbuild
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathes6-fuzzer.js
144 lines (129 loc) · 4.61 KB
/
es6-fuzzer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// This fuzzer attempts to find issues with the "scope hoisting" optimizations
// for ES6-style imports and exports. It compares esbuild's behavior with the
// behavior of node's experimental module support.
(async () => {
// Make sure this script runs as an ES6 module so we can import both ES6 modules and CommonJS modules
if (typeof require !== 'undefined') {
const childProcess = require('child_process')
const child = childProcess.spawn('node', ['--experimental-modules', '--input-type=module'], {
cwd: __dirname,
stdio: ['pipe', 'inherit', 'inherit'],
})
child.stdin.write(require('fs').readFileSync(__filename))
child.stdin.end()
child.on('close', code => process.exit(code))
return
}
const { default: { buildBinary, dirname, removeRecursiveSync } } = await import('./esbuild.js');
const childProcess = await import('child_process');
const util = await import('util');
const path = await import('path');
const fs = await import('fs');
const esbuildPath = buildBinary();
let failureCount = 0;
let nextTest = 0;
function reportFailure(testDir, files, kind, error) {
failureCount++;
console.log(`❌ FAILURE ${kind}: ${error}\n DIR: ${testDir}` +
Object.keys(files).map(x => `\n ${x} => ${files[x]}`).join(''));
}
function circularObjectToString(root) {
let map = new Map();
let counter = 0;
let visit = obj => {
if (typeof obj !== 'object') return JSON.stringify(obj);
if (map.has(obj)) return `$${map.get(obj)}`;
map.set(obj, counter++);
const keys = Object.keys(obj).sort();
return `$${map.get(obj)} = {${keys.map(key =>
`${JSON.stringify(key)}: ${visit(obj[key])}`).join(', ')}}`;
};
return visit(root);
}
function checkSameExportObject(a, b) {
a = circularObjectToString(a);
b = circularObjectToString(b);
if (a !== b) throw new Error(`Different exports:\n ${a}\n ${b}`);
}
async function fuzzOnce(parentDir) {
const mjs_or_cjs = () => Math.random() < 0.1 ? 'cjs' : 'mjs';
const names = [
'a.' + mjs_or_cjs(),
'b.' + mjs_or_cjs(),
'c.' + mjs_or_cjs(),
'd.' + mjs_or_cjs(),
'e.' + mjs_or_cjs(),
];
const randomName = () => names[Math.random() * names.length | 0];
const files = {};
for (const name of names) {
if (name.endsWith('.cjs')) {
files[name] = `module.exports = 123`;
} else {
switch (Math.random() * 5 | 0) {
case 0:
files[name] = `export const foo = 123`;
break;
case 1:
files[name] = `export default 123`;
break;
case 2:
files[name] = `export * from "./${randomName()}"`;
break;
case 3:
files[name] = `export * as foo from "./${randomName()}"`;
break;
case 4:
files[name] = `import * as foo from "./${randomName()}"; export {foo}`;
break;
}
}
}
// Write the files to the file system
const testDir = path.join(parentDir, (nextTest++).toString());
fs.mkdirSync(testDir);
for (const name in files) {
fs.writeFileSync(path.join(testDir, name), files[name]);
}
if (nextTest % 100 === 0) console.log(`Checked ${nextTest} test cases`);
// Load the raw module using node
const entryPoint = path.join(testDir, names[0]);
let realExports = await import(entryPoint);
if (entryPoint.endsWith('.cjs')) realExports = realExports.default;
// Bundle to a CommonJS module using esbuild
const cjsFile = path.join(testDir, 'out.cjs');
await util.promisify(childProcess.execFile)(esbuildPath, [
'--bundle',
'--outfile=' + cjsFile,
'--format=cjs',
entryPoint,
], { stdio: 'pipe' });
// Validate the CommonJS module bundle
try {
let { default: cjsExports } = await import(cjsFile);
checkSameExportObject(realExports, cjsExports);
} catch (e) {
reportFailure(testDir, files, 'cjs', e + '');
return;
}
// Remove data for successful tests
removeRecursiveSync(testDir);
}
const parentDir = path.join(dirname, '.es6-fuzzer');
removeRecursiveSync(parentDir);
fs.mkdirSync(parentDir);
// Run a set number of tests in parallel
let promises = [];
for (let i = 0; i < 10; i++) {
let promise = fuzzOnce(parentDir);
for (let j = 0; j < 100; j++) {
promise = promise.then(() => fuzzOnce(parentDir));
}
promises.push(promise);
}
await Promise.all(promises);
// Remove everything if all tests passed
if (failureCount === 0) {
removeRecursiveSync(parentDir);
}
})().catch(e => setTimeout(() => { throw e }));