Lint large test repos in parallel (via worker threads) for shorter run times.

This commit is contained in:
David Anson 2024-06-07 22:24:28 -07:00
parent 446fe901c3
commit e447db33c9
5 changed files with 68 additions and 6 deletions

View file

@ -0,0 +1,44 @@
// @ts-check
"use strict";
// eslint-disable-next-line n/no-unsupported-features/node-builtins
const { availableParallelism } = require("node:os");
const { Worker } = require("node:worker_threads");
const markdownlintSync = require("../lib/markdownlint").sync;
/**
* Lint specified Markdown files (using multiple threads).
*
* @param {import("../lib/markdownlint").Options} options Configuration options.
* @returns {Promise<import("../lib/markdownlint").LintResults>} Results object.
*/
function markdownlintParallel(options) {
const workerCount = availableParallelism();
const files = options.files || [];
const chunkSize = Math.ceil(files.length / workerCount);
const promises = [];
for (let i = 0; i < workerCount; i++) {
promises.push(new Promise((resolve, reject) => {
const workerData = {
...options,
"files": files.slice(i * chunkSize, (i + 1) * chunkSize)
}
const worker = new Worker(__filename.replace(/parallel\.js$/, "worker.js"), { workerData });
worker.on("message", resolve);
worker.on("error", reject);
}));
}
return Promise.all(promises).then((workerResults) => {
const combinedResults = markdownlintSync(null);
for (const workerResult of workerResults) {
// eslint-disable-next-line guard-for-in
for (const result in workerResult) {
combinedResults[result] = workerResult[result];
}
}
return combinedResults;
});
}
module.exports = markdownlintParallel;

View file

@ -10,5 +10,5 @@ test("https://github.com/dotnet/docs", (t) => {
const rootDir = "./test-repos/dotnet-docs"; const rootDir = "./test-repos/dotnet-docs";
const globPatterns = [ join(rootDir, "**/*.md") ]; const globPatterns = [ join(rootDir, "**/*.md") ];
const configPath = join(rootDir, ".markdownlint-cli2.jsonc"); const configPath = join(rootDir, ".markdownlint-cli2.jsonc");
return lintTestRepo(t, globPatterns, configPath); return lintTestRepo(t, globPatterns, configPath, true);
}); });

View file

@ -10,5 +10,5 @@ test("https://github.com/mdn/content", (t) => {
const rootDir = "./test-repos/mdn-content"; const rootDir = "./test-repos/mdn-content";
const globPatterns = [ join(rootDir, "**/*.md") ]; const globPatterns = [ join(rootDir, "**/*.md") ];
const configPath = join(rootDir, ".markdownlint-cli2.jsonc"); const configPath = join(rootDir, ".markdownlint-cli2.jsonc");
return lintTestRepo(t, globPatterns, configPath); return lintTestRepo(t, globPatterns, configPath, true);
}); });

View file

@ -5,7 +5,8 @@
const { join } = require("node:path").posix; const { join } = require("node:path").posix;
const jsoncParser = require("jsonc-parser"); const jsoncParser = require("jsonc-parser");
const jsYaml = require("js-yaml"); const jsYaml = require("js-yaml");
const markdownlint = require("../lib/markdownlint"); const { markdownlint, readConfig } = require("../lib/markdownlint").promises;
const markdownlintParallel = require("./markdownlint-test-parallel");
/** /**
* Lints a test repository. * Lints a test repository.
@ -13,9 +14,10 @@ const markdownlint = require("../lib/markdownlint");
* @param {Object} t Test instance. * @param {Object} t Test instance.
* @param {string[]} globPatterns Array of files to in/exclude. * @param {string[]} globPatterns Array of files to in/exclude.
* @param {string} configPath Path to config file. * @param {string} configPath Path to config file.
* @param {boolean} [parallel] True to lint in parallel.
* @returns {Promise} Test result. * @returns {Promise} Test result.
*/ */
async function lintTestRepo(t, globPatterns, configPath) { async function lintTestRepo(t, globPatterns, configPath, parallel) {
t.plan(1); t.plan(1);
const { globby } = await import("globby"); const { globby } = await import("globby");
const jsoncParse = (json) => { const jsoncParse = (json) => {
@ -25,7 +27,7 @@ async function lintTestRepo(t, globPatterns, configPath) {
const yamlParse = (yaml) => jsYaml.load(yaml); const yamlParse = (yaml) => jsYaml.load(yaml);
return Promise.all([ return Promise.all([
globby(globPatterns), globby(globPatterns),
markdownlint.promises.readConfig(configPath, [ jsoncParse, yamlParse ]) readConfig(configPath, [ jsoncParse, yamlParse ])
]).then((globbyAndReadConfigResults) => { ]).then((globbyAndReadConfigResults) => {
const [ files, rawConfig ] = globbyAndReadConfigResults; const [ files, rawConfig ] = globbyAndReadConfigResults;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -37,7 +39,7 @@ async function lintTestRepo(t, globPatterns, configPath) {
v v
]) ])
); );
return markdownlint.promises.markdownlint({ return (parallel ? markdownlintParallel : markdownlint)({
files, files,
config config
}).then((results) => { }).then((results) => {

View file

@ -0,0 +1,16 @@
// @ts-check
"use strict";
const { parentPort, workerData } = require("node:worker_threads");
const markdownlint = require("../lib/markdownlint").promises.markdownlint;
// eslint-disable-next-line unicorn/prefer-top-level-await
markdownlint(workerData).then((lintResults) => {
// @ts-ignore
parentPort.
// eslint-disable-next-line unicorn/require-post-message-target-origin
postMessage(lintResults);
// eslint-disable-next-line n/no-process-exit
process.exit();
});