1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-29 02:29:06 -05:00

Refactor asserts in testing (denoland/deno_std#227)

Original: c734e32343
This commit is contained in:
Vincent LE GOFF 2019-03-05 20:58:28 +01:00 committed by Ryan Dahl
parent 39fde3a454
commit 787207f11b
7 changed files with 261 additions and 140 deletions

179
testing/asserts.ts Normal file
View file

@ -0,0 +1,179 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { assertEqual as prettyAssertEqual } from "./pretty.ts";
interface Constructor {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new (...args: any[]): any;
}
/** Make an assertion, if not `true`, then throw. */
export function assert(expr: boolean, msg = ""): void {
if (!expr) {
throw new Error(msg);
}
}
/**
* Make an assertion that `actual` and `expected` are equal, deeply. If not
* deeply equal, then throw.
*/
export function equal(actual: unknown, expected: unknown, msg?: string): void {
prettyAssertEqual(actual, expected, msg);
}
/**
* Make an assertion that `actual` and `expected` are strictly equal. If
* not then throw.
*/
export function assertStrictEq(
actual: unknown,
expected: unknown,
msg?: string
): void {
if (actual !== expected) {
let actualString: string;
let expectedString: string;
try {
actualString = String(actual);
} catch (e) {
actualString = "[Cannot display]";
}
try {
expectedString = String(expected);
} catch (e) {
expectedString = "[Cannot display]";
}
console.error(
"strictEqual failed. actual =",
actualString,
"expected =",
expectedString
);
if (!msg) {
msg = `actual: ${actualString} expected: ${expectedString}`;
}
throw new Error(msg);
}
}
/**
* Make an assertion that actual contains expected. If not
* then thrown.
*/
export function assertStrContains(
actual: string,
expected: string,
msg?: string
): void {
if (!actual.includes(expected)) {
console.error(
"stringContains failed. actual =",
actual,
"not containing ",
expected
);
if (!msg) {
msg = `actual: "${actual}" expected to contains: "${expected}"`;
}
throw new Error(msg);
}
}
/**
* Make an assertion that `actual` match RegExp `expected`. If not
* then thrown
*/
export function assertMatch(
actual: string,
expected: RegExp,
msg?: string
): void {
if (!expected.test(actual)) {
console.error(
"stringMatching failed. actual =",
actual,
"not matching RegExp ",
expected
);
if (!msg) {
msg = `actual: "${actual}" expected to match: "${expected}"`;
}
throw new Error(msg);
}
}
/**
* Forcefully throws a failed assertion
*/
export function fail(msg?: string): void {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
assert(false, `Failed assertion${msg ? `: ${msg}` : "."}`);
}
/** Executes a function, expecting it to throw. If it does not, then it
* throws. An error class and a string that should be included in the
* error message can also be asserted.
*/
export function assertThrows(
fn: () => void,
ErrorClass?: Constructor,
msgIncludes = "",
msg?: string
): void {
let doesThrow = false;
try {
fn();
} catch (e) {
if (ErrorClass && !(Object.getPrototypeOf(e) === ErrorClass.prototype)) {
msg = `Expected error to be instance of "${ErrorClass.name}"${
msg ? `: ${msg}` : "."
}`;
throw new Error(msg);
}
if (msgIncludes) {
if (!e.message.includes(msgIncludes)) {
msg = `Expected error message to include "${msgIncludes}", but got "${
e.message
}"${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
doesThrow = true;
}
if (!doesThrow) {
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
export async function assertThrowsAsync(
fn: () => Promise<void>,
ErrorClass?: Constructor,
msgIncludes = "",
msg?: string
): Promise<void> {
let doesThrow = false;
try {
await fn();
} catch (e) {
if (ErrorClass && !(Object.getPrototypeOf(e) === ErrorClass.prototype)) {
msg = `Expected error to be instance of "${ErrorClass.name}"${
msg ? `: ${msg}` : "."
}`;
throw new Error(msg);
}
if (msgIncludes) {
if (!e.message.includes(msgIncludes)) {
msg = `Expected error message to include "${msgIncludes}", but got "${
e.message
}"${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
doesThrow = true;
}
if (!doesThrow) {
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}

46
testing/asserts_test.ts Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { assertStrContains, assertMatch } from "./asserts.ts";
import { test, assert } from "./mod.ts";
// import { assertEqual as prettyAssertEqual } from "./pretty.ts";
// import "./format_test.ts";
// import "./diff_test.ts";
// import "./pretty_test.ts";
test(function testingAssertStringContains() {
assertStrContains("Denosaurus", "saur");
assertStrContains("Denosaurus", "Deno");
assertStrContains("Denosaurus", "rus");
});
test(function testingAssertStringContainsThrow() {
let didThrow = false;
try {
assertStrContains("Denosaurus from Jurassic", "Raptor");
} catch (e) {
assert(
e.message ===
`actual: "Denosaurus from Jurassic" expected to contains: "Raptor"`
);
didThrow = true;
}
assert(didThrow);
});
test(function testingAssertStringMatching() {
assertMatch("foobar@deno.com", RegExp(/[a-zA-Z]+@[a-zA-Z]+.com/));
});
test(function testingAssertStringMatchingThrows() {
let didThrow = false;
try {
assertMatch("Denosaurus from Jurassic", RegExp(/Raptor/));
} catch (e) {
assert(
e.message ===
`actual: "Denosaurus from Jurassic" expected to match: "/Raptor/"`
);
didThrow = true;
}
assert(didThrow);
});

View file

@ -1,8 +1,6 @@
import { bench, runBenchmarks } from "./../benching/mod.ts"; import { bench, runBenchmarks } from "./../benching/mod.ts";
import { runTests } from "./mod.ts"; import { runTests } from "./mod.ts";
import "./test.ts";
bench(async function testingSerial(b) { bench(async function testingSerial(b) {
b.start(); b.start();
await runTests(); await runTests();

View file

@ -4,7 +4,11 @@ interface FarthestPoint {
id: number; id: number;
} }
export type DiffType = "removed" | "common" | "added"; export enum DiffType {
removed = "removed",
common = "common",
added = "added"
}
export interface DiffResult<T> { export interface DiffResult<T> {
type: DiffType; type: DiffType;
@ -50,12 +54,12 @@ export default function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return []; if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
if (!N) { if (!N) {
return [ return [
...prefixCommon.map(c => ({ type: "common" as DiffType, value: c })), ...prefixCommon.map(c => ({ type: DiffType.common, value: c })),
...A.map(a => ({ ...A.map(a => ({
type: (swapped ? "added" : "removed") as DiffType, type: swapped ? DiffType.added : DiffType.removed,
value: a value: a
})), })),
...suffixCommon.map(c => ({ type: "common" as DiffType, value: c })) ...suffixCommon.map(c => ({ type: DiffType.common, value: c }))
]; ];
} }
const offset = N; const offset = N;
@ -91,18 +95,18 @@ export default function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
const prev = j; const prev = j;
if (type === REMOVED) { if (type === REMOVED) {
result.unshift({ result.unshift({
type: (swapped ? "removed" : "added") as DiffType, type: swapped ? DiffType.removed : DiffType.added,
value: B[b] value: B[b]
}); });
b -= 1; b -= 1;
} else if (type === ADDED) { } else if (type === ADDED) {
result.unshift({ result.unshift({
type: (swapped ? "added" : "removed") as DiffType, type: swapped ? DiffType.added : DiffType.removed,
value: A[a] value: A[a]
}); });
a -= 1; a -= 1;
} else { } else {
result.unshift({ type: "common" as DiffType, value: A[a] }); result.unshift({ type: DiffType.common, value: A[a] });
a -= 1; a -= 1;
b -= 1; b -= 1;
} }
@ -194,8 +198,8 @@ export default function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
); );
} }
return [ return [
...prefixCommon.map(c => ({ type: "common" as DiffType, value: c })), ...prefixCommon.map(c => ({ type: DiffType.common, value: c })),
...backTrace(A, B, fp[delta + offset], swapped), ...backTrace(A, B, fp[delta + offset], swapped),
...suffixCommon.map(c => ({ type: "common" as DiffType, value: c })) ...suffixCommon.map(c => ({ type: DiffType.common, value: c }))
]; ];
} }

View file

@ -2,11 +2,16 @@
import { green, red } from "../colors/mod.ts"; import { green, red } from "../colors/mod.ts";
import { assertEqual as prettyAssertEqual } from "./pretty.ts"; import { assertEqual as prettyAssertEqual } from "./pretty.ts";
import {
interface Constructor { assert as assertImport,
// eslint-disable-next-line @typescript-eslint/no-explicit-any equal as AssertEqual,
new (...args: any[]): any; assertStrictEq,
} assertStrContains,
assertMatch,
fail,
assertThrows,
assertThrowsAsync
} from "./asserts.ts";
export function equal(c: unknown, d: unknown): boolean { export function equal(c: unknown, d: unknown): boolean {
const seen = new Map(); const seen = new Map();
@ -36,125 +41,14 @@ export function equal(c: unknown, d: unknown): boolean {
} }
const assertions = { const assertions = {
/** Make an assertion, if not `true`, then throw. */ assert: assertImport,
assert(expr: boolean, msg = ""): void { equal: AssertEqual,
if (!expr) { strictEqual: assertStrictEq,
throw new Error(msg); assertStrContains: assertStrContains,
} assertMatch: assertMatch,
}, fail: fail,
throws: assertThrows,
/** Make an assertion that `actual` and `expected` are equal, deeply. If not throwsAsync: assertThrowsAsync
* deeply equal, then throw.
*/
equal(actual: unknown, expected: unknown, msg?: string): void {
prettyAssertEqual(actual, expected, msg);
},
/** Make an assertion that `actual` and `expected` are strictly equal. If
* not then throw.
*/
strictEqual(actual: unknown, expected: unknown, msg = ""): void {
if (actual !== expected) {
let actualString: string;
let expectedString: string;
try {
actualString = String(actual);
} catch (e) {
actualString = "[Cannot display]";
}
try {
expectedString = String(expected);
} catch (e) {
expectedString = "[Cannot display]";
}
console.error(
"strictEqual failed. actual =",
actualString,
"expected =",
expectedString
);
if (!msg) {
msg = `actual: ${actualString} expected: ${expectedString}`;
}
throw new Error(msg);
}
},
/**
* Forcefully throws a failed assertion
*/
fail(msg?: string): void {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
assert(false, `Failed assertion${msg ? `: ${msg}` : "."}`);
},
/** Executes a function, expecting it to throw. If it does not, then it
* throws. An error class and a string that should be included in the
* error message can also be asserted.
*/
throws(
fn: () => void,
ErrorClass?: Constructor,
msgIncludes = "",
msg = ""
): void {
let doesThrow = false;
try {
fn();
} catch (e) {
if (ErrorClass && !(Object.getPrototypeOf(e) === ErrorClass.prototype)) {
msg = `Expected error to be instance of "${ErrorClass.name}"${
msg ? `: ${msg}` : "."
}`;
throw new Error(msg);
}
if (msgIncludes) {
if (!e.message.includes(msgIncludes)) {
msg = `Expected error message to include "${msgIncludes}", but got "${
e.message
}"${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
doesThrow = true;
}
if (!doesThrow) {
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
},
async throwsAsync(
fn: () => Promise<void>,
ErrorClass?: Constructor,
msgIncludes = "",
msg = ""
): Promise<void> {
let doesThrow = false;
try {
await fn();
} catch (e) {
if (ErrorClass && !(Object.getPrototypeOf(e) === ErrorClass.prototype)) {
msg = `Expected error to be instance of "${ErrorClass.name}"${
msg ? `: ${msg}` : "."
}`;
throw new Error(msg);
}
if (msgIncludes) {
if (!e.message.includes(msgIncludes)) {
msg = `Expected error message to include "${msgIncludes}", but got "${
e.message
}"${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
doesThrow = true;
}
if (!doesThrow) {
msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
throw new Error(msg);
}
}
}; };
type Assert = typeof assertions.assert & typeof assertions; type Assert = typeof assertions.assert & typeof assertions;

View file

@ -17,9 +17,9 @@ function createStr(v: unknown): string {
function createColor(diffType: DiffType): (s: string) => string { function createColor(diffType: DiffType): (s: string) => string {
switch (diffType) { switch (diffType) {
case "added": case DiffType.added:
return (s: string) => green(bold(s)); return (s: string) => green(bold(s));
case "removed": case DiffType.removed:
return (s: string) => red(bold(s)); return (s: string) => red(bold(s));
default: default:
return white; return white;
@ -28,9 +28,9 @@ function createColor(diffType: DiffType): (s: string) => string {
function createSign(diffType: DiffType): string { function createSign(diffType: DiffType): string {
switch (diffType) { switch (diffType) {
case "added": case DiffType.added:
return "+ "; return "+ ";
case "removed": case DiffType.removed:
return "- "; return "- ";
default: default:
return " "; return " ";

View file

@ -1,10 +1,10 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { test, assert, assertEqual, equal, runIfMain } from "./mod.ts"; import { test, assert, assertEqual, equal, runIfMain } from "./mod.ts";
import { assertEqual as prettyAssertEqual } from "./pretty.ts"; import { assertEqual as prettyAssertEqual } from "./pretty.ts";
import "./format_test.ts"; import "./format_test.ts";
import "./diff_test.ts"; import "./diff_test.ts";
import "./pretty_test.ts"; import "./pretty_test.ts";
import "./asserts_test.ts";
test(function testingEqual() { test(function testingEqual() {
assert(equal("world", "world")); assert(equal("world", "world"));