refactor: testing

This commit is contained in:
Vasil Chimev
2019-11-18 11:19:57 +02:00
parent 791a056fdd
commit 0a4e28f5ae
3 changed files with 96 additions and 1182 deletions

View File

@ -12,346 +12,22 @@
*/
import * as application from "../application";
import * as trace from "../trace";
import * as platform from "../platform";
import * as types from "../utils/types";
import * as timer from "../timer";
import * as trace from "../trace";
const sdkVersion = parseInt(platform.device.sdkVersion);
trace.enable();
export interface TestInfoEntry {
testFunc: () => void;
instance: Object;
isTest: boolean;
testName: string;
isPassed: boolean;
errorMessage: string;
testTimeout: number;
duration: number;
}
export function time(): number {
if (global.android) {
// return java.lang.System.nanoTime() / 1000000; // 1 ms = 1000000 ns
return java.lang.System.nanoTime() / 1000000; // 1 ms = 1000000 ns
} else {
return CACurrentMediaTime() * 1000;
}
}
export function write(message: string, type?: number) {
trace.write(message, trace.categories.Test, type);
}
function runTest(testInfo: TestInfoEntry) {
let start = time();
let duration;
try {
if (testInfo.instance) {
testInfo.testFunc.apply(testInfo.instance);
} else {
testInfo.testFunc();
}
if (testInfo.isTest) {
duration = time() - start;
testInfo.duration = duration;
write(`--- [${testInfo.testName}] OK, duration: ${duration.toFixed(2)}`, trace.messageType.info);
testInfo.isPassed = true;
}
}
catch (e) {
if (testInfo.isTest) {
duration = time() - start;
testInfo.duration = duration;
write(`--- [${testInfo.testName}] FAILED: ${e.message}, Stack: ${e.stack}, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.isPassed = false;
testInfo.errorMessage = e.message;
}
}
}
export interface TestFailure {
moduleName: string;
testName: string;
errorMessage: string;
}
export interface TestModuleRunResult {
name: string;
count: number;
succeeded: number;
failed: Array<TestFailure>;
}
let testsQueue: Array<TestInfoEntry>;
const defaultTimeout = 5000;
function runAsync(testInfo: TestInfoEntry, recursiveIndex: number, testTimeout?: number) {
let error;
let isDone = false;
let handle;
const testStartTime = time();
//write("--- [" + testInfo.testName + "] Started at: " + testStartTime, trace.messageType.info);
const doneCallback = (e: Error) => {
if (e) {
error = e;
} else {
isDone = true;
}
}
const timeout = testTimeout || testInfo.testTimeout || defaultTimeout;
let duration;
const checkFinished = () => {
duration = time() - testStartTime;
testInfo.duration = duration;
if (isDone) {
write(`--- [${testInfo.testName}] OK, duration: ${duration.toFixed(2)}`, trace.messageType.info);
testInfo.isPassed = true;
runTests(testsQueue, recursiveIndex + 1);
} else if (error) {
write(`--- [${testInfo.testName}] FAILED: ${error.message}, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = error.message;
runTests(testsQueue, recursiveIndex + 1);
} else {
const testEndTime = time();
if (testEndTime - testStartTime > timeout) {
write(`--- [${testInfo.testName}] TIMEOUT, duration: ${duration.toFixed(2)}`, trace.messageType.error);
testInfo.errorMessage = "Test timeout.";
runTests(testsQueue, recursiveIndex + 1);
} else {
setTimeout(checkFinished, 20);
}
}
}
try {
if (testInfo.instance) {
testInfo.testFunc.apply(testInfo.instance, [doneCallback]);
} else {
const func: any = testInfo.testFunc;
func(doneCallback);
}
} catch (e) {
doneCallback(e);
}
setTimeout(checkFinished, 20);
}
export function runTests(tests: Array<TestInfoEntry>, recursiveIndex) {
testsQueue = tests;
for (let i = recursiveIndex; i < testsQueue.length; i++) {
const testEntry = testsQueue[i];
if (testEntry.testFunc.length > 0) {
return runAsync(testEntry, i);
} else {
runTest(testEntry);
}
}
}
export function assert(test: any, message?: string) {
if (!test) {
throw new Error(message);
}
}
export function assertTrue(test: boolean, message?: string) {
if (test !== true) {
throw new Error(message);
}
}
export function assertFalse(test: boolean, message?: string) {
if (test !== false) {
throw new Error(message);
}
}
export function assertNotEqual(actual: any, expected: any, message?: string) {
let equals = false;
if (types.isUndefined(actual) && types.isUndefined(expected)) {
equals = true;
} else if (!types.isNullOrUndefined(actual) && !types.isNullOrUndefined(expected)) {
if (types.isFunction(actual.equals)) {
// Use the equals method
if (actual.equals(expected)) {
equals = true;
}
} else {
equals = actual === expected;
}
}
if (equals) {
throw new Error(message + " Actual: " + actual + " Not_Expected: " + expected);
}
}
export function assertEqual<T extends { equals?(arg: T): boolean } | any>(actual: T, expected: T, message: string = '') {
if (!types.isNullOrUndefined(actual)
&& !types.isNullOrUndefined(expected)
&& types.getClass(actual) === types.getClass(expected)
&& types.isFunction(actual.equals)) {
// Use the equals method
if (!actual.equals(expected)) {
throw new Error(`${message} Actual: <${actual}>(${typeof (actual)}). Expected: <${expected}>(${typeof (expected)})`);
}
}
else if (actual !== expected) {
throw new Error(`${message} Actual: <${actual}>(${typeof (actual)}). Expected: <${expected}>(${typeof (expected)})`);
}
}
/**
* Assert two json like objects are deep equal.
*/
export function assertDeepEqual(actual, expected, message: string = '', path: any[] = []): void {
let typeofActual: string = typeof actual;
let typeofExpected: string = typeof expected;
if (typeofActual !== typeofExpected) {
throw new Error(message + ' ' + "At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
} else if (typeofActual === "object" || typeofActual === "array") {
if (expected instanceof Map) {
if (actual instanceof Map) {
expected.forEach((value, key) => {
if (actual.has(key)) {
assertDeepEqual(actual.get(key), value, message, path.concat([key]));
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected Map has key '" + key + "' but actual does not.");
}
});
actual.forEach((value, key) => {
if (!expected.has(key)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual Map has key '" + key + "' but expected does not.");
}
});
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected is Map but actual is not.");
}
}
if (expected instanceof Set) {
if (actual instanceof Set) {
expected.forEach(i => {
if (!actual.has(i)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected Set has item '" + i + "' but actual does not.");
}
});
actual.forEach(i => {
if (!expected.has(i)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual Set has item '" + i + "' but expected does not.");
}
})
} else {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected is Set but actual is not.");
}
}
for (let key in actual) {
if (!(key in expected)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " found unexpected key " + key + ".");
}
assertDeepEqual(actual[key], expected[key], message, path.concat([key]));
}
for (let key in expected) {
if (!(key in actual)) {
throw new Error(message + ' ' + "At /" + path.join("/") + " expected a key " + key + ".");
}
}
} else if (actual !== expected) {
throw new Error(message + ' ' + "At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
}
}
export function assertDeepSuperset(actual, expected, path: any[] = []): void {
let typeofActual: string = typeof actual;
let typeofExpected: string = typeof expected;
if (typeofActual !== typeofExpected) {
throw new Error("At /" + path.join("/") + " types of actual " + typeofActual + " and expected " + typeofExpected + " differ.");
} else if (typeofActual === "object" || typeofActual === "array") {
for (let key in expected) {
if (!(key in actual)) {
throw new Error("At /" + path.join("/") + " expected a key " + key + ".");
}
assertDeepSuperset(actual[key], expected[key], path.concat([key]));
}
} else if (actual !== expected) {
throw new Error("At /" + path.join("/") + " actual: '" + actual + "' and expected: '" + expected + "' differ.");
}
}
export function assertNull(actual: any, message?: string) {
if (actual !== null && actual !== undefined) {
throw new Error(message + " Actual: " + actual + " is not null/undefined");
}
}
export function assertNotNull(actual: any, message?: string) {
if (actual === null || actual === undefined) {
throw new Error(message + " Actual: " + actual + " is null/undefined");
}
}
export function areClose(actual: number, expected: number, delta: number): boolean {
if (isNaN(actual) || Math.abs(actual - expected) > delta) {
return false;
}
return true;
}
export function assertAreClose(actual: number, expected: number, delta: number, message?: string) {
if (!areClose(actual, expected, delta)) {
throw new Error(message + " Numbers are not close enough. Actual: " + actual + " Expected: " + expected + " Delta: " + delta);
}
}
export function assertMatches(actual: string, expected: RegExp, message?: string) {
if (expected.test(actual) !== true) {
throw new Error(`"${actual}" doesn't match "${expected}". ${message}`);
}
}
export function arrayAssert(actual: Array<any>, expected: Array<any>, message?: string) {
if (actual.length !== expected.length) {
throw new Error(message + " Actual array length: " + actual.length + " Expected array length: " + expected.length);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) {
throw new Error(message + " Actual element at " + i + " is: " + actual[i] + " Expected element is: " + expected[i]);
}
}
}
export function assertThrows(testFunc: () => void, assertMessage?: string, expectedMessage?: string) {
const re = expectedMessage ? new RegExp(`^${expectedMessage}$`) : null;
return assertThrowsRegExp(testFunc, assertMessage, re);
}
export function assertThrowsRegExp(testFunc: () => void, assertMessage?: string, expectedMessage?: RegExp) {
let actualError: Error;
try {
testFunc();
} catch (e) {
actualError = e;
}
if (!actualError) {
throw new Error("Missing expected exception. " + assertMessage);
}
if (expectedMessage && !expectedMessage.test(actualError.message)) {
throw new Error("Got unwanted exception. Actual error: " + actualError.message + " Expected to match: " + expectedMessage);
}
}
export function wait(seconds: number): void {
waitUntilReady(() => false, seconds, false);
}