Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent premature exit after an assertion failure #662

Merged
merged 3 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 1 addition & 18 deletions .github/workflows/ci_compatibility-nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version:
[
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'latest',
]
node-version: ['08', '10', '12', '14', '16', '18', '20', 'latest']
name: Node.js ${{ matrix.node-version }}
steps:
- name: ➕ Actions - Checkout
Expand Down
335 changes: 335 additions & 0 deletions src/builders/assert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
import type { ProcessAssertionOptions } from '../@types/assert.js';
import type assert from 'node:assert';
import type { AssertPredicate } from 'node:assert';
import { nodeVersion } from '../parsers/get-runtime.js';
import { processAssert, processAsyncAssert } from '../services/assert.js';

export const createAssert = (nodeAssert: typeof assert) => {
const ok = (
value: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(
() => {
nodeAssert.ok(value);
},
{ message }
);
};

const equal = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(
() => {
nodeAssert.equal(actual, expected);
},
{ message }
);
};

const deepEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.deepEqual(actual, expected), { message });
};

const strictEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.strictEqual(actual, expected), { message });
};

const deepStrictEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.deepStrictEqual(actual, expected), {
message,
});
};

const notEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.notEqual(actual, expected), {
message,
});
};

const notDeepEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.notDeepEqual(actual, expected), { message });
};

const notStrictEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.notStrictEqual(actual, expected), {
message,
});
};

const notDeepStrictEqual = (
actual: unknown,
expected: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(() => nodeAssert.notDeepStrictEqual(actual, expected), {
message,
});
};

const ifError = (
value: unknown,
message?: ProcessAssertionOptions['message']
): void => {
processAssert(
() => {
nodeAssert.ifError(value);
},
{
message,
defaultMessage: 'Expected no error, but received an error',
hideDiff: true,
throw: true,
}
);
};

const fail = (message?: ProcessAssertionOptions['message']): never => {
processAssert(
() => {
nodeAssert.fail(message);
},
{
message,
defaultMessage: 'Test failed intentionally',
hideDiff: true,
}
);

process.exit(1);
};

function doesNotThrow(
block: () => unknown,
message?: string | ProcessAssertionOptions['message']
): void;
function doesNotThrow(
block: () => unknown,
error: AssertPredicate,
message?: ProcessAssertionOptions['message']
): void;
function doesNotThrow(
block: () => unknown,
errorOrMessage?: AssertPredicate | ProcessAssertionOptions['message'],
message?: ProcessAssertionOptions['message']
): void {
processAssert(
() => {
if (
typeof errorOrMessage === 'function' ||
errorOrMessage instanceof RegExp ||
typeof errorOrMessage === 'object'
) {
nodeAssert.doesNotThrow(block, errorOrMessage, message);
} else {
const msg =
typeof errorOrMessage === 'string' ? errorOrMessage : message;
nodeAssert.doesNotThrow(block, msg);
}
},
{
message: typeof errorOrMessage === 'string' ? errorOrMessage : message,
defaultMessage: 'Expected function not to throw',
hideDiff: true,
throw: true,
}
);
}

function throws(
block: () => unknown,
message?: ProcessAssertionOptions['message']
): void;
function throws(
block: () => unknown,
error: AssertPredicate,
message?: ProcessAssertionOptions['message']
): void;
function throws(
block: () => unknown,
errorOrMessage?: AssertPredicate | ProcessAssertionOptions['message'],
message?: ProcessAssertionOptions['message']
): void {
if (
typeof errorOrMessage === 'function' ||
errorOrMessage instanceof RegExp ||
typeof errorOrMessage === 'object'
) {
processAssert(() => nodeAssert.throws(block, errorOrMessage), {
message,
defaultMessage: 'Expected function to throw',
hideDiff: true,
});
} else {
const msg =
typeof errorOrMessage !== 'undefined' ? errorOrMessage : message;

processAssert(() => nodeAssert.throws(block, message), {
message: msg,
defaultMessage: 'Expected function to throw',
hideDiff: true,
});
}
}

