Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
400 changes: 400 additions & 0 deletions benchmark.js

Large diffs are not rendered by default.

7 changes: 1 addition & 6 deletions elm/src/Test/Runner/Node.elm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -140,7 +140,6 @@ update msg ({ testReporter } as model) =
, ( "exitCode", Encode.int exitCode )
, ( "message", summary )
]
|> Encode.encode 0
|> elmTestPort__send
in
( model, cmd )
Expand All @@ -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 )
Expand Down Expand Up @@ -224,7 +222,6 @@ sendResults isFinished testReporter results =
|> Encode.object
)
]
|> Encode.encode 0
|> elmTestPort__send


Expand All @@ -245,7 +242,6 @@ sendBegin model =
[]
in
Encode.object (baseFields ++ extraFields)
|> Encode.encode 0
|> elmTestPort__send


Expand Down Expand Up @@ -336,7 +332,6 @@ failInit message report _ =
, ( "exitCode", Encode.int 1 )
, ( "message", Encode.string message )
]
|> Encode.encode 0
|> elmTestPort__send
in
( model, cmd )
Expand Down
4 changes: 1 addition & 3 deletions lib/Generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,17 @@ 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}
var Elm = (function(module) {
${addKernelTestChecking(content)}
return this.Elm;
})({});
var pipeFilename = ${JSON.stringify(pipeFilename)};
${after}
`.trim();
fs.writeFileSync(dest, finalContent);
Expand Down
35 changes: 2 additions & 33 deletions lib/RunTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -166,8 +146,6 @@ function runTests(
let watcher = undefined;
/** @type { Array<string> } */
let watchedPaths = [];
/** @type { number } */
let runsExecuted = 0;
/** @type { Promise<void> | void } */
let currentRun = undefined;
/** @type { Queue } */
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down
113 changes: 15 additions & 98 deletions lib/Supervisor.js
Original file line number Diff line number Diff line change
@@ -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<number> }
*/
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<string>, todo: string }> } */
var todos = [];
var testsToRun = -1;
var startingTime = Date.now();
/** @type { Array<import('child_process').ChildProcess> } */
var workers = [];

/**
* @param { any } result This `any` became explicit instead of implicit when migrating from Flow to TypeScript.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -155,44 +139,26 @@ 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<import('worker_threads').Worker> } */
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);

// 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':
Expand All @@ -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;
Expand Down Expand Up @@ -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);
});
}

Expand Down
Loading