Skip to content

Commit

Permalink
feature: supertape: add support of worker_threads
Browse files Browse the repository at this point in the history
coderaiser committed Jan 26, 2024
1 parent c19242f commit 64c7acb
Showing 21 changed files with 367 additions and 70 deletions.
3 changes: 2 additions & 1 deletion .nycrc.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@
"all": true,
"exclude": [
"**/lib/**/{fixture,*.spec.{js,mjs}}",
".*"
".*",
"**/packages/supertape/bin/*.mjs"
],
"branches": 100,
"lines": 100,
1 change: 0 additions & 1 deletion packages/formatter-progress-bar/lib/progress-bar.js
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@ const {red} = chalk;
const formatErrorsCount = (a) => a ? red(a) : OK;

const isStr = (a) => typeof a === 'string';

const {stderr} = process;

let SUPERTAPE_PROGRESS_BAR;
2 changes: 1 addition & 1 deletion packages/supertape/.madrun.mjs
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ const env = {
};

export default {
'test': () => [env, `bin/supertape.mjs '{bin,lib}/**/*.spec.{js,mjs}'`],
'test': () => [env, `bin/tracer.mjs '{bin,lib}/**/*.spec.{js,mjs}'`],
'test:dts': () => 'check-dts test/*.ts',
'watch:test': async () => `nodemon -w lib -w test -x "${await cutEnv('test')}"`,
'lint': () => 'putout .',
6 changes: 5 additions & 1 deletion packages/supertape/.nycrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"check-coverage": true,
"all": true,
"exclude": [".*", "{bin,lib}/**/{fixture,*.spec.{js,mjs}}"],
"exclude": [
".*",
"{bin,lib}/**/{fixture,*.spec.{js,mjs}}",
"bin/**/*.mjs"
],
"branches": 100,
"lines": 100,
"functions": 100,
45 changes: 45 additions & 0 deletions packages/supertape/bin/communication.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
parentPort,
workerData,
} from 'node:worker_threads';
import {EventEmitter} from 'node:events';

const {assign} = Object;

export const createCommunication = (argv) => {
if (parentPort)
return {
parentPort,
workerData,
};

const {newWorker, newParentPort} = fakeWorkers();

return {
worker: newWorker,
parentPort: newParentPort,
workerData: argv,
};
};

export function fakeWorkers() {
const newWorker = new EventEmitter();
const newParentPort = new EventEmitter();

assign(newWorker, {
postMessage: (a) => {
newParentPort.emit('message', a);
},
});

assign(newParentPort, {
postMessage: (a) => {
newWorker.emit('message', a);
},
});

return {
newParentPort,
newWorker,
};
}
63 changes: 63 additions & 0 deletions packages/supertape/bin/formatter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {EventEmitter} from 'node:events';

export const createFormatter = (parentPort) => {
const formatter = new EventEmitter();

formatter.on('start', ({total}) => {
parentPort.postMessage(['start', {
total,
}]);
});

formatter.on('test', ({test}) => {
parentPort.postMessage(['test', {
test,
}]);
});

formatter.on('test:end', ({count, total, failed, test}) => {
parentPort.postMessage(['test:end', {
total,
count,
failed,
test,
}]);
});

formatter.on('comment', (message) => {
parentPort.postMessage(['test:end', {
message,
}]);
});

formatter.on('test:success', ({count, message}) => {
parentPort.postMessage(['test:success', {
count,
message,
}]);
});

formatter.on('test:fail', ({at, count, message, operator, result, expected, output, errorStack}) => {
parentPort.postMessage(['fail', {
at,
count,
message,
operator,
result: JSON.stringify(result),
expected: JSON.stringify(expected),
output,
errorStack,
}]);
});

formatter.on('end', ({count, passed, failed, skiped}) => {
parentPort.postMessage(['end', {
count,
passed,
failed,
skiped,
}]);
});

return formatter;
};
28 changes: 28 additions & 0 deletions packages/supertape/bin/subscribe.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import keyPress from '@putout/cli-keypress';
import harnessCreator from '../lib/formatter/harness.js';

const {createHarness} = harnessCreator;

const resolveFormatter = async (name) => await import(`@supertape/formatter-${name}`);

