Compare commits

...

3 Commits

7 changed files with 1415 additions and 1338 deletions

View File

@ -276,7 +276,9 @@ Eltro.prototype.__runTest = async function(stats, test, prefix = 'Test', child =
if (markRealTest.skipTest) {
process.stdout.write(' \x1b[94m- ' + markRealTest.name + '\x1b[0m\n')
} else if (!markRealTest.error) {
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
if (!test.name.startsWith('~')) {
process.stdout.write(' \x1b[32m√\x1b[90m ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
}
} else if (prefix === 'Test') {
process.stdout.write(' \x1b[31m' + this.failedTests.length + ') ' + markRealTest.name + ' (' + markRealTest.totalTime + 'ms)\x1b[0m\n')
}
@ -298,14 +300,17 @@ Eltro.prototype.__runGroup = async function(g, stats) {
}
}
if (g.before) {
await this.__runTest(stats, g.before, 'Before')
if (g.before.error) return
for (let i = 0; i < g.before.length; i++) {
await this.__runTest(stats, g.before[i], 'Before')
if (g.before[i].error) return
}
}
for (let x = 0; x < g.tests.length; x++) {
if (!g.tests[x].skipTest && g.tests[x].isExclusive === g.hasExclusive) {
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) {
await this.__runTest(stats, g.tests[x])
}
@ -313,24 +318,26 @@ Eltro.prototype.__runGroup = async function(g, stats) {
await this.__runTest(stats, g.tests[x])
}
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++) {
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)
if (g.afterEach) {
await this.__runTest(stats, g.afterEach, g.groups[x].name + ': ', g.afterEach)
}
}
}
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')
}
}
}
@ -360,11 +367,9 @@ Eltro.prototype.run = async function() {
if (this.reporter === 'test') {
if (this.logger && this.logger.log) {
if (this.failedTests.length) {
for (let x = 0; x < this.failedTests.length; x++) {
let test = this.failedTests[x];
this.logger.log(test.name, test.error)
}
for (let x = 0; x < this.failedTests.length; x++) {
let test = this.failedTests[x];
this.logger.log(test.name, test.error)
}
}
} else if (this.reporter) {
@ -406,30 +411,48 @@ Eltro.prototype.resetFilename = function() {
}
let beforesandafters = [
['before', 'Before'],
['after', 'After'],
['beforeEach', 'Before each'],
['afterEach', 'After each'],
['before', '~Before', false],
['after', '~After', false],
['beforeEach', '~Before each', true],
['afterEach', '~After each', true],
]
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) {
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) {
test.timeout(this.temporary.timeout || this.activeGroup.customTimeout)
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
}
})
let bringToChildren = ['beforeEach', 'afterEach']
Eltro.prototype.describe = function(name, func) {
let before = this.activeGroup
@ -458,6 +481,16 @@ Eltro.prototype.describe = function(name, func) {
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()
this.activeGroup = before

View File

@ -1,20 +1,14 @@
import fs from 'fs'
import fsPromise from 'fs/promises'
import path from 'path'
import util from 'util'
import events from 'events'
import hasNativeRecursive from './has-native-recursive.mjs'
import * as is from './is.mjs'
const EVENT_UPDATE = 'update';
const EVENT_REMOVE = 'remove';
const SKIP_FLAG = Symbol('skip');
function hasDup(arr) {
return arr.some(function(v, i, self) {
return self.indexOf(v) !== i;
});
}
const TYPE_FILE = 'file'
const TYPE_DIRECTORY = 'directory'
function unique(arr) {
return arr.filter(function(v, i, self) {
@ -22,528 +16,274 @@ function unique(arr) {
});
}
// One level flat
function flat1(arr) {
return arr.reduce(function(acc, v) {
return acc.concat(v);
}, []);
}
export default class Watcher extends events.EventEmitter {
constructor(path, options = null, fn = null, { fs: fsoverwrite } = {}) {
super()
function assertEncoding(encoding) {
if (encoding && encoding !== 'buffer' && !Buffer.isEncoding(encoding)) {
throw new Error('Unknown encoding: ' + encoding);
}
}
function guard(fn) {
if (is.func(fn)) {
return function(arg, action) {
if (fn(arg, false)) action();
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.regExp(fn)) {
return function(arg, action) {
if (fn.test(arg)) action();
if (!is.array(paths)) {
paths = [paths]
}
}
return function(arg, action) {
action();
}
}
function composeMessage(names) {
return names.map(function(n) {
return is.exists(n)
? [EVENT_UPDATE, n]
: [EVENT_REMOVE, n];
});
}
paths = unique(paths)
this.options = options || {}
this.fn = fn || null
function getMessages(cache) {
var filtered = unique(cache);
// Saving file from an editor? If so, assuming the
// non-existed files in the cache are temporary files
// generated by an editor and thus be filtered.
var reg = /~$|^\.#|^##$/g;
var hasSpecialChar = cache.some(function(c) {
return reg.test(c);
});
if (hasSpecialChar) {
var dup = hasDup(cache.map(function(c) {
return c.replace(reg, '');
}));
if (dup) {
filtered = filtered.filter(function(m) {
return is.exists(m);
});
if (is.func(this.options)) {
this.fn = this.options
this.options = {}
}
this._verifyOptions(paths)
}
return composeMessage(filtered);
}
function debounce(info, fn) {
var timer, cache = [];
var encoding = info.options.encoding;
var delay = info.options.delay;
if (!is.number(delay)) {
delay = 200;
isClosed() {
return this.closed
}
function handle() {
getMessages(cache).forEach(function(msg) {
msg[1] = Buffer.from(msg[1]);
if (encoding !== 'buffer') {
msg[1] = msg[1].toString(encoding);
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.'))
}
fn.apply(null, msg);
});
timer = null;
cache = [];
}
return function(rawEvt, name) {
cache.push(name);
if (!timer) {
timer = setTimeout(handle, delay);
}
}
}
function createDupsFilter() {
var memo = {};
return function(fn) {
return function(evt, name) {
memo[evt + name] = [evt, name];
setTimeout(function() {
Object.keys(memo).forEach(function(n) {
fn.apply(null, memo[n]);
});
memo = {};
});
}
}
}
function tryWatch(watcher, dir, opts) {
try {
return fs.watch(dir, opts);
} catch (e) {
process.nextTick(function() {
watcher.emit('error', e);
});
}
}
function getSubDirectories(dir, fn, done = function() {}) {
if (is.directory(dir)) {
fs.readdir(dir, function(err, all) {
if (err) {
// don't throw permission errors.
if (/^(EPERM|EACCES)$/.test(err.code)) {
console.warn('Warning: Cannot access %s.', dir);
} else {
throw err;
}
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 {
all.forEach(function(f) {
var sdir = path.join(dir, f);
if (is.directory(sdir)) fn(sdir);
});
done();
}
});
} else {
done();
}
}
function semaphore(final) {
var counter = 0;
return function start() {
counter++;
return function stop() {
counter--;
if (counter === 0) final();
};
};
}
function nullCounter() {
return function nullStop() {};
}
function shouldNotSkip(filePath, filter) {
// watch it only if the filter is not function
// or not being skipped explicitly.
return !is.func(filter) || filter(filePath, SKIP_FLAG) !== SKIP_FLAG;
}
var deprecationWarning = util.deprecate(
function() {},
'(node-watch) First param in callback function\
is replaced with event name since 0.5.0, use\
`(evt, filename) => {}` if you want to get the filename'
);
function Watcher() {
events.EventEmitter.call(this);
this.watchers = {};
this._isReady = false;
this._isClosed = false;
}
util.inherits(Watcher, events.EventEmitter);
Watcher.prototype.expose = function() {
var expose = {};
var self = this;
var methods = [
'on', 'emit', 'once',
'close', 'isClosed',
'listeners', 'setMaxListeners', 'getMaxListeners',
'getWatchedPaths'
];
methods.forEach(function(name) {
expose[name] = function() {
return self[name].apply(self, arguments);
}
});
expose.options = self.options
return expose;
}
Watcher.prototype.isClosed = function() {
return this._isClosed;
}
Watcher.prototype.close = function(fullPath) {
var self = this;
if (fullPath) {
var watcher = this.watchers[fullPath];
if (watcher && watcher.close) {
watcher.close();
delete self.watchers[fullPath];
}
getSubDirectories(fullPath, function(fpath) {
self.close(fpath);
});
}
else {
Object.keys(self.watchers).forEach(function(fpath) {
var watcher = self.watchers[fpath];
if (watcher && watcher.close) {
watcher.close();
}
});
this.watchers = {};
}
// Do not close the Watcher unless all child watchers are closed.
// https://github.com/yuanchuan/node-watch/issues/75
if (is.emptyObject(self.watchers)) {
// should emit once
if (!this._isClosed) {
this._isClosed = true;
process.nextTick(emitClose, this);
}
}
}
Watcher.prototype.getWatchedPaths = function(fn) {
if (is.func(fn)) {
var self = this;
if (self._isReady) {
fn(Object.keys(self.watchers));
} else {
self.on('ready', function() {
fn(Object.keys(self.watchers));
});
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)
})
} else {
this._startListeners(paths)
}
}
}
function emitReady(self) {
if (!self._isReady) {
self._isReady = true;
// do not call emit for 'ready' until after watch() has returned,
// so that consumer can call on().
process.nextTick(function () {
self.emit('ready');
});
_startListeners(paths) {
Promise.all(paths.map(path => this.safeAdd(path)))
.then(
() => this.emit('ready'),
err => this.emit('error', err),
)
}
}
function emitClose(self) {
self.emit('close');
}
Watcher.prototype.add = function(watcher, info) {
var self = this;
info = info || { fpath: '' };
var watcherPath = path.resolve(info.fpath);
this.watchers[watcherPath] = watcher;
// Internal callback for handling fs.FSWatcher 'change' events
var internalOnChange = function(rawEvt, rawName) {
if (self.isClosed()) {
return;
}
// normalise lack of name and convert to full path
var name = rawName;
if (is.nil(name)) {
name = '';
}
name = path.join(info.fpath, name);
if (info.options.recursive) {
hasNativeRecursive(function(has) {
if (!has) {
var fullPath = path.resolve(name);
// remove watcher on removal
if (!is.exists(name)) {
self.close(fullPath);
}
// watch new created directory
else {
var shouldWatch = is.directory(name)
&& !self.watchers[fullPath]
&& shouldNotSkip(name, info.options.filter);
if (shouldWatch) {
self.watchDirectory(name, info.options);
}
}
}
});
}
handlePublicEvents(rawEvt, name);
};
// Debounced based on the 'delay' option
var handlePublicEvents = debounce(info, function (evt, name) {
// watch single file
if (info.compareName) {
if (info.compareName(name)) {
self.emit('change', evt, name);
getWatcherOrNull(name) {
for (let check of this.listeners) {
if (check.path === name) {
return check
}
}
// watch directory
else {
var filterGuard = guard(info.options.filter);
filterGuard(name, function() {
if (self.flag) self.flag = '';
else self.emit('change', evt, name);
});
}
});
return null
}
watcher.on('error', function(err) {
if (self.isClosed()) {
return;
shouldInclude(name) {
return this.options.filter
?
(is.func(this.options.filter) && this.options.filter.call(this, name) === true)
|| (is.regExp(this.options.filter) && this.options.filter.test(name))
: true
}
closeWatch(orgItem) {
let item = orgItem
if (typeof item === 'string') {
item = getWatcherOrNull(item)
}
if (!item) {
this.emit('error', new Error(`attempted to close watcher for ${item} but such a watcher could not be found`))
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.shouldInclude(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)
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.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) {
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.shouldInclude(name) === false) {
this.safeAdd(subItem, 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') {
watcher.emit('change', EVENT_REMOVE, info.fpath && '');
self.flag = 'windows-error';
self.close(watcherPath);
this.closeWatch(item)
item.flag = 'windows-error'
} else {
self.emit('error', err);
self.emit('error', err)
}
});
watcher.on('change', internalOnChange);
}
Watcher.prototype.watchFile = function(file, options, fn) {
var parent = path.join(file, '../');
var opts = Object.assign({}, options, {
// no filter for single file
filter: null,
encoding: 'utf8'
});
// no need to watch recursively
delete opts.recursive;
var watcher = tryWatch(this, parent, opts);
if (!watcher) {
return;
}
this.add(watcher, {
type: 'file',
fpath: parent,
options: Object.assign({}, opts, {
encoding: options.encoding
}),
compareName: function(n) {
return is.samePath(n, file);
safeAdd(name, orgType) {
let type = orgType
if (!type) {
type = is.file(name) ? TYPE_FILE : TYPE_DIRECTORY
}
});
if (is.func(fn)) {
if (fn.length === 1) deprecationWarning();
this.on('change', fn);
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)))
})
}
}
Watcher.prototype.updateDelay = function(delay) {
console.log()
}
Watcher.prototype.watchDirectory = function(dir, options, fn, counter = nullCounter) {
var self = this;
var done = counter();
hasNativeRecursive(function(has) {
// always specify recursive
options.recursive = !!options.recursive;
// using utf8 internally
var opts = Object.assign({}, options, {
encoding: 'utf8'
});
if (!has) {
delete opts.recursive;
}
// check if it's closed before calling watch.
if (self._isClosed) {
done();
return self.close();
}
var watcher = tryWatch(self, dir, opts);
if (!watcher) {
done();
return;
}
self.add(watcher, {
type: 'dir',
fpath: dir,
options: options
});
if (is.func(fn)) {
if (fn.length === 1) deprecationWarning();
self.on('change', fn);
}
if (options.recursive && !has) {
getSubDirectories(dir, function(d) {
if (shouldNotSkip(d, options.filter)) {
self.watchDirectory(d, options, null, counter);
}
}, counter());
}
done();
});
}
function composeWatcher(watchers) {
var watcher = new Watcher();
var filterDups = createDupsFilter();
var counter = watchers.length;
watchers.forEach(function(w) {
w.on('change', filterDups(function(evt, name) {
watcher.emit('change', evt, name);
}));
w.on('error', function(err) {
watcher.emit('error', err);
});
w.on('ready', function() {
if (!(--counter)) {
emitReady(watcher);
}
});
});
watcher.close = function() {
watchers.forEach(function(w) {
w.close();
});
process.nextTick(emitClose, watcher);
}
watcher.getWatchedPaths = function(fn) {
if (is.func(fn)) {
var promises = watchers.map(function(w) {
return new Promise(function(resolve) {
w.getWatchedPaths(resolve);
});
});
Promise.all(promises).then(function(result) {
var ret = unique(flat1(result));
fn(ret);
});
}
}
return watcher.expose();
}
export default function watch(fpath, options, fn) {
var watcher = new Watcher();
if (is.buffer(fpath)) {
fpath = fpath.toString();
}
if (!is.array(fpath) && !is.exists(fpath)) {
process.nextTick(function() {
watcher.emit('error',
new Error(fpath + ' does not exist.')
);
});
}
if (is.string(options)) {
throw new Error(`Invalid option, encoding as string is no longer supported. Use { encoding: "${options}" } instead.`)
}
if (is.func(options)) {
fn = options;
options = {};
}
if (arguments.length < 2) {
options = {};
}
if (options.encoding) {
assertEncoding(options.encoding);
} else {
options.encoding = 'utf8';
}
if (is.array(fpath)) {
if (fpath.length === 1) {
return watch(fpath[0], options, fn);
}
var filterDups = createDupsFilter();
return composeWatcher(unique(fpath).map(function(f) {
var w = watch(f, options);
if (is.func(fn)) {
w.on('change', filterDups(fn));
}
return w;
}));
}
if (is.file(fpath)) {
watcher.watchFile(fpath, options, fn);
emitReady(watcher);
}
else if (is.directory(fpath)) {
var counter = semaphore(function () {
emitReady(watcher);
});
watcher.watchDirectory(fpath, options, fn, counter);
}
return watcher.expose();
}

View File

@ -4,7 +4,7 @@
"description": "Eltro is a tiny no-dependancy test framework for node",
"main": "index.mjs",
"scripts": {
"test": "node cli.mjs 'test/**/*.test.mjs'",
"test": "node cli.mjs \"test/**/*.test.mjs\"",
"test:watch": "npm-watch test"
},
"watch": {

View File

@ -185,7 +185,6 @@ t.describe('CLI', function() {
cli.targets = ['test/testtree/*/*.mjs']
await cli.processTargets()
console.log(cli.files)
assert.strictEqual(cli.files.length, 4)
cli.files.sort()
assert.deepEqual(cli.files, [

View File

@ -52,23 +52,74 @@ e.describe('#before()', function() {
assert.strictEqual(secondBefore, 1)
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()
t.begin()
t.describe('', function() {
let outside = 0
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
const t = CreateT()
t.begin()
t.describe('', function() {
t.beforeEach(function() {
outside++
})
t.describe('', function() {
t.before(function() {
assert.strictEqual(outside, 2)
})
let inside = 0
t.before(function() {
assert.strictEqual(outside, 1)
})
t.beforeEach(function() {
inside++
})
@ -79,11 +130,12 @@ e.describe('#beforeEach()', function() {
})
t.describe('', function() {
t.before(function() {
assert.strictEqual(outside, 3)
})
let insideSecond = 0
let insideSecond = 0
t.before(function() {
assert.strictEqual(outside, 4)
})
t.beforeEach(function() {
assert.strictEqual(insideSecond, 0)
insideSecond++
@ -99,6 +151,97 @@ e.describe('#beforeEach()', function() {
assert.strictEqual(stats.passed, 5)
assert.strictEqual(stats.failed, 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() {
@ -112,6 +255,7 @@ e.describe('#beforeEach()', function() {
t.describe('CCCC', function() {
t.test('', function() { })
t.test('', function() { })
})
t.describe('DDDD', function() {
@ -121,20 +265,24 @@ e.describe('#beforeEach()', function() {
t.test('AAAA', function() { })
})
let stats = await t.run()
assert.strictEqual(t.failedTests.length, 3)
assert.strictEqual(t.logger.log.callCount, 3)
assert.match(t.logger.log.firstCall[1].message, /1/)
assert.match(t.logger.log.firstCall[0], /before each/i)
assert.match(t.logger.log.firstCall[0], /AAAA/)
assert.match(t.logger.log.firstCall[0], /BBBB/)
assert.match(t.logger.log.secondCall[1].message, /2/)
assert.match(t.logger.log.secondCall[0], /before each/i)
assert.match(t.logger.log.secondCall[0], /CCCC/)
assert.match(t.logger.log.secondCall[0], /BBBB/)
assert.match(t.logger.log.thirdCall[1].message, /3/)
assert.match(t.logger.log.thirdCall[0], /before each/i)
assert.match(t.logger.log.thirdCall[0], /DDDD/)
assert.match(t.logger.log.thirdCall[0], /BBBB/)
assert.strictEqual(t.failedTests.length, 4)
assert.strictEqual(t.logger.log.callCount, 4)
assert.match(t.logger.log.getCallN(1)[1].message, /1/)
assert.match(t.logger.log.getCallN(1)[0], /before each/i)
assert.match(t.logger.log.getCallN(1)[0], /AAAA/)
assert.match(t.logger.log.getCallN(1)[0], /BBBB/)
assert.match(t.logger.log.getCallN(2)[1].message, /2/)
assert.match(t.logger.log.getCallN(2)[0], /before each/i)
assert.match(t.logger.log.getCallN(2)[0], /CCCC/)
assert.match(t.logger.log.getCallN(2)[0], /BBBB/)
assert.match(t.logger.log.getCallN(3)[1].message, /3/)
assert.match(t.logger.log.getCallN(3)[0], /before each/i)
assert.match(t.logger.log.getCallN(3)[0], /CCCC/)
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/)
})
})
@ -179,6 +327,81 @@ e.describe('#after()', function() {
assert.strictEqual(secondAfter, 4)
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() {
@ -187,14 +410,16 @@ e.describe('#afterEach()', function() {
t.begin()
t.describe('', function() {
let outside = 0
t.afterEach(function() {
outside++
})
t.describe('', function() {
let inside = 0
t.before(function() { assert.strictEqual(outside, 1) })
let inside = 0
t.afterEach(function() {
inside++
})
@ -206,10 +431,11 @@ e.describe('#afterEach()', function() {
t.after(function() { assert.strictEqual(inside, 3) })
})
t.describe('', function() {
t.before(function() { assert.strictEqual(outside, 2) })
t.describe('', function() {
let inside = 0
t.before(function() { assert.strictEqual(outside, 4) })
let inside = 0
t.afterEach(function() {
inside++
})
@ -221,7 +447,185 @@ e.describe('#afterEach()', function() {
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()
assert.strictEqual(t.failedTests.length, 0)
@ -254,26 +658,26 @@ e.describe('#afterEach()', function() {
let stats = await t.run()
assert.strictEqual(t.failedTests.length, 5)
assert.strictEqual(t.logger.log.callCount, 5)
assert.match(t.logger.log.getCall(0)[1].message, /1/)
assert.match(t.logger.log.getCall(0)[0], /after each/i)
assert.match(t.logger.log.getCall(0)[0], /AAAA/)
assert.match(t.logger.log.getCall(0)[0], /YYYY/)
assert.match(t.logger.log.getCall(1)[1].message, /2/)
assert.match(t.logger.log.getCall(1)[0], /after each/i)
assert.match(t.logger.log.getCall(1)[0], /BBBB/)
assert.match(t.logger.log.getCall(1)[0], /YYYY/)
assert.match(t.logger.log.getCall(2)[1].message, /3/)
assert.match(t.logger.log.getCall(2)[0], /after each/i)
assert.match(t.logger.log.getCall(2)[0], /CCCC/)
assert.match(t.logger.log.getCall(2)[0], /YYYY/)
assert.match(t.logger.log.getCall(3)[1].message, /4/)
assert.match(t.logger.log.getCall(3)[0], /after each/i)
assert.match(t.logger.log.getCall(3)[0], /HHHH/)
assert.match(t.logger.log.getCall(3)[0], /YYYY/)
assert.match(t.logger.log.getCall(4)[1].message, /5/)
assert.match(t.logger.log.getCall(4)[0], /after each/i)
assert.match(t.logger.log.getCall(4)[0], /JJJJ/)
assert.match(t.logger.log.getCall(4)[0], /YYYY/)
assert.match(t.logger.log.getCallN(1)[1].message, /1/)
assert.match(t.logger.log.getCallN(1)[0], /after each/i)
assert.match(t.logger.log.getCallN(1)[0], /AAAA/)
assert.match(t.logger.log.getCallN(1)[0], /YYYY/)
assert.match(t.logger.log.getCallN(2)[1].message, /2/)
assert.match(t.logger.log.getCallN(2)[0], /after each/i)
assert.match(t.logger.log.getCallN(2)[0], /BBBB/)
assert.match(t.logger.log.getCallN(2)[0], /YYYY/)
assert.match(t.logger.log.getCallN(3)[1].message, /3/)
assert.match(t.logger.log.getCallN(3)[0], /after each/i)
assert.match(t.logger.log.getCallN(3)[0], /CCCC/)
assert.match(t.logger.log.getCallN(3)[0], /YYYY/)
assert.match(t.logger.log.getCallN(4)[1].message, /4/)
assert.match(t.logger.log.getCallN(4)[0], /after each/i)
assert.match(t.logger.log.getCallN(4)[0], /HHHH/)
assert.match(t.logger.log.getCallN(4)[0], /YYYY/)
assert.match(t.logger.log.getCallN(5)[1].message, /5/)
assert.match(t.logger.log.getCallN(5)[0], /after each/i)
assert.match(t.logger.log.getCallN(5)[0], /JJJJ/)
assert.match(t.logger.log.getCallN(5)[0], /YYYY/)
})
})

File diff suppressed because it is too large Load Diff

View File

@ -135,6 +135,8 @@ Builder.prototype.modify = function(fpath, delay) {
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) {
@ -187,6 +189,12 @@ Builder.prototype.getAllDirectories = function() {
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
@ -208,6 +216,10 @@ Counter.prototype.waitForCount = function(promise) {
})
}
Counter.prototype.updateRes = function(res) {
this._res = res
}
Counter.prototype.hasSignal = function() {
if (this._gotSignal && this._signal) {
this._gotSignal = false