function rejects(
block: (() => Promise<unknown>) | Promise<unknown>,
message?: ProcessAssertionOptions['message']
): Promise<void>;
function rejects(
block: (() => Promise<unknown>) | Promise<unknown>,
error: AssertPredicate,
message?: ProcessAssertionOptions['message']
): Promise<void>;
async function rejects(
block: (() => Promise<unknown>) | Promise<unknown>,
errorOrMessage?: AssertPredicate | ProcessAssertionOptions['message'],
message?: ProcessAssertionOptions['message']
): Promise<void> {
await processAsyncAssert(
async () => {
if (
typeof errorOrMessage === 'function' ||
errorOrMessage instanceof RegExp ||
typeof errorOrMessage === 'object'
) {
await nodeAssert.rejects(block, errorOrMessage, message);
} else {
const msg =
typeof errorOrMessage === 'string' ? errorOrMessage : message;
await nodeAssert.rejects(block, msg);
}
},
{
message: typeof errorOrMessage === 'string' ? errorOrMessage : message,
defaultMessage: 'Expected promise to be rejected with specified error',
hideDiff: true,
throw: true,
}
);
}

function doesNotReject(
block: (() => Promise<unknown>) | Promise<unknown>,
message?: ProcessAssertionOptions['message']
): Promise<void>;
function doesNotReject(
block: (() => Promise<unknown>) | Promise<unknown>,
error: AssertPredicate,
message?: ProcessAssertionOptions['message']
): Promise<void>;
async function doesNotReject(
block: (() => Promise<unknown>) | Promise<unknown>,
errorOrMessage?: AssertPredicate | ProcessAssertionOptions['message'],
message?: ProcessAssertionOptions['message']
): Promise<void> {
await processAsyncAssert(
async () => {
if (
typeof errorOrMessage === 'function' ||
errorOrMessage instanceof RegExp ||
typeof errorOrMessage === 'object'
) {
await nodeAssert.doesNotReject(block, errorOrMessage, message);
} else {
await nodeAssert.doesNotReject(block, message);
}
},
{
message: typeof errorOrMessage === 'string' ? errorOrMessage : message,
defaultMessage: 'Got unwanted rejection',
hideDiff: true,
throw: true,
}
);
}

const match = (
value: string,
regExp: RegExp,
message?: ProcessAssertionOptions['message']
): void => {
/* c8 ignore next 3 */ // Platform version
if (typeof nodeVersion === 'number' && nodeVersion < 12) {
throw new Error('match is available from Node.js 12 or higher');
}

processAssert(() => nodeAssert?.match(value, regExp), {
message,
actual: 'Value',
expected: 'RegExp',
defaultMessage: 'Value should match regExp',
});
};

const doesNotMatch = (
value: string,
regExp: RegExp,
message?: ProcessAssertionOptions['message']
): void => {
/* c8 ignore next 3 */ // Platform version
if (typeof nodeVersion === 'number' && nodeVersion < 12) {
throw new Error('doesNotMatch is available from Node.js 12 or higher');
}

processAssert(() => nodeAssert.doesNotMatch(value, regExp), {
message,
actual: 'Value',
expected: 'RegExp',
defaultMessage: 'Value should not match regExp',
});
};

const assert = Object.assign(
(value: unknown, message?: ProcessAssertionOptions['message']) =>
ok(value, message),
{
ok,
equal,
deepEqual,
strictEqual,
deepStrictEqual,
doesNotMatch,
doesNotReject,
throws,
doesNotThrow,
notEqual,
notDeepEqual,
notStrictEqual,
notDeepStrictEqual,
match,
ifError,
fail,
rejects,
}
);

return assert;
};
2 changes: 1 addition & 1 deletion src/modules/essentials/assert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import nodeAssert from 'node:assert';
import { createAssert } from '../../services/assert.js';
import { createAssert } from '../../builders/assert.js';

export const assert = createAssert(nodeAssert);
1 change: 1 addition & 0 deletions src/modules/essentials/poku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export async function poku(
showLogs && showTestResults();

exit(code, configs?.quiet);
return;
}

// Parallel
Expand Down
2 changes: 1 addition & 1 deletion src/modules/essentials/strict.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import nodeAssert from 'node:assert/strict';
import { createAssert } from '../../services/assert.js';
import { createAssert } from '../../builders/assert.js';

export const strict = createAssert(nodeAssert);
Loading
Loading