diff --git a/benchmark.js b/benchmark.js new file mode 100644 index 00000000..c89da155 --- /dev/null +++ b/benchmark.js @@ -0,0 +1,400 @@ +// benchmark.js +const { + Worker, + isMainThread, + parentPort, + workerData, +} = require('worker_threads'); + +const { fork } = require('child_process'); + +const net = require('net'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const readline = require('readline'); + +const SMALL_ITERATIONS = 10_000; +const BIG_ITERATIONS = 1_000; + +const smallPayload = { + id: 123, + name: 'hello', + ok: true, +}; + +const bigPayload = { + items: Array.from({ length: 10_000 }, (_, i) => ({ + id: i, + name: `item-${i}`, + value: Math.random(), + tags: ['a', 'b', 'c'], + })), +}; + +function hrMs(start) { + return Number(process.hrtime.bigint() - start) / 1e6; +} + +async function benchmarkWorker(generator) { + return new Promise((resolve, reject) => { + const worker = new Worker(__filename); + + let iterations = 0; + const start = process.hrtime.bigint(); + + worker.on('message', () => { + iterations++; + const r = generator.next(); + + if (r.done) { + const totalMs = hrMs(start); + + worker.terminate().then(() => { + resolve({ + totalMs, + avgMs: totalMs / iterations, + }); + }); + } else { + worker.postMessage(r.value); + } + }); + + worker.on('error', reject); + + worker.postMessage(generator.next().value); + }); +} + +async function benchmarkWorkerJson(generator) { + return new Promise((resolve, reject) => { + const worker = new Worker(__filename, { + workerData: { mode: 'worker-json' }, + }); + + let iterations = 0; + const start = process.hrtime.bigint(); + + worker.on('message', (msg) => { + JSON.parse(msg); + + iterations++; + const r = generator.next(); + + if (r.done) { + const totalMs = hrMs(start); + + worker.terminate().then(() => { + resolve({ + totalMs, + avgMs: totalMs / iterations, + }); + }); + } else { + worker.postMessage(JSON.stringify(r.value)); + } + }); + + worker.on('error', reject); + + worker.postMessage(JSON.stringify(generator.next().value)); + }); +} + +async function benchmarkForkIPC(generator) { + return new Promise((resolve, reject) => { + const child = fork(__filename, ['fork-ipc'], { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + }); + + let iterations = 0; + const start = process.hrtime.bigint(); + + child.on('message', () => { + iterations++; + const r = generator.next(); + + if (r.done) { + const totalMs = hrMs(start); + + child.kill(); + + resolve({ + totalMs, + avgMs: totalMs / iterations, + }); + } else { + child.send(r.value); + } + }); + + child.on('error', reject); + + child.send(generator.next().value); + }); +} + +async function benchmarkForkIPCJson(generator) { + return new Promise((resolve, reject) => { + const child = fork(__filename, ['fork-ipc-json'], { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + }); + + let iterations = 0; + const start = process.hrtime.bigint(); + + child.on('message', (msg) => { + JSON.parse(msg); + + iterations++; + const r = generator.next(); + + if (r.done) { + const totalMs = hrMs(start); + + child.kill(); + + resolve({ + totalMs, + avgMs: totalMs / iterations, + }); + } else { + child.send(JSON.stringify(r.value)); + } + }); + + child.on('error', reject); + + child.send(JSON.stringify(generator.next().value)); + }); +} + +async function benchmarkUnixSocket(generator) { + const pipeName = + process.platform === 'win32' + ? '\\\\.\\pipe\\node-benchmark-' + process.pid + : path.join(os.tmpdir(), `node-benchmark-${process.pid}.sock`); + + try { + fs.unlinkSync(pipeName); + } catch { + // Ignore errors. + } + + return new Promise((resolve, reject) => { + const child = fork(__filename, ['fork-socket', pipeName], { + stdio: ['inherit', 'inherit', 'inherit', 'ipc'], + }); + + child.once('message', () => { + const socket = net.createConnection(pipeName); + + let iterations = 0; + + socket.setEncoding('utf8'); + socket.setNoDelay(true); + + // https://nodejs.org/api/readline.html#example-read-file-stream-line-by-line + const rl = readline.createInterface({ + input: socket, + crlfDelay: Infinity, + }); + + rl.on('line', (line) => { + JSON.parse(line); + + iterations++; + const r = generator.next(); + + if (r.done) { + const totalMs = hrMs(start); + + socket.destroy(); + child.kill(); + + resolve({ + totalMs, + avgMs: totalMs / iterations, + }); + + return; + } + + socket.write(JSON.stringify(r.value) + '\n'); + }); + + socket.on('error', reject); + + const start = process.hrtime.bigint(); + + socket.write(JSON.stringify(generator.next().value) + '\n'); + }); + + child.on('error', reject); + }); +} + +async function runBenchmarks() { + const cases = [ + { + name: 'real', + generator: function* () { + const messages = fs + .readFileSync('messages.txt', 'utf8') + .trim() + .split('\n') + .map(JSON.parse); + for (var j = 0; j < 10; j++) { + for (var i = 0; i < messages.length; i++) { + yield messages[i]; + } + } + }, + }, + { + name: 'small JSON', + generator: function* () { + for (var i = 0; i < SMALL_ITERATIONS; i++) { + yield smallPayload; + } + }, + }, + { + name: 'big JSON', + generator: function* () { + for (var i = 0; i < BIG_ITERATIONS; i++) { + yield bigPayload; + } + }, + }, + ]; + + for (const testCase of cases) { + console.log(`\n=== ${testCase.name} ===`); + + console.log('worker'); + const worker = await benchmarkWorker(testCase.generator()); + + console.log('workerJson'); + const workerJson = await benchmarkWorkerJson(testCase.generator()); + + console.log('forkIPC'); + const forkIPC = await benchmarkForkIPC(testCase.generator()); + + console.log('forkIPCJson'); + const forkIPCJson = await benchmarkForkIPCJson(testCase.generator()); + + console.log('socket'); + const socket = await benchmarkUnixSocket(testCase.generator()); + + console.table([ + { + transport: 'worker_threads', + avgMs: worker.avgMs.toFixed(6), + totalMs: worker.totalMs.toFixed(2), + }, + { + transport: 'worker_threads + JSON', + avgMs: workerJson.avgMs.toFixed(6), + totalMs: workerJson.totalMs.toFixed(2), + }, + { + transport: 'fork + process.send', + avgMs: forkIPC.avgMs.toFixed(6), + totalMs: forkIPC.totalMs.toFixed(2), + }, + { + transport: 'fork + process.send + JSON', + avgMs: forkIPCJson.avgMs.toFixed(6), + totalMs: forkIPCJson.totalMs.toFixed(2), + }, + { + transport: 'fork + UNIX socket', + avgMs: socket.avgMs.toFixed(6), + totalMs: socket.totalMs.toFixed(2), + }, + ]); + } +} + +// +// Worker implementation (regular and JSON) +// +if (!isMainThread) { + if (workerData?.mode === 'worker-json') { + parentPort.on('message', (str) => { + const obj = JSON.parse(str); + + parentPort.postMessage(JSON.stringify(obj)); + }); + } else { + parentPort.on('message', (msg) => { + parentPort.postMessage(msg); + }); + } +} + +// +// Fork IPC implementation +// +else if (process.argv[2] === 'fork-ipc') { + process.on('message', (msg) => { + process.send(msg); + }); +} + +// +// Fork IPC JSON implementation +// +else if (process.argv[2] === 'fork-ipc-json') { + process.on('message', (str) => { + const obj = JSON.parse(str); + + process.send(JSON.stringify(obj)); + }); +} + +// +// Fork socket implementation +// +else if (process.argv[2] === 'fork-socket') { + const pipeName = process.argv[3]; + + try { + fs.unlinkSync(pipeName); + } catch { + // Ignore errors. + } + + const server = net.createServer((socket) => { + socket.setEncoding('utf8'); + socket.setNoDelay(true); + + // https://nodejs.org/api/readline.html#example-read-file-stream-line-by-line + const rl = readline.createInterface({ + input: socket, + crlfDelay: Infinity, + }); + + rl.on('line', (line) => { + const obj = JSON.parse(line); + + socket.write(JSON.stringify(obj) + '\n'); + }); + }); + + server.listen(pipeName, () => { + process.send('ready'); + }); +} + +// +// Main +// +else { + runBenchmarks().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/elm/src/Test/Runner/Node.elm b/elm/src/Test/Runner/Node.elm index 75235773..679d7e35 100644 --- a/elm/src/Test/Runner/Node.elm +++ b/elm/src/Test/Runner/Node.elm @@ -81,7 +81,7 @@ type Msg {-| The port names are prefixed to reduce the likelihood of the project having a port with the same name, which is a compile error. -} -port elmTestPort__send : String -> Cmd msg +port elmTestPort__send : Decode.Value -> Cmd msg port elmTestPort__receive : (Decode.Value -> msg) -> Sub msg @@ -140,7 +140,6 @@ update msg ({ testReporter } as model) = , ( "exitCode", Encode.int exitCode ) , ( "message", summary ) ] - |> Encode.encode 0 |> elmTestPort__send in ( model, cmd ) @@ -152,7 +151,6 @@ update msg ({ testReporter } as model) = [ ( "type", Encode.string "ERROR" ) , ( "message", Encode.string (Decode.errorToString err) ) ] - |> Encode.encode 0 |> elmTestPort__send in ( model, cmd ) @@ -224,7 +222,6 @@ sendResults isFinished testReporter results = |> Encode.object ) ] - |> Encode.encode 0 |> elmTestPort__send @@ -245,7 +242,6 @@ sendBegin model = [] in Encode.object (baseFields ++ extraFields) - |> Encode.encode 0 |> elmTestPort__send @@ -336,7 +332,6 @@ failInit message report _ = , ( "exitCode", Encode.int 1 ) , ( "message", Encode.string message ) ] - |> Encode.encode 0 |> elmTestPort__send in ( model, cmd ) diff --git a/lib/Generate.js b/lib/Generate.js index f38e5b0e..c781bce9 100644 --- a/lib/Generate.js +++ b/lib/Generate.js @@ -15,11 +15,10 @@ const after = fs.readFileSync( ); /** - * @param { string } pipeFilename * @param { string } dest * @returns { void } */ -function prepareCompiledJsFile(pipeFilename, dest) { +function prepareCompiledJsFile(dest) { const content = fs.readFileSync(dest, 'utf8'); const finalContent = ` ${before} @@ -27,7 +26,6 @@ var Elm = (function(module) { ${addKernelTestChecking(content)} return this.Elm; })({}); -var pipeFilename = ${JSON.stringify(pipeFilename)}; ${after} `.trim(); fs.writeFileSync(dest, finalContent); diff --git a/lib/RunTests.js b/lib/RunTests.js index 7da7be85..123ecf42 100644 --- a/lib/RunTests.js +++ b/lib/RunTests.js @@ -10,26 +10,6 @@ const Project = require('./Project'); const Report = require('./Report'); const Supervisor = require('./Supervisor'); -/** - * Incorporate the process PID into the socket name, so elm-test processes can - * be run parallel without accidentally sharing each others' sockets. - * - * See https://github.com/rtfeldman/node-test-runner/pull/231 - * Also incorporate a salt number into it on Windows, to avoid EADDRINUSE - - * see https://github.com/rtfeldman/node-test-runner/issues/275 - because the - * alternative approach of deleting the file before creating a new one doesn't - * work on Windows. We have to let Windows clean up the named pipe. This is - * essentially a band-aid fix. The alternative is to rewrite a ton of stuff. - * - * @param { number } runsExecuted - * @returns { string } - */ -function getPipeFilename(runsExecuted) { - return process.platform === 'win32' - ? `\\\\.\\pipe\\elm_test-${process.pid}-${runsExecuted}` - : `/tmp/elm_test-${process.pid}.sock`; -} - /** * This lets you log something like `elm.json changed > Compiling > Starting tests` * where each segment appears over time. @@ -166,8 +146,6 @@ function runTests( let watcher = undefined; /** @type { Array } */ let watchedPaths = []; - /** @type { number } */ - let runsExecuted = 0; /** @type { Promise | void } */ let currentRun = undefined; /** @type { Queue } */ @@ -236,8 +214,6 @@ function runTests( watcher.unwatch(diff.removed); } - runsExecuted++; - const pipeFilename = getPipeFilename(runsExecuted); const testModules = FindTests.findTests(testFilePaths, project); const mainModule = Generate.getMainModule(project.generatedCodeDir); const dest = path.join(project.generatedCodeDir, 'elmTestOutput.js'); @@ -265,19 +241,12 @@ function runTests( report ); - Generate.prepareCompiledJsFile(pipeFilename, dest); + Generate.prepareCompiledJsFile(dest); progressLogger.log('Starting tests'); progressLogger.newLine(); - return await Supervisor.run( - packageInfo.version, - pipeFilename, - report, - processes, - dest, - watch - ); + return await Supervisor.run(packageInfo.version, report, processes, dest); } catch (err) { progressLogger.newLine(); console.error(err.message); diff --git a/lib/Supervisor.js b/lib/Supervisor.js index 3bd85fda..8f2230dd 100644 --- a/lib/Supervisor.js +++ b/lib/Supervisor.js @@ -1,34 +1,25 @@ -const chalk = require('./chalk'); -const child_process = require('child_process'); -const fs = require('fs'); -const net = require('net'); -const split = require('split'); +const { Worker } = require('worker_threads'); const Report = require('./Report'); const XMLBuilder = require('./XMLBuilder'); /** * @param { string } elmTestVersion - * @param { string } pipeFilename * @param { import('./Report').Report } report * @param { number } processes * @param { string } dest - * @param { boolean } watch * @returns { Promise } */ -function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { +function run(elmTestVersion, report, processes, dest) { return new Promise(function (resolve) { /** @type { number | null } */ var nextResultToPrint = null; var finishedWorkers = 0; - var closedWorkers = 0; var results = new Map(); var failures = 0; /** @type { Array<{ labels: Array, todo: string }> } */ var todos = []; var testsToRun = -1; var startingTime = Date.now(); - /** @type { Array } */ - var workers = []; /** * @param { any } result This `any` became explicit instead of implicit when migrating from Flow to TypeScript. @@ -95,13 +86,6 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { } } } - function reportRuntimeException() { - console.error( - chalk.red( - '\n\nThere was an unexpected runtime exception while running tests\n\n' - ) - ); - } /** * @param { any } response This `any` became explicit instead of implicit when migrating from Flow to TypeScript. @@ -155,27 +139,11 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { flushResults(); } - /** - * @param { import('net').Socket } socket - * @returns { void } - */ - function initWorker(socket) { - socket.setEncoding('utf8'); - socket.setNoDelay(true); - - // See the long note near client.write() in worker.js for why we do this. - // It fixes a nasty bug! - var stream = socket.pipe(split()); - - stream.on('data', function (data) { - // In watch mode, the socket is drained which results in an extraneous - // message being sent. If we receive no data, ignore it. - if (!data) { - return; - } - - var response = JSON.parse(data); + /** @type { Array } */ + var workers = Array.from({ length: processes }, (_, index) => { + var worker = new Worker(dest, { workerData: index }); + worker.on('message', function (response) { switch (response.type) { case 'FINISHED': handleResults(response); @@ -183,16 +151,14 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { // This worker found no tests remaining to run; it's finished! finishedWorkers++; - // If all the workers have finished, print the summmary. + // If all the workers have finished, print the summary. if (finishedWorkers === workers.length) { - socket.write( - JSON.stringify({ - type: 'SUMMARY', - duration: Date.now() - startingTime, - failures: failures, - todos: todos, - }) - ); + worker.postMessage({ + type: 'SUMMARY', + duration: Date.now() - startingTime, + failures: failures, + todos: todos, + }); } break; case 'SUMMARY': @@ -219,7 +185,7 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { // Close all the workers. workers.forEach(function (worker) { - worker.kill(); + worker.terminate(); }); resolve(response.exitCode); break; @@ -253,58 +219,9 @@ function run(elmTestVersion, pipeFilename, report, processes, dest, watch) { ); } }); - } - var pendingException = false, - server = net.createServer(initWorker); - - server.on('error', function (err) { - console.error(err.stack); - server.close(); + return worker; }); - - server.on('listening', function () { - workers = Array.from({ length: processes }, (_, index) => { - var worker = child_process.fork(dest, [index.toString()]); - - worker.on('close', function (code) { - // code can be null. - var hasNonZeroExitCode = typeof code === 'number' && code !== 0; - - if (watch && !Report.isMachineReadable(report)) { - if (hasNonZeroExitCode) { - // Queue up complaining about an exception. - // Don't print it immediately, or else it might print N times - // where N is the number of cores. - pendingException = true; - } - closedWorkers++; - // If all the workers have closed, we're done! Continue watching. - if (closedWorkers === workers.length) { - if (pendingException) { - // If we had an exception pending, print it and clear pending flag. - reportRuntimeException(); - pendingException = false; - } - resolve(1); - } - } else if (hasNonZeroExitCode) { - reportRuntimeException(); - resolve(1); - } - }); - - return worker; - }); - }); - - if (fs.existsSync(pipeFilename) && process.platform !== 'win32') { - // Never remove named pipes on Windows. The OS will clean them up when - // nothing has a handle to them anymore. - fs.unlinkSync(pipeFilename); - } - - server.listen(pipeFilename); }); } diff --git a/messages.txt b/messages.txt new file mode 100644 index 00000000..a6d86f37 --- /dev/null +++ b/messages.txt @@ -0,0 +1,15 @@ +{"type":"FINISHED","results":{"5":{"type":"complete","status":"pass"},"17":{"type":"complete","status":"pass"},"29":{"type":"complete","status":"pass"},"41":{"type":"complete","status":"pass"},"53":{"type":"complete","status":"pass"},"65":{"type":"complete","status":"pass"},"77":{"type":"complete","status":"pass"},"89":{"type":"complete","status":"pass"},"101":{"type":"complete","status":"pass"},"113":{"type":"complete","status":"pass"},"125":{"type":"complete","status":"pass"},"137":{"type":"complete","status":"pass"},"149":{"type":"complete","status":"pass"},"161":{"type":"complete","status":"pass"},"173":{"type":"complete","status":"pass"},"185":{"type":"complete","status":"pass"},"197":{"type":"complete","status":"pass"},"209":{"type":"complete","status":"pass"},"221":{"type":"complete","status":"pass"},"233":{"type":"complete","status":"pass"},"245":{"type":"complete","status":"pass"},"257":{"type":"complete","status":"pass"},"269":{"type":"complete","status":"pass"},"281":{"type":"complete","status":"pass"},"293":{"type":"complete","status":"pass"},"305":{"type":"complete","status":"pass"},"317":{"type":"complete","status":"pass"},"329":{"type":"complete","status":"pass"},"341":{"type":"complete","status":"pass"},"353":{"type":"complete","status":"pass"},"365":{"type":"complete","status":"pass"},"377":{"type":"complete","status":"pass"},"389":{"type":"complete","status":"pass"},"401":{"type":"complete","status":"pass"},"413":{"type":"complete","status":"pass"},"425":{"type":"complete","status":"pass"},"437":{"type":"complete","status":"pass"},"449":{"type":"complete","status":"pass"},"461":{"type":"complete","status":"pass"},"473":{"type":"complete","status":"pass"},"485":{"type":"complete","status":"pass"},"497":{"type":"complete","status":"pass"},"509":{"type":"complete","status":"pass"},"521":{"type":"complete","status":"pass"},"533":{"type":"complete","status":"pass"},"545":{"type":"complete","status":"pass"},"557":{"type":"complete","status":"pass"},"569":{"type":"complete","status":"pass"},"581":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"9":{"type":"complete","status":"pass"},"21":{"type":"complete","status":"pass"},"33":{"type":"complete","status":"pass"},"45":{"type":"complete","status":"pass"},"57":{"type":"complete","status":"pass"},"69":{"type":"complete","status":"pass"},"81":{"type":"complete","status":"pass"},"93":{"type":"complete","status":"pass"},"105":{"type":"complete","status":"pass"},"117":{"type":"complete","status":"pass"},"129":{"type":"complete","status":"pass"},"141":{"type":"complete","status":"pass"},"153":{"type":"complete","status":"pass"},"165":{"type":"complete","status":"pass"},"177":{"type":"complete","status":"pass"},"189":{"type":"complete","status":"pass"},"201":{"type":"complete","status":"pass"},"213":{"type":"complete","status":"pass"},"225":{"type":"complete","status":"pass"},"237":{"type":"complete","status":"pass"},"249":{"type":"complete","status":"pass"},"261":{"type":"complete","status":"pass"},"273":{"type":"complete","status":"pass"},"285":{"type":"complete","status":"pass"},"297":{"type":"complete","status":"pass"},"309":{"type":"complete","status":"pass"},"321":{"type":"complete","status":"pass"},"333":{"type":"complete","status":"pass"},"345":{"type":"complete","status":"pass"},"357":{"type":"complete","status":"pass"},"369":{"type":"complete","status":"pass"},"381":{"type":"complete","status":"pass"},"393":{"type":"complete","status":"pass"},"405":{"type":"complete","status":"pass"},"417":{"type":"complete","status":"pass"},"429":{"type":"complete","status":"pass"},"441":{"type":"complete","status":"pass"},"453":{"type":"complete","status":"pass"},"465":{"type":"complete","status":"pass"},"477":{"type":"complete","status":"pass"},"489":{"type":"complete","status":"pass"},"501":{"type":"complete","status":"pass"},"513":{"type":"complete","status":"pass"},"525":{"type":"complete","status":"pass"},"537":{"type":"complete","status":"pass"},"549":{"type":"complete","status":"pass"},"561":{"type":"complete","status":"pass"},"573":{"type":"complete","status":"pass"},"585":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"11":{"type":"complete","status":"pass"},"23":{"type":"complete","status":"pass"},"35":{"type":"complete","status":"pass"},"47":{"type":"complete","status":"pass"},"59":{"type":"complete","status":"pass"},"71":{"type":"complete","status":"pass"},"83":{"type":"complete","status":"pass"},"95":{"type":"complete","status":"pass"},"107":{"type":"complete","status":"pass"},"119":{"type":"complete","status":"pass"},"131":{"type":"complete","status":"pass"},"143":{"type":"complete","status":"pass"},"155":{"type":"complete","status":"pass"},"167":{"type":"complete","status":"pass"},"179":{"type":"complete","status":"pass"},"191":{"type":"complete","status":"pass"},"203":{"type":"complete","status":"pass"},"215":{"type":"complete","status":"pass"},"227":{"type":"complete","status":"pass"},"239":{"type":"complete","status":"pass"},"251":{"type":"complete","status":"pass"},"263":{"type":"complete","status":"pass"},"275":{"type":"complete","status":"pass"},"287":{"type":"complete","status":"pass"},"299":{"type":"complete","status":"pass"},"311":{"type":"complete","status":"pass"},"323":{"type":"complete","status":"pass"},"335":{"type":"complete","status":"pass"},"347":{"type":"complete","status":"pass"},"359":{"type":"complete","status":"pass"},"371":{"type":"complete","status":"pass"},"383":{"type":"complete","status":"pass"},"395":{"type":"complete","status":"pass"},"407":{"type":"complete","status":"pass"},"419":{"type":"complete","status":"pass"},"431":{"type":"complete","status":"pass"},"443":{"type":"complete","status":"pass"},"455":{"type":"complete","status":"pass"},"467":{"type":"complete","status":"pass"},"479":{"type":"complete","status":"pass"},"491":{"type":"complete","status":"pass"},"503":{"type":"complete","status":"pass"},"515":{"type":"complete","status":"pass"},"527":{"type":"complete","status":"pass"},"539":{"type":"complete","status":"pass"},"551":{"type":"complete","status":"pass"},"563":{"type":"complete","status":"pass"},"575":{"type":"complete","status":"pass"},"587":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"1":{"type":"complete","status":"pass"},"13":{"type":"complete","status":"pass"},"25":{"type":"complete","status":"pass"},"37":{"type":"complete","status":"pass"},"49":{"type":"complete","status":"pass"},"61":{"type":"complete","status":"pass"},"73":{"type":"complete","status":"pass"},"85":{"type":"complete","status":"pass"},"97":{"type":"complete","status":"pass"},"109":{"type":"complete","status":"pass"},"121":{"type":"complete","status":"pass"},"133":{"type":"complete","status":"pass"},"145":{"type":"complete","status":"pass"},"157":{"type":"complete","status":"pass"},"169":{"type":"complete","status":"pass"},"181":{"type":"complete","status":"pass"},"193":{"type":"complete","status":"pass"},"205":{"type":"complete","status":"pass"},"217":{"type":"complete","status":"pass"},"229":{"type":"complete","status":"pass"},"241":{"type":"complete","status":"pass"},"253":{"type":"complete","status":"pass"},"265":{"type":"complete","status":"pass"},"277":{"type":"complete","status":"pass"},"289":{"type":"complete","status":"pass"},"301":{"type":"complete","status":"pass"},"313":{"type":"complete","status":"pass"},"325":{"type":"complete","status":"pass"},"337":{"type":"complete","status":"pass"},"349":{"type":"complete","status":"pass"},"361":{"type":"complete","status":"pass"},"373":{"type":"complete","status":"pass"},"385":{"type":"complete","status":"pass"},"397":{"type":"complete","status":"pass"},"409":{"type":"complete","status":"pass"},"421":{"type":"complete","status":"pass"},"433":{"type":"complete","status":"pass"},"445":{"type":"complete","status":"pass"},"457":{"type":"complete","status":"pass"},"469":{"type":"complete","status":"pass"},"481":{"type":"complete","status":"pass"},"493":{"type":"complete","status":"pass"},"505":{"type":"complete","status":"pass"},"517":{"type":"complete","status":"pass"},"529":{"type":"complete","status":"pass"},"541":{"type":"complete","status":"pass"},"553":{"type":"complete","status":"pass"},"565":{"type":"complete","status":"pass"},"577":{"type":"complete","status":"pass"},"589":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"10":{"type":"complete","status":"pass"},"22":{"type":"complete","status":"pass"},"34":{"type":"complete","status":"pass"},"46":{"type":"complete","status":"pass"},"58":{"type":"complete","status":"pass"},"70":{"type":"complete","status":"pass"},"82":{"type":"complete","status":"pass"},"94":{"type":"complete","status":"pass"},"106":{"type":"complete","status":"pass"},"118":{"type":"complete","status":"pass"},"130":{"type":"complete","status":"pass"},"142":{"type":"complete","status":"pass"},"154":{"type":"complete","status":"pass"},"166":{"type":"complete","status":"pass"},"178":{"type":"complete","status":"pass"},"190":{"type":"complete","status":"pass"},"202":{"type":"complete","status":"pass"},"214":{"type":"complete","status":"pass"},"226":{"type":"complete","status":"pass"},"238":{"type":"complete","status":"pass"},"250":{"type":"complete","status":"pass"},"262":{"type":"complete","status":"pass"},"274":{"type":"complete","status":"pass"},"286":{"type":"complete","status":"pass"},"298":{"type":"complete","status":"pass"},"310":{"type":"complete","status":"pass"},"322":{"type":"complete","status":"pass"},"334":{"type":"complete","status":"pass"},"346":{"type":"complete","status":"pass"},"358":{"type":"complete","status":"pass"},"370":{"type":"complete","status":"pass"},"382":{"type":"complete","status":"pass"},"394":{"type":"complete","status":"pass"},"406":{"type":"complete","status":"pass"},"418":{"type":"complete","status":"pass"},"430":{"type":"complete","status":"pass"},"442":{"type":"complete","status":"pass"},"454":{"type":"complete","status":"pass"},"466":{"type":"complete","status":"pass"},"478":{"type":"complete","status":"pass"},"490":{"type":"complete","status":"pass"},"502":{"type":"complete","status":"pass"},"514":{"type":"complete","status":"pass"},"526":{"type":"complete","status":"pass"},"538":{"type":"complete","status":"pass"},"550":{"type":"complete","status":"pass"},"562":{"type":"complete","status":"pass"},"574":{"type":"complete","status":"pass"},"586":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"2":{"type":"complete","status":"pass"},"14":{"type":"complete","status":"pass"},"26":{"type":"complete","status":"pass"},"38":{"type":"complete","status":"pass"},"50":{"type":"complete","status":"pass"},"62":{"type":"complete","status":"pass"},"74":{"type":"complete","status":"pass"},"86":{"type":"complete","status":"pass"},"98":{"type":"complete","status":"pass"},"110":{"type":"complete","status":"pass"},"122":{"type":"complete","status":"pass"},"134":{"type":"complete","status":"pass"},"146":{"type":"complete","status":"pass"},"158":{"type":"complete","status":"pass"},"170":{"type":"complete","status":"pass"},"182":{"type":"complete","status":"pass"},"194":{"type":"complete","status":"pass"},"206":{"type":"complete","status":"pass"},"218":{"type":"complete","status":"pass"},"230":{"type":"complete","status":"pass"},"242":{"type":"complete","status":"pass"},"254":{"type":"complete","status":"pass"},"266":{"type":"complete","status":"pass"},"278":{"type":"complete","status":"pass"},"290":{"type":"complete","status":"pass"},"302":{"type":"complete","status":"pass"},"314":{"type":"complete","status":"pass"},"326":{"type":"complete","status":"pass"},"338":{"type":"complete","status":"pass"},"350":{"type":"complete","status":"pass"},"362":{"type":"complete","status":"pass"},"374":{"type":"complete","status":"pass"},"386":{"type":"complete","status":"pass"},"398":{"type":"complete","status":"pass"},"410":{"type":"complete","status":"pass"},"422":{"type":"complete","status":"pass"},"434":{"type":"complete","status":"pass"},"446":{"type":"complete","status":"pass"},"458":{"type":"complete","status":"pass"},"470":{"type":"complete","status":"pass"},"482":{"type":"complete","status":"pass"},"494":{"type":"complete","status":"pass"},"506":{"type":"complete","status":"pass"},"518":{"type":"complete","status":"pass"},"530":{"type":"complete","status":"pass"},"542":{"type":"complete","status":"pass"},"554":{"type":"complete","status":"pass"},"566":{"type":"complete","status":"pass"},"578":{"type":"complete","status":"pass"},"590":{"type":"complete","status":"pass"}}} +{"type":"BEGIN","testCount":592,"message":{"type":"begin","output":"Running 592 tests. To reproduce these results, run: elm-test --fuzz 100 --seed 0\n"}} +{"type":"FINISHED","results":{"0":{"type":"complete","status":"pass"},"12":{"type":"complete","status":"pass"},"24":{"type":"complete","status":"pass"},"36":{"type":"complete","status":"pass"},"48":{"type":"complete","status":"pass"},"60":{"type":"complete","status":"pass"},"72":{"type":"complete","status":"pass"},"84":{"type":"complete","status":"pass"},"96":{"type":"complete","status":"pass"},"108":{"type":"complete","status":"pass"},"120":{"type":"complete","status":"pass"},"132":{"type":"complete","status":"pass"},"144":{"type":"complete","status":"pass"},"156":{"type":"complete","status":"pass"},"168":{"type":"complete","status":"pass"},"180":{"type":"complete","status":"pass"},"192":{"type":"complete","status":"pass"},"204":{"type":"complete","status":"pass"},"216":{"type":"complete","status":"pass"},"228":{"type":"complete","status":"pass"},"240":{"type":"complete","status":"pass"},"252":{"type":"complete","status":"pass"},"264":{"type":"complete","status":"pass"},"276":{"type":"complete","status":"pass"},"288":{"type":"complete","status":"pass"},"300":{"type":"complete","status":"pass"},"312":{"type":"complete","status":"pass"},"324":{"type":"complete","status":"pass"},"336":{"type":"complete","status":"pass"},"348":{"type":"complete","status":"pass"},"360":{"type":"complete","status":"pass"},"372":{"type":"complete","status":"pass"},"384":{"type":"complete","status":"pass"},"396":{"type":"complete","status":"pass"},"408":{"type":"complete","status":"pass"},"420":{"type":"complete","status":"pass"},"432":{"type":"complete","status":"pass"},"444":{"type":"complete","status":"pass"},"456":{"type":"complete","status":"pass"},"468":{"type":"complete","status":"pass"},"480":{"type":"complete","status":"pass"},"492":{"type":"complete","status":"pass"},"504":{"type":"complete","status":"pass"},"516":{"type":"complete","status":"pass"},"528":{"type":"complete","status":"pass"},"540":{"type":"complete","status":"pass"},"552":{"type":"complete","status":"pass"},"564":{"type":"complete","status":"pass"},"576":{"type":"complete","status":"pass"},"588":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"8":{"type":"complete","status":"pass"},"20":{"type":"complete","status":"pass"},"32":{"type":"complete","status":"pass"},"44":{"type":"complete","status":"pass"},"56":{"type":"complete","status":"pass"},"68":{"type":"complete","status":"pass"},"80":{"type":"complete","status":"pass"},"92":{"type":"complete","status":"pass"},"104":{"type":"complete","status":"pass"},"116":{"type":"complete","status":"pass"},"128":{"type":"complete","status":"pass"},"140":{"type":"complete","status":"pass"},"152":{"type":"complete","status":"pass"},"164":{"type":"complete","status":"pass"},"176":{"type":"complete","status":"pass"},"188":{"type":"complete","status":"pass"},"200":{"type":"complete","status":"pass"},"212":{"type":"complete","status":"pass"},"224":{"type":"complete","status":"pass"},"236":{"type":"complete","status":"pass"},"248":{"type":"complete","status":"pass"},"260":{"type":"complete","status":"pass"},"272":{"type":"complete","status":"pass"},"284":{"type":"complete","status":"pass"},"296":{"type":"complete","status":"pass"},"308":{"type":"complete","status":"pass"},"320":{"type":"complete","status":"pass"},"332":{"type":"complete","status":"pass"},"344":{"type":"complete","status":"pass"},"356":{"type":"complete","status":"pass"},"368":{"type":"complete","status":"pass"},"380":{"type":"complete","status":"pass"},"392":{"type":"complete","status":"pass"},"404":{"type":"complete","status":"pass"},"416":{"type":"complete","status":"pass"},"428":{"type":"complete","status":"pass"},"440":{"type":"complete","status":"pass"},"452":{"type":"complete","status":"pass"},"464":{"type":"complete","status":"pass"},"476":{"type":"complete","status":"pass"},"488":{"type":"complete","status":"pass"},"500":{"type":"complete","status":"pass"},"512":{"type":"complete","status":"pass"},"524":{"type":"complete","status":"pass"},"536":{"type":"complete","status":"pass"},"548":{"type":"complete","status":"pass"},"560":{"type":"complete","status":"pass"},"572":{"type":"complete","status":"pass"},"584":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"4":{"type":"complete","status":"pass"},"16":{"type":"complete","status":"pass"},"28":{"type":"complete","status":"pass"},"40":{"type":"complete","status":"pass"},"52":{"type":"complete","status":"pass"},"64":{"type":"complete","status":"pass"},"76":{"type":"complete","status":"pass"},"88":{"type":"complete","status":"pass"},"100":{"type":"complete","status":"pass"},"112":{"type":"complete","status":"pass"},"124":{"type":"complete","status":"pass"},"136":{"type":"complete","status":"pass"},"148":{"type":"complete","status":"pass"},"160":{"type":"complete","status":"pass"},"172":{"type":"complete","status":"pass"},"184":{"type":"complete","status":"pass"},"196":{"type":"complete","status":"pass"},"208":{"type":"complete","status":"pass"},"220":{"type":"complete","status":"pass"},"232":{"type":"complete","status":"pass"},"244":{"type":"complete","status":"pass"},"256":{"type":"complete","status":"pass"},"268":{"type":"complete","status":"pass"},"280":{"type":"complete","status":"pass"},"292":{"type":"complete","status":"pass"},"304":{"type":"complete","status":"pass"},"316":{"type":"complete","status":"pass"},"328":{"type":"complete","status":"pass"},"340":{"type":"complete","status":"pass"},"352":{"type":"complete","status":"pass"},"364":{"type":"complete","status":"pass"},"376":{"type":"complete","status":"pass"},"388":{"type":"complete","status":"pass"},"400":{"type":"complete","status":"pass"},"412":{"type":"complete","status":"pass"},"424":{"type":"complete","status":"pass"},"436":{"type":"complete","status":"pass"},"448":{"type":"complete","status":"pass"},"460":{"type":"complete","status":"pass"},"472":{"type":"complete","status":"pass"},"484":{"type":"complete","status":"pass"},"496":{"type":"complete","status":"pass"},"508":{"type":"complete","status":"pass"},"520":{"type":"complete","status":"pass"},"532":{"type":"complete","status":"pass"},"544":{"type":"complete","status":"pass"},"556":{"type":"complete","status":"pass"},"568":{"type":"complete","status":"pass"},"580":{"type":"complete","status":"pass"}}} +{"type":"RESULTS","results":{"6":{"type":"complete","status":"pass"},"18":{"type":"complete","status":"pass"},"30":{"type":"complete","status":"pass"},"42":{"type":"complete","status":"pass"},"54":{"type":"complete","status":"pass"},"66":{"type":"complete","status":"pass"},"78":{"type":"complete","status":"pass"},"90":{"type":"complete","status":"pass"},"102":{"type":"complete","status":"pass"},"114":{"type":"complete","status":"pass"},"126":{"type":"complete","status":"pass"},"138":{"type":"complete","status":"pass"},"150":{"type":"complete","status":"pass"},"162":{"type":"complete","status":"pass"},"174":{"type":"complete","status":"pass"},"186":{"type":"complete","status":"pass"},"198":{"type":"complete","status":"pass"},"210":{"type":"complete","status":"pass"},"222":{"type":"complete","status":"pass"},"234":{"type":"complete","status":"pass"},"246":{"type":"complete","status":"pass"},"258":{"type":"complete","status":"pass"},"270":{"type":"complete","status":"pass"},"282":{"type":"complete","status":"pass"},"294":{"type":"complete","status":"pass"},"306":{"type":"complete","status":"pass"},"318":{"type":"complete","status":"pass"},"330":{"type":"complete","status":"pass"},"342":{"type":"complete","status":"pass"},"354":{"type":"complete","status":"pass"},"366":{"type":"complete","status":"pass"},"378":{"type":"complete","status":"pass"},"390":{"type":"complete","status":"pass"},"402":{"type":"complete","status":"pass"},"414":{"type":"complete","status":"fail","failure":"\u001b[2m↓ FruitTest.InventoryTest\n\u001b[22m\u001b[2m↓ All Inventory\n\u001b[22m\u001b[31m✗ Always Apple or Banana\n\u001b[39m\u001b[2m\n Distribution report:\n ====================\n Apple: 93.7% (387x) ████████████████████████████░░\n Bananas: 5.6% (23x) ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n Plantains: 0.7% (3x) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n 0 Bananas: 0% (0x) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n Error: 0% (0x) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n Tangerine: 0% (0x) ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n\u001b[22m\u001b[2m\nGiven Bananas { availableFruitInStore = [(Id \"1337\",Orange { children = [{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" }], position = { x = 0, y = 80 }, fruitId = Id \"37\" }),(Id \"1337\",Orange { children = [{ seeds = Just (Coconut True), nodeId = Id \"1337\" },{ seeds = Just (Coconut False), nodeId = Id \"1337\" }], position = { x = 272, y = -128 }, fruitId = Id \"37\" }),(Id \"1337\",Orange { children = [{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") [Id \"13\"]) Watermelon), nodeId = Id \"1337\" }], position = { x = 528, y = 80 }, fruitId = Id \"37\" }),(Id \"1337\",Orange { children = [{ seeds = Just (Strawberry (Nonempty (Id \"13\") [Id \"13\",Id \"13\"]) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") [Id \"13\",Id \"13\",Id \"13\",Id \"13\",Id \"13\",Id \"13\",Id \"13\"]) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" }], position = { x = 1616, y = 400 }, fruitId = Id \"37\" }),(Id \"1337\",Orange { children = [{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" },{ seeds = Just (Strawberry (Nonempty (Id \"13\") []) Watermelon), nodeId = Id \"1337\" }], position = { x = 2640, y = 608 }, fruitId = Id \"37\" }),(Id \"1337\",Orange { children = [{ seeds = Just (Coconut True), nodeId = Id \"1337\" },{ seeds = Just (Coconut False), nodeId = Id \"1337\" }], position = { x = 3136, y = 608 }, fruitId = Id \"37\" })], Bananas = TaggedDict (Dict.fromList [(\"37\",Blueberry (Id \"13\")),(\"37\",Blueberry (Id \"13\")),(\"37\",Blueberry (Id \"13\")),(\"37\",Blueberry (Id \"13\")),(\"37\",Avocado True),(\"37\",Avocado False)]), triggers = [] }\n\u001b[22m\n ### Error ###########################################################################\n \n Fruit shop inventory:\n \n Bananas\n ​9873 or more\n \n Avocados:\n \n 98734983274\n \n \n ### Days ###########################################################################\n \n Friday, Thursday\n \n \n ### Seeds ########################################################\n \n (none)\n \n \n ### Nutrients ###########################################################\n \n Salt\n Moderate\n \n Carbs 88\n (at least)\n \n Energy\n High\n \n Fat\n too much\n \n Starch\n ​987870\n \n Wood\n Yes\n\n"}}} +{"type":"FINISHED","results":{"426":{"type":"complete","status":"pass"},"438":{"type":"complete","status":"pass"},"450":{"type":"complete","status":"pass"},"462":{"type":"complete","status":"pass"},"474":{"type":"complete","status":"pass"},"486":{"type":"complete","status":"pass"},"498":{"type":"complete","status":"pass"},"510":{"type":"complete","status":"pass"},"522":{"type":"complete","status":"pass"},"534":{"type":"complete","status":"pass"},"546":{"type":"complete","status":"pass"},"558":{"type":"complete","status":"pass"},"570":{"type":"complete","status":"pass"},"582":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"3":{"type":"complete","status":"pass"},"15":{"type":"complete","status":"pass"},"27":{"type":"complete","status":"pass"},"39":{"type":"complete","status":"pass"},"51":{"type":"complete","status":"pass"},"63":{"type":"complete","status":"pass"},"75":{"type":"complete","status":"pass"},"87":{"type":"complete","status":"pass"},"99":{"type":"complete","status":"pass"},"111":{"type":"complete","status":"pass"},"123":{"type":"complete","status":"pass"},"135":{"type":"complete","status":"pass"},"147":{"type":"complete","status":"pass"},"159":{"type":"complete","status":"pass"},"171":{"type":"complete","status":"pass"},"183":{"type":"complete","status":"pass"},"195":{"type":"complete","status":"pass"},"207":{"type":"complete","status":"pass"},"219":{"type":"complete","status":"pass"},"231":{"type":"complete","status":"pass"},"243":{"type":"complete","status":"pass"},"255":{"type":"complete","status":"pass"},"267":{"type":"complete","status":"pass"},"279":{"type":"complete","status":"pass"},"291":{"type":"complete","status":"pass"},"303":{"type":"complete","status":"pass"},"315":{"type":"complete","status":"pass"},"327":{"type":"complete","status":"pass"},"339":{"type":"complete","status":"pass"},"351":{"type":"complete","status":"pass"},"363":{"type":"complete","status":"pass"},"375":{"type":"complete","status":"pass"},"387":{"type":"complete","status":"pass"},"399":{"type":"complete","status":"pass"},"411":{"type":"complete","status":"pass"},"423":{"type":"complete","status":"pass"},"435":{"type":"complete","status":"pass"},"447":{"type":"complete","status":"pass"},"459":{"type":"complete","status":"pass"},"471":{"type":"complete","status":"pass"},"483":{"type":"complete","status":"pass"},"495":{"type":"complete","status":"pass"},"507":{"type":"complete","status":"pass"},"519":{"type":"complete","status":"pass"},"531":{"type":"complete","status":"pass"},"543":{"type":"complete","status":"pass"},"555":{"type":"complete","status":"pass"},"567":{"type":"complete","status":"pass"},"579":{"type":"complete","status":"pass"},"591":{"type":"complete","status":"pass"}}} +{"type":"FINISHED","results":{"7":{"type":"complete","status":"pass"},"19":{"type":"complete","status":"pass"},"31":{"type":"complete","status":"pass"},"43":{"type":"complete","status":"pass"},"55":{"type":"complete","status":"pass"},"67":{"type":"complete","status":"pass"},"79":{"type":"complete","status":"pass"},"91":{"type":"complete","status":"pass"},"103":{"type":"complete","status":"pass"},"115":{"type":"complete","status":"pass"},"127":{"type":"complete","status":"pass"},"139":{"type":"complete","status":"pass"},"151":{"type":"complete","status":"pass"},"163":{"type":"complete","status":"pass"},"175":{"type":"complete","status":"pass"},"187":{"type":"complete","status":"pass"},"199":{"type":"complete","status":"pass"},"211":{"type":"complete","status":"pass"},"223":{"type":"complete","status":"pass"},"235":{"type":"complete","status":"pass"},"247":{"type":"complete","status":"pass"},"259":{"type":"complete","status":"pass"},"271":{"type":"complete","status":"pass"},"283":{"type":"complete","status":"pass"},"295":{"type":"complete","status":"pass"},"307":{"type":"complete","status":"pass"},"319":{"type":"complete","status":"pass"},"331":{"type":"complete","status":"pass"},"343":{"type":"complete","status":"pass"},"355":{"type":"complete","status":"pass"},"367":{"type":"complete","status":"pass"},"379":{"type":"complete","status":"pass"},"391":{"type":"complete","status":"pass"},"403":{"type":"complete","status":"pass"},"415":{"type":"complete","status":"pass"},"427":{"type":"complete","status":"pass"},"439":{"type":"complete","status":"pass"},"451":{"type":"complete","status":"pass"},"463":{"type":"complete","status":"pass"},"475":{"type":"complete","status":"pass"},"487":{"type":"complete","status":"pass"},"499":{"type":"complete","status":"pass"},"511":{"type":"complete","status":"pass"},"523":{"type":"complete","status":"pass"},"535":{"type":"complete","status":"pass"},"547":{"type":"complete","status":"pass"},"559":{"type":"complete","status":"pass"},"571":{"type":"complete","status":"pass"},"583":{"type":"complete","status":"pass"}}} +{"type":"SUMMARY","exitCode":2,"message":{"type":"summary","summary":"\u001b[4m\u001b[31m\nTEST RUN FAILED\u001b[39m\u001b[24m\u001b[31m\n\n\u001b[39m\u001b[2mDuration: \u001b[22m2274 ms\n\u001b[2mPassed: \u001b[22m591\n\u001b[2mFailed: \u001b[22m1\n"}} diff --git a/package-lock.json b/package-lock.json index 785e200d..6ddb8ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "chokidar": "^3.5.3", "commander": "^9.4.1", "elm-solve-deps-wasm": "^1.0.2 || ^2.0.0", - "split": "^1.0.1", "tinyglobby": "^0.2.10", "which": "^2.0.2" }, @@ -23,7 +22,6 @@ "@eslint/js": "9.20.0", "@types/mocha": "10.0.10", "@types/node": "26.0.1", - "@types/split": "1.0.5", "@types/which": "3.0.4", "elm-review": "2.13.1", "elm-tooling": "1.18.0", @@ -447,27 +445,6 @@ "@types/node": "*" } }, - "node_modules/@types/split": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.5.tgz", - "integrity": "sha512-gMiDr4vA6YofTpAkPQtP+5pvStIf3CMYphf32YAG/3RwogNL8ii1CQKDc+sxN62KuxPoRaJXcf2zDCDkEBH4FA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/through": "*" - } - }, - "node_modules/@types/through": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", - "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/which": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", @@ -2845,17 +2822,6 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2999,11 +2965,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, "node_modules/tinyglobby": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", @@ -3540,25 +3501,6 @@ "@types/node": "*" } }, - "@types/split": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/split/-/split-1.0.5.tgz", - "integrity": "sha512-gMiDr4vA6YofTpAkPQtP+5pvStIf3CMYphf32YAG/3RwogNL8ii1CQKDc+sxN62KuxPoRaJXcf2zDCDkEBH4FA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/through": "*" - } - }, - "@types/through": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", - "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/which": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", @@ -5110,14 +5052,6 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5224,11 +5158,6 @@ "supports-hyperlinks": "^2.0.0" } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, "tinyglobby": { "version": "0.2.10", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", diff --git a/package.json b/package.json index f759c00c..1ce5dc03 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "chokidar": "^3.5.3", "commander": "^9.4.1", "elm-solve-deps-wasm": "^1.0.2 || ^2.0.0", - "split": "^1.0.1", "tinyglobby": "^0.2.10", "which": "^2.0.2" }, @@ -53,7 +52,6 @@ "@eslint/js": "9.20.0", "@types/mocha": "10.0.10", "@types/node": "26.0.1", - "@types/split": "1.0.5", "@types/which": "3.0.4", "elm-review": "2.13.1", "elm-tooling": "1.18.0", diff --git a/templates/after.js b/templates/after.js index 3ea350fc..c6a4cda5 100644 --- a/templates/after.js +++ b/templates/after.js @@ -1,29 +1,13 @@ -var net = require('net'), - client = net.createConnection(pipeFilename); - -client.on('error', function (error) { - console.error(error); - client.end(); - process.exit(1); -}); - -client.setEncoding('utf8'); -client.setNoDelay(true); +var { parentPort, workerData } = require('worker_threads'); // Run the Elm app. -var app = Elm.Test.Generated.Main.init({ flags: Number(process.argv[2]) }); +var app = Elm.Test.Generated.Main.init({ flags: workerData }); -client.on('data', function (msg) { - app.ports.elmTestPort__receive.send(JSON.parse(msg)); +parentPort.on('message', function (msg) { + app.ports.elmTestPort__receive.send(msg); }); // Use ports for inter-process communication. app.ports.elmTestPort__send.subscribe(function (msg) { - // We split incoming messages on the socket on newlines. The gist is that node - // is rather unpredictable in whether or not a single `write` will result in a - // single `on('data')` callback. Sometimes it does, sometimes multiple writes - // result in a single callback and - worst of all - sometimes a single read - // results in multiple callbacks, each receiving a piece of the data. The - // horror. - client.write(msg + '\n'); + parentPort.postMessage(msg); });