export async function subscribe({name, quiet, exit, worker, stdout}) {
const {isStop} = keyPress();
const harness = createHarness(await resolveFormatter(name));

if (!quiet)
harness.pipe(stdout);

worker.on('exit', (code) => {
exit(code);
});

worker.on('message', ([type, a]) => {
harness.write({
type,
...a,
});

if (isStop())
worker.postMessage(['stop']);
});
}
30 changes: 26 additions & 4 deletions packages/supertape/bin/supertape.mjs
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
#!/usr/bin/env node

import process from 'node:process';
import cli from '../lib/cli.js';
import {createCommunication} from './communication.mjs';
import {subscribe} from './subscribe.mjs';
import {createFormatter} from './formatter.mjs';
import {parseArgs} from '../lib/cli/parse-args.js';

const {
worker,
parentPort,
workerData,
} = createCommunication(process.argv);

const args = parseArgs(process.argv.slice(2));

const {
stdout,
stderr,
exit,
} = process;

export default cli({
const workerFormatter = createFormatter(parentPort);

if (worker)
subscribe({
name: args.format,
quiet: args.quiet,
exit,
worker,
stdout,
});

export default await cli({
stdout,
stderr,
exit,
cwd: process.cwd(),
argv: process.argv.slice(2),
argv: workerData.slice(2),
workerFormatter,
});
2 changes: 1 addition & 1 deletion packages/supertape/bin/supertape.spec.mjs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import test from '../lib/supertape.js';

const require = createRequire(import.meta.url);

const name = new URL('supertape.mjs', import.meta.url).pathname;
const name = new URL('tracer.mjs', import.meta.url).pathname;
const run = runsome(name);

test('supertape: bin: -v', (t) => {
3 changes: 3 additions & 0 deletions packages/supertape/bin/trace.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const createTrace = (parentPort) => (event, data) => {
parentPort?.postMessage([event, data]);
};
34 changes: 34 additions & 0 deletions packages/supertape/bin/trace.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
test,
stub,
} from 'supertape';
import tryCatch from 'try-catch';
import {createTrace} from './trace.mjs';

test('supertape: bin: trace: parentPort', (t) => {
const data = {};
const postMessage = stub();
const parentPort = {
postMessage,
};

const trace = createTrace(parentPort);

trace('start', data);

const args = [
['start', data],
];

t.calledWith(postMessage, args);
t.end();
});

test('supertape: bin: trace: no parentPort', (t) => {
const data = {};
const trace = createTrace();
const [error] = tryCatch(trace, 'start', data);

t.notOk(error);
t.end();
});
37 changes: 37 additions & 0 deletions packages/supertape/bin/tracer.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env node

import process from 'node:process';
import {Worker} from 'node:worker_threads';
import {parseArgs} from '../lib/cli/parse-args.js';
import {subscribe} from './subscribe.mjs';

const {
cwd,
exit,
stdout,
} = process;

const args = parseArgs(process.argv.slice(2));
const write = stdout.write.bind(stdout);

if (!args.worker) {
await import('./supertape.mjs');
exit();
}

const slave = new URL('./supertape.mjs', import.meta.url);

const worker = new Worker(slave, {
workerData: process.argv,
stdin: true,
});

await subscribe({
name: args.format,
args,
worker,
exit,
cwd,
write,
stdout,
});
62 changes: 9 additions & 53 deletions packages/supertape/lib/cli.js
Original file line number Diff line number Diff line change
@@ -5,12 +5,13 @@ const {resolve: resolvePath} = require('path');
const {once} = require('events');
const {pathToFileURL} = require('url');

const yargsParser = require('yargs-parser');
const glob = require('glob');
const fullstore = require('fullstore');
const tryToCatch = require('try-to-catch');
const keypress = require('@putout/cli-keypress');

const {simpleImport} = require('./simple-import');
const {parseArgs, yargsOptions} = require('./cli/parse-args');

const supertape = require('..');
const {
@@ -22,30 +23,22 @@ const {
SKIPED,
} = require('./exit-codes');

const {isArray} = Array;

const maybeFirst = (a) => isArray(a) ? a.pop() : a;
const maybeArray = (a) => isArray(a) ? a : [a];
const isExclude = (a) => !a.includes('node_modules');

const removeDuplicates = (a) => Array.from(new Set(a));

const filesCount = fullstore(0);

const {
SUPERTAPE_CHECK_DUPLICATES = '1',
SUPERTAPE_CHECK_SCOPES = '1',
SUPERTAPE_CHECK_ASSERTIONS_COUNT = '1',
SUPERTAPE_CHECK_SKIPED = '0',
} = process.env;
const {SUPERTAPE_CHECK_SKIPED = '0'} = process.env;

module.exports = async ({argv, cwd, stdout, stderr, exit}) => {
module.exports = async ({argv, cwd, stdout, stderr, exit, workerFormatter}) => {
const {isStop} = keypress();
const [error, result] = await tryToCatch(cli, {
argv,
cwd,
stdout,
exit,
isStop,
workerFormatter,
});

if (error) {
@@ -77,46 +70,8 @@ module.exports = async ({argv, cwd, stdout, stderr, exit}) => {
return exit(OK);
};

const yargsOptions = {
configuration: {
'strip-aliased': true,
'strip-dashed': true,
},
coerce: {
require: maybeArray,
format: maybeFirst,
},
string: [
'format',
'require',
],
boolean: [
'version',
'help',
'check-duplicates',
'check-scopes',
'check-assertions-count',
],
alias: {
version: 'v',
format: 'f',
help: 'h',
require: 'r',
checkDuplicates: 'd',
checkScopes: 's',
checkAssertionsCount: 'a',
},
default: {
format: 'progress-bar',
require: [],
checkDuplicates: SUPERTAPE_CHECK_DUPLICATES !== '0',
checkScopes: SUPERTAPE_CHECK_SCOPES !== '0',
checkAssertionsCount: SUPERTAPE_CHECK_ASSERTIONS_COUNT !== '0',
},
};

async function cli({argv, cwd, stdout, isStop}) {
const args = yargsParser(argv, yargsOptions);
async function cli({argv, cwd, stdout, isStop, workerFormatter}) {
const args = parseArgs(argv);

if (args.version) {
stdout.write(`v${require('../package').version}\n`);
@@ -171,6 +126,7 @@ async function cli({argv, cwd, stdout, isStop}) {
checkDuplicates,
checkScopes,
checkAssertionsCount,
workerFormatter,
});

const stream = await supertape.createStream();
11 changes: 11 additions & 0 deletions packages/supertape/lib/cli.spec.js
Original file line number Diff line number Diff line change
@@ -431,6 +431,7 @@ test('supertape: bin: cli: --check-duplicates', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -473,6 +474,7 @@ test('supertape: bin: cli: --check-assertions-count', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -502,11 +504,13 @@ test('supertape: bin: cli: SUPERTAPE_CHECK_DUPLICATES: env', async (t) => {
mockRequire('..', test);

process.env.SUPERTAPE_CHECK_DUPLICATES = '0';
reRequire('./cli/parse-args');
await runCli({
argv,
});
delete process.env.SUPERTAPE_CHECK_DUPLICATES;

reRequire('./cli/parse-args');
stopAll();

const expected = [{
@@ -517,6 +521,7 @@ test('supertape: bin: cli: SUPERTAPE_CHECK_DUPLICATES: env', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -561,6 +566,7 @@ test('supertape: bin: cli: SUPERTAPE_ASSERTIONS_COUNT: env', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -605,6 +611,7 @@ test('supertape: bin: cli: SUPERTAPE_CHECK_DUPLICATES: disabled with a flag, ena
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -647,6 +654,7 @@ test('supertape: bin: cli: check-duplicates: -d', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -695,6 +703,7 @@ test('supertape: bin: cli: format: apply last', async (t) => {
checkAssertionsCount: true,
checkScopes: true,
isStop,
workerFormatter: null,
}];

t.calledWith(init, expected);
@@ -762,6 +771,7 @@ async function runCli(options) {
stderr = createStream(),
cwd = __dirname,
exit = stub(),
workerFormatter = null,
} = options;

const cli = reRequire('./cli');
@@ -772,6 +782,7 @@ async function runCli(options) {
stderr,
cwd,
exit,
workerFormatter,
});

return [error, cli];
56 changes: 56 additions & 0 deletions packages/supertape/lib/cli/parse-args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict';

const process = require('node:process');
const yargsParser = require('yargs-parser');
const {isArray} = Array;
const maybeFirst = (a) => isArray(a) ? a.pop() : a;
const maybeArray = (a) => isArray(a) ? a : [a];

const {
SUPERTAPE_CHECK_DUPLICATES = '1',
SUPERTAPE_CHECK_SCOPES = '1',
SUPERTAPE_CHECK_ASSERTIONS_COUNT = '1',
} = process.env;

const yargsOptions = {
configuration: {
'strip-aliased': true,
'strip-dashed': true,
},
coerce: {
require: maybeArray,
format: maybeFirst,
},
string: [
'format',
'require',
],
boolean: [
'version',
'help',
'check-duplicates',
'check-scopes',
'check-assertions-count',
'worker',
],
alias: {
version: 'v',
format: 'f',
help: 'h',
require: 'r',
checkDuplicates: 'd',
checkScopes: 's',
checkAssertionsCount: 'a',
},
default: {
format: 'progress-bar',
require: [],
checkDuplicates: SUPERTAPE_CHECK_DUPLICATES !== '0',
checkScopes: SUPERTAPE_CHECK_SCOPES !== '0',
checkAssertionsCount: SUPERTAPE_CHECK_ASSERTIONS_COUNT !== '0',
worker: true,
},
};

module.exports.yargsOptions = yargsOptions;
module.exports.parseArgs = (argv) => yargsParser(argv, yargsOptions);
37 changes: 37 additions & 0 deletions packages/supertape/lib/formatter/communication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const {parentPort, workerData} = require('node:worker_threads');

const {EventEmitter} = require('node:events');
const process = require('node:process');

const {assign} = Object;

module.exports.createCommunication = () => {
if (parentPort)
return {
parentPort,
workerData,
};

const newWorker = new EventEmitter();
const newParentPort = new EventEmitter();

assign(newWorker, {
postMessage: (a) => {
newParentPort.emit('message', a);
},
});

assign(newParentPort, {
postMessage: (a) => {
newWorker.emit('message', a);
},
});

return {
worker: newWorker,
parentPort: newParentPort,
workerData: process.argv,
};
};
3 changes: 2 additions & 1 deletion packages/supertape/lib/formatter/harness.js
Original file line number Diff line number Diff line change
@@ -69,5 +69,6 @@ function run(reporter, type, data) {
if (type === 'fail')
return reporter.fail(data);

return reporter.end(data);
if (type === 'end')
return reporter.end(data);
}
1 change: 0 additions & 1 deletion packages/supertape/lib/run-tests.js
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@ const notSkip = ({skip}) => !skip;
const getInitOperators = async () => await import('./operators.mjs');

const {SUPERTAPE_TIMEOUT = 3000} = process.env;

const DEBUG_TIME = 3000 * 1000;

const timeout = (time, value) => {
7 changes: 4 additions & 3 deletions packages/supertape/lib/supertape.js
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ const defaultOptions = {
checkScopes: true,
};

function _createEmitter({quiet, stream = stdout, format, getOperators, isStop, readyFormatter}) {
function _createEmitter({quiet, stream = stdout, format, getOperators, isStop, readyFormatter, workerFormatter}) {
const tests = [];
const emitter = new EventEmitter();

@@ -77,7 +77,7 @@ function _createEmitter({quiet, stream = stdout, format, getOperators, isStop, r
const operators = await getOperators();

const result = await runTests(tests, {
formatter,
formatter: workerFormatter || formatter,
operators,
isStop,
});
@@ -152,6 +152,7 @@ function test(message, fn, options = {}) {
checkScopes,
checkAssertionsCount,
checkIfEnded,
workerFormatter,
} = {
...defaultOptions,
...initedOptions,
@@ -168,12 +169,12 @@ function test(message, fn, options = {}) {
setValidations(validations);

const at = getAt();

const emitter = options.emitter || createEmitter({
format,
quiet,
getOperators,
isStop,
workerFormatter,
});

mainEmitter = emitter;
2 changes: 1 addition & 1 deletion packages/supertape/lib/supertape.spec.js
Original file line number Diff line number Diff line change
@@ -953,8 +953,8 @@ test('supertape: createTest: formatter', async (t) => {
const {createTest} = reRequire('..');
const {
test,
stream,
run,
stream,
} = await createTest({
run: false,
formatter: await import('@supertape/formatter-json-lines'),
4 changes: 2 additions & 2 deletions packages/supertape/package.json
Original file line number Diff line number Diff line change
@@ -19,8 +19,8 @@
},
"type": "commonjs",
"bin": {
"tape": "bin/supertape.mjs",
"supertape": "bin/supertape.mjs"
"tape": "bin/tracer.mjs",
"supertape": "bin/tracer.mjs"
},
"repository": {
"type": "git",

0 comments on commit 64c7acb

Please sign in to comment.