diff --git a/js/testing/testing.ts b/js/testing/testing.ts new file mode 100644 index 0000000000..828ebec2f0 --- /dev/null +++ b/js/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.ts"; + +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/js/testing/util.ts b/js/testing/util.ts new file mode 100644 index 0000000000..1935403516 --- /dev/null +++ b/js/testing/util.ts @@ -0,0 +1,64 @@ +/*! + 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. + */ + +// TODO(ry) Use unknown here for parameters types. +// 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); + } +} + +// TODO(ry) Use unknown here for parameters types. +// tslint:disable-next-line:no-any +export function equal(c: any, d: any): boolean { + const seen = new Map(); + return (function compare(a, b) { + if (Object.is(a, 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/js/testing/util_test.ts b/js/testing/util_test.ts new file mode 100644 index 0000000000..9ac3dfd71b --- /dev/null +++ b/js/testing/util_test.ts @@ -0,0 +1,40 @@ +/*! + 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 "./testing.ts"; +import { assert } from "./util.ts"; +import * as util from "./util.ts"; + +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/js/tests.ts b/js/unit_tests.ts similarity index 85% rename from js/tests.ts rename to js/unit_tests.ts index 3e8be12a19..6989e22c4f 100644 --- a/js/tests.ts +++ b/js/unit_tests.ts @@ -2,59 +2,14 @@ // This test is executed as part of integration_test.go // But it can also be run manually: // ./deno tests.ts -// There must also be a static file http server running on localhost:4545 -// serving the deno project directory. Try this: -// http-server -p 4545 --cors . + import { test, assert, assertEqual } from "./testing/testing.ts"; -import { readFileSync, writeFileSync } from "deno"; +// import { readFileSync, writeFileSync } from "deno"; test(async function tests_test() { assert(true); }); -test(async function tests_fetch() { - const response = await fetch("http://localhost:4545/package.json"); - const json = await response.json(); - assertEqual(json.name, "deno"); -}); - -test(function tests_console_assert() { - console.assert(true); - - let hasThrown = false; - try { - console.assert(false); - } catch { - hasThrown = true; - } - assertEqual(hasThrown, true); -}); - -test(async function tests_readFileSync() { - const data = readFileSync("package.json"); - if (!data.byteLength) { - throw Error( - `Expected positive value for data.byteLength ${data.byteLength}` - ); - } - const decoder = new TextDecoder("utf-8"); - const json = decoder.decode(data); - const pkg = JSON.parse(json); - assertEqual(pkg.name, "deno"); -}); - -test(async function tests_writeFileSync() { - const enc = new TextEncoder(); - const data = enc.encode("Hello"); - // TODO need ability to get tmp dir. - // const fn = "/tmp/test.txt"; - writeFileSync("/tmp/test.txt", data, 0o666); - const dataRead = readFileSync("/tmp/test.txt"); - const dec = new TextDecoder("utf-8"); - const actual = dec.decode(dataRead); - assertEqual("Hello", actual); -}); - test(function tests_console_assert() { console.assert(true); @@ -123,3 +78,37 @@ test(function tests_console_stringify_circular() { throw new Error("Expected no crash on circular object"); } }); + +/* +test(async function tests_fetch() { + const response = await fetch("http://localhost:4545/package.json"); + const json = await response.json(); + assertEqual(json.name, "deno"); +}); + +test(async function tests_readFileSync() { + const data = readFileSync("package.json"); + if (!data.byteLength) { + throw Error( + `Expected positive value for data.byteLength ${data.byteLength}` + ); + } + const decoder = new TextDecoder("utf-8"); + const json = decoder.decode(data); + const pkg = JSON.parse(json); + assertEqual(pkg.name, "deno"); +}); + +test(async function tests_writeFileSync() { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + // TODO need ability to get tmp dir. + const fn = "/tmp/test.txt"; + writeFileSync("/tmp/test.txt", data, 0o666); + const dataRead = readFileSync("/tmp/test.txt"); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEqual("Hello", actual); +}); + +*/ diff --git a/tools/test.py b/tools/test.py index 3edfa4e015..91557e0fdc 100755 --- a/tools/test.py +++ b/tools/test.py @@ -4,21 +4,24 @@ import os import sys from check_output_test import check_output_test -from util import executable_suffix, run +from util import executable_suffix, run, build_path def check_exists(filename): if not os.path.exists(filename): print "Required target doesn't exist:", filename - print "Build target :all" + print "Run ./tools/build.py" sys.exit(1) def main(argv): - if len(argv) != 2: - print "Usage: tools/test.py [build dir]" + if len(argv) == 2: + build_dir = sys.argv[1] + elif len(argv) == 1: + build_dir = build_path() + else: + print "Usage: tools/test.py [build_dir]" sys.exit(1) - build_dir = argv[1] test_cc = os.path.join(build_dir, "test_cc" + executable_suffix) check_exists(test_cc) @@ -29,6 +32,9 @@ def main(argv): run([test_rs]) deno_exe = os.path.join(build_dir, "deno" + executable_suffix) + check_exists(deno_exe) + run([deno_exe, "js/unit_tests.ts"]) + check_exists(deno_exe) check_output_test(deno_exe)