Compare commits

..

8 Commits

Author SHA1 Message Date
Jonatan Nilsson a67479f4bc assert: Add equalWithMargin for floating and other number comparison with error margin
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2024-09-20 19:33:50 +00:00
Jonatan Nilsson 17d7bb862c expose stub calls and remove logging on before/after code
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2024-09-20 19:09:20 +00:00
TheThing 15d1ba43f4 fix kill and make it work better on more platforms
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2024-03-01 09:40:14 +00:00
TheThing 8fad1b45b1 Fix bug in kill and add basic test. Improve error handling on import errors.
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2023-10-31 13:25:08 +00:00
TheThing a70d64e624 Implement smarter process kill when in npm mode with watch changes
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2023-10-31 13:05:45 +00:00
TheThing 18f9806135 Fix bug in cli preventing npm-only run
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2023-10-31 08:41:03 +00:00
Jonatan Nilsson 264364a152 Fix tests for windows
continuous-integration/appveyor/branch AppVeyor build succeeded Details
2023-10-31 08:32:44 +00:00
TheThing feac2678bd Cli no longer throws if eltro is only being used for npm-watching and not testing 2023-10-31 08:32:34 +00:00
17 changed files with 372 additions and 65 deletions

View File

@ -111,9 +111,11 @@ $ eltro --watch my_watch_name --npm build
Not only does eltro allow you to use any assertion library of your own choosing, it also comes with it's own assertion library based on node's default [assert](https://nodejs.org/api/assert.html) with a few extra methods: Not only does eltro allow you to use any assertion library of your own choosing, it also comes with it's own assertion library based on node's default [assert](https://nodejs.org/api/assert.html) with a few extra methods:
* `assert.equalWithMargin(value, test, margin, [message])`: Check if number value is equal to test with error margin.
* `assert.notOk(value, [message])`: Assert value is not ok. * `assert.notOk(value, [message])`: Assert value is not ok.
* `assert.match(value, test, [message])`: Check if value matches RegExp test. * `assert.match(value, test, [message])`: Check if value matches RegExp test.
* `assert.notMatch(value, [message])`: Check if value does not match RegExp test. * `assert.notMatch(value, [message])`: Check if value does not match RegExp test.
* `assert.throwsAndCatch(fn, [message])`: Checks if function fn throws and returns the thrown error.
* `assert.isFulfilled(promise, [message])`: Assert the promise resolves. * `assert.isFulfilled(promise, [message])`: Assert the promise resolves.
* `assert.isRejected(promise, [message])`: Assert the promise gets rejects. * `assert.isRejected(promise, [message])`: Assert the promise gets rejects.

11
cli.mjs
View File

@ -27,15 +27,20 @@ function PrintHelp() {
process.exit(1) process.exit(1)
} }
function showErrorAndExit(message = '', err = null, code = 1) { function showErrorAndExit(message = '', err = null, code = 1, clean = false) {
if (!clean) {
console.log('') console.log('')
}
if (message) { if (message) {
console.error(`\x1b[31m${message}\x1b[0m`) console.error(`\x1b[31m${message}\x1b[0m`)
} }
if (err) { if (err) {
printError(err) printError(err, '', clean)
if (err.inner) {
return showErrorAndExit(null, err.inner, code, true)
}
} else { } else {
PrintHelp() PrintHelp()
} }
@ -54,7 +59,7 @@ cli.parseOptions(args)
}) })
.catch(function(err) { showErrorAndExit('Unknown error while processing arguments', err) }) .catch(function(err) { showErrorAndExit('Unknown error while processing arguments', err) })
.then(function() { .then(function() {
if (!cli.files.length) { if (!cli.files.length && cli.run === 'test') {
showErrorAndExit('No files were found with pattern', cli.targets.join(',')) showErrorAndExit('No files were found with pattern', cli.targets.join(','))
} }

View File

@ -57,6 +57,14 @@ assert.throwsAndCatch = (fn, message) => {
return err return err
} }
assert.equalWithMargin = (value, test, margin = 0.005, message) => {
assert.strictEqual(typeof value, 'number', 'Value should be number')
assert.strictEqual(typeof test, 'number', 'Test should be number')
let difference = Math.abs(value - test)
assert.ok(difference <= margin, message || `${value} ± ${margin} != ${test} (difference of ${difference} > ${margin})`)
}
assert.isFulfilled = (promise, message) => { assert.isFulfilled = (promise, message) => {
return Promise.resolve(true) return Promise.resolve(true)
.then(() => promise) .then(() => promise)

View File

@ -3,6 +3,7 @@ import fs from 'fs'
import fsPromise from 'fs/promises' import fsPromise from 'fs/promises'
import cluster from 'cluster' import cluster from 'cluster'
import child_process from 'child_process' import child_process from 'child_process'
import kill from './kill.mjs'
import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from './watch/index.mjs' import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from './watch/index.mjs'
export const MESSAGE_FILES_REQUEST = 'message_files_request' export const MESSAGE_FILES_REQUEST = 'message_files_request'
@ -41,8 +42,12 @@ export function CLI(e, overrides = {}) {
this.logger = overrides.logger || console this.logger = overrides.logger || console
this.child_process = overrides.child_process || child_process this.child_process = overrides.child_process || child_process
this.process = overrides.process || process this.process = overrides.process || process
this.kill = overrides.kill || kill
this.importer = overrides.importer this.importer = overrides.importer
this.loadDefaults()
}
CLI.prototype.loadDefaults = function() {
// Eltro specific options // Eltro specific options
this.reporter = 'list' this.reporter = 'list'
this.ignoreOnly = false this.ignoreOnly = false
@ -110,7 +115,7 @@ CLI.prototype.parseOptions = function(args) {
} }
} }
if (!this.targets.length) { if (!this.targets.length && this.run === 'test') {
this.targets.push('test/**') this.targets.push('test/**')
} }
@ -284,7 +289,7 @@ CLI.prototype.runProgram = function() {
if (runningTest) { if (runningTest) {
return return
} else { } else {
this.worker.kill() this.kill(this.worker.pid)
} }
} }
@ -435,9 +440,9 @@ export function getFilesFromTarget(files, match, insidePath, grabAll, insideStar
}) })
} }
export function printError(err, msg) { export function printError(err, msg, clean = false) {
let before = msg || '' let before = msg || ''
console.error('') if (!clean) console.error('')
console.error('\x1b[31m ' console.error('\x1b[31m '
+ before + err.toString() + before + err.toString()
+ '\x1b[0m\n \x1b[90m' + '\x1b[0m\n \x1b[90m'

View File

@ -117,6 +117,7 @@ process.on('uncaughtException', function(err) {
}) })
function Eltro() { function Eltro() {
this.process = process
this.__timeout = 2000 this.__timeout = 2000
this.hasExclusive = false this.hasExclusive = false
this.reporter = 'list' this.reporter = 'list'
@ -159,8 +160,8 @@ Eltro.prototype.begin = function() {
} }
Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child = null) { Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child = null) {
if (this.reporter === 'list') { if (this.reporter === 'list' && prefix === 'Test') {
process.stdout.write(' \x1b[90m? ' + test.name + '\x1b[0m') this.process.stdout.write(' \x1b[90m? ' + test.name + '\x1b[0m')
} }
let markRealTest = child || test let markRealTest = child || test
@ -279,24 +280,24 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
this.captureOutsideExceptions = null this.captureOutsideExceptions = null
if (this.reporter === 'list') { if (this.reporter === 'list') {
readline.clearLine(process.stdout, 0) readline.clearLine(this.process.stdout, 0)
readline.cursorTo(process.stdout, 0, null) readline.cursorTo(this.process.stdout, 0, null)
if (markRealTest.skipTest) { if (markRealTest.skipTest) {
process.stdout.write(' \x1b[94m- ' + markRealTest.name + '\x1b[0m\n') this.process.stdout.write(' \x1b[94m- ' + markRealTest.name + '\x1b[0m\n')
} else if (!markRealTest.error) { } else if (!markRealTest.error) {
if (!test.name.startsWith('~')) { if (!test.name.startsWith('~')) {
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n') this.process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
} }
} else if (prefix === 'Test') { } else if (prefix === 'Test') {
process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n') this.process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
} }
} else if (this.reporter === 'dot') { } else if (this.reporter === 'dot') {
if (markRealTest.skipTest) { if (markRealTest.skipTest) {
process.stdout.write('\x1b[94m.\x1b[0m') this.process.stdout.write('\x1b[94m.\x1b[0m')
} else if (!markRealTest.error) { } else if (markRealTest.error) {
process.stdout.write('\x1b[32m.\x1b[0m') this.process.stdout.write('\x1b[31m.\x1b[0m')
} else if (prefix === 'Test') { } else if (prefix === 'Test') {
process.stdout.write('\x1b[31m.\x1b[0m') this.process.stdout.write('\x1b[32m.\x1b[0m')
} }
} }
} }
@ -304,7 +305,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
Eltro.prototype.__runGroup = async function(g, stats) { Eltro.prototype.__runGroup = async function(g, stats) {
if (g.tests.length) { if (g.tests.length) {
if (this.reporter === 'list') { if (this.reporter === 'list') {
console.log(' ' + g.name) this.process.stdout.write(' ' + g.name + '\n')
} }
} }
if (g.before) { if (g.before) {
@ -351,8 +352,8 @@ Eltro.prototype.__runGroup = async function(g, stats) {
Eltro.prototype.run = async function() { Eltro.prototype.run = async function() {
if (this.reporter && this.reporter !== 'test') { if (this.reporter && this.reporter !== 'test') {
console.log('') this.process.stdout.write('' + '\n')
console.log('') this.process.stdout.write('' + '\n')
} }
captureUnknownErrors(this) captureUnknownErrors(this)
@ -381,23 +382,23 @@ Eltro.prototype.run = async function() {
} }
} }
} else if (this.reporter) { } else if (this.reporter) {
console.log('') this.process.stdout.write('' + '\n')
console.log('') this.process.stdout.write('' + '\n')
if (stats.passed) { if (stats.passed) {
console.log(' \x1b[32m' + stats.passed + ' passing \x1b[90m(' + (end[0] * 1000 + Math.round(end[1] / 1000000)) + 'ms)\x1b[0m') this.process.stdout.write(' \x1b[32m' + stats.passed + ' passing \x1b[90m(' + (end[0] * 1000 + Math.round(end[1] / 1000000)) + 'ms)\x1b[0m' + '\n')
} }
if (stats.failed) { if (stats.failed) {
console.log(' \x1b[31m' + stats.failed + ' failing\x1b[0m') this.process.stdout.write(' \x1b[31m' + stats.failed + ' failing\x1b[0m' + '\n')
} }
if (stats.skipped) { if (stats.skipped) {
console.log(' \x1b[94m' + stats.skipped + ' pending\x1b[0m') this.process.stdout.write(' \x1b[94m' + stats.skipped + ' pending\x1b[0m' + '\n')
} }
console.log('') this.process.stdout.write('' + '\n')
if (this.failedTests.length) { if (this.failedTests.length) {
for (let x = 0; x < this.failedTests.length; x++) { for (let x = 0; x < this.failedTests.length; x++) {
let test = this.failedTests[x]; let test = this.failedTests[x];
console.log(' ' + (x + 1) + ') ' + test.name + ':') this.process.stdout.write(' ' + (x + 1) + ') ' + test.name + ':' + '\n')
printError(test.error) printError(test.error)
} }
} }

85
lib/kill.mjs Normal file
View File

@ -0,0 +1,85 @@
import { promisify } from 'util'
import { spawn, exec } from 'child_process'
const execPromise = promisify(exec)
export default function kill(pid, signal) {
let pids = new Set([pid])
let getSpawn = null
let getPids = null
switch (process.platform) {
case 'win32':
return execPromise('taskkill /pid ' + pid + ' /T /F').then(() => pids)
case 'darwin':
getSpawn = function(parentPid) {
return spawn('pgrep', ['-P', parentPid])
}
getPids = function(data) {
return data.match(/\d+/g).map(Number)
}
break
default:
getSpawn = function (parentPid) {
return exec('ps -opid="" -oppid="" | grep ' + parentPid)
}
getPids = function(data, parentPid) {
let output = data.trim().split('\n')
return output.map(line => {
let [child, parent] = line.trim().split(/ +/)
if (Number(parent) === parentPid) {
return Number(child)
}
return 0
}).filter(x => x)
}
break
}
return buildTree(pids, getSpawn, getPids, pid)
.then(function() {
for (let pid of pids) {
try {
process.kill(pid, signal)
} catch (err) {
if (err.code !== 'ESRCH') throw err;
}
}
return pids
})
}
function buildTree(allPids, spawnGetChildren, spawnGetPids, parentPid) {
allPids.add(parentPid)
let ps = spawnGetChildren(parentPid)
let data = ''
let err = ''
ps.stdout.on('data', function(buf) {
data += buf.toString('ascii')
})
ps.stderr.on('data', function(buf) {
err += buf.toString('ascii')
})
return new Promise(function(res, rej) {
ps.on('close', function(code) {
// Check if ps errored out
if (code !== 0 && err.trim()) {
return rej(new Error('Error running ps to kill processes:\n\t' + err))
}
// Check if we otherwise got an error code (usually means empty results)
if (code !== 0 || !data.trim()) return res()
let pids = spawnGetPids(data, parentPid)
res(Promise.all(
pids.filter(pid => pid && !allPids.has(pid))
.map(buildTree.bind(this, allPids, spawnGetChildren, spawnGetPids))
))
})
})
}

View File

@ -34,6 +34,7 @@ export function stub(returnFunc = null) {
func.lastCall = null func.lastCall = null
func.called = false func.called = false
func.callCount = 0 func.callCount = 0
func.calls = calls
func.findCall = function(fn) { func.findCall = function(fn) {
for (let call of calls) { for (let call of calls) {

View File

@ -1,6 +1,6 @@
{ {
"name": "eltro", "name": "eltro",
"version": "1.4.0", "version": "1.5.0",
"description": "Eltro is a tiny no-dependancy test framework for node", "description": "Eltro is a tiny no-dependancy test framework for node",
"main": "index.mjs", "main": "index.mjs",
"scripts": { "scripts": {

View File

@ -29,6 +29,41 @@ t.describe('#notOk()', function() {
}) })
}) })
t.describe('#equalWithMargin()', function() {
t.test('should support default margin for floating point math', function() {
let check = 0.1 + 0.2
assertExtended.throws(function() {
assertExtended.strictEqual(check, 0.3)
}, assertExtended.AssertionError)
assertExtended.equalWithMargin(check, 0.3)
})
t.test('should support custom margin', function() {
assertExtended.equalWithMargin(1, 2, 1)
})
t.test('should fail if margin is too small', function() {
assertExtended.throws(function() {
assertExtended.equalWithMargin(1, 2, 0.9)
}, assertExtended.AssertionError)
})
t.test('should support custom message', function () {
const assertMessage = 'Hello world'
let error = null
try {
assertExtended.equalWithMargin(1, 2, 0, assertMessage)
} catch (err) {
error = err
}
assert.ok(error)
assert.match(error.message, new RegExp(assertMessage))
})
})
t.describe('#throwAndCatch()', function() { t.describe('#throwAndCatch()', function() {
t.test('should work and return the original error', function() { t.test('should work and return the original error', function() {
const assertError = new Error('Speed') const assertError = new Error('Speed')

View File

@ -26,8 +26,9 @@ t.describe('CLI', function() {
const cluster = { c: 3 } const cluster = { c: 3 }
const process = { d: 4 } const process = { d: 4 }
const importer = { e: 5 } const importer = { e: 5 }
const kill = { f: 6 }
let cliTest = createSafeCli(eltro, { logger, cluster, process, importer }) let cliTest = createSafeCli(eltro, { logger, cluster, process, importer, kill })
assert.strictEqual(cliTest.reporter, 'list') assert.strictEqual(cliTest.reporter, 'list')
assert.strictEqual(cliTest.ignoreOnly, false) assert.strictEqual(cliTest.ignoreOnly, false)
assert.strictEqual(cliTest.timeout, 2000) assert.strictEqual(cliTest.timeout, 2000)
@ -43,6 +44,7 @@ t.describe('CLI', function() {
assert.strictEqual(cliTest.cluster, cluster) assert.strictEqual(cliTest.cluster, cluster)
assert.strictEqual(cliTest.process, process) assert.strictEqual(cliTest.process, process)
assert.strictEqual(cliTest.importer, importer) assert.strictEqual(cliTest.importer, importer)
assert.strictEqual(cliTest.kill, kill)
}) })
t.test('should detect isSlave from cluster', function() { t.test('should detect isSlave from cluster', function() {
@ -59,32 +61,31 @@ t.describe('CLI', function() {
*****************************************/ *****************************************/
t.describe('#parseOptions()', function() { t.describe('#parseOptions()', function() {
t.beforeEach(function() {
cli.loadDefaults()
})
t.test('should not do anything if no options', async function() { t.test('should not do anything if no options', async function() {
cli.reporter = 'list'
await cli.parseOptions([]) await cli.parseOptions([])
assert.strictEqual(cli.reporter, 'list') assert.strictEqual(cli.reporter, 'list')
}) })
t.test('should support overriding reporter with shorthand option', async function() { t.test('should support overriding reporter with shorthand option', async function() {
cli.reporter = 'list'
await cli.parseOptions(['-r', 'dot']) await cli.parseOptions(['-r', 'dot'])
assert.strictEqual(cli.reporter, 'dot') assert.strictEqual(cli.reporter, 'dot')
}) })
t.test('should support overriding reporter with long option', async function() { t.test('should support overriding reporter with long option', async function() {
cli.reporter = 'list'
await cli.parseOptions(['--reporter', 'dot']) await cli.parseOptions(['--reporter', 'dot'])
assert.strictEqual(cli.reporter, 'dot') assert.strictEqual(cli.reporter, 'dot')
}) })
t.test('should support enabling ignore-only long option', async function() { t.test('should support enabling ignore-only long option', async function() {
cli.ignoreOnly = false
await cli.parseOptions(['--ignore-only', '-r', 'dot']) await cli.parseOptions(['--ignore-only', '-r', 'dot'])
assert.strictEqual(cli.ignoreOnly, true) assert.strictEqual(cli.ignoreOnly, true)
}) })
t.test('should support reporter list', async function() { t.test('should support reporter list', async function() {
cli.reporter = 'list'
await cli.parseOptions(['-r', 'list']) await cli.parseOptions(['-r', 'list'])
assert.strictEqual(cli.reporter, 'list') assert.strictEqual(cli.reporter, 'list')
}) })
@ -100,13 +101,11 @@ t.describe('CLI', function() {
}) })
t.test('should support overriding timeout with shorthand option', async function() { t.test('should support overriding timeout with shorthand option', async function() {
cli.timeout = 2000
await cli.parseOptions(['-t', '1000']) await cli.parseOptions(['-t', '1000'])
assert.strictEqual(cli.timeout, 1000) assert.strictEqual(cli.timeout, 1000)
}) })
t.test('should support overriding timeout with long option', async function() { t.test('should support overriding timeout with long option', async function() {
cli.timeout = 2000
await cli.parseOptions(['--timeout', '250']) await cli.parseOptions(['--timeout', '250'])
assert.strictEqual(cli.timeout, 250) assert.strictEqual(cli.timeout, 250)
}) })
@ -122,57 +121,54 @@ t.describe('CLI', function() {
}) })
t.test('should support overriding watch', async function() { t.test('should support overriding watch', async function() {
cli.watch = null
await cli.parseOptions(['-w', 'unittest_test1']) await cli.parseOptions(['-w', 'unittest_test1'])
assert.strictEqual(cli.watch, 'unittest_test1') assert.strictEqual(cli.watch, 'unittest_test1')
}) })
t.test('should support overriding watch with long option', async function() { t.test('should support overriding watch with long option', async function() {
cli.watch = null
await cli.parseOptions(['--watch', 'unittest_test1']) await cli.parseOptions(['--watch', 'unittest_test1'])
assert.strictEqual(cli.watch, 'unittest_test1') assert.strictEqual(cli.watch, 'unittest_test1')
}) })
t.test('should fail overriding if next parameter is missing', async function() { t.test('should fail setting watch if value is missing', async function() {
cli.watch = null
let err = await assert.isRejected(cli.parseOptions(['--watch'])) let err = await assert.isRejected(cli.parseOptions(['--watch']))
assert.strictEqual(cli.watch, null) assert.strictEqual(cli.watch, null)
assert.match(err.message, /watch/i) assert.match(err.message, /watch/i)
}) })
t.test('should fail overriding if next parameter is a parameter', async function() { t.test('should fail setting watch if value is parameter', async function() {
cli.watch = null
let err = await assert.isRejected(cli.parseOptions(['-w', '--reporter', 'list'])) let err = await assert.isRejected(cli.parseOptions(['-w', '--reporter', 'list']))
assert.strictEqual(cli.watch, null) assert.strictEqual(cli.watch, null)
assert.match(err.message, /watch/i) assert.match(err.message, /watch/i)
}) })
t.test('should support overriding run', async function() { t.test('should support overriding run', async function() {
cli.run = null
await cli.parseOptions(['-n', 'unittest_run1']) await cli.parseOptions(['-n', 'unittest_run1'])
assert.strictEqual(cli.run, 'unittest_run1') assert.strictEqual(cli.run, 'unittest_run1')
}) })
t.test('should support overriding run with long option', async function() { t.test('should support overriding run with long option', async function() {
cli.run = null
await cli.parseOptions(['--npm', 'unittest_run1']) await cli.parseOptions(['--npm', 'unittest_run1'])
assert.strictEqual(cli.run, 'unittest_run1') assert.strictEqual(cli.run, 'unittest_run1')
}) })
t.test('should fail overriding if next parameter is missing', async function() { t.test('should fail setting npm if value is missing', async function() {
cli.run = null
let err = await assert.isRejected(cli.parseOptions(['--npm'])) let err = await assert.isRejected(cli.parseOptions(['--npm']))
assert.strictEqual(cli.run, null) assert.strictEqual(cli.run, 'test')
assert.match(err.message, /npm/i) assert.match(err.message, /npm/i)
}) })
t.test('should fail overriding if next parameter is a parameter', async function() { t.test('should fail setting npm if value is parameter', async function() {
cli.run = null
let err = await assert.isRejected(cli.parseOptions(['-n', '--reporter', 'list'])) let err = await assert.isRejected(cli.parseOptions(['-n', '--reporter', 'list']))
assert.strictEqual(cli.run, null) assert.strictEqual(cli.run, 'test')
assert.match(err.message, /npm/i) assert.match(err.message, /npm/i)
}) })
t.test('when using run and no target, leave target empty', async function() {
await cli.parseOptions(['--npm', 'unittest_run1'])
assert.strictEqual(cli.targets.length, 0)
})
t.test('should add file to targets', async function() { t.test('should add file to targets', async function() {
await cli.parseOptions(['test']) await cli.parseOptions(['test'])
assert.deepEqual(cli.targets, ['test']) assert.deepEqual(cli.targets, ['test'])
@ -756,10 +752,10 @@ t.describe('CLI', function() {
assert.ok(eltro.starting) assert.ok(eltro.starting)
assert.strictEqual(loaded.length, 2) assert.strictEqual(loaded.length, 2)
assert.match(loaded[0], path.join('root', testFiles[1])) assert.match(loaded[0], new RegExp(path.join('root', testFiles[1]).replace(/\\/g, '\\\\')))
assert.match(loaded[1], path.join('root', testFiles[2])) assert.match(loaded[1], new RegExp(path.join('root', testFiles[2]).replace(/\\/g, '\\\\')))
assert.match(filenames[0], testFiles[1]) assert.match(filenames[0], new RegExp(testFiles[1].replace(/\\/g, '\\\\')))
assert.match(filenames[1], testFiles[2]) assert.match(filenames[1], new RegExp(testFiles[2].replace(/\\/g, '\\\\')))
}) })
t.test('on error should throw and wrap in inner error', async function() { t.test('on error should throw and wrap in inner error', async function() {
@ -919,14 +915,16 @@ t.describe('CLI', function() {
let testCluster let testCluster
let testChildProcess let testChildProcess
let testWorker let testWorker
let testKill
let cli let cli
t.beforeEach(function() { t.beforeEach(function() {
testKill = stub()
testProcess = { stdout: { write: stub() }, stderr: { write: stub() } } testProcess = { stdout: { write: stub() }, stderr: { write: stub() } }
testWorker = { on: stub(), once: stub(), kill: stub(), send: stub(), stderr: { on: stub() }, stdout: { on: stub() } } testWorker = { on: stub(), once: stub(), send: stub(), stderr: { on: stub() }, stdout: { on: stub() } }
testCluster = { fork: stub().returns(testWorker) } testCluster = { fork: stub().returns(testWorker) }
testChildProcess = { spawn: stub().returns(testWorker) } testChildProcess = { spawn: stub().returns(testWorker) }
cli = createSafeCli(null, { cluster: testCluster, child_process: testChildProcess, process: testProcess }) cli = createSafeCli(null, { cluster: testCluster, child_process: testChildProcess, process: testProcess, kill: testKill })
}) })
t.describe('in test mode', function() { t.describe('in test mode', function() {
@ -990,14 +988,14 @@ t.describe('CLI', function() {
t.test('multiple calls should cancel', function() { t.test('multiple calls should cancel', function() {
cli.runProgram() cli.runProgram()
assert.notOk(testWorker.kill.called) assert.notOk(testKill.called)
assert.ok(testCluster.fork.called) assert.ok(testCluster.fork.called)
testCluster.fork.reset() testCluster.fork.reset()
cli.runProgram() cli.runProgram()
assert.notOk(testCluster.fork.called) assert.notOk(testCluster.fork.called)
assert.notOk(testWorker.kill.called) assert.notOk(testKill.called)
}) })
}) })
@ -1069,16 +1067,19 @@ t.describe('CLI', function() {
}) })
t.test('multiple calls should kill', function() { t.test('multiple calls should kill', function() {
const assertPid = 12345235
testWorker.pid = assertPid
cli.runProgram() cli.runProgram()
assert.notOk(testWorker.kill.called) assert.notOk(testKill.called)
assert.ok(testChildProcess.spawn.called) assert.ok(testChildProcess.spawn.called)
testChildProcess.spawn.reset().returns(testWorker) testChildProcess.spawn.reset().returns(testWorker)
cli.runProgram() cli.runProgram()
assert.ok(testChildProcess.spawn.called) assert.ok(testChildProcess.spawn.called)
assert.ok(testWorker.kill.called) assert.ok(testKill.called)
assert.strictEqual(testKill.firstCall[0], assertPid)
}) })
}) })
}) })

View File

@ -8,6 +8,9 @@ function CreateT() {
t.logger = { t.logger = {
log: stub() log: stub()
} }
t.process = {
stdout: { write: stub() }
}
return t return t
} }
@ -284,6 +287,60 @@ e.describe('#beforeEach()', function() {
assert.match(t.logger.log.getCallN(4)[0], /DDDD/) assert.match(t.logger.log.getCallN(4)[0], /DDDD/)
assert.match(t.logger.log.getCallN(4)[0], /BBBB/) assert.match(t.logger.log.getCallN(4)[0], /BBBB/)
}) })
e.describe('reporter', function() {
e.test('should not log before each with reporter list', async function() {
const t = CreateT()
t.reporter = 'list'
t.begin()
t.describe('BBBB', function() {
t.beforeEach(function() {})
t.describe('CCCC', function() {
t.test('c1', function() { })
t.test('c2', function() { })
})
t.describe('DDDD', function() {
t.test('d1', function() { })
})
t.test('AAAA', function() { })
})
await t.run()
for (let row of t.process.stdout.write.calls) {
assert.notMatch(row.filter(x => x).join(' '), /before each/i)
}
})
e.test('should not log success before each with reporter dot', async function() {
const t = CreateT()
t.reporter = 'dot'
t.begin()
t.describe('BBBB', function() {
t.beforeEach(function() {})
t.describe('CCCC', function() {
t.test('c1', function() { })
t.test('c2', function() { })
})
t.describe('DDDD', function() {
t.test('d1', function() { })
})
t.test('AAAA', function() { })
})
await t.run()
let total = 0
for (let row of t.process.stdout.write.calls) {
if (row.filter(x => x).join(' ').match(/\[32m\./)) {
total++
}
}
assert.strictEqual(total, 4)
})
})
}) })
e.describe('#after()', function() { e.describe('#after()', function() {
@ -679,6 +736,60 @@ e.describe('#afterEach()', function() {
assert.match(t.logger.log.getCallN(5)[0], /JJJJ/) assert.match(t.logger.log.getCallN(5)[0], /JJJJ/)
assert.match(t.logger.log.getCallN(5)[0], /YYYY/) assert.match(t.logger.log.getCallN(5)[0], /YYYY/)
}) })
e.describe('reporter', function() {
e.test('should not log before each with reporter list', async function() {
const t = CreateT()
t.reporter = 'list'
t.begin()
t.describe('BBBB', function() {
t.afterEach(function() {})
t.describe('CCCC', function() {
t.test('c1', function() { })
t.test('c2', function() { })
})
t.describe('DDDD', function() {
t.test('d1', function() { })
})
t.test('AAAA', function() { })
})
await t.run()
for (let row of t.process.stdout.write.calls) {
assert.notMatch(row.filter(x => x).join(' '), /after each/i)
}
})
e.test('should not log success before each with reporter dot', async function() {
const t = CreateT()
t.reporter = 'dot'
t.begin()
t.describe('BBBB', function() {
t.afterEach(function() {})
t.describe('CCCC', function() {
t.test('c1', function() { })
t.test('c2', function() { })
})
t.describe('DDDD', function() {
t.test('d1', function() { })
})
t.test('AAAA', function() { })
})
await t.run()
let total = 0
for (let row of t.process.stdout.write.calls) {
if (row.filter(x => x).join(' ').match(/\[32m\./)) {
total++
}
}
assert.strictEqual(total, 4)
})
})
}) })
let commonBeforeTests = ['before', 'beforeEach'] let commonBeforeTests = ['before', 'beforeEach']

View File

@ -234,7 +234,6 @@ e.test('Eltro should support capturing unknown errors outside scope', async func
t.describe('', function() { t.describe('', function() {
t.test('', function(cb) { t.test('', function(cb) {
process.nextTick(function() { process.nextTick(function() {
console.log('throw')
throw assertError throw assertError
}) })
}) })

41
test/kill/kill.test.mjs Normal file
View File

@ -0,0 +1,41 @@
import { spawn } from 'child_process'
import t from '../../lib/eltro.mjs'
import assert from '../../lib/assert.mjs'
import kill from '../../lib/kill.mjs'
t.describe('kill', function() {
let worker
t.afterEach(function() {
if (worker?.pid && !worker.killed) {
worker.kill()
}
})
t.test('should kill process correctly', function(done) {
worker = spawn('node', ['./test/kill/runner.mjs'])
assert.ok(worker.pid)
worker.on('exit', done.finish(function(code, signal) {
assert.ok(code || signal)
}))
kill(worker.pid)
})
t.test('should succeed in killing tree', async function() {
worker = spawn('node', ['./test/kill/runner.mjs'])
assert.ok(worker.pid)
// Give it some time to start
await new Promise(res => {
worker.stdout.on('data', function(data) {
if (data.toString().indexOf('secondary') >= 0) res()
})
})
return kill(worker.pid).then(function(pids) {
assert.strictEqual(pids.size, 2)
})
})
})

11
test/kill/runner.mjs Normal file
View File

@ -0,0 +1,11 @@
import { spawn } from 'child_process'
console.log('primary', process.pid)
let secondary = spawn('node', ['./test/kill/second_runner.mjs'])
secondary.stdout.on('data', function(data) {
process.stdout.write(data)
})
setInterval(function() { console.log('primary', process.pid) }, 100)

View File

@ -0,0 +1,2 @@
console.log('secondary', process.pid)
setInterval(function() { console.log('secondary', process.pid) }, 100)

View File

@ -28,7 +28,7 @@ t.afterEach(function(done) {
t.after(function() { t.after(function() {
if (builder) { if (builder) {
builder.cleanup() return builder.cleanup()
} }
}) })

View File

@ -173,7 +173,7 @@ Builder.prototype.newRandomFiles = function(fpath, count) {
} }
Builder.prototype.cleanup = function() { Builder.prototype.cleanup = function() {
return fs.rmdir(this.root) return fs.rm(this.root, { recursive: true, force: true })
} }
Builder.prototype.getAllDirectories = function() { Builder.prototype.getAllDirectories = function() {