1
0
Fork 0

fix(test): cover pid file, signal helpers, improve coverage for others

This commit is contained in:
ssube 2020-03-30 21:31:51 -05:00
parent f751652033
commit 76e2ba46dd
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
16 changed files with 212 additions and 27 deletions

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@apextoaster/js-utils](./js-utils.md) &gt; [AsyncTracker](./js-utils.asynctracker.md) &gt; [filter](./js-utils.asynctracker.filter.md)
## AsyncTracker.filter property
<b>Signature:</b>
```typescript
filter: Optional<StackFilter>;
```

View File

@ -7,7 +7,7 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
static getStack(): string; getStack(): string;
``` ```
<b>Returns:</b> <b>Returns:</b>

View File

@ -24,6 +24,7 @@ export declare class AsyncTracker
| Property | Modifiers | Type | Description | | Property | Modifiers | Type | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [filter](./js-utils.asynctracker.filter.md) | | <code>Optional&lt;StackFilter&gt;</code> | |
| [size](./js-utils.asynctracker.size.md) | | <code>number</code> | | | [size](./js-utils.asynctracker.size.md) | | <code>number</code> | |
## Methods ## Methods
@ -34,5 +35,5 @@ export declare class AsyncTracker
| [disable()](./js-utils.asynctracker.disable.md) | | | | [disable()](./js-utils.asynctracker.disable.md) | | |
| [dump()](./js-utils.asynctracker.dump.md) | | | | [dump()](./js-utils.asynctracker.dump.md) | | |
| [enable()](./js-utils.asynctracker.enable.md) | | | | [enable()](./js-utils.asynctracker.enable.md) | | |
| [getStack()](./js-utils.asynctracker.getstack.md) | <code>static</code> | | | [getStack()](./js-utils.asynctracker.getstack.md) | | |

View File

@ -4,6 +4,8 @@
## isNil() function ## isNil() function
Check if a value is nil.
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript

View File

@ -44,13 +44,13 @@
| [getOrDefault(map, key, defaultValue)](./js-utils.getordefault.md) | | | [getOrDefault(map, key, defaultValue)](./js-utils.getordefault.md) | |
| [getTestLogger(verbose)](./js-utils.gettestlogger.md) | | | [getTestLogger(verbose)](./js-utils.gettestlogger.md) | |
| [isDebug()](./js-utils.isdebug.md) | | | [isDebug()](./js-utils.isdebug.md) | |
| [isNil(val)](./js-utils.isnil.md) | | | [isNil(val)](./js-utils.isnil.md) | Check if a value is nil. |
| [leftPad(val, min, fill)](./js-utils.leftpad.md) | | | [leftPad(val, min, fill)](./js-utils.leftpad.md) | |
| [makeDict(map)](./js-utils.makedict.md) | Turns a map or dict into a dict | | [makeDict(map)](./js-utils.makedict.md) | Turns a map or dict into a dict |
| [makeMap(val)](./js-utils.makemap.md) | Clone a map or map-like object into a new map. | | [makeMap(val)](./js-utils.makemap.md) | Clone a map or map-like object into a new map. |
| [mergeList(parts)](./js-utils.mergelist.md) | Merge arguments, which may or may not be arrays, into one return that is definitely an array. | | [mergeList(parts)](./js-utils.mergelist.md) | Merge arguments, which may or may not be arrays, into one return that is definitely an array. |
| [mergeMap(target, source)](./js-utils.mergemap.md) | | | [mergeMap(target, source)](./js-utils.mergemap.md) | |
| [mustCoalesce(values)](./js-utils.mustcoalesce.md) | Return the first value that is not nil. | | [mustCoalesce(values)](./js-utils.mustcoalesce.md) | Return the first value that is not nil.<!-- -->TODO: rename to mustDefault |
| [mustExist(val)](./js-utils.mustexist.md) | Assert that a variable is not nil and return the value. | | [mustExist(val)](./js-utils.mustexist.md) | Assert that a variable is not nil and return the value. |
| [mustFind(list, predicate)](./js-utils.mustfind.md) | Find a value matching the given predicate or throw. | | [mustFind(list, predicate)](./js-utils.mustfind.md) | Find a value matching the given predicate or throw. |
| [mustGet(map, key)](./js-utils.mustget.md) | Get an element from a Map and guard against nil values. | | [mustGet(map, key)](./js-utils.mustget.md) | Get an element from a Map and guard against nil values. |

View File

@ -6,6 +6,8 @@
Return the first value that is not nil. Return the first value that is not nil.
TODO: rename to mustDefault
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript

View File

