diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a6509c2d0..04539cac15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ #### :house: Internal - Remove `Primitive_option.toUndefined`; use `valFromOption` for optional ffi args. https://github.com/rescript-lang/rescript/pull/8380 +- Run `super_errors` fixtures in parallel (~2.4× faster locally). https://github.com/rescript-lang/rescript/pull/8430 # 13.0.0-alpha.4 diff --git a/tests/build_tests/super_errors/input.js b/tests/build_tests/super_errors/input.js index 3ea9e08c68..8b8385dbcb 100644 --- a/tests/build_tests/super_errors/input.js +++ b/tests/build_tests/super_errors/input.js @@ -2,6 +2,7 @@ import { readdirSync } from "node:fs"; import * as fs from "node:fs/promises"; +import * as os from "node:os"; import * as path from "node:path"; import { setup } from "#dev/process"; import { normalizeNewlines } from "#dev/utils"; @@ -10,9 +11,9 @@ const { bsc } = setup(import.meta.dirname); const expectedDir = path.join(import.meta.dirname, "expected"); -const fixtures = readdirSync(path.join(import.meta.dirname, "fixtures")).filter( - fileName => path.extname(fileName) === ".res", -); +const fixtures = readdirSync(path.join(import.meta.dirname, "fixtures")) + .filter(fileName => path.extname(fileName) === ".res") + .sort(); const prefix = ["-w", "+A", "-bs-jsx", "4"]; @@ -32,13 +33,13 @@ function postProcessErrorOutput(output) { return normalizeNewlines(result); } -let doneTasksCount = 0; -let atLeastOneTaskFailed = false; - -for (const fileName of fixtures) { +/** + * @param {string} fileName + * @returns {Promise<{ fileName: string, failure: string | null }>} + */ +async function runFixture(fileName) { const fullFilePath = path.join(import.meta.dirname, "fixtures", fileName); const { stderr } = await bsc([...prefix, "-color", "always", fullFilePath]); - doneTasksCount++; // careful of: // - warning test that actually succeeded in compiling (warning's still in stderr, so the code path is shared here) // - accidentally succeeding tests (not likely in this context), @@ -47,23 +48,50 @@ for (const fileName of fixtures) { const expectedFilePath = path.join(expectedDir, `${fileName}.expected`); if (updateTests) { await fs.writeFile(expectedFilePath, actualErrorOutput); - } else { - const expectedErrorOutput = postProcessErrorOutput( - await fs.readFile(expectedFilePath, "utf-8"), - ); - if (expectedErrorOutput !== actualErrorOutput) { - console.error( - `The old and new error output for the test ${fullFilePath} aren't the same`, - ); - console.error("\n=== Old:"); - console.error(expectedErrorOutput); - console.error("\n=== New:"); - console.error(actualErrorOutput); - atLeastOneTaskFailed = true; - } + return { fileName, failure: null }; + } + const expectedErrorOutput = postProcessErrorOutput( + await fs.readFile(expectedFilePath, "utf-8"), + ); + if (expectedErrorOutput === actualErrorOutput) { + return { fileName, failure: null }; + } + return { + fileName, + failure: [ + `The old and new error output for the test ${fullFilePath} aren't the same`, + "\n=== Old:", + expectedErrorOutput, + "\n=== New:", + actualErrorOutput, + ].join("\n"), + }; +} - if (doneTasksCount === fixtures.length && atLeastOneTaskFailed) { - process.exit(1); +// Run fixtures in parallel with a worker-pool. Each fixture spawns a bsc +// process, so wall time is dominated by process startup; serialising the +// loop made the suite scale linearly with fixture count. +const concurrency = Math.max(1, os.availableParallelism()); +let cursor = 0; +const results = new Array(fixtures.length); + +await Promise.all( + Array.from({ length: Math.min(concurrency, fixtures.length) }, async () => { + while (true) { + const i = cursor++; + if (i >= fixtures.length) return; + results[i] = await runFixture(fixtures[i]); } + }), +); + +let atLeastOneTaskFailed = false; +for (const { failure } of results) { + if (failure !== null) { + console.error(failure); + atLeastOneTaskFailed = true; } } +if (atLeastOneTaskFailed) { + process.exit(1); +}