mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			529 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| "use strict";
 | |
| 
 | |
| require("./helpers/warmup-webpack");
 | |
| const path = require("path");
 | |
| const fs = require("graceful-fs");
 | |
| const vm = require("vm");
 | |
| const { pathToFileURL, URL } = require("url");
 | |
| const rimraf = require("rimraf");
 | |
| const checkArrayExpectation = require("./checkArrayExpectation");
 | |
| const createLazyTestEnv = require("./helpers/createLazyTestEnv");
 | |
| const deprecationTracking = require("./helpers/deprecationTracking");
 | |
| const captureStdio = require("./helpers/captureStdio");
 | |
| const asModule = require("./helpers/asModule");
 | |
| const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors");
 | |
| 
 | |
| const casesPath = path.join(__dirname, "cases");
 | |
| let categories = fs.readdirSync(casesPath);
 | |
| categories = categories.map(cat => {
 | |
| 	return {
 | |
| 		name: cat,
 | |
| 		tests: fs
 | |
| 			.readdirSync(path.join(casesPath, cat))
 | |
| 			.filter(folder => folder.indexOf("_") < 0)
 | |
| 	};
 | |
| });
 | |
| 
 | |
| const createLogger = appendTarget => {
 | |
| 	return {
 | |
| 		log: l => appendTarget.push(l),
 | |
| 		debug: l => appendTarget.push(l),
 | |
| 		trace: l => appendTarget.push(l),
 | |
| 		info: l => appendTarget.push(l),
 | |
| 		warn: console.warn.bind(console),
 | |
| 		error: console.error.bind(console),
 | |
| 		logTime: () => {},
 | |
| 		group: () => {},
 | |
| 		groupCollapsed: () => {},
 | |
| 		groupEnd: () => {},
 | |
| 		profile: () => {},
 | |
| 		profileEnd: () => {},
 | |
| 		clear: () => {},
 | |
| 		status: () => {}
 | |
| 	};
 | |
| };
 | |
| 
 | |