@ -28,6 +28,7 @@
"@types/chai-as-promised": "7.1.2", "@types/chai-as-promised": "7.1.2",
"@types/lodash": "4.14.149", "@types/lodash": "4.14.149",
"@types/mocha": "7.0.2", "@types/mocha": "7.0.2",
"@types/mock-fs": "^4.10.0",
"@types/node": "13.9.5", "@types/node": "13.9.5",
"@types/sinon-chai": "3.2.3", "@types/sinon-chai": "3.2.3",
"@types/source-map-support": "0.5.1", "@types/source-map-support": "0.5.1",
@ -45,6 +46,7 @@
"eslint-plugin-sonarjs": "0.5.0", "eslint-plugin-sonarjs": "0.5.0",
"lodash": "4.17.15", "lodash": "4.17.15",
"mocha": "7.1.1", "mocha": "7.1.1",
"mock-fs": "^4.11.0",
"noicejs": "3.0.1", "noicejs": "3.0.1",
"nyc": "15.0.0", "nyc": "15.0.0",
"rollup": "2.3.1", "rollup": "2.3.1",

View File

@ -1,6 +1,6 @@
import { AsyncHook, createHook } from 'async_hooks'; import { AsyncHook, createHook } from 'async_hooks';
import { isNil } from './utils'; import { isNil, Optional } from './utils';
import { isDebug } from './utils/Env'; import { isDebug } from './utils/Env';
export interface TrackedResource { export interface TrackedResource {
@ -9,6 +9,8 @@ export interface TrackedResource {
type: string; type: string;
} }
export type StackFilter = (stack: string) => string;
/** /**
* Async resource tracker using node's internal hooks. * Async resource tracker using node's internal hooks.
* *
@ -16,15 +18,7 @@ export interface TrackedResource {
* Adapted from https://gist.github.com/boneskull/7fe75b63d613fa940db7ec990a5f5843#file-async-dump-js * Adapted from https://gist.github.com/boneskull/7fe75b63d613fa940db7ec990a5f5843#file-async-dump-js
*/ */
export class AsyncTracker { export class AsyncTracker {
public static getStack(): string { public filter: Optional<StackFilter>;
const err = new Error();
if (isNil(err.stack)) {
return 'no stack trace available';
} else {
return err.stack; // TODO: filterStack(err.stack);
}
}
private readonly hook: AsyncHook; private readonly hook: AsyncHook;
private readonly resources: Map<number, TrackedResource>; private readonly resources: Map<number, TrackedResource>;
@ -35,7 +29,7 @@ export class AsyncTracker {
this.resources.delete(id); this.resources.delete(id);
}, },
init: (id: number, type: string, triggerAsyncId: number) => { init: (id: number, type: string, triggerAsyncId: number) => {
const source = AsyncTracker.getStack(); const source = this.getStack();
// @TODO: exclude async hooks, including this one // @TODO: exclude async hooks, including this one
this.resources.set(id, { this.resources.set(id, {
source, source,
@ -49,6 +43,19 @@ export class AsyncTracker {
}); });
} }
public getStack(): string {
const err = new Error();
if (isNil(err.stack)) {
return 'no stack trace available';
}
if (isNil(this.filter)) {
return err.stack;
}
return this.filter(err.stack);
}
public clear() { public clear() {
this.resources.clear(); this.resources.clear();
} }

View File

@ -1,4 +1,5 @@
import { TimeoutError } from '../error/TimeoutError'; import { TimeoutError } from '../error/TimeoutError';
import { PredicateC0 } from '.';
/** /**
* Resolve after a set amount of time. * Resolve after a set amount of time.
@ -24,7 +25,7 @@ export function timeout<T>(ms: number, oper: Promise<T>): Promise<T> {
return Promise.race([limit, oper]); return Promise.race([limit, oper]);
} }
export async function waitFor(cb: () => boolean, step: number, count: number): Promise<void> { export async function waitFor(cb: PredicateC0, step: number, count: number): Promise<void> {
let accum = 0; let accum = 0;
while (accum < count) { while (accum < count) {
await defer(step); await defer(step);

View File

@ -6,26 +6,49 @@ import { NotFoundError } from '../error/NotFoundError';
/* eslint-disable-next-line @typescript-eslint/ban-types */ /* eslint-disable-next-line @typescript-eslint/ban-types */
export type Nil = null | undefined; export type Nil = null | undefined;
export type SortAfter = 1;
export type SortBefore = -1;
export type SortEqual = 0;
export type SortOrder = SortAfter | SortBefore | SortEqual;
/** /**
* Value that may be nil. * Value that may be nil.
*/ */
export type Optional<T> = T | Nil; export type Optional<T> = T | Nil;
/** /**
* Comparison (filter) predicate for a single value. * Comparison predicate for arity 0 - assert?
*/
export type PredicateC0 = () => boolean;
/**
* Comparison predicate for arity 1 - filter.
*/ */
export type PredicateC1<TVal> = (val: TVal, idx: number, list: Array<TVal>) => boolean; export type PredicateC1<TVal> = (val: TVal, idx: number, list: Array<TVal>) => boolean;
/** /**
* Comparison (sort) predicate for two values. * Comparison predicate for arity 2 - sort.
*/ */
export type PredicateC2<TVal> = (pval: TVal, nval: TVal, idx: number, list: Array<TVal>) => number; export type PredicateC2<TVal> = (pval: TVal, nval: TVal, idx: number, list: Array<TVal>) => SortOrder;
/** /**
* Reduction predicate for two values. * Transform predicate for arity 0 - constructor.
*/
export type PredicateR0<TVal> = () => TVal;
/**
* Transform predicate for arity 1 - map.
*/
export type PredicateR1<TVal> = (val: TVal, idx: number, list: Array<TVal>) => TVal;
/**
* Transform predicate for arity 2 - reduce.
*/ */
export type PredicateR2<TVal> = (pval: TVal, nval: TVal, idx: number, list: Array<TVal>) => TVal; export type PredicateR2<TVal> = (pval: TVal, nval: TVal, idx: number, list: Array<TVal>) => TVal;
/**
* Check if a value is nil.
*/
/* eslint-disable-next-line @typescript-eslint/ban-types */ /* eslint-disable-next-line @typescript-eslint/ban-types */
export function isNil<T>(val: Optional<T>): val is Nil { export function isNil<T>(val: Optional<T>): val is Nil {
/* eslint-disable-next-line no-null/no-null */ /* eslint-disable-next-line no-null/no-null */
@ -113,6 +136,8 @@ export function mustExist<T>(val: Optional<T>): T {
/** /**
* Return the first value that is not nil. * Return the first value that is not nil.
*
* TODO: rename to mustDefault
*/ */
export function mustCoalesce<T>(...values: Array<Optional<T>>): T { export function mustCoalesce<T>(...values: Array<Optional<T>>): T {
return mustFind(values, doesExist); return mustFind(values, doesExist);

View File

@ -13,6 +13,7 @@ import {
setOrPush, setOrPush,
mergeMap, mergeMap,
pushMergeMap, pushMergeMap,
normalizeMap,
} from '../../src/utils/Map'; } from '../../src/utils/Map';
import { describeLeaks, itLeaks } from '../helpers/async'; import { describeLeaks, itLeaks } from '../helpers/async';
@ -206,4 +207,28 @@ describeLeaks('map utils', async () => {
})).to.deep.equal(singleItem); })).to.deep.equal(singleItem);
}); });
}); });
describe('normalize map helper', () => {
it('should convert values into arrays of strings', () => {
const banVal = [Symbol()];
const initial = new Map<string, unknown>([
['bar', 'bin'],
['ban', banVal],
['toad', {
toString() {
return 'too';
},
}],
]);
const normalized = normalizeMap(initial);
expect(normalized.bar).to.deep.equal(['bin']);
expect(normalized.ban).to.equal(banVal);
expect(normalized.toad).to.deep.equal(['too']);
});
/* ['foo', 1] */
xit('should convert numbers into string values');
});
}); });

