webpack/test/BenchmarkTestCases.benchmar...

319 lines
9.0 KiB
JavaScript
Raw Normal View History

2017-01-18 05:26:38 +08:00
"use strict";
const path = require("path");
const fs = require("graceful-fs");
2018-02-11 12:27:09 +08:00
const asyncLib = require("neo-async");
2017-01-18 05:26:38 +08:00
const Benchmark = require("benchmark");
const { remove } = require("./helpers/remove");
2017-01-18 05:26:38 +08:00
2020-03-29 06:10:15 +08:00
describe("BenchmarkTestCases", function () {
2017-01-18 05:26:38 +08:00
const casesPath = path.join(__dirname, "benchmarkCases");
2020-03-29 06:10:15 +08:00
const tests = fs.readdirSync(casesPath).filter(function (folder) {
2018-02-25 18:46:17 +08:00
return (
2024-07-31 16:02:41 +08:00
!folder.includes("_") &&
2018-02-25 18:46:17 +08:00
fs.existsSync(path.resolve(casesPath, folder, "webpack.config.js"))
);
2017-01-18 05:26:38 +08:00
});
const baselinesPath = path.join(__dirname, "js", "benchmark-baselines");
const baselines = [];
try {
fs.mkdirSync(path.join(__dirname, "js"));
2024-07-31 15:37:05 +08:00
} catch (_err) {} // eslint-disable-line no-empty
2017-01-18 05:26:38 +08:00
try {
fs.mkdirSync(baselinesPath);
2024-07-31 15:37:05 +08:00
} catch (_err) {} // eslint-disable-line no-empty
2017-01-18 05:26:38 +08:00
2020-03-29 06:10:15 +08:00
beforeAll(function (done) {
2017-01-18 05:26:38 +08:00
const git = require("simple-git");
const rootPath = path.join(__dirname, "..");
getBaselineRevs(rootPath, (err, baselineRevisions) => {
2018-02-25 18:46:17 +08:00
if (err) return done(err);
asyncLib.eachSeries(
baselineRevisions,
(baselineInfo, callback) => {
const baselineRevision = baselineInfo.rev;
const baselinePath = path.resolve(baselinesPath, baselineRevision);
if (fs.existsSync(path.resolve(baselinePath, ".git"))) {
doLoadWebpack();
} else {
try {
fs.mkdirSync(baselinePath);
2024-07-31 15:37:05 +08:00
} catch (_err) {} // eslint-disable-line no-empty
2018-02-25 18:46:17 +08:00
const gitIndex = path.resolve(rootPath, ".git/index");
const index = fs.readFileSync(gitIndex);
git(rootPath).raw(
["rev-list", "-n", "1", "HEAD"],
(err, prevHead) => {
if (err) return callback(err);
git(baselinePath).raw(
[
"--git-dir",
path.join(rootPath, ".git"),
"reset",
"--hard",
baselineRevision
],
err => {
if (err) return callback(err);
git(rootPath).raw(
["reset", "--soft", prevHead.split("\n")[0]],
err => {
if (err) return callback(err);
fs.writeFileSync(gitIndex, index);
2018-03-07 08:15:41 +08:00
try {
doLoadWebpack();
} catch (err) {
callback(err);
}
2018-02-25 18:46:17 +08:00
}
);
}
);
}
);
}
2017-01-18 05:26:38 +08:00
2018-02-25 18:46:17 +08:00
function doLoadWebpack() {
const baselineWebpack = jest.requireActual(
2018-12-09 21:26:35 +08:00
path.resolve(baselinePath, "lib/index.js")
);
2018-02-25 18:46:17 +08:00
baselines.push({
name: baselineInfo.name,
rev: baselineRevision,
webpack: baselineWebpack
});
callback();
}
},
err => {
2018-03-07 08:15:41 +08:00
if (err) {
done(err);
return;
2018-03-07 08:15:41 +08:00
}
createTests();
done();
}
2018-02-25 18:46:17 +08:00
);
2017-01-18 05:26:38 +08:00
});
2018-01-27 23:34:56 +08:00
}, 270000);
2017-01-18 05:26:38 +08:00
2018-03-07 08:15:41 +08:00
afterAll(() => {
remove(baselinesPath);
});
2018-03-07 08:15:41 +08:00
2017-01-18 05:26:38 +08:00
function getBaselineRevs(rootPath, callback) {
const git = require("simple-git")(rootPath);
2024-07-31 10:39:30 +08:00
const lastVersionTag = `v${require("../package.json").version}`;
2017-01-18 05:26:38 +08:00
git.raw(["rev-list", "-n", "1", lastVersionTag], (err, resultVersion) => {
2018-02-25 18:46:17 +08:00
if (err) return callback(err);
2017-01-18 05:26:38 +08:00
const matchVersion = /^([a-f0-9]+)\s*$/.exec(resultVersion);
2018-02-25 18:46:17 +08:00
if (!matchVersion)
return callback(new Error("Invalid result from git revparse"));
2017-01-18 05:26:38 +08:00
const revLastVersion = matchVersion[1];
2018-02-25 18:46:17 +08:00
git.raw(
["rev-list", "--parents", "-n", "1", "HEAD"],
(err, resultParents) => {
if (err) return callback(err);
const match = /^([a-f0-9]+)\s*([a-f0-9]+)\s*([a-f0-9]+)?\s*$/.exec(
resultParents
);
if (!match)
return callback(new Error("Invalid result from git rev-list"));
const head = match[1];
const parent1 = match[2];
const parent2 = match[3];
if (parent2 && parent1) {
return callback(
null,
[
{
name: "HEAD",
rev: head
},
head !== revLastVersion && {
name: lastVersionTag,
rev: revLastVersion
},
parent1 !== revLastVersion &&
head !== revLastVersion && {
name: "base",
rev: parent1
}
].filter(Boolean)
);
} else if (parent1) {
return callback(
null,
[
{
name: "HEAD",
rev: head
},
head !== revLastVersion && {
name: lastVersionTag,
rev: revLastVersion
}
].filter(Boolean)
);
}
2024-07-31 04:21:27 +08:00
return callback(new Error("No baseline found"));
2017-01-18 05:26:38 +08:00
}
2018-02-25 18:46:17 +08:00
);
2017-01-18 05:26:38 +08:00
});
}
function tDistribution(n) {
// two-sided, 90%
// https://en.wikipedia.org/wiki/Student%27s_t-distribution
2018-02-25 18:46:17 +08:00
if (n <= 30) {
// 1 2 ...
2018-02-25 18:46:17 +08:00
const data = [
2021-05-11 15:31:46 +08:00
6.314, 2.92, 2.353, 2.132, 2.015, 1.943, 1.895, 1.86, 1.833, 1.812,
1.796, 1.782, 1.771, 1.761, 1.753, 1.746, 1.74, 1.734, 1.729, 1.725,
1.721, 1.717, 1.714, 1.711, 1.708, 1.706, 1.703, 1.701, 1.699, 1.697
2018-02-25 18:46:17 +08:00
];
return data[n - 1];
2018-02-25 18:46:17 +08:00
} else if (n <= 120) {
2017-01-18 05:26:38 +08:00
// 30 40 50 60 70 80 90 100 110 120
2018-02-25 18:46:17 +08:00
const data = [
2021-05-11 15:31:46 +08:00
1.697, 1.684, 1.676, 1.671, 1.667, 1.664, 1.662, 1.66, 1.659, 1.658
2018-02-25 18:46:17 +08:00
];
2017-01-18 05:26:38 +08:00
var a = data[Math.floor(n / 10) - 3];
var b = data[Math.ceil(n / 10) - 3];
var f = n / 10 - Math.floor(n / 10);
return a * (1 - f) + b * f;
}
2024-07-31 04:21:27 +08:00
return 1.645;
}
2017-01-18 05:26:38 +08:00
function runBenchmark(webpack, config, callback) {
// warmup
const warmupCompiler = webpack(config, (err, stats) => {
warmupCompiler.purgeInputFileSystem();
2018-02-25 18:46:17 +08:00
const bench = new Benchmark(
2020-03-29 06:10:15 +08:00
function (deferred) {
2018-02-25 18:46:17 +08:00
const compiler = webpack(config, (err, stats) => {
compiler.purgeInputFileSystem();
if (err) {
callback(err);
return;
}
if (stats.hasErrors()) {
callback(new Error(stats.toJson().errors.join("\n\n")));
return;
}
deferred.resolve();
});
2017-01-18 05:26:38 +08:00
},
2018-02-25 18:46:17 +08:00
{
maxTime: 30,
defer: true,
initCount: 1,
2020-03-29 06:10:15 +08:00
onComplete: function () {
2018-02-25 18:46:17 +08:00
const stats = bench.stats;
const n = stats.sample.length;
const nSqrt = Math.sqrt(n);
const z = tDistribution(n - 1);
2018-05-29 22:14:16 +08:00
stats.minConfidence = stats.mean - (z * stats.deviation) / nSqrt;
stats.maxConfidence = stats.mean + (z * stats.deviation) / nSqrt;
stats.text = `${Math.round(stats.mean * 1000)} ms ± ${Math.round(
2018-02-25 18:46:17 +08:00
stats.deviation * 1000
)} ms [${Math.round(stats.minConfidence * 1000)} ms; ${Math.round(
2018-02-25 18:46:17 +08:00
stats.maxConfidence * 1000
)} ms]`;
2018-02-25 18:46:17 +08:00
callback(null, bench.stats);
},
onError: callback
}
);
2017-01-18 05:26:38 +08:00
bench.run({
async: true
});
});
}
2018-03-07 08:15:41 +08:00
function createTests() {
2024-08-02 02:36:27 +08:00
for (const testName of tests) {
2018-03-07 08:15:41 +08:00
const testDirectory = path.join(casesPath, testName);
let headStats = null;
2020-03-29 06:10:15 +08:00
describe(`${testName} create benchmarks`, function () {
2024-08-02 02:36:27 +08:00
for (const baseline of baselines) {
2018-03-07 08:15:41 +08:00
let baselineStats = null;
2024-08-02 02:36:27 +08:00
// eslint-disable-next-line no-loop-func
2020-03-29 06:10:15 +08:00
it(`should benchmark ${baseline.name} (${baseline.rev})`, function (done) {
const outputDirectory = path.join(
__dirname,
"js",
"benchmark",
`baseline-${baseline.name}`,
testName
);
const config =
Object.create(
jest.requireActual(
path.join(testDirectory, "webpack.config.js")
)
) || {};
config.output = Object.create(config.output || {});
if (!config.context) config.context = testDirectory;
if (!config.output.path) config.output.path = outputDirectory;
runBenchmark(baseline.webpack, config, (err, stats) => {
if (err) return done(err);
process.stderr.write(` ${baseline.name} ${stats.text}`);
if (baseline.name === "HEAD") headStats = stats;
else baselineStats = stats;
done();
});
}, 180000);
2024-08-02 02:36:27 +08:00
// eslint-disable-next-line no-loop-func
it(`should benchmark ${baseline.name} (${baseline.rev})`, done => {
const outputDirectory = path.join(
__dirname,
"js",
"benchmark",
`baseline-${baseline.name}`,
testName
);
const config =
jest.requireActual(
path.join(testDirectory, "webpack.config.js")
) || {};
config.output = config.output || {};
if (!config.context) config.context = testDirectory;
if (!config.output.path) config.output.path = outputDirectory;
runBenchmark(baseline.webpack, config, (err, stats) => {
if (err) return done(err);
process.stderr.write(` ${baseline.name} ${stats.text}`);
if (baseline.name === "HEAD") headStats = stats;
else baselineStats = stats;
done();
});
}, 180000);
2018-03-07 08:15:41 +08:00
if (baseline.name !== "HEAD") {
2024-08-02 02:36:27 +08:00
// eslint-disable-next-line no-loop-func
2020-03-29 06:10:15 +08:00
it(`HEAD should not be slower than ${baseline.name} (${baseline.rev})`, function () {
2018-03-07 08:15:41 +08:00
if (baselineStats.maxConfidence < headStats.minConfidence) {
throw new Error(
2019-06-09 17:23:42 +08:00
`HEAD (${headStats.text}) is slower than ${baseline.name} (${baselineStats.text}) (90% confidence)`
2018-03-07 08:15:41 +08:00
);
} else if (
baselineStats.minConfidence > headStats.maxConfidence
) {
2018-03-07 08:15:41 +08:00
console.log(
`======> HEAD is ${Math.round(
2018-05-29 22:14:16 +08:00
(baselineStats.mean / headStats.mean) * 100 - 100
2018-03-07 08:15:41 +08:00
)}% faster than ${baseline.name} (90% confidence)!`
);
}
});
}
2024-08-02 02:36:27 +08:00
}
2017-01-18 05:26:38 +08:00
});
2024-08-02 02:36:27 +08:00
}
2018-03-07 08:15:41 +08:00
}
2017-01-18 05:26:38 +08:00
});