1
0
Fork 0

remove(test): async leak helpers (#313)

This commit is contained in:
Sean Sube 2020-04-02 18:58:57 -05:00 committed by GitHub
parent 0436db33a5
commit cb3ee1fa8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 58 additions and 138 deletions

View File

@ -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([

View File

@ -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',

View File

@ -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)
);
});

View File

@ -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);
});
});
});
}

View File

@ -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();

View File

@ -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: {

View File

@ -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: {

View File

@ -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: {