42
test/utils/TestPidFile.ts Normal file
View File

@ -0,0 +1,42 @@
import { expect } from 'chai';
import mockFS from 'mock-fs';
import { removePid, writePid } from '../../src';
import { describeLeaks, itLeaks } from '../helpers/async';
const PID_PATH = 'foo';
const PID_NAME = 'foo/test.pid';
describeLeaks('pid file utils', async () => {
beforeEach(() => {
mockFS({
[PID_PATH]: mockFS.directory(),
});
});
afterEach(() => {
mockFS.restore();
});
itLeaks('should create a marker', async () => {
await writePid(PID_NAME);
mockFS.restore();
});
itLeaks('should not replace an existing marker', async () => {
await writePid(PID_NAME);
return expect(writePid(PID_PATH)).to.eventually.be.rejectedWith(Error);
});
itLeaks('should remove an existing marker', async () => {
await writePid(PID_NAME);
await removePid(PID_NAME);
mockFS.restore();
});
itLeaks('should fail to remove a missing marker', async () =>
expect(removePid(PID_PATH)).to.eventually.be.rejectedWith(Error)
);
});

View File

@ -1,18 +1,33 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { getMethods } from '../../src/utils/Reflect'; import { getMethods, getConstructor, constructorName } from '../../src/utils/Reflect';
describe('reflect utils', () => {
describe('get methods helper', () => {
it('should collect method functions', () => {
class Test { class Test {
public foo() { /* noop */ } public foo() { /* noop */ }
public bar() { /* noop */ } public bar() { /* noop */ }
} }
describe('reflect utils', () => {
describe('get methods helper', () => {
it('should collect method functions', () => {
const methods = getMethods(new Test()).values(); const methods = getMethods(new Test()).values();
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
expect(methods).to.include(Test.prototype.foo); expect(methods).to.include(Test.prototype.foo);
expect(methods).to.include(Test.prototype.bar); expect(methods).to.include(Test.prototype.bar);
}); });
}); });
describe('get constructor helper', () => {
it('should get the constructor from an instance', () => {
const instance = new Test();
expect(getConstructor(instance)).to.equal(Test);
});
});
describe('get constructor name helper', () => {
it('should get the constructor name from an instance', () => {
const instance = new Test();
expect(constructorName(instance)).to.equal(Test.name);
});
});
}); });

