mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			524 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			524 lines
		
	
	
		
			14 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 => ({
 | |
| 	name: cat,
 | |
| 	tests: fs
 | |
| 		.readdirSync(path.join(casesPath, cat))
 | |
| 		.filter(folder => !folder.includes("_"))
 | |
| }));
 | |
| 
 | |
| const createLogger = appendTarget => ({
 | |
| 	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();
 | |
| 		});
 | |
| 		for (const category of categories) {
 | |
| 			// eslint-disable-next-line no-loop-func
 | |
| 			describe(category.name, function () {
 | |
| 				jest.setTimeout(20000);
 | |
| 
 | |
| 				for (const testName of 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)) {
 | |
| 						// eslint-disable-next-line jest/no-disabled-tests
 | |
| 						describe.skip(test, () => {
 | |
| 							it("filtered", () => {});
 | |
| 						});
 | |
| 						return false;
 | |
| 					}
 | |
| 					return true;
 | |
| 				})) {
 | |
| 					const infraStructureLog = [];
 | |
| 
 | |
| 					// eslint-disable-next-line no-loop-func
 | |
| 					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 => {
 | |
| 									for (const hook of [
 | |
| 										"optimize",
 | |
| 										"optimizeModules",
 | |
| 										"optimizeChunks",
 | |
| 										"afterOptimizeTree",
 | |
| 										"afterOptimizeAssets"
 | |
| 									]) {
 | |
| 										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",
 | |
| 												options,
 | |
| 												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",
 | |
| 												options,
 | |
| 												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",
 | |
| 												options,
 | |
| 												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",
 | |
| 													options,
 | |
| 													done
 | |
| 												)
 | |
| 											) {
 | |
| 												return;
 | |
| 											}
 | |
| 											if (
 | |
| 												checkArrayExpectation(
 | |
| 													testDirectory,
 | |
| 													jsonStats,
 | |
| 													"warning",
 | |
| 													"Warning",
 | |
| 													options,
 | |
| 													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 (err) {
 | |
| 											console.log(err);
 | |
| 											err.message += `\nwhile parsing ${p}`;
 | |
| 											throw err;
 | |
| 										}
 | |
| 										if (esmMode === "unlinked") return esm;
 | |
| 										return (async () => {
 | |
| 											await esm.link(
 | |
| 												async (specifier, module) =>
 | |
| 													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;
 | |
| 										})();
 | |
| 									}
 | |
| 									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;
 | |
| 								}
 | |
| 								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
 | |
| 						);
 | |
| 					});
 | |
| 				}
 | |
| 			});
 | |
| 		}
 | |
| 	});
 | |
| };
 | |
| 
 | |
| // eslint-disable-next-line jest/no-export
 | |
| module.exports.describeCases = describeCases;
 |