Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
a67479f4bc | |||
17d7bb862c | |||
15d1ba43f4 | |||
8fad1b45b1 | |||
a70d64e624 | |||
18f9806135 | |||
264364a152 | |||
feac2678bd | |||
b0874cfaa1 | |||
6aaf0533d7 | |||
b7866113f0 | |||
5c9ead16b6 | |||
25f50483e1 | |||
c09a4c805e | |||
b476d23a77 | |||
fe2f6ccca9 | |||
b47fa2b068 | |||
bd478e7753 |
30 changed files with 4148 additions and 386 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -102,3 +102,5 @@ dist
|
||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
|
|
||||||
|
test/watch/__TREE__
|
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
53
README.md
53
README.md
|
@ -65,13 +65,57 @@ $ npm test
|
||||||
1 passing (3ms)
|
1 passing (3ms)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Watch
|
||||||
|
|
||||||
|
You can also run eltro in watch mode. Update your package.json and add the following:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
/* ... */
|
||||||
|
"scripts": {
|
||||||
|
"test": "eltro",
|
||||||
|
"test:watch": "eltro --watch my_watch_name",
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"my_watch_name": {
|
||||||
|
"patterns": [ "src", "test" ],
|
||||||
|
"extensions": "js,mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add `--watch my_watch_name` to your eltro command (as seen in the above example) and you're good to go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm test:watch
|
||||||
|
|
||||||
|
|
||||||
|
test/test.mjs
|
||||||
|
√ Array #indexOf() should return -1 when value is not present
|
||||||
|
|
||||||
|
|
||||||
|
1 passing (3ms)
|
||||||
|
|
||||||
|
[my_watch_name] 09:49:38: Ran successfully. Waiting for file changes before running again...
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run your own npm command while using the eltro file watcher like so:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ eltro --watch my_watch_name --npm build
|
||||||
|
```
|
||||||
|
|
||||||
# Assertions
|
# Assertions
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
@ -458,6 +502,15 @@ let err = await assert.isRejected(() => { throw new Error('hello') }) // ok
|
||||||
assert.strictEqual(err.message, 'hello')
|
assert.strictEqual(err.message, 'hello')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### assert.throwsAndCatch(fn[, message])
|
||||||
|
|
||||||
|
Tests to make sure the function throws an exception. The important feature is this returns the original error that was thrown.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let err = assert.throwsAndCatch(() => { throw new Error('hello world') }) // ok
|
||||||
|
assert.strictEqual(err.message, 'hello world')
|
||||||
|
```
|
||||||
|
|
||||||
# Sinon-like spy() stub()
|
# Sinon-like spy() stub()
|
||||||
|
|
||||||
Using sinon-inspired mechanics for spying on calls as well as being able
|
Using sinon-inspired mechanics for spying on calls as well as being able
|
||||||
|
|
74
cli.mjs
74
cli.mjs
|
@ -6,15 +6,6 @@ const [,, ...args] = process.argv
|
||||||
import e from './lib/eltro.mjs'
|
import e from './lib/eltro.mjs'
|
||||||
import { CLI, printError } from './lib/cli.mjs'
|
import { CLI, printError } from './lib/cli.mjs'
|
||||||
|
|
||||||
e.begin()
|
|
||||||
|
|
||||||
const cli = new CLI(e)
|
|
||||||
cli.parseOptions(args)
|
|
||||||
|
|
||||||
if (cli.errored) {
|
|
||||||
PrintHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
function PrintHelp() {
|
function PrintHelp() {
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('Usage: eltro <options> <files>')
|
console.log('Usage: eltro <options> <files>')
|
||||||
|
@ -25,6 +16,8 @@ function PrintHelp() {
|
||||||
console.log(' Supported reporters: list, dot')
|
console.log(' Supported reporters: list, dot')
|
||||||
console.log(' -t, --timeout - Specify the timeout for tests in ms.')
|
console.log(' -t, --timeout - Specify the timeout for tests in ms.')
|
||||||
console.log(' Default value is 2000ms')
|
console.log(' Default value is 2000ms')
|
||||||
|
console.log(' -w, --watch - specify which group of files to watch from package.json')
|
||||||
|
console.log(' ')
|
||||||
console.log(' --ignore-only - Specify to ignore any .only() tests found')
|
console.log(' --ignore-only - Specify to ignore any .only() tests found')
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('eltro test/mytest.mjs')
|
console.log('eltro test/mytest.mjs')
|
||||||
|
@ -34,43 +27,52 @@ function PrintHelp() {
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.processTargets().then(function() {
|
function showErrorAndExit(message = '', err = null, code = 1, clean = false) {
|
||||||
if (!cli.files.length) {
|
if (!clean) {
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('No files were found with pattern', cli.targets.join(','))
|
}
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
console.error(`\x1b[31m${message}\x1b[0m`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
printError(err, '', clean)
|
||||||
|
if (err.inner) {
|
||||||
|
return showErrorAndExit(null, err.inner, code, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
PrintHelp()
|
PrintHelp()
|
||||||
}
|
}
|
||||||
return cli.loadFiles()
|
process.exit(code)
|
||||||
.then(function() {
|
}
|
||||||
e.reporter = cli.reporter
|
|
||||||
e.ignoreOnly = cli.ignoreOnly
|
|
||||||
e.__timeout = cli.timeout
|
|
||||||
|
|
||||||
return e.run()
|
const cli = new CLI(e)
|
||||||
.catch(function(err) {
|
cli.parseOptions(args)
|
||||||
console.log('')
|
.catch(function(err) { showErrorAndExit(err.message) })
|
||||||
console.error('\x1b[31mUnknown error occured while running the tests\x1b[0m')
|
.then(function() {
|
||||||
printError(err)
|
return cli.startWatcher()
|
||||||
process.exit(1)
|
|
||||||
})
|
})
|
||||||
}, function(err) {
|
.catch(function(err) { showErrorAndExit('Unknown error while starting watcher', err) })
|
||||||
console.log('')
|
.then(function() {
|
||||||
console.error('\x1b[31m' + err.message + '\x1b[0m')
|
return cli.getFiles()
|
||||||
printError(err.inner)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
})
|
||||||
}, function(err) {
|
.catch(function(err) { showErrorAndExit('Unknown error while processing arguments', err) })
|
||||||
console.log('')
|
.then(function() {
|
||||||
console.error('\x1b[31mUnknown error while processing arguments\x1b[0m')
|
if (!cli.files.length && cli.run === 'test') {
|
||||||
printError(err)
|
showErrorAndExit('No files were found with pattern', cli.targets.join(','))
|
||||||
process.exit(1)
|
}
|
||||||
|
|
||||||
|
return cli.loadFiles()
|
||||||
})
|
})
|
||||||
|
.catch(function(err) { showErrorAndExit('', err) })
|
||||||
|
.then(function() {
|
||||||
|
return cli.beginRun()
|
||||||
|
})
|
||||||
|
.catch(function(err) { showErrorAndExit('Unknown error occured while running the tests', err) })
|
||||||
.then(function(stats) {
|
.then(function(stats) {
|
||||||
if (stats.failed > 0) {
|
if (stats.failed > 0) {
|
||||||
process.exit(10)
|
process.exit(10)
|
||||||
}
|
}
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}, function(err) {
|
|
||||||
console.error('\x1b[31mInternal error occured:\x1b[0m', err)
|
|
||||||
process.exit(2)
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -48,6 +48,23 @@ assert.notMatch = (value, test, message) => {
|
||||||
fail(m);
|
fail(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.throwsAndCatch = (fn, message) => {
|
||||||
|
let err = null
|
||||||
|
assert.throws(fn, function(error) {
|
||||||
|
err = error
|
||||||
|
return true
|
||||||
|
}, message)
|
||||||
|
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)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export function runWithCallbackSafe(test) {
|
export function runWithCallbackSafe(test) {
|
||||||
|
let finished = false
|
||||||
return new Promise(function(res, rej) {
|
return new Promise(function(res, rej) {
|
||||||
try {
|
try {
|
||||||
let cb = function(err) {
|
let cb = function(err) {
|
||||||
|
@ -7,27 +8,30 @@ export function runWithCallbackSafe(test) {
|
||||||
}
|
}
|
||||||
res()
|
res()
|
||||||
}
|
}
|
||||||
let safeWrap = function(finish) {
|
let safeWrap = function(fn, ...args) {
|
||||||
// return a safe wrap support
|
|
||||||
return function(fun) {
|
|
||||||
return function(a, b, c) {
|
|
||||||
try {
|
try {
|
||||||
fun(a, b, c)
|
return fn(...args)
|
||||||
if (finish) {
|
|
||||||
res()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
return rej(err)
|
return rej(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let safeWrapInFunction = function(finish, fn) {
|
||||||
|
return function(...args) {
|
||||||
|
safeWrap(fn, ...args)
|
||||||
|
if (finish && !finished) { res() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb.wrap = safeWrap(false)
|
cb.wrap = safeWrapInFunction.bind(this, false)
|
||||||
cb.finish = safeWrap(true)
|
cb.finish = safeWrapInFunction.bind(this, true)
|
||||||
|
cb.safeWrap = safeWrap
|
||||||
test.func(cb)
|
test.func(cb)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
rej(err)
|
rej(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(
|
||||||
|
function() { finished = true },
|
||||||
|
function(err) { finished = true; return Promise.reject(err) }
|
||||||
|
)
|
||||||
}
|
}
|
304
lib/cli.mjs
304
lib/cli.mjs
|
@ -1,58 +1,189 @@
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import fsPromise from 'fs/promises'
|
||||||
|
import cluster from 'cluster'
|
||||||
|
import child_process from 'child_process'
|
||||||
|
import kill from './kill.mjs'
|
||||||
|
import Watcher, { EVENT_REMOVE, EVENT_UPDATE } from './watch/index.mjs'
|
||||||
|
|
||||||
export function CLI(e) {
|
export const MESSAGE_FILES_REQUEST = 'message_files_request'
|
||||||
|
export const MESSAGE_FILES_PAYLOAD = 'message_files_payload'
|
||||||
|
export const MESSAGE_RUN_FINISHED = 'message_run_finished'
|
||||||
|
|
||||||
|
export function createMessage(type, data = null) {
|
||||||
|
return {
|
||||||
|
messageType: type,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegexCache = new Map()
|
||||||
|
|
||||||
|
function targetToRegex(target) {
|
||||||
|
let regex = RegexCache.get(target)
|
||||||
|
if (!regex) {
|
||||||
|
let parsed = target.startsWith('./') ? target.slice(2) : target
|
||||||
|
parsed = parsed.endsWith('/') ? parsed + '*' : parsed
|
||||||
|
RegexCache.set(target, regex = new RegExp('^' +
|
||||||
|
parsed.replace(/\./g, '\\.')
|
||||||
|
.replace(/\*\*\/?/g, '&&&&&&&&&&&&&')
|
||||||
|
.replace(/\*/g, '[^/]+')
|
||||||
|
.replace(/&&&&&&&&&&&&&/g, '.*')
|
||||||
|
+ '$'
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return regex
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CLI(e, overrides = {}) {
|
||||||
this.e = e
|
this.e = e
|
||||||
|
this.ac = new AbortController()
|
||||||
|
this.cluster = overrides.cluster || cluster
|
||||||
|
this.logger = overrides.logger || console
|
||||||
|
this.child_process = overrides.child_process || child_process
|
||||||
|
this.process = overrides.process || process
|
||||||
|
this.kill = overrides.kill || kill
|
||||||
|
this.importer = overrides.importer
|
||||||
|
this.loadDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype.loadDefaults = function() {
|
||||||
|
// Eltro specific options
|
||||||
this.reporter = 'list'
|
this.reporter = 'list'
|
||||||
this.ignoreOnly = false
|
this.ignoreOnly = false
|
||||||
this.timeout = 2000
|
this.timeout = 2000
|
||||||
|
|
||||||
|
// Cli specific options
|
||||||
|
this.watch = null
|
||||||
|
this.watcher = null
|
||||||
|
this.worker = null
|
||||||
|
this.run = 'test'
|
||||||
|
this.isSlave = this.cluster.isWorker || false
|
||||||
this.targets = ['test/**']
|
this.targets = ['test/**']
|
||||||
this.files = []
|
this.files = []
|
||||||
this.errored = false
|
}
|
||||||
|
|
||||||
|
CLI.prototype.fileMatchesTarget = function(path) {
|
||||||
|
for (let target of this.targets) {
|
||||||
|
if (targetToRegex(target).test(path)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI.prototype.parseOptions = function(args) {
|
CLI.prototype.parseOptions = function(args) {
|
||||||
if (!args || !args.length) {
|
if (!args || !args.length) {
|
||||||
this.targets.push('test/**')
|
this.targets.push('test/**')
|
||||||
this.errored = false
|
return Promise.resolve()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errored = false
|
|
||||||
this.targets.splice(0, this.targets.length)
|
this.targets.splice(0, this.targets.length)
|
||||||
|
|
||||||
|
|
||||||
for (let i = 0; i < args.length; i++) {
|
for (let i = 0; i < args.length; i++) {
|
||||||
if (args[i] === '-r' || args[i] === '--reporter') {
|
if (args[i] === '-r' || args[i] === '--reporter') {
|
||||||
if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) {
|
if (!args[i + 1] || (args[i + 1] !== 'list' && args[i + 1] !== 'dot')) {
|
||||||
this.errored = true
|
return Promise.reject(new Error('Reporter was missing or invalid. Only "list" and "dot" are supported.'))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.reporter = args[i + 1]
|
this.reporter = args[i + 1]
|
||||||
i++
|
i++
|
||||||
} else if (args[i] === '-t' || args[i] === '--timeout') {
|
} else if (args[i] === '-t' || args[i] === '--timeout') {
|
||||||
if (!args[i + 1] || isNaN(Number(args[i + 1]))) {
|
if (!args[i + 1] || isNaN(Number(args[i + 1]))) {
|
||||||
this.errored = true
|
return Promise.reject(new Error('Timeout was missing or invalid'))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
this.timeout = Number(args[i + 1])
|
this.timeout = Number(args[i + 1])
|
||||||
i++
|
i++
|
||||||
|
} else if (args[i] === '-w' || args[i] === '--watch') {
|
||||||
|
if (!args[i + 1] || args[i + 1][0] === '-') {
|
||||||
|
return Promise.reject(new Error('Watch was missing or invalid'))
|
||||||
|
}
|
||||||
|
this.watch = args[i + 1]
|
||||||
|
i++
|
||||||
|
} else if (args[i] === '-n' || args[i] === '--npm') {
|
||||||
|
if (!args[i + 1] || args[i + 1][0] === '-') {
|
||||||
|
return Promise.reject(new Error('Npm was missing or invalid'))
|
||||||
|
}
|
||||||
|
this.run = args[i + 1]
|
||||||
|
i++
|
||||||
} else if (args[i] === '--ignore-only') {
|
} else if (args[i] === '--ignore-only') {
|
||||||
this.ignoreOnly = true
|
this.ignoreOnly = true
|
||||||
} else if (args[i][0] === '-') {
|
} else if (args[i][0] === '-') {
|
||||||
this.errored = true
|
return Promise.reject(new Error(`Unknown option ${args[i]}`))
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
this.targets.push(args[i])
|
this.targets.push(args[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.targets.length) {
|
if (!this.targets.length && this.run === 'test') {
|
||||||
this.targets.push('test/**')
|
this.targets.push('test/**')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype.startWatcher = async function() {
|
||||||
|
if (!this.watch || this.isSlave) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
let packageJson
|
||||||
|
try {
|
||||||
|
packageJson = JSON.parse(await fsPromise.readFile('package.json'))
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`package.json was missing or invalid JSON: ${err.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentGroup = packageJson.watch && packageJson.watch[this.watch]
|
||||||
|
|
||||||
|
if (!currentGroup || !currentGroup.patterns) {
|
||||||
|
throw new Error(`package.json was missing watch property or missing ${this.watch} in watch or missing pattern`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentGroup.extensions) {
|
||||||
|
currentGroup.extensions = 'js,mjs'
|
||||||
|
} else if (!currentGroup.extensions.match(/^([a-zA-Z]{2,3})(,[a-zA-Z]{2,3})*$/)) {
|
||||||
|
throw new Error(`package.json watch ${this.watch} extension "${currentGroup.extensions}" was invalid`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
this.watcher = new Watcher(currentGroup.patterns, {
|
||||||
|
quickNativeCheck: true,
|
||||||
|
delay: currentGroup.delay || 200,
|
||||||
|
skip: function(name) {
|
||||||
|
return name.indexOf('node_modules') >= 0
|
||||||
|
},
|
||||||
|
filter: new RegExp(currentGroup.extensions.split(',').map(x => `(\\.${x}$)`).join('|'))
|
||||||
|
})
|
||||||
|
this.watcher.once('error', rej)
|
||||||
|
this.watcher.once('ready', () => {
|
||||||
|
this.watcher.off('error', rej)
|
||||||
|
res()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype.getFiles = function() {
|
||||||
|
if (this.isSlave) {
|
||||||
|
return this._askMasterForFiles()
|
||||||
|
} else {
|
||||||
|
return this._processTargets()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI.prototype.processTargets = function() {
|
CLI.prototype._askMasterForFiles = function() {
|
||||||
|
return new Promise(res => {
|
||||||
|
const handler = (payload) => {
|
||||||
|
if (isMessageInvalid(payload, MESSAGE_FILES_PAYLOAD)) return
|
||||||
|
this.process.off('message', handler)
|
||||||
|
res(this.files = payload.data)
|
||||||
|
}
|
||||||
|
this.process.on('message', handler)
|
||||||
|
this.process.send(createMessage(MESSAGE_FILES_REQUEST))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype._processTargets = function() {
|
||||||
this.files.splice(0, this.files.length)
|
this.files.splice(0, this.files.length)
|
||||||
|
|
||||||
if (!this.targets.length) {
|
if (!this.targets.length) {
|
||||||
|
@ -60,22 +191,28 @@ CLI.prototype.processTargets = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(this.targets.map((target) => {
|
return Promise.all(this.targets.map((target) => {
|
||||||
return getFiles(this.files, target)
|
return getFilesFromTarget(this.files, target)
|
||||||
})).then(() => {
|
})).then(() => {
|
||||||
if (!this.files.length) {
|
if (!this.files.length) {
|
||||||
this.errored = 'empty'
|
this.errored = 'empty'
|
||||||
}
|
}
|
||||||
|
return this.files
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
CLI.prototype.loadFiles = async function() {
|
CLI.prototype.loadFiles = async function() {
|
||||||
let cwd = process.cwd()
|
if (!this.isSlave && this.watch) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
this.e.begin()
|
||||||
|
|
||||||
|
let cwd = this.process.cwd()
|
||||||
|
|
||||||
for (let i = 0; i < this.files.length; i++) {
|
for (let i = 0; i < this.files.length; i++) {
|
||||||
if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) {
|
if (this.files[i].endsWith('.mjs') || this.files[i].endsWith('.js')) {
|
||||||
try {
|
try {
|
||||||
this.e.setFilename(this.files[i])
|
this.e.setFilename(this.files[i])
|
||||||
await import('file:///' + path.join(cwd, this.files[i]))
|
await this.import('file:///' + path.join(cwd, this.files[i]))
|
||||||
this.e.resetFilename()
|
this.e.resetFilename()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let newError = new Error(`Error while loading ${this.files[i]}`)
|
let newError = new Error(`Error while loading ${this.files[i]}`)
|
||||||
|
@ -86,6 +223,131 @@ CLI.prototype.loadFiles = async function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CLI.prototype.beginRun = async function() {
|
||||||
|
if (this.watcher) {
|
||||||
|
return this._runWorker()
|
||||||
|
} else {
|
||||||
|
return this._runTests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype._runTests = function() {
|
||||||
|
this.e.reporter = this.reporter
|
||||||
|
this.e.ignoreOnly = this.ignoreOnly
|
||||||
|
this.e.__timeout = this.timeout
|
||||||
|
|
||||||
|
return this.e.run().then((stats) => {
|
||||||
|
if (this.isSlave) {
|
||||||
|
this.process.send(createMessage(MESSAGE_RUN_FINISHED, { stats: stats }))
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype._runWorker = function() {
|
||||||
|
let lastStats = null
|
||||||
|
|
||||||
|
const messageHandler = (payload) => {
|
||||||
|
if (isMessageInvalid(payload, MESSAGE_RUN_FINISHED)) return
|
||||||
|
lastStats = payload.data.stats
|
||||||
|
}
|
||||||
|
const changeHandler = (evt, name) => {
|
||||||
|
if (evt === EVENT_UPDATE && !this.files.includes(name) && this.fileMatchesTarget(name)) {
|
||||||
|
this.files.push(name)
|
||||||
|
} else if (evt === EVENT_REMOVE) {
|
||||||
|
let index = this.files.indexOf(name)
|
||||||
|
if (index >= 0) {
|
||||||
|
this.files.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const changedHandler = () => {
|
||||||
|
this.runProgram()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(res => {
|
||||||
|
const cleanup = () => {
|
||||||
|
this.process.off('message', messageHandler)
|
||||||
|
this.watcher.off('change', changeHandler)
|
||||||
|
this.watcher.off('changed', changedHandler)
|
||||||
|
res(lastStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.process.on('message', messageHandler)
|
||||||
|
this.watcher.on('change', changeHandler)
|
||||||
|
this.watcher.on('changed', changedHandler)
|
||||||
|
|
||||||
|
this.ac.signal.addEventListener('abort', cleanup, { once: true });
|
||||||
|
changedHandler()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype.runProgram = function() {
|
||||||
|
let runningTest = this.run === 'test'
|
||||||
|
|
||||||
|
if (this.worker) {
|
||||||
|
if (runningTest) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
this.kill(this.worker.pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let worker
|
||||||
|
if (runningTest) {
|
||||||
|
worker = this.worker = this.cluster.fork()
|
||||||
|
} else {
|
||||||
|
worker = this.worker = this.child_process.spawn('npm', ['run', this.run])
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.once('exit', (exitCode) => {
|
||||||
|
if (this.worker !== worker) return
|
||||||
|
|
||||||
|
let currentTime = new Date().toISOString().split('T')[1].split('.')[0]
|
||||||
|
if (!runningTest) {
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
if (exitCode > 0) {
|
||||||
|
console.error(`\x1b[31m[${this.watch}] ${currentTime}: Exited with error code ${exitCode}. Waiting for file changes before running again...\x1b[0m`)
|
||||||
|
} else {
|
||||||
|
console.error(`\x1b[32m[${this.watch}] ${currentTime}: Ran successfully. Waiting for file changes before running again...\x1b[0m`)
|
||||||
|
}
|
||||||
|
this.worker = null
|
||||||
|
})
|
||||||
|
if (runningTest) {
|
||||||
|
worker.on('message', (msg) => {
|
||||||
|
if (isMessageValid(msg, MESSAGE_FILES_REQUEST)) {
|
||||||
|
worker.send(createMessage(MESSAGE_FILES_PAYLOAD, this.files))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
worker.stdout.on('data', (d) => { this.process.stdout.write(d.toString()) })
|
||||||
|
worker.stderr.on('data', (d) => { this.process.stderr.write(d.toString()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI.prototype.import = function(path) {
|
||||||
|
if (this.importer) {
|
||||||
|
return this.importer(path)
|
||||||
|
}
|
||||||
|
return import(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMessageInvalid(payload, messageType) {
|
||||||
|
if (!payload
|
||||||
|
|| typeof(payload) !== 'object'
|
||||||
|
|| typeof(payload.messageType) !== 'string'
|
||||||
|
|| payload.messageType !== messageType
|
||||||
|
|| (payload.data != null && typeof(payload.data) !== 'object')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMessageValid(payload, messageType) {
|
||||||
|
return !isMessageInvalid(payload, messageType)
|
||||||
|
}
|
||||||
|
|
||||||
function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, includeFiles) {
|
function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, includeFiles) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
return fs.readdir(curr, function(err, data) {
|
return fs.readdir(curr, function(err, data) {
|
||||||
|
@ -105,7 +367,7 @@ function traverseFolder(files, curr, match, insidePath, grabAll, insideStar, inc
|
||||||
if (stat.isDirectory() && grabAll) {
|
if (stat.isDirectory() && grabAll) {
|
||||||
return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles))
|
return res(traverseFolder(files, path.join(curr, file), match, path.join(insidePath, file), grabAll, insideStar, includeFiles))
|
||||||
} else if (stat.isDirectory() && match) {
|
} else if (stat.isDirectory() && match) {
|
||||||
return res(getFiles(files, match, path.join(insidePath, file), grabAll, insideStar))
|
return res(getFilesFromTarget(files, match, path.join(insidePath, file), grabAll, insideStar))
|
||||||
}
|
}
|
||||||
res(null)
|
res(null)
|
||||||
})
|
})
|
||||||
|
@ -119,7 +381,7 @@ export function fileMatches(filename, match) {
|
||||||
return Boolean(filename.match(new RegExp(match.replace(/\./, '\\.').replace(/\*/, '.*'))))
|
return Boolean(filename.match(new RegExp(match.replace(/\./, '\\.').replace(/\*/, '.*'))))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFiles(files, match, insidePath, grabAll, insideStar) {
|
export function getFilesFromTarget(files, match, insidePath, grabAll, insideStar) {
|
||||||
let isGrabbingAll = grabAll || false
|
let isGrabbingAll = grabAll || false
|
||||||
let isStarred = insideStar || false
|
let isStarred = insideStar || false
|
||||||
let cwd = process.cwd()
|
let cwd = process.cwd()
|
||||||
|
@ -148,7 +410,7 @@ export function getFiles(files, match, insidePath, grabAll, insideStar) {
|
||||||
return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false)
|
return traverseFolder(files, curr, splitted.slice(start + 1).join('/'), currPath, isGrabbingAll, isStarred, false)
|
||||||
.then(res, rej)
|
.then(res, rej)
|
||||||
}
|
}
|
||||||
return getFiles(files, splitted.slice(start + 1).join('/'), path.join(currPath, first), grabAll, isStarred)
|
return getFilesFromTarget(files, splitted.slice(start + 1).join('/'), path.join(currPath, first), grabAll, isStarred)
|
||||||
.then(res, rej)
|
.then(res, rej)
|
||||||
} else if (first.indexOf('*') >= 0) {
|
} else if (first.indexOf('*') >= 0) {
|
||||||
if (first === '**') {
|
if (first === '**') {
|
||||||
|
@ -178,9 +440,9 @@ export function getFiles(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'
|
||||||
|
|
47
lib/eltro.d.ts
vendored
Normal file
47
lib/eltro.d.ts
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
declare interface Stats {
|
||||||
|
passed: number
|
||||||
|
failed: number
|
||||||
|
skipped: number
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Eltro {
|
||||||
|
__timeout: number
|
||||||
|
hasExclusive: boolean
|
||||||
|
reporter: 'dot' | 'list'
|
||||||
|
Eltro: Eltro
|
||||||
|
fileGroupMap: Map<string, Group>
|
||||||
|
groups: Array<Group>
|
||||||
|
activeGroup: Group | null
|
||||||
|
failedTests: Array<Test>
|
||||||
|
hasTests: boolean
|
||||||
|
starting: boolean
|
||||||
|
ignoreOnly: boolean
|
||||||
|
logger: null | { log: function(...param): void }
|
||||||
|
filename: string
|
||||||
|
prefix: string
|
||||||
|
temporary: { timeout: number, skip: boolean, only: boolean }
|
||||||
|
describeTemporary: { timeout: number, skip: boolean, only: boolean }
|
||||||
|
|
||||||
|
__runTest(stats: Stats, test: Test, prefix: string = 'Test', child?: Test | null = null)
|
||||||
|
__runGroup(group: Group, stats: Stats)
|
||||||
|
begin()
|
||||||
|
run()
|
||||||
|
setFilename(filename: string)
|
||||||
|
resetFilename(filename: string)
|
||||||
|
|
||||||
|
before(fn: (done?: Function) => void | Promise)
|
||||||
|
after(fn: (done?: Function) => void | Promise)
|
||||||
|
beforeEach(fn: (done?: Function) => void | Promise)
|
||||||
|
afterEach(fn: (done?: Function) => void | Promise)
|
||||||
|
describe(name: string, fn: Function)
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Test {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class Group {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Eltro()
|
186
lib/eltro.mjs
186
lib/eltro.mjs
|
@ -1,3 +1,4 @@
|
||||||
|
/// <reference path="./eltro.d.ts"/>
|
||||||
import * as readline from 'readline'
|
import * as readline from 'readline'
|
||||||
import { runWithCallbackSafe } from './callback.mjs'
|
import { runWithCallbackSafe } from './callback.mjs'
|
||||||
import { printError } from './cli.mjs'
|
import { printError } from './cli.mjs'
|
||||||
|
@ -63,6 +64,13 @@ function Test(e, group, name, func) {
|
||||||
this.name = name
|
this.name = name
|
||||||
this.func = func
|
this.func = func
|
||||||
this.error = null
|
this.error = null
|
||||||
|
this.startTime = null
|
||||||
|
this.totalTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test.prototype.calculateTime = function() {
|
||||||
|
let end = process.hrtime(this.startTime)
|
||||||
|
this.totalTime = (end[0] * 1000 + Math.round(end[1] / 1000000))
|
||||||
}
|
}
|
||||||
|
|
||||||
Test.prototype.timeout = function(time) {
|
Test.prototype.timeout = function(time) {
|
||||||
|
@ -89,7 +97,27 @@ Test.prototype.clone = function(prefix = '') {
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stack = []
|
||||||
|
function captureUnknownErrors(e) {
|
||||||
|
stack.push(e)
|
||||||
|
}
|
||||||
|
function cancelCaptureUnknown(e) {
|
||||||
|
stack.splice(stack.indexOf(e), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('uncaughtException', function(err) {
|
||||||
|
for (let i = stack.length - 1; i >= 0; i--) {
|
||||||
|
if (stack[i].captureOutsideExceptions) {
|
||||||
|
stack[i].captureOutsideExceptions(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.error('-- UNCAUGHT EXCPEPTION OUTSIDE OF TEST RUNNER --')
|
||||||
|
console.error(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'
|
||||||
|
@ -99,7 +127,7 @@ function Eltro() {
|
||||||
this.activeGroup = null
|
this.activeGroup = null
|
||||||
this.failedTests = []
|
this.failedTests = []
|
||||||
this.hasTests = false
|
this.hasTests = false
|
||||||
this.starting = false
|
this.starting = null
|
||||||
this.ignoreOnly = false
|
this.ignoreOnly = false
|
||||||
this.logger = null
|
this.logger = null
|
||||||
this.filename = ''
|
this.filename = ''
|
||||||
|
@ -114,29 +142,39 @@ function Eltro() {
|
||||||
skip: false,
|
skip: false,
|
||||||
only: false
|
only: false
|
||||||
}
|
}
|
||||||
|
this.captureOutsideExceptions = null
|
||||||
}
|
}
|
||||||
|
|
||||||
Eltro.prototype.begin = function() {
|
Eltro.prototype.begin = function() {
|
||||||
if (this.starting) {
|
if (this.starting) {
|
||||||
console.warn('WARNING: Multiple calls to Eltro.begin were done.')
|
console.warn('WARNING: Multiple calls to Eltro.begin were done.')
|
||||||
|
console.warn(this.starting)
|
||||||
|
console.warn(new Error('Second call'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.hasTests = false
|
this.hasTests = false
|
||||||
this.starting = true
|
this.starting = new Error('First call')
|
||||||
this.filename = ''
|
this.filename = ''
|
||||||
this.prefix = ''
|
this.prefix = ''
|
||||||
this.fileGroupMap.clear()
|
this.fileGroupMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if (!test.skipTest) {
|
if (!test.skipTest) {
|
||||||
let err = await new Promise((resolve, reject) => {
|
let err = await new Promise((resolve, reject) => {
|
||||||
|
if (test.error) {
|
||||||
|
return reject(test.error)
|
||||||
|
}
|
||||||
|
if (!test.func) {
|
||||||
|
return reject(new Error(`Test ${test.name} was missing function`))
|
||||||
|
}
|
||||||
|
this.captureOutsideExceptions = reject
|
||||||
// Flag to check if we finished
|
// Flag to check if we finished
|
||||||
let finished = false
|
let finished = false
|
||||||
let timeout = test.customTimeout || this.__timeout
|
let timeout = test.customTimeout || this.__timeout
|
||||||
|
@ -144,6 +182,8 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// Timeout timer in case test times out
|
// Timeout timer in case test times out
|
||||||
let timer = setTimeout(function() {
|
let timer = setTimeout(function() {
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
reject(new Error('timeout of ' + timeout + 'ms exceeded. Ensure the done() callback is being called in this test.'))
|
reject(new Error('timeout of ' + timeout + 'ms exceeded. Ensure the done() callback is being called in this test.'))
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
|
@ -153,6 +193,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
let checkIsCallback = (test.func.toString()).match(/^(function)? *\([^\)]+\)/)
|
let checkIsCallback = (test.func.toString()).match(/^(function)? *\([^\)]+\)/)
|
||||||
let promise
|
let promise
|
||||||
|
|
||||||
|
test.startTime = process.hrtime()
|
||||||
// If the test requires callback, wrap it in a promise where callback
|
// If the test requires callback, wrap it in a promise where callback
|
||||||
// either resolves or rejects that promise
|
// either resolves or rejects that promise
|
||||||
if (checkIsCallback) {
|
if (checkIsCallback) {
|
||||||
|
@ -170,6 +211,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
resolve()
|
resolve()
|
||||||
|
@ -177,6 +219,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
reject(err)
|
reject(err)
|
||||||
|
@ -185,6 +228,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
// Possible this was a synchronous test, pass immediately
|
// Possible this was a synchronous test, pass immediately
|
||||||
finished = true
|
finished = true
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
|
@ -194,6 +238,7 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
// check if our test had already finished and if so, do nothing
|
// check if our test had already finished and if so, do nothing
|
||||||
if (finished === true) return
|
if (finished === true) return
|
||||||
|
|
||||||
|
test.calculateTime()
|
||||||
// An error occured while running function. Possible exception
|
// An error occured while running function. Possible exception
|
||||||
// during a synchronous test or something else.
|
// during a synchronous test or something else.
|
||||||
finished = true
|
finished = true
|
||||||
|
@ -232,23 +277,27 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
|
||||||
stats.skipped++
|
stats.skipped++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + '\x1b[0m\n')
|
if (!test.name.startsWith('~')) {
|
||||||
|
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 + '\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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,18 +305,21 @@ 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) {
|
||||||
await this.__runTest(stats, g.before, 'Before')
|
for (let i = 0; i < g.before.length; i++) {
|
||||||
if (g.before.error) return
|
await this.__runTest(stats, g.before[i], 'Before')
|
||||||
|
if (g.before[i].error) return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (let x = 0; x < g.tests.length; x++) {
|
for (let x = 0; x < g.tests.length; x++) {
|
||||||
if (!g.tests[x].skipTest && g.tests[x].isExclusive === g.hasExclusive) {
|
if (!g.tests[x].skipTest && g.tests[x].isExclusive === g.hasExclusive) {
|
||||||
|
|
||||||
if (g.beforeEach) {
|
if (g.beforeEach) {
|
||||||
await this.__runTest(stats, g.beforeEach, 'Before each: ', g.tests[x])
|
for (let i = 0; i < g.beforeEach.length && !g.tests[x].error; i++) {
|
||||||
|
await this.__runTest(stats, g.beforeEach[i], 'Before each: ', g.tests[x])
|
||||||
|
}
|
||||||
if (!g.tests[x].error) {
|
if (!g.tests[x].error) {
|
||||||
await this.__runTest(stats, g.tests[x])
|
await this.__runTest(stats, g.tests[x])
|
||||||
}
|
}
|
||||||
|
@ -275,33 +327,37 @@ Eltro.prototype.__runGroup = async function(g, stats) {
|
||||||
await this.__runTest(stats, g.tests[x])
|
await this.__runTest(stats, g.tests[x])
|
||||||
}
|
}
|
||||||
if (g.afterEach) {
|
if (g.afterEach) {
|
||||||
await this.__runTest(stats, g.afterEach, 'After each: ', g.tests[x])
|
let oldError = g.tests[x].error
|
||||||
|
g.tests[x].error = null
|
||||||
|
for (let i = 0; i < g.afterEach.length && !g.tests[x].error; i++) {
|
||||||
|
await this.__runTest(stats, g.afterEach[i], 'After each: ', g.tests[x])
|
||||||
|
}
|
||||||
|
if (oldError) {
|
||||||
|
g.tests[x].error = oldError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let x = 0; x < g.groups.length; x++) {
|
for (let x = 0; x < g.groups.length; x++) {
|
||||||
if (!g.groups[x].skipTest && g.hasExclusive === (g.groups[x].hasExclusive || g.groups[x].isExclusive)) {
|
if (!g.groups[x].skipTest && g.hasExclusive === (g.groups[x].hasExclusive || g.groups[x].isExclusive)) {
|
||||||
if (g.beforeEach) {
|
|
||||||
await this.__runTest(stats, g.beforeEach, g.groups[x].name + ': ', g.beforeEach)
|
|
||||||
if (g.beforeEach.error) continue
|
|
||||||
}
|
|
||||||
await this.__runGroup(g.groups[x], stats)
|
await this.__runGroup(g.groups[x], stats)
|
||||||
if (g.afterEach) {
|
|
||||||
await this.__runTest(stats, g.afterEach, g.groups[x].name + ': ', g.afterEach)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (g.after) {
|
if (g.after) {
|
||||||
await this.__runTest(stats, g.after, 'After')
|
for (let i = 0; i < g.after.length && !g.after.error; i++) {
|
||||||
|
await this.__runTest(stats, g.after[i], 'After')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
let stats = {
|
let stats = {
|
||||||
passed: 0,
|
passed: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
|
@ -314,36 +370,35 @@ Eltro.prototype.run = async function() {
|
||||||
await this.__runGroup(this.groups[i], stats)
|
await this.__runGroup(this.groups[i], stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let end = process.hrtime(start)
|
let end = process.hrtime(start)
|
||||||
|
|
||||||
|
cancelCaptureUnknown(this)
|
||||||
|
|
||||||
if (this.reporter === 'test') {
|
if (this.reporter === 'test') {
|
||||||
if (this.logger && this.logger.log) {
|
if (this.logger && this.logger.log) {
|
||||||
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];
|
||||||
this.logger.log(test.name, test.error)
|
this.logger.log(test.name, test.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,30 +420,48 @@ Eltro.prototype.resetFilename = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let beforesandafters = [
|
let beforesandafters = [
|
||||||
['before', 'Before'],
|
['before', '~Before', false],
|
||||||
['after', 'After'],
|
['after', '~After', false],
|
||||||
['beforeEach', 'Before each'],
|
['beforeEach', '~Before each', true],
|
||||||
['afterEach', 'After each'],
|
['afterEach', '~After each', true],
|
||||||
]
|
]
|
||||||
|
|
||||||
beforesandafters.forEach(function(item) {
|
beforesandafters.forEach(function(item) {
|
||||||
Eltro.prototype[item[0]] = function(func) {
|
let beforeAfter = item[0]
|
||||||
|
let fullName = item[1]
|
||||||
|
let bringToChildren = item[2]
|
||||||
|
|
||||||
|
Eltro.prototype[beforeAfter] = function(func) {
|
||||||
if (!this.activeGroup) {
|
if (!this.activeGroup) {
|
||||||
throw new Error('Tests outside groups are not allowed.')
|
throw new Error('Tests outside groups are not allowed.')
|
||||||
}
|
}
|
||||||
|
|
||||||
let test = new Test(this, this.activeGroup, item[1] + ': ' + this.activeGroup.name, func)
|
let test = func
|
||||||
|
|
||||||
|
if (!(test instanceof Test)) {
|
||||||
|
test = new Test(this, this.activeGroup, fullName + ': ' + this.activeGroup.name, func)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.temporary.timeout || this.activeGroup.customTimeout) {
|
if (this.temporary.timeout || this.activeGroup.customTimeout) {
|
||||||
test.timeout(this.temporary.timeout || this.activeGroup.customTimeout)
|
test.timeout(this.temporary.timeout || this.activeGroup.customTimeout)
|
||||||
this.temporary.timeout = 0
|
this.temporary.timeout = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeGroup[item[0]] = test
|
this.activeGroup[beforeAfter] = this.activeGroup[beforeAfter] || []
|
||||||
|
this.activeGroup[beforeAfter].push(test)
|
||||||
|
|
||||||
|
if (bringToChildren) {
|
||||||
|
for (let group of this.activeGroup.groups) {
|
||||||
|
group[beforeAfter].push(test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return test
|
return test
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let bringToChildren = ['beforeEach', 'afterEach']
|
||||||
|
|
||||||
Eltro.prototype.describe = function(name, func) {
|
Eltro.prototype.describe = function(name, func) {
|
||||||
let before = this.activeGroup
|
let before = this.activeGroup
|
||||||
|
|
||||||
|
@ -417,6 +490,16 @@ Eltro.prototype.describe = function(name, func) {
|
||||||
this.temporary.only = false
|
this.temporary.only = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (before) {
|
||||||
|
for (let beforeAfter of bringToChildren) {
|
||||||
|
if (!before[beforeAfter]) continue
|
||||||
|
|
||||||
|
for (let test of before[beforeAfter]) {
|
||||||
|
this[beforeAfter](test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func()
|
func()
|
||||||
|
|
||||||
this.activeGroup = before
|
this.activeGroup = before
|
||||||
|
@ -444,9 +527,18 @@ Eltro.prototype.test = function(name, func) {
|
||||||
throw new Error('Tests outside groups are not allowed.')
|
throw new Error('Tests outside groups are not allowed.')
|
||||||
}
|
}
|
||||||
|
|
||||||
let test = new Test(this, this.activeGroup, this.activeGroup.name + ' ' + name, func)
|
let test = new Test(
|
||||||
|
this,
|
||||||
|
this.activeGroup,
|
||||||
|
[this.activeGroup.name.trim(), (name || '').trim()].filter(x => x).join(' '),
|
||||||
|
func
|
||||||
|
)
|
||||||
this.activeGroup.tests.push(test)
|
this.activeGroup.tests.push(test)
|
||||||
|
|
||||||
|
if (name == null) {
|
||||||
|
test.error = new Error(`An empty test or missing name under ${this.activeGroup.name.trim()} was found`)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.temporary.only && !this.temporary.skip) {
|
if (this.temporary.only && !this.temporary.skip) {
|
||||||
test.only()
|
test.only()
|
||||||
this.temporary.only = false
|
this.temporary.only = false
|
||||||
|
|
85
lib/kill.mjs
Normal file
85
lib/kill.mjs
Normal 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))
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
22
lib/watch/LICENSE
Normal file
22
lib/watch/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2012-2021 Yuan Chuan <yuanchuan23@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
118
lib/watch/has-native-recursive.mjs
Normal file
118
lib/watch/has-native-recursive.mjs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import * as is from './is.mjs'
|
||||||
|
|
||||||
|
let IS_SUPPORT
|
||||||
|
let TEMP_DIR = os.tmpdir && os.tmpdir()
|
||||||
|
|| process.env.TMPDIR
|
||||||
|
|| process.env.TEMP
|
||||||
|
|| process.cwd()
|
||||||
|
|
||||||
|
function TempStack() {
|
||||||
|
this.stack = []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TempStack.prototype.create = function(type, base) {
|
||||||
|
let name = path.join(base,
|
||||||
|
'node-watch-' + Math.random().toString(16).substr(2)
|
||||||
|
)
|
||||||
|
this.stack.push({ name: name, type: type })
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
TempStack.prototype.write = function(/* file */) {
|
||||||
|
for (let i = 0; i < arguments.length; ++i) {
|
||||||
|
fs.writeFileSync(arguments[i], ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempStack.prototype.mkdir = function(/* dirs */) {
|
||||||
|
for (let i = 0; i < arguments.length; ++i) {
|
||||||
|
fs.mkdirSync(arguments[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempStack.prototype.cleanup = function(fn) {
|
||||||
|
try {
|
||||||
|
let temp
|
||||||
|
while ((temp = this.stack.pop())) {
|
||||||
|
let type = temp.type
|
||||||
|
let name = temp.name
|
||||||
|
if (type === 'file' && is.file(name)) {
|
||||||
|
fs.unlinkSync(name)
|
||||||
|
}
|
||||||
|
else if (type === 'dir' && is.directory(name)) {
|
||||||
|
fs.rmdirSync(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (is.func(fn)) fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pending = false
|
||||||
|
|
||||||
|
export default function hasNativeRecursive(fn, opts = {}) {
|
||||||
|
if (!is.func(fn)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (IS_SUPPORT !== undefined) {
|
||||||
|
return fn(IS_SUPPORT)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.quickCheck) {
|
||||||
|
return fn(IS_SUPPORT = (process.platform === 'darwin' || process.platform === 'win32'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pending) {
|
||||||
|
pending = true
|
||||||
|
}
|
||||||
|
// check again later
|
||||||
|
else {
|
||||||
|
return setTimeout(function() {
|
||||||
|
hasNativeRecursive(fn)
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
let stack = new TempStack()
|
||||||
|
let parent = stack.create('dir', TEMP_DIR)
|
||||||
|
let child = stack.create('dir', parent)
|
||||||
|
let file = stack.create('file', child)
|
||||||
|
|
||||||
|
stack.mkdir(parent, child)
|
||||||
|
|
||||||
|
let options = { recursive: true }
|
||||||
|
let watcher
|
||||||
|
|
||||||
|
try {
|
||||||
|
watcher = fs.watch(parent, options)
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code == 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
|
||||||
|
return fn(IS_SUPPORT = false)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watcher) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer = setTimeout(function() {
|
||||||
|
watcher.close()
|
||||||
|
stack.cleanup(function() {
|
||||||
|
fn(IS_SUPPORT = false)
|
||||||
|
})
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
watcher.on('change', function(evt, name) {
|
||||||
|
if (path.basename(file) === path.basename(name)) {
|
||||||
|
watcher.close()
|
||||||
|
clearTimeout(timer)
|
||||||
|
stack.cleanup(function() {
|
||||||
|
fn(IS_SUPPORT = true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stack.write(file)
|
||||||
|
}
|
303
lib/watch/index.mjs
Normal file
303
lib/watch/index.mjs
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import fsPromise from 'fs/promises'
|
||||||
|
import path from 'path'
|
||||||
|
import events from 'events'
|
||||||
|
import hasNativeRecursive from './has-native-recursive.mjs'
|
||||||
|
import * as is from './is.mjs'
|
||||||
|
|
||||||
|
export const EVENT_UPDATE = 'update';
|
||||||
|
export const EVENT_REMOVE = 'remove';
|
||||||
|
const TYPE_FILE = 'file'
|
||||||
|
const TYPE_DIRECTORY = 'directory'
|
||||||
|
|
||||||
|
function unique(arr) {
|
||||||
|
return arr.filter(function(v, i, self) {
|
||||||
|
return self.indexOf(v) === i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Watcher extends events.EventEmitter {
|
||||||
|
constructor(path, options = null, fn = null, { fs: fsoverwrite } = {}) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.ac = new AbortController()
|
||||||
|
events.setMaxListeners(2000, this.ac.signal)
|
||||||
|
this._fs = fsoverwrite || fs
|
||||||
|
this._cache = []
|
||||||
|
this._cacheTimeout = null
|
||||||
|
this.listeners = []
|
||||||
|
this.closed = false
|
||||||
|
let paths = path
|
||||||
|
if (is.buffer(paths)) {
|
||||||
|
paths = paths.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is.array(paths)) {
|
||||||
|
paths = [paths]
|
||||||
|
}
|
||||||
|
|
||||||
|
paths = unique(paths)
|
||||||
|
this.options = options || {}
|
||||||
|
this.fn = fn || null
|
||||||
|
this.originalPaths = paths
|
||||||
|
|
||||||
|
if (is.func(this.options)) {
|
||||||
|
this.fn = this.options
|
||||||
|
this.options = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._verifyOptions(paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
isClosed() {
|
||||||
|
return this.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.closed = true
|
||||||
|
this.ac.abort()
|
||||||
|
this._cache = this.listeners = []
|
||||||
|
|
||||||
|
this.emitAsync('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
emitAsync(name, ...args) {
|
||||||
|
process.nextTick(() => this.emit(name, ...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
_verifyOptions(paths) {
|
||||||
|
for (let path of paths) {
|
||||||
|
if (!is.exists(path)) {
|
||||||
|
this.emitAsync('error', new Error(path + ' does not exist.'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.encoding) {
|
||||||
|
if (this.options.encoding && this.options.encoding !== 'buffer' && !Buffer.isEncoding(this.options.encoding)) {
|
||||||
|
throw new Error('Unknown encoding: ' + this.options.encoding);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.encoding = 'utf8'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.delay !== 0 && !this.options.delay) {
|
||||||
|
this.options.delay = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is.func(this.fn)) {
|
||||||
|
this.on('change', this.fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.manualRecursive !== true) {
|
||||||
|
hasNativeRecursive(nativeRecursive => {
|
||||||
|
this.supportsNativeRecursive = nativeRecursive
|
||||||
|
this.options.manualRecursive = !nativeRecursive
|
||||||
|
this._startListeners(paths)
|
||||||
|
}, { quickCheck: this.options.quickNativeCheck || true })
|
||||||
|
} else {
|
||||||
|
this._startListeners(paths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_startListeners(paths) {
|
||||||
|
Promise.all(paths.map(path => this.safeAdd(path)))
|
||||||
|
.then(
|
||||||
|
() => this.emit('ready'),
|
||||||
|
err => this.emit('error', err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getWatcherOrNull(name) {
|
||||||
|
for (let check of this.listeners) {
|
||||||
|
if (check.path === name) {
|
||||||
|
return check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldSkip(name) {
|
||||||
|
return this.options.skip
|
||||||
|
?
|
||||||
|
(is.func(this.options.skip) && this.options.skip.call(this, name))
|
||||||
|
|| (is.regExp(this.options.skip) && this.options.skip.test(name))
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldNotify(name) {
|
||||||
|
return this.options.filter
|
||||||
|
?
|
||||||
|
(is.func(this.options.filter) && this.options.filter.call(this, name))
|
||||||
|
|| (is.regExp(this.options.filter) && this.options.filter.test(name))
|
||||||
|
: true
|
||||||
|
}
|
||||||
|
|
||||||
|
closeWatch(orgItem) {
|
||||||
|
let item = orgItem
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
item = this.getWatcherOrNull(item)
|
||||||
|
}
|
||||||
|
if (!item) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.watcher) {
|
||||||
|
item.watcher.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
this._emitEvent(item, EVENT_REMOVE, item.path)
|
||||||
|
let index = this.listeners.indexOf(item)
|
||||||
|
if (index < 0) return
|
||||||
|
this.listeners.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
_emitEvent(item, evt, name) {
|
||||||
|
if (item.type === TYPE_FILE && !is.samePath(name, item.filename)) return
|
||||||
|
if (item.type === TYPE_DIRECTORY && this.shouldSkip(name)) return
|
||||||
|
if (!this.shouldNotify(name)) return
|
||||||
|
|
||||||
|
if (item.flag) {
|
||||||
|
item.flag = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputName = name
|
||||||
|
|
||||||
|
if (this.options.encoding !== 'utf8') {
|
||||||
|
outputName = Buffer.from(outputName)
|
||||||
|
|
||||||
|
if (this.options.encoding !== 'buffer') {
|
||||||
|
outputName = outputName.toString(this.options.encoding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.options.delay) {
|
||||||
|
this.emit('change', evt, outputName)
|
||||||
|
this.emit('changed', evt, outputName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._cache.push([evt, name, outputName])
|
||||||
|
|
||||||
|
if (this._cacheTimeout) return
|
||||||
|
|
||||||
|
this._cacheTimeout = setTimeout(() => {
|
||||||
|
let cache = this._filterCache(this._cache)
|
||||||
|
this._cache = []
|
||||||
|
this._cacheTimeout = null
|
||||||
|
|
||||||
|
for (let event of cache) {
|
||||||
|
try {
|
||||||
|
this.emit('change', event[0], event[2])
|
||||||
|
} catch (err) {
|
||||||
|
this.emit('error', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit('changed')
|
||||||
|
}, this.options.delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
_filterCache(cache) {
|
||||||
|
let setFound = new Set()
|
||||||
|
|
||||||
|
let out = cache.reverse().filter(([evt, name]) => {
|
||||||
|
if (setFound.has(name)) return false
|
||||||
|
setFound.add(name)
|
||||||
|
return true
|
||||||
|
}).reverse()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_watcherSink(item, rawEvt, rawName, c) {
|
||||||
|
if (this.closed) return
|
||||||
|
|
||||||
|
let name = path.join(item.path, rawName || '')
|
||||||
|
let evt = is.exists(name) ? EVENT_UPDATE : EVENT_REMOVE
|
||||||
|
|
||||||
|
if (this.options.recursive && this.options.manualRecursive && item.type === TYPE_DIRECTORY) {
|
||||||
|
if (evt === EVENT_REMOVE) {
|
||||||
|
this.closeWatch(name)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if (is.directory(name)
|
||||||
|
&& this.getWatcherOrNull(name) === null) {
|
||||||
|
this.safeAdd(name, TYPE_DIRECTORY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._emitEvent(item, evt, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
_pathToItem(name, type) {
|
||||||
|
if (type === TYPE_FILE) {
|
||||||
|
let parent = path.join(name, '../')
|
||||||
|
return {
|
||||||
|
path: parent,
|
||||||
|
type: TYPE_FILE,
|
||||||
|
filename: name,
|
||||||
|
watcher: null,
|
||||||
|
flag: '',
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
path: name,
|
||||||
|
type: TYPE_DIRECTORY,
|
||||||
|
watcher: null,
|
||||||
|
flag: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_watcherError(item, err) {
|
||||||
|
if (this.closed) return
|
||||||
|
|
||||||
|
if (is.windows() && err.code === 'EPERM') {
|
||||||
|
this.closeWatch(item)
|
||||||
|
item.flag = 'windows-error'
|
||||||
|
} else {
|
||||||
|
self.emit('error', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
safeAdd(name, orgType) {
|
||||||
|
let type = orgType
|
||||||
|
if (!type) {
|
||||||
|
type = is.file(name) ? TYPE_FILE : TYPE_DIRECTORY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldSkip(name)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = this._pathToItem(name, type)
|
||||||
|
let options = {
|
||||||
|
encoding: 'utf8',
|
||||||
|
signal: this.ac.signal,
|
||||||
|
}
|
||||||
|
if (!this.options.manualRecursive && item.type !== TYPE_FILE && this.options.recursive) {
|
||||||
|
options.recursive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
item.watcher = this._fs.watch(item.path, options)
|
||||||
|
} catch (err) {
|
||||||
|
this.emitAsync('error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.watcher) return
|
||||||
|
|
||||||
|
this.listeners.push(item)
|
||||||
|
|
||||||
|
item.watcher.on('error', this._watcherError.bind(this, item))
|
||||||
|
item.watcher.on('change', this._watcherSink.bind(this, item))
|
||||||
|
|
||||||
|
if (options.recursive || item.type === TYPE_FILE) return
|
||||||
|
|
||||||
|
return fsPromise.readdir(item.path, { withFileTypes: true })
|
||||||
|
.then(directories => directories.filter(dir => dir.isDirectory()))
|
||||||
|
.then(directories => {
|
||||||
|
return Promise.all(directories.map(dir => this.safeAdd(path.join(item.path, dir.name), TYPE_DIRECTORY)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
74
lib/watch/is.mjs
Normal file
74
lib/watch/is.mjs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import os from 'os'
|
||||||
|
|
||||||
|
function matchObject(item, str) {
|
||||||
|
return Object.prototype.toString.call(item)
|
||||||
|
=== '[object ' + str + ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStat(name, fn) {
|
||||||
|
try {
|
||||||
|
return fn(name)
|
||||||
|
} catch (err) {
|
||||||
|
if (/^(ENOENT|EPERM|EACCES)$/.test(err.code)) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
console.warn('Warning: Cannot access %s', name)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nil(item) {
|
||||||
|
return item == null
|
||||||
|
}
|
||||||
|
export function array(item) {
|
||||||
|
return Array.isArray(item)
|
||||||
|
}
|
||||||
|
export function emptyObject(item) {
|
||||||
|
for (var key in item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
export function buffer(item) {
|
||||||
|
return Buffer.isBuffer(item)
|
||||||
|
}
|
||||||
|
export function regExp(item) {
|
||||||
|
return matchObject(item, 'RegExp')
|
||||||
|
}
|
||||||
|
export function string(item) {
|
||||||
|
return matchObject(item, 'String')
|
||||||
|
}
|
||||||
|
export function func(item) {
|
||||||
|
return typeof item === 'function'
|
||||||
|
}
|
||||||
|
export function number(item) {
|
||||||
|
return matchObject(item, 'Number')
|
||||||
|
}
|
||||||
|
export function exists(name) {
|
||||||
|
return fs.existsSync(name)
|
||||||
|
}
|
||||||
|
export function file(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.statSync(n).isFile()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function samePath(a, b) {
|
||||||
|
return path.resolve(a) === path.resolve(b)
|
||||||
|
}
|
||||||
|
export function directory(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.statSync(n).isDirectory()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function symbolicLink(name) {
|
||||||
|
return checkStat(name, function(n) {
|
||||||
|
return fs.lstatSync(n).isSymbolicLink()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function windows() {
|
||||||
|
return os.platform() === 'win32'
|
||||||
|
}
|
27
package.json
27
package.json
|
@ -1,20 +1,31 @@
|
||||||
{
|
{
|
||||||
"name": "eltro",
|
"name": "eltro",
|
||||||
"version": "1.3.3",
|
"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": {
|
||||||
"test": "node cli.mjs test/**/*.test.mjs",
|
"echo": "echo helloworld",
|
||||||
"test:watch": "npm-watch test"
|
"echo:watch": "node cli.mjs --watch test --npm echo",
|
||||||
|
"test": "node cli.mjs \"test/**/*.test.mjs\"",
|
||||||
|
"test:watch": "node cli.mjs \"test/**/*.test.mjs\" --watch test",
|
||||||
|
"test:watch:legacy": "npm-watch test"
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
"test": {
|
"test": {
|
||||||
"patterns": [
|
"patterns": [ "lib", "test", "cli.mjs", "index.mjs" ],
|
||||||
"*"
|
|
||||||
],
|
|
||||||
"extensions": "js,mjs",
|
"extensions": "js,mjs",
|
||||||
"quiet": true,
|
"delay": 50
|
||||||
"inherit": true
|
},
|
||||||
|
"test_quick": {
|
||||||
|
"patterns": [ "cli.mjs", "index.mjs" ]
|
||||||
|
},
|
||||||
|
"test_quick_js": {
|
||||||
|
"patterns": [ "cli.mjs", "index.mjs" ],
|
||||||
|
"extensions": "js"
|
||||||
|
},
|
||||||
|
"test_invalid_extensions": {
|
||||||
|
"patterns": [ "cli.mjs", "index.mjs" ],
|
||||||
|
"extensions": ".js.bla"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -29,6 +29,63 @@ 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.test('should work and return the original error', function() {
|
||||||
|
const assertError = new Error('Speed')
|
||||||
|
|
||||||
|
let err = assert.throwsAndCatch(() => { throw assertError })
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should otherwise throw if no error', function() {
|
||||||
|
const assertMessage = 'Hello world'
|
||||||
|
let error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert.throwsAndCatch(() => { }, assertMessage)
|
||||||
|
} catch (err) {
|
||||||
|
error = err
|
||||||
|
}
|
||||||
|
assert.ok(error)
|
||||||
|
assert.match(error.message, new RegExp(assertMessage))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.describe('#isFulfilled()', function() {
|
t.describe('#isFulfilled()', function() {
|
||||||
t.test('should exist', function() {
|
t.test('should exist', function() {
|
||||||
assertExtended.ok(assertExtended.isFulfilled)
|
assertExtended.ok(assertExtended.isFulfilled)
|
||||||
|
|
1010
test/cli.test.mjs
1010
test/cli.test.mjs
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,9 @@ function CreateT() {
|
||||||
t.logger = {
|
t.logger = {
|
||||||
log: stub()
|
log: stub()
|
||||||
}
|
}
|
||||||
|
t.process = {
|
||||||
|
stdout: { write: stub() }
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,23 +55,74 @@ e.describe('#before()', function() {
|
||||||
assert.strictEqual(secondBefore, 1)
|
assert.strictEqual(secondBefore, 1)
|
||||||
assert.strictEqual(thirdBefore, 4)
|
assert.strictEqual(thirdBefore, 4)
|
||||||
})
|
})
|
||||||
})
|
e.test('should support multiple functions in describe group', async function() {
|
||||||
|
let assertRan = 0
|
||||||
|
let firstBefore = -1
|
||||||
|
let secondBefore = -1
|
||||||
|
let thirdBefore = -1
|
||||||
|
let fourthBefore = -1
|
||||||
|
|
||||||
e.describe('#beforeEach()', function() {
|
|
||||||
e.test('should support functions in describe group and run before each test and group', async function() {
|
|
||||||
const t = CreateT()
|
const t = CreateT()
|
||||||
t.begin()
|
t.begin()
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
|
t.before(function() {
|
||||||
|
firstBefore = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.before(function() {
|
||||||
|
thirdBefore = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.before(function() {
|
||||||
|
fourthBefore = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
secondBefore = assertRan
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(assertRan, 5)
|
||||||
|
assert.strictEqual(stats.passed, 5)
|
||||||
|
assert.strictEqual(firstBefore, 0)
|
||||||
|
assert.strictEqual(secondBefore, 0)
|
||||||
|
assert.strictEqual(thirdBefore, 1)
|
||||||
|
assert.strictEqual(fourthBefore, 4)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
e.describe('#beforeEach()', function() {
|
||||||
|
e.test('should support functions in describe group and run before each test and each test in every group', async function() {
|
||||||
let outside = 0
|
let outside = 0
|
||||||
|
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
|
||||||
t.beforeEach(function() {
|
t.beforeEach(function() {
|
||||||
outside++
|
outside++
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
t.before(function() {
|
|
||||||
assert.strictEqual(outside, 2)
|
|
||||||
})
|
|
||||||
let inside = 0
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
assert.strictEqual(outside, 1)
|
||||||
|
})
|
||||||
|
|
||||||
t.beforeEach(function() {
|
t.beforeEach(function() {
|
||||||
inside++
|
inside++
|
||||||
})
|
})
|
||||||
|
@ -79,11 +133,12 @@ e.describe('#beforeEach()', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
|
let insideSecond = 0
|
||||||
|
|
||||||
t.before(function() {
|
t.before(function() {
|
||||||
assert.strictEqual(outside, 3)
|
assert.strictEqual(outside, 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
let insideSecond = 0
|
|
||||||
t.beforeEach(function() {
|
t.beforeEach(function() {
|
||||||
assert.strictEqual(insideSecond, 0)
|
assert.strictEqual(insideSecond, 0)
|
||||||
insideSecond++
|
insideSecond++
|
||||||
|
@ -99,6 +154,97 @@ e.describe('#beforeEach()', function() {
|
||||||
assert.strictEqual(stats.passed, 5)
|
assert.strictEqual(stats.passed, 5)
|
||||||
assert.strictEqual(stats.failed, 0)
|
assert.strictEqual(stats.failed, 0)
|
||||||
assert.strictEqual(stats.skipped, 0)
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
assert.strictEqual(outside, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should work even if before is specifed after all the tests', async function() {
|
||||||
|
let outside = 0
|
||||||
|
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
assert.strictEqual(outside, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 1) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 2) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 3) })
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
let insideSecond = 0
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
assert.strictEqual(outside, 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(insideSecond, 1) })
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
assert.strictEqual(insideSecond, 0)
|
||||||
|
insideSecond++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(outside, 1) })
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(stats.passed, 5)
|
||||||
|
assert.strictEqual(stats.failed, 0)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
assert.strictEqual(outside, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should support multiple beforeEach', async function() {
|
||||||
|
let outside = 0
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.beforeEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.beforeEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 2) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 4) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 6) })
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(outside, 2) })
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(stats.passed, 4)
|
||||||
|
assert.strictEqual(stats.failed, 0)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
assert.strictEqual(outside, 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
e.test('should be able to keep track of every error that occurs', async function() {
|
e.test('should be able to keep track of every error that occurs', async function() {
|
||||||
|
@ -112,6 +258,7 @@ e.describe('#beforeEach()', function() {
|
||||||
|
|
||||||
t.describe('CCCC', function() {
|
t.describe('CCCC', function() {
|
||||||
t.test('', function() { })
|
t.test('', function() { })
|
||||||
|
t.test('', function() { })
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('DDDD', function() {
|
t.describe('DDDD', function() {
|
||||||
|
@ -121,20 +268,78 @@ e.describe('#beforeEach()', function() {
|
||||||
t.test('AAAA', function() { })
|
t.test('AAAA', function() { })
|
||||||
})
|
})
|
||||||
let stats = await t.run()
|
let stats = await t.run()
|
||||||
assert.strictEqual(t.failedTests.length, 3)
|
assert.strictEqual(t.failedTests.length, 4)
|
||||||
assert.strictEqual(t.logger.log.callCount, 3)
|
assert.strictEqual(t.logger.log.callCount, 4)
|
||||||
assert.match(t.logger.log.firstCall[1].message, /1/)
|
assert.match(t.logger.log.getCallN(1)[1].message, /1/)
|
||||||
assert.match(t.logger.log.firstCall[0], /before each/i)
|
assert.match(t.logger.log.getCallN(1)[0], /before each/i)
|
||||||
assert.match(t.logger.log.firstCall[0], /AAAA/)
|
assert.match(t.logger.log.getCallN(1)[0], /AAAA/)
|
||||||
assert.match(t.logger.log.firstCall[0], /BBBB/)
|
assert.match(t.logger.log.getCallN(1)[0], /BBBB/)
|
||||||
assert.match(t.logger.log.secondCall[1].message, /2/)
|
assert.match(t.logger.log.getCallN(2)[1].message, /2/)
|
||||||
assert.match(t.logger.log.secondCall[0], /before each/i)
|
assert.match(t.logger.log.getCallN(2)[0], /before each/i)
|
||||||
assert.match(t.logger.log.secondCall[0], /CCCC/)
|
assert.match(t.logger.log.getCallN(2)[0], /CCCC/)
|
||||||
assert.match(t.logger.log.secondCall[0], /BBBB/)
|
assert.match(t.logger.log.getCallN(2)[0], /BBBB/)
|
||||||
assert.match(t.logger.log.thirdCall[1].message, /3/)
|
assert.match(t.logger.log.getCallN(3)[1].message, /3/)
|
||||||
assert.match(t.logger.log.thirdCall[0], /before each/i)
|
assert.match(t.logger.log.getCallN(3)[0], /before each/i)
|
||||||
assert.match(t.logger.log.thirdCall[0], /DDDD/)
|
assert.match(t.logger.log.getCallN(3)[0], /CCCC/)
|
||||||
assert.match(t.logger.log.thirdCall[0], /BBBB/)
|
assert.match(t.logger.log.getCallN(3)[0], /BBBB/)
|
||||||
|
assert.match(t.logger.log.getCallN(4)[1].message, /4/)
|
||||||
|
assert.match(t.logger.log.getCallN(4)[0], /before each/i)
|
||||||
|
assert.match(t.logger.log.getCallN(4)[0], /DDDD/)
|
||||||
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -179,6 +384,81 @@ e.describe('#after()', function() {
|
||||||
assert.strictEqual(secondAfter, 4)
|
assert.strictEqual(secondAfter, 4)
|
||||||
assert.strictEqual(thirdAfter, 5)
|
assert.strictEqual(thirdAfter, 5)
|
||||||
})
|
})
|
||||||
|
e.test('should support multiple functions in describe group', async function() {
|
||||||
|
let assertRan = 0
|
||||||
|
let firstAfter = -1
|
||||||
|
let secondAfter = -1
|
||||||
|
let thirdAfter = -1
|
||||||
|
let fourthAfter = -1
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.after(function() {
|
||||||
|
firstAfter = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.after(function() {
|
||||||
|
thirdAfter = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.after(function() {
|
||||||
|
fourthAfter = assertRan
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assertRan++ })
|
||||||
|
|
||||||
|
t.after(function() {
|
||||||
|
secondAfter = assertRan
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(stats.passed, 5)
|
||||||
|
assert.strictEqual(assertRan, 5)
|
||||||
|
|
||||||
|
assert.strictEqual(firstAfter, 5)
|
||||||
|
assert.strictEqual(secondAfter, 5)
|
||||||
|
assert.strictEqual(thirdAfter, 4)
|
||||||
|
assert.strictEqual(fourthAfter, 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should log even if it throws and test throws', async function() {
|
||||||
|
const assertError = new Error('test')
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.after(function() { throw assertError })
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
t.after(function() { throw assertError })
|
||||||
|
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
t.test('', function() { })
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
|
||||||
|
t.after(function() { throw assertError })
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 6)
|
||||||
|
assert.strictEqual(stats.passed, 1)
|
||||||
|
|
||||||
|
for (let failedTest of t.failedTests) {
|
||||||
|
assert.strictEqual(failedTest.error, assertError)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
e.describe('#afterEach()', function() {
|
e.describe('#afterEach()', function() {
|
||||||
|
@ -187,14 +467,16 @@ e.describe('#afterEach()', function() {
|
||||||
t.begin()
|
t.begin()
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
let outside = 0
|
let outside = 0
|
||||||
|
|
||||||
t.afterEach(function() {
|
t.afterEach(function() {
|
||||||
outside++
|
outside++
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
t.before(function() { assert.strictEqual(outside, 1) })
|
t.before(function() { assert.strictEqual(outside, 1) })
|
||||||
|
|
||||||
let inside = 0
|
|
||||||
t.afterEach(function() {
|
t.afterEach(function() {
|
||||||
inside++
|
inside++
|
||||||
})
|
})
|
||||||
|
@ -207,9 +489,10 @@ e.describe('#afterEach()', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('', function() {
|
t.describe('', function() {
|
||||||
t.before(function() { assert.strictEqual(outside, 2) })
|
|
||||||
|
|
||||||
let inside = 0
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() { assert.strictEqual(outside, 4) })
|
||||||
|
|
||||||
t.afterEach(function() {
|
t.afterEach(function() {
|
||||||
inside++
|
inside++
|
||||||
})
|
})
|
||||||
|
@ -221,7 +504,185 @@ e.describe('#afterEach()', function() {
|
||||||
|
|
||||||
t.test('', function() { assert.strictEqual(outside, 0) })
|
t.test('', function() { assert.strictEqual(outside, 0) })
|
||||||
|
|
||||||
t.after(function() { assert.strictEqual(outside, 3) })
|
t.after(function() { assert.strictEqual(outside, 5) })
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(stats.passed, 5)
|
||||||
|
assert.strictEqual(stats.failed, 0)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should work even if after is specified after the tests', async function() {
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
let outside = 0
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() { assert.strictEqual(outside, 1) })
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 0) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 1) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 2) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(inside, 3) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() { assert.strictEqual(outside, 4) })
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 0) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(inside, 1) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(outside, 0) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(outside, 5) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
assert.strictEqual(stats.passed, 5)
|
||||||
|
assert.strictEqual(stats.failed, 0)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should run even if each test throws', async function() {
|
||||||
|
let outside = 0
|
||||||
|
let inside = 0
|
||||||
|
const assertError = new Error('test')
|
||||||
|
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.describe('', function() {
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 4)
|
||||||
|
for (let failedTest of t.failedTests) {
|
||||||
|
assert.strictEqual(failedTest.error, assertError)
|
||||||
|
}
|
||||||
|
assert.strictEqual(stats.passed, 0)
|
||||||
|
assert.strictEqual(stats.failed, 4)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
assert.strictEqual(outside, 4)
|
||||||
|
assert.strictEqual(inside, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should log even if afterEach fails', async function() {
|
||||||
|
const assertError = new Error('test')
|
||||||
|
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.describe('', function() {
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
|
||||||
|
t.afterEach(function() { throw assertError })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { throw assertError })
|
||||||
|
|
||||||
|
t.afterEach(function() { throw assertError })
|
||||||
|
})
|
||||||
|
let stats = await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 8)
|
||||||
|
for (let failedTest of t.failedTests) {
|
||||||
|
assert.strictEqual(failedTest.error, assertError)
|
||||||
|
}
|
||||||
|
assert.strictEqual(stats.passed, 0)
|
||||||
|
assert.strictEqual(stats.failed, 8)
|
||||||
|
assert.strictEqual(stats.skipped, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('should support multiple afterEach', async function() {
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
let outside = 0
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() { assert.strictEqual(outside, 2) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 0) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 2) })
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 4) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(inside, 6) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('', function() {
|
||||||
|
let inside = 0
|
||||||
|
|
||||||
|
t.before(function() { assert.strictEqual(outside, 8) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(inside, 0) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(inside, 2) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
inside++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('', function() { assert.strictEqual(outside, 0) })
|
||||||
|
|
||||||
|
t.after(function() { assert.strictEqual(outside, 10) })
|
||||||
|
|
||||||
|
t.afterEach(function() {
|
||||||
|
outside++
|
||||||
|
})
|
||||||
})
|
})
|
||||||
let stats = await t.run()
|
let stats = await t.run()
|
||||||
assert.strictEqual(t.failedTests.length, 0)
|
assert.strictEqual(t.failedTests.length, 0)
|
||||||
|
@ -254,26 +715,80 @@ e.describe('#afterEach()', function() {
|
||||||
let stats = await t.run()
|
let stats = await t.run()
|
||||||
assert.strictEqual(t.failedTests.length, 5)
|
assert.strictEqual(t.failedTests.length, 5)
|
||||||
assert.strictEqual(t.logger.log.callCount, 5)
|
assert.strictEqual(t.logger.log.callCount, 5)
|
||||||
assert.match(t.logger.log.getCall(0)[1].message, /1/)
|
assert.match(t.logger.log.getCallN(1)[1].message, /1/)
|
||||||
assert.match(t.logger.log.getCall(0)[0], /after each/i)
|
assert.match(t.logger.log.getCallN(1)[0], /after each/i)
|
||||||
assert.match(t.logger.log.getCall(0)[0], /AAAA/)
|
assert.match(t.logger.log.getCallN(1)[0], /AAAA/)
|
||||||
assert.match(t.logger.log.getCall(0)[0], /YYYY/)
|
assert.match(t.logger.log.getCallN(1)[0], /YYYY/)
|
||||||
assert.match(t.logger.log.getCall(1)[1].message, /2/)
|
assert.match(t.logger.log.getCallN(2)[1].message, /2/)
|
||||||
assert.match(t.logger.log.getCall(1)[0], /after each/i)
|
assert.match(t.logger.log.getCallN(2)[0], /after each/i)
|
||||||
assert.match(t.logger.log.getCall(1)[0], /BBBB/)
|
assert.match(t.logger.log.getCallN(2)[0], /BBBB/)
|
||||||
assert.match(t.logger.log.getCall(1)[0], /YYYY/)
|
assert.match(t.logger.log.getCallN(2)[0], /YYYY/)
|
||||||
assert.match(t.logger.log.getCall(2)[1].message, /3/)
|
assert.match(t.logger.log.getCallN(3)[1].message, /3/)
|
||||||
assert.match(t.logger.log.getCall(2)[0], /after each/i)
|
assert.match(t.logger.log.getCallN(3)[0], /after each/i)
|
||||||
assert.match(t.logger.log.getCall(2)[0], /CCCC/)
|
assert.match(t.logger.log.getCallN(3)[0], /CCCC/)
|
||||||
assert.match(t.logger.log.getCall(2)[0], /YYYY/)
|
assert.match(t.logger.log.getCallN(3)[0], /YYYY/)
|
||||||
assert.match(t.logger.log.getCall(3)[1].message, /4/)
|
assert.match(t.logger.log.getCallN(4)[1].message, /4/)
|
||||||
assert.match(t.logger.log.getCall(3)[0], /after each/i)
|
assert.match(t.logger.log.getCallN(4)[0], /after each/i)
|
||||||
assert.match(t.logger.log.getCall(3)[0], /HHHH/)
|
assert.match(t.logger.log.getCallN(4)[0], /HHHH/)
|
||||||
assert.match(t.logger.log.getCall(3)[0], /YYYY/)
|
assert.match(t.logger.log.getCallN(4)[0], /YYYY/)
|
||||||
assert.match(t.logger.log.getCall(4)[1].message, /5/)
|
assert.match(t.logger.log.getCallN(5)[1].message, /5/)
|
||||||
assert.match(t.logger.log.getCall(4)[0], /after each/i)
|
assert.match(t.logger.log.getCallN(5)[0], /after each/i)
|
||||||
assert.match(t.logger.log.getCall(4)[0], /JJJJ/)
|
assert.match(t.logger.log.getCallN(5)[0], /JJJJ/)
|
||||||
assert.match(t.logger.log.getCall(4)[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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -226,6 +226,58 @@ e.test('Eltro should support callback rejected errors', async function() {
|
||||||
assert.strictEqual(t.failedTests[0].error, assertError)
|
assert.strictEqual(t.failedTests[0].error, assertError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
e.test('Eltro should support capturing unknown errors outside scope', async function() {
|
||||||
|
testsWereRun = true
|
||||||
|
const assertError = new Error()
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.test('', function(cb) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
throw assertError
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 1)
|
||||||
|
assert.strictEqual(t.failedTests[0].error, assertError)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
e.test('Eltro should log an error if test is missing', async function() {
|
||||||
|
testsWereRun = true
|
||||||
|
const assertError = new Error()
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.describe('herpderp', function() {
|
||||||
|
t.test('blatest')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 1)
|
||||||
|
assert.match(t.failedTests[0].error.message, /herpderp/)
|
||||||
|
assert.match(t.failedTests[0].error.message, /blatest/)
|
||||||
|
assert.match(t.failedTests[0].error.message, /missing/)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.test('Eltro should log an error if text is missing', async function() {
|
||||||
|
testsWereRun = true
|
||||||
|
const assertError = new Error()
|
||||||
|
const t = CreateT()
|
||||||
|
t.begin()
|
||||||
|
t.describe('', function() {
|
||||||
|
t.describe('herpderp', function() {
|
||||||
|
t.test()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await t.run()
|
||||||
|
assert.strictEqual(t.failedTests.length, 1)
|
||||||
|
assert.match(t.failedTests[0].error.message, /herpderp/)
|
||||||
|
assert.match(t.failedTests[0].error.message, /empty/)
|
||||||
|
assert.match(t.failedTests[0].error.message, /name/)
|
||||||
|
})
|
||||||
|
|
||||||
e.test('Eltro should support timing out tests', async function() {
|
e.test('Eltro should support timing out tests', async function() {
|
||||||
testsWereRun = true
|
testsWereRun = true
|
||||||
const t = CreateT()
|
const t = CreateT()
|
||||||
|
@ -541,9 +593,8 @@ e.test('Eltro nested timeout should work as expected', async function() {
|
||||||
// Extra testing to make sure tests were run at all
|
// Extra testing to make sure tests were run at all
|
||||||
process.on('exit', function(e) {
|
process.on('exit', function(e) {
|
||||||
try {
|
try {
|
||||||
assert.strictEqual(testsWereRun, true)
|
assert.strictEqual(testsWereRun, true, 'Not all tests were run, remove all .only() and try again.')
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log('Checking if tests were run at all failed:')
|
|
||||||
printError(err)
|
printError(err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
41
test/kill/kill.test.mjs
Normal file
41
test/kill/kill.test.mjs
Normal 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
11
test/kill/runner.mjs
Normal 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)
|
2
test/kill/second_runner.mjs
Normal file
2
test/kill/second_runner.mjs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
console.log('secondary', process.pid)
|
||||||
|
setInterval(function() { console.log('secondary', process.pid) }, 100)
|
935
test/watch.test.mjs
Normal file
935
test/watch.test.mjs
Normal file
|
@ -0,0 +1,935 @@
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import assert from '../lib/assert.mjs'
|
||||||
|
import t from '../lib/eltro.mjs'
|
||||||
|
import { Builder, Counter } from './watch/builder.mjs'
|
||||||
|
import Watcher from '../lib/watch/index.mjs'
|
||||||
|
import * as is from '../lib/watch/is.mjs'
|
||||||
|
|
||||||
|
const builder = new Builder()
|
||||||
|
let watcher
|
||||||
|
|
||||||
|
function htTimeToMs(end) {
|
||||||
|
return end[0] * 1000 + Math.round(end[1] / 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
return builder.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.afterEach(function(done) {
|
||||||
|
if (watcher && !watcher.isClosed()) {
|
||||||
|
watcher.on('close', done)
|
||||||
|
watcher.close()
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.after(function() {
|
||||||
|
if (builder) {
|
||||||
|
return builder.cleanup()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('watcher', function() {
|
||||||
|
t.describe('process events', function() {
|
||||||
|
t.test('should emit `close` event', function(done) {
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
watcher = new Watcher(fpath, function() {})
|
||||||
|
watcher.on('close', done)
|
||||||
|
watcher.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should emit `ready` event when watching a file', function(done) {
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
watcher = new Watcher(fpath)
|
||||||
|
watcher.on('ready', done)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should emit `ready` event when watching a directory recursively', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
watcher = new Watcher(dir, { recursive: true })
|
||||||
|
watcher.on('ready', done)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should emit `ready` properly in a composed watcher', function(done) {
|
||||||
|
var dir1 = builder.getPath('home/a')
|
||||||
|
var dir2 = builder.getPath('home/b')
|
||||||
|
var file = builder.getPath('home/b/file1')
|
||||||
|
watcher = new Watcher([dir1, dir2, file], { recursive: true })
|
||||||
|
watcher.on('ready', done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('watch for files', function() {
|
||||||
|
t.test('should watch a single file and keep watching', function(done) {
|
||||||
|
var counter = new Counter(done, 3)
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
watcher = new Watcher(fpath, { delay: 0 }, function(evt, name) {
|
||||||
|
assert.strictEqual(fpath, name)
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.waitForCount(builder.modify(file))
|
||||||
|
.then(() => counter.waitForCount(builder.modify(file)))
|
||||||
|
.then(() => counter.waitForCount(builder.modify(file)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should watch files inside a directory', function(done) {
|
||||||
|
var fpath = builder.getPath('home/a')
|
||||||
|
var set = new Set()
|
||||||
|
set.add(builder.getPath('home/a/file1'))
|
||||||
|
set.add(builder.getPath('home/a/file2'))
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
builder.newFile('home/a/file1'),
|
||||||
|
builder.newFile('home/a/file2'),
|
||||||
|
]).then(() => {
|
||||||
|
watcher = new Watcher(fpath, { delay: 0 }, function(evt, name) {
|
||||||
|
set.delete(name)
|
||||||
|
if (!set.size) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
Promise.all([
|
||||||
|
builder.modify('home/a/file1'),
|
||||||
|
builder.modify('home/a/file2'),
|
||||||
|
]).then()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should debounce multiple triggers', function(done) {
|
||||||
|
var counter = new Counter()
|
||||||
|
var file = 'home/a/file2'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
var start = process.hrtime()
|
||||||
|
var middle = start
|
||||||
|
var end = start
|
||||||
|
|
||||||
|
watcher = new Watcher(fpath, { delay: 100 }, function(evt, name) {
|
||||||
|
if (fpath === name) counter.count()
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file)
|
||||||
|
.then(() => builder.modify(file))
|
||||||
|
.then(() => builder.modify(file))
|
||||||
|
.then(() => {
|
||||||
|
middle = htTimeToMs(process.hrtime(start))
|
||||||
|
return counter.waitForCount()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
assert.strictEqual(counter.counter, 1)
|
||||||
|
end = htTimeToMs(process.hrtime(start))
|
||||||
|
|
||||||
|
assert.ok(end - middle > 50)
|
||||||
|
assert.ok(end >= 100)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should listen to new created files', function(done) {
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
var counter = new Counter()
|
||||||
|
var newfile1 = 'home/a/newfile' + builder.randomName()
|
||||||
|
var newfile2 = 'home/a/newfile' + builder.randomName()
|
||||||
|
var set = new Set([
|
||||||
|
builder.getPath(newfile1),
|
||||||
|
builder.getPath(newfile2),
|
||||||
|
])
|
||||||
|
var changes = []
|
||||||
|
var extra = []
|
||||||
|
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
|
if (set.has(name)) {
|
||||||
|
changes.push(name)
|
||||||
|
counter.count()
|
||||||
|
} else {
|
||||||
|
extra.push(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.waitForCount(builder.newFile(newfile1))
|
||||||
|
.then(() => counter.waitForCount(builder.newFile(newfile2)))
|
||||||
|
.then(() => {
|
||||||
|
assert.deepStrictEqual(changes, [...set.values()])
|
||||||
|
// assert.deepStrictEqual(extra, [])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should error when parent gets deleted before calling fs.watch', function(done) {
|
||||||
|
var fpath = builder.getPath('home/a/removeme/file1')
|
||||||
|
builder.newFile('home/a/removeme/file1')
|
||||||
|
.then(() => {
|
||||||
|
watcher = new Watcher(fpath, null, null, { fs: { watch: function(path, options) {
|
||||||
|
builder.removeSync('home/a/removeme')
|
||||||
|
return fs.watch(path, options)
|
||||||
|
} } })
|
||||||
|
|
||||||
|
watcher.on('error', done.finish(function(err) {
|
||||||
|
assert.deepStrictEqual(watcher.listeners, [])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('watch for directories', function() {
|
||||||
|
t.test('should watch directories inside a directory', function(done) {
|
||||||
|
var home = builder.getPath('home/c')
|
||||||
|
var dir = builder.getPath('home/c/removeme')
|
||||||
|
|
||||||
|
builder.createDirectory('home/c/removeme').then(() => {
|
||||||
|
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
|
if (name === dir && evt === 'remove') {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.remove('home/c/removeme').catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should watch new created directories', function(done) {
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
|
||||||
|
builder.remove('home/new').then(() => {
|
||||||
|
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
|
if (name === builder.getPath('home/new/file1')) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.newFile('home/new/file1')
|
||||||
|
.then(() => builder.modify('home/new/file1'))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should not watch new created directories which are being skipped', function(done) {
|
||||||
|
var counter = new Counter(done, 1)
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
skip: function(filePath) {
|
||||||
|
if (/ignored/.test(filePath)) {
|
||||||
|
counter.count()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.remove('home/ignored/file').then(() => {
|
||||||
|
watcher = new Watcher(home, options, function(evt, name) {
|
||||||
|
assert.fail("should not watch new created directories which are being skipped event detect: " + name)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.newFile('home/ignored/file')
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should keep watching after removal of sub directory', function(done) {
|
||||||
|
var counter = new Counter(done, 3)
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
var file1 = builder.getPath('home/e/file1')
|
||||||
|
var file2 = builder.getPath('home/e/file2')
|
||||||
|
var dir = builder.getPath('home/e/sub')
|
||||||
|
var set = new Set()
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
builder.newFile('home/e/sub/testfile'),
|
||||||
|
builder.newFile('home/e/file1'),
|
||||||
|
builder.newFile('home/e/file2'),
|
||||||
|
]).then(() => {
|
||||||
|
|
||||||
|
watcher = new Watcher(home, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
|
if (name === dir || name === file1 || name === file2) {
|
||||||
|
if (!set.has(name)) {
|
||||||
|
set.add(name)
|
||||||
|
counter.count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.remove('home/e/sub')
|
||||||
|
builder.modify('home/e/file1')
|
||||||
|
builder.modify('home/e/file2')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should watch new directories without delay', function(done) {
|
||||||
|
var counter = new Counter(done, 1)
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
|
||||||
|
builder.remove('home/new').then(() => {
|
||||||
|
watcher = new Watcher(home, { delay: 200, recursive: true }, function(evt, name) {
|
||||||
|
if (name === builder.getPath('home/new/file1')) {
|
||||||
|
counter.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.newFile('home/new/file1')
|
||||||
|
.then(() => builder.modify('home/new/file1'))
|
||||||
|
.then(() => builder.modify('home/new/file1'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should trigger changed at the end of all debounce', function(done) {
|
||||||
|
var counter = new Counter()
|
||||||
|
var fpath = builder.getPath('home/a/')
|
||||||
|
|
||||||
|
watcher = new Watcher(fpath, { delay: 100 })
|
||||||
|
|
||||||
|
watcher.on('change', function(evt, name) {
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('changed', done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(counter.counter, 3)
|
||||||
|
}))
|
||||||
|
|
||||||
|
watcher.on('ready', done.wrap(function() {
|
||||||
|
builder.modify('home/a/file1')
|
||||||
|
.then(() => builder.modify('home/a/file2'))
|
||||||
|
.then(() => builder.modify('home/a/file3'))
|
||||||
|
.catch(done)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should trigger changed for each change when delay is zero', function(done) {
|
||||||
|
var counter = new Counter(done, 3)
|
||||||
|
var set = new Set([
|
||||||
|
builder.getPath('home/a/file1'),
|
||||||
|
builder.getPath('home/a/file2'),
|
||||||
|
builder.getPath('home/a/file3'),
|
||||||
|
])
|
||||||
|
var fpath = builder.getPath('home/a')
|
||||||
|
|
||||||
|
watcher = new Watcher(fpath, { delay: 0 })
|
||||||
|
|
||||||
|
watcher.on('changed', done.finish(function(evt, name) {
|
||||||
|
if (set.has(name)) {
|
||||||
|
set.delete(name)
|
||||||
|
counter.count()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify('home/a/file1')
|
||||||
|
.then(() => builder.modify('home/a/file2'))
|
||||||
|
.then(() => builder.modify('home/a/file3'))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should error when directory gets deleted before calling fs.watch', function(done) {
|
||||||
|
var dir = 'home/c/removeme'
|
||||||
|
var fpath = builder.getPath(dir)
|
||||||
|
|
||||||
|
builder.createDirectory(dir).then(() => {
|
||||||
|
watcher = new Watcher(fpath, null, null, { fs: { watch: function(path, options) {
|
||||||
|
builder.removeSync(dir)
|
||||||
|
return fs.watch(path, options)
|
||||||
|
} } })
|
||||||
|
|
||||||
|
watcher.on('error', done.finish(function(err) {
|
||||||
|
assert.deepStrictEqual(watcher.listeners, [])
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('file events', function() {
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
t.beforeEach(function() {
|
||||||
|
return builder.newFile(file)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should identify `remove` event', function(done) {
|
||||||
|
watcher = new Watcher(fpath, { delay: 0 }, function(evt, name) {
|
||||||
|
if (evt === 'remove' && name === fpath) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.remove(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should identify `remove` event on directory', function(done) {
|
||||||
|
var dir = 'home/a/removeme'
|
||||||
|
var home = builder.getPath('home/a')
|
||||||
|
var fpath = builder.getPath(dir)
|
||||||
|
|
||||||
|
builder.createDirectory('home/a/removeme').then(done.wrap(function() {
|
||||||
|
watcher = new Watcher(home, { delay: 0 }, function(evt, name) {
|
||||||
|
if (evt === 'remove' && name === fpath) done()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.remove(dir).catch(done)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should be able to handle many events on deleting', function(done) {
|
||||||
|
var dir = 'home/a'
|
||||||
|
var fpath = builder.getPath(dir)
|
||||||
|
|
||||||
|
builder.newRandomFiles(dir, 100).then(names => {
|
||||||
|
var counter = new Counter(done, names.length)
|
||||||
|
|
||||||
|
watcher = new Watcher(fpath, { delay: 10 }, function(evt, name) {
|
||||||
|
if (evt === 'remove') counter.count()
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
Promise.all(names.map(x => builder.remove(path.join(dir, x)))).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should identify `update` event', function(done) {
|
||||||
|
var file = 'home/b/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
builder.newFile(file).then(() => {
|
||||||
|
watcher = new Watcher(fpath, { delay: 0 }, function(evt, name) {
|
||||||
|
if (evt === 'update' && name === fpath) done()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should report `update` on new files', function(done) {
|
||||||
|
var dir = builder.getPath('home/a')
|
||||||
|
var file = 'home/a/newfile_' + builder.randomName()
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
watcher = new Watcher(dir, { delay: 0 }, function(evt, name) {
|
||||||
|
if (evt === 'update' && name === fpath) done()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.newFile(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('options', function() {
|
||||||
|
t.describe('recursive', function() {
|
||||||
|
t.test('should watch recursively with `recursive: true` option', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
var file = builder.getPath('home/bb/file1')
|
||||||
|
watcher = new Watcher(dir, { delay: 0, recursive: true }, function(evt, name) {
|
||||||
|
if (file === name) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify('home/bb/file1').catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should store original paths in object', function() {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
watcher = new Watcher(dir)
|
||||||
|
assert.deepStrictEqual(watcher.originalPaths, [dir])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should store all original paths in object', function() {
|
||||||
|
var dir1 = builder.getPath('home/a')
|
||||||
|
var dir2 = builder.getPath('home/b')
|
||||||
|
watcher = new Watcher([dir1, dir2])
|
||||||
|
assert.deepStrictEqual(watcher.originalPaths, [dir1, dir2])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('encoding', function() {
|
||||||
|
let options = {
|
||||||
|
delay: 0,
|
||||||
|
encoding: 'unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
var fdir = builder.getPath('home/a')
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
t.before(() => {
|
||||||
|
return builder.newFile(file)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should throw on invalid encoding', function(done) {
|
||||||
|
options.encoding = 'unknown'
|
||||||
|
try {
|
||||||
|
watcher = new Watcher(fdir, options)
|
||||||
|
} catch (e) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should accept an encoding string', function(done) {
|
||||||
|
options.encoding = 'utf8'
|
||||||
|
watcher = new Watcher(fdir, options, done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(name.toString(), fpath)
|
||||||
|
}))
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should support buffer encoding', function(done) {
|
||||||
|
options.encoding = 'buffer'
|
||||||
|
watcher = new Watcher(fdir, options, done.finish(function(evt, name) {
|
||||||
|
assert.ok(Buffer.isBuffer(name), 'not a Buffer')
|
||||||
|
assert.strictEqual(name.toString(), fpath)
|
||||||
|
}))
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should support base64 encoding', function(done) {
|
||||||
|
options.encoding = 'base64'
|
||||||
|
watcher = new Watcher(fdir, options, done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(
|
||||||
|
name,
|
||||||
|
Buffer.from(fpath).toString('base64'),
|
||||||
|
'wrong base64 encoding'
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should support hex encoding', function(done) {
|
||||||
|
options.encoding = 'hex'
|
||||||
|
watcher = new Watcher(fdir, options, done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(
|
||||||
|
name,
|
||||||
|
Buffer.from(fpath).toString('hex'),
|
||||||
|
'wrong hex encoding'
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('skip', function() {
|
||||||
|
t.test('should only watch non-skipped directories', function(done) {
|
||||||
|
var matchRegularDir = false
|
||||||
|
var matchIgnoredDir = false
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert(matchRegularDir, 'watch failed to detect regular file')
|
||||||
|
assert(!matchIgnoredDir, 'fail to ignore path `deep_node_modules`')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
skip: function(name) {
|
||||||
|
return /deep_node_modules/.test(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher = new Watcher(builder.getPath('home'), options, function(evt, name) {
|
||||||
|
if (/deep_node_modules/.test(name)) {
|
||||||
|
matchIgnoredDir = true
|
||||||
|
} else {
|
||||||
|
matchRegularDir = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
builder.modify('home/deep_node_modules/ma/file1')
|
||||||
|
.then(() => builder.modify('home/b/file1'))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should only report non-skipped files', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
var file1 = 'home/bb/file1'
|
||||||
|
var file2 = 'home/bb/file2'
|
||||||
|
|
||||||
|
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
skip: function(name) {
|
||||||
|
return /file1/.test(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchIgnoredFile = false
|
||||||
|
watcher = new Watcher(dir, options, function(evt, name) {
|
||||||
|
if (name === builder.getPath(file1)) {
|
||||||
|
matchIgnoredFile = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
|
||||||
|
builder.modify(file2)
|
||||||
|
.then(() => builder.modify(file1))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should be able to skip directly with regexp', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
var file1 = 'home/bb/file1'
|
||||||
|
var file2 = 'home/bb/file2'
|
||||||
|
|
||||||
|
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert.strictEqual(matchIgnoredFile, false, 'home/bb/file1 should be ignored')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
skip: /file1/
|
||||||
|
}
|
||||||
|
|
||||||
|
var times = 0
|
||||||
|
var matchIgnoredFile = false
|
||||||
|
watcher = new Watcher(dir, options, function(evt, name) {
|
||||||
|
if (name === builder.getPath(file1)) {
|
||||||
|
matchIgnoredFile = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
|
||||||
|
builder.modify(file2)
|
||||||
|
.then(() => builder.modify(file1))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should be able to skip subdirectories with `skip` flag', function(done) {
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
manualRecursive: true,
|
||||||
|
skip: function(name) {
|
||||||
|
if (/\/deep_node_modules/.test(name)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watcher = new Watcher(home, options)
|
||||||
|
|
||||||
|
watcher.on('ready', done.finish(function() {
|
||||||
|
let homeFiltered = builder.getAllDirectories().filter(function(name) {
|
||||||
|
return !/\/deep_node_modules/.test(name)
|
||||||
|
}).sort()
|
||||||
|
let watchersPaths = watcher.listeners.map(x => x.path).sort()
|
||||||
|
|
||||||
|
assert.deepStrictEqual(watchersPaths, homeFiltered)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('filter', function() {
|
||||||
|
t.test('should not have impact on watched directories', function(done) {
|
||||||
|
var matchNonFilterDir = false
|
||||||
|
var matchFilteredDir = false
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert(!matchNonFilterDir, 'watch should not detect non-filter file')
|
||||||
|
assert(matchFilteredDir, 'watch failed to detect filter path `deep_node_modules`')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
filter: function(name) {
|
||||||
|
return /deep_node_modules/.test(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher = new Watcher(builder.getPath('home'), options, function(evt, name) {
|
||||||
|
if (/deep_node_modules/.test(name)) {
|
||||||
|
matchFilteredDir = true
|
||||||
|
} else {
|
||||||
|
matchNonFilterDir = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
builder.modify('home/b/file1')
|
||||||
|
.then(() => builder.modify('home/deep_node_modules/ma/file1'))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should only report filtered files', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
var file1 = 'home/bb/file1'
|
||||||
|
var file2 = 'home/bb/file2'
|
||||||
|
var matchFilterFile = false
|
||||||
|
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert.strictEqual(matchFilterFile, true, 'home/bb/file1 should be visible')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
filter: function(name) {
|
||||||
|
return /file1/.test(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher = new Watcher(dir, options, function(evt, name) {
|
||||||
|
if (name === builder.getPath(file1)) {
|
||||||
|
matchFilterFile = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
|
||||||
|
builder.modify(file2)
|
||||||
|
.then(() => builder.modify(file1))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should be able to filter directly with regexp', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
var file1 = 'home/bb/file1'
|
||||||
|
var file2 = 'home/bb/file2'
|
||||||
|
|
||||||
|
|
||||||
|
var counter = new Counter(done.finish(function() {
|
||||||
|
assert.strictEqual(matchFilterFile, true, 'home/bb/file1 should be visible')
|
||||||
|
}), 1, true)
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
filter: /file1/
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchFilterFile = false
|
||||||
|
watcher = new Watcher(dir, options, function(evt, name) {
|
||||||
|
if (name === builder.getPath(file1)) {
|
||||||
|
matchFilterFile = true
|
||||||
|
}
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
counter.startCounting()
|
||||||
|
|
||||||
|
builder.modify(file2)
|
||||||
|
.then(() => builder.modify(file1))
|
||||||
|
.catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should not filter subdirectories with `filter` flag', function(done) {
|
||||||
|
var home = builder.getPath('home')
|
||||||
|
var options = {
|
||||||
|
delay: 0,
|
||||||
|
recursive: true,
|
||||||
|
manualRecursive: true,
|
||||||
|
filter: function(name) {
|
||||||
|
if (/\/deep_node_modules/.test(name)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watcher = new Watcher(home, options)
|
||||||
|
|
||||||
|
watcher.on('ready', done.finish(function() {
|
||||||
|
let homeFiltered = builder.getAllDirectories().sort()
|
||||||
|
let watchersPaths = watcher.listeners.map(x => x.path).sort()
|
||||||
|
|
||||||
|
assert.deepStrictEqual(watchersPaths, homeFiltered)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('parameters', function() {
|
||||||
|
t.test('should throw error on non-existed file', function(done) {
|
||||||
|
var somedir = builder.getPath('home/somedir')
|
||||||
|
watcher = new Watcher(somedir)
|
||||||
|
watcher.on('error', done.finish(function(err) {
|
||||||
|
assert.match(err.message, /not exist/)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should accept filename as Buffer', function(done) {
|
||||||
|
var fpath = builder.getPath('home/a/file1')
|
||||||
|
watcher = new Watcher(Buffer.from(fpath), { delay: 0 }, done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(name, fpath)
|
||||||
|
}))
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
builder.modify('home/a/file1').catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should compose array of files or directories', function(done) {
|
||||||
|
var counter = new Counter(done, 2)
|
||||||
|
var file1 = 'home/a/file1'
|
||||||
|
var file2 = 'home/a/file2'
|
||||||
|
var fpaths = [
|
||||||
|
builder.getPath(file1),
|
||||||
|
builder.getPath(file2)
|
||||||
|
]
|
||||||
|
var set = new Set(fpaths)
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
builder.newFile(file1),
|
||||||
|
builder.newFile(file2),
|
||||||
|
]).then(function() {
|
||||||
|
watcher = new Watcher(fpaths, { delay: 0 }, function(evt, name) {
|
||||||
|
if (set.has(name)) {
|
||||||
|
set.delete(name)
|
||||||
|
counter.count()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
Promise.all([
|
||||||
|
builder.modify(file1),
|
||||||
|
builder.modify(file2),
|
||||||
|
]).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should filter duplicate events for composed watcher', function(done) {
|
||||||
|
var counter = new Counter(done, 2)
|
||||||
|
var home = 'home'
|
||||||
|
var dir = 'home/a'
|
||||||
|
var file1 = 'home/a/file1'
|
||||||
|
var file2 = 'home/a/file2'
|
||||||
|
var fpaths = [
|
||||||
|
builder.getPath(home),
|
||||||
|
builder.getPath(dir),
|
||||||
|
builder.getPath(file1),
|
||||||
|
builder.getPath(file2)
|
||||||
|
]
|
||||||
|
|
||||||
|
var changes = []
|
||||||
|
watcher = new Watcher(fpaths, { delay: 100, recursive: true }, function(evt, name) {
|
||||||
|
changes.push(name)
|
||||||
|
counter.count()
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
new Promise(res => {
|
||||||
|
counter.updateRes(res)
|
||||||
|
|
||||||
|
builder.modify(file1)
|
||||||
|
.then(() => builder.modify(file2))
|
||||||
|
})
|
||||||
|
.then(() => builder.delay(50))
|
||||||
|
.then(() => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
changes,
|
||||||
|
[fpaths[2], fpaths[3]]
|
||||||
|
)
|
||||||
|
done()
|
||||||
|
}).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('watcher object', function() {
|
||||||
|
t.test('should using watcher object to watch', function(done) {
|
||||||
|
var dir = builder.getPath('home/a')
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var fpath = builder.getPath(file)
|
||||||
|
|
||||||
|
watcher = new Watcher(dir, { delay: 0 })
|
||||||
|
|
||||||
|
watcher.on('change', done.finish(function(evt, name) {
|
||||||
|
assert.strictEqual(evt, 'update')
|
||||||
|
assert.strictEqual(name, fpath)
|
||||||
|
}))
|
||||||
|
|
||||||
|
watcher.on('ready', done.wrap(function() {
|
||||||
|
builder.modify(file).catch(done)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('close()', function() {
|
||||||
|
t.test('should close a watcher using .close()', function(done) {
|
||||||
|
var dir = builder.getPath('home/a')
|
||||||
|
var file = 'home/a/file1'
|
||||||
|
var times = 0
|
||||||
|
watcher = new Watcher(dir, { delay: 0 })
|
||||||
|
watcher.on('change', function(evt, name) {
|
||||||
|
times++
|
||||||
|
})
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
watcher.close()
|
||||||
|
|
||||||
|
builder.modify(file)
|
||||||
|
.then(() => builder.delay(50))
|
||||||
|
.then(() => builder.modify(file))
|
||||||
|
.then(() => builder.delay(50))
|
||||||
|
.then(() => {
|
||||||
|
assert(watcher.isClosed(), 'watcher should be closed')
|
||||||
|
assert.strictEqual(times, 0, 'failed to close the watcher')
|
||||||
|
done()
|
||||||
|
}).catch(done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should not watch after .close() is called', function(done) {
|
||||||
|
var dir = builder.getPath('home')
|
||||||
|
watcher = new Watcher(dir, { delay: 0, recursive: true })
|
||||||
|
|
||||||
|
watcher.on('ready', function() {
|
||||||
|
watcher.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
watcher.on('close', done.finish(function(dirs) {
|
||||||
|
assert.strictEqual(watcher.listeners.length, 0)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
248
test/watch/builder.mjs
Normal file
248
test/watch/builder.mjs
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import fsSync from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
let structure = `
|
||||||
|
home/
|
||||||
|
a/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
b/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
c/
|
||||||
|
bb/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
d/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
e/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
sub/
|
||||||
|
deep_node_modules/
|
||||||
|
ma/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
mb/
|
||||||
|
file1
|
||||||
|
file2
|
||||||
|
mc/
|
||||||
|
`
|
||||||
|
|
||||||
|
let code = structure
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line)
|
||||||
|
.map(function(line) {
|
||||||
|
return {
|
||||||
|
indent: line.length - line.replace(/^\s+/,'').length,
|
||||||
|
type: /\/$/.test(line) ? 'dir': 'file',
|
||||||
|
text: line.replace(/^\s+|\s*\/\s*|\s+$/g, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function join(arr) {
|
||||||
|
return arr.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
function transform(arr) {
|
||||||
|
let result = []
|
||||||
|
let temp = []
|
||||||
|
let indent = 0
|
||||||
|
arr.forEach(function(line) {
|
||||||
|
if (!line.text) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (!line.indent) {
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if (indent < line.indent) {
|
||||||
|
temp.push(line.text)
|
||||||
|
result[result.length - 1].type = 'dir'
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if (indent === line.indent) {
|
||||||
|
temp.pop()
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
else if(indent > line.indent) {
|
||||||
|
temp.pop()
|
||||||
|
temp.pop()
|
||||||
|
temp.push(line.text)
|
||||||
|
result.push({type: line.type, text: join(temp) })
|
||||||
|
}
|
||||||
|
|
||||||
|
indent = line.indent
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function gracefully(promise) {
|
||||||
|
return promise.catch(function(err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyFile(target) {
|
||||||
|
let folder = target.slice(0, target.lastIndexOf('/'))
|
||||||
|
|
||||||
|
/*return fs.mkdir(folder, { recursive: true, force: true })
|
||||||
|
.then(() => fs.open(target, 'w').then(fd => fd.close()))*/
|
||||||
|
|
||||||
|
return fs.stat(folder)
|
||||||
|
.catch(() => fs.mkdir(folder, { recursive: true, force: true }))
|
||||||
|
.then(() => {
|
||||||
|
return fs.open(target, 'w').then(fd => fd.close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Builder() {
|
||||||
|
this.root = path.join(__dirname, '__TREE__')
|
||||||
|
this.transformed = transform(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.init = async function() {
|
||||||
|
await Promise.all(
|
||||||
|
this.transformed.filter(line => line.type === 'dir').map(line => {
|
||||||
|
let target = path.join(this.root, line.text)
|
||||||
|
return gracefully(fs.mkdir(target, { recursive: true }))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(
|
||||||
|
this.transformed.filter(line => line.type !== 'dir').map(line => {
|
||||||
|
let target = path.join(this.root, line.text)
|
||||||
|
return gracefully(emptyFile(target))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.getPath = function(fpath, sub) {
|
||||||
|
return path.join(this.root, fpath, sub || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.modify = function(fpath, delay) {
|
||||||
|
if (delay) throw new Error('remove delay')
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.appendFile(filePath, 'hello')
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.remove = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.rm(filePath, { recursive: true, force: true })
|
||||||
|
.catch(() =>
|
||||||
|
this.delay(100)
|
||||||
|
.then(() => fs.rm(filePath, { recursive: true, force: true }))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.removeSync = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fsSync.rmSync(filePath, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.newFile = function(fpath, delay) {
|
||||||
|
if (delay) throw new Error('remove delay')
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return emptyFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.createDirectory = function(fpath) {
|
||||||
|
let filePath = this.getPath(fpath)
|
||||||
|
return fs.mkdir(filePath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.randomName = function() {
|
||||||
|
return Math.random().toString(36).substring(2, 14)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.newRandomFiles = function(fpath, count) {
|
||||||
|
return Promise.all(
|
||||||
|
new Array(count).fill(0).map(() => {
|
||||||
|
let name = this.randomName()
|
||||||
|
let filePath = this.getPath(fpath, name)
|
||||||
|
|
||||||
|
return emptyFile(filePath).then(() => name)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.cleanup = function() {
|
||||||
|
return fs.rm(this.root, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.getAllDirectories = function() {
|
||||||
|
function walk(dir) {
|
||||||
|
let ret = []
|
||||||
|
fsSync.readdirSync(dir).forEach(function(d) {
|
||||||
|
let fpath = path.join(dir, d)
|
||||||
|
if (fsSync.statSync(fpath).isDirectory()) {
|
||||||
|
ret.push(fpath)
|
||||||
|
ret = ret.concat(walk(fpath))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return walk(this.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
Builder.prototype.delay = function(delay) {
|
||||||
|
return new Promise(res => {
|
||||||
|
setTimeout(res, delay)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Counter(res, countTotal, waitForSignal = false) {
|
||||||
|
this._res = res
|
||||||
|
this.counter = 0
|
||||||
|
this._countTotal = countTotal
|
||||||
|
this._startCount = !waitForSignal
|
||||||
|
this._gotSignal = false
|
||||||
|
this._signal = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.waitForCount = function(promise) {
|
||||||
|
if (!promise) {
|
||||||
|
promise = Promise.resolve()
|
||||||
|
}
|
||||||
|
return new Promise(res => {
|
||||||
|
promise.then(() => {
|
||||||
|
this._signal = res
|
||||||
|
this.hasSignal()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.updateRes = function(res) {
|
||||||
|
this._res = res
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.hasSignal = function() {
|
||||||
|
if (this._gotSignal && this._signal) {
|
||||||
|
this._gotSignal = false
|
||||||
|
let temp = this._signal
|
||||||
|
this._signal = null
|
||||||
|
temp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.startCounting = function() {
|
||||||
|
this._startCount = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.prototype.count = function() {
|
||||||
|
if (!this._startCount) return
|
||||||
|
|
||||||
|
this._gotSignal = true
|
||||||
|
this.counter++
|
||||||
|
if (this.counter === this._countTotal && this._res) {
|
||||||
|
this._res()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasSignal()
|
||||||
|
}
|
Loading…
Reference in a new issue