diff --git a/deno_testing/testing.ts b/deno_testing/testing.ts new file mode 100644 index 0000000000..969c157048 --- /dev/null +++ b/deno_testing/testing.ts @@ -0,0 +1,96 @@ +/*! + Copyright 2018 Propel http://propel.site/. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +export { assert, assertEqual, equal } from "./util"; + +export type TestFunction = () => void | Promise; + +export interface TestDefinition { + fn: TestFunction; + name: string; +} + +export const exitOnFail = true; + +/* A subset of the tests can be ran by providing a filter expression. + * In Node.js the filter is specified on the command line: + * + * ts-node test_node log # all tests with 'log' in the name + * ts-node test_node ^util # tests starting with 'util' + * + * In the browser, the filter is specified as part of the url: + * + * http://localhost:9876/test.html#script=some/script.js&filter=log + * http://localhost:9876/test.html#script=some/script.js&filter=^util + */ +let filterExpr: string = null; + +const filterRegExp = filterExpr ? new RegExp(filterExpr, "i") : null; +const tests: TestDefinition[] = []; + +export function test(t: TestDefinition | TestFunction): void { + const fn: TestFunction = typeof t === "function" ? t : t.fn; + const name: string = t.name; + + if (!name) { + throw new Error("Test function may not be anonymous"); + } + if (filter(name)) { + tests.push({ fn, name }); + } +} + +function filter(name: string): boolean { + if (filterRegExp) { + return filterRegExp.test(name); + } else { + return true; + } +} + +async function runTests() { + let passed = 0; + let failed = 0; + + for (let i = 0; i < tests.length; i++) { + const { fn, name } = tests[i]; + console.log(`${i+1}/${tests.length} +${passed} -${failed}: ${name}`); + try { + await fn(); + passed++; + } catch (e) { + console.error("\nTest FAIL", name); + console.error((e && e.stack) || e); + failed++; + if (exitOnFail) { + break; + } + } + } + + console.log(`\nDONE. Test passed: ${passed}, failed: ${failed}`); + + if (failed === 0) { + // All good. + } else { + // Use setTimeout to avoid the error being ignored due to unhandled + // promise rejections being swallowed. + setTimeout(() => { + throw new Error(`There were ${failed} test failures.`); + }, 0); + } +} + +setTimeout(runTests, 0); diff --git a/deno_testing/util.ts b/deno_testing/util.ts new file mode 100644 index 0000000000..509713005b --- /dev/null +++ b/deno_testing/util.ts @@ -0,0 +1,60 @@ +/*! + Copyright 2018 Propel http://propel.site/. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +// tslint:disable-next-line:no-any +export function assertEqual(actual: any, expected: any, msg?: string) { + if (!msg) { msg = `actual: ${actual} expected: ${expected}`; } + if (!equal(actual, expected)) { + console.error( + "assertEqual failed. actual = ", actual, "expected =", expected); + throw new Error(msg); + } +} + +export function assert(expr: boolean, msg = "") { + if (!expr) { + throw new Error(msg); + } +} + +// tslint:disable-next-line:no-any +export function equal(c: any, d: any): boolean { + const seen = new Map(); + return (function compare(a, b) { + if (a === b) { + return true; + } + if (typeof a === "number" && typeof b === "number" && + isNaN(a) && isNaN(b)) { + return true; + } + if (a && typeof a === "object" && b && typeof b === "object") { + if (seen.get(a) === b) { + return true; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (const key in { ...a, ...b }) { + if (!compare(a[key], b[key])) { + return false; + } + } + seen.set(a, b); + return true; + } + return false; + })(c, d); +} diff --git a/deno_testing/util_test.ts b/deno_testing/util_test.ts new file mode 100644 index 0000000000..44f61f9c4b --- /dev/null +++ b/deno_testing/util_test.ts @@ -0,0 +1,32 @@ +/*! + Copyright 2018 Propel http://propel.site/. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import { test } from "./index"; +import { assert } from "./util"; +import * as util from "./util"; + +test(async function util_equal() { + assert(util.equal("world", "world")); + assert(!util.equal("hello", "world")); + assert(util.equal(5, 5)); + assert(!util.equal(5, 6)); + assert(util.equal(NaN, NaN)); + assert(util.equal({ hello: "world" }, { hello: "world" })); + assert(!util.equal({ world: "hello" }, { hello: "world" })); + assert(util.equal({ hello: "world", hi: { there: "everyone" } }, + { hello: "world", hi: { there: "everyone" } })); + assert(!util.equal({ hello: "world", hi: { there: "everyone" } }, + { hello: "world", hi: { there: "everyone else" } })); +}); diff --git a/integration_test.go b/integration_test.go index 37c87f30a4..a3f8f16cef 100644 --- a/integration_test.go +++ b/integration_test.go @@ -144,3 +144,14 @@ func TestErrors(t *testing.T) { t.Fatalf("Expected error.") } } + +func TestTestsTs(t *testing.T) { + integrationTestSetup() + cmd := exec.Command(denoFn, "tests.ts") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + t.Fatal(err.Error()) + } +} diff --git a/runtime.ts b/runtime.ts index ca2e40ba04..7d91484ab5 100644 --- a/runtime.ts +++ b/runtime.ts @@ -29,6 +29,11 @@ type AmdDefine = (deps: string[], factory: AmdFactory) => void; // Uncaught exceptions are sent to window.onerror by v8worker2. window.onerror = (message, source, lineno, colno, error) => { + // TODO Currently there is a bug in v8_source_maps.ts that causes a segfault + // if it is used within window.onerror. To workaround we uninstall the + // Error.prepareStackTrace handler. Users will get unmapped stack traces on + // uncaught exceptions until this issue is fixed. + Error.prepareStackTrace = null; console.log(error.message, error.stack); os.exit(1); }; diff --git a/tests.ts b/tests.ts new file mode 100644 index 0000000000..b9fcbf37f8 --- /dev/null +++ b/tests.ts @@ -0,0 +1,5 @@ +import { test, assert } from "./deno_testing/testing.ts"; + +test(async function test_matchTesters() { + assert(false, "assert failed on purpose."); +}); diff --git a/tsconfig.json b/tsconfig.json index 4de82936ce..769d75b46e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,5 +20,6 @@ "allowUnreachableCode": false, "experimentalDecorators": true }, - "include": ["*.ts", "*.js"] + "include": ["*.ts", "*.js"], + "exclude": ["tests.ts"] }