20
test/utils/TestSignal.ts Normal file
View File

@ -0,0 +1,20 @@
import { expect } from 'chai';
import { timeout } from '../../src/utils/Async';
import { signal, SIGNAL_RESET } from '../../src/utils/Signal';
import { describeLeaks, itLeaks } from '../helpers/async';
const MAX_SIGNAL_TIME = 500;
describeLeaks('signal utils', async () => {
itLeaks('should wait for a signal', async () => {
const signalPromise = signal(SIGNAL_RESET);
process.kill(process.pid, SIGNAL_RESET);
await timeout(MAX_SIGNAL_TIME, signalPromise);
const signalValue = await signalPromise;
expect(signalValue).to.equal(SIGNAL_RESET);
});
});

View File

@ -1,7 +1,7 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { NotFoundError } from '../../src/error/NotFoundError'; import { NotFoundError } from '../../src/error/NotFoundError';
import { countOf, filterNil, mustFind } from '../../src/utils'; import { countOf, filterNil, mustFind, defaultWhen, mustCoalesce } from '../../src/utils';
import { describeLeaks, itLeaks } from '../helpers/async'; import { describeLeaks, itLeaks } from '../helpers/async';
describeLeaks('utils', async () => { describeLeaks('utils', async () => {
@ -42,4 +42,24 @@ describeLeaks('utils', async () => {
}).to.throw(NotFoundError); }).to.throw(NotFoundError);
}); });
}); });
describeLeaks('default when', async () => {
itLeaks('should return the first item when the condition is true', async () => {
expect(defaultWhen(true, 1, 2)).to.equal(1);
});
itLeaks('should return the second item otherwise', async () => {
expect(defaultWhen(false, 1, 2)).to.equal(2);
});
});
describeLeaks('must coalesce helper', async () => {
/* eslint-disable no-null/no-null */
itLeaks('should return the first existent value', async () => {
expect(mustCoalesce(null, null, 3, null)).to.equal(3);
expect(mustCoalesce(null, null, undefined, 'string')).to.equal('string');
expect(mustCoalesce(null, undefined, [], null)).to.deep.equal([]);
});
/* eslint-enable no-null/no-null */
});
}); });

View File

@ -383,6 +383,13 @@
resolved "https://artifacts.apextoaster.com/repository/group-npm/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" resolved "https://artifacts.apextoaster.com/repository/group-npm/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce"
integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==
"@types/mock-fs@^4.10.0":
version "4.10.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@types/mock-fs/-/mock-fs-4.10.0.tgz#460061b186993d76856f669d5317cda8a007c24b"
integrity sha512-FQ5alSzmHMmliqcL36JqIA4Yyn9jyJKvRSGV3mvPh108VFatX7naJDzSG4fnFQNZFq9dIx0Dzoe6ddflMB2Xkg==
dependencies:
"@types/node" "*"
"@types/node@*": "@types/node@*":
version "12.7.8" version "12.7.8"
resolved "https://artifacts.apextoaster.com/repository/group-npm/@types/node/-/node-12.7.8.tgz#cb1bf6800238898bc2ff6ffa5702c3cadd350708" resolved "https://artifacts.apextoaster.com/repository/group-npm/@types/node/-/node-12.7.8.tgz#cb1bf6800238898bc2ff6ffa5702c3cadd350708"
@ -2567,6 +2574,11 @@ mocha@7.1.1:
yargs-parser "13.1.2" yargs-parser "13.1.2"
yargs-unparser "1.6.0" yargs-unparser "1.6.0"
mock-fs@^4.11.0:
version "4.11.0"
resolved "https://artifacts.apextoaster.com/repository/group-npm/mock-fs/-/mock-fs-4.11.0.tgz#0828107e4b843a6ba855ecebfe3c6e073b69db92"
integrity sha512-Yp4o3/ZA15wsXqJTT+R+9w2AYIkD1i80Lds47wDbuUhOvQvm+O2EfjFZSz0pMgZZSPHRhGxgcd2+GL4+jZMtdw==
modify-values@^1.0.0: modify-values@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://artifacts.apextoaster.com/repository/group-npm/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" resolved "https://artifacts.apextoaster.com/repository/group-npm/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"