From e964c7d8597ba8b57434551c3bd058eefd7c29d8 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Thu, 14 Oct 2021 09:54:37 +0300 Subject: [PATCH] Allow configuring the number of threads --- README.md | 7 +++++ lib/getThreads.js | 34 ++++++++++++++++++++++ lib/htmllint.js | 11 +++----- tasks/html.js | 3 +- test/threads_test.js | 67 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 lib/getThreads.js create mode 100644 test/threads_test.js diff --git a/README.md b/README.md index 0737708..3a69b7c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,13 @@ When combined with a watching task (such as [grunt-contrib-watch][watch]), even ## Options +### `threads` + +* Type: `String`, `Boolean`, `Number` +* Default: `auto`, which is the number of threads -1 + +Use this option to control the number of threads that grunt-htmllint will use when validating a big number of files. This will spawn as many Java processes as the number of threads. + ### `ignore` * Type: `Array`, `String`, or `RegExp` diff --git a/lib/getThreads.js b/lib/getThreads.js new file mode 100644 index 0000000..0e399ce --- /dev/null +++ b/lib/getThreads.js @@ -0,0 +1,34 @@ +'use strict'; + +const os = require('os'); + +// eslint-disable-next-line unicorn/explicit-length-check +const CPUS = os.cpus() && os.cpus().length; +const THREADS = CPUS > 2 ? CPUS - 1 : 1; + +function getThreads(config) { + const { threads } = config; + + const defaultCheck = threads === 'auto' || + threads === -1 || + threads === '' || + threads === true || + threads === null || + typeof threads === 'undefined'; + + if (defaultCheck) { + return THREADS; + } + + if (threads === 0 || threads === 1 || threads === false) { + return 1; + } + + if (threads > 1) { + return threads; + } + + throw new Error(`Invalid threads option! (${threads})`); +} + +module.exports = getThreads; diff --git a/lib/htmllint.js b/lib/htmllint.js index a3b9783..c7cdb60 100644 --- a/lib/htmllint.js +++ b/lib/htmllint.js @@ -1,12 +1,12 @@ 'use strict'; -const os = require('os'); const path = require('path'); const { execFile } = require('child_process'); const async = require('async'); const chunkify = require('./chunkify.js'); -const javaDetect = require('./javaDetect.js'); +const getThreads = require('./getThreads.js'); const javaArgs = require('./javaArgs.js'); +const javaDetect = require('./javaDetect.js'); const parseErrorMessages = require('./parseErrorMessages.js'); const processErrorMessages = require('./processErrorMessages.js'); @@ -20,10 +20,6 @@ const MAX_CHARS = 5000; */ const MAX_BUFFER = 20_000 * 1024; -// eslint-disable-next-line unicorn/explicit-length-check -const CPUS = os.cpus() && os.cpus().length; -const THREADS = CPUS > 2 ? CPUS - 1 : 1; - function htmllint(config, done) { if (config.files.length === 0) { return done(null, []); @@ -42,8 +38,9 @@ function htmllint(config, done) { const files = config.files.map(file => path.normalize(file)); const chunks = chunkify(files, MAX_CHARS); + const threads = getThreads(config); - async.mapLimit(chunks, THREADS, (chunk, cb) => { + async.mapLimit(chunks, threads, (chunk, cb) => { const args = javaArgs(java, chunk, config); execFile('java', args, { maxBuffer: MAX_BUFFER, shell: true }, (error, stdout, stderr) => { diff --git a/tasks/html.js b/tasks/html.js index 75761ce..a82bc2d 100644 --- a/tasks/html.js +++ b/tasks/html.js @@ -21,7 +21,8 @@ module.exports = grunt => { force: false, absoluteFilePathsForReporter: false, errorlevels: ['info', 'warning', 'error'], - noLangDetect: false + noLangDetect: false, + threads: 'auto' }); const { force } = options; let { reporterOutput } = options; diff --git a/test/threads_test.js b/test/threads_test.js new file mode 100644 index 0000000..a509d44 --- /dev/null +++ b/test/threads_test.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('assert').strict; +const os = require('os'); +const getThreads = require('../lib/getThreads.js'); + +// eslint-disable-next-line unicorn/explicit-length-check +const CPUS = os.cpus() && os.cpus().length; +const THREADS = CPUS > 2 ? CPUS - 1 : 1; + +describe('getThreads', () => { + it('should use the number of available threads -1', done => { + const config = {}; + + for (const option of ['auto', true, -1, '', null, undefined]) { + config.threads = option; + const expected = THREADS; + const actual = getThreads(config); + + assert.equal(actual, expected, `"thread: ${option}" failed!`); + } + + done(); + }); + + it('threads: false', done => { + const config = { + threads: false + }; + const expected = 1; + const actual = getThreads(config); + + assert.equal(actual, expected); + done(); + }); + + it('threads: 4', done => { + const config = { + threads: 4 + }; + const expected = 4; + const actual = getThreads(config); + + assert.equal(actual, expected); + done(); + }); + + it('threads: -2', done => { + const config = { + threads: -2 + }; + const expected = () => getThreads(config); + + assert.throws(expected, /^Error: Invalid threads/); + done(); + }); + + it('threads: "foo"', done => { + const config = { + threads: 'foo' + }; + const expected = () => getThreads(config); + + assert.throws(expected, /^Error: Invalid threads/); + done(); + }); +});