| const describeCases = config => {
 | |
| 	describe(config.name, () => {
 | |
| 		let stderr;
 | |
| 		beforeEach(() => {
 | |
| 			stderr = captureStdio(process.stderr, true);
 | |
| 		});
 | |
| 		afterEach(() => {
 | |
| 			stderr.restore();
 | |
| 		});
 | |
| 		categories.forEach(category => {
 | |
| 			describe(category.name, function () {
 | |
| 				jest.setTimeout(20000);
 | |
| 
 | |
| 				category.tests
 | |
| 					.filter(test => {
 | |
| 						const testDirectory = path.join(casesPath, category.name, test);
 | |
| 						const filterPath = path.join(testDirectory, "test.filter.js");
 | |
| 						if (fs.existsSync(filterPath) && !require(filterPath)(config)) {
 | |
| 							describe.skip(test, () => {
 | |
| 								it("filtered", () => {});
 | |
| 							});
 | |
| 							return false;
 | |
| 						}
 | |
| 						return true;
 | |
| 					})
 | |
| 					.forEach(testName => {
 | |
| 						let infraStructureLog = [];
 | |
| 
 | |
| 						describe(testName, () => {
 | |
| 							const testDirectory = path.join(
 | |
| 								casesPath,
 | |
| 								category.name,
 | |
| 								testName
 | |
| 							);
 | |
| 							const outputDirectory = path.join(
 | |
| 								__dirname,
 | |
| 								"js",
 | |
| 								config.name,
 | |
| 								category.name,
 | |
| 								testName
 | |
| 							);
 | |
| 							const cacheDirectory = path.join(
 | |
| 								__dirname,
 | |
| 								"js/.cache",
 | |
| 								config.name,
 | |
| 								category.name,
 | |
| 								testName
 | |
| 							);
 | |
| 							let testConfig = {};
 | |
| 							const testConfigPath = path.join(testDirectory, "test.config.js");
 | |
| 							if (fs.existsSync(testConfigPath)) {
 | |
| 								testConfig = require(testConfigPath);
 | |
| 							}
 | |
| 							const TerserPlugin = require("terser-webpack-plugin");
 | |
| 							const terserForTesting = new TerserPlugin({
 | |
| 								parallel: false
 | |
| 							});
 | |
| 							let options = {
 | |
| 								context: casesPath,
 | |
| 								entry: "./" + category.name + "/" + testName + "/",
 | |
| 								target: config.target || "async-node",
 | |
| 								devtool: config.devtool,
 | |
| 								mode: config.mode || "none",
 | |
| 								optimization: config.mode
 | |
| 									? {
 | |
| 											emitOnErrors: true,
 | |
| 											minimizer: [terserForTesting],
 | |
| 											...config.optimization
 | |
| 									  }
 | |
| 									: {
 | |
| 											removeAvailableModules: true,
 | |
| 											removeEmptyChunks: true,
 | |
| 											mergeDuplicateChunks: true,
 | |
| 											flagIncludedChunks: true,
 | |
| 											sideEffects: true,
 | |
| 											providedExports: true,
 | |
| 											usedExports: true,
 | |
| 											mangleExports: true,
 | |
| 											emitOnErrors: true,
 | |
| 											concatenateModules: false,
 | |
| 											moduleIds: "size",
 | |
| 											chunkIds: "size",
 | |
| 											minimizer: [terserForTesting],
 | |
| 											...config.optimization
 | |
| 									  },
 | |
| 								performance: {
 | |
| 									hints: false
 | |
| 								},
 | |
| 								node: {
 | |
| 									__dirname: "mock",
 | |
| 									__filename: "mock"
 | |
| 								},
 | |
| 								cache: config.cache && {
 | |
| 									cacheDirectory,
 | |
| 									...config.cache
 | |
| 								},
 | |
| 								output: {
 | |
| 									pathinfo: "verbose",
 | |
| 									path: outputDirectory,
 | |
| 									filename: config.module ? "bundle.mjs" : "bundle.js"
 | |
| 								},
 | |
| 								resolve: {
 | |
| 									modules: ["web_modules", "node_modules"],
 | |
| 									mainFields: [
 | |
| 										"webpack",
 | |
| 										"browser",
 | |
| 										"web",
 | |
| 										"browserify",
 | |
| 										["jam", "main"],
 | |
| 										"main"
 | |
| 									],
 | |
| 									aliasFields: ["browser"],
 | |
| 									extensions: [".webpack.js", ".web.js", ".js", ".json"]
 | |
| 								},
 | |
| 								resolveLoader: {
 | |
| 									modules: [
 | |
| 										"web_loaders",
 | |
| 										"web_modules",
 | |
| 										"node_loaders",
 | |
| 										"node_modules"
 | |
| 									],
 | |
| 									mainFields: ["webpackLoader", "webLoader", "loader", "main"],
 | |
| 									extensions: [
 | |
| 										".webpack-loader.js",
 | |
| 										".web-loader.js",
 | |
| 										".loader.js",
 | |
| 										".js"
 | |
| 									]
 | |
| 								},
 | |
| 								module: {
 | |
| 									rules: [
 | |
| 										{
 | |
| 											test: /\.coffee$/,
 | |
| 											loader: "coffee-loader"
 | |
| 										},
 | |
| 										{
 | |
| 											test: /\.pug/,
 | |
| 											loader: "pug-loader"
 | |
| 										},
 | |
| 										{
 | |
| 											test: /\.wat$/i,
 | |
| 											loader: "wast-loader",
 | |
| 											type: "webassembly/async"
 | |
| 										}
 | |
| 									]
 | |
| 								},
 | |
| 								plugins: (config.plugins || []).concat(function () {
 | |
| 									this.hooks.compilation.tap("TestCasesTest", compilation => {
 | |
| 										[
 | |
| 											"optimize",
 | |
| 											"optimizeModules",
 | |
| 											"optimizeChunks",
 | |
| 											"afterOptimizeTree",
 | |
| 											"afterOptimizeAssets"
 | |
| 										].forEach(hook => {
 | |
| 											compilation.hooks[hook].tap("TestCasesTest", () =>
 | |
| 												compilation.checkConstraints()
 | |
| 											);
 | |
| 										});
 | |
| 									});
 | |
| 								}),
 | |
| 								experiments: {
 | |
| 									asyncWebAssembly: true,
 | |
| 									topLevelAwait: true,
 | |
| 									backCompat: false,
 | |
| 									...(config.module ? { outputModule: true } : {})
 | |
| 								},
 | |
| 								infrastructureLogging: config.cache && {
 | |
| 									debug: true,
 | |
| 									console: createLogger(infraStructureLog)
 | |
| 								}
 | |
| 							};
 | |
| 							const cleanups = [];
 | |
| 							afterAll(() => {
 | |
| 								options = undefined;
 | |
| 								testConfig = undefined;
 | |
| 								for (const fn of cleanups) fn();
 | |
| 							});
 | |
| 							beforeAll(done => {
 | |
| 								rimraf(cacheDirectory, done);
 | |
| 							});
 | |
| 							if (config.cache) {
 | |
| 								it(
 | |
| 									`${testName} should pre-compile to fill disk cache (1st)`,
 | |
| 									done => {
 | |
| 										const oldPath = options.output.path;
 | |
| 										options.output.path = path.join(
 | |
| 											options.output.path,
 | |
| 											"cache1"
 | |
| 										);
 | |
| 										infraStructureLog.length = 0;
 | |
| 										const deprecationTracker = deprecationTracking.start();
 | |
| 										const webpack = require("..");
 | |
| 										webpack(options, err => {
 | |
| 											deprecationTracker();
 | |
| 											options.output.path = oldPath;
 | |
| 											if (err) return done(err);
 | |
| 											const infrastructureLogErrors =
 | |
| 												filterInfraStructureErrors(infraStructureLog, {
 | |
| 													run: 1,
 | |
| 													options
 | |
| 												});
 | |
| 											if (
 | |
| 												infrastructureLogErrors.length &&
 | |
| 												checkArrayExpectation(
 | |
| 													testDirectory,
 | |
| 													{ infrastructureLogs: infrastructureLogErrors },
 | |
| 													"infrastructureLog",
 | |
| 													"infrastructure-log",
 | |
| 													"InfrastructureLog",
 | |
| 													done
 | |
| 												)
 | |
| 											) {
 | |
| 												return;
 | |
| 											}
 | |
| 											done();
 | |
| 										});
 | |
| 									},
 | |
| 									testConfig.timeout || 60000
 | |
| 								);
 | |
| 								it(
 | |
| 									`${testName} should pre-compile to fill disk cache (2nd)`,
 | |
| 									done => {
 | |
| 										const oldPath = options.output.path;
 | |
| 										options.output.path = path.join(
 | |
| 											options.output.path,
 | |
| 											"cache2"
 | |
| 										);
 | |
| 										infraStructureLog.length = 0;
 | |
| 										const deprecationTracker = deprecationTracking.start();
 | |
| 										const webpack = require("..");
 | |
| 										webpack(options, err => {
 | |
| 											deprecationTracker();
 | |
| 											options.output.path = oldPath;
 | |
| 											if (err) return done(err);
 | |
| 											const infrastructureLogErrors =
 | |
| 												filterInfraStructureErrors(infraStructureLog, {
 | |
| 													run: 2,
 | |
| 													options
 | |
| 												});
 | |
| 											if (
 | |
| 												infrastructureLogErrors.length &&
 | |
| 												checkArrayExpectation(
 | |
| 													testDirectory,
 | |
| 													{ infrastructureLogs: infrastructureLogErrors },
 | |
| 													"infrastructureLog",
 | |
| 													"infrastructure-log",
 | |
| 													"InfrastructureLog",
 | |
| 													done
 | |
| 												)
 | |
| 											) {
 | |
| 												return;
 | |
| 											}
 | |
| 											done();
 | |
| 										});
 | |
| 									},
 | |
| 									testConfig.cachedTimeout || testConfig.timeout || 10000
 | |
| 								);
 | |
| 							}
 | |
| 							it(
 | |
| 								testName + " should compile",
 | |
| 								done => {
 | |
| 									infraStructureLog.length = 0;
 | |
| 									const webpack = require("..");
 | |
| 									const compiler = webpack(options);
 | |
| 									const run = () => {
 | |
| 										const deprecationTracker = deprecationTracking.start();
 | |
| 										compiler.run((err, stats) => {
 | |
| 											const deprecations = deprecationTracker();
 | |
| 											if (err) return done(err);
 | |
| 											const infrastructureLogErrors =
 | |
| 												filterInfraStructureErrors(infraStructureLog, {
 | |
| 													run: 3,
 | |
| 													options
 | |
| 												});
 | |
| 											if (
 | |
| 												infrastructureLogErrors.length &&
 | |
| 												checkArrayExpectation(
 | |
| 													testDirectory,
 | |
| 													{ infrastructureLogs: infrastructureLogErrors },
 | |
| 													"infrastructureLog",
 | |
| 													"infrastructure-log",
 | |
| 													"InfrastructureLog",
 | |
| 													done
 | |
| 												)
 | |
| 											) {
 | |
| 												return;
 | |
| 											}
 | |
| 											compiler.close(err => {
 | |
| 												if (err) return done(err);
 | |
| 												const statOptions = {
 | |
| 													preset: "verbose",
 | |
| 													colors: false,
 | |
| 													modules: true,
 | |
| 													reasonsSpace: 1000
 | |
| 												};
 | |
| 												fs.mkdirSync(outputDirectory, { recursive: true });
 | |
| 												fs.writeFileSync(
 | |
| 													path.join(outputDirectory, "stats.txt"),
 | |
| 													stats.toString(statOptions),
 | |
| 													"utf-8"
 | |
| 												);
 | |
| 												const jsonStats = stats.toJson({
 | |
| 													errorDetails: true,
 | |
| 													modules: false,
 | |
| 													assets: false,
 | |
| 													chunks: false
 | |
| 												});
 | |
| 												if (
 | |
| 													checkArrayExpectation(
 | |
| 														testDirectory,
 | |
| 														jsonStats,
 | |
| 														"error",
 | |
| 														"Error",
 | |
| 														done
 | |
| 													)
 | |
| 												) {
 | |
| 													return;
 | |
| 												}
 | |
| 												if (
 | |
| 													checkArrayExpectation(
 | |
| 														testDirectory,
 | |
| 														jsonStats,
 | |
| 														"warning",
 | |
| 														"Warning",
 | |
| 														done
 | |
| 													)
 | |
| 												) {
 | |
| 													return;
 | |
| 												}
 | |
| 												const infrastructureLogging = stderr.toString();
 | |
| 												if (infrastructureLogging) {
 | |
| 													done(
 | |
| 														new Error(
 | |
| 															"Errors/Warnings during build:\n" +
 | |
| 																infrastructureLogging
 | |
| 														)
 | |
| 													);
 | |
| 												}
 | |
| 
 | |
| 												expect(deprecations).toEqual(config.deprecations || []);
 | |
| 
 | |
| 												Promise.resolve().then(done);
 | |
| 											});
 | |
| 										});
 | |
| 									};
 | |
| 									if (config.cache) {
 | |
| 										// pre-compile to fill memory cache
 | |
| 										const deprecationTracker = deprecationTracking.start();
 | |
| 										compiler.run(err => {
 | |
| 											deprecationTracker();
 | |
| 											if (err) return done(err);
 | |
| 											run();
 | |
| 										});
 | |
| 									} else {
 | |
| 										run();
 | |
| 									}
 | |
| 								},
 | |
| 								testConfig.cachedTimeout ||
 | |
| 									testConfig.timeout ||
 | |
| 									(config.cache ? 20000 : 60000)
 | |
| 							);
 | |
| 
 | |
| 							it(
 | |
| 								testName + " should load the compiled tests",
 | |
| 								done => {
 | |
| 									const esmContext = vm.createContext({
 | |
| 										it: _it,
 | |
| 										expect,
 | |
| 										process,
 | |
| 										global,
 | |
| 										URL,
 | |
| 										Buffer,
 | |
| 										setTimeout,
 | |
| 										setImmediate,
 | |
| 										nsObj: function (m) {
 | |
| 											Object.defineProperty(m, Symbol.toStringTag, {
 | |
| 												value: "Module"
 | |
| 											});
 | |
| 											return m;
 | |
| 										}
 | |
| 									});
 | |
| 									cleanups.push(() => (esmContext.it = undefined));
 | |
| 									function _require(module, esmMode) {
 | |
| 										if (module.startsWith("./")) {
 | |
| 											const p = path.join(outputDirectory, module);
 | |
| 											const content = fs.readFileSync(p, "utf-8");
 | |
| 											if (p.endsWith(".mjs")) {
 | |
| 												let esm;
 | |
| 												try {
 | |
| 													esm = new vm.SourceTextModule(content, {
 | |
| 														identifier: p,
 | |
| 														context: esmContext,
 | |
| 														initializeImportMeta: (meta, module) => {
 | |
| 															meta.url = pathToFileURL(p).href;
 | |
| 														},
 | |
| 														importModuleDynamically: async (
 | |
| 															specifier,
 | |
| 															module
 | |
| 														) => {
 | |
| 															const result = await _require(
 | |
| 																specifier,
 | |
| 																"evaluated"
 | |
| 															);
 | |
| 															return await asModule(result, module.context);
 | |
| 														}
 | |
| 													});
 | |
| 													cleanups.push(() => (esmContext.it = undefined));
 | |
| 												} catch (e) {
 | |
| 													console.log(e);
 | |
| 													e.message += `\nwhile parsing ${p}`;
 | |
| 													throw e;
 | |
| 												}
 | |
| 												if (esmMode === "unlinked") return esm;
 | |
| 												return (async () => {
 | |
| 													await esm.link(async (specifier, module) => {
 | |
| 														return await asModule(
 | |
| 															await _require(specifier, "unlinked"),
 | |
| 															module.context,
 | |
| 															true
 | |
| 														);
 | |
| 													});
 | |
| 													// node.js 10 needs instantiate
 | |
| 													if (esm.instantiate) esm.instantiate();
 | |
| 													await esm.evaluate();
 | |
| 													if (esmMode === "evaluated") return esm;
 | |
| 													const ns = esm.namespace;
 | |
| 													return ns.default && ns.default instanceof Promise
 | |
| 														? ns.default
 | |
| 														: ns;
 | |
| 												})();
 | |
| 											} else {
 | |
| 												const fn = vm.runInThisContext(
 | |
| 													"(function(require, module, exports, __dirname, __filename, it, expect) {" +
 | |
| 														"global.expect = expect;" +
 | |
| 														'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
 | |
| 														content +
 | |
| 														"\n})",
 | |
| 													p
 | |
| 												);
 | |
| 												const m = {
 | |
| 													exports: {},
 | |
| 													webpackTestSuiteModule: true
 | |
| 												};
 | |
| 												fn.call(
 | |
| 													m.exports,
 | |
| 													_require,
 | |
| 													m,
 | |
| 													m.exports,
 | |
| 													outputDirectory,
 | |
| 													p,
 | |
| 													_it,
 | |
| 													expect
 | |
| 												);
 | |
| 												return m.exports;
 | |
| 											}
 | |
| 										} else return require(module);
 | |
| 									}
 | |
| 									_require.webpackTestSuiteRequire = true;
 | |
| 									Promise.resolve()
 | |
| 										.then(() => _require("./" + options.output.filename))
 | |
| 										.then(() => {
 | |
| 											if (getNumberOfTests() === 0)
 | |
| 												return done(
 | |
| 													new Error("No tests exported by test case")
 | |
| 												);
 | |
| 											done();
 | |
| 										}, done);
 | |
| 								},
 | |
| 								10000
 | |
| 							);
 | |
| 
 | |
| 							const { it: _it, getNumberOfTests } = createLazyTestEnv(
 | |
| 								testConfig.timeout || 10000
 | |
| 							);
 | |
| 						});
 | |
| 					});
 | |
| 			});
 | |
| 		});
 | |
| 	});
 | |
| };
 | |
| 
 | |
| exports.describeCases = describeCases;
 |