remove(test): async leak helpers (#313)
This commit is contained in:
parent
0436db33a5
commit
cb3ee1fa8e
|
@ -3,7 +3,6 @@ import mockFs from 'mock-fs';
|
|||
|
||||
import { main, STATUS_ERROR, STATUS_SUCCESS } from '../src/app';
|
||||
import { readSource } from '../src/source';
|
||||
import { describeLeaks, itLeaks } from './helpers/async';
|
||||
|
||||
const TEST_ARGS_PRE = ['node', 'test'];
|
||||
const TEST_ARGS_CONFIG = ['--config-path', 'docs', '--config-name', 'config.yml'];
|
||||
|
@ -37,13 +36,13 @@ const TEST_FILES = {
|
|||
'test.yml': 'hello world',
|
||||
};
|
||||
|
||||
describeLeaks('main app', async () => {
|
||||
itLeaks('completion should succeed', async () => {
|
||||
describe('main app', async () => {
|
||||
it('completion should succeed', async () => {
|
||||
const status = await main(['node', 'test', 'complete']);
|
||||
expect(status).to.equal(STATUS_SUCCESS);
|
||||
});
|
||||
|
||||
itLeaks('should list rules and exit', async () => {
|
||||
it('should list rules and exit', async () => {
|
||||
mockFs(TEST_FILES);
|
||||
|
||||
const status = await main([
|
||||
|
@ -57,7 +56,7 @@ describeLeaks('main app', async () => {
|
|||
expect(status).to.equal(STATUS_SUCCESS);
|
||||
});
|
||||
|
||||
itLeaks('should load the source', async () => {
|
||||
it('should load the source', async () => {
|
||||
mockFs(TEST_FILES);
|
||||
|
||||
const status = await main([
|
||||
|
@ -71,7 +70,7 @@ describeLeaks('main app', async () => {
|
|||
expect(status).to.equal(STATUS_SUCCESS);
|
||||
});
|
||||
|
||||
itLeaks('should exit with rule errors', async () => {
|
||||
it('should exit with rule errors', async () => {
|
||||
mockFs(TEST_FILES);
|
||||
|
||||
const status = await main([
|
||||
|
@ -86,7 +85,7 @@ describeLeaks('main app', async () => {
|
|||
expect(status).to.equal(STATUS_ERROR);
|
||||
});
|
||||
|
||||
itLeaks('should exit with error count', async () => {
|
||||
it('should exit with error count', async () => {
|
||||
mockFs(TEST_FILES);
|
||||
|
||||
const status = await main([
|
||||
|
|
|
@ -5,12 +5,11 @@ import { spy, stub } from 'sinon';
|
|||
import { PassThrough } from 'stream';
|
||||
|
||||
import { readSource, writeSource } from '../src/source';
|
||||
import { describeLeaks, itLeaks } from './helpers/async';
|
||||
|
||||
export const TEST_STRING = 'hello world';
|
||||
|
||||
describeLeaks('load source helper', async () => {
|
||||
itLeaks('should read from stdin', async () => {
|
||||
describe('load source helper', async () => {
|
||||
it('should read from stdin', async () => {
|
||||
const pt = new PassThrough();
|
||||
|
||||
const futureSource = readSource('-', pt);
|
||||
|
@ -42,7 +41,7 @@ describeLeaks('load source helper', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('write source helper', async () => {
|
||||
describe('write source helper', async () => {
|
||||
it('should write to a file', async () => {
|
||||
mockFs({
|
||||
test: 'empty',
|
||||
|
|
|
@ -3,10 +3,9 @@ import { expect } from 'chai';
|
|||
import { join } from 'path';
|
||||
|
||||
import { loadConfig, readConfig } from '../../src/config';
|
||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||
|
||||
describeLeaks('load config helper', async () => {
|
||||
itLeaks('should load an existing config', async () =>
|
||||
describe('load config helper', async () => {
|
||||
it('should load an existing config', () =>
|
||||
expect(loadConfig('config-stderr.yml', join(__dirname, '..', 'docs'))).to.eventually.deep.include({
|
||||
data: {
|
||||
logger: {
|
||||
|
@ -18,17 +17,17 @@ describeLeaks('load config helper', async () => {
|
|||
})
|
||||
);
|
||||
|
||||
itLeaks('should throw when config is missing', async () =>
|
||||
it('should throw when config is missing', () =>
|
||||
expect(loadConfig('missing.yml', join(__dirname, '..', 'docs'))).to.eventually.be.rejectedWith(NotFoundError)
|
||||
);
|
||||
});
|
||||
|
||||
describeLeaks('read config helper', async () => {
|
||||
itLeaks('should consume enoent errors', async () =>
|
||||
describe('read config helper', async () => {
|
||||
it('should consume enoent errors', () =>
|
||||
expect(readConfig(join('docs', 'missing.yml'))).to.eventually.equal(undefined)
|
||||
);
|
||||
|
||||
itLeaks('should rethrow unknown errors', async () =>
|
||||
it('should rethrow unknown errors', () =>
|
||||
expect(readConfig('test')).to.eventually.be.rejectedWith(Error)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import { AsyncTracker, isDebug, isNil } from '@apextoaster/js-utils';
|
||||
|
||||
// this will pull Mocha internals out of the stacks
|
||||
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
|
||||
const { stackTraceFilter } = require('mocha/lib/utils');
|
||||
const filterStack = stackTraceFilter();
|
||||
|
||||
type AsyncMochaTest = (this: Mocha.Context | void) => Promise<void>;
|
||||
type AsyncMochaSuite = (this: Mocha.Suite) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Describe a suite of async tests. This wraps mocha's describe to track async resources and report leaks.
|
||||
*/
|
||||
export function describeLeaks(description: string, cb: AsyncMochaSuite): Mocha.Suite {
|
||||
return describe(description, function trackSuite(this: Mocha.Suite) {
|
||||
const tracker = new AsyncTracker();
|
||||
tracker.filter = filterStack;
|
||||
|
||||
beforeEach(() => {
|
||||
tracker.enable();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tracker.disable();
|
||||
const leaked = tracker.size;
|
||||
|
||||
// @TODO: this should only exclude the single Immediate set by the Tracker
|
||||
if (leaked > 1) {
|
||||
tracker.dump();
|
||||
const msg = `test leaked ${leaked - 1} async resources`;
|
||||
if (isDebug()) {
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.warn(msg);
|
||||
}
|
||||
}
|
||||
|
||||
tracker.clear();
|
||||
});
|
||||
|
||||
/* eslint-disable-next-line no-invalid-this */
|
||||
const suite: PromiseLike<void> | undefined = cb.call(this);
|
||||
if (isNil(suite) || !Reflect.has(suite, 'then')) {
|
||||
/* eslint-disable-next-line no-console */
|
||||
console.error(`test suite '${description}' did not return a promise`);
|
||||
}
|
||||
|
||||
return suite;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an asynchronous test with unhandled rejection guards.
|
||||
*
|
||||
* This function may not have any direct test coverage. It is too simple to reasonably mock.
|
||||
*/
|
||||
export function itLeaks(expectation: string, cb?: AsyncMochaTest): Mocha.Test {
|
||||
if (isNil(cb)) {
|
||||
return it(expectation);
|
||||
}
|
||||
|
||||
return it(expectation, function trackTest(this: Mocha.Context) {
|
||||
return new Promise<unknown>((res, rej) => {
|
||||
/* eslint-disable-next-line no-invalid-this */
|
||||
cb.call(this).then((value: unknown) => {
|
||||
res(value);
|
||||
}, (err: Error) => {
|
||||
rej(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -6,7 +6,6 @@ import { spy, stub } from 'sinon';
|
|||
import { loadRuleFiles, loadRuleModules, loadRulePaths, loadRuleSource } from '../../src/rule';
|
||||
import { SchemaRule } from '../../src/rule/SchemaRule';
|
||||
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||
|
||||
const EXAMPLE_EMPTY = '{name: foo, definitions: {}, rules: []}';
|
||||
const EXAMPLE_RULES = `{
|
||||
|
@ -32,8 +31,8 @@ function testContext() {
|
|||
});
|
||||
}
|
||||
|
||||
describeLeaks('load rule file helper', async () => {
|
||||
itLeaks('should add schema', async () => {
|
||||
describe('load rule file helper', async () => {
|
||||
it('should add schema', async () => {
|
||||
mockFS({
|
||||
test: EXAMPLE_EMPTY,
|
||||
});
|
||||
|
@ -58,7 +57,7 @@ describeLeaks('load rule file helper', async () => {
|
|||
expect(rules.length).to.equal(0);
|
||||
});
|
||||
|
||||
itLeaks('should load rules', async () => {
|
||||
it('should load rules', async () => {
|
||||
mockFS({
|
||||
test: EXAMPLE_RULES,
|
||||
});
|
||||
|
@ -81,7 +80,7 @@ describeLeaks('load rule file helper', async () => {
|
|||
expect(rules.length).to.equal(1);
|
||||
});
|
||||
|
||||
itLeaks('should validate rule files', async () => {
|
||||
it('should validate rule files', async () => {
|
||||
mockFS({
|
||||
test: `{
|
||||
name: foo,
|
||||
|
@ -109,8 +108,8 @@ describeLeaks('load rule file helper', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('load rule path helper', async () => {
|
||||
itLeaks('should only load matching rule files', async () => {
|
||||
describe('load rule path helper', async () => {
|
||||
it('should only load matching rule files', async () => {
|
||||
mockFS({
|
||||
test: {
|
||||
'bin.nope': '{}', // will parse but throw on lack of rules
|
||||
|
@ -136,7 +135,7 @@ describeLeaks('load rule path helper', async () => {
|
|||
expect(rules.length).to.equal(1);
|
||||
});
|
||||
|
||||
itLeaks('should recursively load rule files', async () => {
|
||||
it('should recursively load rule files', async () => {
|
||||
mockFS({
|
||||
test: {
|
||||
'bar-dir': {
|
||||
|
@ -169,8 +168,8 @@ describeLeaks('load rule path helper', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('load rule module helper', async () => {
|
||||
itLeaks('should load rule modules', async () => {
|
||||
describe('load rule module helper', async () => {
|
||||
it('should load rule modules', async () => {
|
||||
const ctx = testContext();
|
||||
const requireStub = stub().withArgs('test').returns({
|
||||
name: 'test',
|
||||
|
@ -187,7 +186,7 @@ describeLeaks('load rule module helper', async () => {
|
|||
expect(rules.length).to.equal(1);
|
||||
});
|
||||
|
||||
itLeaks('should handle errors loading rule modules', async () => {
|
||||
it('should handle errors loading rule modules', async () => {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
const requireStub = stub().throws(new Error('could not load this module')) as any;
|
||||
const ctx = testContext();
|
||||
|
@ -195,7 +194,7 @@ describeLeaks('load rule module helper', async () => {
|
|||
return expect(loadRuleModules(['test'], ctx, requireStub)).to.eventually.deep.equal([]);
|
||||
});
|
||||
|
||||
itLeaks('should validate rule module exports', async () => {
|
||||
it('should validate rule module exports', async () => {
|
||||
const requireStub = stub().returns({
|
||||
name: 'test-rules',
|
||||
rules: {},
|
||||
|
@ -206,7 +205,7 @@ describeLeaks('load rule module helper', async () => {
|
|||
return expect(loadRuleModules(['test'], ctx, requireStub)).to.eventually.deep.equal([]);
|
||||
});
|
||||
|
||||
itLeaks('should load module definitions', async () => {
|
||||
it('should load module definitions', async () => {
|
||||
const requireStub = stub().returns({
|
||||
definitions: {
|
||||
foo: {
|
||||
|
@ -229,7 +228,7 @@ describeLeaks('load rule module helper', async () => {
|
|||
expect(schema('foo')).to.equal(true);
|
||||
});
|
||||
|
||||
itLeaks('should not instantiate class instances', async () => {
|
||||
it('should not instantiate class instances', async () => {
|
||||
class TestRule extends SchemaRule {}
|
||||
const ctx = testContext();
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import { ConsoleLogger, LogLevel, NullLogger } from 'noicejs';
|
|||
import { createRuleSelector, createRuleSources, resolveRules, validateRules } from '../../src/rule';
|
||||
import { SchemaRule } from '../../src/rule/SchemaRule';
|
||||
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||
|
||||
const TEST_RULES = [new SchemaRule({
|
||||
check: {},
|
||||
|
@ -29,9 +28,9 @@ const TEST_RULES = [new SchemaRule({
|
|||
tags: ['all', 'test'],
|
||||
})];
|
||||
|
||||
describeLeaks('rule resolver', async () => {
|
||||
describeLeaks('include by level', async () => {
|
||||
itLeaks('should include info rules', async () => {
|
||||
describe('rule resolver', async () => {
|
||||
describe('include by level', async () => {
|
||||
it('should include info rules', async () => {
|
||||
const info = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
includeLevel: [LogLevel.Info],
|
||||
}));
|
||||
|
@ -40,7 +39,7 @@ describeLeaks('rule resolver', async () => {
|
|||
expect(info[0]).to.equal(TEST_RULES[0]);
|
||||
});
|
||||
|
||||
itLeaks('should include warn rules', async () => {
|
||||
it('should include warn rules', async () => {
|
||||
const info = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
includeLevel: [LogLevel.Warn],
|
||||
}));
|
||||
|
@ -53,8 +52,8 @@ describeLeaks('rule resolver', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('include by name', async () => {
|
||||
itLeaks('should include foo rules', async () => {
|
||||
describe('include by name', async () => {
|
||||
it('should include foo rules', async () => {
|
||||
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
includeName: ['foo'],
|
||||
}));
|
||||
|
@ -64,8 +63,8 @@ describeLeaks('rule resolver', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('include by tag', async () => {
|
||||
itLeaks('should include test rules', async () => {
|
||||
describe('include by tag', async () => {
|
||||
it('should include test rules', async () => {
|
||||
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
includeTag: ['test'],
|
||||
}));
|
||||
|
@ -78,8 +77,8 @@ describeLeaks('rule resolver', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('exclude by name', async () => {
|
||||
itLeaks('should exclude foo rules', async () => {
|
||||
describe('exclude by name', async () => {
|
||||
it('should exclude foo rules', async () => {
|
||||
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
excludeName: ['foo'],
|
||||
includeTag: ['all'],
|
||||
|
@ -93,8 +92,8 @@ describeLeaks('rule resolver', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('exclude by tag', async () => {
|
||||
itLeaks('should exclude test rules', async () => {
|
||||
describe('exclude by tag', async () => {
|
||||
it('should exclude test rules', async () => {
|
||||
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
excludeTag: ['test'],
|
||||
includeTag: ['all'],
|
||||
|
@ -105,8 +104,8 @@ describeLeaks('rule resolver', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('exclude by level', async () => {
|
||||
itLeaks('should exclude warn rules', async () => {
|
||||
describe('exclude by level', async () => {
|
||||
it('should exclude warn rules', async () => {
|
||||
const rules = await resolveRules(TEST_RULES, createRuleSelector({
|
||||
excludeLevel: [LogLevel.Warn],
|
||||
includeTag: ['all'],
|
||||
|
@ -141,8 +140,8 @@ describe('create rule selector helper', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describeLeaks('validate rule helper', async () => {
|
||||
itLeaks('should accept valid modules', async () => {
|
||||
describe('validate rule helper', async () => {
|
||||
it('should accept valid modules', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: ConsoleLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -158,7 +157,7 @@ describeLeaks('validate rule helper', async () => {
|
|||
})).to.equal(true);
|
||||
});
|
||||
|
||||
itLeaks('should reject partial modules', async () => {
|
||||
it('should reject partial modules', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
|
|
@ -5,10 +5,9 @@ import { mock, spy, stub } from 'sinon';
|
|||
import { RuleVisitor } from '../../src/rule/RuleVisitor';
|
||||
import { SchemaRule } from '../../src/rule/SchemaRule';
|
||||
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||
|
||||
describeLeaks('rule visitor', async () => {
|
||||
itLeaks('should only call visit for selected items', async () => {
|
||||
describe('rule visitor', async () => {
|
||||
it('should only call visit for selected items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -43,7 +42,7 @@ describeLeaks('rule visitor', async () => {
|
|||
expect(ctx.errors.length).to.equal(0);
|
||||
});
|
||||
|
||||
itLeaks('should call visit for each selected item', async () => {
|
||||
it('should call visit for each selected item', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -81,7 +80,7 @@ describeLeaks('rule visitor', async () => {
|
|||
expect(ctx.errors.length).to.equal(0);
|
||||
});
|
||||
|
||||
itLeaks('should visit individual items', async () => {
|
||||
it('should visit individual items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -117,7 +116,7 @@ describeLeaks('rule visitor', async () => {
|
|||
expect(visitStub).to.have.callCount(data.foo.length);
|
||||
});
|
||||
|
||||
itLeaks('should visit individual items', async () => {
|
||||
it('should visit individual items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -157,7 +156,7 @@ describeLeaks('rule visitor', async () => {
|
|||
expect(ctx.errors.length).to.equal(EXPECTED_VISITS);
|
||||
});
|
||||
|
||||
itLeaks('should not pick items', async () => {
|
||||
it('should not pick items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
|
|
@ -4,14 +4,13 @@ import { stub } from 'sinon';
|
|||
|
||||
import { friendlyError, SchemaRule } from '../../src/rule/SchemaRule';
|
||||
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
||||
import { describeLeaks, itLeaks } from '../helpers/async';
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
const TEST_NAME = 'test-rule';
|
||||
|
||||
describeLeaks('schema rule', async () => {
|
||||
itLeaks('should pick items from the scope', async () => {
|
||||
describe('schema rule', async () => {
|
||||
it('should pick items from the scope', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -36,7 +35,7 @@ describeLeaks('schema rule', async () => {
|
|||
expect(results).to.deep.equal([data.foo]);
|
||||
});
|
||||
|
||||
itLeaks('should pick no items', async () => {
|
||||
it('should pick no items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -61,7 +60,7 @@ describeLeaks('schema rule', async () => {
|
|||
expect(results).to.deep.equal([]);
|
||||
});
|
||||
|
||||
itLeaks('should filter out items', async () => {
|
||||
it('should filter out items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -95,7 +94,7 @@ describeLeaks('schema rule', async () => {
|
|||
expect(results.errors.length).to.equal(0);
|
||||
});
|
||||
|
||||
itLeaks('should pick items from the root', async () => {
|
||||
it('should pick items from the root', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -118,7 +117,7 @@ describeLeaks('schema rule', async () => {
|
|||
expect(Array.isArray(results)).to.equal(true);
|
||||
});
|
||||
|
||||
itLeaks('should visit selected items', async () => {
|
||||
it('should visit selected items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
@ -151,7 +150,7 @@ describeLeaks('schema rule', async () => {
|
|||
expect(checkSpy, 'check spy should have been called with data').to.have.callCount(1).and.been.calledWithExactly(data);
|
||||
});
|
||||
|
||||
itLeaks('should skip filtered items', async () => {
|
||||
it('should skip filtered items', async () => {
|
||||
const ctx = new VisitorContext({
|
||||
logger: NullLogger.global,
|
||||
schemaOptions: {
|
||||
|
|
Loading…
Reference in New Issue