diff options
Diffstat (limited to 'cli/js/tests')
64 files changed, 9743 insertions, 0 deletions
diff --git a/cli/js/tests/README.md b/cli/js/tests/README.md new file mode 100644 index 000000000..5809224f7 --- /dev/null +++ b/cli/js/tests/README.md @@ -0,0 +1,47 @@ +# Deno runtime tests + +Files in this directory are unit tests for Deno runtime. + +They are run under compiled Deno binary as opposed to files in `cli/js/` which +are bundled and snapshotted using `deno_typescript` crate. + +Testing Deno runtime code requires checking API under different runtime +permissions (ie. running with different `--allow-*` flags). To accomplish this +all tests exercised are created using `unitTest()` function. + +``` +import { unitTest } from "./test_util.ts"; + +unitTest(function simpleTestFn(): void { + // test code here +}); + +unitTest({ + skip: Deno.build.os === "win", + perms: { read: true, write: true }, + }, + function complexTestFn(): void { + // test code here + } +); +``` + +`unitTest` is is a wrapper function that enhances `Deno.test()` API in several +ways: + +- ability to conditionally skip tests using `UnitTestOptions.skip` +- ability to register required set of permissions for given test case using + `UnitTestOptions.perms` +- sanitization of resources - ensuring that tests close all opened resources + preventing interference between tests +- sanitization of async ops - ensuring that tests don't leak async ops by + ensuring that all started async ops are done before test finishes + +`unit_test_runner.ts` is main script used to run unit tests. + +Runner discoveres required permissions combinations by loading +`cli/js/tests/unit_tests.ts` and going through all registered instances of +`unitTest`. For each discovered permission combination a new Deno process is +created with respective `--allow-*` flags which loads +`cli/js/tests/unit_tests.ts` and executes all `unitTest` that match runtime +permissions. diff --git a/cli/js/tests/blob_test.ts b/cli/js/tests/blob_test.ts new file mode 100644 index 000000000..54071a11e --- /dev/null +++ b/cli/js/tests/blob_test.ts @@ -0,0 +1,62 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function blobString(): void { + const b1 = new Blob(["Hello World"]); + const str = "Test"; + const b2 = new Blob([b1, str]); + assertEquals(b2.size, b1.size + str.length); +}); + +unitTest(function blobBuffer(): void { + const buffer = new ArrayBuffer(12); + const u8 = new Uint8Array(buffer); + const f1 = new Float32Array(buffer); + const b1 = new Blob([buffer, u8]); + assertEquals(b1.size, 2 * u8.length); + const b2 = new Blob([b1, f1]); + assertEquals(b2.size, 3 * u8.length); +}); + +unitTest(function blobSlice(): void { + const blob = new Blob(["Deno", "Foo"]); + const b1 = blob.slice(0, 3, "Text/HTML"); + assert(b1 instanceof Blob); + assertEquals(b1.size, 3); + assertEquals(b1.type, "text/html"); + const b2 = blob.slice(-1, 3); + assertEquals(b2.size, 0); + const b3 = blob.slice(100, 3); + assertEquals(b3.size, 0); + const b4 = blob.slice(0, 10); + assertEquals(b4.size, blob.size); +}); + +unitTest(function blobShouldNotThrowError(): void { + let hasThrown = false; + + try { + const options1: object = { + ending: "utf8", + hasOwnProperty: "hasOwnProperty" + }; + const options2: object = Object.create(null); + new Blob(["Hello World"], options1); + new Blob(["Hello World"], options2); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); + +unitTest(function nativeEndLine(): void { + const options: object = { + ending: "native" + }; + const blob = new Blob(["Hello\nWorld"], options); + + assertEquals(blob.size, Deno.build.os === "win" ? 12 : 11); +}); + +// TODO(qti3e) Test the stored data in a Blob after implementing FileReader API. diff --git a/cli/js/tests/body_test.ts b/cli/js/tests/body_test.ts new file mode 100644 index 000000000..23f6d89e4 --- /dev/null +++ b/cli/js/tests/body_test.ts @@ -0,0 +1,74 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals, assert } from "./test_util.ts"; + +// just a hack to get a body object +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function buildBody(body: any): Body { + const stub = new Request("", { + body: body + }); + return stub as Body; +} + +const intArrays = [ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array +]; +unitTest(async function arrayBufferFromByteArrays(): Promise<void> { + const buffer = new TextEncoder().encode("ahoyhoy8").buffer; + + for (const type of intArrays) { + const body = buildBody(new type(buffer)); + const text = new TextDecoder("utf-8").decode(await body.arrayBuffer()); + assertEquals(text, "ahoyhoy8"); + } +}); + +//FormData +unitTest( + { perms: { net: true } }, + async function bodyMultipartFormData(): Promise<void> { + const response = await fetch( + "http://localhost:4545/cli/tests/subdir/multipart_form_data.txt" + ); + const text = await response.text(); + + const body = buildBody(text); + + // @ts-ignore + body.contentType = "multipart/form-data;boundary=boundary"; + + const formData = await body.formData(); + assert(formData.has("field_1")); + assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n"); + assert(formData.has("field_2")); + } +); + +unitTest( + { perms: { net: true } }, + async function bodyURLEncodedFormData(): Promise<void> { + const response = await fetch( + "http://localhost:4545/cli/tests/subdir/form_urlencoded.txt" + ); + const text = await response.text(); + + const body = buildBody(text); + + // @ts-ignore + body.contentType = "application/x-www-form-urlencoded"; + + const formData = await body.formData(); + assert(formData.has("field_1")); + assertEquals(formData.get("field_1")!.toString(), "Hi"); + assert(formData.has("field_2")); + assertEquals(formData.get("field_2")!.toString(), "<Deno>"); + } +); diff --git a/cli/js/tests/buffer_test.ts b/cli/js/tests/buffer_test.ts new file mode 100644 index 000000000..163ed0a30 --- /dev/null +++ b/cli/js/tests/buffer_test.ts @@ -0,0 +1,296 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This code has been ported almost directly from Go's src/bytes/buffer_test.go +// Copyright 2009 The Go Authors. All rights reserved. BSD license. +// https://github.com/golang/go/blob/master/LICENSE +import { + assertEquals, + assert, + assertStrContains, + unitTest +} from "./test_util.ts"; + +const { Buffer, readAll, readAllSync, writeAll, writeAllSync } = Deno; +type Buffer = Deno.Buffer; + +// N controls how many iterations of certain checks are performed. +const N = 100; +let testBytes: Uint8Array | null; +let testString: string | null; + +function init(): void { + if (testBytes == null) { + testBytes = new Uint8Array(N); + for (let i = 0; i < N; i++) { + testBytes[i] = "a".charCodeAt(0) + (i % 26); + } + const decoder = new TextDecoder(); + testString = decoder.decode(testBytes); + } +} + +function check(buf: Deno.Buffer, s: string): void { + const bytes = buf.bytes(); + assertEquals(buf.length, bytes.byteLength); + const decoder = new TextDecoder(); + const bytesStr = decoder.decode(bytes); + assertEquals(bytesStr, s); + assertEquals(buf.length, buf.toString().length); + assertEquals(buf.length, s.length); +} + +// Fill buf through n writes of byte slice fub. +// The initial contents of buf corresponds to the string s; +// the result is the final contents of buf returned as a string. +async function fillBytes( + buf: Buffer, + s: string, + n: number, + fub: Uint8Array +): Promise<string> { + check(buf, s); + for (; n > 0; n--) { + const m = await buf.write(fub); + assertEquals(m, fub.byteLength); + const decoder = new TextDecoder(); + s += decoder.decode(fub); + check(buf, s); + } + return s; +} + +// Empty buf through repeated reads into fub. +// The initial contents of buf corresponds to the string s. +async function empty(buf: Buffer, s: string, fub: Uint8Array): Promise<void> { + check(buf, s); + while (true) { + const r = await buf.read(fub); + if (r === Deno.EOF) { + break; + } + s = s.slice(r); + check(buf, s); + } + check(buf, ""); +} + +function repeat(c: string, bytes: number): Uint8Array { + assertEquals(c.length, 1); + const ui8 = new Uint8Array(bytes); + ui8.fill(c.charCodeAt(0)); + return ui8; +} + +unitTest(function bufferNewBuffer(): void { + init(); + assert(testBytes); + assert(testString); + const buf = new Buffer(testBytes.buffer as ArrayBuffer); + check(buf, testString); +}); + +unitTest(async function bufferBasicOperations(): Promise<void> { + init(); + assert(testBytes); + assert(testString); + const buf = new Buffer(); + for (let i = 0; i < 5; i++) { + check(buf, ""); + + buf.reset(); + check(buf, ""); + + buf.truncate(0); + check(buf, ""); + + let n = await buf.write(testBytes.subarray(0, 1)); + assertEquals(n, 1); + check(buf, "a"); + + n = await buf.write(testBytes.subarray(1, 2)); + assertEquals(n, 1); + check(buf, "ab"); + + n = await buf.write(testBytes.subarray(2, 26)); + assertEquals(n, 24); + check(buf, testString.slice(0, 26)); + + buf.truncate(26); + check(buf, testString.slice(0, 26)); + + buf.truncate(20); + check(buf, testString.slice(0, 20)); + + await empty(buf, testString.slice(0, 20), new Uint8Array(5)); + await empty(buf, "", new Uint8Array(100)); + + // TODO buf.writeByte() + // TODO buf.readByte() + } +}); + +unitTest(async function bufferReadEmptyAtEOF(): Promise<void> { + // check that EOF of 'buf' is not reached (even though it's empty) if + // results are written to buffer that has 0 length (ie. it can't store any data) + const buf = new Buffer(); + const zeroLengthTmp = new Uint8Array(0); + const result = await buf.read(zeroLengthTmp); + assertEquals(result, 0); +}); + +unitTest(async function bufferLargeByteWrites(): Promise<void> { + init(); + const buf = new Buffer(); + const limit = 9; + for (let i = 3; i < limit; i += 3) { + const s = await fillBytes(buf, "", 5, testBytes!); + await empty(buf, s, new Uint8Array(Math.floor(testString!.length / i))); + } + check(buf, ""); +}); + +unitTest(async function bufferTooLargeByteWrites(): Promise<void> { + init(); + const tmp = new Uint8Array(72); + const growLen = Number.MAX_VALUE; + const xBytes = repeat("x", 0); + const buf = new Buffer(xBytes.buffer as ArrayBuffer); + await buf.read(tmp); + + let err; + try { + buf.grow(growLen); + } catch (e) { + err = e; + } + + assert(err instanceof Error); + assertStrContains(err.message, "grown beyond the maximum size"); +}); + +unitTest(async function bufferLargeByteReads(): Promise<void> { + init(); + assert(testBytes); + assert(testString); + const buf = new Buffer(); + for (let i = 3; i < 30; i += 3) { + const n = Math.floor(testBytes.byteLength / i); + const s = await fillBytes(buf, "", 5, testBytes.subarray(0, n)); + await empty(buf, s, new Uint8Array(testString.length)); + } + check(buf, ""); +}); + +unitTest(function bufferCapWithPreallocatedSlice(): void { + const buf = new Buffer(new ArrayBuffer(10)); + assertEquals(buf.capacity, 10); +}); + +unitTest(async function bufferReadFrom(): Promise<void> { + init(); + assert(testBytes); + assert(testString); + const buf = new Buffer(); + for (let i = 3; i < 30; i += 3) { + const s = await fillBytes( + buf, + "", + 5, + testBytes.subarray(0, Math.floor(testBytes.byteLength / i)) + ); + const b = new Buffer(); + await b.readFrom(buf); + const fub = new Uint8Array(testString.length); + await empty(b, s, fub); + } +}); + +unitTest(async function bufferReadFromSync(): Promise<void> { + init(); + assert(testBytes); + assert(testString); + const buf = new Buffer(); + for (let i = 3; i < 30; i += 3) { + const s = await fillBytes( + buf, + "", + 5, + testBytes.subarray(0, Math.floor(testBytes.byteLength / i)) + ); + const b = new Buffer(); + b.readFromSync(buf); + const fub = new Uint8Array(testString.length); + await empty(b, s, fub); + } +}); + +unitTest(async function bufferTestGrow(): Promise<void> { + const tmp = new Uint8Array(72); + for (const startLen of [0, 100, 1000, 10000, 100000]) { + const xBytes = repeat("x", startLen); + for (const growLen of [0, 100, 1000, 10000, 100000]) { + const buf = new Buffer(xBytes.buffer as ArrayBuffer); + // If we read, this affects buf.off, which is good to test. + const result = await buf.read(tmp); + const nread = result === Deno.EOF ? 0 : result; + buf.grow(growLen); + const yBytes = repeat("y", growLen); + await buf.write(yBytes); + // Check that buffer has correct data. + assertEquals( + buf.bytes().subarray(0, startLen - nread), + xBytes.subarray(nread) + ); + assertEquals( + buf.bytes().subarray(startLen - nread, startLen - nread + growLen), + yBytes + ); + } + } +}); + +unitTest(async function testReadAll(): Promise<void> { + init(); + assert(testBytes); + const reader = new Buffer(testBytes.buffer as ArrayBuffer); + const actualBytes = await readAll(reader); + assertEquals(testBytes.byteLength, actualBytes.byteLength); + for (let i = 0; i < testBytes.length; ++i) { + assertEquals(testBytes[i], actualBytes[i]); + } +}); + +unitTest(function testReadAllSync(): void { + init(); + assert(testBytes); + const reader = new Buffer(testBytes.buffer as ArrayBuffer); + const actualBytes = readAllSync(reader); + assertEquals(testBytes.byteLength, actualBytes.byteLength); + for (let i = 0; i < testBytes.length; ++i) { + assertEquals(testBytes[i], actualBytes[i]); + } +}); + +unitTest(async function testWriteAll(): Promise<void> { + init(); + assert(testBytes); + const writer = new Buffer(); + await writeAll(writer, testBytes); + const actualBytes = writer.bytes(); + assertEquals(testBytes.byteLength, actualBytes.byteLength); + for (let i = 0; i < testBytes.length; ++i) { + assertEquals(testBytes[i], actualBytes[i]); + } +}); + +unitTest(function testWriteAllSync(): void { + init(); + assert(testBytes); + const writer = new Buffer(); + writeAllSync(writer, testBytes); + const actualBytes = writer.bytes(); + assertEquals(testBytes.byteLength, actualBytes.byteLength); + for (let i = 0; i < testBytes.length; ++i) { + assertEquals(testBytes[i], actualBytes[i]); + } +}); diff --git a/cli/js/tests/build_test.ts b/cli/js/tests/build_test.ts new file mode 100644 index 000000000..50c7b519c --- /dev/null +++ b/cli/js/tests/build_test.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function buildInfo(): void { + // Deno.build is injected by rollup at compile time. Here + // we check it has been properly transformed. + const { arch, os } = Deno.build; + assert(arch === "x64"); + assert(os === "mac" || os === "win" || os === "linux"); +}); diff --git a/cli/js/tests/chmod_test.ts b/cli/js/tests/chmod_test.ts new file mode 100644 index 000000000..8731fad22 --- /dev/null +++ b/cli/js/tests/chmod_test.ts @@ -0,0 +1,159 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +const isNotWindows = Deno.build.os !== "win"; + +unitTest( + { perms: { read: true, write: true } }, + function chmodSyncSuccess(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + + // On windows no effect, but should not crash + Deno.chmodSync(filename, 0o777); + + // Check success when not on windows + if (isNotWindows) { + const fileInfo = Deno.statSync(filename); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + } + } +); + +// Check symlink when not on windows +unitTest( + { + skip: Deno.build.os === "win", + perms: { read: true, write: true } + }, + function chmodSyncSymlinkSuccess(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + + const filename = tempDir + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const symlinkName = tempDir + "/test_symlink.txt"; + Deno.symlinkSync(filename, symlinkName); + + let symlinkInfo = Deno.lstatSync(symlinkName); + assert(symlinkInfo.mode); + const symlinkMode = symlinkInfo.mode & 0o777; // platform dependent + + Deno.chmodSync(symlinkName, 0o777); + + // Change actual file mode, not symlink + const fileInfo = Deno.statSync(filename); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + symlinkInfo = Deno.lstatSync(symlinkName); + assert(symlinkInfo.mode); + assertEquals(symlinkInfo.mode & 0o777, symlinkMode); + } +); + +unitTest({ perms: { write: true } }, function chmodSyncFailure(): void { + let err; + try { + const filename = "/badfile.txt"; + Deno.chmodSync(filename, 0o777); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest({ perms: { write: false } }, function chmodSyncPerm(): void { + let err; + try { + Deno.chmodSync("/somefile.txt", 0o777); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { read: true, write: true } }, + async function chmodSuccess(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + + // On windows no effect, but should not crash + await Deno.chmod(filename, 0o777); + + // Check success when not on windows + if (isNotWindows) { + const fileInfo = Deno.statSync(filename); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + } + } +); + +// Check symlink when not on windows + +unitTest( + { + skip: Deno.build.os === "win", + perms: { read: true, write: true } + }, + async function chmodSymlinkSuccess(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + + const filename = tempDir + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const symlinkName = tempDir + "/test_symlink.txt"; + Deno.symlinkSync(filename, symlinkName); + + let symlinkInfo = Deno.lstatSync(symlinkName); + assert(symlinkInfo.mode); + const symlinkMode = symlinkInfo.mode & 0o777; // platform dependent + + await Deno.chmod(symlinkName, 0o777); + + // Just change actual file mode, not symlink + const fileInfo = Deno.statSync(filename); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + symlinkInfo = Deno.lstatSync(symlinkName); + assert(symlinkInfo.mode); + assertEquals(symlinkInfo.mode & 0o777, symlinkMode); + } +); + +unitTest({ perms: { write: true } }, async function chmodFailure(): Promise< + void +> { + let err; + try { + const filename = "/badfile.txt"; + await Deno.chmod(filename, 0o777); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest({ perms: { write: false } }, async function chmodPerm(): Promise< + void +> { + let err; + try { + await Deno.chmod("/somefile.txt", 0o777); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); diff --git a/cli/js/tests/chown_test.ts b/cli/js/tests/chown_test.ts new file mode 100644 index 000000000..50dcd6dc1 --- /dev/null +++ b/cli/js/tests/chown_test.ts @@ -0,0 +1,147 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals, assert } from "./test_util.ts"; + +// chown on Windows is noop for now, so ignore its testing on Windows +if (Deno.build.os !== "win") { + async function getUidAndGid(): Promise<{ uid: number; gid: number }> { + // get the user ID and group ID of the current process + const uidProc = Deno.run({ + stdout: "piped", + args: ["python", "-c", "import os; print(os.getuid())"] + }); + const gidProc = Deno.run({ + stdout: "piped", + args: ["python", "-c", "import os; print(os.getgid())"] + }); + + assertEquals((await uidProc.status()).code, 0); + assertEquals((await gidProc.status()).code, 0); + const uid = parseInt( + new TextDecoder("utf-8").decode(await uidProc.output()) + ); + uidProc.close(); + const gid = parseInt( + new TextDecoder("utf-8").decode(await gidProc.output()) + ); + gidProc.close(); + + return { uid, gid }; + } + + unitTest(async function chownNoWritePermission(): Promise<void> { + const filePath = "chown_test_file.txt"; + try { + await Deno.chown(filePath, 1000, 1000); + } catch (e) { + assert(e instanceof Deno.errors.PermissionDenied); + } + }); + + unitTest( + { perms: { run: true, write: true } }, + async function chownSyncFileNotExist(): Promise<void> { + const { uid, gid } = await getUidAndGid(); + const filePath = Deno.makeTempDirSync() + "/chown_test_file.txt"; + + try { + Deno.chownSync(filePath, uid, gid); + } catch (e) { + assert(e instanceof Deno.errors.NotFound); + } + } + ); + + unitTest( + { perms: { run: true, write: true } }, + async function chownFileNotExist(): Promise<void> { + const { uid, gid } = await getUidAndGid(); + const filePath = (await Deno.makeTempDir()) + "/chown_test_file.txt"; + + try { + await Deno.chown(filePath, uid, gid); + } catch (e) { + assert(e instanceof Deno.errors.NotFound); + } + } + ); + + unitTest( + { perms: { write: true } }, + function chownSyncPermissionDenied(): void { + const enc = new TextEncoder(); + const dirPath = Deno.makeTempDirSync(); + const filePath = dirPath + "/chown_test_file.txt"; + const fileData = enc.encode("Hello"); + Deno.writeFileSync(filePath, fileData); + + try { + // try changing the file's owner to root + Deno.chownSync(filePath, 0, 0); + } catch (e) { + assert(e instanceof Deno.errors.PermissionDenied); + } + Deno.removeSync(dirPath, { recursive: true }); + } + ); + + unitTest( + { perms: { write: true } }, + async function chownPermissionDenied(): Promise<void> { + const enc = new TextEncoder(); + const dirPath = await Deno.makeTempDir(); + const filePath = dirPath + "/chown_test_file.txt"; + const fileData = enc.encode("Hello"); + await Deno.writeFile(filePath, fileData); + + try { + // try changing the file's owner to root + await Deno.chown(filePath, 0, 0); + } catch (e) { + assert(e instanceof Deno.errors.PermissionDenied); + } + await Deno.remove(dirPath, { recursive: true }); + } + ); + + unitTest( + { perms: { run: true, write: true } }, + async function chownSyncSucceed(): Promise<void> { + // TODO: when a file's owner is actually being changed, + // chown only succeeds if run under priviledged user (root) + // The test script has no such priviledge, so need to find a better way to test this case + const { uid, gid } = await getUidAndGid(); + + const enc = new TextEncoder(); + const dirPath = Deno.makeTempDirSync(); + const filePath = dirPath + "/chown_test_file.txt"; + const fileData = enc.encode("Hello"); + Deno.writeFileSync(filePath, fileData); + + // the test script creates this file with the same uid and gid, + // here chown is a noop so it succeeds under non-priviledged user + Deno.chownSync(filePath, uid, gid); + + Deno.removeSync(dirPath, { recursive: true }); + } + ); + + unitTest( + { perms: { run: true, write: true } }, + async function chownSucceed(): Promise<void> { + // TODO: same as chownSyncSucceed + const { uid, gid } = await getUidAndGid(); + + const enc = new TextEncoder(); + const dirPath = await Deno.makeTempDir(); + const filePath = dirPath + "/chown_test_file.txt"; + const fileData = enc.encode("Hello"); + await Deno.writeFile(filePath, fileData); + + // the test script creates this file with the same uid and gid, + // here chown is a noop so it succeeds under non-priviledged user + await Deno.chown(filePath, uid, gid); + + Deno.removeSync(dirPath, { recursive: true }); + } + ); +} diff --git a/cli/js/tests/compiler_api_test.ts b/cli/js/tests/compiler_api_test.ts new file mode 100644 index 000000000..3b0449f9a --- /dev/null +++ b/cli/js/tests/compiler_api_test.ts @@ -0,0 +1,153 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +import { assert, assertEquals, unitTest } from "./test_util.ts"; + +const { compile, transpileOnly, bundle } = Deno; + +unitTest(async function compilerApiCompileSources() { + const [diagnostics, actual] = await compile("/foo.ts", { + "/foo.ts": `import * as bar from "./bar.ts";\n\nconsole.log(bar);\n`, + "/bar.ts": `export const bar = "bar";\n` + }); + assert(diagnostics == null); + assert(actual); + assertEquals(Object.keys(actual), [ + "/bar.js.map", + "/bar.js", + "/foo.js.map", + "/foo.js" + ]); +}); + +unitTest(async function compilerApiCompileNoSources() { + const [diagnostics, actual] = await compile("./cli/tests/subdir/mod1.ts"); + assert(diagnostics == null); + assert(actual); + const keys = Object.keys(actual); + assertEquals(keys.length, 6); + assert(keys[0].endsWith("print_hello.js.map")); + assert(keys[1].endsWith("print_hello.js")); +}); + +unitTest(async function compilerApiCompileOptions() { + const [diagnostics, actual] = await compile( + "/foo.ts", + { + "/foo.ts": `export const foo = "foo";` + }, + { + module: "amd", + sourceMap: false + } + ); + assert(diagnostics == null); + assert(actual); + assertEquals(Object.keys(actual), ["/foo.js"]); + assert(actual["/foo.js"].startsWith("define(")); +}); + +unitTest(async function compilerApiCompileLib() { + const [diagnostics, actual] = await compile( + "/foo.ts", + { + "/foo.ts": `console.log(document.getElementById("foo")); + console.log(Deno.args);` + }, + { + lib: ["dom", "es2018", "deno.ns"] + } + ); + assert(diagnostics == null); + assert(actual); + assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]); +}); + +unitTest(async function compilerApiCompileTypes() { + const [diagnostics, actual] = await compile( + "/foo.ts", + { + "/foo.ts": `console.log(Foo.bar);` + }, + { + types: ["./cli/tests/subdir/foo_types.d.ts"] + } + ); + assert(diagnostics == null); + assert(actual); + assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]); +}); + +unitTest(async function transpileOnlyApi() { + const actual = await transpileOnly({ + "foo.ts": `export enum Foo { Foo, Bar, Baz };\n` + }); + assert(actual); + assertEquals(Object.keys(actual), ["foo.ts"]); + assert(actual["foo.ts"].source.startsWith("export var Foo;")); + assert(actual["foo.ts"].map); +}); + +unitTest(async function transpileOnlyApiConfig() { + const actual = await transpileOnly( + { + "foo.ts": `export enum Foo { Foo, Bar, Baz };\n` + }, + { + sourceMap: false, + module: "amd" + } + ); + assert(actual); + assertEquals(Object.keys(actual), ["foo.ts"]); + assert(actual["foo.ts"].source.startsWith("define(")); + assert(actual["foo.ts"].map == null); +}); + +unitTest(async function bundleApiSources() { + const [diagnostics, actual] = await bundle("/foo.ts", { + "/foo.ts": `export * from "./bar.ts";\n`, + "/bar.ts": `export const bar = "bar";\n` + }); + assert(diagnostics == null); + assert(actual.includes(`__instantiate("foo")`)); + assert(actual.includes(`__exp["bar"]`)); +}); + +unitTest(async function bundleApiNoSources() { + const [diagnostics, actual] = await bundle("./cli/tests/subdir/mod1.ts"); + assert(diagnostics == null); + assert(actual.includes(`__instantiate("mod1")`)); + assert(actual.includes(`__exp["printHello3"]`)); +}); + +unitTest(async function bundleApiConfig() { + const [diagnostics, actual] = await bundle( + "/foo.ts", + { + "/foo.ts": `// random comment\nexport * from "./bar.ts";\n`, + "/bar.ts": `export const bar = "bar";\n` + }, + { + removeComments: true + } + ); + assert(diagnostics == null); + assert(!actual.includes(`random`)); +}); + +unitTest(async function bundleApiJsModules() { + const [diagnostics, actual] = await bundle("/foo.js", { + "/foo.js": `export * from "./bar.js";\n`, + "/bar.js": `export const bar = "bar";\n` + }); + assert(diagnostics == null); + assert(actual.includes(`System.register("bar",`)); +}); + +unitTest(async function diagnosticsTest() { + const [diagnostics] = await compile("/foo.ts", { + "/foo.ts": `document.getElementById("foo");` + }); + assert(Array.isArray(diagnostics)); + assert(diagnostics.length === 1); +}); diff --git a/cli/js/tests/console_test.ts b/cli/js/tests/console_test.ts new file mode 100644 index 000000000..34a61c25f --- /dev/null +++ b/cli/js/tests/console_test.ts @@ -0,0 +1,706 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { assert, assertEquals, unitTest } from "./test_util.ts"; + +// Some of these APIs aren't exposed in the types and so we have to cast to any +// in order to "trick" TypeScript. +const { + inspect, + writeSync, + stdout + // eslint-disable-next-line @typescript-eslint/no-explicit-any +} = Deno as any; + +const customInspect = Deno.symbols.customInspect; +const { + Console, + stringifyArgs + // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol +} = Deno[Deno.symbols.internal]; + +function stringify(...args: unknown[]): string { + return stringifyArgs(args).replace(/\n$/, ""); +} + +// test cases from web-platform-tests +// via https://github.com/web-platform-tests/wpt/blob/master/console/console-is-a-namespace.any.js +unitTest(function consoleShouldBeANamespace(): void { + const prototype1 = Object.getPrototypeOf(console); + const prototype2 = Object.getPrototypeOf(prototype1); + + assertEquals(Object.getOwnPropertyNames(prototype1).length, 0); + assertEquals(prototype2, Object.prototype); +}); + +unitTest(function consoleHasRightInstance(): void { + assert(console instanceof Console); + assertEquals({} instanceof Console, false); +}); + +unitTest(function consoleTestAssertShouldNotThrowError(): void { + mockConsole(console => { + console.assert(true); + let hasThrown = undefined; + try { + console.assert(false); + hasThrown = false; + } catch { + hasThrown = true; + } + assertEquals(hasThrown, false); + }); +}); + +unitTest(function consoleTestStringifyComplexObjects(): void { + assertEquals(stringify("foo"), "foo"); + assertEquals(stringify(["foo", "bar"]), `[ "foo", "bar" ]`); + assertEquals(stringify({ foo: "bar" }), `{ foo: "bar" }`); +}); + +unitTest(function consoleTestStringifyLongStrings(): void { + const veryLongString = "a".repeat(200); + // If we stringify an object containing the long string, it gets abbreviated. + let actual = stringify({ veryLongString }); + assert(actual.includes("...")); + assert(actual.length < 200); + // However if we stringify the string itself, we get it exactly. + actual = stringify(veryLongString); + assertEquals(actual, veryLongString); +}); + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +unitTest(function consoleTestStringifyCircular(): void { + class Base { + a = 1; + m1() {} + } + + class Extended extends Base { + b = 2; + m2() {} + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nestedObj: any = { + num: 1, + bool: true, + str: "a", + method() {}, + async asyncMethod() {}, + *generatorMethod() {}, + un: undefined, + nu: null, + arrowFunc: () => {}, + extendedClass: new Extended(), + nFunc: new Function(), + extendedCstr: Extended + }; + + const circularObj = { + num: 2, + bool: false, + str: "b", + method() {}, + un: undefined, + nu: null, + nested: nestedObj, + emptyObj: {}, + arr: [1, "s", false, null, nestedObj], + baseClass: new Base() + }; + + nestedObj.o = circularObj; + const nestedObjExpected = `{ num, bool, str, method, asyncMethod, generatorMethod, un, nu, arrowFunc, extendedClass, nFunc, extendedCstr, o }`; + + assertEquals(stringify(1), "1"); + assertEquals(stringify(-0), "-0"); + assertEquals(stringify(1n), "1n"); + assertEquals(stringify("s"), "s"); + assertEquals(stringify(false), "false"); + assertEquals(stringify(new Number(1)), "[Number: 1]"); + assertEquals(stringify(new Boolean(true)), "[Boolean: true]"); + assertEquals(stringify(new String("deno")), `[String: "deno"]`); + assertEquals(stringify(/[0-9]*/), "/[0-9]*/"); + assertEquals( + stringify(new Date("2018-12-10T02:26:59.002Z")), + "2018-12-10T02:26:59.002Z" + ); + assertEquals(stringify(new Set([1, 2, 3])), "Set { 1, 2, 3 }"); + assertEquals( + stringify( + new Map([ + [1, "one"], + [2, "two"] + ]) + ), + `Map { 1 => "one", 2 => "two" }` + ); + assertEquals(stringify(new WeakSet()), "WeakSet { [items unknown] }"); + assertEquals(stringify(new WeakMap()), "WeakMap { [items unknown] }"); + assertEquals(stringify(Symbol(1)), "Symbol(1)"); + assertEquals(stringify(null), "null"); + assertEquals(stringify(undefined), "undefined"); + assertEquals(stringify(new Extended()), "Extended { a: 1, b: 2 }"); + assertEquals( + stringify(function f(): void {}), + "[Function: f]" + ); + assertEquals( + stringify(async function af(): Promise<void> {}), + "[AsyncFunction: af]" + ); + assertEquals( + stringify(function* gf() {}), + "[GeneratorFunction: gf]" + ); + assertEquals( + stringify(async function* agf() {}), + "[AsyncGeneratorFunction: agf]" + ); + assertEquals(stringify(new Uint8Array([1, 2, 3])), "Uint8Array [ 1, 2, 3 ]"); + assertEquals(stringify(Uint8Array.prototype), "TypedArray []"); + assertEquals( + stringify({ a: { b: { c: { d: new Set([1]) } } } }), + "{ a: { b: { c: { d: [Set] } } } }" + ); + assertEquals(stringify(nestedObj), nestedObjExpected); + assertEquals(stringify(JSON), "{}"); + assertEquals( + stringify(console), + "{ printFunc, log, debug, info, dir, dirxml, warn, error, assert, count, countReset, table, time, timeLog, timeEnd, group, groupCollapsed, groupEnd, clear, trace, indentLevel }" + ); + // test inspect is working the same + assertEquals(inspect(nestedObj), nestedObjExpected); +}); +/* eslint-enable @typescript-eslint/explicit-function-return-type */ + +unitTest(function consoleTestStringifyWithDepth(): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } }; + assertEquals( + stringifyArgs([nestedObj], { depth: 3 }), + "{ a: { b: { c: [Object] } } }" + ); + assertEquals( + stringifyArgs([nestedObj], { depth: 4 }), + "{ a: { b: { c: { d: [Object] } } } }" + ); + assertEquals(stringifyArgs([nestedObj], { depth: 0 }), "[Object]"); + assertEquals( + stringifyArgs([nestedObj], { depth: null }), + "{ a: { b: { c: { d: [Object] } } } }" + ); + // test inspect is working the same way + assertEquals( + inspect(nestedObj, { depth: 4 }), + "{ a: { b: { c: { d: [Object] } } } }" + ); +}); + +unitTest(function consoleTestWithCustomInspector(): void { + class A { + [customInspect](): string { + return "b"; + } + } + + assertEquals(stringify(new A()), "b"); +}); + +unitTest(function consoleTestWithCustomInspectorError(): void { + class A { + [customInspect](): string { + throw new Error("BOOM"); + return "b"; + } + } + + assertEquals(stringify(new A()), "A {}"); + + class B { + constructor(public field: { a: string }) {} + [customInspect](): string { + return this.field.a; + } + } + + assertEquals(stringify(new B({ a: "a" })), "a"); + assertEquals(stringify(B.prototype), "{}"); +}); + +unitTest(function consoleTestWithIntegerFormatSpecifier(): void { + assertEquals(stringify("%i"), "%i"); + assertEquals(stringify("%i", 42.0), "42"); + assertEquals(stringify("%i", 42), "42"); + assertEquals(stringify("%i", "42"), "42"); + assertEquals(stringify("%i", "42.0"), "42"); + assertEquals(stringify("%i", 1.5), "1"); + assertEquals(stringify("%i", -0.5), "0"); + assertEquals(stringify("%i", ""), "NaN"); + assertEquals(stringify("%i", Symbol()), "NaN"); + assertEquals(stringify("%i %d", 42, 43), "42 43"); + assertEquals(stringify("%d %i", 42), "42 %i"); + assertEquals(stringify("%d", 12345678901234567890123), "1"); + assertEquals( + stringify("%i", 12345678901234567890123n), + "12345678901234567890123n" + ); +}); + +unitTest(function consoleTestWithFloatFormatSpecifier(): void { + assertEquals(stringify("%f"), "%f"); + assertEquals(stringify("%f", 42.0), "42"); + assertEquals(stringify("%f", 42), "42"); + assertEquals(stringify("%f", "42"), "42"); + assertEquals(stringify("%f", "42.0"), "42"); + assertEquals(stringify("%f", 1.5), "1.5"); + assertEquals(stringify("%f", -0.5), "-0.5"); + assertEquals(stringify("%f", Math.PI), "3.141592653589793"); + assertEquals(stringify("%f", ""), "NaN"); + assertEquals(stringify("%f", Symbol("foo")), "NaN"); + assertEquals(stringify("%f", 5n), "5"); + assertEquals(stringify("%f %f", 42, 43), "42 43"); + assertEquals(stringify("%f %f", 42), "42 %f"); +}); + +unitTest(function consoleTestWithStringFormatSpecifier(): void { + assertEquals(stringify("%s"), "%s"); + assertEquals(stringify("%s", undefined), "undefined"); + assertEquals(stringify("%s", "foo"), "foo"); + assertEquals(stringify("%s", 42), "42"); + assertEquals(stringify("%s", "42"), "42"); + assertEquals(stringify("%s %s", 42, 43), "42 43"); + assertEquals(stringify("%s %s", 42), "42 %s"); + assertEquals(stringify("%s", Symbol("foo")), "Symbol(foo)"); +}); + +unitTest(function consoleTestWithObjectFormatSpecifier(): void { + assertEquals(stringify("%o"), "%o"); + assertEquals(stringify("%o", 42), "42"); + assertEquals(stringify("%o", "foo"), "foo"); + assertEquals(stringify("o: %o, a: %O", {}, []), "o: {}, a: []"); + assertEquals(stringify("%o", { a: 42 }), "{ a: 42 }"); + assertEquals( + stringify("%o", { a: { b: { c: { d: new Set([1]) } } } }), + "{ a: { b: { c: { d: [Set] } } } }" + ); +}); + +unitTest(function consoleTestWithVariousOrInvalidFormatSpecifier(): void { + assertEquals(stringify("%s:%s"), "%s:%s"); + assertEquals(stringify("%i:%i"), "%i:%i"); + assertEquals(stringify("%d:%d"), "%d:%d"); + assertEquals(stringify("%%s%s", "foo"), "%sfoo"); + assertEquals(stringify("%s:%s", undefined), "undefined:%s"); + assertEquals(stringify("%s:%s", "foo", "bar"), "foo:bar"); + assertEquals(stringify("%s:%s", "foo", "bar", "baz"), "foo:bar baz"); + assertEquals(stringify("%%%s%%", "hi"), "%hi%"); + assertEquals(stringify("%d:%d", 12), "12:%d"); + assertEquals(stringify("%i:%i", 12), "12:%i"); + assertEquals(stringify("%f:%f", 12), "12:%f"); + assertEquals(stringify("o: %o, a: %o", {}), "o: {}, a: %o"); + assertEquals(stringify("abc%", 1), "abc% 1"); +}); + +unitTest(function consoleTestCallToStringOnLabel(): void { + const methods = ["count", "countReset", "time", "timeLog", "timeEnd"]; + mockConsole(console => { + for (const method of methods) { + let hasCalled = false; + // @ts-ignore + console[method]({ + toString(): void { + hasCalled = true; + } + }); + assertEquals(hasCalled, true); + } + }); +}); + +unitTest(function consoleTestError(): void { + class MyError extends Error { + constructor(errStr: string) { + super(errStr); + this.name = "MyError"; + } + } + try { + throw new MyError("This is an error"); + } catch (e) { + assert( + stringify(e) + .split("\n")[0] // error has been caught + .includes("MyError: This is an error") + ); + } +}); + +unitTest(function consoleTestClear(): void { + const stdoutWriteSync = stdout.writeSync; + const uint8 = new TextEncoder().encode("\x1b[1;1H" + "\x1b[0J"); + let buffer = new Uint8Array(0); + + stdout.writeSync = (u8: Uint8Array): Promise<number> => { + const tmp = new Uint8Array(buffer.length + u8.length); + tmp.set(buffer, 0); + tmp.set(u8, buffer.length); + buffer = tmp; + + return writeSync(stdout.rid, u8); + }; + console.clear(); + stdout.writeSync = stdoutWriteSync; + assertEquals(buffer, uint8); +}); + +// Test bound this issue +unitTest(function consoleDetachedLog(): void { + mockConsole(console => { + const log = console.log; + const dir = console.dir; + const dirxml = console.dirxml; + const debug = console.debug; + const info = console.info; + const warn = console.warn; + const error = console.error; + const consoleAssert = console.assert; + const consoleCount = console.count; + const consoleCountReset = console.countReset; + const consoleTable = console.table; + const consoleTime = console.time; + const consoleTimeLog = console.timeLog; + const consoleTimeEnd = console.timeEnd; + const consoleGroup = console.group; + const consoleGroupEnd = console.groupEnd; + const consoleClear = console.clear; + log("Hello world"); + dir("Hello world"); + dirxml("Hello world"); + debug("Hello world"); + info("Hello world"); + warn("Hello world"); + error("Hello world"); + consoleAssert(true); + consoleCount("Hello world"); + consoleCountReset("Hello world"); + consoleTable({ test: "Hello world" }); + consoleTime("Hello world"); + consoleTimeLog("Hello world"); + consoleTimeEnd("Hello world"); + consoleGroup("Hello world"); + consoleGroupEnd(); + consoleClear(); + }); +}); + +class StringBuffer { + chunks: string[] = []; + add(x: string): void { + this.chunks.push(x); + } + toString(): string { + return this.chunks.join(""); + } +} + +type ConsoleExamineFunc = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + csl: any, + out: StringBuffer, + err?: StringBuffer, + both?: StringBuffer +) => void; + +function mockConsole(f: ConsoleExamineFunc): void { + const out = new StringBuffer(); + const err = new StringBuffer(); + const both = new StringBuffer(); + const csl = new Console( + (x: string, isErr: boolean, printsNewLine: boolean): void => { + const content = x + (printsNewLine ? "\n" : ""); + const buf = isErr ? err : out; + buf.add(content); + both.add(content); + } + ); + f(csl, out, err, both); +} + +// console.group test +unitTest(function consoleGroup(): void { + mockConsole((console, out): void => { + console.group("1"); + console.log("2"); + console.group("3"); + console.log("4"); + console.groupEnd(); + console.groupEnd(); + console.log("5"); + console.log("6"); + + assertEquals( + out.toString(), + `1 + 2 + 3 + 4 +5 +6 +` + ); + }); +}); + +// console.group with console.warn test +unitTest(function consoleGroupWarn(): void { + mockConsole((console, _out, _err, both): void => { + assert(both); + console.warn("1"); + console.group(); + console.warn("2"); + console.group(); + console.warn("3"); + console.groupEnd(); + console.warn("4"); + console.groupEnd(); + console.warn("5"); + + console.warn("6"); + console.warn("7"); + assertEquals( + both.toString(), + `1 + 2 + 3 + 4 +5 +6 +7 +` + ); + }); +}); + +// console.table test +unitTest(function consoleTable(): void { + mockConsole((console, out): void => { + console.table({ a: "test", b: 1 }); + assertEquals( + out.toString(), + `┌─────────┬────────┐ +│ (index) │ Values │ +├─────────┼────────┤ +│ a │ "test" │ +│ b │ 1 │ +└─────────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]); + assertEquals( + out.toString(), + `┌─────────┬────┐ +│ (index) │ c │ +├─────────┼────┤ +│ a │ │ +│ b │ 30 │ +└─────────┴────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]); + assertEquals( + out.toString(), + `┌─────────┬───────┬───────┬────────┐ +│ (index) │ 0 │ 1 │ Values │ +├─────────┼───────┼───────┼────────┤ +│ 0 │ │ │ 1 │ +│ 1 │ │ │ 2 │ +│ 2 │ 3 │ [ 4 ] │ │ +│ 3 │ 5 │ 6 │ │ +│ 4 │ [ 7 ] │ [ 8 ] │ │ +└─────────┴───────┴───────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table(new Set([1, 2, 3, "test"])); + assertEquals( + out.toString(), + `┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +│ 3 │ "test" │ +└───────────────────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table( + new Map([ + [1, "one"], + [2, "two"] + ]) + ); + assertEquals( + out.toString(), + `┌───────────────────┬─────┬────────┐ +│ (iteration index) │ Key │ Values │ +├───────────────────┼─────┼────────┤ +│ 0 │ 1 │ "one" │ +│ 1 │ 2 │ "two" │ +└───────────────────┴─────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table({ + a: true, + b: { c: { d: 10 }, e: [1, 2, [5, 6]] }, + f: "test", + g: new Set([1, 2, 3, "test"]), + h: new Map([[1, "one"]]) + }); + assertEquals( + out.toString(), + `┌─────────┬───────────┬───────────────────┬────────┐ +│ (index) │ c │ e │ Values │ +├─────────┼───────────┼───────────────────┼────────┤ +│ a │ │ │ true │ +│ b │ { d: 10 } │ [ 1, 2, [Array] ] │ │ +│ f │ │ │ "test" │ +│ g │ │ │ │ +│ h │ │ │ │ +└─────────┴───────────┴───────────────────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table([ + 1, + "test", + false, + { a: 10 }, + ["test", { b: 20, c: "test" }] + ]); + assertEquals( + out.toString(), + `┌─────────┬────────┬──────────────────────┬────┬────────┐ +│ (index) │ 0 │ 1 │ a │ Values │ +├─────────┼────────┼──────────────────────┼────┼────────┤ +│ 0 │ │ │ │ 1 │ +│ 1 │ │ │ │ "test" │ +│ 2 │ │ │ │ false │ +│ 3 │ │ │ 10 │ │ +│ 4 │ "test" │ { b: 20, c: "test" } │ │ │ +└─────────┴────────┴──────────────────────┴────┴────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table([]); + assertEquals( + out.toString(), + `┌─────────┐ +│ (index) │ +├─────────┤ +└─────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table({}); + assertEquals( + out.toString(), + `┌─────────┐ +│ (index) │ +├─────────┤ +└─────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table(new Set()); + assertEquals( + out.toString(), + `┌───────────────────┐ +│ (iteration index) │ +├───────────────────┤ +└───────────────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table(new Map()); + assertEquals( + out.toString(), + `┌───────────────────┐ +│ (iteration index) │ +├───────────────────┤ +└───────────────────┘ +` + ); + }); + mockConsole((console, out): void => { + console.table("test"); + assertEquals(out.toString(), "test\n"); + }); +}); + +// console.log(Error) test +unitTest(function consoleLogShouldNotThrowError(): void { + mockConsole(console => { + let result = 0; + try { + console.log(new Error("foo")); + result = 1; + } catch (e) { + result = 2; + } + assertEquals(result, 1); + }); + + // output errors to the console should not include "Uncaught" + mockConsole((console, out): void => { + console.log(new Error("foo")); + assertEquals(out.toString().includes("Uncaught"), false); + }); +}); + +// console.dir test +unitTest(function consoleDir(): void { + mockConsole((console, out): void => { + console.dir("DIR"); + assertEquals(out.toString(), "DIR\n"); + }); + mockConsole((console, out): void => { + console.dir("DIR", { indentLevel: 2 }); + assertEquals(out.toString(), " DIR\n"); + }); +}); + +// console.dir test +unitTest(function consoleDirXml(): void { + mockConsole((console, out): void => { + console.dirxml("DIRXML"); + assertEquals(out.toString(), "DIRXML\n"); + }); + mockConsole((console, out): void => { + console.dirxml("DIRXML", { indentLevel: 2 }); + assertEquals(out.toString(), " DIRXML\n"); + }); +}); + +// console.trace test +unitTest(function consoleTrace(): void { + mockConsole((console, _out, err): void => { + console.trace("%s", "custom message"); + assert(err); + assert(err.toString().includes("Trace: custom message")); + }); +}); diff --git a/cli/js/tests/copy_file_test.ts b/cli/js/tests/copy_file_test.ts new file mode 100644 index 000000000..986bab53b --- /dev/null +++ b/cli/js/tests/copy_file_test.ts @@ -0,0 +1,176 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +function readFileString(filename: string): string { + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + return dec.decode(dataRead); +} + +function writeFileString(filename: string, s: string): void { + const enc = new TextEncoder(); + const data = enc.encode(s); + Deno.writeFileSync(filename, data, { mode: 0o666 }); +} + +function assertSameContent(filename1: string, filename2: string): void { + const data1 = Deno.readFileSync(filename1); + const data2 = Deno.readFileSync(filename2); + assertEquals(data1, data2); +} + +unitTest( + { perms: { read: true, write: true } }, + function copyFileSyncSuccess(): void { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + writeFileString(fromFilename, "Hello world!"); + Deno.copyFileSync(fromFilename, toFilename); + // No change to original file + assertEquals(readFileString(fromFilename), "Hello world!"); + // Original == Dest + assertSameContent(fromFilename, toFilename); + } +); + +unitTest( + { perms: { write: true, read: true } }, + function copyFileSyncFailure(): void { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + // We skip initial writing here, from.txt does not exist + let err; + try { + Deno.copyFileSync(fromFilename, toFilename); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: false } }, + function copyFileSyncPerm1(): void { + let caughtError = false; + try { + Deno.copyFileSync("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); + +unitTest( + { perms: { write: false, read: true } }, + function copyFileSyncPerm2(): void { + let caughtError = false; + try { + Deno.copyFileSync("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function copyFileSyncOverwrite(): void { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + writeFileString(fromFilename, "Hello world!"); + // Make Dest exist and have different content + writeFileString(toFilename, "Goodbye!"); + Deno.copyFileSync(fromFilename, toFilename); + // No change to original file + assertEquals(readFileString(fromFilename), "Hello world!"); + // Original == Dest + assertSameContent(fromFilename, toFilename); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function copyFileSuccess(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + writeFileString(fromFilename, "Hello world!"); + await Deno.copyFile(fromFilename, toFilename); + // No change to original file + assertEquals(readFileString(fromFilename), "Hello world!"); + // Original == Dest + assertSameContent(fromFilename, toFilename); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function copyFileFailure(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + // We skip initial writing here, from.txt does not exist + let err; + try { + await Deno.copyFile(fromFilename, toFilename); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function copyFileOverwrite(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const fromFilename = tempDir + "/from.txt"; + const toFilename = tempDir + "/to.txt"; + writeFileString(fromFilename, "Hello world!"); + // Make Dest exist and have different content + writeFileString(toFilename, "Goodbye!"); + await Deno.copyFile(fromFilename, toFilename); + // No change to original file + assertEquals(readFileString(fromFilename), "Hello world!"); + // Original == Dest + assertSameContent(fromFilename, toFilename); + } +); + +unitTest( + { perms: { read: false, write: true } }, + async function copyFilePerm1(): Promise<void> { + let caughtError = false; + try { + await Deno.copyFile("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: false } }, + async function copyFilePerm2(): Promise<void> { + let caughtError = false; + try { + await Deno.copyFile("/from.txt", "/to.txt"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); diff --git a/cli/js/tests/custom_event_test.ts b/cli/js/tests/custom_event_test.ts new file mode 100644 index 000000000..7a5cc44ca --- /dev/null +++ b/cli/js/tests/custom_event_test.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals } from "./test_util.ts"; + +unitTest(function customEventInitializedWithDetail(): void { + const type = "touchstart"; + const detail = { message: "hello" }; + const customEventInit = { + bubbles: true, + cancelable: true, + detail + } as CustomEventInit; + const event = new CustomEvent(type, customEventInit); + + assertEquals(event.bubbles, true); + assertEquals(event.cancelable, true); + assertEquals(event.currentTarget, null); + assertEquals(event.detail, detail); + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.type, type); +}); + +unitTest(function toStringShouldBeWebCompatibility(): void { + const type = "touchstart"; + const event = new CustomEvent(type, {}); + assertEquals(event.toString(), "[object CustomEvent]"); +}); diff --git a/cli/js/tests/dir_test.ts b/cli/js/tests/dir_test.ts new file mode 100644 index 000000000..75184587b --- /dev/null +++ b/cli/js/tests/dir_test.ts @@ -0,0 +1,54 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function dirCwdNotNull(): void { + assert(Deno.cwd() != null); +}); + +unitTest({ perms: { write: true } }, function dirCwdChdirSuccess(): void { + const initialdir = Deno.cwd(); + const path = Deno.makeTempDirSync(); + Deno.chdir(path); + const current = Deno.cwd(); + if (Deno.build.os === "mac") { + assertEquals(current, "/private" + path); + } else { + assertEquals(current, path); + } + Deno.chdir(initialdir); +}); + +unitTest({ perms: { write: true } }, function dirCwdError(): void { + // excluding windows since it throws resource busy, while removeSync + if (["linux", "mac"].includes(Deno.build.os)) { + const initialdir = Deno.cwd(); + const path = Deno.makeTempDirSync(); + Deno.chdir(path); + Deno.removeSync(path); + try { + Deno.cwd(); + throw Error("current directory removed, should throw error"); + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + assert(err.name === "NotFound"); + } else { + throw Error("raised different exception"); + } + } + Deno.chdir(initialdir); + } +}); + +unitTest({ perms: { write: true } }, function dirChdirError(): void { + const path = Deno.makeTempDirSync() + "test"; + try { + Deno.chdir(path); + throw Error("directory not available, should throw error"); + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + assert(err.name === "NotFound"); + } else { + throw Error("raised different exception"); + } + } +}); diff --git a/cli/js/tests/dispatch_json_test.ts b/cli/js/tests/dispatch_json_test.ts new file mode 100644 index 000000000..a2306dda8 --- /dev/null +++ b/cli/js/tests/dispatch_json_test.ts @@ -0,0 +1,32 @@ +import { assert, unitTest, assertMatch, unreachable } from "./test_util.ts"; + +const openErrorStackPattern = new RegExp( + `^.* + at unwrapResponse \\(.*dispatch_json\\.ts:.*\\) + at Object.sendAsync \\(.*dispatch_json\\.ts:.*\\) + at async Object\\.open \\(.*files\\.ts:.*\\).*$`, + "ms" +); + +unitTest( + { perms: { read: true } }, + async function sendAsyncStackTrace(): Promise<void> { + await Deno.open("nonexistent.txt") + .then(unreachable) + .catch((error): void => { + assertMatch(error.stack, openErrorStackPattern); + }); + } +); + +unitTest(async function malformedJsonControlBuffer(): Promise<void> { + // @ts-ignore + const opId = Deno.core.ops()["op_open"]; + // @ts-ignore + const res = Deno.core.send(opId, new Uint8Array([1, 2, 3, 4, 5])); + const resText = new TextDecoder().decode(res); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const resJson = JSON.parse(resText) as any; + assert(!resJson.ok); + assert(resJson.err); +}); diff --git a/cli/js/tests/dispatch_minimal_test.ts b/cli/js/tests/dispatch_minimal_test.ts new file mode 100644 index 000000000..724a41698 --- /dev/null +++ b/cli/js/tests/dispatch_minimal_test.ts @@ -0,0 +1,43 @@ +import { + assert, + assertEquals, + assertMatch, + unitTest, + unreachable +} from "./test_util.ts"; + +const readErrorStackPattern = new RegExp( + `^.* + at unwrapResponse \\(.*dispatch_minimal\\.ts:.*\\) + at Object.sendAsyncMinimal \\(.*dispatch_minimal\\.ts:.*\\) + at async Object\\.read \\(.*io\\.ts:.*\\).*$`, + "ms" +); + +unitTest(async function sendAsyncStackTrace(): Promise<void> { + const buf = new Uint8Array(10); + const rid = 10; + try { + await Deno.read(rid, buf); + unreachable(); + } catch (error) { + assertMatch(error.stack, readErrorStackPattern); + } +}); + +unitTest(async function malformedMinimalControlBuffer(): Promise<void> { + // @ts-ignore + const readOpId = Deno.core.ops()["op_read"]; + // @ts-ignore + const res = Deno.core.send(readOpId, new Uint8Array([1, 2, 3, 4, 5])); + const header = res.slice(0, 12); + const buf32 = new Int32Array( + header.buffer, + header.byteOffset, + header.byteLength / 4 + ); + const arg = buf32[1]; + const message = new TextDecoder().decode(res.slice(12)).trim(); + assert(arg < 0); + assertEquals(message, "Unparsable control buffer"); +}); diff --git a/cli/js/tests/dom_iterable_test.ts b/cli/js/tests/dom_iterable_test.ts new file mode 100644 index 000000000..e16378945 --- /dev/null +++ b/cli/js/tests/dom_iterable_test.ts @@ -0,0 +1,87 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +function setup() { + const dataSymbol = Symbol("data symbol"); + class Base { + private [dataSymbol] = new Map<string, number>(); + + constructor( + data: Array<[string, number]> | IterableIterator<[string, number]> + ) { + for (const [key, value] of data) { + this[dataSymbol].set(key, value); + } + } + } + + return { + Base, + // This is using an internal API we don't want published as types, so having + // to cast to any to "trick" TypeScript + // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol + DomIterable: Deno[Deno.symbols.internal].DomIterableMixin(Base, dataSymbol) + }; +} + +unitTest(function testDomIterable(): void { + const { DomIterable, Base } = setup(); + + const fixture: Array<[string, number]> = [ + ["foo", 1], + ["bar", 2] + ]; + + const domIterable = new DomIterable(fixture); + + assertEquals(Array.from(domIterable.entries()), fixture); + assertEquals(Array.from(domIterable.values()), [1, 2]); + assertEquals(Array.from(domIterable.keys()), ["foo", "bar"]); + + let result: Array<[string, number]> = []; + for (const [key, value] of domIterable) { + assert(key != null); + assert(value != null); + result.push([key, value]); + } + assertEquals(fixture, result); + + result = []; + const scope = {}; + function callback( + this: typeof scope, + value: number, + key: string, + parent: typeof domIterable + ): void { + assertEquals(parent, domIterable); + assert(key != null); + assert(value != null); + assert(this === scope); + result.push([key, value]); + } + domIterable.forEach(callback, scope); + assertEquals(fixture, result); + + assertEquals(DomIterable.name, Base.name); +}); + +unitTest(function testDomIterableScope(): void { + const { DomIterable } = setup(); + + const domIterable = new DomIterable([["foo", 1]]); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function checkScope(thisArg: any, expected: any): void { + function callback(this: typeof thisArg): void { + assertEquals(this, expected); + } + domIterable.forEach(callback, thisArg); + } + + checkScope(0, Object(0)); + checkScope("", Object("")); + checkScope(null, window); + checkScope(undefined, window); +}); diff --git a/cli/js/tests/error_stack_test.ts b/cli/js/tests/error_stack_test.ts new file mode 100644 index 000000000..7acbd3a3d --- /dev/null +++ b/cli/js/tests/error_stack_test.ts @@ -0,0 +1,108 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +// @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol +const { setPrepareStackTrace } = Deno[Deno.symbols.internal]; + +interface CallSite { + getThis(): unknown; + getTypeName(): string; + getFunction(): Function; + getFunctionName(): string; + getMethodName(): string; + getFileName(): string; + getLineNumber(): number | null; + getColumnNumber(): number | null; + getEvalOrigin(): string | null; + isToplevel(): boolean; + isEval(): boolean; + isNative(): boolean; + isConstructor(): boolean; + isAsync(): boolean; + isPromiseAll(): boolean; + getPromiseIndex(): number | null; +} + +function getMockCallSite( + filename: string, + line: number | null, + column: number | null +): CallSite { + return { + getThis(): unknown { + return undefined; + }, + getTypeName(): string { + return ""; + }, + getFunction(): Function { + return (): void => {}; + }, + getFunctionName(): string { + return ""; + }, + getMethodName(): string { + return ""; + }, + getFileName(): string { + return filename; + }, + getLineNumber(): number | null { + return line; + }, + getColumnNumber(): number | null { + return column; + }, + getEvalOrigin(): null { + return null; + }, + isToplevel(): false { + return false; + }, + isEval(): false { + return false; + }, + isNative(): false { + return false; + }, + isConstructor(): false { + return false; + }, + isAsync(): false { + return false; + }, + isPromiseAll(): false { + return false; + }, + getPromiseIndex(): null { + return null; + } + }; +} + +unitTest(function prepareStackTrace(): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const MockError = {} as any; + setPrepareStackTrace(MockError); + assert(typeof MockError.prepareStackTrace === "function"); + const prepareStackTrace: ( + error: Error, + structuredStackTrace: CallSite[] + ) => string = MockError.prepareStackTrace; + const result = prepareStackTrace(new Error("foo"), [ + getMockCallSite("CLI_SNAPSHOT.js", 23, 0) + ]); + assert(result.startsWith("Error: foo\n")); + assert(result.includes(".ts:"), "should remap to something in 'js/'"); +}); + +unitTest(function applySourceMap(): void { + const result = Deno.applySourceMap({ + filename: "CLI_SNAPSHOT.js", + line: 23, + column: 0 + }); + assert(result.filename.endsWith(".ts")); + assert(result.line != null); + assert(result.column != null); +}); diff --git a/cli/js/tests/event_target_test.ts b/cli/js/tests/event_target_test.ts new file mode 100644 index 000000000..09d47f704 --- /dev/null +++ b/cli/js/tests/event_target_test.ts @@ -0,0 +1,231 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals } from "./test_util.ts"; + +unitTest(function addEventListenerTest(): void { + const document = new EventTarget(); + + // @ts-ignore tests ignoring the type system for resilience + assertEquals(document.addEventListener("x", null, false), undefined); + // @ts-ignore + assertEquals(document.addEventListener("x", null, true), undefined); + // @ts-ignore + assertEquals(document.addEventListener("x", null), undefined); +}); + +unitTest(function constructedEventTargetCanBeUsedAsExpected(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = (e: Event): void => { + assertEquals(e, event); + ++callCount; + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +unitTest(function anEventTargetCanBeSubclassed(): void { + class NicerEventTarget extends EventTarget { + on( + type: string, + callback: ((e: Event) => void) | null, + options?: __domTypes.AddEventListenerOptions + ): void { + this.addEventListener(type, callback, options); + } + + off( + type: string, + callback: ((e: Event) => void) | null, + options?: __domTypes.EventListenerOptions + ): void { + this.removeEventListener(type, callback, options); + } + } + + const target = new NicerEventTarget(); + new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = (): void => { + ++callCount; + }; + + target.on("foo", listener); + assertEquals(callCount, 0); + + target.off("foo", listener); + assertEquals(callCount, 0); +}); + +unitTest(function removingNullEventListenerShouldSucceed(): void { + const document = new EventTarget(); + // @ts-ignore + assertEquals(document.removeEventListener("x", null, false), undefined); + // @ts-ignore + assertEquals(document.removeEventListener("x", null, true), undefined); + // @ts-ignore + assertEquals(document.removeEventListener("x", null), undefined); +}); + +unitTest(function constructedEventTargetUseObjectPrototype(): void { + const target = new EventTarget(); + const event = new Event("toString", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = (e: Event): void => { + assertEquals(e, event); + ++callCount; + }; + + target.addEventListener("toString", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("toString", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +unitTest(function toStringShouldBeWebCompatible(): void { + const target = new EventTarget(); + assertEquals(target.toString(), "[object EventTarget]"); +}); + +unitTest(function dispatchEventShouldNotThrowError(): void { + let hasThrown = false; + + try { + const target = new EventTarget(); + const event = new Event("hasOwnProperty", { + bubbles: true, + cancelable: false + }); + const listener = (): void => {}; + target.addEventListener("hasOwnProperty", listener); + target.dispatchEvent(event); + } catch { + hasThrown = true; + } + + assertEquals(hasThrown, false); +}); + +unitTest(function eventTargetThisShouldDefaultToWindow(): void { + const { + addEventListener, + dispatchEvent, + removeEventListener + } = EventTarget.prototype; + let n = 1; + const event = new Event("hello"); + const listener = (): void => { + n = 2; + }; + + addEventListener("hello", listener); + window.dispatchEvent(event); + assertEquals(n, 2); + n = 1; + removeEventListener("hello", listener); + window.dispatchEvent(event); + assertEquals(n, 1); + + window.addEventListener("hello", listener); + dispatchEvent(event); + assertEquals(n, 2); + n = 1; + window.removeEventListener("hello", listener); + dispatchEvent(event); + assertEquals(n, 1); +}); + +unitTest(function eventTargetShouldAcceptEventListenerObject(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = { + handleEvent(e: Event): void { + assertEquals(e, event); + ++callCount; + } + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +unitTest(function eventTargetShouldAcceptAsyncFunction(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = async (e: Event): Promise<void> => { + assertEquals(e, event); + ++callCount; + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +unitTest( + function eventTargetShouldAcceptAsyncFunctionForEventListenerObject(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = { + async handleEvent(e: Event): Promise<void> { + assertEquals(e, event); + ++callCount; + } + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); + } +); diff --git a/cli/js/tests/event_test.ts b/cli/js/tests/event_test.ts new file mode 100644 index 000000000..05a9ed577 --- /dev/null +++ b/cli/js/tests/event_test.ts @@ -0,0 +1,96 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals, assert } from "./test_util.ts"; + +unitTest(function eventInitializedWithType(): void { + const type = "click"; + const event = new Event(type); + + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "click"); + assertEquals(event.bubbles, false); + assertEquals(event.cancelable, false); +}); + +unitTest(function eventInitializedWithTypeAndDict(): void { + const init = "submit"; + const eventInit = { bubbles: true, cancelable: true } as EventInit; + const event = new Event(init, eventInit); + + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "submit"); + assertEquals(event.bubbles, true); + assertEquals(event.cancelable, true); +}); + +unitTest(function eventComposedPathSuccess(): void { + const type = "click"; + const event = new Event(type); + const composedPath = event.composedPath(); + + assertEquals(composedPath, []); +}); + +unitTest(function eventStopPropagationSuccess(): void { + const type = "click"; + const event = new Event(type); + + assertEquals(event.cancelBubble, false); + event.stopPropagation(); + assertEquals(event.cancelBubble, true); +}); + +unitTest(function eventStopImmediatePropagationSuccess(): void { + const type = "click"; + const event = new Event(type); + + assertEquals(event.cancelBubble, false); + assertEquals(event.cancelBubbleImmediately, false); + event.stopImmediatePropagation(); + assertEquals(event.cancelBubble, true); + assertEquals(event.cancelBubbleImmediately, true); +}); + +unitTest(function eventPreventDefaultSuccess(): void { + const type = "click"; + const event = new Event(type); + + assertEquals(event.defaultPrevented, false); + event.preventDefault(); + assertEquals(event.defaultPrevented, false); + + const eventInit = { bubbles: true, cancelable: true } as EventInit; + const cancelableEvent = new Event(type, eventInit); + assertEquals(cancelableEvent.defaultPrevented, false); + cancelableEvent.preventDefault(); + assertEquals(cancelableEvent.defaultPrevented, true); +}); + +unitTest(function eventInitializedWithNonStringType(): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const type: any = undefined; + const event = new Event(type); + + assertEquals(event.isTrusted, false); + assertEquals(event.target, null); + assertEquals(event.currentTarget, null); + assertEquals(event.type, "undefined"); + assertEquals(event.bubbles, false); + assertEquals(event.cancelable, false); +}); + +// ref https://github.com/web-platform-tests/wpt/blob/master/dom/events/Event-isTrusted.any.js +unitTest(function eventIsTrusted(): void { + const desc1 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted"); + assert(desc1); + assertEquals(typeof desc1.get, "function"); + + const desc2 = Object.getOwnPropertyDescriptor(new Event("x"), "isTrusted"); + assert(desc2); + assertEquals(typeof desc2!.get, "function"); + + assertEquals(desc1!.get, desc2!.get); +}); diff --git a/cli/js/tests/fetch_test.ts b/cli/js/tests/fetch_test.ts new file mode 100644 index 000000000..5ebef92d9 --- /dev/null +++ b/cli/js/tests/fetch_test.ts @@ -0,0 +1,497 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + unitTest, + assert, + assertEquals, + assertStrContains, + assertThrows, + fail +} from "./test_util.ts"; + +unitTest({ perms: { net: true } }, async function fetchProtocolError(): Promise< + void +> { + let err; + try { + await fetch("file:///"); + } catch (err_) { + err = err_; + } + assert(err instanceof TypeError); + assertStrContains(err.message, "not supported"); +}); + +unitTest( + { perms: { net: true } }, + async function fetchConnectionError(): Promise<void> { + let err; + try { + await fetch("http://localhost:4000"); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.Http); + assertStrContains(err.message, "error trying to connect"); + } +); + +unitTest({ perms: { net: true } }, async function fetchJsonSuccess(): Promise< + void +> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + const json = await response.json(); + assertEquals(json.name, "deno"); +}); + +unitTest(async function fetchPerm(): Promise<void> { + let err; + try { + await fetch("http://localhost:4545/cli/tests/fixture.json"); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest({ perms: { net: true } }, async function fetchUrl(): Promise<void> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + assertEquals(response.url, "http://localhost:4545/cli/tests/fixture.json"); + response.body.close(); +}); + +unitTest({ perms: { net: true } }, async function fetchURL(): Promise<void> { + const response = await fetch( + new URL("http://localhost:4545/cli/tests/fixture.json") + ); + assertEquals(response.url, "http://localhost:4545/cli/tests/fixture.json"); + response.body.close(); +}); + +unitTest({ perms: { net: true } }, async function fetchHeaders(): Promise< + void +> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + const headers = response.headers; + assertEquals(headers.get("Content-Type"), "application/json"); + assert(headers.get("Server")!.startsWith("SimpleHTTP")); + response.body.close(); +}); + +unitTest({ perms: { net: true } }, async function fetchBlob(): Promise<void> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + const headers = response.headers; + const blob = await response.blob(); + assertEquals(blob.type, headers.get("Content-Type")); + assertEquals(blob.size, Number(headers.get("Content-Length"))); +}); + +unitTest({ perms: { net: true } }, async function fetchBodyUsed(): Promise< + void +> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + assertEquals(response.bodyUsed, false); + assertThrows((): void => { + // Assigning to read-only property throws in the strict mode. + response.bodyUsed = true; + }); + await response.blob(); + assertEquals(response.bodyUsed, true); +}); + +unitTest({ perms: { net: true } }, async function fetchAsyncIterator(): Promise< + void +> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + const headers = response.headers; + let total = 0; + for await (const chunk of response.body) { + total += chunk.length; + } + + assertEquals(total, Number(headers.get("Content-Length"))); + response.body.close(); +}); + +unitTest({ perms: { net: true } }, async function responseClone(): Promise< + void +> { + const response = await fetch("http://localhost:4545/cli/tests/fixture.json"); + const response1 = response.clone(); + assert(response !== response1); + assertEquals(response.status, response1.status); + assertEquals(response.statusText, response1.statusText); + const u8a = new Uint8Array(await response.arrayBuffer()); + const u8a1 = new Uint8Array(await response1.arrayBuffer()); + for (let i = 0; i < u8a.byteLength; i++) { + assertEquals(u8a[i], u8a1[i]); + } +}); + +unitTest({ perms: { net: true } }, async function fetchEmptyInvalid(): Promise< + void +> { + let err; + try { + await fetch(""); + } catch (err_) { + err = err_; + } + assert(err instanceof URIError); +}); + +unitTest( + { perms: { net: true } }, + async function fetchMultipartFormDataSuccess(): Promise<void> { + const response = await fetch( + "http://localhost:4545/cli/tests/subdir/multipart_form_data.txt" + ); + const formData = await response.formData(); + assert(formData.has("field_1")); + assertEquals(formData.get("field_1")!.toString(), "value_1 \r\n"); + assert(formData.has("field_2")); + /* TODO(ry) Re-enable this test once we bring back the global File type. + const file = formData.get("field_2") as File; + assertEquals(file.name, "file.js"); + */ + // Currently we cannot read from file... + } +); + +unitTest( + { perms: { net: true } }, + async function fetchURLEncodedFormDataSuccess(): Promise<void> { + const response = await fetch( + "http://localhost:4545/cli/tests/subdir/form_urlencoded.txt" + ); + const formData = await response.formData(); + assert(formData.has("field_1")); + assertEquals(formData.get("field_1")!.toString(), "Hi"); + assert(formData.has("field_2")); + assertEquals(formData.get("field_2")!.toString(), "<Deno>"); + } +); + +unitTest( + { + // TODO(bartlomieju): leaking resources + skip: true, + perms: { net: true } + }, + async function fetchWithRedirection(): Promise<void> { + const response = await fetch("http://localhost:4546/"); // will redirect to http://localhost:4545/ + assertEquals(response.status, 200); + assertEquals(response.statusText, "OK"); + assertEquals(response.url, "http://localhost:4545/"); + const body = await response.text(); + assert(body.includes("<title>Directory listing for /</title>")); + } +); + +unitTest( + { + // TODO: leaking resources + skip: true, + perms: { net: true } + }, + async function fetchWithRelativeRedirection(): Promise<void> { + const response = await fetch("http://localhost:4545/cli/tests"); // will redirect to /cli/tests/ + assertEquals(response.status, 200); + assertEquals(response.statusText, "OK"); + const body = await response.text(); + assert(body.includes("<title>Directory listing for /cli/tests/</title>")); + } +); + +// The feature below is not implemented, but the test should work after implementation +/* +unitTest({ perms: { net: true} }, async function fetchWithInfRedirection(): Promise< + void +> { + const response = await fetch("http://localhost:4549/cli/tests"); // will redirect to the same place + assertEquals(response.status, 0); // network error +}); +*/ + +unitTest( + { perms: { net: true } }, + async function fetchInitStringBody(): Promise<void> { + const data = "Hello World"; + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: data + }); + const text = await response.text(); + assertEquals(text, data); + assert(response.headers.get("content-type")!.startsWith("text/plain")); + } +); + +unitTest( + { perms: { net: true } }, + async function fetchRequestInitStringBody(): Promise<void> { + const data = "Hello World"; + const req = new Request("http://localhost:4545/echo_server", { + method: "POST", + body: data + }); + const response = await fetch(req); + const text = await response.text(); + assertEquals(text, data); + } +); + +unitTest( + { perms: { net: true } }, + async function fetchInitTypedArrayBody(): Promise<void> { + const data = "Hello World"; + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: new TextEncoder().encode(data) + }); + const text = await response.text(); + assertEquals(text, data); + } +); + +unitTest( + { perms: { net: true } }, + async function fetchInitURLSearchParamsBody(): Promise<void> { + const data = "param1=value1¶m2=value2"; + const params = new URLSearchParams(data); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: params + }); + const text = await response.text(); + assertEquals(text, data); + assert( + response.headers + .get("content-type")! + .startsWith("application/x-www-form-urlencoded") + ); + } +); + +unitTest({ perms: { net: true } }, async function fetchInitBlobBody(): Promise< + void +> { + const data = "const a = 1"; + const blob = new Blob([data], { + type: "text/javascript" + }); + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: blob + }); + const text = await response.text(); + assertEquals(text, data); + assert(response.headers.get("content-type")!.startsWith("text/javascript")); +}); + +unitTest({ perms: { net: true } }, async function fetchUserAgent(): Promise< + void +> { + const data = "Hello World"; + const response = await fetch("http://localhost:4545/echo_server", { + method: "POST", + body: new TextEncoder().encode(data) + }); + assertEquals(response.headers.get("user-agent"), `Deno/${Deno.version.deno}`); + await response.text(); +}); + +// TODO(ry) The following tests work but are flaky. There's a race condition +// somewhere. Here is what one of these flaky failures looks like: +// +// unitTest fetchPostBodyString_permW0N1E0R0 +// assertEquals failed. actual = expected = POST /blah HTTP/1.1 +// hello: World +// foo: Bar +// host: 127.0.0.1:4502 +// content-length: 11 +// hello world +// Error: actual: expected: POST /blah HTTP/1.1 +// hello: World +// foo: Bar +// host: 127.0.0.1:4502 +// content-length: 11 +// hello world +// at Object.assertEquals (file:///C:/deno/js/testing/util.ts:29:11) +// at fetchPostBodyString (file + +/* +function bufferServer(addr: string): Deno.Buffer { + const listener = Deno.listen(addr); + const buf = new Deno.Buffer(); + listener.accept().then(async conn => { + const p1 = buf.readFrom(conn); + const p2 = conn.write( + new TextEncoder().encode( + "HTTP/1.0 404 Not Found\r\nContent-Length: 2\r\n\r\nNF" + ) + ); + // Wait for both an EOF on the read side of the socket and for the write to + // complete before closing it. Due to keep-alive, the EOF won't be sent + // until the Connection close (HTTP/1.0) response, so readFrom() can't + // proceed write. Conversely, if readFrom() is async, waiting for the + // write() to complete is not a guarantee that we've read the incoming + // request. + await Promise.all([p1, p2]); + conn.close(); + listener.close(); + }); + return buf; +} + +unitTest({ perms: { net: true} }, async function fetchRequest():Promise<void> { + const addr = "127.0.0.1:4501"; + const buf = bufferServer(addr); + const response = await fetch(`http://${addr}/blah`, { + method: "POST", + headers: [["Hello", "World"], ["Foo", "Bar"]] + }); + assertEquals(response.status, 404); + assertEquals(response.headers.get("Content-Length"), "2"); + + const actual = new TextDecoder().decode(buf.bytes()); + const expected = [ + "POST /blah HTTP/1.1\r\n", + "hello: World\r\n", + "foo: Bar\r\n", + `host: ${addr}\r\n\r\n` + ].join(""); + assertEquals(actual, expected); +}); + +unitTest({ perms: { net: true} }, async function fetchPostBodyString():Promise<void> { + const addr = "127.0.0.1:4502"; + const buf = bufferServer(addr); + const body = "hello world"; + const response = await fetch(`http://${addr}/blah`, { + method: "POST", + headers: [["Hello", "World"], ["Foo", "Bar"]], + body + }); + assertEquals(response.status, 404); + assertEquals(response.headers.get("Content-Length"), "2"); + + const actual = new TextDecoder().decode(buf.bytes()); + const expected = [ + "POST /blah HTTP/1.1\r\n", + "hello: World\r\n", + "foo: Bar\r\n", + `host: ${addr}\r\n`, + `content-length: ${body.length}\r\n\r\n`, + body + ].join(""); + assertEquals(actual, expected); +}); + +unitTest({ perms: { net: true} }, async function fetchPostBodyTypedArray():Promise<void> { + const addr = "127.0.0.1:4503"; + const buf = bufferServer(addr); + const bodyStr = "hello world"; + const body = new TextEncoder().encode(bodyStr); + const response = await fetch(`http://${addr}/blah`, { + method: "POST", + headers: [["Hello", "World"], ["Foo", "Bar"]], + body + }); + assertEquals(response.status, 404); + assertEquals(response.headers.get("Content-Length"), "2"); + + const actual = new TextDecoder().decode(buf.bytes()); + const expected = [ + "POST /blah HTTP/1.1\r\n", + "hello: World\r\n", + "foo: Bar\r\n", + `host: ${addr}\r\n`, + `content-length: ${body.byteLength}\r\n\r\n`, + bodyStr + ].join(""); + assertEquals(actual, expected); +}); +*/ + +unitTest( + { + // TODO: leaking resources + skip: true, + perms: { net: true } + }, + async function fetchWithManualRedirection(): Promise<void> { + const response = await fetch("http://localhost:4546/", { + redirect: "manual" + }); // will redirect to http://localhost:4545/ + assertEquals(response.status, 0); + assertEquals(response.statusText, ""); + assertEquals(response.url, ""); + assertEquals(response.type, "opaqueredirect"); + try { + await response.text(); + fail( + "Reponse.text() didn't throw on a filtered response without a body (type opaqueredirect)" + ); + } catch (e) { + return; + } + } +); + +unitTest( + { + // TODO: leaking resources + skip: true, + perms: { net: true } + }, + async function fetchWithErrorRedirection(): Promise<void> { + const response = await fetch("http://localhost:4546/", { + redirect: "error" + }); // will redirect to http://localhost:4545/ + assertEquals(response.status, 0); + assertEquals(response.statusText, ""); + assertEquals(response.url, ""); + assertEquals(response.type, "error"); + try { + await response.text(); + fail( + "Reponse.text() didn't throw on a filtered response without a body (type error)" + ); + } catch (e) { + return; + } + } +); + +unitTest(function responseRedirect(): void { + const response = new Response( + "example.com/beforeredirect", + 200, + "OK", + [["This-Should", "Disappear"]], + -1, + false, + null + ); + const redir = response.redirect("example.com/newLocation", 301); + assertEquals(redir.status, 301); + assertEquals(redir.statusText, ""); + assertEquals(redir.url, ""); + assertEquals(redir.headers.get("Location"), "example.com/newLocation"); + assertEquals(redir.type, "default"); +}); + +unitTest(function responseConstructionHeaderRemoval(): void { + const res = new Response( + "example.com", + 200, + "OK", + [["Set-Cookie", "mysessionid"]], + -1, + false, + "basic", + null + ); + assert(res.headers.get("Set-Cookie") != "mysessionid"); +}); diff --git a/cli/js/tests/file_test.ts b/cli/js/tests/file_test.ts new file mode 100644 index 000000000..1a7a5f88b --- /dev/null +++ b/cli/js/tests/file_test.ts @@ -0,0 +1,105 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function testFirstArgument(arg1: any[], expectedSize: number): void { + const file = new File(arg1, "name"); + assert(file instanceof File); + assertEquals(file.name, "name"); + assertEquals(file.size, expectedSize); + assertEquals(file.type, ""); +} + +unitTest(function fileEmptyFileBits(): void { + testFirstArgument([], 0); +}); + +unitTest(function fileStringFileBits(): void { + testFirstArgument(["bits"], 4); +}); + +unitTest(function fileUnicodeStringFileBits(): void { + testFirstArgument(["𝓽𝓮𝔁𝓽"], 16); +}); + +unitTest(function fileStringObjectFileBits(): void { + testFirstArgument([new String("string object")], 13); +}); + +unitTest(function fileEmptyBlobFileBits(): void { + testFirstArgument([new Blob()], 0); +}); + +unitTest(function fileBlobFileBits(): void { + testFirstArgument([new Blob(["bits"])], 4); +}); + +unitTest(function fileEmptyFileFileBits(): void { + testFirstArgument([new File([], "world.txt")], 0); +}); + +unitTest(function fileFileFileBits(): void { + testFirstArgument([new File(["bits"], "world.txt")], 4); +}); + +unitTest(function fileArrayBufferFileBits(): void { + testFirstArgument([new ArrayBuffer(8)], 8); +}); + +unitTest(function fileTypedArrayFileBits(): void { + testFirstArgument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4); +}); + +unitTest(function fileVariousFileBits(): void { + testFirstArgument( + [ + "bits", + new Blob(["bits"]), + new Blob(), + new Uint8Array([0x50, 0x41]), + new Uint16Array([0x5353]), + new Uint32Array([0x53534150]) + ], + 16 + ); +}); + +unitTest(function fileNumberInFileBits(): void { + testFirstArgument([12], 2); +}); + +unitTest(function fileArrayInFileBits(): void { + testFirstArgument([[1, 2, 3]], 5); +}); + +unitTest(function fileObjectInFileBits(): void { + // "[object Object]" + testFirstArgument([{}], 15); +}); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function testSecondArgument(arg2: any, expectedFileName: string): void { + const file = new File(["bits"], arg2); + assert(file instanceof File); + assertEquals(file.name, expectedFileName); +} + +unitTest(function fileUsingFileName(): void { + testSecondArgument("dummy", "dummy"); +}); + +unitTest(function fileUsingSpecialCharacterInFileName(): void { + testSecondArgument("dummy/foo", "dummy:foo"); +}); + +unitTest(function fileUsingNullFileName(): void { + testSecondArgument(null, "null"); +}); + +unitTest(function fileUsingNumberFileName(): void { + testSecondArgument(1, "1"); +}); + +unitTest(function fileUsingEmptyStringFileName(): void { + testSecondArgument("", ""); +}); diff --git a/cli/js/tests/files_test.ts b/cli/js/tests/files_test.ts new file mode 100644 index 000000000..49fecabe0 --- /dev/null +++ b/cli/js/tests/files_test.ts @@ -0,0 +1,446 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + unitTest, + assert, + assertEquals, + assertStrContains +} from "./test_util.ts"; + +unitTest(function filesStdioFileDescriptors(): void { + assertEquals(Deno.stdin.rid, 0); + assertEquals(Deno.stdout.rid, 1); + assertEquals(Deno.stderr.rid, 2); +}); + +unitTest({ perms: { read: true } }, async function filesCopyToStdout(): Promise< + void +> { + const filename = "cli/tests/fixture.json"; + const file = await Deno.open(filename); + assert(file.rid > 2); + const bytesWritten = await Deno.copy(Deno.stdout, file); + const fileSize = Deno.statSync(filename).len; + assertEquals(bytesWritten, fileSize); + console.log("bytes written", bytesWritten); + file.close(); +}); + +unitTest( + { perms: { read: true } }, + async function filesToAsyncIterator(): Promise<void> { + const filename = "cli/tests/hello.txt"; + const file = await Deno.open(filename); + + let totalSize = 0; + for await (const buf of Deno.toAsyncIterator(file)) { + totalSize += buf.byteLength; + } + + assertEquals(totalSize, 12); + file.close(); + } +); + +unitTest(async function readerToAsyncIterator(): Promise<void> { + // ref: https://github.com/denoland/deno/issues/2330 + const encoder = new TextEncoder(); + + class TestReader implements Deno.Reader { + private offset = 0; + private buf = new Uint8Array(encoder.encode(this.s)); + + constructor(private readonly s: string) {} + + async read(p: Uint8Array): Promise<number | Deno.EOF> { + const n = Math.min(p.byteLength, this.buf.byteLength - this.offset); + p.set(this.buf.slice(this.offset, this.offset + n)); + this.offset += n; + + if (n === 0) { + return Deno.EOF; + } + + return n; + } + } + + const reader = new TestReader("hello world!"); + + let totalSize = 0; + for await (const buf of Deno.toAsyncIterator(reader)) { + totalSize += buf.byteLength; + } + + assertEquals(totalSize, 12); +}); + +unitTest( + { perms: { write: false } }, + async function writePermFailure(): Promise<void> { + const filename = "tests/hello.txt"; + const writeModes: Deno.OpenMode[] = ["w", "a", "x"]; + for (const mode of writeModes) { + let err; + try { + await Deno.open(filename, mode); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + } +); + +unitTest(async function openOptions(): Promise<void> { + const filename = "cli/tests/fixture.json"; + let err; + try { + await Deno.open(filename, { write: false }); + } catch (e) { + err = e; + } + assert(!!err); + assertStrContains( + err.message, + "OpenOptions requires at least one option to be true" + ); + + try { + await Deno.open(filename, { truncate: true, write: false }); + } catch (e) { + err = e; + } + assert(!!err); + assertStrContains(err.message, "'truncate' option requires 'write' option"); + + try { + await Deno.open(filename, { create: true, write: false }); + } catch (e) { + err = e; + } + assert(!!err); + assertStrContains( + err.message, + "'create' or 'createNew' options require 'write' or 'append' option" + ); + + try { + await Deno.open(filename, { createNew: true, append: false }); + } catch (e) { + err = e; + } + assert(!!err); + assertStrContains( + err.message, + "'create' or 'createNew' options require 'write' or 'append' option" + ); +}); + +unitTest({ perms: { read: false } }, async function readPermFailure(): Promise< + void +> { + let caughtError = false; + try { + await Deno.open("package.json", "r"); + await Deno.open("cli/tests/fixture.json", "r"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest( + { perms: { write: true } }, + async function writeNullBufferFailure(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "hello.txt"; + const w = { + write: true, + truncate: true, + create: true + }; + const file = await Deno.open(filename, w); + + // writing null should throw an error + let err; + try { + // @ts-ignore + await file.write(null); + } catch (e) { + err = e; + } + // TODO: Check error kind when dispatch_minimal pipes errors properly + assert(!!err); + + file.close(); + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function readNullBufferFailure(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "hello.txt"; + const file = await Deno.open(filename, "w+"); + + // reading into an empty buffer should return 0 immediately + const bytesRead = await file.read(new Uint8Array(0)); + assert(bytesRead === 0); + + // reading file into null buffer should throw an error + let err; + try { + // @ts-ignore + await file.read(null); + } catch (e) { + err = e; + } + // TODO: Check error kind when dispatch_minimal pipes errors properly + assert(!!err); + + file.close(); + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { write: false, read: false } }, + async function readWritePermFailure(): Promise<void> { + const filename = "tests/hello.txt"; + const writeModes: Deno.OpenMode[] = ["r+", "w+", "a+", "x+"]; + for (const mode of writeModes) { + let err; + try { + await Deno.open(filename, mode); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function createFile(): Promise<void> { + const tempDir = await Deno.makeTempDir(); + const filename = tempDir + "/test.txt"; + const f = await Deno.create(filename); + let fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); + assert(fileInfo.len === 0); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + await f.write(data); + fileInfo = Deno.statSync(filename); + assert(fileInfo.len === 5); + f.close(); + + // TODO: test different modes + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function openModeWrite(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const encoder = new TextEncoder(); + const filename = tempDir + "hello.txt"; + const data = encoder.encode("Hello world!\n"); + let file = await Deno.open(filename, "w"); + // assert file was created + let fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); + assertEquals(fileInfo.len, 0); + // write some data + await file.write(data); + fileInfo = Deno.statSync(filename); + assertEquals(fileInfo.len, 13); + // assert we can't read from file + let thrown = false; + try { + const buf = new Uint8Array(20); + await file.read(buf); + } catch (e) { + thrown = true; + } finally { + assert(thrown, "'w' mode shouldn't allow to read file"); + } + file.close(); + // assert that existing file is truncated on open + file = await Deno.open(filename, "w"); + file.close(); + const fileSize = Deno.statSync(filename).len; + assertEquals(fileSize, 0); + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function openModeWriteRead(): Promise<void> { + const tempDir = Deno.makeTempDirSync(); + const encoder = new TextEncoder(); + const filename = tempDir + "hello.txt"; + const data = encoder.encode("Hello world!\n"); + + const file = await Deno.open(filename, "w+"); + const seekPosition = 0; + // assert file was created + let fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); + assertEquals(fileInfo.len, 0); + // write some data + await file.write(data); + fileInfo = Deno.statSync(filename); + assertEquals(fileInfo.len, 13); + + const buf = new Uint8Array(20); + // seeking from beginning of a file + const cursorPosition = await file.seek( + seekPosition, + Deno.SeekMode.SEEK_START + ); + assertEquals(seekPosition, cursorPosition); + const result = await file.read(buf); + assertEquals(result, 13); + file.close(); + + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest({ perms: { read: true } }, async function seekStart(): Promise<void> { + const filename = "cli/tests/hello.txt"; + const file = await Deno.open(filename); + const seekPosition = 6; + // Deliberately move 1 step forward + await file.read(new Uint8Array(1)); // "H" + // Skipping "Hello " + // seeking from beginning of a file plus seekPosition + const cursorPosition = await file.seek( + seekPosition, + Deno.SeekMode.SEEK_START + ); + assertEquals(seekPosition, cursorPosition); + const buf = new Uint8Array(6); + await file.read(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, function seekSyncStart(): void { + const filename = "cli/tests/hello.txt"; + const file = Deno.openSync(filename); + const seekPosition = 6; + // Deliberately move 1 step forward + file.readSync(new Uint8Array(1)); // "H" + // Skipping "Hello " + // seeking from beginning of a file plus seekPosition + const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.SEEK_START); + assertEquals(seekPosition, cursorPosition); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, async function seekCurrent(): Promise< + void +> { + const filename = "cli/tests/hello.txt"; + const file = await Deno.open(filename); + // Deliberately move 1 step forward + await file.read(new Uint8Array(1)); // "H" + // Skipping "ello " + const seekPosition = 5; + // seekPosition is relative to current cursor position after read + const cursorPosition = await file.seek( + seekPosition, + Deno.SeekMode.SEEK_CURRENT + ); + assertEquals(seekPosition + 1, cursorPosition); + const buf = new Uint8Array(6); + await file.read(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, function seekSyncCurrent(): void { + const filename = "cli/tests/hello.txt"; + const file = Deno.openSync(filename); + // Deliberately move 1 step forward + file.readSync(new Uint8Array(1)); // "H" + // Skipping "ello " + const seekPosition = 5; + // seekPosition is relative to current cursor position after read + const cursorPosition = file.seekSync( + seekPosition, + Deno.SeekMode.SEEK_CURRENT + ); + assertEquals(seekPosition + 1, cursorPosition); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, async function seekEnd(): Promise<void> { + const filename = "cli/tests/hello.txt"; + const file = await Deno.open(filename); + const seekPosition = -6; + // seek from end of file that has 12 chars, 12 - 6 = 6 + const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.SEEK_END); + assertEquals(6, cursorPosition); + const buf = new Uint8Array(6); + await file.read(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, function seekSyncEnd(): void { + const filename = "cli/tests/hello.txt"; + const file = Deno.openSync(filename); + const seekPosition = -6; + // seek from end of file that has 12 chars, 12 - 6 = 6 + const cursorPosition = file.seekSync(seekPosition, Deno.SeekMode.SEEK_END); + assertEquals(6, cursorPosition); + const buf = new Uint8Array(6); + file.readSync(buf); + const decoded = new TextDecoder().decode(buf); + assertEquals(decoded, "world!"); + file.close(); +}); + +unitTest({ perms: { read: true } }, async function seekMode(): Promise<void> { + const filename = "cli/tests/hello.txt"; + const file = await Deno.open(filename); + let err; + try { + await file.seek(1, -1); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof TypeError); + assertStrContains(err.message, "Invalid seek mode"); + + // We should still be able to read the file + // since it is still open. + const buf = new Uint8Array(1); + await file.read(buf); // "H" + assertEquals(new TextDecoder().decode(buf), "H"); + file.close(); +}); diff --git a/cli/js/tests/form_data_test.ts b/cli/js/tests/form_data_test.ts new file mode 100644 index 000000000..9b218547c --- /dev/null +++ b/cli/js/tests/form_data_test.ts @@ -0,0 +1,189 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function formDataHasCorrectNameProp(): void { + assertEquals(FormData.name, "FormData"); +}); + +unitTest(function formDataParamsAppendSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + assertEquals(formData.get("a"), "true"); +}); + +unitTest(function formDataParamsDeleteSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + formData.append("b", "false"); + assertEquals(formData.get("b"), "false"); + formData.delete("b"); + assertEquals(formData.get("a"), "true"); + assertEquals(formData.get("b"), null); +}); + +unitTest(function formDataParamsGetAllSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + formData.append("b", "false"); + formData.append("a", "null"); + assertEquals(formData.getAll("a"), ["true", "null"]); + assertEquals(formData.getAll("b"), ["false"]); + assertEquals(formData.getAll("c"), []); +}); + +unitTest(function formDataParamsGetSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + formData.append("b", "false"); + formData.append("a", "null"); + // @ts-ignore + formData.append("d", undefined); + // @ts-ignore + formData.append("e", null); + assertEquals(formData.get("a"), "true"); + assertEquals(formData.get("b"), "false"); + assertEquals(formData.get("c"), null); + assertEquals(formData.get("d"), "undefined"); + assertEquals(formData.get("e"), "null"); +}); + +unitTest(function formDataParamsHasSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + formData.append("b", "false"); + assert(formData.has("a")); + assert(formData.has("b")); + assert(!formData.has("c")); +}); + +unitTest(function formDataParamsSetSuccess(): void { + const formData = new FormData(); + formData.append("a", "true"); + formData.append("b", "false"); + formData.append("a", "null"); + assertEquals(formData.getAll("a"), ["true", "null"]); + assertEquals(formData.getAll("b"), ["false"]); + formData.set("a", "false"); + assertEquals(formData.getAll("a"), ["false"]); + // @ts-ignore + formData.set("d", undefined); + assertEquals(formData.get("d"), "undefined"); + // @ts-ignore + formData.set("e", null); + assertEquals(formData.get("e"), "null"); +}); + +unitTest(function formDataSetEmptyBlobSuccess(): void { + const formData = new FormData(); + formData.set("a", new Blob([]), "blank.txt"); + formData.get("a"); + /* TODO Fix this test. + assert(file instanceof File); + if (typeof file !== "string") { + assertEquals(file.name, "blank.txt"); + } + */ +}); + +unitTest(function formDataParamsForEachSuccess(): void { + const init = [ + ["a", "54"], + ["b", "true"] + ]; + const formData = new FormData(); + for (const [name, value] of init) { + formData.append(name, value); + } + let callNum = 0; + formData.forEach((value, key, parent): void => { + assertEquals(formData, parent); + assertEquals(value, init[callNum][1]); + assertEquals(key, init[callNum][0]); + callNum++; + }); + assertEquals(callNum, init.length); +}); + +unitTest(function formDataParamsArgumentsCheck(): void { + const methodRequireOneParam = [ + "delete", + "getAll", + "get", + "has", + "forEach" + ] as const; + + const methodRequireTwoParams = ["append", "set"] as const; + + methodRequireOneParam.forEach((method): void => { + const formData = new FormData(); + let hasThrown = 0; + let errMsg = ""; + try { + // @ts-ignore + formData[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `FormData.${method} requires at least 1 argument, but only 0 present` + ); + }); + + methodRequireTwoParams.forEach((method: string): void => { + const formData = new FormData(); + let hasThrown = 0; + let errMsg = ""; + + try { + // @ts-ignore + formData[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `FormData.${method} requires at least 2 arguments, but only 0 present` + ); + + hasThrown = 0; + errMsg = ""; + try { + // @ts-ignore + formData[method]("foo"); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `FormData.${method} requires at least 2 arguments, but only 1 present` + ); + }); +}); + +unitTest(function toStringShouldBeWebCompatibility(): void { + const formData = new FormData(); + assertEquals(formData.toString(), "[object FormData]"); +}); diff --git a/cli/js/tests/format_error_test.ts b/cli/js/tests/format_error_test.ts new file mode 100644 index 000000000..9831ef160 --- /dev/null +++ b/cli/js/tests/format_error_test.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { assert, unitTest } from "./test_util.ts"; + +unitTest(function formatDiagnosticBasic() { + const fixture: Deno.DiagnosticItem[] = [ + { + message: "Example error", + category: Deno.DiagnosticCategory.Error, + sourceLine: "abcdefghijklmnopqrstuv", + lineNumber: 1000, + scriptResourceName: "foo.ts", + startColumn: 1, + endColumn: 2, + code: 4000 + } + ]; + const out = Deno.formatDiagnostics(fixture); + assert(out.includes("Example error")); + assert(out.includes("foo.ts")); +}); + +unitTest(function formatDiagnosticError() { + let thrown = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const bad = ([{ hello: 123 }] as any) as Deno.DiagnosticItem[]; + try { + Deno.formatDiagnostics(bad); + } catch (e) { + assert(e instanceof TypeError); + thrown = true; + } + assert(thrown); +}); + +if (import.meta.main) { + Deno.runTests(); +} diff --git a/cli/js/tests/fs_events_test.ts b/cli/js/tests/fs_events_test.ts new file mode 100644 index 000000000..b1697971a --- /dev/null +++ b/cli/js/tests/fs_events_test.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +// TODO(ry) Add more tests to specify format. + +unitTest({ perms: { read: false } }, function fsEventsPermissions() { + let thrown = false; + try { + Deno.fsEvents("."); + } catch (err) { + assert(err instanceof Deno.errors.PermissionDenied); + thrown = true; + } + assert(thrown); +}); + +async function getTwoEvents( + iter: AsyncIterableIterator<Deno.FsEvent> +): Promise<Deno.FsEvent[]> { + const events = []; + for await (const event of iter) { + events.push(event); + if (events.length > 2) break; + } + return events; +} + +unitTest( + { perms: { read: true, write: true } }, + async function fsEventsBasic(): Promise<void> { + const testDir = await Deno.makeTempDir(); + const iter = Deno.fsEvents(testDir); + + // Asynchornously capture two fs events. + const eventsPromise = getTwoEvents(iter); + + // Make some random file system activity. + const file1 = testDir + "/file1.txt"; + const file2 = testDir + "/file2.txt"; + Deno.writeFileSync(file1, new Uint8Array([0, 1, 2])); + Deno.writeFileSync(file2, new Uint8Array([0, 1, 2])); + + // We should have gotten two fs events. + const events = await eventsPromise; + assert(events.length >= 2); + assert(events[0].kind == "create"); + assert(events[0].paths[0].includes(testDir)); + assert(events[1].kind == "create" || events[1].kind == "modify"); + assert(events[1].paths[0].includes(testDir)); + } +); diff --git a/cli/js/tests/get_random_values_test.ts b/cli/js/tests/get_random_values_test.ts new file mode 100644 index 000000000..76fa732ea --- /dev/null +++ b/cli/js/tests/get_random_values_test.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertNotEquals, assertStrictEq } from "./test_util.ts"; + +unitTest(function getRandomValuesInt8Array(): void { + const arr = new Int8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int8Array(32)); +}); + +unitTest(function getRandomValuesUint8Array(): void { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8Array(32)); +}); + +unitTest(function getRandomValuesUint8ClampedArray(): void { + const arr = new Uint8ClampedArray(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8ClampedArray(32)); +}); + +unitTest(function getRandomValuesInt16Array(): void { + const arr = new Int16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int16Array(4)); +}); + +unitTest(function getRandomValuesUint16Array(): void { + const arr = new Uint16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint16Array(4)); +}); + +unitTest(function getRandomValuesInt32Array(): void { + const arr = new Int32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int32Array(8)); +}); + +unitTest(function getRandomValuesUint32Array(): void { + const arr = new Uint32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); +}); + +unitTest(function getRandomValuesReturnValue(): void { + const arr = new Uint32Array(8); + const rtn = crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); + assertStrictEq(rtn, arr); +}); diff --git a/cli/js/tests/globals_test.ts b/cli/js/tests/globals_test.ts new file mode 100644 index 000000000..aa8b4f46e --- /dev/null +++ b/cli/js/tests/globals_test.ts @@ -0,0 +1,112 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function globalThisExists(): void { + assert(globalThis != null); +}); + +unitTest(function windowExists(): void { + assert(window != null); +}); + +unitTest(function selfExists(): void { + assert(self != null); +}); + +unitTest(function windowWindowExists(): void { + assert(window.window === window); +}); + +unitTest(function windowSelfExists(): void { + assert(window.self === window); +}); + +unitTest(function globalThisEqualsWindow(): void { + assert(globalThis === window); +}); + +unitTest(function globalThisEqualsSelf(): void { + assert(globalThis === self); +}); + +unitTest(function DenoNamespaceExists(): void { + assert(Deno != null); +}); + +unitTest(function DenoNamespaceEqualsWindowDeno(): void { + assert(Deno === window.Deno); +}); + +unitTest(function DenoNamespaceIsFrozen(): void { + assert(Object.isFrozen(Deno)); +}); + +unitTest(function webAssemblyExists(): void { + assert(typeof WebAssembly.compile === "function"); +}); + +unitTest(function DenoNamespaceImmutable(): void { + const denoCopy = window.Deno; + try { + // @ts-ignore + Deno = 1; + } catch {} + assert(denoCopy === Deno); + try { + // @ts-ignore + window.Deno = 1; + } catch {} + assert(denoCopy === Deno); + try { + delete window.Deno; + } catch {} + assert(denoCopy === Deno); + + const { readFile } = Deno; + try { + // @ts-ignore + Deno.readFile = 1; + } catch {} + assert(readFile === Deno.readFile); + try { + delete window.Deno.readFile; + } catch {} + assert(readFile === Deno.readFile); + + // @ts-ignore + const { print } = Deno.core; + try { + // @ts-ignore + Deno.core.print = 1; + } catch {} + // @ts-ignore + assert(print === Deno.core.print); + try { + // @ts-ignore + delete Deno.core.print; + } catch {} + // @ts-ignore + assert(print === Deno.core.print); +}); + +unitTest(async function windowQueueMicrotask(): Promise<void> { + let resolve1: () => void | undefined; + let resolve2: () => void | undefined; + let microtaskDone = false; + const p1 = new Promise((res): void => { + resolve1 = (): void => { + microtaskDone = true; + res(); + }; + }); + const p2 = new Promise((res): void => { + resolve2 = (): void => { + assert(microtaskDone); + res(); + }; + }); + window.queueMicrotask(resolve1!); + setTimeout(resolve2!, 0); + await p1; + await p2; +}); diff --git a/cli/js/tests/headers_test.ts b/cli/js/tests/headers_test.ts new file mode 100644 index 000000000..fcb5385a5 --- /dev/null +++ b/cli/js/tests/headers_test.ts @@ -0,0 +1,355 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; +const { + stringifyArgs + // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol +} = Deno[Deno.symbols.internal]; + +// Logic heavily copied from web-platform-tests, make +// sure pass mostly header basic test +// ref: https://github.com/web-platform-tests/wpt/blob/7c50c216081d6ea3c9afe553ee7b64534020a1b2/fetch/api/headers/headers-basic.html +unitTest(function newHeaderTest(): void { + new Headers(); + new Headers(undefined); + new Headers({}); + try { + // @ts-ignore + new Headers(null); + } catch (e) { + assertEquals( + e.message, + "Failed to construct 'Headers'; The provided value was not valid" + ); + } +}); + +const headerDict: Record<string, string> = { + name1: "value1", + name2: "value2", + name3: "value3", + // @ts-ignore + name4: undefined, + "Content-Type": "value4" +}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const headerSeq: any[] = []; +for (const name in headerDict) { + headerSeq.push([name, headerDict[name]]); +} + +unitTest(function newHeaderWithSequence(): void { + const headers = new Headers(headerSeq); + for (const name in headerDict) { + assertEquals(headers.get(name), String(headerDict[name])); + } + assertEquals(headers.get("length"), null); +}); + +unitTest(function newHeaderWithRecord(): void { + const headers = new Headers(headerDict); + for (const name in headerDict) { + assertEquals(headers.get(name), String(headerDict[name])); + } +}); + +unitTest(function newHeaderWithHeadersInstance(): void { + const headers = new Headers(headerDict); + const headers2 = new Headers(headers); + for (const name in headerDict) { + assertEquals(headers2.get(name), String(headerDict[name])); + } +}); + +unitTest(function headerAppendSuccess(): void { + const headers = new Headers(); + for (const name in headerDict) { + headers.append(name, headerDict[name]); + assertEquals(headers.get(name), String(headerDict[name])); + } +}); + +unitTest(function headerSetSuccess(): void { + const headers = new Headers(); + for (const name in headerDict) { + headers.set(name, headerDict[name]); + assertEquals(headers.get(name), String(headerDict[name])); + } +}); + +unitTest(function headerHasSuccess(): void { + const headers = new Headers(headerDict); + for (const name in headerDict) { + assert(headers.has(name), "headers has name " + name); + assert( + !headers.has("nameNotInHeaders"), + "headers do not have header: nameNotInHeaders" + ); + } +}); + +unitTest(function headerDeleteSuccess(): void { + const headers = new Headers(headerDict); + for (const name in headerDict) { + assert(headers.has(name), "headers have a header: " + name); + headers.delete(name); + assert(!headers.has(name), "headers do not have anymore a header: " + name); + } +}); + +unitTest(function headerGetSuccess(): void { + const headers = new Headers(headerDict); + for (const name in headerDict) { + assertEquals(headers.get(name), String(headerDict[name])); + assertEquals(headers.get("nameNotInHeaders"), null); + } +}); + +unitTest(function headerEntriesSuccess(): void { + const headers = new Headers(headerDict); + const iterators = headers.entries(); + for (const it of iterators) { + const key = it[0]; + const value = it[1]; + assert(headers.has(key)); + assertEquals(value, headers.get(key)); + } +}); + +unitTest(function headerKeysSuccess(): void { + const headers = new Headers(headerDict); + const iterators = headers.keys(); + for (const it of iterators) { + assert(headers.has(it)); + } +}); + +unitTest(function headerValuesSuccess(): void { + const headers = new Headers(headerDict); + const iterators = headers.values(); + const entries = headers.entries(); + const values = []; + for (const pair of entries) { + values.push(pair[1]); + } + for (const it of iterators) { + assert(values.includes(it)); + } +}); + +const headerEntriesDict: Record<string, string> = { + name1: "value1", + Name2: "value2", + name: "value3", + "content-Type": "value4", + "Content-Typ": "value5", + "Content-Types": "value6" +}; + +unitTest(function headerForEachSuccess(): void { + const headers = new Headers(headerEntriesDict); + const keys = Object.keys(headerEntriesDict); + keys.forEach((key): void => { + const value = headerEntriesDict[key]; + const newkey = key.toLowerCase(); + headerEntriesDict[newkey] = value; + }); + let callNum = 0; + headers.forEach((value, key, container): void => { + assertEquals(headers, container); + assertEquals(value, headerEntriesDict[key]); + callNum++; + }); + assertEquals(callNum, keys.length); +}); + +unitTest(function headerSymbolIteratorSuccess(): void { + assert(Symbol.iterator in Headers.prototype); + const headers = new Headers(headerEntriesDict); + for (const header of headers) { + const key = header[0]; + const value = header[1]; + assert(headers.has(key)); + assertEquals(value, headers.get(key)); + } +}); + +unitTest(function headerTypesAvailable(): void { + function newHeaders(): Headers { + return new Headers(); + } + const headers = newHeaders(); + assert(headers instanceof Headers); +}); + +// Modified from https://github.com/bitinn/node-fetch/blob/7d3293200a91ad52b5ca7962f9d6fd1c04983edb/test/test.js#L2001-L2014 +// Copyright (c) 2016 David Frank. MIT License. +unitTest(function headerIllegalReject(): void { + let errorCount = 0; + try { + new Headers({ "He y": "ok" }); + } catch (e) { + errorCount++; + } + try { + new Headers({ "Hé-y": "ok" }); + } catch (e) { + errorCount++; + } + try { + new Headers({ "He-y": "ăk" }); + } catch (e) { + errorCount++; + } + const headers = new Headers(); + try { + headers.append("Hé-y", "ok"); + } catch (e) { + errorCount++; + } + try { + headers.delete("Hé-y"); + } catch (e) { + errorCount++; + } + try { + headers.get("Hé-y"); + } catch (e) { + errorCount++; + } + try { + headers.has("Hé-y"); + } catch (e) { + errorCount++; + } + try { + headers.set("Hé-y", "ok"); + } catch (e) { + errorCount++; + } + try { + headers.set("", "ok"); + } catch (e) { + errorCount++; + } + assertEquals(errorCount, 9); + // 'o k' is valid value but invalid name + new Headers({ "He-y": "o k" }); +}); + +// If pair does not contain exactly two items,then throw a TypeError. +unitTest(function headerParamsShouldThrowTypeError(): void { + let hasThrown = 0; + + try { + new Headers(([["1"]] as unknown) as Array<[string, string]>); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + + assertEquals(hasThrown, 2); +}); + +unitTest(function headerParamsArgumentsCheck(): void { + const methodRequireOneParam = ["delete", "get", "has", "forEach"]; + + const methodRequireTwoParams = ["append", "set"]; + + methodRequireOneParam.forEach((method): void => { + const headers = new Headers(); + let hasThrown = 0; + let errMsg = ""; + try { + // @ts-ignore + headers[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `Headers.${method} requires at least 1 argument, but only 0 present` + ); + }); + + methodRequireTwoParams.forEach((method): void => { + const headers = new Headers(); + let hasThrown = 0; + let errMsg = ""; + + try { + // @ts-ignore + headers[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `Headers.${method} requires at least 2 arguments, but only 0 present` + ); + + hasThrown = 0; + errMsg = ""; + try { + // @ts-ignore + headers[method]("foo"); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + assertEquals( + errMsg, + `Headers.${method} requires at least 2 arguments, but only 1 present` + ); + }); +}); + +unitTest(function toStringShouldBeWebCompatibility(): void { + const headers = new Headers(); + assertEquals(headers.toString(), "[object Headers]"); +}); + +function stringify(...args: unknown[]): string { + return stringifyArgs(args).replace(/\n$/, ""); +} + +unitTest(function customInspectReturnsCorrectHeadersFormat(): void { + const blankHeaders = new Headers(); + assertEquals(stringify(blankHeaders), "Headers {}"); + const singleHeader = new Headers([["Content-Type", "application/json"]]); + assertEquals( + stringify(singleHeader), + "Headers { content-type: application/json }" + ); + const multiParamHeader = new Headers([ + ["Content-Type", "application/json"], + ["Content-Length", "1337"] + ]); + assertEquals( + stringify(multiParamHeader), + "Headers { content-type: application/json, content-length: 1337 }" + ); +}); diff --git a/cli/js/tests/internals_test.ts b/cli/js/tests/internals_test.ts new file mode 100644 index 000000000..fb712707c --- /dev/null +++ b/cli/js/tests/internals_test.ts @@ -0,0 +1,10 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function internalsExists(): void { + const { + stringifyArgs + // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol + } = Deno[Deno.symbols.internal]; + assert(!!stringifyArgs); +}); diff --git a/cli/js/tests/link_test.ts b/cli/js/tests/link_test.ts new file mode 100644 index 000000000..e9f72ebef --- /dev/null +++ b/cli/js/tests/link_test.ts @@ -0,0 +1,147 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function linkSyncSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const oldData = "Hardlink"; + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + Deno.writeFileSync(oldName, new TextEncoder().encode(oldData)); + // Create the hard link. + Deno.linkSync(oldName, newName); + // We should expect reading the same content. + const newData = new TextDecoder().decode(Deno.readFileSync(newName)); + assertEquals(oldData, newData); + // Writing to newname also affects oldname. + const newData2 = "Modified"; + Deno.writeFileSync(newName, new TextEncoder().encode(newData2)); + assertEquals( + newData2, + new TextDecoder().decode(Deno.readFileSync(oldName)) + ); + // Writing to oldname also affects newname. + const newData3 = "ModifiedAgain"; + Deno.writeFileSync(oldName, new TextEncoder().encode(newData3)); + assertEquals( + newData3, + new TextDecoder().decode(Deno.readFileSync(newName)) + ); + // Remove oldname. File still accessible through newname. + Deno.removeSync(oldName); + const newNameStat = Deno.statSync(newName); + assert(newNameStat.isFile()); + assert(!newNameStat.isSymlink()); // Not a symlink. + assertEquals( + newData3, + new TextDecoder().decode(Deno.readFileSync(newName)) + ); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function linkSyncExists(): void { + const testDir = Deno.makeTempDirSync(); + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + Deno.writeFileSync(oldName, new TextEncoder().encode("oldName")); + // newname is already created. + Deno.writeFileSync(newName, new TextEncoder().encode("newName")); + + let err; + try { + Deno.linkSync(oldName, newName); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.AlreadyExists); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function linkSyncNotFound(): void { + const testDir = Deno.makeTempDirSync(); + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + + let err; + try { + Deno.linkSync(oldName, newName); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { read: false, write: true } }, + function linkSyncReadPerm(): void { + let err; + try { + Deno.linkSync("oldbaddir", "newbaddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } +); + +unitTest( + { perms: { read: true, write: false } }, + function linkSyncWritePerm(): void { + let err; + try { + Deno.linkSync("oldbaddir", "newbaddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function linkSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const oldData = "Hardlink"; + const oldName = testDir + "/oldname"; + const newName = testDir + "/newname"; + Deno.writeFileSync(oldName, new TextEncoder().encode(oldData)); + // Create the hard link. + await Deno.link(oldName, newName); + // We should expect reading the same content. + const newData = new TextDecoder().decode(Deno.readFileSync(newName)); + assertEquals(oldData, newData); + // Writing to newname also affects oldname. + const newData2 = "Modified"; + Deno.writeFileSync(newName, new TextEncoder().encode(newData2)); + assertEquals( + newData2, + new TextDecoder().decode(Deno.readFileSync(oldName)) + ); + // Writing to oldname also affects newname. + const newData3 = "ModifiedAgain"; + Deno.writeFileSync(oldName, new TextEncoder().encode(newData3)); + assertEquals( + newData3, + new TextDecoder().decode(Deno.readFileSync(newName)) + ); + // Remove oldname. File still accessible through newname. + Deno.removeSync(oldName); + const newNameStat = Deno.statSync(newName); + assert(newNameStat.isFile()); + assert(!newNameStat.isSymlink()); // Not a symlink. + assertEquals( + newData3, + new TextDecoder().decode(Deno.readFileSync(newName)) + ); + } +); diff --git a/cli/js/tests/location_test.ts b/cli/js/tests/location_test.ts new file mode 100644 index 000000000..78ecb55b3 --- /dev/null +++ b/cli/js/tests/location_test.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function locationBasic(): void { + // location example: file:///Users/rld/src/deno/js/unit_tests.ts + assert(window.location.toString().endsWith("unit_tests.ts")); +}); diff --git a/cli/js/tests/make_temp_test.ts b/cli/js/tests/make_temp_test.ts new file mode 100644 index 000000000..9804a7043 --- /dev/null +++ b/cli/js/tests/make_temp_test.ts @@ -0,0 +1,134 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest({ perms: { write: true } }, function makeTempDirSyncSuccess(): void { + const dir1 = Deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); + const dir2 = Deno.makeTempDirSync({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(dir1 !== dir2); + for (const dir of [dir1, dir2]) { + // Check that the prefix and suffix are applied. + const lastPart = dir.replace(/^.*[\\\/]/, ""); + assert(lastPart.startsWith("hello")); + assert(lastPart.endsWith("world")); + } + // Check that the `dir` option works. + const dir3 = Deno.makeTempDirSync({ dir: dir1 }); + assert(dir3.startsWith(dir1)); + assert(/^[\\\/]/.test(dir3.slice(dir1.length))); + // Check that creating a temp dir inside a nonexisting directory fails. + let err; + try { + Deno.makeTempDirSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest(function makeTempDirSyncPerm(): void { + // makeTempDirSync should require write permissions (for now). + let err; + try { + Deno.makeTempDirSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { write: true } }, + async function makeTempDirSuccess(): Promise<void> { + const dir1 = await Deno.makeTempDir({ prefix: "hello", suffix: "world" }); + const dir2 = await Deno.makeTempDir({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(dir1 !== dir2); + for (const dir of [dir1, dir2]) { + // Check that the prefix and suffix are applied. + const lastPart = dir.replace(/^.*[\\\/]/, ""); + assert(lastPart.startsWith("hello")); + assert(lastPart.endsWith("world")); + } + // Check that the `dir` option works. + const dir3 = await Deno.makeTempDir({ dir: dir1 }); + assert(dir3.startsWith(dir1)); + assert(/^[\\\/]/.test(dir3.slice(dir1.length))); + // Check that creating a temp dir inside a nonexisting directory fails. + let err; + try { + await Deno.makeTempDir({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest({ perms: { write: true } }, function makeTempFileSyncSuccess(): void { + const file1 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); + const file2 = Deno.makeTempFileSync({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(file1 !== file2); + for (const dir of [file1, file2]) { + // Check that the prefix and suffix are applied. + const lastPart = dir.replace(/^.*[\\\/]/, ""); + assert(lastPart.startsWith("hello")); + assert(lastPart.endsWith("world")); + } + // Check that the `dir` option works. + const dir = Deno.makeTempDirSync({ prefix: "tempdir" }); + const file3 = Deno.makeTempFileSync({ dir }); + assert(file3.startsWith(dir)); + assert(/^[\\\/]/.test(file3.slice(dir.length))); + // Check that creating a temp file inside a nonexisting directory fails. + let err; + try { + Deno.makeTempFileSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest(function makeTempFileSyncPerm(): void { + // makeTempFileSync should require write permissions (for now). + let err; + try { + Deno.makeTempFileSync({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { write: true } }, + async function makeTempFileSuccess(): Promise<void> { + const file1 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); + const file2 = await Deno.makeTempFile({ prefix: "hello", suffix: "world" }); + // Check that both dirs are different. + assert(file1 !== file2); + for (const dir of [file1, file2]) { + // Check that the prefix and suffix are applied. + const lastPart = dir.replace(/^.*[\\\/]/, ""); + assert(lastPart.startsWith("hello")); + assert(lastPart.endsWith("world")); + } + // Check that the `dir` option works. + const dir = Deno.makeTempDirSync({ prefix: "tempdir" }); + const file3 = await Deno.makeTempFile({ dir }); + assert(file3.startsWith(dir)); + assert(/^[\\\/]/.test(file3.slice(dir.length))); + // Check that creating a temp file inside a nonexisting directory fails. + let err; + try { + await Deno.makeTempFile({ dir: "/baddir" }); + } catch (err_) { + err = err_; + } + assert(err instanceof Deno.errors.NotFound); + } +); diff --git a/cli/js/tests/metrics_test.ts b/cli/js/tests/metrics_test.ts new file mode 100644 index 000000000..9b7d83887 --- /dev/null +++ b/cli/js/tests/metrics_test.ts @@ -0,0 +1,58 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(async function metrics(): Promise<void> { + const m1 = Deno.metrics(); + assert(m1.opsDispatched > 0); + assert(m1.opsDispatchedSync > 0); + assert(m1.opsCompleted > 0); + assert(m1.opsCompletedSync > 0); + assert(m1.bytesSentControl > 0); + assert(m1.bytesSentData >= 0); + assert(m1.bytesReceived > 0); + + // Write to stdout to ensure a "data" message gets sent instead of just + // control messages. + const dataMsg = new Uint8Array([13, 13, 13]); // "\r\r\r", + await Deno.stdout.write(dataMsg); + + const m2 = Deno.metrics(); + assert(m2.opsDispatched > m1.opsDispatched); + assert(m2.opsDispatchedSync > m1.opsDispatchedSync); + assert(m2.opsDispatchedAsync > m1.opsDispatchedAsync); + assert(m2.opsCompleted > m1.opsCompleted); + assert(m2.opsCompletedSync > m1.opsCompletedSync); + assert(m2.opsCompletedAsync > m1.opsCompletedAsync); + assert(m2.bytesSentControl > m1.bytesSentControl); + assert(m2.bytesSentData >= m1.bytesSentData + dataMsg.byteLength); + assert(m2.bytesReceived > m1.bytesReceived); +}); + +unitTest( + { perms: { write: true } }, + function metricsUpdatedIfNoResponseSync(): void { + const filename = Deno.makeTempDirSync() + "/test.txt"; + + const data = new Uint8Array([41, 42, 43]); + Deno.writeFileSync(filename, data, { mode: 0o666 }); + + const metrics = Deno.metrics(); + assert(metrics.opsDispatched === metrics.opsCompleted); + assert(metrics.opsDispatchedSync === metrics.opsCompletedSync); + } +); + +unitTest( + { perms: { write: true } }, + async function metricsUpdatedIfNoResponseAsync(): Promise<void> { + const filename = Deno.makeTempDirSync() + "/test.txt"; + + const data = new Uint8Array([41, 42, 43]); + await Deno.writeFile(filename, data, { mode: 0o666 }); + + const metrics = Deno.metrics(); + assert(metrics.opsDispatched === metrics.opsCompleted); + assert(metrics.opsDispatchedSync === metrics.opsCompletedSync); + assert(metrics.opsDispatchedAsync === metrics.opsCompletedAsync); + } +); diff --git a/cli/js/tests/mkdir_test.ts b/cli/js/tests/mkdir_test.ts new file mode 100644 index 000000000..2921177eb --- /dev/null +++ b/cli/js/tests/mkdir_test.ts @@ -0,0 +1,76 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function mkdirSyncSuccess(): void { + const path = Deno.makeTempDirSync() + "/dir"; + Deno.mkdirSync(path); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function mkdirSyncMode(): void { + const path = Deno.makeTempDirSync() + "/dir"; + Deno.mkdirSync(path, { mode: 0o755 }); // no perm for x + const pathInfo = Deno.statSync(path); + if (pathInfo.mode !== null) { + // Skip windows + assertEquals(pathInfo.mode & 0o777, 0o755); + } + } +); + +unitTest({ perms: { write: false } }, function mkdirSyncPerm(): void { + let err; + try { + Deno.mkdirSync("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { read: true, write: true } }, + async function mkdirSuccess(): Promise<void> { + const path = Deno.makeTempDirSync() + "/dir"; + await Deno.mkdir(path); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); + } +); + +unitTest({ perms: { write: true } }, function mkdirErrIfExists(): void { + let err; + try { + Deno.mkdirSync("."); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.AlreadyExists); +}); + +unitTest( + { perms: { read: true, write: true } }, + function mkdirSyncRecursive(): void { + const path = Deno.makeTempDirSync() + "/nested/directory"; + Deno.mkdirSync(path, { recursive: true }); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function mkdirRecursive(): Promise<void> { + const path = Deno.makeTempDirSync() + "/nested/directory"; + await Deno.mkdir(path, { recursive: true }); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); + } +); diff --git a/cli/js/tests/net_test.ts b/cli/js/tests/net_test.ts new file mode 100644 index 000000000..46543acdf --- /dev/null +++ b/cli/js/tests/net_test.ts @@ -0,0 +1,307 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest({ perms: { net: true } }, function netTcpListenClose(): void { + const listener = Deno.listen({ hostname: "127.0.0.1", port: 4500 }); + assertEquals(listener.addr.transport, "tcp"); + assertEquals(listener.addr.hostname, "127.0.0.1"); + assertEquals(listener.addr.port, 4500); + listener.close(); +}); + +unitTest( + { + perms: { net: true }, + // TODO: + skip: Deno.build.os === "win" + }, + function netUdpListenClose(): void { + const socket = Deno.listen({ + hostname: "127.0.0.1", + port: 4500, + transport: "udp" + }); + assertEquals(socket.addr.transport, "udp"); + assertEquals(socket.addr.hostname, "127.0.0.1"); + assertEquals(socket.addr.port, 4500); + socket.close(); + } +); + +unitTest( + { + perms: { net: true } + }, + async function netTcpCloseWhileAccept(): Promise<void> { + const listener = Deno.listen({ port: 4501 }); + const p = listener.accept(); + listener.close(); + let err; + try { + await p; + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Error); + assertEquals(err.message, "Listener has been closed"); + } +); + +unitTest( + { perms: { net: true } }, + async function netTcpConcurrentAccept(): Promise<void> { + const listener = Deno.listen({ port: 4502 }); + let acceptErrCount = 0; + const checkErr = (e: Error): void => { + if (e.message === "Listener has been closed") { + assertEquals(acceptErrCount, 1); + } else if (e.message === "Another accept task is ongoing") { + acceptErrCount++; + } else { + throw new Error("Unexpected error message"); + } + }; + const p = listener.accept().catch(checkErr); + const p1 = listener.accept().catch(checkErr); + await Promise.race([p, p1]); + listener.close(); + await Promise.all([p, p1]); + assertEquals(acceptErrCount, 1); + } +); + +unitTest({ perms: { net: true } }, async function netTcpDialListen(): Promise< + void +> { + const listener = Deno.listen({ port: 4500 }); + listener.accept().then( + async (conn): Promise<void> => { + assert(conn.remoteAddr != null); + assertEquals(conn.localAddr.hostname, "127.0.0.1"); + assertEquals(conn.localAddr.port, 4500); + await conn.write(new Uint8Array([1, 2, 3])); + conn.close(); + } + ); + const conn = await Deno.connect({ hostname: "127.0.0.1", port: 4500 }); + assertEquals(conn.remoteAddr.hostname, "127.0.0.1"); + assertEquals(conn.remoteAddr.port, 4500); + assert(conn.localAddr != null); + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEquals(3, readResult); + assertEquals(1, buf[0]); + assertEquals(2, buf[1]); + assertEquals(3, buf[2]); + assert(conn.rid > 0); + + assert(readResult !== Deno.EOF); + + const readResult2 = await conn.read(buf); + assertEquals(Deno.EOF, readResult2); + + listener.close(); + conn.close(); +}); + +unitTest( + { skip: Deno.build.os === "win", perms: { net: true } }, + async function netUdpSendReceive(): Promise<void> { + const alice = Deno.listen({ port: 4500, transport: "udp" }); + assertEquals(alice.addr.port, 4500); + assertEquals(alice.addr.hostname, "0.0.0.0"); + assertEquals(alice.addr.transport, "udp"); + + const bob = Deno.listen({ port: 4501, transport: "udp" }); + assertEquals(bob.addr.port, 4501); + assertEquals(bob.addr.hostname, "0.0.0.0"); + assertEquals(bob.addr.transport, "udp"); + + const sent = new Uint8Array([1, 2, 3]); + await alice.send(sent, bob.addr); + + const [recvd, remote] = await bob.receive(); + assertEquals(remote.port, 4500); + assertEquals(recvd.length, 3); + assertEquals(1, recvd[0]); + assertEquals(2, recvd[1]); + assertEquals(3, recvd[2]); + alice.close(); + bob.close(); + } +); + +unitTest( + { perms: { net: true } }, + async function netTcpListenCloseWhileIterating(): Promise<void> { + const listener = Deno.listen({ port: 8000 }); + const nextWhileClosing = listener[Symbol.asyncIterator]().next(); + listener.close(); + assertEquals(await nextWhileClosing, { value: undefined, done: true }); + + const nextAfterClosing = listener[Symbol.asyncIterator]().next(); + assertEquals(await nextAfterClosing, { value: undefined, done: true }); + } +); + +unitTest( + { skip: Deno.build.os === "win", perms: { net: true } }, + async function netUdpListenCloseWhileIterating(): Promise<void> { + const socket = Deno.listen({ port: 8000, transport: "udp" }); + const nextWhileClosing = socket[Symbol.asyncIterator]().next(); + socket.close(); + assertEquals(await nextWhileClosing, { value: undefined, done: true }); + + const nextAfterClosing = socket[Symbol.asyncIterator]().next(); + assertEquals(await nextAfterClosing, { value: undefined, done: true }); + } +); + +/* TODO(ry) Re-enable this test. +unitTest({ perms: { net: true } }, async function netListenAsyncIterator(): Promise<void> { + const listener = Deno.listen(":4500"); + const runAsyncIterator = async (): Promise<void> => { + for await (let conn of listener) { + await conn.write(new Uint8Array([1, 2, 3])); + conn.close(); + } + }; + runAsyncIterator(); + const conn = await Deno.connect("127.0.0.1:4500"); + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEquals(3, readResult); + assertEquals(1, buf[0]); + assertEquals(2, buf[1]); + assertEquals(3, buf[2]); + assert(conn.rid > 0); + + assert(readResult !== Deno.EOF); + + const readResult2 = await conn.read(buf); + assertEquals(Deno.EOF, readResult2); + + listener.close(); + conn.close(); +}); + */ + +/* TODO Fix broken test. +unitTest({ perms: { net: true } }, async function netCloseReadSuccess() { + const addr = "127.0.0.1:4500"; + const listener = Deno.listen(addr); + const closeDeferred = deferred(); + const closeReadDeferred = deferred(); + listener.accept().then(async conn => { + await closeReadDeferred.promise; + await conn.write(new Uint8Array([1, 2, 3])); + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEquals(3, readResult); + assertEquals(4, buf[0]); + assertEquals(5, buf[1]); + assertEquals(6, buf[2]); + conn.close(); + closeDeferred.resolve(); + }); + const conn = await Deno.connect(addr); + conn.closeRead(); // closing read + closeReadDeferred.resolve(); + const buf = new Uint8Array(1024); + const readResult = await conn.read(buf); + assertEquals(Deno.EOF, readResult); // with immediate EOF + // Ensure closeRead does not impact write + await conn.write(new Uint8Array([4, 5, 6])); + await closeDeferred.promise; + listener.close(); + conn.close(); +}); +*/ + +/* TODO Fix broken test. +unitTest({ perms: { net: true } }, async function netDoubleCloseRead() { + const addr = "127.0.0.1:4500"; + const listener = Deno.listen(addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await conn.write(new Uint8Array([1, 2, 3])); + await closeDeferred.promise; + conn.close(); + }); + const conn = await Deno.connect(addr); + conn.closeRead(); // closing read + let err; + try { + // Duplicated close should throw error + conn.closeRead(); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.NotConnected); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); +*/ + +/* TODO Fix broken test. +unitTest({ perms: { net: true } }, async function netCloseWriteSuccess() { + const addr = "127.0.0.1:4500"; + const listener = Deno.listen(addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await conn.write(new Uint8Array([1, 2, 3])); + await closeDeferred.promise; + conn.close(); + }); + const conn = await Deno.connect(addr); + conn.closeWrite(); // closing write + const buf = new Uint8Array(1024); + // Check read not impacted + const readResult = await conn.read(buf); + assertEquals(3, readResult); + assertEquals(1, buf[0]); + assertEquals(2, buf[1]); + assertEquals(3, buf[2]); + // Check write should be closed + let err; + try { + await conn.write(new Uint8Array([1, 2, 3])); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.BrokenPipe); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); +*/ + +/* TODO Fix broken test. +unitTest({ perms: { net: true } }, async function netDoubleCloseWrite() { + const addr = "127.0.0.1:4500"; + const listener = Deno.listen(addr); + const closeDeferred = deferred(); + listener.accept().then(async conn => { + await closeDeferred.promise; + conn.close(); + }); + const conn = await Deno.connect(addr); + conn.closeWrite(); // closing write + let err; + try { + // Duplicated close should throw error + conn.closeWrite(); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof Deno.errors.NotConnected); + closeDeferred.resolve(); + listener.close(); + conn.close(); +}); +*/ diff --git a/cli/js/tests/os_test.ts b/cli/js/tests/os_test.ts new file mode 100644 index 000000000..0c851be51 --- /dev/null +++ b/cli/js/tests/os_test.ts @@ -0,0 +1,337 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + assert, + assertEquals, + assertNotEquals, + assertThrows, + unitTest +} from "./test_util.ts"; + +unitTest({ perms: { env: true } }, function envSuccess(): void { + const env = Deno.env(); + assert(env !== null); + // eslint-disable-next-line @typescript-eslint/camelcase + env.test_var = "Hello World"; + const newEnv = Deno.env(); + assertEquals(env.test_var, newEnv.test_var); + assertEquals(Deno.env("test_var"), env.test_var); +}); + +unitTest({ perms: { env: true } }, function envNotFound(): void { + const r = Deno.env("env_var_does_not_exist!"); + assertEquals(r, undefined); +}); + +unitTest(function envPermissionDenied1(): void { + let err; + try { + Deno.env(); + } catch (e) { + err = e; + } + assertNotEquals(err, undefined); + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest(function envPermissionDenied2(): void { + let err; + try { + Deno.env("PATH"); + } catch (e) { + err = e; + } + assertNotEquals(err, undefined); + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +// This test verifies that on Windows, environment variables are +// case-insensitive. Case normalization needs be done using the collation +// that Windows uses, rather than naively using String.toLowerCase(). +unitTest( + { skip: Deno.build.os !== "win", perms: { env: true, run: true } }, + async function envCaseInsensitive() { + // Utility function that runs a Deno subprocess with the environment + // specified in `inputEnv`. The subprocess reads the environment variables + // which are in the keys of `expectedEnv` and writes them to stdout as JSON. + // It is then verified that these match with the values of `expectedEnv`. + const checkChildEnv = async ( + inputEnv: Record<string, string>, + expectedEnv: Record<string, string> + ): Promise<void> => { + const src = ` + console.log( + ${JSON.stringify(Object.keys(expectedEnv))}.map(k => Deno.env(k)) + )`; + const proc = Deno.run({ + args: [Deno.execPath(), "eval", src], + env: inputEnv, + stdout: "piped" + }); + const status = await proc.status(); + assertEquals(status.success, true); + const expectedValues = Object.values(expectedEnv); + const actualValues = JSON.parse( + new TextDecoder().decode(await proc.output()) + ); + assertEquals(actualValues, expectedValues); + proc.close(); + }; + + assertEquals(Deno.env("path"), Deno.env("PATH")); + assertEquals(Deno.env("Path"), Deno.env("PATH")); + + // Check 'foo', 'Foo' and 'Foo' are case folded. + await checkChildEnv({ foo: "X" }, { foo: "X", Foo: "X", FOO: "X" }); + + // Check that 'µ' and 'Μ' are not case folded. + const lc1 = "µ"; + const uc1 = lc1.toUpperCase(); + assertNotEquals(lc1, uc1); + await checkChildEnv( + { [lc1]: "mu", [uc1]: "MU" }, + { [lc1]: "mu", [uc1]: "MU" } + ); + + // Check that 'dž' and 'DŽ' are folded, but 'Dž' is preserved. + const c2 = "Dž"; + const lc2 = c2.toLowerCase(); + const uc2 = c2.toUpperCase(); + assertNotEquals(c2, lc2); + assertNotEquals(c2, uc2); + await checkChildEnv( + { [c2]: "Dz", [lc2]: "dz" }, + { [c2]: "Dz", [lc2]: "dz", [uc2]: "dz" } + ); + await checkChildEnv( + { [c2]: "Dz", [uc2]: "DZ" }, + { [c2]: "Dz", [uc2]: "DZ", [lc2]: "DZ" } + ); + } +); + +unitTest(function osPid(): void { + assert(Deno.pid > 0); +}); + +unitTest({ perms: { env: true } }, function getDir(): void { + type supportOS = "mac" | "win" | "linux"; + + interface Runtime { + os: supportOS; + shouldHaveValue: boolean; + } + + interface Scenes { + kind: Deno.DirKind; + runtime: Runtime[]; + } + + const scenes: Scenes[] = [ + { + kind: "config", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "cache", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "executable", + runtime: [ + { os: "mac", shouldHaveValue: false }, + { os: "win", shouldHaveValue: false }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "data", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "data_local", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "audio", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "desktop", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "document", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "download", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "font", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: false }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "picture", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "public", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "template", + runtime: [ + { os: "mac", shouldHaveValue: false }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + }, + { + kind: "tmp", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: true } + ] + }, + { + kind: "video", + runtime: [ + { os: "mac", shouldHaveValue: true }, + { os: "win", shouldHaveValue: true }, + { os: "linux", shouldHaveValue: false } + ] + } + ]; + + for (const s of scenes) { + for (const r of s.runtime) { + if (Deno.build.os !== r.os) continue; + if (r.shouldHaveValue) { + const d = Deno.dir(s.kind); + assert(d); + assert(d.length > 0); + } + } + } +}); + +unitTest(function getDirWithoutPermission(): void { + assertThrows( + () => Deno.dir("home"), + Deno.errors.PermissionDenied, + `run again with the --allow-env flag` + ); +}); + +unitTest({ perms: { env: true } }, function execPath(): void { + assertNotEquals(Deno.execPath(), ""); +}); + +unitTest({ perms: { env: false } }, function execPathPerm(): void { + let caughtError = false; + try { + Deno.execPath(); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + assert(caughtError); +}); + +unitTest({ perms: { env: true } }, function loadavgSuccess(): void { + const load = Deno.loadavg(); + assertEquals(load.length, 3); +}); + +unitTest({ perms: { env: false } }, function loadavgPerm(): void { + let caughtError = false; + try { + Deno.loadavg(); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + assert(caughtError); +}); + +unitTest({ perms: { env: true } }, function hostnameDir(): void { + assertNotEquals(Deno.hostname(), ""); +}); + +unitTest({ perms: { env: false } }, function hostnamePerm(): void { + let caughtError = false; + try { + Deno.hostname(); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + assert(caughtError); +}); + +unitTest({ perms: { env: true } }, function releaseDir(): void { + assertNotEquals(Deno.osRelease(), ""); +}); + +unitTest({ perms: { env: false } }, function releasePerm(): void { + let caughtError = false; + try { + Deno.osRelease(); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } + assert(caughtError); +}); diff --git a/cli/js/tests/performance_test.ts b/cli/js/tests/performance_test.ts new file mode 100644 index 000000000..89b7cad8b --- /dev/null +++ b/cli/js/tests/performance_test.ts @@ -0,0 +1,15 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, createResolvable } from "./test_util.ts"; + +unitTest({ perms: { hrtime: false } }, async function performanceNow(): Promise< + void +> { + const resolvable = createResolvable(); + const start = performance.now(); + setTimeout((): void => { + const end = performance.now(); + assert(end - start >= 10); + resolvable.resolve(); + }, 10); + await resolvable; +}); diff --git a/cli/js/tests/permissions_test.ts b/cli/js/tests/permissions_test.ts new file mode 100644 index 000000000..7528768a1 --- /dev/null +++ b/cli/js/tests/permissions_test.ts @@ -0,0 +1,27 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(async function permissionInvalidName(): Promise<void> { + let thrown = false; + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await Deno.permissions.query({ name: "foo" as any }); + } catch (e) { + thrown = true; + assert(e instanceof Error); + } finally { + assert(thrown); + } +}); + +unitTest(async function permissionNetInvalidUrl(): Promise<void> { + let thrown = false; + try { + await Deno.permissions.query({ name: "net", url: ":" }); + } catch (e) { + thrown = true; + assert(e instanceof URIError); + } finally { + assert(thrown); + } +}); diff --git a/cli/js/tests/process_test.ts b/cli/js/tests/process_test.ts new file mode 100644 index 000000000..2411b5134 --- /dev/null +++ b/cli/js/tests/process_test.ts @@ -0,0 +1,383 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + assert, + assertEquals, + assertStrContains, + unitTest +} from "./test_util.ts"; +const { kill, run, readFile, open, makeTempDir, writeFile } = Deno; + +unitTest(function runPermissions(): void { + let caughtError = false; + try { + Deno.run({ args: ["python", "-c", "print('hello world')"] }); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { run: true } }, async function runSuccess(): Promise<void> { + const p = run({ + args: ["python", "-c", "print('hello world')"], + stdout: "piped", + stderr: "null" + }); + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.stdout!.close(); + p.close(); +}); + +unitTest( + { perms: { run: true } }, + async function runCommandFailedWithCode(): Promise<void> { + const p = run({ + args: ["python", "-c", "import sys;sys.exit(41 + 1)"] + }); + const status = await p.status(); + assertEquals(status.success, false); + assertEquals(status.code, 42); + assertEquals(status.signal, undefined); + p.close(); + } +); + +unitTest( + { + // No signals on windows. + skip: Deno.build.os === "win", + perms: { run: true } + }, + async function runCommandFailedWithSignal(): Promise<void> { + const p = run({ + args: ["python", "-c", "import os;os.kill(os.getpid(), 9)"] + }); + const status = await p.status(); + assertEquals(status.success, false); + assertEquals(status.code, undefined); + assertEquals(status.signal, 9); + p.close(); + } +); + +unitTest({ perms: { run: true } }, function runNotFound(): void { + let error; + try { + run({ args: ["this file hopefully doesn't exist"] }); + } catch (e) { + error = e; + } + assert(error !== undefined); + assert(error instanceof Deno.errors.NotFound); +}); + +unitTest( + { perms: { write: true, run: true } }, + async function runWithCwdIsAsync(): Promise<void> { + const enc = new TextEncoder(); + const cwd = await makeTempDir({ prefix: "deno_command_test" }); + + const exitCodeFile = "deno_was_here"; + const pyProgramFile = "poll_exit.py"; + const pyProgram = ` +from sys import exit +from time import sleep + +while True: + try: + with open("${exitCodeFile}", "r") as f: + line = f.readline() + code = int(line) + exit(code) + except IOError: + # Retry if we got here before deno wrote the file. + sleep(0.01) + pass +`; + + Deno.writeFileSync(`${cwd}/${pyProgramFile}.py`, enc.encode(pyProgram)); + const p = run({ + cwd, + args: ["python", `${pyProgramFile}.py`] + }); + + // Write the expected exit code *after* starting python. + // This is how we verify that `run()` is actually asynchronous. + const code = 84; + Deno.writeFileSync(`${cwd}/${exitCodeFile}`, enc.encode(`${code}`)); + + const status = await p.status(); + assertEquals(status.success, false); + assertEquals(status.code, code); + assertEquals(status.signal, undefined); + p.close(); + } +); + +unitTest({ perms: { run: true } }, async function runStdinPiped(): Promise< + void +> { + const p = run({ + args: ["python", "-c", "import sys; assert 'hello' == sys.stdin.read();"], + stdin: "piped" + }); + assert(p.stdin); + assert(!p.stdout); + assert(!p.stderr); + + const msg = new TextEncoder().encode("hello"); + const n = await p.stdin.write(msg); + assertEquals(n, msg.byteLength); + + p.stdin!.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); +}); + +unitTest({ perms: { run: true } }, async function runStdoutPiped(): Promise< + void +> { + const p = run({ + args: ["python", "-c", "import sys; sys.stdout.write('hello')"], + stdout: "piped" + }); + assert(!p.stdin); + assert(!p.stderr); + + const data = new Uint8Array(10); + let r = await p.stdout!.read(data); + if (r === Deno.EOF) { + throw new Error("p.stdout.read(...) should not be EOF"); + } + assertEquals(r, 5); + const s = new TextDecoder().decode(data.subarray(0, r)); + assertEquals(s, "hello"); + r = await p.stdout!.read(data); + assertEquals(r, Deno.EOF); + p.stdout!.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); +}); + +unitTest({ perms: { run: true } }, async function runStderrPiped(): Promise< + void +> { + const p = run({ + args: ["python", "-c", "import sys; sys.stderr.write('hello')"], + stderr: "piped" + }); + assert(!p.stdin); + assert(!p.stdout); + + const data = new Uint8Array(10); + let r = await p.stderr!.read(data); + if (r === Deno.EOF) { + throw new Error("p.stderr.read should not return EOF here"); + } + assertEquals(r, 5); + const s = new TextDecoder().decode(data.subarray(0, r)); + assertEquals(s, "hello"); + r = await p.stderr!.read(data); + assertEquals(r, Deno.EOF); + p.stderr!.close(); + + const status = await p.status(); + assertEquals(status.success, true); + assertEquals(status.code, 0); + assertEquals(status.signal, undefined); + p.close(); +}); + +unitTest({ perms: { run: true } }, async function runOutput(): Promise<void> { + const p = run({ + args: ["python", "-c", "import sys; sys.stdout.write('hello')"], + stdout: "piped" + }); + const output = await p.output(); + const s = new TextDecoder().decode(output); + assertEquals(s, "hello"); + p.close(); +}); + +unitTest({ perms: { run: true } }, async function runStderrOutput(): Promise< + void +> { + const p = run({ + args: ["python", "-c", "import sys; sys.stderr.write('error')"], + stderr: "piped" + }); + const error = await p.stderrOutput(); + const s = new TextDecoder().decode(error); + assertEquals(s, "error"); + p.close(); +}); + +unitTest( + { perms: { run: true, write: true, read: true } }, + async function runRedirectStdoutStderr(): Promise<void> { + const tempDir = await makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + const file = await open(fileName, "w"); + + const p = run({ + args: [ + "python", + "-c", + "import sys; sys.stderr.write('error\\n'); sys.stdout.write('output\\n');" + ], + stdout: file.rid, + stderr: file.rid + }); + + await p.status(); + p.close(); + file.close(); + + const fileContents = await readFile(fileName); + const decoder = new TextDecoder(); + const text = decoder.decode(fileContents); + + assertStrContains(text, "error"); + assertStrContains(text, "output"); + } +); + +unitTest( + { perms: { run: true, write: true, read: true } }, + async function runRedirectStdin(): Promise<void> { + const tempDir = await makeTempDir(); + const fileName = tempDir + "/redirected_stdio.txt"; + const encoder = new TextEncoder(); + await writeFile(fileName, encoder.encode("hello")); + const file = await open(fileName, "r"); + + const p = run({ + args: ["python", "-c", "import sys; assert 'hello' == sys.stdin.read();"], + stdin: file.rid + }); + + const status = await p.status(); + assertEquals(status.code, 0); + p.close(); + file.close(); + } +); + +unitTest({ perms: { run: true } }, async function runEnv(): Promise<void> { + const p = run({ + args: [ + "python", + "-c", + "import os, sys; sys.stdout.write(os.environ.get('FOO', '') + os.environ.get('BAR', ''))" + ], + env: { + FOO: "0123", + BAR: "4567" + }, + stdout: "piped" + }); + const output = await p.output(); + const s = new TextDecoder().decode(output); + assertEquals(s, "01234567"); + p.close(); +}); + +unitTest({ perms: { run: true } }, async function runClose(): Promise<void> { + const p = run({ + args: [ + "python", + "-c", + "from time import sleep; import sys; sleep(10000); sys.stderr.write('error')" + ], + stderr: "piped" + }); + assert(!p.stdin); + assert(!p.stdout); + + p.close(); + + const data = new Uint8Array(10); + const r = await p.stderr!.read(data); + assertEquals(r, Deno.EOF); + p.stderr!.close(); +}); + +unitTest(function signalNumbers(): void { + if (Deno.build.os === "mac") { + assertEquals(Deno.Signal.SIGSTOP, 17); + } else if (Deno.build.os === "linux") { + assertEquals(Deno.Signal.SIGSTOP, 19); + } +}); + +// Ignore signal tests on windows for now... +if (Deno.build.os !== "win") { + unitTest(function killPermissions(): void { + let caughtError = false; + try { + // Unlike the other test cases, we don't have permission to spawn a + // subprocess we can safely kill. Instead we send SIGCONT to the current + // process - assuming that Deno does not have a special handler set for it + // and will just continue even if a signal is erroneously sent. + Deno.kill(Deno.pid, Deno.Signal.SIGCONT); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + }); + + unitTest({ perms: { run: true } }, async function killSuccess(): Promise< + void + > { + const p = run({ + args: ["python", "-c", "from time import sleep; sleep(10000)"] + }); + + assertEquals(Deno.Signal.SIGINT, 2); + kill(p.pid, Deno.Signal.SIGINT); + const status = await p.status(); + + assertEquals(status.success, false); + // TODO(ry) On Linux, status.code is sometimes undefined and sometimes 1. + // The following assert is causing this test to be flaky. Investigate and + // re-enable when it can be made deterministic. + // assertEquals(status.code, 1); + // assertEquals(status.signal, Deno.Signal.SIGINT); + p.close(); + }); + + unitTest({ perms: { run: true } }, async function killFailed(): Promise< + void + > { + const p = run({ + args: ["python", "-c", "from time import sleep; sleep(10000)"] + }); + assert(!p.stdin); + assert(!p.stdout); + + let err; + try { + kill(p.pid, 12345); + } catch (e) { + err = e; + } + + assert(!!err); + assert(err instanceof TypeError); + + p.close(); + }); +} diff --git a/cli/js/tests/read_dir_test.ts b/cli/js/tests/read_dir_test.ts new file mode 100644 index 000000000..95936c645 --- /dev/null +++ b/cli/js/tests/read_dir_test.ts @@ -0,0 +1,86 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +type FileInfo = Deno.FileInfo; + +function assertSameContent(files: FileInfo[]): void { + let counter = 0; + + for (const file of files) { + if (file.name === "subdir") { + assert(file.isDirectory()); + counter++; + } + + if (file.name === "002_hello.ts") { + assertEquals(file.mode!, Deno.statSync(`cli/tests/${file.name}`).mode!); + counter++; + } + } + + assertEquals(counter, 2); +} + +unitTest({ perms: { read: true } }, function readdirSyncSuccess(): void { + const files = Deno.readdirSync("cli/tests/"); + assertSameContent(files); +}); + +unitTest({ perms: { read: false } }, function readdirSyncPerm(): void { + let caughtError = false; + try { + Deno.readdirSync("tests/"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function readdirSyncNotDir(): void { + let caughtError = false; + let src; + + try { + src = Deno.readdirSync("cli/tests/fixture.json"); + } catch (err) { + caughtError = true; + assert(err instanceof Error); + } + assert(caughtError); + assertEquals(src, undefined); +}); + +unitTest({ perms: { read: true } }, function readdirSyncNotFound(): void { + let caughtError = false; + let src; + + try { + src = Deno.readdirSync("bad_dir_name"); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.NotFound); + } + assert(caughtError); + assertEquals(src, undefined); +}); + +unitTest({ perms: { read: true } }, async function readdirSuccess(): Promise< + void +> { + const files = await Deno.readdir("cli/tests/"); + assertSameContent(files); +}); + +unitTest({ perms: { read: false } }, async function readdirPerm(): Promise< + void +> { + let caughtError = false; + try { + await Deno.readdir("tests/"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); diff --git a/cli/js/tests/read_file_test.ts b/cli/js/tests/read_file_test.ts new file mode 100644 index 000000000..1b709b1f4 --- /dev/null +++ b/cli/js/tests/read_file_test.ts @@ -0,0 +1,59 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest({ perms: { read: true } }, function readFileSyncSuccess(): void { + const data = Deno.readFileSync("cli/tests/fixture.json"); + assert(data.byteLength > 0); + const decoder = new TextDecoder("utf-8"); + const json = decoder.decode(data); + const pkg = JSON.parse(json); + assertEquals(pkg.name, "deno"); +}); + +unitTest({ perms: { read: false } }, function readFileSyncPerm(): void { + let caughtError = false; + try { + Deno.readFileSync("cli/tests/fixture.json"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function readFileSyncNotFound(): void { + let caughtError = false; + let data; + try { + data = Deno.readFileSync("bad_filename"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + assert(data === undefined); +}); + +unitTest({ perms: { read: true } }, async function readFileSuccess(): Promise< + void +> { + const data = await Deno.readFile("cli/tests/fixture.json"); + assert(data.byteLength > 0); + const decoder = new TextDecoder("utf-8"); + const json = decoder.decode(data); + const pkg = JSON.parse(json); + assertEquals(pkg.name, "deno"); +}); + +unitTest({ perms: { read: false } }, async function readFilePerm(): Promise< + void +> { + let caughtError = false; + try { + await Deno.readFile("cli/tests/fixture.json"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); diff --git a/cli/js/tests/read_link_test.ts b/cli/js/tests/read_link_test.ts new file mode 100644 index 000000000..409f37110 --- /dev/null +++ b/cli/js/tests/read_link_test.ts @@ -0,0 +1,75 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { write: true, read: true } }, + function readlinkSyncSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + // TODO Add test for Windows once symlink is implemented for Windows. + // See https://github.com/denoland/deno/issues/815. + if (Deno.build.os !== "win") { + Deno.symlinkSync(target, symlink); + const targetPath = Deno.readlinkSync(symlink); + assertEquals(targetPath, target); + } + } +); + +unitTest({ perms: { read: false } }, async function readlinkSyncPerm(): Promise< + void +> { + let caughtError = false; + try { + Deno.readlinkSync("/symlink"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function readlinkSyncNotFound(): void { + let caughtError = false; + let data; + try { + data = Deno.readlinkSync("bad_filename"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + assertEquals(data, undefined); +}); + +unitTest( + { perms: { write: true, read: true } }, + async function readlinkSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + // TODO Add test for Windows once symlink is implemented for Windows. + // See https://github.com/denoland/deno/issues/815. + if (Deno.build.os !== "win") { + Deno.symlinkSync(target, symlink); + const targetPath = await Deno.readlink(symlink); + assertEquals(targetPath, target); + } + } +); + +unitTest({ perms: { read: false } }, async function readlinkPerm(): Promise< + void +> { + let caughtError = false; + try { + await Deno.readlink("/symlink"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); diff --git a/cli/js/tests/realpath_test.ts b/cli/js/tests/realpath_test.ts new file mode 100644 index 000000000..cda7ddaf1 --- /dev/null +++ b/cli/js/tests/realpath_test.ts @@ -0,0 +1,108 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest({ perms: { read: true } }, function realpathSyncSuccess(): void { + const incompletePath = "cli/tests/fixture.json"; + const realPath = Deno.realpathSync(incompletePath); + if (Deno.build.os !== "win") { + assert(realPath.startsWith("/")); + } else { + assert(/^[A-Z]/.test(realPath)); + } + assert(realPath.endsWith(incompletePath)); +}); + +unitTest( + { + skip: Deno.build.os === "win", + perms: { read: true, write: true } + }, + function realpathSyncSymlink(): void { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + Deno.symlinkSync(target, symlink); + const targetPath = Deno.realpathSync(symlink); + assert(targetPath.startsWith("/")); + assert(targetPath.endsWith("/target")); + } +); + +unitTest({ perms: { read: false } }, function realpathSyncPerm(): void { + let caughtError = false; + try { + Deno.realpathSync("some_file"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function realpathSyncNotFound(): void { + let caughtError = false; + try { + Deno.realpathSync("bad_filename"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function realpathSuccess(): Promise< + void +> { + const incompletePath = "cli/tests/fixture.json"; + const realPath = await Deno.realpath(incompletePath); + if (Deno.build.os !== "win") { + assert(realPath.startsWith("/")); + } else { + assert(/^[A-Z]/.test(realPath)); + } + assert(realPath.endsWith(incompletePath)); +}); + +unitTest( + { + skip: Deno.build.os === "win", + perms: { read: true, write: true } + }, + async function realpathSymlink(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const target = testDir + "/target"; + const symlink = testDir + "/symln"; + Deno.mkdirSync(target); + Deno.symlinkSync(target, symlink); + const targetPath = await Deno.realpath(symlink); + assert(targetPath.startsWith("/")); + assert(targetPath.endsWith("/target")); + } +); + +unitTest({ perms: { read: false } }, async function realpathPerm(): Promise< + void +> { + let caughtError = false; + try { + await Deno.realpath("some_file"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function realpathNotFound(): Promise< + void +> { + let caughtError = false; + try { + await Deno.realpath("bad_filename"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); +}); diff --git a/cli/js/tests/remove_test.ts b/cli/js/tests/remove_test.ts new file mode 100644 index 000000000..fac5ba303 --- /dev/null +++ b/cli/js/tests/remove_test.ts @@ -0,0 +1,481 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +// SYNC + +unitTest( + { perms: { write: true, read: true } }, + function removeSyncDirSuccess(): void { + // REMOVE EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/subdir"; + Deno.mkdirSync(path); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + Deno.removeSync(path); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + function removeSyncFileSuccess(): void { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); // check exist first + Deno.removeSync(filename); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + function removeSyncFail(): void { + // NON-EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory()); // check exist first + let err; + try { + // Should not be able to recursively remove + Deno.removeSync(path); + } catch (e) { + err = e; + } + // TODO(ry) Is Other really the error we should get here? What would Go do? + assert(err instanceof Error); + // NON-EXISTENT DIRECTORY/FILE + try { + // Non-existent + Deno.removeSync("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + function removeSyncDanglingSymlinkSuccess(): void { + const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; + // TODO(#3832): Remove "Not Implemented" error checking when symlink creation is implemented for Windows + let errOnWindows; + try { + Deno.symlinkSync("unexistent_file", danglingSymlinkPath); + } catch (err) { + errOnWindows = err; + } + if (Deno.build.os === "win") { + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const pathInfo = Deno.lstatSync(danglingSymlinkPath); + assert(pathInfo.isSymlink()); + Deno.removeSync(danglingSymlinkPath); + let err; + try { + Deno.lstatSync(danglingSymlinkPath); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + } + } +); + +unitTest( + { perms: { write: true, read: true } }, + function removeSyncValidSymlinkSuccess(): void { + const encoder = new TextEncoder(); + const data = encoder.encode("Test"); + const tempDir = Deno.makeTempDirSync(); + const filePath = tempDir + "/test.txt"; + const validSymlinkPath = tempDir + "/valid_symlink"; + Deno.writeFileSync(filePath, data, { mode: 0o666 }); + // TODO(#3832): Remove "Not Implemented" error checking when symlink creation is implemented for Windows + let errOnWindows; + try { + Deno.symlinkSync(filePath, validSymlinkPath); + } catch (err) { + errOnWindows = err; + } + if (Deno.build.os === "win") { + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const symlinkPathInfo = Deno.statSync(validSymlinkPath); + assert(symlinkPathInfo.isFile()); + Deno.removeSync(validSymlinkPath); + let err; + try { + Deno.statSync(validSymlinkPath); + } catch (e) { + err = e; + } + Deno.removeSync(filePath); + assert(err instanceof Deno.errors.NotFound); + } + } +); + +unitTest({ perms: { write: false } }, function removeSyncPerm(): void { + let err; + try { + Deno.removeSync("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { write: true, read: true } }, + function removeAllSyncDirSuccess(): void { + // REMOVE EMPTY DIRECTORY + let path = Deno.makeTempDirSync() + "/dir/subdir"; + Deno.mkdirSync(path, { recursive: true }); + let pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + Deno.removeSync(path, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + + // REMOVE NON-EMPTY DIRECTORY + path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory()); // check exist first + Deno.removeSync(path, { recursive: true }); // remove + // We then check parent directory again after remove + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + function removeAllSyncFileSuccess(): void { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); // check exist first + Deno.removeSync(filename, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest({ perms: { write: true } }, function removeAllSyncFail(): void { + // NON-EXISTENT DIRECTORY/FILE + let err; + try { + // Non-existent + Deno.removeSync("/baddir", { recursive: true }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest({ perms: { write: false } }, function removeAllSyncPerm(): void { + let err; + try { + Deno.removeSync("/baddir", { recursive: true }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +// ASYNC + +unitTest( + { perms: { write: true, read: true } }, + async function removeDirSuccess(): Promise<void> { + // REMOVE EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/dir/subdir"; + Deno.mkdirSync(path, { recursive: true }); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + await Deno.remove(path); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function removeFileSuccess(): Promise<void> { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); // check exist first + await Deno.remove(filename); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function removeFail(): Promise<void> { + // NON-EMPTY DIRECTORY + const path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + const pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory()); // check exist first + let err; + try { + // Should not be able to recursively remove + await Deno.remove(path); + } catch (e) { + err = e; + } + assert(err instanceof Error); + // NON-EXISTENT DIRECTORY/FILE + try { + // Non-existent + await Deno.remove("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function removeDanglingSymlinkSuccess(): Promise<void> { + const danglingSymlinkPath = Deno.makeTempDirSync() + "/dangling_symlink"; + // TODO(#3832): Remove "Not Implemented" error checking when symlink creation is implemented for Windows + let errOnWindows; + try { + Deno.symlinkSync("unexistent_file", danglingSymlinkPath); + } catch (e) { + errOnWindows = e; + } + if (Deno.build.os === "win") { + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const pathInfo = Deno.lstatSync(danglingSymlinkPath); + assert(pathInfo.isSymlink()); + await Deno.remove(danglingSymlinkPath); + let err; + try { + Deno.lstatSync(danglingSymlinkPath); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + } + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function removeValidSymlinkSuccess(): Promise<void> { + const encoder = new TextEncoder(); + const data = encoder.encode("Test"); + const tempDir = Deno.makeTempDirSync(); + const filePath = tempDir + "/test.txt"; + const validSymlinkPath = tempDir + "/valid_symlink"; + Deno.writeFileSync(filePath, data, { mode: 0o666 }); + // TODO(#3832): Remove "Not Implemented" error checking when symlink creation is implemented for Windows + let errOnWindows; + try { + Deno.symlinkSync(filePath, validSymlinkPath); + } catch (e) { + errOnWindows = e; + } + if (Deno.build.os === "win") { + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const symlinkPathInfo = Deno.statSync(validSymlinkPath); + assert(symlinkPathInfo.isFile()); + await Deno.remove(validSymlinkPath); + let err; + try { + Deno.statSync(validSymlinkPath); + } catch (e) { + err = e; + } + Deno.removeSync(filePath); + assert(err instanceof Deno.errors.NotFound); + } + } +); + +unitTest({ perms: { write: false } }, async function removePerm(): Promise< + void +> { + let err; + try { + await Deno.remove("/baddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { write: true, read: true } }, + async function removeAllDirSuccess(): Promise<void> { + // REMOVE EMPTY DIRECTORY + let path = Deno.makeTempDirSync() + "/dir/subdir"; + Deno.mkdirSync(path, { recursive: true }); + let pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + await Deno.remove(path, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + + // REMOVE NON-EMPTY DIRECTORY + path = Deno.makeTempDirSync() + "/dir/subdir"; + const subPath = path + "/subsubdir"; + Deno.mkdirSync(path, { recursive: true }); + Deno.mkdirSync(subPath); + pathInfo = Deno.statSync(path); + assert(pathInfo.isDirectory()); // check exist first + const subPathInfo = Deno.statSync(subPath); + assert(subPathInfo.isDirectory()); // check exist first + await Deno.remove(path, { recursive: true }); // remove + // We then check parent directory again after remove + try { + Deno.statSync(path); + } catch (e) { + err = e; + } + // Directory is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { write: true, read: true } }, + async function removeAllFileSuccess(): Promise<void> { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(filename); + assert(fileInfo.isFile()); // check exist first + await Deno.remove(filename, { recursive: true }); // remove + // We then check again after remove + let err; + try { + Deno.statSync(filename); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest({ perms: { write: true } }, async function removeAllFail(): Promise< + void +> { + // NON-EXISTENT DIRECTORY/FILE + let err; + try { + // Non-existent + await Deno.remove("/baddir", { recursive: true }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); +}); + +unitTest({ perms: { write: false } }, async function removeAllPerm(): Promise< + void +> { + let err; + try { + await Deno.remove("/baddir", { recursive: true }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); diff --git a/cli/js/tests/rename_test.ts b/cli/js/tests/rename_test.ts new file mode 100644 index 000000000..288f24bd7 --- /dev/null +++ b/cli/js/tests/rename_test.ts @@ -0,0 +1,84 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function renameSyncSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const oldpath = testDir + "/oldpath"; + const newpath = testDir + "/newpath"; + Deno.mkdirSync(oldpath); + Deno.renameSync(oldpath, newpath); + const newPathInfo = Deno.statSync(newpath); + assert(newPathInfo.isDirectory()); + + let caughtErr = false; + let oldPathInfo; + + try { + oldPathInfo = Deno.statSync(oldpath); + } catch (e) { + caughtErr = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtErr); + assertEquals(oldPathInfo, undefined); + } +); + +unitTest( + { perms: { read: false, write: true } }, + function renameSyncReadPerm(): void { + let err; + try { + const oldpath = "/oldbaddir"; + const newpath = "/newbaddir"; + Deno.renameSync(oldpath, newpath); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } +); + +unitTest( + { perms: { read: true, write: false } }, + function renameSyncWritePerm(): void { + let err; + try { + const oldpath = "/oldbaddir"; + const newpath = "/newbaddir"; + Deno.renameSync(oldpath, newpath); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function renameSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const oldpath = testDir + "/oldpath"; + const newpath = testDir + "/newpath"; + Deno.mkdirSync(oldpath); + await Deno.rename(oldpath, newpath); + const newPathInfo = Deno.statSync(newpath); + assert(newPathInfo.isDirectory()); + + let caughtErr = false; + let oldPathInfo; + + try { + oldPathInfo = Deno.statSync(oldpath); + } catch (e) { + caughtErr = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtErr); + assertEquals(oldPathInfo, undefined); + } +); diff --git a/cli/js/tests/request_test.ts b/cli/js/tests/request_test.ts new file mode 100644 index 000000000..15e19e285 --- /dev/null +++ b/cli/js/tests/request_test.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function fromInit(): void { + const req = new Request("https://example.com", { + body: "ahoyhoy", + method: "POST", + headers: { + "test-header": "value" + } + }); + + // @ts-ignore + assertEquals("ahoyhoy", req._bodySource); + assertEquals(req.url, "https://example.com"); + assertEquals(req.headers.get("test-header"), "value"); +}); + +unitTest(function fromRequest(): void { + const r = new Request("https://example.com"); + // @ts-ignore + r._bodySource = "ahoyhoy"; + r.headers.set("test-header", "value"); + + const req = new Request(r); + + // @ts-ignore + assertEquals(req._bodySource, r._bodySource); + assertEquals(req.url, r.url); + assertEquals(req.headers.get("test-header"), r.headers.get("test-header")); +}); + +unitTest(async function cloneRequestBodyStream(): Promise<void> { + // hack to get a stream + const stream = new Request("", { body: "a test body" }).body; + const r1 = new Request("https://example.com", { + body: stream + }); + + const r2 = r1.clone(); + + const b1 = await r1.text(); + const b2 = await r2.text(); + + assertEquals(b1, b2); + + // @ts-ignore + assert(r1._bodySource !== r2._bodySource); +}); diff --git a/cli/js/tests/resources_test.ts b/cli/js/tests/resources_test.ts new file mode 100644 index 000000000..84b713a6d --- /dev/null +++ b/cli/js/tests/resources_test.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals } from "./test_util.ts"; + +unitTest(function resourcesStdio(): void { + const res = Deno.resources(); + + assertEquals(res[0], "stdin"); + assertEquals(res[1], "stdout"); + assertEquals(res[2], "stderr"); +}); + +unitTest({ perms: { net: true } }, async function resourcesNet(): Promise< + void +> { + const listener = Deno.listen({ port: 4501 }); + const dialerConn = await Deno.connect({ port: 4501 }); + const listenerConn = await listener.accept(); + + const res = Deno.resources(); + assertEquals( + Object.values(res).filter((r): boolean => r === "tcpListener").length, + 1 + ); + assertEquals( + Object.values(res).filter((r): boolean => r === "tcpStream").length, + 2 + ); + + listenerConn.close(); + dialerConn.close(); + listener.close(); +}); + +unitTest({ perms: { read: true } }, async function resourcesFile(): Promise< + void +> { + const resourcesBefore = Deno.resources(); + const f = await Deno.open("cli/tests/hello.txt"); + const resourcesAfter = Deno.resources(); + f.close(); + + // check that exactly one new resource (file) was added + assertEquals( + Object.keys(resourcesAfter).length, + Object.keys(resourcesBefore).length + 1 + ); + const newRid = +Object.keys(resourcesAfter).find((rid): boolean => { + return !resourcesBefore.hasOwnProperty(rid); + })!; + assertEquals(resourcesAfter[newRid], "fsFile"); +}); diff --git a/cli/js/tests/signal_test.ts b/cli/js/tests/signal_test.ts new file mode 100644 index 000000000..e1d00c669 --- /dev/null +++ b/cli/js/tests/signal_test.ts @@ -0,0 +1,201 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + unitTest, + assert, + assertEquals, + assertThrows, + createResolvable +} from "./test_util.ts"; + +function defer(n: number): Promise<void> { + return new Promise((resolve: () => void, _) => { + setTimeout(resolve, n); + }); +} + +unitTest( + { skip: Deno.build.os !== "win" }, + async function signalsNotImplemented(): Promise<void> { + assertThrows( + () => { + Deno.signal(1); + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.alarm(); // for SIGALRM + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.child(); // for SIGCHLD + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.hungup(); // for SIGHUP + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.interrupt(); // for SIGINT + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.io(); // for SIGIO + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.pipe(); // for SIGPIPE + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.quit(); // for SIGQUIT + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.terminate(); // for SIGTERM + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.userDefined1(); // for SIGUSR1 + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.userDefined2(); // for SIGURS2 + }, + Error, + "not implemented" + ); + assertThrows( + () => { + Deno.signals.windowChange(); // for SIGWINCH + }, + Error, + "not implemented" + ); + } +); + +unitTest( + { skip: Deno.build.os === "win", perms: { run: true, net: true } }, + async function signalStreamTest(): Promise<void> { + const resolvable = createResolvable(); + // This prevents the program from exiting. + const t = setInterval(() => {}, 1000); + + let c = 0; + const sig = Deno.signal(Deno.Signal.SIGUSR1); + setTimeout(async () => { + await defer(20); + for (const _ of Array(3)) { + // Sends SIGUSR1 3 times. + Deno.kill(Deno.pid, Deno.Signal.SIGUSR1); + await defer(20); + } + sig.dispose(); + resolvable.resolve(); + }); + + for await (const _ of sig) { + c += 1; + } + + assertEquals(c, 3); + + clearInterval(t); + // Defer for a moment to allow async op from `setInterval` to resolve; + // for more explanation see `FIXME` in `cli/js/timers.ts::setGlobalTimeout` + await defer(20); + await resolvable; + } +); + +unitTest( + { skip: Deno.build.os === "win", perms: { run: true } }, + async function signalPromiseTest(): Promise<void> { + const resolvable = createResolvable(); + // This prevents the program from exiting. + const t = setInterval(() => {}, 1000); + + const sig = Deno.signal(Deno.Signal.SIGUSR1); + setTimeout(() => { + Deno.kill(Deno.pid, Deno.Signal.SIGUSR1); + resolvable.resolve(); + }, 20); + await sig; + sig.dispose(); + + clearInterval(t); + // Defer for a moment to allow async op from `setInterval` to resolve; + // for more explanation see `FIXME` in `cli/js/timers.ts::setGlobalTimeout` + await defer(20); + await resolvable; + } +); + +unitTest( + { skip: Deno.build.os === "win", perms: { run: true } }, + async function signalShorthandsTest(): Promise<void> { + let s: Deno.SignalStream; + s = Deno.signals.alarm(); // for SIGALRM + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.child(); // for SIGCHLD + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.hungup(); // for SIGHUP + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.interrupt(); // for SIGINT + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.io(); // for SIGIO + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.pipe(); // for SIGPIPE + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.quit(); // for SIGQUIT + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.terminate(); // for SIGTERM + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.userDefined1(); // for SIGUSR1 + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.userDefined2(); // for SIGURS2 + assert(s instanceof Deno.SignalStream); + s.dispose(); + s = Deno.signals.windowChange(); // for SIGWINCH + assert(s instanceof Deno.SignalStream); + s.dispose(); + } +); diff --git a/cli/js/tests/stat_test.ts b/cli/js/tests/stat_test.ts new file mode 100644 index 000000000..0a33901b7 --- /dev/null +++ b/cli/js/tests/stat_test.ts @@ -0,0 +1,229 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +// TODO Add tests for modified, accessed, and created fields once there is a way +// to create temp files. +unitTest({ perms: { read: true } }, async function statSyncSuccess(): Promise< + void +> { + const packageInfo = Deno.statSync("README.md"); + assert(packageInfo.isFile()); + assert(!packageInfo.isSymlink()); + + const modulesInfo = Deno.statSync("cli/tests/symlink_to_subdir"); + assert(modulesInfo.isDirectory()); + assert(!modulesInfo.isSymlink()); + + const testsInfo = Deno.statSync("cli/tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); +}); + +unitTest({ perms: { read: false } }, async function statSyncPerm(): Promise< + void +> { + let caughtError = false; + try { + Deno.statSync("README.md"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function statSyncNotFound(): Promise< + void +> { + let caughtError = false; + let badInfo; + + try { + badInfo = Deno.statSync("bad_file_name"); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.NotFound); + } + + assert(caughtError); + assertEquals(badInfo, undefined); +}); + +unitTest({ perms: { read: true } }, async function lstatSyncSuccess(): Promise< + void +> { + const packageInfo = Deno.lstatSync("README.md"); + assert(packageInfo.isFile()); + assert(!packageInfo.isSymlink()); + + const modulesInfo = Deno.lstatSync("cli/tests/symlink_to_subdir"); + assert(!modulesInfo.isDirectory()); + assert(modulesInfo.isSymlink()); + + const coreInfo = Deno.lstatSync("core"); + assert(coreInfo.isDirectory()); + assert(!coreInfo.isSymlink()); +}); + +unitTest({ perms: { read: false } }, async function lstatSyncPerm(): Promise< + void +> { + let caughtError = false; + try { + Deno.lstatSync("README.md"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function lstatSyncNotFound(): Promise< + void +> { + let caughtError = false; + let badInfo; + + try { + badInfo = Deno.lstatSync("bad_file_name"); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.NotFound); + } + + assert(caughtError); + assertEquals(badInfo, undefined); +}); + +unitTest({ perms: { read: true } }, async function statSuccess(): Promise< + void +> { + const packageInfo = await Deno.stat("README.md"); + assert(packageInfo.isFile()); + assert(!packageInfo.isSymlink()); + + const modulesInfo = await Deno.stat("cli/tests/symlink_to_subdir"); + assert(modulesInfo.isDirectory()); + assert(!modulesInfo.isSymlink()); + + const testsInfo = await Deno.stat("cli/tests"); + assert(testsInfo.isDirectory()); + assert(!testsInfo.isSymlink()); +}); + +unitTest({ perms: { read: false } }, async function statPerm(): Promise<void> { + let caughtError = false; + try { + await Deno.stat("README.md"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function statNotFound(): Promise< + void +> { + let caughtError = false; + let badInfo; + + try { + badInfo = await Deno.stat("bad_file_name"); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.NotFound); + } + + assert(caughtError); + assertEquals(badInfo, undefined); +}); + +unitTest({ perms: { read: true } }, async function lstatSuccess(): Promise< + void +> { + const packageInfo = await Deno.lstat("README.md"); + assert(packageInfo.isFile()); + assert(!packageInfo.isSymlink()); + + const modulesInfo = await Deno.lstat("cli/tests/symlink_to_subdir"); + assert(!modulesInfo.isDirectory()); + assert(modulesInfo.isSymlink()); + + const coreInfo = await Deno.lstat("core"); + assert(coreInfo.isDirectory()); + assert(!coreInfo.isSymlink()); +}); + +unitTest({ perms: { read: false } }, async function lstatPerm(): Promise<void> { + let caughtError = false; + try { + await Deno.lstat("README.md"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, async function lstatNotFound(): Promise< + void +> { + let caughtError = false; + let badInfo; + + try { + badInfo = await Deno.lstat("bad_file_name"); + } catch (err) { + caughtError = true; + assert(err instanceof Deno.errors.NotFound); + } + + assert(caughtError); + assertEquals(badInfo, undefined); +}); + +unitTest( + { skip: Deno.build.os !== "win", perms: { read: true, write: true } }, + async function statNoUnixFields(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + const s = Deno.statSync(filename); + assert(s.dev === null); + assert(s.ino === null); + assert(s.mode === null); + assert(s.nlink === null); + assert(s.uid === null); + assert(s.gid === null); + assert(s.rdev === null); + assert(s.blksize === null); + assert(s.blocks === null); + } +); + +unitTest( + { skip: Deno.build.os === "win", perms: { read: true, write: true } }, + async function statUnixFields(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const filename = tempDir + "/test.txt"; + const filename2 = tempDir + "/test2.txt"; + Deno.writeFileSync(filename, data, { mode: 0o666 }); + // Create a link + Deno.linkSync(filename, filename2); + const s = Deno.statSync(filename); + assert(s.dev !== null); + assert(s.ino !== null); + assertEquals(s.mode! & 0o666, 0o666); + assertEquals(s.nlink, 2); + assert(s.uid !== null); + assert(s.gid !== null); + assert(s.rdev !== null); + assert(s.blksize !== null); + assert(s.blocks !== null); + } +); diff --git a/cli/js/tests/symbols_test.ts b/cli/js/tests/symbols_test.ts new file mode 100644 index 000000000..ad05e9a70 --- /dev/null +++ b/cli/js/tests/symbols_test.ts @@ -0,0 +1,7 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function symbolsExists(): void { + assert("internal" in Deno.symbols); + assert("customInspect" in Deno.symbols); +}); diff --git a/cli/js/tests/symlink_test.ts b/cli/js/tests/symlink_test.ts new file mode 100644 index 000000000..221960bb0 --- /dev/null +++ b/cli/js/tests/symlink_test.ts @@ -0,0 +1,85 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function symlinkSyncSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const oldname = testDir + "/oldname"; + const newname = testDir + "/newname"; + Deno.mkdirSync(oldname); + let errOnWindows; + // Just for now, until we implement symlink for Windows. + try { + Deno.symlinkSync(oldname, newname); + } catch (e) { + errOnWindows = e; + } + if (errOnWindows) { + assertEquals(Deno.build.os, "win"); + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const newNameInfoLStat = Deno.lstatSync(newname); + const newNameInfoStat = Deno.statSync(newname); + assert(newNameInfoLStat.isSymlink()); + assert(newNameInfoStat.isDirectory()); + } + } +); + +unitTest(function symlinkSyncPerm(): void { + let err; + try { + Deno.symlinkSync("oldbaddir", "newbaddir"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +// Just for now, until we implement symlink for Windows. +// Symlink with type should succeed on other platforms with type ignored +unitTest( + { perms: { write: true } }, + function symlinkSyncNotImplemented(): void { + const testDir = Deno.makeTempDirSync(); + const oldname = testDir + "/oldname"; + const newname = testDir + "/newname"; + let err; + try { + Deno.symlinkSync(oldname, newname, "dir"); + } catch (e) { + err = e; + } + if (err) { + assertEquals(Deno.build.os, "win"); + assertEquals(err.message, "Not implemented"); + } + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function symlinkSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const oldname = testDir + "/oldname"; + const newname = testDir + "/newname"; + Deno.mkdirSync(oldname); + let errOnWindows; + // Just for now, until we implement symlink for Windows. + try { + await Deno.symlink(oldname, newname); + } catch (e) { + errOnWindows = e; + } + if (errOnWindows) { + assertEquals(errOnWindows.message, "Not implemented"); + } else { + const newNameInfoLStat = Deno.lstatSync(newname); + const newNameInfoStat = Deno.statSync(newname); + assert(newNameInfoLStat.isSymlink()); + assert(newNameInfoStat.isDirectory()); + } + } +); diff --git a/cli/js/tests/test_util.ts b/cli/js/tests/test_util.ts new file mode 100644 index 000000000..c8f28437d --- /dev/null +++ b/cli/js/tests/test_util.ts @@ -0,0 +1,417 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +// +// We want to test many ops in deno which have different behavior depending on +// the permissions set. These tests can specify which permissions they expect, +// which appends a special string like "permW1N0" to the end of the test name. +// Here we run several copies of deno with different permissions, filtering the +// tests by the special string. permW1N0 means allow-write but not allow-net. +// See tools/unit_tests.py for more details. + +import { readLines } from "../../../std/io/bufio.ts"; +import { assert, assertEquals } from "../../../std/testing/asserts.ts"; +export { + assert, + assertThrows, + assertEquals, + assertMatch, + assertNotEquals, + assertStrictEq, + assertStrContains, + unreachable, + fail +} from "../../../std/testing/asserts.ts"; + +interface TestPermissions { + read?: boolean; + write?: boolean; + net?: boolean; + env?: boolean; + run?: boolean; + plugin?: boolean; + hrtime?: boolean; +} + +export interface Permissions { + read: boolean; + write: boolean; + net: boolean; + env: boolean; + run: boolean; + plugin: boolean; + hrtime: boolean; +} + +const isGranted = async (name: Deno.PermissionName): Promise<boolean> => + (await Deno.permissions.query({ name })).state === "granted"; + +async function getProcessPermissions(): Promise<Permissions> { + return { + run: await isGranted("run"), + read: await isGranted("read"), + write: await isGranted("write"), + net: await isGranted("net"), + env: await isGranted("env"), + plugin: await isGranted("plugin"), + hrtime: await isGranted("hrtime") + }; +} + +const processPerms = await getProcessPermissions(); + +function permissionsMatch( + processPerms: Permissions, + requiredPerms: Permissions +): boolean { + for (const permName in processPerms) { + if ( + processPerms[permName as keyof Permissions] !== + requiredPerms[permName as keyof Permissions] + ) { + return false; + } + } + + return true; +} + +export const permissionCombinations: Map<string, Permissions> = new Map(); + +function permToString(perms: Permissions): string { + const r = perms.read ? 1 : 0; + const w = perms.write ? 1 : 0; + const n = perms.net ? 1 : 0; + const e = perms.env ? 1 : 0; + const u = perms.run ? 1 : 0; + const p = perms.plugin ? 1 : 0; + const h = perms.hrtime ? 1 : 0; + return `permR${r}W${w}N${n}E${e}U${u}P${p}H${h}`; +} + +function registerPermCombination(perms: Permissions): void { + const key = permToString(perms); + if (!permissionCombinations.has(key)) { + permissionCombinations.set(key, perms); + } +} + +function normalizeTestPermissions(perms: TestPermissions): Permissions { + return { + read: !!perms.read, + write: !!perms.write, + net: !!perms.net, + run: !!perms.run, + env: !!perms.env, + plugin: !!perms.plugin, + hrtime: !!perms.hrtime + }; +} + +// Wrap `TestFunction` in additional assertion that makes sure +// the test case does not leak async "ops" - ie. number of async +// completed ops after the test is the same as number of dispatched +// ops. Note that "unref" ops are ignored since in nature that are +// optional. +function assertOps(fn: Deno.TestFunction): Deno.TestFunction { + return async function asyncOpSanitizer(): Promise<void> { + const pre = Deno.metrics(); + await fn(); + const post = Deno.metrics(); + // We're checking diff because one might spawn HTTP server in the background + // that will be a pending async op before test starts. + assertEquals( + post.opsDispatchedAsync - pre.opsDispatchedAsync, + post.opsCompletedAsync - pre.opsCompletedAsync, + `Test case is leaking async ops. + Before: + - dispatched: ${pre.opsDispatchedAsync} + - completed: ${pre.opsCompletedAsync} + After: + - dispatched: ${post.opsDispatchedAsync} + - completed: ${post.opsCompletedAsync}` + ); + }; +} + +// Wrap `TestFunction` in additional assertion that makes sure +// the test case does not "leak" resources - ie. resource table after +// the test has exactly the same contents as before the test. +function assertResources(fn: Deno.TestFunction): Deno.TestFunction { + return async function resourceSanitizer(): Promise<void> { + const pre = Deno.resources(); + await fn(); + const post = Deno.resources(); + const msg = `Test case is leaking resources. + Before: ${JSON.stringify(pre, null, 2)} + After: ${JSON.stringify(post, null, 2)}`; + assertEquals(pre, post, msg); + }; +} + +interface UnitTestOptions { + skip?: boolean; + perms?: TestPermissions; +} + +export function unitTest(fn: Deno.TestFunction): void; +export function unitTest(options: UnitTestOptions, fn: Deno.TestFunction): void; +export function unitTest( + optionsOrFn: UnitTestOptions | Deno.TestFunction, + maybeFn?: Deno.TestFunction +): void { + assert(optionsOrFn, "At least one argument is required"); + + let options: UnitTestOptions; + let name: string; + let fn: Deno.TestFunction; + + if (typeof optionsOrFn === "function") { + options = {}; + fn = optionsOrFn; + name = fn.name; + assert(name, "Missing test function name"); + } else { + options = optionsOrFn; + assert(maybeFn, "Missing test function definition"); + assert( + typeof maybeFn === "function", + "Second argument should be test function definition" + ); + fn = maybeFn; + name = fn.name; + assert(name, "Missing test function name"); + } + + if (options.skip) { + return; + } + + const normalizedPerms = normalizeTestPermissions(options.perms || {}); + registerPermCombination(normalizedPerms); + if (!permissionsMatch(processPerms, normalizedPerms)) { + return; + } + + const testDefinition: Deno.TestDefinition = { + name, + fn: assertResources(assertOps(fn)) + }; + Deno.test(testDefinition); +} + +function extractNumber(re: RegExp, str: string): number | undefined { + const match = str.match(re); + + if (match) { + return Number.parseInt(match[1]); + } +} + +export async function parseUnitTestOutput( + reader: Deno.Reader, + print: boolean +): Promise<{ actual?: number; expected?: number; resultOutput?: string }> { + let expected, actual, result; + + for await (const line of readLines(reader)) { + if (!expected) { + // expect "running 30 tests" + expected = extractNumber(/running (\d+) tests/, line); + } else if (line.indexOf("test result:") !== -1) { + result = line; + } + + if (print) { + console.log(line); + } + } + + // Check that the number of expected tests equals what was reported at the + // bottom. + if (result) { + // result should be a string like this: + // "test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; ..." + actual = extractNumber(/(\d+) passed/, result); + } + + return { actual, expected, resultOutput: result }; +} + +export interface ResolvableMethods<T> { + resolve: (value?: T | PromiseLike<T>) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void; +} + +export type Resolvable<T> = Promise<T> & ResolvableMethods<T>; + +export function createResolvable<T>(): Resolvable<T> { + let methods: ResolvableMethods<T>; + const promise = new Promise<T>((resolve, reject): void => { + methods = { resolve, reject }; + }); + // TypeScript doesn't know that the Promise callback occurs synchronously + // therefore use of not null assertion (`!`) + return Object.assign(promise, methods!) as Resolvable<T>; +} + +unitTest(function permissionsMatches(): void { + assert( + permissionsMatch( + { + read: true, + write: false, + net: false, + env: false, + run: false, + plugin: false, + hrtime: false + }, + normalizeTestPermissions({ read: true }) + ) + ); + + assert( + permissionsMatch( + { + read: false, + write: false, + net: false, + env: false, + run: false, + plugin: false, + hrtime: false + }, + normalizeTestPermissions({}) + ) + ); + + assertEquals( + permissionsMatch( + { + read: false, + write: true, + net: true, + env: true, + run: true, + plugin: true, + hrtime: true + }, + normalizeTestPermissions({ read: true }) + ), + false + ); + + assertEquals( + permissionsMatch( + { + read: true, + write: false, + net: true, + env: false, + run: false, + plugin: false, + hrtime: false + }, + normalizeTestPermissions({ read: true }) + ), + false + ); + + assert( + permissionsMatch( + { + read: true, + write: true, + net: true, + env: true, + run: true, + plugin: true, + hrtime: true + }, + { + read: true, + write: true, + net: true, + env: true, + run: true, + plugin: true, + hrtime: true + } + ) + ); +}); + +unitTest( + { perms: { read: true } }, + async function parsingUnitTestOutput(): Promise<void> { + const cwd = Deno.cwd(); + const testDataPath = `${cwd}/tools/testdata/`; + + let result; + + // This is an example of a successful unit test output. + const f1 = await Deno.open(`${testDataPath}/unit_test_output1.txt`); + result = await parseUnitTestOutput(f1, false); + assertEquals(result.actual, 96); + assertEquals(result.expected, 96); + f1.close(); + + // This is an example of a silently dying unit test. + const f2 = await Deno.open(`${testDataPath}/unit_test_output2.txt`); + result = await parseUnitTestOutput(f2, false); + assertEquals(result.actual, undefined); + assertEquals(result.expected, 96); + f2.close(); + + // This is an example of compiling before successful unit tests. + const f3 = await Deno.open(`${testDataPath}/unit_test_output3.txt`); + result = await parseUnitTestOutput(f3, false); + assertEquals(result.actual, 96); + assertEquals(result.expected, 96); + f3.close(); + + // Check what happens on empty output. + const f = new Deno.Buffer(new TextEncoder().encode("\n\n\n")); + result = await parseUnitTestOutput(f, false); + assertEquals(result.actual, undefined); + assertEquals(result.expected, undefined); + } +); + +/* + * Ensure all unit test files (e.g. xxx_test.ts) are present as imports in + * cli/js/tests/unit_tests.ts as it is easy to miss this out + */ +unitTest( + { perms: { read: true } }, + async function assertAllUnitTestFilesImported(): Promise<void> { + const directoryTestFiles = Deno.readdirSync("./cli/js/tests/") + .map(k => k.name) + .filter( + file => + file!.endsWith(".ts") && + !file!.endsWith("unit_tests.ts") && + !file!.endsWith("test_util.ts") && + !file!.endsWith("unit_test_runner.ts") + ); + const unitTestsFile: Uint8Array = Deno.readFileSync( + "./cli/js/tests/unit_tests.ts" + ); + const importLines = new TextDecoder("utf-8") + .decode(unitTestsFile) + .split("\n") + .filter(line => line.startsWith("import")); + const importedTestFiles = importLines.map( + relativeFilePath => relativeFilePath.match(/\/([^\/]+)";/)![1] + ); + + directoryTestFiles.forEach(dirFile => { + if (!importedTestFiles.includes(dirFile!)) { + throw new Error( + "cil/js/tests/unit_tests.ts is missing import of test file: cli/js/" + + dirFile + ); + } + }); + } +); diff --git a/cli/js/tests/testing_test.ts b/cli/js/tests/testing_test.ts new file mode 100644 index 000000000..b47eb03e2 --- /dev/null +++ b/cli/js/tests/testing_test.ts @@ -0,0 +1,37 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { assertThrows, unitTest } from "./test_util.ts"; + +unitTest(function testFnOverloading(): void { + // just verifying that you can use this test definition syntax + Deno.test("test fn overloading", (): void => {}); +}); + +unitTest(function nameOfTestCaseCantBeEmpty(): void { + assertThrows( + () => { + Deno.test("", () => {}); + }, + Error, + "The name of test case can't be empty" + ); + assertThrows( + () => { + Deno.test({ + name: "", + fn: () => {} + }); + }, + Error, + "The name of test case can't be empty" + ); +}); + +unitTest(function testFnCantBeAnonymous(): void { + assertThrows( + () => { + Deno.test(function() {}); + }, + Error, + "Test function can't be anonymous" + ); +}); diff --git a/cli/js/tests/text_encoding_test.ts b/cli/js/tests/text_encoding_test.ts new file mode 100644 index 000000000..e85655feb --- /dev/null +++ b/cli/js/tests/text_encoding_test.ts @@ -0,0 +1,193 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function btoaSuccess(): void { + const text = "hello world"; + const encoded = btoa(text); + assertEquals(encoded, "aGVsbG8gd29ybGQ="); +}); + +unitTest(function atobSuccess(): void { + const encoded = "aGVsbG8gd29ybGQ="; + const decoded = atob(encoded); + assertEquals(decoded, "hello world"); +}); + +unitTest(function atobWithAsciiWhitespace(): void { + const encodedList = [ + " aGVsbG8gd29ybGQ=", + " aGVsbG8gd29ybGQ=", + "aGVsbG8gd29ybGQ= ", + "aGVsbG8gd29ybGQ=\n", + "aGVsbG\t8gd29ybGQ=", + `aGVsbG\t8g + d29ybGQ=` + ]; + + for (const encoded of encodedList) { + const decoded = atob(encoded); + assertEquals(decoded, "hello world"); + } +}); + +unitTest(function atobThrows(): void { + let threw = false; + try { + atob("aGVsbG8gd29ybGQ=="); + } catch (e) { + threw = true; + } + assert(threw); +}); + +unitTest(function atobThrows2(): void { + let threw = false; + try { + atob("aGVsbG8gd29ybGQ==="); + } catch (e) { + threw = true; + } + assert(threw); +}); + +unitTest(function btoaFailed(): void { + const text = "你好"; + let err; + try { + btoa(text); + } catch (e) { + err = e; + } + assert(!!err); + assert(err instanceof TypeError); +}); + +unitTest(function textDecoder2(): void { + // prettier-ignore + const fixture = new Uint8Array([ + 0xf0, 0x9d, 0x93, 0xbd, + 0xf0, 0x9d, 0x93, 0xae, + 0xf0, 0x9d, 0x94, 0x81, + 0xf0, 0x9d, 0x93, 0xbd + ]); + const decoder = new TextDecoder(); + assertEquals(decoder.decode(fixture), "𝓽𝓮𝔁𝓽"); +}); + +unitTest(function textDecoderIgnoreBOM(): void { + // prettier-ignore + const fixture = new Uint8Array([ + 0xef, 0xbb, 0xbf, + 0xf0, 0x9d, 0x93, 0xbd, + 0xf0, 0x9d, 0x93, 0xae, + 0xf0, 0x9d, 0x94, 0x81, + 0xf0, 0x9d, 0x93, 0xbd + ]); + const decoder = new TextDecoder("utf-8", { ignoreBOM: true }); + assertEquals(decoder.decode(fixture), "𝓽𝓮𝔁𝓽"); +}); + +unitTest(function textDecoderNotBOM(): void { + // prettier-ignore + const fixture = new Uint8Array([ + 0xef, 0xbb, 0x89, + 0xf0, 0x9d, 0x93, 0xbd, + 0xf0, 0x9d, 0x93, 0xae, + 0xf0, 0x9d, 0x94, 0x81, + 0xf0, 0x9d, 0x93, 0xbd + ]); + const decoder = new TextDecoder("utf-8", { ignoreBOM: true }); + assertEquals(decoder.decode(fixture), "ﻉ𝓽𝓮𝔁𝓽"); +}); + +unitTest(function textDecoderASCII(): void { + const fixture = new Uint8Array([0x89, 0x95, 0x9f, 0xbf]); + const decoder = new TextDecoder("ascii"); + assertEquals(decoder.decode(fixture), "‰•Ÿ¿"); +}); + +unitTest(function textDecoderErrorEncoding(): void { + let didThrow = false; + try { + new TextDecoder("foo"); + } catch (e) { + didThrow = true; + assertEquals(e.message, "The encoding label provided ('foo') is invalid."); + } + assert(didThrow); +}); + +unitTest(function textEncoder(): void { + const fixture = "𝓽𝓮𝔁𝓽"; + const encoder = new TextEncoder(); + // prettier-ignore + assertEquals(Array.from(encoder.encode(fixture)), [ + 0xf0, 0x9d, 0x93, 0xbd, + 0xf0, 0x9d, 0x93, 0xae, + 0xf0, 0x9d, 0x94, 0x81, + 0xf0, 0x9d, 0x93, 0xbd + ]); +}); + +unitTest(function textEncodeInto(): void { + const fixture = "text"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(5); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 4); + assertEquals(result.written, 4); + // prettier-ignore + assertEquals(Array.from(bytes), [ + 0x74, 0x65, 0x78, 0x74, 0x00, + ]); +}); + +unitTest(function textEncodeInto2(): void { + const fixture = "𝓽𝓮𝔁𝓽"; + const encoder = new TextEncoder(); + const bytes = new Uint8Array(17); + const result = encoder.encodeInto(fixture, bytes); + assertEquals(result.read, 8); + assertEquals(result.written, 16); + // prettier-ignore + assertEquals(Array.from(bytes), [ + 0xf0, 0x9d, 0x93, 0xbd, + 0xf0, 0x9d, 0x93, 0xae, + 0xf0, 0x9d, 0x94, 0x81, + 0xf0, 0x9d, 0x93, 0xbd, 0x00, + ]); +}); + +unitTest(function textDecoderSharedUint8Array(): void { + const ab = new SharedArrayBuffer(6); + const dataView = new DataView(ab); + const charCodeA = "A".charCodeAt(0); + for (let i = 0; i < ab.byteLength; i++) { + dataView.setUint8(i, charCodeA + i); + } + const ui8 = new Uint8Array(ab); + const decoder = new TextDecoder(); + const actual = decoder.decode(ui8); + assertEquals(actual, "ABCDEF"); +}); + +unitTest(function textDecoderSharedInt32Array(): void { + const ab = new SharedArrayBuffer(8); + const dataView = new DataView(ab); + const charCodeA = "A".charCodeAt(0); + for (let i = 0; i < ab.byteLength; i++) { + dataView.setUint8(i, charCodeA + i); + } + const i32 = new Int32Array(ab); + const decoder = new TextDecoder(); + const actual = decoder.decode(i32); + assertEquals(actual, "ABCDEFGH"); +}); + +unitTest(function toStringShouldBeWebCompatibility(): void { + const encoder = new TextEncoder(); + assertEquals(encoder.toString(), "[object TextEncoder]"); + + const decoder = new TextDecoder(); + assertEquals(decoder.toString(), "[object TextDecoder]"); +}); diff --git a/cli/js/tests/timers_test.ts b/cli/js/tests/timers_test.ts new file mode 100644 index 000000000..429e42692 --- /dev/null +++ b/cli/js/tests/timers_test.ts @@ -0,0 +1,353 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + unitTest, + createResolvable, + assert, + assertEquals, + assertNotEquals +} from "./test_util.ts"; + +function deferred(): { + promise: Promise<{}>; + resolve: (value?: {} | PromiseLike<{}>) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + reject: (reason?: any) => void; +} { + let resolve: (value?: {} | PromiseLike<{}>) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let reject: ((reason?: any) => void) | undefined = undefined; + const promise = new Promise<{}>((res, rej): void => { + resolve = res; + reject = rej; + }); + return { + promise, + resolve: resolve!, + reject: reject! + }; +} + +async function waitForMs(ms: number): Promise<number> { + return new Promise((resolve: () => void): number => setTimeout(resolve, ms)); +} + +unitTest(async function timeoutSuccess(): Promise<void> { + const { promise, resolve } = deferred(); + let count = 0; + setTimeout((): void => { + count++; + resolve(); + }, 500); + await promise; + // count should increment + assertEquals(count, 1); +}); + +unitTest(async function timeoutArgs(): Promise<void> { + const { promise, resolve } = deferred(); + const arg = 1; + setTimeout( + (a, b, c): void => { + assertEquals(a, arg); + assertEquals(b, arg.toString()); + assertEquals(c, [arg]); + resolve(); + }, + 10, + arg, + arg.toString(), + [arg] + ); + await promise; +}); + +unitTest(async function timeoutCancelSuccess(): Promise<void> { + let count = 0; + const id = setTimeout((): void => { + count++; + }, 1); + // Cancelled, count should not increment + clearTimeout(id); + await waitForMs(600); + assertEquals(count, 0); +}); + +unitTest(async function timeoutCancelMultiple(): Promise<void> { + function uncalled(): never { + throw new Error("This function should not be called."); + } + + // Set timers and cancel them in the same order. + const t1 = setTimeout(uncalled, 10); + const t2 = setTimeout(uncalled, 10); + const t3 = setTimeout(uncalled, 10); + clearTimeout(t1); + clearTimeout(t2); + clearTimeout(t3); + + // Set timers and cancel them in reverse order. + const t4 = setTimeout(uncalled, 20); + const t5 = setTimeout(uncalled, 20); + const t6 = setTimeout(uncalled, 20); + clearTimeout(t6); + clearTimeout(t5); + clearTimeout(t4); + + // Sleep until we're certain that the cancelled timers aren't gonna fire. + await waitForMs(50); +}); + +unitTest(async function timeoutCancelInvalidSilentFail(): Promise<void> { + // Expect no panic + const { promise, resolve } = deferred(); + let count = 0; + const id = setTimeout((): void => { + count++; + // Should have no effect + clearTimeout(id); + resolve(); + }, 500); + await promise; + assertEquals(count, 1); + + // Should silently fail (no panic) + clearTimeout(2147483647); +}); + +unitTest(async function intervalSuccess(): Promise<void> { + const { promise, resolve } = deferred(); + let count = 0; + const id = setInterval((): void => { + count++; + clearInterval(id); + resolve(); + }, 100); + await promise; + // Clear interval + clearInterval(id); + // count should increment twice + assertEquals(count, 1); +}); + +unitTest(async function intervalCancelSuccess(): Promise<void> { + let count = 0; + const id = setInterval((): void => { + count++; + }, 1); + clearInterval(id); + await waitForMs(500); + assertEquals(count, 0); +}); + +unitTest(async function intervalOrdering(): Promise<void> { + const timers: number[] = []; + let timeouts = 0; + function onTimeout(): void { + ++timeouts; + for (let i = 1; i < timers.length; i++) { + clearTimeout(timers[i]); + } + } + for (let i = 0; i < 10; i++) { + timers[i] = setTimeout(onTimeout, 1); + } + await waitForMs(500); + assertEquals(timeouts, 1); +}); + +unitTest(async function intervalCancelInvalidSilentFail(): Promise<void> { + // Should silently fail (no panic) + clearInterval(2147483647); +}); + +unitTest(async function fireCallbackImmediatelyWhenDelayOverMaxValue(): Promise< + void +> { + let count = 0; + setTimeout((): void => { + count++; + }, 2 ** 31); + await waitForMs(1); + assertEquals(count, 1); +}); + +unitTest(async function timeoutCallbackThis(): Promise<void> { + const { promise, resolve } = deferred(); + const obj = { + foo(): void { + assertEquals(this, window); + resolve(); + } + }; + setTimeout(obj.foo, 1); + await promise; +}); + +unitTest(async function timeoutBindThis(): Promise<void> { + const thisCheckPassed = [null, undefined, window, globalThis]; + + const thisCheckFailed = [ + 0, + "", + true, + false, + {}, + [], + "foo", + (): void => {}, + Object.prototype + ]; + + for (const thisArg of thisCheckPassed) { + const resolvable = createResolvable(); + let hasThrown = 0; + try { + setTimeout.call(thisArg, () => resolvable.resolve(), 1); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + await resolvable; + assertEquals(hasThrown, 1); + } + + for (const thisArg of thisCheckFailed) { + let hasThrown = 0; + try { + setTimeout.call(thisArg, () => {}, 1); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + } +}); + +unitTest(async function clearTimeoutShouldConvertToNumber(): Promise<void> { + let called = false; + const obj = { + valueOf(): number { + called = true; + return 1; + } + }; + clearTimeout((obj as unknown) as number); + assert(called); +}); + +unitTest(function setTimeoutShouldThrowWithBigint(): void { + let hasThrown = 0; + try { + setTimeout((): void => {}, (1n as unknown) as number); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); +}); + +unitTest(function clearTimeoutShouldThrowWithBigint(): void { + let hasThrown = 0; + try { + clearTimeout((1n as unknown) as number); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); +}); + +unitTest(function testFunctionName(): void { + assertEquals(clearTimeout.name, "clearTimeout"); + assertEquals(clearInterval.name, "clearInterval"); +}); + +unitTest(function testFunctionParamsLength(): void { + assertEquals(setTimeout.length, 1); + assertEquals(setInterval.length, 1); + assertEquals(clearTimeout.length, 0); + assertEquals(clearInterval.length, 0); +}); + +unitTest(function clearTimeoutAndClearIntervalNotBeEquals(): void { + assertNotEquals(clearTimeout, clearInterval); +}); + +unitTest(async function timerMaxCpuBug(): Promise<void> { + // There was a bug where clearing a timeout would cause Deno to use 100% CPU. + clearTimeout(setTimeout(() => {}, 1000)); + // We can check this by counting how many ops have triggered in the interim. + // Certainly less than 10 ops should have been dispatched in next 100 ms. + const { opsDispatched } = Deno.metrics(); + await waitForMs(100); + const opsDispatched_ = Deno.metrics().opsDispatched; + assert(opsDispatched_ - opsDispatched < 10); +}); + +unitTest(async function timerBasicMicrotaskOrdering(): Promise<void> { + let s = ""; + let count = 0; + const { promise, resolve } = deferred(); + setTimeout(() => { + Promise.resolve().then(() => { + count++; + s += "de"; + if (count === 2) { + resolve(); + } + }); + }); + setTimeout(() => { + count++; + s += "no"; + if (count === 2) { + resolve(); + } + }); + await promise; + assertEquals(s, "deno"); +}); + +unitTest(async function timerNestedMicrotaskOrdering(): Promise<void> { + let s = ""; + const { promise, resolve } = deferred(); + s += "0"; + setTimeout(() => { + s += "4"; + setTimeout(() => (s += "8")); + Promise.resolve().then(() => { + setTimeout(() => { + s += "9"; + resolve(); + }); + }); + }); + setTimeout(() => (s += "5")); + Promise.resolve().then(() => (s += "2")); + Promise.resolve().then(() => + setTimeout(() => { + s += "6"; + Promise.resolve().then(() => (s += "7")); + }) + ); + Promise.resolve().then(() => Promise.resolve().then(() => (s += "3"))); + s += "1"; + await promise; + assertEquals(s, "0123456789"); +}); diff --git a/cli/js/tests/tls_test.ts b/cli/js/tests/tls_test.ts new file mode 100644 index 000000000..b405a94dd --- /dev/null +++ b/cli/js/tests/tls_test.ts @@ -0,0 +1,214 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { + assert, + assertEquals, + createResolvable, + unitTest +} from "./test_util.ts"; +import { BufWriter, BufReader } from "../../../std/io/bufio.ts"; +import { TextProtoReader } from "../../../std/textproto/mod.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +unitTest(async function connectTLSNoPerm(): Promise<void> { + let err; + try { + await Deno.connectTLS({ hostname: "github.com", port: 443 }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest(async function connectTLSCertFileNoReadPerm(): Promise<void> { + let err; + try { + await Deno.connectTLS({ + hostname: "github.com", + port: 443, + certFile: "cli/tests/tls/RootCA.crt" + }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest( + { perms: { read: true, net: true } }, + async function listenTLSNonExistentCertKeyFiles(): Promise<void> { + let err; + const options = { + hostname: "localhost", + port: 4500, + certFile: "cli/tests/tls/localhost.crt", + keyFile: "cli/tests/tls/localhost.key" + }; + + try { + Deno.listenTLS({ + ...options, + certFile: "./non/existent/file" + }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + + try { + Deno.listenTLS({ + ...options, + keyFile: "./non/existent/file" + }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.NotFound); + } +); + +unitTest( + { perms: { net: true } }, + async function listenTLSNoReadPerm(): Promise<void> { + let err; + try { + Deno.listenTLS({ + hostname: "localhost", + port: 4500, + certFile: "cli/tests/tls/localhost.crt", + keyFile: "cli/tests/tls/localhost.key" + }); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); + } +); + +unitTest( + { + perms: { read: true, write: true, net: true } + }, + async function listenTLSEmptyKeyFile(): Promise<void> { + let err; + const options = { + hostname: "localhost", + port: 4500, + certFile: "cli/tests/tls/localhost.crt", + keyFile: "cli/tests/tls/localhost.key" + }; + + const testDir = Deno.makeTempDirSync(); + const keyFilename = testDir + "/key.pem"; + Deno.writeFileSync(keyFilename, new Uint8Array([]), { + mode: 0o666 + }); + + try { + Deno.listenTLS({ + ...options, + keyFile: keyFilename + }); + } catch (e) { + err = e; + } + assert(err instanceof Error); + } +); + +unitTest( + { perms: { read: true, write: true, net: true } }, + async function listenTLSEmptyCertFile(): Promise<void> { + let err; + const options = { + hostname: "localhost", + port: 4500, + certFile: "cli/tests/tls/localhost.crt", + keyFile: "cli/tests/tls/localhost.key" + }; + + const testDir = Deno.makeTempDirSync(); + const certFilename = testDir + "/cert.crt"; + Deno.writeFileSync(certFilename, new Uint8Array([]), { + mode: 0o666 + }); + + try { + Deno.listenTLS({ + ...options, + certFile: certFilename + }); + } catch (e) { + err = e; + } + assert(err instanceof Error); + } +); + +unitTest( + { perms: { read: true, net: true } }, + async function dialAndListenTLS(): Promise<void> { + const resolvable = createResolvable(); + const hostname = "localhost"; + const port = 4500; + + const listener = Deno.listenTLS({ + hostname, + port, + certFile: "cli/tests/tls/localhost.crt", + keyFile: "cli/tests/tls/localhost.key" + }); + + const response = encoder.encode( + "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n" + ); + + listener.accept().then( + async (conn): Promise<void> => { + assert(conn.remoteAddr != null); + assert(conn.localAddr != null); + await conn.write(response); + // TODO(bartlomieju): this might be a bug + setTimeout(() => { + conn.close(); + resolvable.resolve(); + }, 0); + } + ); + + const conn = await Deno.connectTLS({ + hostname, + port, + certFile: "cli/tests/tls/RootCA.pem" + }); + assert(conn.rid > 0); + const w = new BufWriter(conn); + const r = new BufReader(conn); + const body = `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`; + const writeResult = await w.write(encoder.encode(body)); + assertEquals(body.length, writeResult); + await w.flush(); + const tpr = new TextProtoReader(r); + const statusLine = await tpr.readLine(); + assert(statusLine !== Deno.EOF, `line must be read: ${String(statusLine)}`); + const m = statusLine.match(/^(.+?) (.+?) (.+?)$/); + assert(m !== null, "must be matched"); + const [_, proto, status, ok] = m; + assertEquals(proto, "HTTP/1.1"); + assertEquals(status, "200"); + assertEquals(ok, "OK"); + const headers = await tpr.readMIMEHeader(); + assert(headers !== Deno.EOF); + const contentLength = parseInt(headers.get("content-length")!); + const bodyBuf = new Uint8Array(contentLength); + await r.readFull(bodyBuf); + assertEquals(decoder.decode(bodyBuf), "Hello World\n"); + conn.close(); + listener.close(); + await resolvable; + } +); diff --git a/cli/js/tests/truncate_test.ts b/cli/js/tests/truncate_test.ts new file mode 100644 index 000000000..3fd85c990 --- /dev/null +++ b/cli/js/tests/truncate_test.ts @@ -0,0 +1,80 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals, assert } from "./test_util.ts"; + +function readDataSync(name: string): string { + const data = Deno.readFileSync(name); + const decoder = new TextDecoder("utf-8"); + const text = decoder.decode(data); + return text; +} + +async function readData(name: string): Promise<string> { + const data = await Deno.readFile(name); + const decoder = new TextDecoder("utf-8"); + const text = decoder.decode(data); + return text; +} + +unitTest( + { perms: { read: true, write: true } }, + function truncateSyncSuccess(): void { + const enc = new TextEncoder(); + const d = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test_truncateSync.txt"; + Deno.writeFileSync(filename, d); + Deno.truncateSync(filename, 20); + let data = readDataSync(filename); + assertEquals(data.length, 20); + Deno.truncateSync(filename, 5); + data = readDataSync(filename); + assertEquals(data.length, 5); + Deno.truncateSync(filename, -5); + data = readDataSync(filename); + assertEquals(data.length, 0); + Deno.removeSync(filename); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function truncateSuccess(): Promise<void> { + const enc = new TextEncoder(); + const d = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test_truncate.txt"; + await Deno.writeFile(filename, d); + await Deno.truncate(filename, 20); + let data = await readData(filename); + assertEquals(data.length, 20); + await Deno.truncate(filename, 5); + data = await readData(filename); + assertEquals(data.length, 5); + await Deno.truncate(filename, -5); + data = await readData(filename); + assertEquals(data.length, 0); + await Deno.remove(filename); + } +); + +unitTest({ perms: { write: false } }, function truncateSyncPerm(): void { + let err; + try { + Deno.mkdirSync("/test_truncateSyncPermission.txt"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); + +unitTest({ perms: { write: false } }, async function truncatePerm(): Promise< + void +> { + let err; + try { + await Deno.mkdir("/test_truncatePermission.txt"); + } catch (e) { + err = e; + } + assert(err instanceof Deno.errors.PermissionDenied); + assertEquals(err.name, "PermissionDenied"); +}); diff --git a/cli/js/tests/tty_test.ts b/cli/js/tests/tty_test.ts new file mode 100644 index 000000000..116b0dfe9 --- /dev/null +++ b/cli/js/tests/tty_test.ts @@ -0,0 +1,23 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +// Note tests for Deno.setRaw is in integration tests. + +unitTest({ perms: { read: true } }, function isatty(): void { + // CI not under TTY, so cannot test stdin/stdout/stderr. + const f = Deno.openSync("cli/tests/hello.txt"); + assert(!Deno.isatty(f.rid)); + f.close(); +}); + +unitTest(function isattyError(): void { + let caught = false; + try { + // Absurdly large rid. + Deno.isatty(0x7fffffff); + } catch (e) { + caught = true; + assert(e instanceof Deno.errors.BadResource); + } + assert(caught); +}); diff --git a/cli/js/tests/unit_test_runner.ts b/cli/js/tests/unit_test_runner.ts new file mode 100755 index 000000000..a5b7c3a48 --- /dev/null +++ b/cli/js/tests/unit_test_runner.ts @@ -0,0 +1,108 @@ +#!/usr/bin/env -S deno run --reload --allow-run +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import "./unit_tests.ts"; +import { + permissionCombinations, + parseUnitTestOutput, + Permissions +} from "./test_util.ts"; + +interface TestResult { + perms: string; + output?: string; + result: number; +} + +function permsToCliFlags(perms: Permissions): string[] { + return Object.keys(perms) + .map(key => { + if (!perms[key as keyof Permissions]) return ""; + + const cliFlag = key.replace( + /\.?([A-Z])/g, + (x, y): string => `-${y.toLowerCase()}` + ); + return `--allow-${cliFlag}`; + }) + .filter((e): boolean => e.length > 0); +} + +function fmtPerms(perms: Permissions): string { + let fmt = permsToCliFlags(perms).join(" "); + + if (!fmt) { + fmt = "<no permissions>"; + } + + return fmt; +} + +async function main(): Promise<void> { + console.log( + "Discovered permission combinations for tests:", + permissionCombinations.size + ); + + for (const perms of permissionCombinations.values()) { + console.log("\t" + fmtPerms(perms)); + } + + const testResults = new Set<TestResult>(); + + for (const perms of permissionCombinations.values()) { + const permsFmt = fmtPerms(perms); + console.log(`Running tests for: ${permsFmt}`); + const cliPerms = permsToCliFlags(perms); + // run subsequent tests using same deno executable + const args = [ + Deno.execPath(), + "run", + ...cliPerms, + "cli/js/tests/unit_tests.ts" + ]; + + const p = Deno.run({ + args, + stdout: "piped" + }); + + const { actual, expected, resultOutput } = await parseUnitTestOutput( + p.stdout!, + true + ); + + let result = 0; + + if (!actual && !expected) { + console.error("Bad cli/js/tests/unit_test.ts output"); + result = 1; + } else if (expected !== actual) { + result = 1; + } + + testResults.add({ + perms: permsFmt, + output: resultOutput, + result + }); + } + + // if any run tests returned non-zero status then whole test + // run should fail + let testsFailed = false; + + for (const testResult of testResults) { + console.log(`Summary for ${testResult.perms}`); + console.log(testResult.output + "\n"); + testsFailed = testsFailed || Boolean(testResult.result); + } + + if (testsFailed) { + console.error("Unit tests failed"); + Deno.exit(1); + } + + console.log("Unit tests passed"); +} + +main(); diff --git a/cli/js/tests/unit_tests.ts b/cli/js/tests/unit_tests.ts new file mode 100644 index 000000000..cc51e6ade --- /dev/null +++ b/cli/js/tests/unit_tests.ts @@ -0,0 +1,69 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This test is executed as part of tools/test.py +// But it can also be run manually: ./target/debug/deno cli/js/tests/unit_tests.ts + +import "./blob_test.ts"; +import "./body_test.ts"; +import "./buffer_test.ts"; +import "./build_test.ts"; +import "./chmod_test.ts"; +import "./chown_test.ts"; +import "./compiler_api_test.ts"; +import "./console_test.ts"; +import "./copy_file_test.ts"; +import "./custom_event_test.ts"; +import "./dir_test.ts"; +import "./dispatch_minimal_test.ts"; +import "./dispatch_json_test.ts"; +import "./error_stack_test.ts"; +import "./event_test.ts"; +import "./event_target_test.ts"; +import "./fetch_test.ts"; +import "./file_test.ts"; +import "./files_test.ts"; +import "./form_data_test.ts"; +import "./format_error_test.ts"; +import "./fs_events_test.ts"; +import "./get_random_values_test.ts"; +import "./globals_test.ts"; +import "./headers_test.ts"; +import "./internals_test.ts"; +import "./link_test.ts"; +import "./location_test.ts"; +import "./make_temp_test.ts"; +import "./metrics_test.ts"; +import "./dom_iterable_test.ts"; +import "./mkdir_test.ts"; +import "./net_test.ts"; +import "./os_test.ts"; +import "./permissions_test.ts"; +import "./process_test.ts"; +import "./realpath_test.ts"; +import "./read_dir_test.ts"; +import "./read_file_test.ts"; +import "./read_link_test.ts"; +import "./remove_test.ts"; +import "./rename_test.ts"; +import "./request_test.ts"; +import "./resources_test.ts"; +import "./signal_test.ts"; +import "./stat_test.ts"; +import "./symbols_test.ts"; +import "./symlink_test.ts"; +import "./text_encoding_test.ts"; +import "./testing_test.ts"; +import "./timers_test.ts"; +import "./tls_test.ts"; +import "./truncate_test.ts"; +import "./tty_test.ts"; +import "./url_test.ts"; +import "./url_search_params_test.ts"; +import "./utime_test.ts"; +import "./write_file_test.ts"; +import "./performance_test.ts"; +import "./version_test.ts"; + +if (import.meta.main) { + await Deno.runTests(); +} diff --git a/cli/js/tests/url_search_params_test.ts b/cli/js/tests/url_search_params_test.ts new file mode 100644 index 000000000..b256395a0 --- /dev/null +++ b/cli/js/tests/url_search_params_test.ts @@ -0,0 +1,245 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest(function urlSearchParamsInitString(): void { + const init = "c=4&a=2&b=3&%C3%A1=1"; + const searchParams = new URLSearchParams(init); + assert( + init === searchParams.toString(), + "The init query string does not match" + ); +}); + +unitTest(function urlSearchParamsInitIterable(): void { + const init = [ + ["a", "54"], + ["b", "true"] + ]; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "a=54&b=true"); +}); + +unitTest(function urlSearchParamsInitRecord(): void { + const init = { a: "54", b: "true" }; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "a=54&b=true"); +}); + +unitTest(function urlSearchParamsInit(): void { + const params1 = new URLSearchParams("a=b"); + assertEquals(params1.toString(), "a=b"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const params2 = new URLSearchParams(params1 as any); + assertEquals(params2.toString(), "a=b"); +}); + +unitTest(function urlSearchParamsAppendSuccess(): void { + const searchParams = new URLSearchParams(); + searchParams.append("a", "true"); + assertEquals(searchParams.toString(), "a=true"); +}); + +unitTest(function urlSearchParamsDeleteSuccess(): void { + const init = "a=54&b=true"; + const searchParams = new URLSearchParams(init); + searchParams.delete("b"); + assertEquals(searchParams.toString(), "a=54"); +}); + +unitTest(function urlSearchParamsGetAllSuccess(): void { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.getAll("a"), ["54", "true"]); + assertEquals(searchParams.getAll("b"), ["true"]); + assertEquals(searchParams.getAll("c"), []); +}); + +unitTest(function urlSearchParamsGetSuccess(): void { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("a"), "54"); + assertEquals(searchParams.get("b"), "true"); + assertEquals(searchParams.get("c"), null); +}); + +unitTest(function urlSearchParamsHasSuccess(): void { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + assert(searchParams.has("a")); + assert(searchParams.has("b")); + assert(!searchParams.has("c")); +}); + +unitTest(function urlSearchParamsSetReplaceFirstAndRemoveOthers(): void { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + searchParams.set("a", "false"); + assertEquals(searchParams.toString(), "a=false&b=true"); +}); + +unitTest(function urlSearchParamsSetAppendNew(): void { + const init = "a=54&b=true&a=true"; + const searchParams = new URLSearchParams(init); + searchParams.set("c", "foo"); + assertEquals(searchParams.toString(), "a=54&b=true&a=true&c=foo"); +}); + +unitTest(function urlSearchParamsSortSuccess(): void { + const init = "c=4&a=2&b=3&a=1"; + const searchParams = new URLSearchParams(init); + searchParams.sort(); + assertEquals(searchParams.toString(), "a=2&a=1&b=3&c=4"); +}); + +unitTest(function urlSearchParamsForEachSuccess(): void { + const init = [ + ["a", "54"], + ["b", "true"] + ]; + const searchParams = new URLSearchParams(init); + let callNum = 0; + searchParams.forEach((value, key, parent): void => { + assertEquals(searchParams, parent); + assertEquals(value, init[callNum][1]); + assertEquals(key, init[callNum][0]); + callNum++; + }); + assertEquals(callNum, init.length); +}); + +unitTest(function urlSearchParamsMissingName(): void { + const init = "=4"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get(""), "4"); + assertEquals(searchParams.toString(), "=4"); +}); + +unitTest(function urlSearchParamsMissingValue(): void { + const init = "4="; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("4"), ""); + assertEquals(searchParams.toString(), "4="); +}); + +unitTest(function urlSearchParamsMissingEqualSign(): void { + const init = "4"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.get("4"), ""); + assertEquals(searchParams.toString(), "4="); +}); + +unitTest(function urlSearchParamsMissingPair(): void { + const init = "c=4&&a=54&"; + const searchParams = new URLSearchParams(init); + assertEquals(searchParams.toString(), "c=4&a=54"); +}); + +// If pair does not contain exactly two items, then throw a TypeError. +// ref https://url.spec.whatwg.org/#interface-urlsearchparams +unitTest(function urlSearchParamsShouldThrowTypeError(): void { + let hasThrown = 0; + + try { + new URLSearchParams([["1"]]); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + + assertEquals(hasThrown, 2); + + try { + new URLSearchParams([["1", "2", "3"]]); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + + assertEquals(hasThrown, 2); +}); + +unitTest(function urlSearchParamsAppendArgumentsCheck(): void { + const methodRequireOneParam = ["delete", "getAll", "get", "has", "forEach"]; + + const methodRequireTwoParams = ["append", "set"]; + + methodRequireOneParam + .concat(methodRequireTwoParams) + .forEach((method: string): void => { + const searchParams = new URLSearchParams(); + let hasThrown = 0; + try { + // @ts-ignore + searchParams[method](); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); + + methodRequireTwoParams.forEach((method: string): void => { + const searchParams = new URLSearchParams(); + let hasThrown = 0; + try { + // @ts-ignore + searchParams[method]("foo"); + hasThrown = 1; + } catch (err) { + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEquals(hasThrown, 2); + }); +}); + +// ref: https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-delete.any.js +unitTest(function urlSearchParamsDeletingAppendedMultiple(): void { + const params = new URLSearchParams(); + params.append("first", (1 as unknown) as string); + assert(params.has("first")); + assertEquals(params.get("first"), "1"); + params.delete("first"); + assertEquals(params.has("first"), false); + params.append("first", (1 as unknown) as string); + params.append("first", (10 as unknown) as string); + params.delete("first"); + assertEquals(params.has("first"), false); +}); + +// ref: https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js#L176-L182 +unitTest(function urlSearchParamsCustomSymbolIterator(): void { + const params = new URLSearchParams(); + params[Symbol.iterator] = function*(): IterableIterator<[string, string]> { + yield ["a", "b"]; + }; + const params1 = new URLSearchParams((params as unknown) as string[][]); + assertEquals(params1.get("a"), "b"); +}); + +unitTest( + function urlSearchParamsCustomSymbolIteratorWithNonStringParams(): void { + const params = {}; + // @ts-ignore + params[Symbol.iterator] = function*(): IterableIterator<[number, number]> { + yield [1, 2]; + }; + const params1 = new URLSearchParams((params as unknown) as string[][]); + assertEquals(params1.get("1"), "2"); + } +); diff --git a/cli/js/tests/url_test.ts b/cli/js/tests/url_test.ts new file mode 100644 index 000000000..46468c8ae --- /dev/null +++ b/cli/js/tests/url_test.ts @@ -0,0 +1,208 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals, assertThrows } from "./test_util.ts"; + +unitTest(function urlParsing(): void { + const url = new URL( + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + assertEquals(url.hash, "#qat"); + assertEquals(url.host, "baz.qat:8000"); + assertEquals(url.hostname, "baz.qat"); + assertEquals( + url.href, + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + assertEquals(url.origin, "https://baz.qat:8000"); + assertEquals(url.password, "bar"); + assertEquals(url.pathname, "/qux/quux"); + assertEquals(url.port, "8000"); + assertEquals(url.protocol, "https:"); + assertEquals(url.search, "?foo=bar&baz=12"); + assertEquals(url.searchParams.getAll("foo"), ["bar"]); + assertEquals(url.searchParams.getAll("baz"), ["12"]); + assertEquals(url.username, "foo"); + assertEquals( + String(url), + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + assertEquals( + JSON.stringify({ key: url }), + `{"key":"https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"}` + ); +}); + +unitTest(function urlModifications(): void { + const url = new URL( + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + url.hash = ""; + assertEquals( + url.href, + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12" + ); + url.host = "qat.baz:8080"; + assertEquals( + url.href, + "https://foo:bar@qat.baz:8080/qux/quux?foo=bar&baz=12" + ); + url.hostname = "foo.bar"; + assertEquals( + url.href, + "https://foo:bar@foo.bar:8080/qux/quux?foo=bar&baz=12" + ); + url.password = "qux"; + assertEquals( + url.href, + "https://foo:qux@foo.bar:8080/qux/quux?foo=bar&baz=12" + ); + url.pathname = "/foo/bar%qat"; + assertEquals( + url.href, + "https://foo:qux@foo.bar:8080/foo/bar%qat?foo=bar&baz=12" + ); + url.port = ""; + assertEquals(url.href, "https://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); + url.protocol = "http:"; + assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&baz=12"); + url.search = "?foo=bar&foo=baz"; + assertEquals(url.href, "http://foo:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz"); + assertEquals(url.searchParams.getAll("foo"), ["bar", "baz"]); + url.username = "foo@bar"; + assertEquals( + url.href, + "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz" + ); + url.searchParams.set("bar", "qat"); + assertEquals( + url.href, + "http://foo%40bar:qux@foo.bar/foo/bar%qat?foo=bar&foo=baz&bar=qat" + ); + url.searchParams.delete("foo"); + assertEquals(url.href, "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat"); + url.searchParams.append("foo", "bar"); + assertEquals( + url.href, + "http://foo%40bar:qux@foo.bar/foo/bar%qat?bar=qat&foo=bar" + ); +}); + +unitTest(function urlModifyHref(): void { + const url = new URL("http://example.com/"); + url.href = "https://foo:bar@example.com:8080/baz/qat#qux"; + assertEquals(url.protocol, "https:"); + assertEquals(url.username, "foo"); + assertEquals(url.password, "bar"); + assertEquals(url.host, "example.com:8080"); + assertEquals(url.hostname, "example.com"); + assertEquals(url.pathname, "/baz/qat"); + assertEquals(url.hash, "#qux"); +}); + +unitTest(function urlModifyPathname(): void { + const url = new URL("http://foo.bar/baz%qat/qux%quux"); + assertEquals(url.pathname, "/baz%qat/qux%quux"); + url.pathname = url.pathname; + assertEquals(url.pathname, "/baz%qat/qux%quux"); + url.pathname = "baz#qat qux"; + assertEquals(url.pathname, "/baz%23qat%20qux"); + url.pathname = url.pathname; + assertEquals(url.pathname, "/baz%23qat%20qux"); +}); + +unitTest(function urlModifyHash(): void { + const url = new URL("http://foo.bar"); + url.hash = "%foo bar/qat%qux#bar"; + assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); + url.hash = url.hash; + assertEquals(url.hash, "#%foo%20bar/qat%qux#bar"); +}); + +unitTest(function urlSearchParamsReuse(): void { + const url = new URL( + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + const sp = url.searchParams; + url.host = "baz.qat"; + assert(sp === url.searchParams, "Search params should be reused."); +}); + +unitTest(function urlBaseURL(): void { + const base = new URL( + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + const url = new URL("/foo/bar?baz=foo#qux", base); + assertEquals(url.href, "https://foo:bar@baz.qat:8000/foo/bar?baz=foo#qux"); +}); + +unitTest(function urlBaseString(): void { + const url = new URL( + "/foo/bar?baz=foo#qux", + "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat" + ); + assertEquals(url.href, "https://foo:bar@baz.qat:8000/foo/bar?baz=foo#qux"); +}); + +unitTest(function urlRelativeWithBase(): void { + assertEquals(new URL("", "file:///a/a/a").href, "file:///a/a/a"); + assertEquals(new URL(".", "file:///a/a/a").href, "file:///a/a/"); + assertEquals(new URL("..", "file:///a/a/a").href, "file:///a/"); + assertEquals(new URL("b", "file:///a/a/a").href, "file:///a/a/b"); + assertEquals(new URL("b", "file:///a/a/a/").href, "file:///a/a/a/b"); + assertEquals(new URL("b/", "file:///a/a/a").href, "file:///a/a/b/"); + assertEquals(new URL("../b", "file:///a/a/a").href, "file:///a/b"); +}); + +unitTest(function emptyBasePath(): void { + assertEquals(new URL("", "http://example.com").href, "http://example.com/"); +}); + +unitTest(function deletingAllParamsRemovesQuestionMarkFromURL(): void { + const url = new URL("http://example.com/?param1¶m2"); + url.searchParams.delete("param1"); + url.searchParams.delete("param2"); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); + +unitTest(function removingNonExistentParamRemovesQuestionMarkFromURL(): void { + const url = new URL("http://example.com/?"); + assertEquals(url.href, "http://example.com/?"); + url.searchParams.delete("param1"); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); + +unitTest(function sortingNonExistentParamRemovesQuestionMarkFromURL(): void { + const url = new URL("http://example.com/?"); + assertEquals(url.href, "http://example.com/?"); + url.searchParams.sort(); + assertEquals(url.href, "http://example.com/"); + assertEquals(url.search, ""); +}); + +/* +unitTest(function customInspectFunction(): void { + const url = new URL("http://example.com/?"); + assertEquals( + Deno.inspect(url), + 'URL { href: "http://example.com/?", origin: "http://example.com", protocol: "http:", username: "", password: "", host: "example.com", hostname: "example.com", port: "", pathname: "/", hash: "", search: "?" }' + ); +}); +*/ + +unitTest(function protocolNotHttpOrFile() { + const url = new URL("about:blank"); + assertEquals(url.href, "about:blank"); + assertEquals(url.protocol, "about:"); + assertEquals(url.origin, "null"); +}); + +unitTest(function createBadUrl(): void { + assertThrows(() => { + new URL("0.0.0.0:8080"); + }); +}); + +if (import.meta.main) { + Deno.runTests(); +} diff --git a/cli/js/tests/utime_test.ts b/cli/js/tests/utime_test.ts new file mode 100644 index 000000000..8cd34d39a --- /dev/null +++ b/cli/js/tests/utime_test.ts @@ -0,0 +1,193 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert } from "./test_util.ts"; + +// Allow 10 second difference. +// Note this might not be enough for FAT (but we are not testing on such fs). +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function assertFuzzyTimestampEquals(t1: any, t2: number): void { + assert(typeof t1 === "number"); + assert(Math.abs(t1 - t2) < 10); +} + +unitTest( + { perms: { read: true, write: true } }, + function utimeSyncFileSuccess(): void { + const testDir = Deno.makeTempDirSync(); + const filename = testDir + "/file.txt"; + Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { + mode: 0o666 + }); + + const atime = 1000; + const mtime = 50000; + Deno.utimeSync(filename, atime, mtime); + + const fileInfo = Deno.statSync(filename); + assertFuzzyTimestampEquals(fileInfo.accessed, atime); + assertFuzzyTimestampEquals(fileInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function utimeSyncDirectorySuccess(): void { + const testDir = Deno.makeTempDirSync(); + + const atime = 1000; + const mtime = 50000; + Deno.utimeSync(testDir, atime, mtime); + + const dirInfo = Deno.statSync(testDir); + assertFuzzyTimestampEquals(dirInfo.accessed, atime); + assertFuzzyTimestampEquals(dirInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function utimeSyncDateSuccess(): void { + const testDir = Deno.makeTempDirSync(); + + const atime = 1000; + const mtime = 50000; + Deno.utimeSync(testDir, new Date(atime * 1000), new Date(mtime * 1000)); + + const dirInfo = Deno.statSync(testDir); + assertFuzzyTimestampEquals(dirInfo.accessed, atime); + assertFuzzyTimestampEquals(dirInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function utimeSyncLargeNumberSuccess(): void { + const testDir = Deno.makeTempDirSync(); + + // There are Rust side caps (might be fs relate), + // so JUST make them slightly larger than UINT32_MAX. + const atime = 0x100000001; + const mtime = 0x100000002; + Deno.utimeSync(testDir, atime, mtime); + + const dirInfo = Deno.statSync(testDir); + assertFuzzyTimestampEquals(dirInfo.accessed, atime); + assertFuzzyTimestampEquals(dirInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function utimeSyncNotFound(): void { + const atime = 1000; + const mtime = 50000; + + let caughtError = false; + try { + Deno.utimeSync("/baddir", atime, mtime); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: false } }, + function utimeSyncPerm(): void { + const atime = 1000; + const mtime = 50000; + + let caughtError = false; + try { + Deno.utimeSync("/some_dir", atime, mtime); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function utimeFileSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + const filename = testDir + "/file.txt"; + Deno.writeFileSync(filename, new TextEncoder().encode("hello"), { + mode: 0o666 + }); + + const atime = 1000; + const mtime = 50000; + await Deno.utime(filename, atime, mtime); + + const fileInfo = Deno.statSync(filename); + assertFuzzyTimestampEquals(fileInfo.accessed, atime); + assertFuzzyTimestampEquals(fileInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function utimeDirectorySuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + + const atime = 1000; + const mtime = 50000; + await Deno.utime(testDir, atime, mtime); + + const dirInfo = Deno.statSync(testDir); + assertFuzzyTimestampEquals(dirInfo.accessed, atime); + assertFuzzyTimestampEquals(dirInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function utimeDateSuccess(): Promise<void> { + const testDir = Deno.makeTempDirSync(); + + const atime = 1000; + const mtime = 50000; + await Deno.utime(testDir, new Date(atime * 1000), new Date(mtime * 1000)); + + const dirInfo = Deno.statSync(testDir); + assertFuzzyTimestampEquals(dirInfo.accessed, atime); + assertFuzzyTimestampEquals(dirInfo.modified, mtime); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function utimeNotFound(): Promise<void> { + const atime = 1000; + const mtime = 50000; + + let caughtError = false; + try { + await Deno.utime("/baddir", atime, mtime); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: false } }, + async function utimeSyncPerm(): Promise<void> { + const atime = 1000; + const mtime = 50000; + + let caughtError = false; + try { + await Deno.utime("/some_dir", atime, mtime); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); diff --git a/cli/js/tests/version_test.ts b/cli/js/tests/version_test.ts new file mode 100644 index 000000000..0417b27de --- /dev/null +++ b/cli/js/tests/version_test.ts @@ -0,0 +1,8 @@ +import { unitTest, assert } from "./test_util.ts"; + +unitTest(function version(): void { + const pattern = /^\d+\.\d+\.\d+/; + assert(pattern.test(Deno.version.deno)); + assert(pattern.test(Deno.version.v8)); + assert(pattern.test(Deno.version.typescript)); +}); diff --git a/cli/js/tests/write_file_test.ts b/cli/js/tests/write_file_test.ts new file mode 100644 index 000000000..c06c5b330 --- /dev/null +++ b/cli/js/tests/write_file_test.ts @@ -0,0 +1,228 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function writeFileSyncSuccess(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); + +unitTest({ perms: { write: true } }, function writeFileSyncFail(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + Deno.writeFileSync(filename, data); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); +}); + +unitTest({ perms: { write: false } }, function writeFileSyncPerm(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + Deno.writeFileSync(filename, data); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest( + { perms: { read: true, write: true } }, + function writeFileSyncUpdateMode(): void { + if (Deno.build.os !== "win") { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data, { mode: 0o755 }); + assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); + Deno.writeFileSync(filename, data, { mode: 0o666 }); + assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); + } + } +); + +unitTest( + { perms: { read: true, write: true } }, + function writeFileSyncCreate(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + let caughtError = false; + // if create turned off, the file won't be created + try { + Deno.writeFileSync(filename, data, { create: false }); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + + // Turn on create, should have no error + Deno.writeFileSync(filename, data, { create: true }); + Deno.writeFileSync(filename, data, { create: false }); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function writeFileSyncAppend(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeFileSync(filename, data); + Deno.writeFileSync(filename, data, { append: true }); + let dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + let actual = dec.decode(dataRead); + assertEquals("HelloHello", actual); + // Now attempt overwrite + Deno.writeFileSync(filename, data, { append: false }); + dataRead = Deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEquals("Hello", actual); + // append not set should also overwrite + Deno.writeFileSync(filename, data); + dataRead = Deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeFileSuccess(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeFile(filename, data); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeFileNotFound(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + await Deno.writeFile(filename, data); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: false } }, + async function writeFilePerm(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + await Deno.writeFile(filename, data); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeFileUpdateMode(): Promise<void> { + if (Deno.build.os !== "win") { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeFile(filename, data, { mode: 0o755 }); + assertEquals(Deno.statSync(filename).mode! & 0o777, 0o755); + await Deno.writeFile(filename, data, { mode: 0o666 }); + assertEquals(Deno.statSync(filename).mode! & 0o777, 0o666); + } + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeFileCreate(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + let caughtError = false; + // if create turned off, the file won't be created + try { + await Deno.writeFile(filename, data, { create: false }); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + + // Turn on create, should have no error + await Deno.writeFile(filename, data, { create: true }); + await Deno.writeFile(filename, data, { create: false }); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeFileAppend(): Promise<void> { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeFile(filename, data); + await Deno.writeFile(filename, data, { append: true }); + let dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + let actual = dec.decode(dataRead); + assertEquals("HelloHello", actual); + // Now attempt overwrite + await Deno.writeFile(filename, data, { append: false }); + dataRead = Deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEquals("Hello", actual); + // append not set should also overwrite + await Deno.writeFile(filename, data); + dataRead = Deno.readFileSync(filename); + actual = dec.decode(dataRead); + assertEquals("Hello", actual); + } +); |