summaryrefslogtreecommitdiff
path: root/cli/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests/unit')
-rw-r--r--cli/tests/unit/README.md80
-rw-r--r--cli/tests/unit/abort_controller_test.ts56
-rw-r--r--cli/tests/unit/blob_test.ts92
-rw-r--r--cli/tests/unit/body_test.ts74
-rw-r--r--cli/tests/unit/buffer_test.ts294
-rw-r--r--cli/tests/unit/build_test.ts10
-rw-r--r--cli/tests/unit/chmod_test.ts149
-rw-r--r--cli/tests/unit/chown_test.ts147
-rw-r--r--cli/tests/unit/console_test.ts1139
-rw-r--r--cli/tests/unit/copy_file_test.ts176
-rw-r--r--cli/tests/unit/custom_event_test.ts27
-rw-r--r--cli/tests/unit/dir_test.ts60
-rw-r--r--cli/tests/unit/dispatch_json_test.ts32
-rw-r--r--cli/tests/unit/dispatch_minimal_test.ts43
-rw-r--r--cli/tests/unit/dom_exception_test.ts9
-rw-r--r--cli/tests/unit/dom_iterable_test.ts87
-rw-r--r--cli/tests/unit/error_stack_test.ts108
-rw-r--r--cli/tests/unit/event_target_test.ts231
-rw-r--r--cli/tests/unit/event_test.ts94
-rw-r--r--cli/tests/unit/fetch_test.ts534
-rw-r--r--cli/tests/unit/file_test.ts105
-rw-r--r--cli/tests/unit/files_test.ts571
-rw-r--r--cli/tests/unit/form_data_test.ts203
-rw-r--r--cli/tests/unit/format_error_test.ts33
-rw-r--r--cli/tests/unit/fs_events_test.ts71
-rw-r--r--cli/tests/unit/get_random_values_test.ts51
-rw-r--r--cli/tests/unit/globals_test.ts112
-rw-r--r--cli/tests/unit/headers_test.ts420
-rw-r--r--cli/tests/unit/internals_test.ts10
-rw-r--r--cli/tests/unit/io_test.ts73
-rw-r--r--cli/tests/unit/link_test.ts147
-rw-r--r--cli/tests/unit/make_temp_test.ts178
-rw-r--r--cli/tests/unit/metrics_test.ts58
-rw-r--r--cli/tests/unit/mkdir_test.ts206
-rw-r--r--cli/tests/unit/net_test.ts527
-rw-r--r--cli/tests/unit/os_test.ts338
-rw-r--r--cli/tests/unit/performance_test.ts15
-rw-r--r--cli/tests/unit/permissions_test.ts27
-rw-r--r--cli/tests/unit/process_test.ts386
-rw-r--r--cli/tests/unit/read_dir_test.ts82
-rw-r--r--cli/tests/unit/read_file_test.ts65
-rw-r--r--cli/tests/unit/read_link_test.ts73
-rw-r--r--cli/tests/unit/read_text_file_test.ts61
-rw-r--r--cli/tests/unit/real_path_test.ts108
-rw-r--r--cli/tests/unit/remove_test.ts506
-rw-r--r--cli/tests/unit/rename_test.ts216
-rw-r--r--cli/tests/unit/request_test.ts49
-rw-r--r--cli/tests/unit/resources_test.ts61
-rw-r--r--cli/tests/unit/signal_test.ts195
-rw-r--r--cli/tests/unit/stat_test.ts238
-rw-r--r--cli/tests/unit/streams_piping_test.ts131
-rw-r--r--cli/tests/unit/streams_transform_test.ts562
-rw-r--r--cli/tests/unit/streams_writable_test.ts253
-rw-r--r--cli/tests/unit/symlink_test.ts44
-rw-r--r--cli/tests/unit/test_util.ts364
-rw-r--r--cli/tests/unit/testing_test.ts27
-rw-r--r--cli/tests/unit/text_encoding_test.ts206
-rw-r--r--cli/tests/unit/timers_test.ts368
-rw-r--r--cli/tests/unit/tls_test.ts262
-rw-r--r--cli/tests/unit/truncate_test.ts80
-rw-r--r--cli/tests/unit/tty_test.ts23
-rw-r--r--cli/tests/unit/umask_test.ts15
-rwxr-xr-xcli/tests/unit/unit_test_runner.ts309
-rw-r--r--cli/tests/unit/unit_tests.ts72
-rw-r--r--cli/tests/unit/url_search_params_test.ts275
-rw-r--r--cli/tests/unit/url_test.ts407
-rw-r--r--cli/tests/unit/utime_test.ts230
-rw-r--r--cli/tests/unit/version_test.ts8
-rw-r--r--cli/tests/unit/write_file_test.ts228
-rw-r--r--cli/tests/unit/write_text_file_test.ts79
70 files changed, 12570 insertions, 0 deletions
diff --git a/cli/tests/unit/README.md b/cli/tests/unit/README.md
new file mode 100644
index 000000000..c333b0046
--- /dev/null
+++ b/cli/tests/unit/README.md
@@ -0,0 +1,80 @@
+# 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({
+ ignore: Deno.build.os === "windows",
+ 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
+
+## Running tests
+
+`unit_test_runner.ts` is the main script used to run unit tests.
+
+Runner discovers required permissions combinations by loading
+`cli/tests/unit/unit_tests.ts` and going through all registered instances of
+`unitTest`.
+
+There are three ways to run `unit_test_runner.ts`:
+
+```
+# Run all tests. Spawns worker processes for each discovered permission
+# combination:
+target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --master
+
+# By default all output of worker processes is discarded; for debug purposes
+# the --verbose flag preserves output from the worker
+target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --master --verbose
+
+# Run subset of tests that don't require any permissions
+target/debug/deno run --unstable cli/tests/unit/unit_test_runner.ts
+
+# Run subset tests that require "net" and "read" permissions
+target/debug/deno run --unstable --allow-net --allow-read cli/tests/unit/unit_test_runner.ts
+
+# "worker" mode communicates with parent using TCP socket on provided address;
+# after initial setup drops permissions to specified set. It shouldn't be used
+# directly, only be "master" process.
+target/debug/deno run -A cli/tests/unit/unit_test_runner.ts --worker --addr=127.0.0.1:4500 --perms=net,write,run
+
+# Run specific tests
+target/debug/deno run --unstable --allow-net cli/tests/unit/unit_test_runner.ts -- netTcpListenClose
+
+RUST_BACKTRACE=1 cargo run -- run --unstable --allow-read --allow-write cli/tests/unit/unit_test_runner.ts -- netUnixDialListen
+```
+
+### Http server
+
+`tools/http_server.py` is required to run when one's running unit tests. During
+CI it's spawned automatically, but if you want to run tests manually make sure
+that server is spawned otherwise there'll be cascade of test failures.
diff --git a/cli/tests/unit/abort_controller_test.ts b/cli/tests/unit/abort_controller_test.ts
new file mode 100644
index 000000000..ecc1abb88
--- /dev/null
+++ b/cli/tests/unit/abort_controller_test.ts
@@ -0,0 +1,56 @@
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest(function basicAbortController() {
+ const controller = new AbortController();
+ assert(controller);
+ const { signal } = controller;
+ assert(signal);
+ assertEquals(signal.aborted, false);
+ controller.abort();
+ assertEquals(signal.aborted, true);
+});
+
+unitTest(function signalCallsOnabort() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.onabort = (evt): void => {
+ assert(evt);
+ assertEquals(evt.type, "abort");
+ called = true;
+ };
+ controller.abort();
+ assert(called);
+});
+
+unitTest(function signalEventListener() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.addEventListener("abort", function (ev) {
+ assert(this === signal);
+ assertEquals(ev.type, "abort");
+ called = true;
+ });
+ controller.abort();
+ assert(called);
+});
+
+unitTest(function onlyAbortsOnce() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = 0;
+ signal.addEventListener("abort", () => called++);
+ signal.onabort = (): void => {
+ called++;
+ };
+ controller.abort();
+ assertEquals(called, 2);
+ controller.abort();
+ assertEquals(called, 2);
+});
+
+unitTest(function controllerHasProperToString() {
+ const actual = Object.prototype.toString.call(new AbortController());
+ assertEquals(actual, "[object AbortController]");
+});
diff --git a/cli/tests/unit/blob_test.ts b/cli/tests/unit/blob_test.ts
new file mode 100644
index 000000000..70f8fb3d5
--- /dev/null
+++ b/cli/tests/unit/blob_test.ts
@@ -0,0 +1,92 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+import { concat } from "../../../std/bytes/mod.ts";
+import { decode } from "../../../std/encoding/utf8.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 blobInvalidType(): void {
+ const blob = new Blob(["foo"], {
+ type: "\u0521",
+ });
+
+ assertEquals(blob.type, "");
+});
+
+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 === "windows" ? 12 : 11);
+});
+
+unitTest(async function blobText(): Promise<void> {
+ const blob = new Blob(["Hello World"]);
+ assertEquals(await blob.text(), "Hello World");
+});
+
+unitTest(async function blobStream(): Promise<void> {
+ const blob = new Blob(["Hello World"]);
+ const stream = blob.stream();
+ assert(stream instanceof ReadableStream);
+ const reader = stream.getReader();
+ let bytes = new Uint8Array();
+ const read = async (): Promise<void> => {
+ const { done, value } = await reader.read();
+ if (!done && value) {
+ bytes = concat(bytes, value);
+ return read();
+ }
+ };
+ await read();
+ assertEquals(decode(bytes), "Hello World");
+});
diff --git a/cli/tests/unit/body_test.ts b/cli/tests/unit/body_test.ts
new file mode 100644
index 000000000..c8f783e04
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/buffer_test.ts b/cli/tests/unit/buffer_test.ts
new file mode 100644
index 000000000..ac2d7db7b
--- /dev/null
+++ b/cli/tests/unit/buffer_test.ts
@@ -0,0 +1,294 @@
+// 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, 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 === null) {
+ 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 nread = (await buf.read(tmp)) ?? 0;
+ 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/tests/unit/build_test.ts b/cli/tests/unit/build_test.ts
new file mode 100644
index 000000000..987ed8d39
--- /dev/null
+++ b/cli/tests/unit/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.length > 0);
+ assert(os === "darwin" || os === "windows" || os === "linux");
+});
diff --git a/cli/tests/unit/chmod_test.ts b/cli/tests/unit/chmod_test.ts
new file mode 100644
index 000000000..d0d17629d
--- /dev/null
+++ b/cli/tests/unit/chmod_test.ts
@@ -0,0 +1,149 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest(
+ { ignore: Deno.build.os === "windows", 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 });
+
+ Deno.chmodSync(filename, 0o777);
+
+ const fileInfo = Deno.statSync(filename);
+ assert(fileInfo.mode);
+ assertEquals(fileInfo.mode & 0o777, 0o777);
+ }
+);
+
+// Check symlink when not on windows
+unitTest(
+ {
+ ignore: Deno.build.os === "windows",
+ 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(
+ { ignore: Deno.build.os === "windows", 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 });
+
+ await Deno.chmod(filename, 0o777);
+
+ const fileInfo = Deno.statSync(filename);
+ assert(fileInfo.mode);
+ assertEquals(fileInfo.mode & 0o777, 0o777);
+ }
+);
+
+// Check symlink when not on windows
+
+unitTest(
+ {
+ ignore: Deno.build.os === "windows",
+ 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/tests/unit/chown_test.ts b/cli/tests/unit/chown_test.ts
new file mode 100644
index 000000000..724ea5a21
--- /dev/null
+++ b/cli/tests/unit/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 !== "windows") {
+ 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",
+ cmd: ["python", "-c", "import os; print(os.getuid())"],
+ });
+ const gidProc = Deno.run({
+ stdout: "piped",
+ cmd: ["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 privilege, 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/tests/unit/console_test.ts b/cli/tests/unit/console_test.ts
new file mode 100644
index 000000000..02d71e4ca
--- /dev/null
+++ b/cli/tests/unit/console_test.ts
@@ -0,0 +1,1139 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// TODO(ry) The unit test functions in this module are too coarse. They should
+// be broken up into smaller bits.
+
+// TODO(ry) These tests currentl strip all the ANSI colors out. We don't have a
+// good way to control whether we produce color output or not since
+// std/fmt/colors auto determines whether to put colors in or not. We need
+// better infrastructure here so we can properly test the colors.
+
+import { assert, assertEquals, unitTest } from "./test_util.ts";
+import { stripColor } from "../../../std/fmt/colors.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,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+} = Deno as any;
+
+const customInspect = Deno.customInspect;
+const {
+ Console,
+ stringifyArgs,
+ // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol
+} = Deno[Deno.internal];
+
+function stringify(...args: unknown[]): string {
+ return stripColor(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: 1,
+ bool: true,
+ str: "a",
+ method: [Function: method],
+ asyncMethod: [AsyncFunction: asyncMethod],
+ generatorMethod: [GeneratorFunction: generatorMethod],
+ un: undefined,
+ nu: null,
+ arrowFunc: [Function: arrowFunc],
+ extendedClass: Extended { a: 1, b: 2 },
+ nFunc: [Function],
+ extendedCstr: [Function: Extended],
+ o: {
+ num: 2,
+ bool: false,
+ str: "b",
+ method: [Function: method],
+ un: undefined,
+ nu: null,
+ nested: [Circular],
+ emptyObj: {},
+ arr: [ 1, "s", false, null, [Circular] ],
+ baseClass: Base { a: 1 }
+ }
+}`;
+
+ 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(3) [ 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), 'JSON { Symbol(Symbol.toStringTag): "JSON" }');
+ assertEquals(
+ stringify(console),
+ `{
+ log: [Function],
+ debug: [Function],
+ info: [Function],
+ dir: [Function],
+ dirxml: [Function],
+ warn: [Function],
+ error: [Function],
+ assert: [Function],
+ count: [Function],
+ countReset: [Function],
+ table: [Function],
+ time: [Function],
+ timeLog: [Function],
+ timeEnd: [Function],
+ group: [Function],
+ groupCollapsed: [Function],
+ groupEnd: [Function],
+ clear: [Function],
+ trace: [Function],
+ indentLevel: 0,
+ Symbol(isConsoleInstance): true
+}`
+ );
+ assertEquals(
+ stringify({ str: 1, [Symbol.for("sym")]: 2, [Symbol.toStringTag]: "TAG" }),
+ 'TAG { str: 1, Symbol(sym): 2, Symbol(Symbol.toStringTag): "TAG" }'
+ );
+ // test inspect is working the same
+ assertEquals(stripColor(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(
+ stripColor(stringifyArgs([nestedObj], { depth: 3 })),
+ "{ a: { b: { c: [Object] } } }"
+ );
+ assertEquals(
+ stripColor(stringifyArgs([nestedObj], { depth: 4 })),
+ "{ a: { b: { c: { d: [Object] } } } }"
+ );
+ assertEquals(
+ stripColor(stringifyArgs([nestedObj], { depth: 0 })),
+ "[Object]"
+ );
+ assertEquals(
+ stripColor(stringifyArgs([nestedObj])),
+ "{ a: { b: { c: { d: [Object] } } } }"
+ );
+ // test inspect is working the same way
+ assertEquals(
+ stripColor(inspect(nestedObj, { depth: 4 })),
+ "{ a: { b: { c: { d: [Object] } } } }"
+ );
+});
+
+unitTest(function consoleTestStringifyLargeObject(): void {
+ const obj = {
+ a: 2,
+ o: {
+ a: "1",
+ b: "2",
+ c: "3",
+ d: "4",
+ e: "5",
+ f: "6",
+ g: 10,
+ asd: 2,
+ asda: 3,
+ x: { a: "asd", x: 3 },
+ },
+ };
+ assertEquals(
+ stringify(obj),
+ `{
+ a: 2,
+ o: {
+ a: "1",
+ b: "2",
+ c: "3",
+ d: "4",
+ e: "5",
+ f: "6",
+ g: 10,
+ asd: 2,
+ asda: 3,
+ x: { a: "asd", x: 3 }
+ }
+}`
+ );
+});
+
+unitTest(function consoleTestStringifyIterable() {
+ const shortArray = [1, 2, 3, 4, 5];
+ assertEquals(stringify(shortArray), "[ 1, 2, 3, 4, 5 ]");
+
+ const longArray = new Array(200).fill(0);
+ assertEquals(
+ stringify(longArray),
+ `[
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ... 100 more items
+]`
+ );
+
+ const obj = { a: "a", longArray };
+ assertEquals(
+ stringify(obj),
+ `{
+ a: "a",
+ longArray: [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ... 100 more items
+ ]
+}`
+ );
+
+ const shortMap = new Map([
+ ["a", 0],
+ ["b", 1],
+ ]);
+ assertEquals(stringify(shortMap), `Map { "a" => 0, "b" => 1 }`);
+
+ const longMap = new Map();
+ for (const key of Array(200).keys()) {
+ longMap.set(`${key}`, key);
+ }
+ assertEquals(
+ stringify(longMap),
+ `Map {
+ "0" => 0,
+ "1" => 1,
+ "2" => 2,
+ "3" => 3,
+ "4" => 4,
+ "5" => 5,
+ "6" => 6,
+ "7" => 7,
+ "8" => 8,
+ "9" => 9,
+ "10" => 10,
+ "11" => 11,
+ "12" => 12,
+ "13" => 13,
+ "14" => 14,
+ "15" => 15,
+ "16" => 16,
+ "17" => 17,
+ "18" => 18,
+ "19" => 19,
+ "20" => 20,
+ "21" => 21,
+ "22" => 22,
+ "23" => 23,
+ "24" => 24,
+ "25" => 25,
+ "26" => 26,
+ "27" => 27,
+ "28" => 28,
+ "29" => 29,
+ "30" => 30,
+ "31" => 31,
+ "32" => 32,
+ "33" => 33,
+ "34" => 34,
+ "35" => 35,
+ "36" => 36,
+ "37" => 37,
+ "38" => 38,
+ "39" => 39,
+ "40" => 40,
+ "41" => 41,
+ "42" => 42,
+ "43" => 43,
+ "44" => 44,
+ "45" => 45,
+ "46" => 46,
+ "47" => 47,
+ "48" => 48,
+ "49" => 49,
+ "50" => 50,
+ "51" => 51,
+ "52" => 52,
+ "53" => 53,
+ "54" => 54,
+ "55" => 55,
+ "56" => 56,
+ "57" => 57,
+ "58" => 58,
+ "59" => 59,
+ "60" => 60,
+ "61" => 61,
+ "62" => 62,
+ "63" => 63,
+ "64" => 64,
+ "65" => 65,
+ "66" => 66,
+ "67" => 67,
+ "68" => 68,
+ "69" => 69,
+ "70" => 70,
+ "71" => 71,
+ "72" => 72,
+ "73" => 73,
+ "74" => 74,
+ "75" => 75,
+ "76" => 76,
+ "77" => 77,
+ "78" => 78,
+ "79" => 79,
+ "80" => 80,
+ "81" => 81,
+ "82" => 82,
+ "83" => 83,
+ "84" => 84,
+ "85" => 85,
+ "86" => 86,
+ "87" => 87,
+ "88" => 88,
+ "89" => 89,
+ "90" => 90,
+ "91" => 91,
+ "92" => 92,
+ "93" => 93,
+ "94" => 94,
+ "95" => 95,
+ "96" => 96,
+ "97" => 97,
+ "98" => 98,
+ "99" => 99,
+ ... 100 more items
+}`
+ );
+
+ const shortSet = new Set([1, 2, 3]);
+ assertEquals(stringify(shortSet), `Set { 1, 2, 3 }`);
+ const longSet = new Set();
+ for (const key of Array(200).keys()) {
+ longSet.add(key);
+ }
+ assertEquals(
+ stringify(longSet),
+ `Set {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 61,
+ 62,
+ 63,
+ 64,
+ 65,
+ 66,
+ 67,
+ 68,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 77,
+ 78,
+ 79,
+ 80,
+ 81,
+ 82,
+ 83,
+ 84,
+ 85,
+ 86,
+ 87,
+ 88,
+ 89,
+ 90,
+ 91,
+ 92,
+ 93,
+ 94,
+ 95,
+ 96,
+ 97,
+ 98,
+ 99,
+ ... 100 more items
+}`
+ );
+
+ const withEmptyEl = Array(10);
+ withEmptyEl.fill(0, 4, 6);
+ assertEquals(
+ stringify(withEmptyEl),
+ `[ <4 empty items>, 0, 0, <4 empty items> ]`
+ );
+
+ /* TODO(ry) Fix this test
+ const lWithEmptyEl = Array(200);
+ lWithEmptyEl.fill(0, 50, 80);
+ assertEquals(
+ stringify(lWithEmptyEl),
+ `[
+ <50 empty items>, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, 0, 0,
+ 0, <120 empty items>
+]`
+ );
+*/
+});
+
+unitTest(async function consoleTestStringifyPromises(): Promise<void> {
+ const pendingPromise = new Promise((_res, _rej) => {});
+ assertEquals(stringify(pendingPromise), "Promise { <pending> }");
+
+ const resolvedPromise = new Promise((res, _rej) => {
+ res("Resolved!");
+ });
+ assertEquals(stringify(resolvedPromise), `Promise { "Resolved!" }`);
+
+ let rejectedPromise;
+ try {
+ rejectedPromise = new Promise((_, rej) => {
+ rej(Error("Whoops"));
+ });
+ await rejectedPromise;
+ } catch (err) {}
+ const strLines = stringify(rejectedPromise).split("\n");
+ assertEquals(strLines[0], "Promise {");
+ assertEquals(strLines[1], " <rejected> Error: Whoops");
+});
+
+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),
+ "{ Symbol(Deno.symbols.customInspect): [Function: [Deno.symbols.customInspect]] }"
+ );
+});
+
+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 {
+ mockConsole((console, out) => {
+ console.clear();
+ assertEquals(out.toString(), "\x1b[1;1H" + "\x1b[0J");
+ });
+});
+
+// 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(
+ stripColor(out.toString()),
+ `┌───────┬────────┐
+│ (idx) │ Values │
+├───────┼────────┤
+│ a │ "test" │
+│ b │ 1 │
+└───────┴────────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]);
+ assertEquals(
+ stripColor(out.toString()),
+ `┌───────┬────┐
+│ (idx) │ c │
+├───────┼────┤
+│ a │ │
+│ b │ 30 │
+└───────┴────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]);
+ assertEquals(
+ stripColor(out.toString()),
+ `┌───────┬───────┬───────┬────────┐
+│ (idx) │ 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(
+ stripColor(out.toString()),
+ `┌────────────┬────────┐
+│ (iter idx) │ Values │
+├────────────┼────────┤
+│ 0 │ 1 │
+│ 1 │ 2 │
+│ 2 │ 3 │
+│ 3 │ "test" │
+└────────────┴────────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table(
+ new Map([
+ [1, "one"],
+ [2, "two"],
+ ])
+ );
+ assertEquals(
+ stripColor(out.toString()),
+ `┌────────────┬─────┬────────┐
+│ (iter idx) │ 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(
+ stripColor(out.toString()),
+ `┌───────┬───────────┬───────────────────┬────────┐
+│ (idx) │ 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(
+ stripColor(out.toString()),
+ `┌───────┬────────┬──────────────────────┬────┬────────┐
+│ (idx) │ 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(
+ stripColor(out.toString()),
+ `┌───────┐
+│ (idx) │
+├───────┤
+└───────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table({});
+ assertEquals(
+ stripColor(out.toString()),
+ `┌───────┐
+│ (idx) │
+├───────┤
+└───────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table(new Set());
+ assertEquals(
+ stripColor(out.toString()),
+ `┌────────────┐
+│ (iter idx) │
+├────────────┤
+└────────────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table(new Map());
+ assertEquals(
+ stripColor(out.toString()),
+ `┌────────────┐
+│ (iter idx) │
+├────────────┤
+└────────────┘
+`
+ );
+ });
+ mockConsole((console, out): void => {
+ console.table("test");
+ assertEquals(out.toString(), "test\n");
+ });
+ mockConsole((console, out): void => {
+ console.table(["Hello", "你好", "Amapá"]);
+ assertEquals(
+ stripColor(out.toString()),
+ `┌───────┬─────────┐
+│ (idx) │ Values │
+├───────┼─────────┤
+│ 0 │ "Hello" │
+│ 1 │ "你好" │
+│ 2 │ "Amapá" │
+└───────┴─────────┘
+`
+ );
+ });
+});
+
+// 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.log(Invalid Date) test
+unitTest(function consoleLogShoultNotThrowErrorWhenInvalidDateIsPassed(): void {
+ mockConsole((console, out) => {
+ const invalidDate = new Date("test");
+ console.log(invalidDate);
+ assertEquals(stripColor(out.toString()), "Invalid Date\n");
+ });
+});
+
+// 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/tests/unit/copy_file_test.ts b/cli/tests/unit/copy_file_test.ts
new file mode 100644
index 000000000..986bab53b
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/custom_event_test.ts b/cli/tests/unit/custom_event_test.ts
new file mode 100644
index 000000000..a8b2fcf88
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/dir_test.ts b/cli/tests/unit/dir_test.ts
new file mode 100644
index 000000000..dc05d9564
--- /dev/null
+++ b/cli/tests/unit/dir_test.ts
@@ -0,0 +1,60 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest({ perms: { read: true } }, function dirCwdNotNull(): void {
+ assert(Deno.cwd() != null);
+});
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function dirCwdChdirSuccess(): void {
+ const initialdir = Deno.cwd();
+ const path = Deno.makeTempDirSync();
+ Deno.chdir(path);
+ const current = Deno.cwd();
+ if (Deno.build.os === "darwin") {
+ assertEquals(current, "/private" + path);
+ } else {
+ assertEquals(current, path);
+ }
+ Deno.chdir(initialdir);
+ }
+);
+
+unitTest({ perms: { read: true, write: true } }, function dirCwdError(): void {
+ // excluding windows since it throws resource busy, while removeSync
+ if (["linux", "darwin"].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: { read: true, 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/tests/unit/dispatch_json_test.ts b/cli/tests/unit/dispatch_json_test.ts
new file mode 100644
index 000000000..4e95b86a2
--- /dev/null
+++ b/cli/tests/unit/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(function malformedJsonControlBuffer(): 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/tests/unit/dispatch_minimal_test.ts b/cli/tests/unit/dispatch_minimal_test.ts
new file mode 100644
index 000000000..afc17f4fb
--- /dev/null
+++ b/cli/tests/unit/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(function malformedMinimalControlBuffer(): 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/tests/unit/dom_exception_test.ts b/cli/tests/unit/dom_exception_test.ts
new file mode 100644
index 000000000..2eb7633e1
--- /dev/null
+++ b/cli/tests/unit/dom_exception_test.ts
@@ -0,0 +1,9 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assertEquals, assert } from "./test_util.ts";
+
+unitTest(function testDomError() {
+ const de = new DOMException("foo", "bar");
+ assert(de);
+ assertEquals(de.message, "foo");
+ assertEquals(de.name, "bar");
+});
diff --git a/cli/tests/unit/dom_iterable_test.ts b/cli/tests/unit/dom_iterable_test.ts
new file mode 100644
index 000000000..b9435b3bc
--- /dev/null
+++ b/cli/tests/unit/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 {
+ [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.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/tests/unit/error_stack_test.ts b/cli/tests/unit/error_stack_test.ts
new file mode 100644
index 000000000..e5cedfcf5
--- /dev/null
+++ b/cli/tests/unit/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.internal];
+
+interface CallSite {
+ getThis(): unknown;
+ getTypeName(): string | null;
+ getFunction(): Function | null;
+ getFunctionName(): string | null;
+ getMethodName(): string | null;
+ getFileName(): string | null;
+ getLineNumber(): number | null;
+ getColumnNumber(): number | null;
+ getEvalOrigin(): string | null;
+ isToplevel(): boolean | null;
+ isEval(): boolean;
+ isNative(): boolean;
+ isConstructor(): boolean;
+ isAsync(): boolean;
+ isPromiseAll(): boolean;
+ getPromiseIndex(): number | null;
+}
+
+function getMockCallSite(
+ fileName: string,
+ lineNumber: number | null,
+ columnNumber: 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 lineNumber;
+ },
+ getColumnNumber(): number | null {
+ return columnNumber;
+ },
+ 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",
+ lineNumber: 23,
+ columnNumber: 0,
+ });
+ assert(result.fileName.endsWith(".ts"));
+ assert(result.lineNumber != null);
+ assert(result.columnNumber != null);
+});
diff --git a/cli/tests/unit/event_target_test.ts b/cli/tests/unit/event_target_test.ts
new file mode 100644
index 000000000..0c4eb4d0d
--- /dev/null
+++ b/cli/tests/unit/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?: AddEventListenerOptions
+ ): void {
+ this.addEventListener(type, callback, options);
+ }
+
+ off(
+ type: string,
+ callback: ((e: Event) => void) | null,
+ options?: 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 = (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 eventTargetShouldAcceptAsyncFunctionForEventListenerObject(): 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);
+ }
+);
diff --git a/cli/tests/unit/event_test.ts b/cli/tests/unit/event_test.ts
new file mode 100644
index 000000000..ce3076e58
--- /dev/null
+++ b/cli/tests/unit/event_test.ts
@@ -0,0 +1,94 @@
+// 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);
+ event.stopImmediatePropagation();
+ assertEquals(event.cancelBubble, 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/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts
new file mode 100644
index 000000000..37fca2112
--- /dev/null
+++ b/cli/tests/unit/fetch_test.ts
@@ -0,0 +1,534 @@
+// 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");
+ const _json = await response.json();
+});
+
+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");
+ const _json = await response.json();
+});
+
+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"));
+ const _json = await response.json();
+});
+
+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.
+ // @ts-ignore
+ response.bodyUsed = true;
+ });
+ await response.blob();
+ assertEquals(response.bodyUsed, true);
+});
+
+// TODO(ry) response.body shouldn't be iterable. Instead we should use
+// response.body.getReader().
+/*
+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")));
+ const _json = await response.json();
+});
+*/
+
+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(
+ {
+ 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(
+ {
+ 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>"));
+ }
+);
+
+unitTest(
+ {
+ // FIXME(bartlomieju):
+ // The feature below is not implemented, but the test should work after implementation
+ ignore: true,
+ 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&param2=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 fetchInitFormDataBody(): Promise<void> {
+ const form = new FormData();
+ form.append("field", "value");
+ const response = await fetch("http://localhost:4545/echo_server", {
+ method: "POST",
+ body: form,
+ });
+ const resultForm = await response.formData();
+ assertEquals(form.get("field"), resultForm.get("field"));
+ }
+);
+
+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 [hostname, port] = addr.split(":");
+ const listener = Deno.listen({
+ hostname,
+ port: Number(port),
+ }) as Deno.Listener;
+ const buf = new Deno.Buffer();
+ listener.accept().then(async (conn: Deno.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(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ 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(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ 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(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ 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(
+ {
+ 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(
+ {
+ 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 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/tests/unit/file_test.ts b/cli/tests/unit/file_test.ts
new file mode 100644
index 000000000..4941554ad
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts
new file mode 100644
index 000000000..a035c7074
--- /dev/null
+++ b/cli/tests/unit/files_test.ts
@@ -0,0 +1,571 @@
+// 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(file, Deno.stdout);
+ const fileSize = Deno.statSync(filename).size;
+ assertEquals(bytesWritten, fileSize);
+ console.log("bytes written", bytesWritten);
+ file.close();
+});
+
+unitTest({ perms: { read: true } }, async function filesIter(): Promise<void> {
+ const filename = "cli/tests/hello.txt";
+ const file = await Deno.open(filename);
+
+ let totalSize = 0;
+ for await (const buf of Deno.iter(file)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+ file.close();
+});
+
+unitTest(
+ { perms: { read: true } },
+ async function filesIterCustomBufSize(): Promise<void> {
+ const filename = "cli/tests/hello.txt";
+ const file = await Deno.open(filename);
+
+ let totalSize = 0;
+ let iterations = 0;
+ for await (const buf of Deno.iter(file, { bufSize: 6 })) {
+ totalSize += buf.byteLength;
+ iterations += 1;
+ }
+
+ assertEquals(totalSize, 12);
+ assertEquals(iterations, 2);
+ file.close();
+ }
+);
+
+unitTest({ perms: { read: true } }, function filesIterSync(): void {
+ const filename = "cli/tests/hello.txt";
+ const file = Deno.openSync(filename);
+
+ let totalSize = 0;
+ for (const buf of Deno.iterSync(file)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+ file.close();
+});
+
+unitTest(
+ { perms: { read: true } },
+ function filesIterSyncCustomBufSize(): void {
+ const filename = "cli/tests/hello.txt";
+ const file = Deno.openSync(filename);
+
+ let totalSize = 0;
+ let iterations = 0;
+ for (const buf of Deno.iterSync(file, { bufSize: 6 })) {
+ totalSize += buf.byteLength;
+ iterations += 1;
+ }
+
+ assertEquals(totalSize, 12);
+ assertEquals(iterations, 2);
+ file.close();
+ }
+);
+
+unitTest(async function readerIter(): Promise<void> {
+ // ref: https://github.com/denoland/deno/issues/2330
+ const encoder = new TextEncoder();
+
+ class TestReader implements Deno.Reader {
+ #offset = 0;
+ #buf: Uint8Array;
+
+ constructor(s: string) {
+ this.#buf = new Uint8Array(encoder.encode(s));
+ }
+
+ read(p: Uint8Array): Promise<number | null> {
+ 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 Promise.resolve(null);
+ }
+
+ return Promise.resolve(n);
+ }
+ }
+
+ const reader = new TestReader("hello world!");
+
+ let totalSize = 0;
+ for await (const buf of Deno.iter(reader)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+unitTest(async function readerIterSync(): Promise<void> {
+ // ref: https://github.com/denoland/deno/issues/2330
+ const encoder = new TextEncoder();
+
+ class TestReader implements Deno.ReaderSync {
+ #offset = 0;
+ #buf: Uint8Array;
+
+ constructor(s: string) {
+ this.#buf = new Uint8Array(encoder.encode(s));
+ }
+
+ readSync(p: Uint8Array): number | null {
+ 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 null;
+ }
+
+ return n;
+ }
+ }
+
+ const reader = new TestReader("hello world!");
+
+ let totalSize = 0;
+ for await (const buf of Deno.iterSync(reader)) {
+ totalSize += buf.byteLength;
+ }
+
+ assertEquals(totalSize, 12);
+});
+
+unitTest(
+ {
+ perms: { read: true, write: true },
+ },
+ function openSyncMode(): void {
+ const path = Deno.makeTempDirSync() + "/test_openSync.txt";
+ const file = Deno.openSync(path, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ file.close();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+ }
+);
+
+unitTest(
+ {
+ perms: { read: true, write: true },
+ },
+ async function openMode(): Promise<void> {
+ const path = (await Deno.makeTempDir()) + "/test_open.txt";
+ const file = await Deno.open(path, {
+ write: true,
+ createNew: true,
+ mode: 0o626,
+ });
+ file.close();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
+ }
+ }
+);
+
+unitTest(
+ { perms: { write: false } },
+ async function writePermFailure(): Promise<void> {
+ const filename = "tests/hello.txt";
+ const openOptions: Deno.OpenOptions[] = [{ write: true }, { append: true }];
+ for (const options of openOptions) {
+ let err;
+ try {
+ await Deno.open(filename, options);
+ } 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", { read: true });
+ await Deno.open("cli/tests/fixture.json", { read: true });
+ } 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, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+
+ // 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";
+ let err;
+ try {
+ await Deno.open(filename, { read: true });
+ } 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.size === 0);
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ await f.write(data);
+ fileInfo = Deno.statSync(filename);
+ assert(fileInfo.size === 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, {
+ create: true,
+ write: true,
+ truncate: true,
+ });
+ // assert file was created
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assertEquals(fileInfo.size, 0);
+ // write some data
+ await file.write(data);
+ fileInfo = Deno.statSync(filename);
+ assertEquals(fileInfo.size, 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, {
+ write: true,
+ truncate: true,
+ });
+ file.close();
+ const fileSize = Deno.statSync(filename).size;
+ 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, {
+ write: true,
+ truncate: true,
+ create: true,
+ read: true,
+ });
+ const seekPosition = 0;
+ // assert file was created
+ let fileInfo = Deno.statSync(filename);
+ assert(fileInfo.isFile);
+ assertEquals(fileInfo.size, 0);
+ // write some data
+ await file.write(data);
+ fileInfo = Deno.statSync(filename);
+ assertEquals(fileInfo.size, 13);
+
+ const buf = new Uint8Array(20);
+ // seeking from beginning of a file
+ const cursorPosition = await file.seek(seekPosition, Deno.SeekMode.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.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.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.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.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.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.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/tests/unit/form_data_test.ts b/cli/tests/unit/form_data_test.ts
new file mode 100644
index 000000000..10cbd30a7
--- /dev/null
+++ b/cli/tests/unit/form_data_test.ts
@@ -0,0 +1,203 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import {
+ unitTest,
+ assert,
+ assertEquals,
+ assertStrContains,
+} from "./test_util.ts";
+
+unitTest({ ignore: true }, 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 fromDataUseDomFile(): void {
+ const formData = new FormData();
+ const file = new File(["foo"], "bar", {
+ type: "text/plain",
+ });
+ formData.append("file", file);
+ assertEquals(formData.get("file"), file);
+});
+
+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);
+ assertStrContains(
+ errMsg,
+ `${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);
+ assertStrContains(
+ errMsg,
+ `${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);
+ assertStrContains(
+ errMsg,
+ `${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/tests/unit/format_error_test.ts b/cli/tests/unit/format_error_test.ts
new file mode 100644
index 000000000..ae7559c82
--- /dev/null
+++ b/cli/tests/unit/format_error_test.ts
@@ -0,0 +1,33 @@
+// 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 Deno.errors.InvalidData);
+ thrown = true;
+ }
+ assert(thrown);
+});
diff --git a/cli/tests/unit/fs_events_test.ts b/cli/tests/unit/fs_events_test.ts
new file mode 100644
index 000000000..ad8ba8502
--- /dev/null
+++ b/cli/tests/unit/fs_events_test.ts
@@ -0,0 +1,71 @@
+// 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 watchFsPermissions() {
+ let thrown = false;
+ try {
+ Deno.watchFs(".");
+ } catch (err) {
+ assert(err instanceof Deno.errors.PermissionDenied);
+ thrown = true;
+ }
+ assert(thrown);
+});
+
+unitTest({ perms: { read: true } }, function watchFsInvalidPath() {
+ let thrown = false;
+ try {
+ Deno.watchFs("non-existant.file");
+ } catch (err) {
+ console.error(err);
+ if (Deno.build.os === "windows") {
+ assert(
+ err.message.includes(
+ "Input watch path is neither a file nor a directory"
+ )
+ );
+ } else {
+ assert(err instanceof Deno.errors.NotFound);
+ }
+ 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 watchFsBasic(): Promise<void> {
+ const testDir = await Deno.makeTempDir();
+ const iter = Deno.watchFs(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/tests/unit/get_random_values_test.ts b/cli/tests/unit/get_random_values_test.ts
new file mode 100644
index 000000000..76fa732ea
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/globals_test.ts b/cli/tests/unit/globals_test.ts
new file mode 100644
index 000000000..aa8b4f46e
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/headers_test.ts b/cli/tests/unit/headers_test.ts
new file mode 100644
index 000000000..aaa829837
--- /dev/null
+++ b/cli/tests/unit/headers_test.ts
@@ -0,0 +1,420 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import {
+ unitTest,
+ assert,
+ assertEquals,
+ assertStrContains,
+} from "./test_util.ts";
+const {
+ stringifyArgs,
+ // @ts-ignore TypeScript (as of 3.7) does not support indexing namespaces by symbol
+} = Deno[Deno.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);
+ assertStrContains(
+ errMsg,
+ `${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);
+ assertStrContains(
+ errMsg,
+ `${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);
+ assertStrContains(
+ errMsg,
+ `${method} requires at least 2 arguments, but only 1 present`
+ );
+ });
+});
+
+unitTest(function headersInitMultiple(): void {
+ const headers = new Headers([
+ ["Set-Cookie", "foo=bar"],
+ ["Set-Cookie", "bar=baz"],
+ ["X-Deno", "foo"],
+ ["X-Deno", "bar"],
+ ]);
+ const actual = [...headers];
+ assertEquals(actual, [
+ ["set-cookie", "foo=bar"],
+ ["set-cookie", "bar=baz"],
+ ["x-deno", "foo, bar"],
+ ]);
+});
+
+unitTest(function headersAppendMultiple(): void {
+ const headers = new Headers([
+ ["Set-Cookie", "foo=bar"],
+ ["X-Deno", "foo"],
+ ]);
+ headers.append("set-Cookie", "bar=baz");
+ headers.append("x-Deno", "bar");
+ const actual = [...headers];
+ assertEquals(actual, [
+ ["set-cookie", "foo=bar"],
+ ["x-deno", "foo, bar"],
+ ["set-cookie", "bar=baz"],
+ ]);
+});
+
+unitTest(function headersAppendDuplicateSetCookieKey(): void {
+ const headers = new Headers([["Set-Cookie", "foo=bar"]]);
+ headers.append("set-Cookie", "foo=baz");
+ headers.append("Set-cookie", "baz=bar");
+ const actual = [...headers];
+ assertEquals(actual, [
+ ["set-cookie", "foo=baz"],
+ ["set-cookie", "baz=bar"],
+ ]);
+});
+
+unitTest(function headersSetDuplicateCookieKey(): void {
+ const headers = new Headers([["Set-Cookie", "foo=bar"]]);
+ headers.set("set-Cookie", "foo=baz");
+ headers.set("set-cookie", "bar=qat");
+ const actual = [...headers];
+ assertEquals(actual, [
+ ["set-cookie", "foo=baz"],
+ ["set-cookie", "bar=qat"],
+ ]);
+});
+
+unitTest(function headersGetSetCookie(): void {
+ const headers = new Headers([
+ ["Set-Cookie", "foo=bar"],
+ ["set-Cookie", "bar=qat"],
+ ]);
+ assertEquals(headers.get("SET-COOKIE"), "foo=bar, bar=qat");
+});
+
+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/tests/unit/internals_test.ts b/cli/tests/unit/internals_test.ts
new file mode 100644
index 000000000..abd4c94c3
--- /dev/null
+++ b/cli/tests/unit/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.internal];
+ assert(!!stringifyArgs);
+});
diff --git a/cli/tests/unit/io_test.ts b/cli/tests/unit/io_test.ts
new file mode 100644
index 000000000..0ccd83ea2
--- /dev/null
+++ b/cli/tests/unit/io_test.ts
@@ -0,0 +1,73 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assertEquals } from "./test_util.ts";
+
+const DEFAULT_BUF_SIZE = 32 * 1024;
+
+type Spy = { calls: number };
+
+function repeat(c: string, bytes: number): Uint8Array {
+ assertEquals(c.length, 1);
+ const ui8 = new Uint8Array(bytes);
+ ui8.fill(c.charCodeAt(0));
+ return ui8;
+}
+
+function spyRead(obj: Deno.Buffer): Spy {
+ const spy: Spy = {
+ calls: 0,
+ };
+
+ const orig = obj.read.bind(obj);
+
+ obj.read = (p: Uint8Array): Promise<number | null> => {
+ spy.calls++;
+ return orig(p);
+ };
+
+ return spy;
+}
+
+unitTest(async function copyWithDefaultBufferSize() {
+ const xBytes = repeat("b", DEFAULT_BUF_SIZE);
+ const reader = new Deno.Buffer(xBytes.buffer as ArrayBuffer);
+ const write = new Deno.Buffer();
+
+ const readSpy = spyRead(reader);
+
+ const n = await Deno.copy(reader, write);
+
+ assertEquals(n, xBytes.length);
+ assertEquals(write.length, xBytes.length);
+ assertEquals(readSpy.calls, 2); // read with DEFAULT_BUF_SIZE bytes + read with 0 bytes
+});
+
+unitTest(async function copyWithCustomBufferSize() {
+ const bufSize = 1024;
+ const xBytes = repeat("b", DEFAULT_BUF_SIZE);
+ const reader = new Deno.Buffer(xBytes.buffer as ArrayBuffer);
+ const write = new Deno.Buffer();
+
+ const readSpy = spyRead(reader);
+
+ const n = await Deno.copy(reader, write, { bufSize });
+
+ assertEquals(n, xBytes.length);
+ assertEquals(write.length, xBytes.length);
+ assertEquals(readSpy.calls, DEFAULT_BUF_SIZE / bufSize + 1);
+});
+
+unitTest({ perms: { write: true } }, async function copyBufferToFile() {
+ const filePath = "test-file.txt";
+ // bigger than max File possible buffer 16kb
+ const bufSize = 32 * 1024;
+ const xBytes = repeat("b", bufSize);
+ const reader = new Deno.Buffer(xBytes.buffer as ArrayBuffer);
+ const write = await Deno.open(filePath, { write: true, create: true });
+
+ const n = await Deno.copy(reader, write, { bufSize });
+
+ assertEquals(n, xBytes.length);
+
+ write.close();
+ await Deno.remove(filePath);
+});
diff --git a/cli/tests/unit/link_test.ts b/cli/tests/unit/link_test.ts
new file mode 100644
index 000000000..c6ea4901e
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/make_temp_test.ts b/cli/tests/unit/make_temp_test.ts
new file mode 100644
index 000000000..59fe8c5f5
--- /dev/null
+++ b/cli/tests/unit/make_temp_test.ts
@@ -0,0 +1,178 @@
+// 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(
+ { perms: { read: true, write: true } },
+ function makeTempDirSyncMode(): void {
+ const path = Deno.makeTempDirSync();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o700 & ~Deno.umask());
+ }
+ }
+);
+
+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: { read: true, write: true } },
+ async function makeTempDirMode(): Promise<void> {
+ const path = await Deno.makeTempDir();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o700 & ~Deno.umask());
+ }
+ }
+);
+
+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(
+ { perms: { read: true, write: true } },
+ function makeTempFileSyncMode(): void {
+ const path = Deno.makeTempFileSync();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o600 & ~Deno.umask());
+ }
+ }
+);
+
+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);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function makeTempFileMode(): Promise<void> {
+ const path = await Deno.makeTempFile();
+ const pathInfo = Deno.statSync(path);
+ if (Deno.build.os !== "windows") {
+ assertEquals(pathInfo.mode! & 0o777, 0o600 & ~Deno.umask());
+ }
+ }
+);
diff --git a/cli/tests/unit/metrics_test.ts b/cli/tests/unit/metrics_test.ts
new file mode 100644
index 000000000..9b7d83887
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/mkdir_test.ts b/cli/tests/unit/mkdir_test.ts
new file mode 100644
index 000000000..68755ef4d
--- /dev/null
+++ b/cli/tests/unit/mkdir_test.ts
@@ -0,0 +1,206 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals, assertThrows } from "./test_util.ts";
+
+function assertDirectory(path: string, mode?: number): void {
+ const info = Deno.lstatSync(path);
+ assert(info.isDirectory);
+ if (Deno.build.os !== "windows" && mode !== undefined) {
+ assertEquals(info.mode! & 0o777, mode & ~Deno.umask());
+ }
+}
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function mkdirSyncSuccess(): void {
+ const path = Deno.makeTempDirSync() + "/dir";
+ Deno.mkdirSync(path);
+ assertDirectory(path);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function mkdirSyncMode(): void {
+ const path = Deno.makeTempDirSync() + "/dir";
+ Deno.mkdirSync(path, { mode: 0o737 });
+ assertDirectory(path, 0o737);
+ }
+);
+
+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);
+ assertDirectory(path);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function mkdirMode(): Promise<void> {
+ const path = Deno.makeTempDirSync() + "/dir";
+ await Deno.mkdir(path, { mode: 0o737 });
+ assertDirectory(path, 0o737);
+ }
+);
+
+unitTest({ perms: { write: true } }, function mkdirErrSyncIfExists(): void {
+ let err;
+ try {
+ Deno.mkdirSync(".");
+ } catch (e) {
+ err = e;
+ }
+ assert(err instanceof Deno.errors.AlreadyExists);
+});
+
+unitTest({ perms: { write: true } }, async function mkdirErrIfExists(): Promise<
+ void
+> {
+ let err;
+ try {
+ await Deno.mkdir(".");
+ } 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 });
+ assertDirectory(path);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function mkdirRecursive(): Promise<void> {
+ const path = Deno.makeTempDirSync() + "/nested/directory";
+ await Deno.mkdir(path, { recursive: true });
+ assertDirectory(path);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function mkdirSyncRecursiveMode(): void {
+ const nested = Deno.makeTempDirSync() + "/nested";
+ const path = nested + "/dir";
+ Deno.mkdirSync(path, { mode: 0o737, recursive: true });
+ assertDirectory(path, 0o737);
+ assertDirectory(nested, 0o737);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function mkdirRecursiveMode(): Promise<void> {
+ const nested = Deno.makeTempDirSync() + "/nested";
+ const path = nested + "/dir";
+ await Deno.mkdir(path, { mode: 0o737, recursive: true });
+ assertDirectory(path, 0o737);
+ assertDirectory(nested, 0o737);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function mkdirSyncRecursiveIfExists(): void {
+ const path = Deno.makeTempDirSync() + "/dir";
+ Deno.mkdirSync(path, { mode: 0o737 });
+ Deno.mkdirSync(path, { recursive: true });
+ Deno.mkdirSync(path, { recursive: true, mode: 0o731 });
+ assertDirectory(path, 0o737);
+ if (Deno.build.os !== "windows") {
+ const pathLink = path + "Link";
+ Deno.symlinkSync(path, pathLink);
+ Deno.mkdirSync(pathLink, { recursive: true });
+ Deno.mkdirSync(pathLink, { recursive: true, mode: 0o731 });
+ assertDirectory(path, 0o737);
+ }
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function mkdirRecursiveIfExists(): Promise<void> {
+ const path = Deno.makeTempDirSync() + "/dir";
+ await Deno.mkdir(path, { mode: 0o737 });
+ await Deno.mkdir(path, { recursive: true });
+ await Deno.mkdir(path, { recursive: true, mode: 0o731 });
+ assertDirectory(path, 0o737);
+ if (Deno.build.os !== "windows") {
+ const pathLink = path + "Link";
+ Deno.symlinkSync(path, pathLink);
+ await Deno.mkdir(pathLink, { recursive: true });
+ await Deno.mkdir(pathLink, { recursive: true, mode: 0o731 });
+ assertDirectory(path, 0o737);
+ }
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function mkdirSyncErrors(): void {
+ const testDir = Deno.makeTempDirSync();
+ const emptydir = testDir + "/empty";
+ const fulldir = testDir + "/dir";
+ const file = fulldir + "/file";
+ Deno.mkdirSync(emptydir);
+ Deno.mkdirSync(fulldir);
+ Deno.createSync(file).close();
+
+ assertThrows((): void => {
+ Deno.mkdirSync(emptydir, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(fulldir, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(file, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(file, { recursive: true });
+ }, Deno.errors.AlreadyExists);
+
+ if (Deno.build.os !== "windows") {
+ const fileLink = testDir + "/fileLink";
+ const dirLink = testDir + "/dirLink";
+ const danglingLink = testDir + "/danglingLink";
+ Deno.symlinkSync(file, fileLink);
+ Deno.symlinkSync(emptydir, dirLink);
+ Deno.symlinkSync(testDir + "/nonexistent", danglingLink);
+
+ assertThrows((): void => {
+ Deno.mkdirSync(dirLink, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(fileLink, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(fileLink, { recursive: true });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(danglingLink, { recursive: false });
+ }, Deno.errors.AlreadyExists);
+ assertThrows((): void => {
+ Deno.mkdirSync(danglingLink, { recursive: true });
+ }, Deno.errors.AlreadyExists);
+ }
+ }
+);
diff --git a/cli/tests/unit/net_test.ts b/cli/tests/unit/net_test.ts
new file mode 100644
index 000000000..9e9a1e5e8
--- /dev/null
+++ b/cli/tests/unit/net_test.ts
@@ -0,0 +1,527 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import {
+ unitTest,
+ assert,
+ assertEquals,
+ createResolvable,
+} from "./test_util.ts";
+
+unitTest({ perms: { net: true } }, function netTcpListenClose(): void {
+ const listener = Deno.listen({ hostname: "127.0.0.1", port: 3500 });
+ assert(listener.addr.transport === "tcp");
+ assertEquals(listener.addr.hostname, "127.0.0.1");
+ assertEquals(listener.addr.port, 3500);
+ listener.close();
+});
+
+unitTest(
+ {
+ perms: { net: true },
+ // TODO:
+ ignore: Deno.build.os === "windows",
+ },
+ function netUdpListenClose(): void {
+ const socket = Deno.listenDatagram({
+ hostname: "127.0.0.1",
+ port: 3500,
+ transport: "udp",
+ });
+ assert(socket.addr.transport === "udp");
+ assertEquals(socket.addr.hostname, "127.0.0.1");
+ assertEquals(socket.addr.port, 3500);
+ socket.close();
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ function netUnixListenClose(): void {
+ const filePath = Deno.makeTempFileSync();
+ const socket = Deno.listen({
+ path: filePath,
+ transport: "unix",
+ });
+ assert(socket.addr.transport === "unix");
+ assertEquals(socket.addr.path, filePath);
+ socket.close();
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ function netUnixPacketListenClose(): void {
+ const filePath = Deno.makeTempFileSync();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(socket.addr.transport === "unixpacket");
+ assertEquals(socket.addr.path, filePath);
+ 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(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ async function netUnixCloseWhileAccept(): Promise<void> {
+ const filePath = await Deno.makeTempFile();
+ const listener = Deno.listen({
+ path: filePath,
+ transport: "unix",
+ });
+ 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);
+ }
+);
+
+// TODO(jsouto): Enable when tokio updates mio to v0.7!
+unitTest(
+ { ignore: true, perms: { read: true, write: true } },
+ async function netUnixConcurrentAccept(): Promise<void> {
+ const filePath = await Deno.makeTempFile();
+ const listener = Deno.listen({ transport: "unix", path: filePath });
+ 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 [p, p1];
+ assertEquals(acceptErrCount, 1);
+ }
+);
+
+unitTest({ perms: { net: true } }, async function netTcpDialListen(): Promise<
+ void
+> {
+ const listener = Deno.listen({ port: 3500 });
+ listener.accept().then(
+ async (conn): Promise<void> => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "tcp");
+ assertEquals(conn.localAddr.hostname, "127.0.0.1");
+ assertEquals(conn.localAddr.port, 3500);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ }
+ );
+
+ const conn = await Deno.connect({ hostname: "127.0.0.1", port: 3500 });
+ assert(conn.remoteAddr.transport === "tcp");
+ assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
+ assertEquals(conn.remoteAddr.port, 3500);
+ 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 !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+});
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ async function netUnixDialListen(): Promise<void> {
+ const filePath = await Deno.makeTempFile();
+ const listener = Deno.listen({ path: filePath, transport: "unix" });
+ listener.accept().then(
+ async (conn): Promise<void> => {
+ assert(conn.remoteAddr != null);
+ assert(conn.localAddr.transport === "unix");
+ assertEquals(conn.localAddr.path, filePath);
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ }
+ );
+ const conn = await Deno.connect({ path: filePath, transport: "unix" });
+ assert(conn.remoteAddr.transport === "unix");
+ assertEquals(conn.remoteAddr.path, filePath);
+ assert(conn.remoteAddr != 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 !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { net: true } },
+ async function netUdpSendReceive(): Promise<void> {
+ const alice = Deno.listenDatagram({ port: 3500, transport: "udp" });
+ assert(alice.addr.transport === "udp");
+ assertEquals(alice.addr.port, 3500);
+ assertEquals(alice.addr.hostname, "127.0.0.1");
+
+ const bob = Deno.listenDatagram({ port: 4501, transport: "udp" });
+ assert(bob.addr.transport === "udp");
+ assertEquals(bob.addr.port, 4501);
+ assertEquals(bob.addr.hostname, "127.0.0.1");
+
+ const sent = new Uint8Array([1, 2, 3]);
+ await alice.send(sent, bob.addr);
+
+ const [recvd, remote] = await bob.receive();
+ assert(remote.transport === "udp");
+ assertEquals(remote.port, 3500);
+ assertEquals(recvd.length, 3);
+ assertEquals(1, recvd[0]);
+ assertEquals(2, recvd[1]);
+ assertEquals(3, recvd[2]);
+ alice.close();
+ bob.close();
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ async function netUnixPacketSendReceive(): Promise<void> {
+ const filePath = await Deno.makeTempFile();
+ const alice = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(alice.addr.transport === "unixpacket");
+ assertEquals(alice.addr.path, filePath);
+
+ const bob = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ assert(bob.addr.transport === "unixpacket");
+ assertEquals(bob.addr.path, filePath);
+
+ const sent = new Uint8Array([1, 2, 3]);
+ await alice.send(sent, bob.addr);
+
+ const [recvd, remote] = await bob.receive();
+ assert(remote.transport === "unixpacket");
+ assertEquals(remote.path, filePath);
+ 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 netTcpListenIteratorBreakClosesResource(): Promise<void> {
+ const promise = createResolvable();
+
+ async function iterate(listener: Deno.Listener): Promise<void> {
+ let i = 0;
+
+ for await (const conn of listener) {
+ conn.close();
+ i++;
+
+ if (i > 1) {
+ break;
+ }
+ }
+
+ promise.resolve();
+ }
+
+ const addr = { hostname: "127.0.0.1", port: 8888 };
+ const listener = Deno.listen(addr);
+ iterate(listener);
+
+ await new Promise((resolve: () => void, _) => {
+ setTimeout(resolve, 100);
+ });
+ const conn1 = await Deno.connect(addr);
+ conn1.close();
+ const conn2 = await Deno.connect(addr);
+ conn2.close();
+
+ await promise;
+ }
+);
+
+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(
+ { ignore: Deno.build.os === "windows", perms: { net: true } },
+ async function netUdpListenCloseWhileIterating(): Promise<void> {
+ const socket = Deno.listenDatagram({ 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 });
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ async function netUnixListenCloseWhileIterating(): Promise<void> {
+ const filePath = Deno.makeTempFileSync();
+ const socket = Deno.listen({ path: filePath, transport: "unix" });
+ 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 });
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ async function netUnixPacketListenCloseWhileIterating(): Promise<void> {
+ const filePath = Deno.makeTempFileSync();
+ const socket = Deno.listenDatagram({
+ path: filePath,
+ transport: "unixpacket",
+ });
+ 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 });
+ }
+);
+
+unitTest(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ perms: { net: true },
+ },
+ async function netListenAsyncIterator(): Promise<void> {
+ const addr = { hostname: "127.0.0.1", port: 3500 };
+ const listener = Deno.listen(addr);
+ const runAsyncIterator = async (): Promise<void> => {
+ for await (const conn of listener) {
+ await conn.write(new Uint8Array([1, 2, 3]));
+ conn.close();
+ }
+ };
+ runAsyncIterator();
+ const conn = await Deno.connect(addr);
+ 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 !== null);
+
+ const readResult2 = await conn.read(buf);
+ assertEquals(readResult2, null);
+
+ listener.close();
+ conn.close();
+ }
+);
+
+unitTest(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ perms: { net: true },
+ },
+ async function netCloseWriteSuccess() {
+ const addr = { hostname: "127.0.0.1", port: 3500 };
+ const listener = Deno.listen(addr);
+ const closeDeferred = createResolvable();
+ listener.accept().then(async (conn) => {
+ await conn.write(new Uint8Array([1, 2, 3]));
+ await closeDeferred;
+ 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();
+ }
+);
+
+unitTest(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ perms: { net: true },
+ },
+ async function netDoubleCloseWrite() {
+ const addr = { hostname: "127.0.0.1", port: 3500 };
+ const listener = Deno.listen(addr);
+ const closeDeferred = createResolvable();
+ listener.accept().then(async (conn) => {
+ await closeDeferred;
+ 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();
+ }
+);
+
+unitTest(
+ {
+ perms: { net: true },
+ },
+ async function netHangsOnClose() {
+ let acceptedConn: Deno.Conn;
+ const resolvable = createResolvable();
+
+ async function iteratorReq(listener: Deno.Listener): Promise<void> {
+ const p = new Uint8Array(10);
+ const conn = await listener.accept();
+ acceptedConn = conn;
+
+ try {
+ while (true) {
+ const nread = await conn.read(p);
+ if (nread === null) {
+ break;
+ }
+ await conn.write(new Uint8Array([1, 2, 3]));
+ }
+ } catch (err) {
+ assert(!!err);
+ assert(err instanceof Deno.errors.BadResource);
+ }
+
+ resolvable.resolve();
+ }
+
+ const addr = { hostname: "127.0.0.1", port: 3500 };
+ const listener = Deno.listen(addr);
+ iteratorReq(listener);
+ const conn = await Deno.connect(addr);
+ await conn.write(new Uint8Array([1, 2, 3, 4]));
+ const buf = new Uint8Array(10);
+ await conn.read(buf);
+ conn!.close();
+ acceptedConn!.close();
+ listener.close();
+ await resolvable;
+ }
+);
diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts
new file mode 100644
index 000000000..e99002534
--- /dev/null
+++ b/cli/tests/unit/os_test.ts
@@ -0,0 +1,338 @@
+// 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 {
+ Deno.env.set("TEST_VAR", "A");
+ const env = Deno.env.toObject();
+ Deno.env.set("TEST_VAR", "B");
+ assertEquals(env["TEST_VAR"], "A");
+ assertNotEquals(Deno.env.get("TEST_VAR"), env["TEST_VAR"]);
+});
+
+unitTest({ perms: { env: true } }, function envNotFound(): void {
+ const r = Deno.env.get("env_var_does_not_exist!");
+ assertEquals(r, undefined);
+});
+
+unitTest(function envPermissionDenied1(): void {
+ let err;
+ try {
+ Deno.env.toObject();
+ } 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.get("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(
+ {
+ ignore: Deno.build.os !== "windows",
+ perms: { read: true, 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.get(k))
+ )`;
+ const proc = Deno.run({
+ cmd: [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.get("path"), Deno.env.get("PATH"));
+ assertEquals(Deno.env.get("Path"), Deno.env.get("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 = "darwin" | "windows" | "linux";
+
+ interface Runtime {
+ os: supportOS;
+ shouldHaveValue: boolean;
+ }
+
+ interface Scenes {
+ kind: Deno.DirKind;
+ runtime: Runtime[];
+ }
+
+ const scenes: Scenes[] = [
+ {
+ kind: "config",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "cache",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "executable",
+ runtime: [
+ { os: "darwin", shouldHaveValue: false },
+ { os: "windows", shouldHaveValue: false },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "data",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "data_local",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "audio",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "desktop",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "document",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "download",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "font",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: false },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "picture",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "public",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "template",
+ runtime: [
+ { os: "darwin", shouldHaveValue: false },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: false },
+ ],
+ },
+ {
+ kind: "tmp",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", shouldHaveValue: true },
+ { os: "linux", shouldHaveValue: true },
+ ],
+ },
+ {
+ kind: "video",
+ runtime: [
+ { os: "darwin", shouldHaveValue: true },
+ { os: "windows", 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: { read: true } }, function execPath(): void {
+ assertNotEquals(Deno.execPath(), "");
+});
+
+unitTest({ perms: { read: 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/tests/unit/performance_test.ts b/cli/tests/unit/performance_test.ts
new file mode 100644
index 000000000..89b7cad8b
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts
new file mode 100644
index 000000000..7528768a1
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/process_test.ts b/cli/tests/unit/process_test.ts
new file mode 100644
index 000000000..1ea6f95b7
--- /dev/null
+++ b/cli/tests/unit/process_test.ts
@@ -0,0 +1,386 @@
+// 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,
+ writeFileSync,
+} = Deno;
+
+unitTest(function runPermissions(): void {
+ let caughtError = false;
+ try {
+ run({ cmd: ["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({
+ cmd: ["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({
+ cmd: ["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.
+ ignore: Deno.build.os === "windows",
+ perms: { run: true },
+ },
+ async function runCommandFailedWithSignal(): Promise<void> {
+ const p = run({
+ cmd: ["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({ cmd: ["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
+`;
+
+ writeFileSync(`${cwd}/${pyProgramFile}.py`, enc.encode(pyProgram));
+ const p = run({
+ cwd,
+ cmd: ["python", `${pyProgramFile}.py`],
+ });
+
+ // Write the expected exit code *after* starting python.
+ // This is how we verify that `run()` is actually asynchronous.
+ const code = 84;
+ 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({
+ cmd: ["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({
+ cmd: ["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 === null) {
+ throw new Error("p.stdout.read(...) should not be null");
+ }
+ assertEquals(r, 5);
+ const s = new TextDecoder().decode(data.subarray(0, r));
+ assertEquals(s, "hello");
+ r = await p.stdout!.read(data);
+ assertEquals(r, null);
+ 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({
+ cmd: ["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 === null) {
+ throw new Error("p.stderr.read should not return null here");
+ }
+ assertEquals(r, 5);
+ const s = new TextDecoder().decode(data.subarray(0, r));
+ assertEquals(s, "hello");
+ r = await p.stderr!.read(data);
+ assertEquals(r, null);
+ 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({
+ cmd: ["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({
+ cmd: ["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, {
+ create: true,
+ write: true,
+ });
+
+ const p = run({
+ cmd: [
+ "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);
+
+ const p = run({
+ cmd: ["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({
+ cmd: [
+ "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({
+ cmd: [
+ "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, null);
+ p.stderr!.close();
+});
+
+unitTest(function signalNumbers(): void {
+ if (Deno.build.os === "darwin") {
+ assertEquals(Deno.Signal.SIGSTOP, 17);
+ } else if (Deno.build.os === "linux") {
+ assertEquals(Deno.Signal.SIGSTOP, 19);
+ }
+});
+
+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.
+ 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({
+ cmd: ["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 } }, function killFailed(): void {
+ const p = run({
+ cmd: ["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/tests/unit/read_dir_test.ts b/cli/tests/unit/read_dir_test.ts
new file mode 100644
index 000000000..79e2a1507
--- /dev/null
+++ b/cli/tests/unit/read_dir_test.ts
@@ -0,0 +1,82 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+function assertSameContent(files: Deno.DirEntry[]): void {
+ let counter = 0;
+
+ for (const entry of files) {
+ if (entry.name === "subdir") {
+ assert(entry.isDirectory);
+ counter++;
+ }
+ }
+
+ assertEquals(counter, 1);
+}
+
+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 = [];
+ for await (const dirEntry of Deno.readDir("cli/tests/")) {
+ files.push(dirEntry);
+ }
+ assertSameContent(files);
+});
+
+unitTest({ perms: { read: false } }, async function readDirPerm(): Promise<
+ void
+> {
+ let caughtError = false;
+ try {
+ await Deno.readDir("tests/")[Symbol.asyncIterator]().next();
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.PermissionDenied);
+ }
+ assert(caughtError);
+});
diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts
new file mode 100644
index 000000000..0d3cdf422
--- /dev/null
+++ b/cli/tests/unit/read_file_test.ts
@@ -0,0 +1,65 @@
+// 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);
+});
+
+unitTest({ perms: { read: true } }, function readFileSyncLoop(): void {
+ for (let i = 0; i < 256; i++) {
+ Deno.readFileSync("cli/tests/fixture.json");
+ }
+});
diff --git a/cli/tests/unit/read_link_test.ts b/cli/tests/unit/read_link_test.ts
new file mode 100644
index 000000000..6821d8dc8
--- /dev/null
+++ b/cli/tests/unit/read_link_test.ts
@@ -0,0 +1,73 @@
+// 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 !== "windows") {
+ Deno.symlinkSync(target, symlink);
+ const targetPath = Deno.readLinkSync(symlink);
+ assertEquals(targetPath, target);
+ }
+ }
+);
+
+unitTest({ perms: { read: false } }, function readLinkSyncPerm(): 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 !== "windows") {
+ 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/tests/unit/read_text_file_test.ts b/cli/tests/unit/read_text_file_test.ts
new file mode 100644
index 000000000..3e7493e4a
--- /dev/null
+++ b/cli/tests/unit/read_text_file_test.ts
@@ -0,0 +1,61 @@
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void {
+ const data = Deno.readTextFileSync("cli/tests/fixture.json");
+ assert(data.length > 0);
+ const pkg = JSON.parse(data);
+ assertEquals(pkg.name, "deno");
+});
+
+unitTest({ perms: { read: false } }, function readTextFileSyncPerm(): void {
+ let caughtError = false;
+ try {
+ Deno.readTextFileSync("cli/tests/fixture.json");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.PermissionDenied);
+ }
+ assert(caughtError);
+});
+
+unitTest({ perms: { read: true } }, function readTextFileSyncNotFound(): void {
+ let caughtError = false;
+ let data;
+ try {
+ data = Deno.readTextFileSync("bad_filename");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.NotFound);
+ }
+ assert(caughtError);
+ assert(data === undefined);
+});
+
+unitTest(
+ { perms: { read: true } },
+ async function readTextFileSuccess(): Promise<void> {
+ const data = await Deno.readTextFile("cli/tests/fixture.json");
+ assert(data.length > 0);
+ const pkg = JSON.parse(data);
+ assertEquals(pkg.name, "deno");
+ }
+);
+
+unitTest({ perms: { read: false } }, async function readTextFilePerm(): Promise<
+ void
+> {
+ let caughtError = false;
+ try {
+ await Deno.readTextFile("cli/tests/fixture.json");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.PermissionDenied);
+ }
+ assert(caughtError);
+});
+
+unitTest({ perms: { read: true } }, function readTextFileSyncLoop(): void {
+ for (let i = 0; i < 256; i++) {
+ Deno.readTextFileSync("cli/tests/fixture.json");
+ }
+});
diff --git a/cli/tests/unit/real_path_test.ts b/cli/tests/unit/real_path_test.ts
new file mode 100644
index 000000000..c88955270
--- /dev/null
+++ b/cli/tests/unit/real_path_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 !== "windows") {
+ assert(realPath.startsWith("/"));
+ } else {
+ assert(/^[A-Z]/.test(realPath));
+ }
+ assert(realPath.endsWith(incompletePath));
+});
+
+unitTest(
+ {
+ ignore: Deno.build.os === "windows",
+ 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 !== "windows") {
+ assert(realPath.startsWith("/"));
+ } else {
+ assert(/^[A-Z]/.test(realPath));
+ }
+ assert(realPath.endsWith(incompletePath));
+});
+
+unitTest(
+ {
+ ignore: Deno.build.os === "windows",
+ 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/tests/unit/remove_test.ts b/cli/tests/unit/remove_test.ts
new file mode 100644
index 000000000..35e5c821e
--- /dev/null
+++ b/cli/tests/unit/remove_test.ts
@@ -0,0 +1,506 @@
+// 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";
+ if (Deno.build.os === "windows") {
+ Deno.symlinkSync("unexistent_file", danglingSymlinkPath, {
+ type: "file",
+ });
+ } else {
+ Deno.symlinkSync("unexistent_file", danglingSymlinkPath);
+ }
+ 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 });
+ if (Deno.build.os === "windows") {
+ Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" });
+ } else {
+ Deno.symlinkSync(filePath, validSymlinkPath);
+ }
+ 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";
+ if (Deno.build.os === "windows") {
+ Deno.symlinkSync("unexistent_file", danglingSymlinkPath, {
+ type: "file",
+ });
+ } else {
+ Deno.symlinkSync("unexistent_file", danglingSymlinkPath);
+ }
+ 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 });
+ if (Deno.build.os === "windows") {
+ Deno.symlinkSync(filePath, validSymlinkPath, { type: "file" });
+ } else {
+ Deno.symlinkSync(filePath, validSymlinkPath);
+ }
+ 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");
+});
+
+if (Deno.build.os === "windows") {
+ unitTest(
+ { perms: { run: true, write: true, read: true } },
+ async function removeFileSymlink(): Promise<void> {
+ const symlink = Deno.run({
+ cmd: ["cmd", "/c", "mklink", "file_link", "bar"],
+ stdout: "null",
+ });
+
+ assert(await symlink.status());
+ symlink.close();
+ await Deno.remove("file_link");
+ let err;
+ try {
+ await Deno.lstat("file_link");
+ } catch (e) {
+ err = e;
+ }
+ assert(err instanceof Deno.errors.NotFound);
+ }
+ );
+
+ unitTest(
+ { perms: { run: true, write: true, read: true } },
+ async function removeDirSymlink(): Promise<void> {
+ const symlink = Deno.run({
+ cmd: ["cmd", "/c", "mklink", "/d", "dir_link", "bar"],
+ stdout: "null",
+ });
+
+ assert(await symlink.status());
+ symlink.close();
+
+ await Deno.remove("dir_link");
+ let err;
+ try {
+ await Deno.lstat("dir_link");
+ } catch (e) {
+ err = e;
+ }
+ assert(err instanceof Deno.errors.NotFound);
+ }
+ );
+}
diff --git a/cli/tests/unit/rename_test.ts b/cli/tests/unit/rename_test.ts
new file mode 100644
index 000000000..b4047f906
--- /dev/null
+++ b/cli/tests/unit/rename_test.ts
@@ -0,0 +1,216 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals, assertThrows } from "./test_util.ts";
+
+function assertMissing(path: string): void {
+ let caughtErr = false;
+ let info;
+ try {
+ info = Deno.lstatSync(path);
+ } catch (e) {
+ caughtErr = true;
+ assert(e instanceof Deno.errors.NotFound);
+ }
+ assert(caughtErr);
+ assertEquals(info, undefined);
+}
+
+function assertFile(path: string): void {
+ const info = Deno.lstatSync(path);
+ assert(info.isFile);
+}
+
+function assertDirectory(path: string, mode?: number): void {
+ const info = Deno.lstatSync(path);
+ assert(info.isDirectory);
+ if (Deno.build.os !== "windows" && mode !== undefined) {
+ assertEquals(info.mode! & 0o777, mode & ~Deno.umask());
+ }
+}
+
+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);
+ assertDirectory(newpath);
+ assertMissing(oldpath);
+ }
+);
+
+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);
+ assertDirectory(newpath);
+ assertMissing(oldpath);
+ }
+);
+
+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 });
+}
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ function renameSyncErrorsUnix(): void {
+ const testDir = Deno.makeTempDirSync();
+ const oldfile = testDir + "/oldfile";
+ const olddir = testDir + "/olddir";
+ const emptydir = testDir + "/empty";
+ const fulldir = testDir + "/dir";
+ const file = fulldir + "/file";
+ writeFileString(oldfile, "Hello");
+ Deno.mkdirSync(olddir);
+ Deno.mkdirSync(emptydir);
+ Deno.mkdirSync(fulldir);
+ writeFileString(file, "world");
+
+ assertThrows(
+ (): void => {
+ Deno.renameSync(oldfile, emptydir);
+ },
+ Error,
+ "Is a directory"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, fulldir);
+ },
+ Error,
+ "Directory not empty"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, file);
+ },
+ Error,
+ "Not a directory"
+ );
+
+ const fileLink = testDir + "/fileLink";
+ const dirLink = testDir + "/dirLink";
+ const danglingLink = testDir + "/danglingLink";
+ Deno.symlinkSync(file, fileLink);
+ Deno.symlinkSync(emptydir, dirLink);
+ Deno.symlinkSync(testDir + "/nonexistent", danglingLink);
+
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, fileLink);
+ },
+ Error,
+ "Not a directory"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, dirLink);
+ },
+ Error,
+ "Not a directory"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, danglingLink);
+ },
+ Error,
+ "Not a directory"
+ );
+
+ // should succeed on Unix
+ Deno.renameSync(olddir, emptydir);
+ Deno.renameSync(oldfile, dirLink);
+ Deno.renameSync(dirLink, danglingLink);
+ assertFile(danglingLink);
+ assertEquals("Hello", readFileString(danglingLink));
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os !== "windows", perms: { read: true, write: true } },
+ function renameSyncErrorsWin(): void {
+ const testDir = Deno.makeTempDirSync();
+ const oldfile = testDir + "/oldfile";
+ const olddir = testDir + "/olddir";
+ const emptydir = testDir + "/empty";
+ const fulldir = testDir + "/dir";
+ const file = fulldir + "/file";
+ writeFileString(oldfile, "Hello");
+ Deno.mkdirSync(olddir);
+ Deno.mkdirSync(emptydir);
+ Deno.mkdirSync(fulldir);
+ writeFileString(file, "world");
+
+ assertThrows(
+ (): void => {
+ Deno.renameSync(oldfile, emptydir);
+ },
+ Deno.errors.PermissionDenied,
+ "Access is denied"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, fulldir);
+ },
+ Deno.errors.PermissionDenied,
+ "Access is denied"
+ );
+ assertThrows(
+ (): void => {
+ Deno.renameSync(olddir, emptydir);
+ },
+ Deno.errors.PermissionDenied,
+ "Access is denied"
+ );
+
+ // should succeed on Windows
+ Deno.renameSync(olddir, file);
+ assertDirectory(file);
+ }
+);
diff --git a/cli/tests/unit/request_test.ts b/cli/tests/unit/request_test.ts
new file mode 100644
index 000000000..8a276c5e7
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/resources_test.ts b/cli/tests/unit/resources_test.ts
new file mode 100644
index 000000000..385905a6e
--- /dev/null
+++ b/cli/tests/unit/resources_test.ts
@@ -0,0 +1,61 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assertEquals, assert } from "./test_util.ts";
+
+unitTest(function resourcesCloseBadArgs(): void {
+ let err;
+ try {
+ Deno.close((null as unknown) as number);
+ } catch (e) {
+ err = e;
+ }
+ assert(err instanceof Deno.errors.InvalidData);
+});
+
+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
+ );
+ const tcpStreams = Object.values(res).filter(
+ (r): boolean => r === "tcpStream"
+ );
+ assert(tcpStreams.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/tests/unit/signal_test.ts b/cli/tests/unit/signal_test.ts
new file mode 100644
index 000000000..2f117f8d1
--- /dev/null
+++ b/cli/tests/unit/signal_test.ts
@@ -0,0 +1,195 @@
+// 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(
+ { ignore: Deno.build.os !== "windows" },
+ function signalsNotImplemented(): 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(
+ { ignore: Deno.build.os === "windows", 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);
+ await resolvable;
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", 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);
+ await resolvable;
+ }
+);
+
+unitTest(
+ { ignore: Deno.build.os === "windows", perms: { run: true } },
+ function signalShorthandsTest(): 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/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts
new file mode 100644
index 000000000..7eaf73d58
--- /dev/null
+++ b/cli/tests/unit/stat_test.ts
@@ -0,0 +1,238 @@
+// 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 statSyncSuccess(): 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);
+
+ const tempFile = Deno.makeTempFileSync();
+ const tempInfo = Deno.statSync(tempFile);
+ const now = Date.now();
+ assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
+ assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
+ assert(
+ tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
+ );
+ }
+);
+
+unitTest({ perms: { read: false } }, function statSyncPerm(): 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 } }, function statSyncNotFound(): 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 } }, function lstatSyncSuccess(): 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 } }, function lstatSyncPerm(): 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 } }, function lstatSyncNotFound(): 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, write: 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);
+
+ const tempFile = await Deno.makeTempFile();
+ const tempInfo = await Deno.stat(tempFile);
+ const now = Date.now();
+ assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
+ assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
+
+ assert(
+ tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
+ );
+ }
+);
+
+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(
+ { ignore: Deno.build.os !== "windows", perms: { read: true, write: true } },
+ function statNoUnixFields(): 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(
+ { ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
+ function statUnixFields(): 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/tests/unit/streams_piping_test.ts b/cli/tests/unit/streams_piping_test.ts
new file mode 100644
index 000000000..a947b3821
--- /dev/null
+++ b/cli/tests/unit/streams_piping_test.ts
@@ -0,0 +1,131 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+import { assertThrowsAsync } from "../../../std/testing/asserts.ts";
+
+unitTest(function streamPipeLocks() {
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ assertEquals(rs.locked, false);
+ assertEquals(ws.locked, false);
+
+ rs.pipeTo(ws);
+
+ assert(rs.locked);
+ assert(ws.locked);
+});
+
+unitTest(async function streamPipeFinishUnlocks() {
+ const rs = new ReadableStream({
+ start(controller: ReadableStreamDefaultController): void {
+ controller.close();
+ },
+ });
+ const ws = new WritableStream();
+
+ await rs.pipeTo(ws);
+ assertEquals(rs.locked, false);
+ assertEquals(ws.locked, false);
+});
+
+unitTest(async function streamPipeReadableStreamLocked() {
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ rs.getReader();
+
+ await assertThrowsAsync(async () => {
+ await rs.pipeTo(ws);
+ }, TypeError);
+});
+
+unitTest(async function streamPipeReadableStreamLocked() {
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ ws.getWriter();
+
+ await assertThrowsAsync(async () => {
+ await rs.pipeTo(ws);
+ }, TypeError);
+});
+
+unitTest(async function streamPipeLotsOfChunks() {
+ const CHUNKS = 10;
+
+ const rs = new ReadableStream<number>({
+ start(c: ReadableStreamDefaultController): void {
+ for (let i = 0; i < CHUNKS; ++i) {
+ c.enqueue(i);
+ }
+ c.close();
+ },
+ });
+
+ const written: Array<string | number> = [];
+ const ws = new WritableStream(
+ {
+ write(chunk: number): void {
+ written.push(chunk);
+ },
+ close(): void {
+ written.push("closed");
+ },
+ },
+ new CountQueuingStrategy({ highWaterMark: CHUNKS })
+ );
+
+ await rs.pipeTo(ws);
+ const targetValues = [];
+ for (let i = 0; i < CHUNKS; ++i) {
+ targetValues.push(i);
+ }
+ targetValues.push("closed");
+
+ assertEquals(written, targetValues, "the correct values must be written");
+
+ // Ensure both readable and writable are closed by the time the pipe finishes.
+ await Promise.all([rs.getReader().closed, ws.getWriter().closed]);
+});
+
+for (const preventAbort of [true, false]) {
+ unitTest(function undefinedRejectionFromPull() {
+ const rs = new ReadableStream({
+ pull(): Promise<void> {
+ return Promise.reject(undefined);
+ },
+ });
+
+ return rs.pipeTo(new WritableStream(), { preventAbort }).then(
+ () => {
+ throw new Error("pipeTo promise should be rejected");
+ },
+ (value) =>
+ assertEquals(value, undefined, "rejection value should be undefined")
+ );
+ });
+}
+
+for (const preventCancel of [true, false]) {
+ unitTest(function undefinedRejectionWithPreventCancel() {
+ const rs = new ReadableStream({
+ pull(controller: ReadableStreamDefaultController<number>): void {
+ controller.enqueue(0);
+ },
+ });
+
+ const ws = new WritableStream({
+ write(): Promise<void> {
+ return Promise.reject(undefined);
+ },
+ });
+
+ return rs.pipeTo(ws, { preventCancel }).then(
+ () => {
+ throw new Error("pipeTo promise should be rejected");
+ },
+ (value) =>
+ assertEquals(value, undefined, "rejection value should be undefined")
+ );
+ });
+}
diff --git a/cli/tests/unit/streams_transform_test.ts b/cli/tests/unit/streams_transform_test.ts
new file mode 100644
index 000000000..f3ec148ae
--- /dev/null
+++ b/cli/tests/unit/streams_transform_test.ts
@@ -0,0 +1,562 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import {
+ unitTest,
+ assert,
+ assertEquals,
+ assertNotEquals,
+ assertThrows,
+} from "./test_util.ts";
+
+function delay(seconds: number): Promise<void> {
+ return new Promise<void>((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, seconds);
+ });
+}
+
+function readableStreamToArray<R>(
+ readable: { getReader(): ReadableStreamDefaultReader<R> },
+ reader?: ReadableStreamDefaultReader<R>
+): Promise<R[]> {
+ if (reader === undefined) {
+ reader = readable.getReader();
+ }
+
+ const chunks: R[] = [];
+
+ return pump();
+
+ function pump(): Promise<R[]> {
+ return reader!.read().then((result) => {
+ if (result.done) {
+ return chunks;
+ }
+
+ chunks.push(result.value);
+ return pump();
+ });
+ }
+}
+
+unitTest(function transformStreamConstructedWithTransformFunction() {
+ new TransformStream({ transform(): void {} });
+});
+
+unitTest(function transformStreamConstructedNoTransform() {
+ new TransformStream();
+ new TransformStream({});
+});
+
+unitTest(function transformStreamIntstancesHaveProperProperties() {
+ const ts = new TransformStream({ transform(): void {} });
+ const proto = Object.getPrototypeOf(ts);
+
+ const writableStream = Object.getOwnPropertyDescriptor(proto, "writable");
+ assert(writableStream !== undefined, "it has a writable property");
+ assert(!writableStream.enumerable, "writable should be non-enumerable");
+ assertEquals(
+ typeof writableStream.get,
+ "function",
+ "writable should have a getter"
+ );
+ assertEquals(
+ writableStream.set,
+ undefined,
+ "writable should not have a setter"
+ );
+ assert(writableStream.configurable, "writable should be configurable");
+ assert(
+ ts.writable instanceof WritableStream,
+ "writable is an instance of WritableStream"
+ );
+ assert(
+ WritableStream.prototype.getWriter.call(ts.writable),
+ "writable should pass WritableStream brand check"
+ );
+
+ const readableStream = Object.getOwnPropertyDescriptor(proto, "readable");
+ assert(readableStream !== undefined, "it has a readable property");
+ assert(!readableStream.enumerable, "readable should be non-enumerable");
+ assertEquals(
+ typeof readableStream.get,
+ "function",
+ "readable should have a getter"
+ );
+ assertEquals(
+ readableStream.set,
+ undefined,
+ "readable should not have a setter"
+ );
+ assert(readableStream.configurable, "readable should be configurable");
+ assert(
+ ts.readable instanceof ReadableStream,
+ "readable is an instance of ReadableStream"
+ );
+ assertNotEquals(
+ ReadableStream.prototype.getReader.call(ts.readable),
+ undefined,
+ "readable should pass ReadableStream brand check"
+ );
+});
+
+unitTest(function transformStreamWritableStartsAsWritable() {
+ const ts = new TransformStream({ transform(): void {} });
+
+ const writer = ts.writable.getWriter();
+ assertEquals(writer.desiredSize, 1, "writer.desiredSize should be 1");
+});
+
+unitTest(async function transformStreamReadableCanReadOutOfWritable() {
+ const ts = new TransformStream();
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ assertEquals(
+ writer.desiredSize,
+ 0,
+ "writer.desiredSize should be 0 after write()"
+ );
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "a",
+ "result from reading the readable is the same as was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+
+ await delay(0);
+ assert(writer.desiredSize === 1, "desiredSize should be 1 again");
+});
+
+unitTest(async function transformStreamCanReadWhatIsWritten() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): void {
+ c.enqueue(chunk.toUpperCase());
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "A",
+ "result from reading the readable is the transformation of what was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamCanReadBothChunks() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): void {
+ c.enqueue(chunk.toUpperCase());
+ c.enqueue(chunk.toUpperCase());
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const reader = ts.readable.getReader();
+
+ const result1 = await reader.read();
+ assertEquals(
+ result1.value,
+ "A",
+ "the first chunk read is the transformation of the single chunk written"
+ );
+ assert(!result1.done, "stream should not be done");
+
+ const result2 = await reader.read();
+ assertEquals(
+ result2.value,
+ "A",
+ "the second chunk read is also the transformation of the single chunk written"
+ );
+ assert(!result2.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamCanReadWhatIsWritten() {
+ let c: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(chunk: string): Promise<void> {
+ return delay(0).then(() => c.enqueue(chunk.toUpperCase()));
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result = await ts.readable.getReader().read();
+ assertEquals(
+ result.value,
+ "A",
+ "result from reading the readable is the transformation of what was written to writable"
+ );
+ assert(!result.done, "stream should not be done");
+});
+
+unitTest(async function transformStreamAsyncReadMultipleChunks() {
+ let doSecondEnqueue: () => void;
+ let returnFromTransform: () => void;
+ const ts = new TransformStream({
+ transform(
+ chunk: string,
+ controller: TransformStreamDefaultController
+ ): Promise<void> {
+ delay(0).then(() => controller.enqueue(chunk.toUpperCase()));
+ doSecondEnqueue = (): void => controller.enqueue(chunk.toUpperCase());
+ return new Promise((resolve) => {
+ returnFromTransform = resolve;
+ });
+ },
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+
+ const result1 = await reader.read();
+ assertEquals(
+ result1.value,
+ "A",
+ "the first chunk read is the transformation of the single chunk written"
+ );
+ assert(!result1.done, "stream should not be done");
+ doSecondEnqueue!();
+
+ const result2 = await reader.read();
+ assertEquals(
+ result2.value,
+ "A",
+ "the second chunk read is also the transformation of the single chunk written"
+ );
+ assert(!result2.done, "stream should not be done");
+ returnFromTransform!();
+});
+
+unitTest(function transformStreamClosingWriteClosesRead() {
+ const ts = new TransformStream({ transform(): void {} });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]).then(
+ undefined
+ );
+});
+
+unitTest(async function transformStreamCloseWaitAwaitsTransforms() {
+ let transformResolve: () => void;
+ const transformPromise = new Promise<void>((resolve) => {
+ transformResolve = resolve;
+ });
+ const ts = new TransformStream(
+ {
+ transform(): Promise<void> {
+ return transformPromise;
+ },
+ },
+ undefined,
+ { highWaterMark: 1 }
+ );
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ let rsClosed = false;
+ ts.readable.getReader().closed.then(() => {
+ rsClosed = true;
+ });
+
+ await delay(0);
+ assertEquals(rsClosed, false, "readable is not closed after a tick");
+ transformResolve!();
+
+ await writer.closed;
+ // TODO: Is this expectation correct?
+ assertEquals(rsClosed, true, "readable is closed at that point");
+});
+
+unitTest(async function transformStreamCloseWriteAfterSyncEnqueues() {
+ let c: TransformStreamDefaultController<string>;
+ const ts = new TransformStream<string, string>({
+ start(controller: TransformStreamDefaultController): void {
+ c = controller;
+ },
+ transform(): Promise<void> {
+ c.enqueue("x");
+ c.enqueue("y");
+ return delay(0);
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["x", "y"],
+ "both enqueued chunks can be read from the readable"
+ );
+});
+
+unitTest(async function transformStreamWritableCloseAsyncAfterAsyncEnqueues() {
+ let c: TransformStreamDefaultController<string>;
+ const ts = new TransformStream<string, string>({
+ start(controller: TransformStreamDefaultController<string>): void {
+ c = controller;
+ },
+ transform(): Promise<void> {
+ return delay(0)
+ .then(() => c.enqueue("x"))
+ .then(() => c.enqueue("y"))
+ .then(() => delay(0));
+ },
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["x", "y"],
+ "both enqueued chunks can be read from the readable"
+ );
+});
+
+unitTest(async function transformStreamTransformerMethodsCalledAsMethods() {
+ let c: TransformStreamDefaultController<string>;
+ const transformer = {
+ suffix: "-suffix",
+
+ start(controller: TransformStreamDefaultController<string>): void {
+ c = controller;
+ c.enqueue("start" + this.suffix);
+ },
+
+ transform(chunk: string): void {
+ c.enqueue(chunk + this.suffix);
+ },
+
+ flush(): void {
+ c.enqueue("flushed" + this.suffix);
+ },
+ };
+ const ts = new TransformStream(transformer);
+
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ await writer.closed;
+ const chunks = await readableChunks;
+ assertEquals(
+ chunks,
+ ["start-suffix", "a-suffix", "flushed-suffix"],
+ "all enqueued chunks have suffixes"
+ );
+});
+
+unitTest(async function transformStreamMethodsShouldNotBeAppliedOrCalled() {
+ function functionWithOverloads(): void {}
+ functionWithOverloads.apply = (): void => {
+ throw new Error("apply() should not be called");
+ };
+ functionWithOverloads.call = (): void => {
+ throw new Error("call() should not be called");
+ };
+ const ts = new TransformStream({
+ start: functionWithOverloads,
+ transform: functionWithOverloads,
+ flush: functionWithOverloads,
+ });
+ const writer = ts.writable.getWriter();
+ writer.write("a");
+ writer.close();
+
+ await readableStreamToArray(ts.readable);
+});
+
+unitTest(async function transformStreamCallTransformSync() {
+ let transformCalled = false;
+ const ts = new TransformStream(
+ {
+ transform(): void {
+ transformCalled = true;
+ },
+ },
+ undefined,
+ { highWaterMark: Infinity }
+ );
+ // transform() is only called synchronously when there is no backpressure and
+ // all microtasks have run.
+ await delay(0);
+ const writePromise = ts.writable.getWriter().write(undefined);
+ assert(transformCalled, "transform() should have been called");
+ await writePromise;
+});
+
+unitTest(function transformStreamCloseWriteCloesesReadWithNoChunks() {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]).then(
+ undefined
+ );
+});
+
+unitTest(function transformStreamEnqueueThrowsAfterTerminate() {
+ new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ controller.terminate();
+ assertThrows(() => {
+ controller.enqueue(undefined);
+ }, TypeError);
+ },
+ });
+});
+
+unitTest(function transformStreamEnqueueThrowsAfterReadableCancel() {
+ let controller: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(c: TransformStreamDefaultController): void {
+ controller = c;
+ },
+ });
+ const cancelPromise = ts.readable.cancel();
+ assertThrows(
+ () => controller.enqueue(undefined),
+ TypeError,
+ undefined,
+ "enqueue should throw"
+ );
+ return cancelPromise;
+});
+
+unitTest(function transformStreamSecondTerminateNoOp() {
+ new TransformStream({
+ start(controller: TransformStreamDefaultController): void {
+ controller.terminate();
+ controller.terminate();
+ },
+ });
+});
+
+unitTest(async function transformStreamTerminateAfterReadableCancelIsNoop() {
+ let controller: TransformStreamDefaultController;
+ const ts = new TransformStream({
+ start(c: TransformStreamDefaultController): void {
+ controller = c;
+ },
+ });
+ const cancelReason = { name: "cancelReason" };
+ const cancelPromise = ts.readable.cancel(cancelReason);
+ controller!.terminate();
+ await cancelPromise;
+ try {
+ await ts.writable.getWriter().closed;
+ } catch (e) {
+ assert(e === cancelReason);
+ return;
+ }
+ throw new Error("closed should have rejected");
+});
+
+unitTest(async function transformStreamStartCalledOnce() {
+ let calls = 0;
+ new TransformStream({
+ start(): void {
+ ++calls;
+ },
+ });
+ await delay(0);
+ assertEquals(calls, 1, "start() should have been called exactly once");
+});
+
+unitTest(function transformStreamReadableTypeThrows() {
+ assertThrows(
+ // eslint-disable-next-line
+ () => new TransformStream({ readableType: "bytes" as any }),
+ RangeError,
+ undefined,
+ "constructor should throw"
+ );
+});
+
+unitTest(function transformStreamWirtableTypeThrows() {
+ assertThrows(
+ // eslint-disable-next-line
+ () => new TransformStream({ writableType: "bytes" as any }),
+ RangeError,
+ undefined,
+ "constructor should throw"
+ );
+});
+
+unitTest(function transformStreamSubclassable() {
+ class Subclass extends TransformStream {
+ extraFunction(): boolean {
+ return true;
+ }
+ }
+ assert(
+ Object.getPrototypeOf(Subclass.prototype) === TransformStream.prototype,
+ "Subclass.prototype's prototype should be TransformStream.prototype"
+ );
+ assert(
+ Object.getPrototypeOf(Subclass) === TransformStream,
+ "Subclass's prototype should be TransformStream"
+ );
+ const sub = new Subclass();
+ assert(
+ sub instanceof TransformStream,
+ "Subclass object should be an instance of TransformStream"
+ );
+ assert(
+ sub instanceof Subclass,
+ "Subclass object should be an instance of Subclass"
+ );
+ const readableGetter = Object.getOwnPropertyDescriptor(
+ TransformStream.prototype,
+ "readable"
+ )!.get;
+ assert(
+ readableGetter!.call(sub) === sub.readable,
+ "Subclass object should pass brand check"
+ );
+ assert(
+ sub.extraFunction(),
+ "extraFunction() should be present on Subclass object"
+ );
+});
diff --git a/cli/tests/unit/streams_writable_test.ts b/cli/tests/unit/streams_writable_test.ts
new file mode 100644
index 000000000..54c1624af
--- /dev/null
+++ b/cli/tests/unit/streams_writable_test.ts
@@ -0,0 +1,253 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assert, assertEquals, assertThrows } from "./test_util.ts";
+
+unitTest(function writableStreamDesiredSizeOnReleasedWriter() {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ writer.releaseLock();
+ assertThrows(() => {
+ writer.desiredSize;
+ }, TypeError);
+});
+
+unitTest(function writableStreamDesiredSizeInitialValue() {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ assertEquals(writer.desiredSize, 1);
+});
+
+unitTest(async function writableStreamDesiredSizeClosed() {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ await writer.close();
+ assertEquals(writer.desiredSize, 0);
+});
+
+unitTest(function writableStreamStartThrowsDesiredSizeNull() {
+ const ws = new WritableStream({
+ start(c): void {
+ c.error();
+ },
+ });
+
+ const writer = ws.getWriter();
+ assertEquals(writer.desiredSize, null, "desiredSize should be null");
+});
+
+unitTest(function getWriterOnClosingStream() {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ ws.getWriter();
+});
+
+unitTest(async function getWriterOnClosedStream() {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ await writer.close();
+ writer.releaseLock();
+
+ ws.getWriter();
+});
+
+unitTest(function getWriterOnAbortedStream() {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.abort();
+ writer.releaseLock();
+
+ ws.getWriter();
+});
+
+unitTest(function getWriterOnErroredStream() {
+ const ws = new WritableStream({
+ start(c): void {
+ c.error();
+ },
+ });
+
+ const writer = ws.getWriter();
+ return writer.closed.then(
+ (v) => {
+ throw new Error(`writer.closed fulfilled unexpectedly with: ${v}`);
+ },
+ () => {
+ writer.releaseLock();
+ ws.getWriter();
+ }
+ );
+});
+
+unitTest(function closedAndReadyOnReleasedWriter() {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.releaseLock();
+
+ return writer.closed.then(
+ (v) => {
+ throw new Error("writer.closed fulfilled unexpectedly with: " + v);
+ },
+ (closedRejection) => {
+ assertEquals(
+ closedRejection.name,
+ "TypeError",
+ "closed promise should reject with a TypeError"
+ );
+ return writer.ready.then(
+ (v) => {
+ throw new Error("writer.ready fulfilled unexpectedly with: " + v);
+ },
+ (readyRejection) =>
+ assertEquals(
+ readyRejection,
+ closedRejection,
+ "ready promise should reject with the same error"
+ )
+ );
+ }
+ );
+});
+
+unitTest(function sinkMethodsCalledAsMethods() {
+ let thisObject: Sink | null = null;
+ // Calls to Sink methods after the first are implicitly ignored. Only the
+ // first value that is passed to the resolver is used.
+ class Sink {
+ start(): void {
+ assertEquals(this, thisObject, "start should be called as a method");
+ }
+
+ write(): void {
+ assertEquals(this, thisObject, "write should be called as a method");
+ }
+
+ close(): void {
+ assertEquals(this, thisObject, "close should be called as a method");
+ }
+
+ abort(): void {
+ assertEquals(this, thisObject, "abort should be called as a method");
+ }
+ }
+
+ const theSink = new Sink();
+ thisObject = theSink;
+ const ws = new WritableStream(theSink);
+
+ const writer = ws.getWriter();
+
+ writer.write("a");
+ const closePromise = writer.close();
+
+ const ws2 = new WritableStream(theSink);
+ const writer2 = ws2.getWriter();
+ const abortPromise = writer2.abort();
+
+ return Promise.all([closePromise, abortPromise]).then(undefined);
+});
+
+unitTest(function sizeShouldNotBeCalledAsMethod() {
+ const strategy = {
+ size(): number {
+ if (this !== undefined) {
+ throw new Error("size called as a method");
+ }
+ return 1;
+ },
+ };
+
+ const ws = new WritableStream({}, strategy);
+ const writer = ws.getWriter();
+ return writer.write("a");
+});
+
+unitTest(function redundantReleaseLockIsNoOp() {
+ const ws = new WritableStream();
+ const writer1 = ws.getWriter();
+ assertEquals(
+ undefined,
+ writer1.releaseLock(),
+ "releaseLock() should return undefined"
+ );
+ const writer2 = ws.getWriter();
+ assertEquals(
+ undefined,
+ writer1.releaseLock(),
+ "no-op releaseLock() should return undefined"
+ );
+ // Calling releaseLock() on writer1 should not interfere with writer2. If it did, then the ready promise would be
+ // rejected.
+ return writer2.ready;
+});
+
+unitTest(function readyPromiseShouldFireBeforeReleaseLock() {
+ const events: string[] = [];
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ // Force the ready promise back to a pending state.
+ const writerPromise = writer.write("dummy");
+ const readyPromise = writer.ready.catch(() => events.push("ready"));
+ const closedPromise = writer.closed.catch(() => events.push("closed"));
+ writer.releaseLock();
+ return Promise.all([readyPromise, closedPromise]).then(() => {
+ assertEquals(
+ events,
+ ["ready", "closed"],
+ "ready promise should fire before closed promise"
+ );
+ // Stop the writer promise hanging around after the test has finished.
+ return Promise.all([writerPromise, ws.abort()]).then(undefined);
+ });
+ });
+});
+
+unitTest(function subclassingWritableStream() {
+ class Subclass extends WritableStream {
+ extraFunction(): boolean {
+ return true;
+ }
+ }
+ assert(
+ Object.getPrototypeOf(Subclass.prototype) === WritableStream.prototype,
+ "Subclass.prototype's prototype should be WritableStream.prototype"
+ );
+ assert(
+ Object.getPrototypeOf(Subclass) === WritableStream,
+ "Subclass's prototype should be WritableStream"
+ );
+ const sub = new Subclass();
+ assert(
+ sub instanceof WritableStream,
+ "Subclass object should be an instance of WritableStream"
+ );
+ assert(
+ sub instanceof Subclass,
+ "Subclass object should be an instance of Subclass"
+ );
+ const lockedGetter = Object.getOwnPropertyDescriptor(
+ WritableStream.prototype,
+ "locked"
+ )!.get!;
+ assert(
+ lockedGetter.call(sub) === sub.locked,
+ "Subclass object should pass brand check"
+ );
+ assert(
+ sub.extraFunction(),
+ "extraFunction() should be present on Subclass object"
+ );
+});
+
+unitTest(function lockedGetterShouldReturnTrue() {
+ const ws = new WritableStream();
+ assert(!ws.locked, "stream should not be locked");
+ ws.getWriter();
+ assert(ws.locked, "stream should be locked");
+});
diff --git a/cli/tests/unit/symlink_test.ts b/cli/tests/unit/symlink_test.ts
new file mode 100644
index 000000000..505a49bab
--- /dev/null
+++ b/cli/tests/unit/symlink_test.ts
@@ -0,0 +1,44 @@
+// 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);
+ // Just for now, until we implement symlink for Windows.
+ Deno.symlinkSync(oldname, newname);
+ 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");
+});
+
+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);
+ await Deno.symlink(oldname, newname);
+ const newNameInfoLStat = Deno.lstatSync(newname);
+ const newNameInfoStat = Deno.statSync(newname);
+ assert(newNameInfoLStat.isSymlink, "NOT SYMLINK");
+ assert(newNameInfoStat.isDirectory, "NOT DIRECTORY");
+ }
+);
diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts
new file mode 100644
index 000000000..1c5b6ff21
--- /dev/null
+++ b/cli/tests/unit/test_util.ts
@@ -0,0 +1,364 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+import { assert, assertEquals } from "../../../std/testing/asserts.ts";
+export {
+ assert,
+ assertThrows,
+ assertEquals,
+ assertMatch,
+ assertNotEquals,
+ assertStrictEq,
+ assertStrContains,
+ unreachable,
+ fail,
+} from "../../../std/testing/asserts.ts";
+export { readLines } from "../../../std/io/bufio.ts";
+export { parse as parseArgs } from "../../../std/flags/mod.ts";
+
+export interface Permissions {
+ read: boolean;
+ write: boolean;
+ net: boolean;
+ env: boolean;
+ run: boolean;
+ plugin: boolean;
+ hrtime: boolean;
+}
+
+export function fmtPerms(perms: Permissions): string {
+ const p = Object.keys(perms)
+ .filter((e): boolean => perms[e as keyof Permissions] === true)
+ .map((key) => `--allow-${key}`);
+
+ if (p.length) {
+ return p.join(" ");
+ }
+
+ return "<no permissions>";
+}
+
+const isGranted = async (name: Deno.PermissionName): Promise<boolean> =>
+ (await Deno.permissions.query({ name })).state === "granted";
+
+export 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"),
+ };
+}
+
+export 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);
+ }
+}
+
+export async function registerUnitTests(): Promise<void> {
+ const processPerms = await getProcessPermissions();
+
+ for (const unitTestDefinition of REGISTERED_UNIT_TESTS) {
+ if (!permissionsMatch(processPerms, unitTestDefinition.perms)) {
+ continue;
+ }
+
+ Deno.test(unitTestDefinition);
+ }
+}
+
+function normalizeTestPermissions(perms: UnitTestPermissions): Permissions {
+ return {
+ read: !!perms.read,
+ write: !!perms.write,
+ net: !!perms.net,
+ run: !!perms.run,
+ env: !!perms.env,
+ plugin: !!perms.plugin,
+ hrtime: !!perms.hrtime,
+ };
+}
+
+interface UnitTestPermissions {
+ read?: boolean;
+ write?: boolean;
+ net?: boolean;
+ env?: boolean;
+ run?: boolean;
+ plugin?: boolean;
+ hrtime?: boolean;
+}
+
+interface UnitTestOptions {
+ ignore?: boolean;
+ perms?: UnitTestPermissions;
+}
+
+interface UnitTestDefinition extends Deno.TestDefinition {
+ ignore: boolean;
+ perms: Permissions;
+}
+
+type TestFunction = () => void | Promise<void>;
+
+export const REGISTERED_UNIT_TESTS: UnitTestDefinition[] = [];
+
+export function unitTest(fn: TestFunction): void;
+export function unitTest(options: UnitTestOptions, fn: TestFunction): void;
+export function unitTest(
+ optionsOrFn: UnitTestOptions | TestFunction,
+ maybeFn?: TestFunction
+): void {
+ assert(optionsOrFn, "At least one argument is required");
+
+ let options: UnitTestOptions;
+ let name: string;
+ let fn: 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");
+ }
+
+ const normalizedPerms = normalizeTestPermissions(options.perms || {});
+ registerPermCombination(normalizedPerms);
+
+ const unitTestDefinition: UnitTestDefinition = {
+ name,
+ fn,
+ ignore: !!options.ignore,
+ perms: normalizedPerms,
+ };
+
+ REGISTERED_UNIT_TESTS.push(unitTestDefinition);
+}
+
+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>;
+}
+
+const encoder = new TextEncoder();
+
+// Replace functions with null, errors with their stack strings, and JSONify.
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function serializeTestMessage(message: any): string {
+ return JSON.stringify({
+ start: message.start && {
+ ...message.start,
+ tests: message.start.tests.map((test: Deno.TestDefinition) => ({
+ ...test,
+ fn: null,
+ })),
+ },
+ testStart: message.testStart && { ...message.testStart, fn: null },
+ testEnd: message.testEnd && {
+ ...message.testEnd,
+ error: String(message.testEnd.error?.stack),
+ },
+ end: message.end && {
+ ...message.end,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ results: message.end.results.map((result: any) => ({
+ ...result,
+ error: result.error?.stack,
+ })),
+ },
+ });
+}
+
+export async function reportToConn(
+ conn: Deno.Conn,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ message: any
+): Promise<void> {
+ const line = serializeTestMessage(message);
+ const encodedMsg = encoder.encode(line + (message.end == null ? "\n" : ""));
+ await Deno.writeAll(conn, encodedMsg);
+ if (message.end != null) {
+ conn.closeWrite();
+ }
+}
+
+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,
+ }
+ )
+ );
+});
+
+/*
+ * Ensure all unit test files (e.g. xxx_test.ts) are present as imports in
+ * cli/tests/unit/unit_tests.ts as it is easy to miss this out
+ */
+unitTest(
+ { perms: { read: true } },
+ function assertAllUnitTestFilesImported(): void {
+ const directoryTestFiles = [...Deno.readDirSync("./cli/tests/unit/")]
+ .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/tests/unit/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/tests/unit/unit_tests.ts is missing import of test file: cli/js/" +
+ dirFile
+ );
+ }
+ });
+ }
+);
diff --git a/cli/tests/unit/testing_test.ts b/cli/tests/unit/testing_test.ts
new file mode 100644
index 000000000..854e7161c
--- /dev/null
+++ b/cli/tests/unit/testing_test.ts
@@ -0,0 +1,27 @@
+// 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("", () => {});
+ },
+ TypeError,
+ "The test name can't be empty"
+ );
+ assertThrows(
+ () => {
+ Deno.test({
+ name: "",
+ fn: () => {},
+ });
+ },
+ TypeError,
+ "The test name can't be empty"
+ );
+});
diff --git a/cli/tests/unit/text_encoding_test.ts b/cli/tests/unit/text_encoding_test.ts
new file mode 100644
index 000000000..87b601f36
--- /dev/null
+++ b/cli/tests/unit/text_encoding_test.ts
@@ -0,0 +1,206 @@
+// 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 textEncodeInto3(): void {
+ const fixture = "𝓽𝓮𝔁𝓽";
+ const encoder = new TextEncoder();
+ const bytes = new Uint8Array(5);
+ const result = encoder.encodeInto(fixture, bytes);
+ assertEquals(result.read, 2);
+ assertEquals(result.written, 4);
+ // prettier-ignore
+ assertEquals(Array.from(bytes), [
+ 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/tests/unit/timers_test.ts b/cli/tests/unit/timers_test.ts
new file mode 100644
index 000000000..7adff0095
--- /dev/null
+++ b/cli/tests/unit/timers_test.ts
@@ -0,0 +1,368 @@
+// 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!,
+ };
+}
+
+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);
+ // Similar false async leaking alarm.
+ // Force next round of polling.
+ await waitForMs(0);
+});
+
+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(function intervalCancelInvalidSilentFail(): 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(function clearTimeoutShouldConvertToNumber(): 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 += "A"));
+ Promise.resolve()
+ .then(() => {
+ setTimeout(() => {
+ s += "B";
+ resolve();
+ });
+ })
+ .then(() => {
+ s += "5";
+ });
+ });
+ setTimeout(() => (s += "6"));
+ Promise.resolve().then(() => (s += "2"));
+ Promise.resolve().then(() =>
+ setTimeout(() => {
+ s += "7";
+ Promise.resolve()
+ .then(() => (s += "8"))
+ .then(() => {
+ s += "9";
+ });
+ })
+ );
+ Promise.resolve().then(() => Promise.resolve().then(() => (s += "3")));
+ s += "1";
+ await promise;
+ assertEquals(s, "0123456789AB");
+});
+
+unitTest(function testQueueMicrotask() {
+ assertEquals(typeof queueMicrotask, "function");
+});
diff --git a/cli/tests/unit/tls_test.ts b/cli/tests/unit/tls_test.ts
new file mode 100644
index 000000000..3d071441d
--- /dev/null
+++ b/cli/tests/unit/tls_test.ts
@@ -0,0 +1,262 @@
+// 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 } },
+ function listenTLSNonExistentCertKeyFiles(): void {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 3500,
+ 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 } }, function listenTLSNoReadPerm(): void {
+ let err;
+ try {
+ Deno.listenTls({
+ hostname: "localhost",
+ port: 3500,
+ 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 },
+ },
+ function listenTLSEmptyKeyFile(): void {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 3500,
+ 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 } },
+ function listenTLSEmptyCertFile(): void {
+ let err;
+ const options = {
+ hostname: "localhost",
+ port: 3500,
+ 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 = 3500;
+
+ 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 !== null, `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 !== null);
+ 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;
+ }
+);
+
+unitTest(
+ { perms: { read: true, net: true } },
+ async function startTls(): Promise<void> {
+ const hostname = "smtp.gmail.com";
+ const port = 587;
+ const encoder = new TextEncoder();
+
+ let conn = await Deno.connect({
+ hostname,
+ port,
+ });
+
+ let writer = new BufWriter(conn);
+ let reader = new TextProtoReader(new BufReader(conn));
+
+ let line: string | null = (await reader.readLine()) as string;
+ assert(line.startsWith("220"));
+
+ await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
+ await writer.flush();
+
+ while ((line = (await reader.readLine()) as string)) {
+ assert(line.startsWith("250"));
+ if (line.startsWith("250 ")) break;
+ }
+
+ await writer.write(encoder.encode("STARTTLS\r\n"));
+ await writer.flush();
+
+ line = await reader.readLine();
+
+ // Received the message that the server is ready to establish TLS
+ assertEquals(line, "220 2.0.0 Ready to start TLS");
+
+ conn = await Deno.startTls(conn, { hostname });
+ writer = new BufWriter(conn);
+ reader = new TextProtoReader(new BufReader(conn));
+
+ // After that use TLS communication again
+ await writer.write(encoder.encode(`EHLO ${hostname}\r\n`));
+ await writer.flush();
+
+ while ((line = (await reader.readLine()) as string)) {
+ assert(line.startsWith("250"));
+ if (line.startsWith("250 ")) break;
+ }
+
+ conn.close();
+ }
+);
diff --git a/cli/tests/unit/truncate_test.ts b/cli/tests/unit/truncate_test.ts
new file mode 100644
index 000000000..3fd85c990
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/tty_test.ts b/cli/tests/unit/tty_test.ts
new file mode 100644
index 000000000..116b0dfe9
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/umask_test.ts b/cli/tests/unit/umask_test.ts
new file mode 100644
index 000000000..bfac65d52
--- /dev/null
+++ b/cli/tests/unit/umask_test.ts
@@ -0,0 +1,15 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { unitTest, assertEquals } from "./test_util.ts";
+
+unitTest(
+ {
+ ignore: Deno.build.os === "windows",
+ },
+ function umaskSuccess(): void {
+ const prevMask = Deno.umask(0o020);
+ const newMask = Deno.umask(prevMask);
+ const finalMask = Deno.umask();
+ assertEquals(newMask, 0o020);
+ assertEquals(finalMask, prevMask);
+ }
+);
diff --git a/cli/tests/unit/unit_test_runner.ts b/cli/tests/unit/unit_test_runner.ts
new file mode 100755
index 000000000..715dda500
--- /dev/null
+++ b/cli/tests/unit/unit_test_runner.ts
@@ -0,0 +1,309 @@
+#!/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 {
+ readLines,
+ permissionCombinations,
+ Permissions,
+ registerUnitTests,
+ fmtPerms,
+ parseArgs,
+ reportToConn,
+} from "./test_util.ts";
+
+// @ts-ignore
+const internalObj = Deno[Deno.internal];
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const reportToConsole = internalObj.reportToConsole as (message: any) => void;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const runTests = internalObj.runTests as (options: any) => Promise<any>;
+
+interface PermissionSetTestResult {
+ perms: Permissions;
+ passed: boolean;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ endMessage: any;
+ permsStr: string;
+}
+
+const PERMISSIONS: Deno.PermissionName[] = [
+ "read",
+ "write",
+ "net",
+ "env",
+ "run",
+ "plugin",
+ "hrtime",
+];
+
+/**
+ * Take a list of permissions and revoke missing permissions.
+ */
+async function dropWorkerPermissions(
+ requiredPermissions: Deno.PermissionName[]
+): Promise<void> {
+ const permsToDrop = PERMISSIONS.filter((p): boolean => {
+ return !requiredPermissions.includes(p);
+ });
+
+ for (const perm of permsToDrop) {
+ await Deno.permissions.revoke({ name: perm });
+ }
+}
+
+async function workerRunnerMain(
+ addrStr: string,
+ permsStr: string,
+ filter?: string
+): Promise<void> {
+ const [hostname, port] = addrStr.split(":");
+ const addr = { hostname, port: Number(port) };
+
+ let perms: Deno.PermissionName[] = [];
+ if (permsStr.length > 0) {
+ perms = permsStr.split(",") as Deno.PermissionName[];
+ }
+ // Setup reporter
+ const conn = await Deno.connect(addr);
+ // Drop current process permissions to requested set
+ await dropWorkerPermissions(perms);
+ // Register unit tests that match process permissions
+ await registerUnitTests();
+ // Execute tests
+ await runTests({
+ exitOnFail: false,
+ filter,
+ reportToConsole: false,
+ onMessage: reportToConn.bind(null, conn),
+ });
+}
+
+function spawnWorkerRunner(
+ verbose: boolean,
+ addr: string,
+ perms: Permissions,
+ filter?: string
+): Deno.Process {
+ // run subsequent tests using same deno executable
+ const permStr = Object.keys(perms)
+ .filter((permName): boolean => {
+ return perms[permName as Deno.PermissionName] === true;
+ })
+ .join(",");
+
+ const cmd = [
+ Deno.execPath(),
+ "run",
+ "--unstable", // TODO(ry) be able to test stable vs unstable
+ "-A",
+ "cli/tests/unit/unit_test_runner.ts",
+ "--worker",
+ `--addr=${addr}`,
+ `--perms=${permStr}`,
+ ];
+
+ if (filter) {
+ cmd.push("--");
+ cmd.push(filter);
+ }
+
+ const ioMode = verbose ? "inherit" : "null";
+
+ const p = Deno.run({
+ cmd,
+ stdin: ioMode,
+ stdout: ioMode,
+ stderr: ioMode,
+ });
+
+ return p;
+}
+
+async function runTestsForPermissionSet(
+ listener: Deno.Listener,
+ addrStr: string,
+ verbose: boolean,
+ perms: Permissions,
+ filter?: string
+): Promise<PermissionSetTestResult> {
+ const permsFmt = fmtPerms(perms);
+ console.log(`Running tests for: ${permsFmt}`);
+ const workerProcess = spawnWorkerRunner(verbose, addrStr, perms, filter);
+ // Wait for worker subprocess to go online
+ const conn = await listener.accept();
+
+ let expectedPassedTests;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let endMessage: any;
+
+ try {
+ for await (const line of readLines(conn)) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const message = JSON.parse(line) as any;
+ reportToConsole(message);
+ if (message.start != null) {
+ expectedPassedTests = message.start.tests.length;
+ } else if (message.end != null) {
+ endMessage = message.end;
+ }
+ }
+ } finally {
+ // Close socket to worker.
+ conn.close();
+ }
+
+ if (expectedPassedTests == null) {
+ throw new Error("Worker runner didn't report start");
+ }
+
+ if (endMessage == null) {
+ throw new Error("Worker runner didn't report end");
+ }
+
+ const workerStatus = await workerProcess.status();
+ if (!workerStatus.success) {
+ throw new Error(
+ `Worker runner exited with status code: ${workerStatus.code}`
+ );
+ }
+
+ workerProcess.close();
+
+ const passed = expectedPassedTests === endMessage.passed + endMessage.ignored;
+
+ return {
+ perms,
+ passed,
+ permsStr: permsFmt,
+ endMessage,
+ };
+}
+
+async function masterRunnerMain(
+ verbose: boolean,
+ filter?: string
+): 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<PermissionSetTestResult>();
+ const addr = { hostname: "127.0.0.1", port: 4510 };
+ const addrStr = `${addr.hostname}:${addr.port}`;
+ const listener = Deno.listen(addr);
+
+ for (const perms of permissionCombinations.values()) {
+ const result = await runTestsForPermissionSet(
+ listener,
+ addrStr,
+ verbose,
+ perms,
+ filter
+ );
+ testResults.add(result);
+ }
+
+ // if any run tests returned non-zero status then whole test
+ // run should fail
+ let testsPassed = true;
+
+ for (const testResult of testResults) {
+ const { permsStr, endMessage } = testResult;
+ console.log(`Summary for ${permsStr}`);
+ reportToConsole({ end: endMessage });
+ testsPassed = testsPassed && testResult.passed;
+ }
+
+ if (!testsPassed) {
+ console.error("Unit tests failed");
+ Deno.exit(1);
+ }
+
+ console.log("Unit tests passed");
+}
+
+const HELP = `Unit test runner
+
+Run tests matching current process permissions:
+
+ deno --allow-write unit_test_runner.ts
+
+ deno --allow-net --allow-hrtime unit_test_runner.ts
+
+ deno --allow-write unit_test_runner.ts -- testWriteFile
+
+Run "master" process that creates "worker" processes
+for each discovered permission combination:
+
+ deno -A unit_test_runner.ts --master
+
+Run worker process for given permissions:
+
+ deno -A unit_test_runner.ts --worker --perms=net,read,write --addr=127.0.0.1:4500
+
+
+OPTIONS:
+ --master
+ Run in master mode, spawning worker processes for
+ each discovered permission combination
+
+ --worker
+ Run in worker mode, requires "perms" and "addr" flags,
+ should be run with "-A" flag; after setup worker will
+ drop permissions to required set specified in "perms"
+
+ --perms=<perm_name>...
+ Set of permissions this process should run tests with,
+
+ --addr=<addr>
+ Address of TCP socket for reporting
+
+ARGS:
+ -- <filter>...
+ Run only tests with names matching filter, must
+ be used after "--"
+`;
+
+function assertOrHelp(expr: unknown): asserts expr {
+ if (!expr) {
+ console.log(HELP);
+ Deno.exit(1);
+ }
+}
+
+async function main(): Promise<void> {
+ const args = parseArgs(Deno.args, {
+ boolean: ["master", "worker", "verbose"],
+ "--": true,
+ });
+
+ if (args.help) {
+ console.log(HELP);
+ return;
+ }
+
+ const filter = args["--"][0];
+
+ // Master mode
+ if (args.master) {
+ return masterRunnerMain(args.verbose, filter);
+ }
+
+ // Worker mode
+ if (args.worker) {
+ assertOrHelp(typeof args.addr === "string");
+ assertOrHelp(typeof args.perms === "string");
+ return workerRunnerMain(args.addr, args.perms, filter);
+ }
+
+ // Running tests matching current process permissions
+ await registerUnitTests();
+ await runTests({ filter });
+}
+
+main();
diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts
new file mode 100644
index 000000000..7327bcc05
--- /dev/null
+++ b/cli/tests/unit/unit_tests.ts
@@ -0,0 +1,72 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This test is executed as part of unit test suite.
+//
+// Test runner automatically spawns subprocesses for each required permissions combination.
+
+import "./abort_controller_test.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 "./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 "./dom_exception_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 "./io_test.ts";
+import "./link_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 "./real_path_test.ts";
+import "./read_dir_test.ts";
+import "./read_text_file_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 "./streams_piping_test.ts";
+import "./streams_transform_test.ts";
+import "./streams_writable_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 "./umask_test.ts";
+import "./url_test.ts";
+import "./url_search_params_test.ts";
+import "./utime_test.ts";
+import "./write_file_test.ts";
+import "./write_text_file_test.ts";
+import "./performance_test.ts";
+import "./version_test.ts";
diff --git a/cli/tests/unit/url_search_params_test.ts b/cli/tests/unit/url_search_params_test.ts
new file mode 100644
index 000000000..7b7dbab76
--- /dev/null
+++ b/cli/tests/unit/url_search_params_test.ts
@@ -0,0 +1,275 @@
+// 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");
+ }
+);
+
+// If a class extends URLSearchParams, override one method should not change another's behavior.
+unitTest(
+ function urlSearchParamsOverridingAppendNotChangeConstructorAndSet(): void {
+ let overridedAppendCalled = 0;
+ class CustomSearchParams extends URLSearchParams {
+ append(name: string, value: string): void {
+ ++overridedAppendCalled;
+ super.append(name, value);
+ }
+ }
+ new CustomSearchParams("foo=bar");
+ new CustomSearchParams([["foo", "bar"]]);
+ new CustomSearchParams(new CustomSearchParams({ foo: "bar" }));
+ new CustomSearchParams().set("foo", "bar");
+ assertEquals(overridedAppendCalled, 0);
+ }
+);
+
+unitTest(function urlSearchParamsOverridingEntriesNotChangeForEach(): void {
+ class CustomSearchParams extends URLSearchParams {
+ *entries(): IterableIterator<[string, string]> {
+ yield* [];
+ }
+ }
+ let loopCount = 0;
+ const params = new CustomSearchParams({ foo: "bar" });
+ params.forEach(() => void ++loopCount);
+ assertEquals(loopCount, 1);
+});
diff --git a/cli/tests/unit/url_test.ts b/cli/tests/unit/url_test.ts
new file mode 100644
index 000000000..68fcbd95e
--- /dev/null
+++ b/cli/tests/unit/url_test.ts
@@ -0,0 +1,407 @@
+// 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 urlNormalize(): void {
+ const url = new URL("http://example.com");
+ assertEquals(url.pathname, "/");
+ assertEquals(url.href, "http://example.com/");
+});
+
+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 urlBackSlashes(): void {
+ const url = new URL(
+ "https:\\\\foo:bar@baz.qat:8000\\qux\\quux?foo=bar&baz=12#qat"
+ );
+ assertEquals(
+ url.href,
+ "https://foo:bar@baz.qat:8000/qux/quux?foo=bar&baz=12#qat"
+ );
+});
+
+unitTest(function urlRequireHost(): void {
+ assertEquals(new URL("file:///").href, "file:///");
+ assertThrows(() => {
+ new URL("ftp:///");
+ });
+ assertThrows(() => {
+ new URL("http:///");
+ });
+ assertThrows(() => {
+ new URL("https:///");
+ });
+ assertThrows(() => {
+ new URL("ws:///");
+ });
+ assertThrows(() => {
+ new URL("wss:///");
+ });
+});
+
+unitTest(function urlDriveLetter() {
+ assertEquals(
+ new URL("file:///C:").href,
+ Deno.build.os == "windows" ? "file:///C:/" : "file:///C:"
+ );
+ assertEquals(new URL("http://example.com/C:").href, "http://example.com/C:");
+});
+
+unitTest(function urlHostnameUpperCase() {
+ assertEquals(new URL("https://EXAMPLE.COM").href, "https://example.com/");
+});
+
+unitTest(function urlTrim() {
+ assertEquals(new URL(" https://example.com ").href, "https://example.com/");
+});
+
+unitTest(function urlEncoding() {
+ assertEquals(
+ new URL("https://a !$&*()=,;+'\"@example.com").username,
+ "a%20!$&*()%3D,%3B+%27%22"
+ );
+ assertEquals(
+ new URL("https://:a !$&*()=,;+'\"@example.com").password,
+ "a%20!$&*()%3D,%3B+%27%22"
+ );
+ // FIXME: https://url.spec.whatwg.org/#idna
+ // assertEquals(
+ // new URL("https://a !$&*()=,+'\"").hostname,
+ // "a%20%21%24%26%2A%28%29%3D%2C+%27%22"
+ // );
+ assertEquals(
+ new URL("https://example.com/a ~!@$&*()=:/,;+'\"\\").pathname,
+ "/a%20~!@$&*()=:/,;+'%22/"
+ );
+ assertEquals(
+ new URL("https://example.com?a ~!@$&*()=:/,;?+'\"\\").search,
+ "?a%20~!@$&*()=:/,;?+%27%22\\"
+ );
+ assertEquals(
+ new URL("https://example.com#a ~!@#$&*()=:/,;?+'\"\\").hash,
+ "#a%20~!@#$&*()=:/,;?+'%22\\"
+ );
+});
+
+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 urlDriveLetterBase() {
+ assertEquals(
+ new URL("/b", "file:///C:/a/b").href,
+ Deno.build.os == "windows" ? "file:///C:/b" : "file:///b"
+ );
+ assertEquals(
+ new URL("D:", "file:///C:/a/b").href,
+ Deno.build.os == "windows" ? "file:///D:/" : "file:///C:/a/D:"
+ );
+ assertEquals(
+ new URL("/D:", "file:///C:/a/b").href,
+ Deno.build.os == "windows" ? "file:///D:/" : "file:///D:"
+ );
+ assertEquals(
+ new URL("D:/b", "file:///C:/a/b").href,
+ Deno.build.os == "windows" ? "file:///D:/b" : "file:///C:/a/D:/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&param2");
+ 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(
+ {
+ // FIXME(bartlomieju)
+ ignore: true,
+ },
+ 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");
+ });
+});
+
+unitTest(function throwForInvalidPortConstructor(): void {
+ const urls = [
+ // If port is greater than 2^16 − 1, validation error, return failure.
+ `https://baz.qat:${2 ** 16}`,
+ "https://baz.qat:-32",
+ "https://baz.qat:deno",
+ "https://baz.qat:9land",
+ "https://baz.qat:10.5",
+ ];
+
+ for (const url of urls) {
+ assertThrows(() => new URL(url), TypeError, "Invalid URL.");
+ }
+
+ // Do not throw for 0 & 65535
+ new URL("https://baz.qat:65535");
+ new URL("https://baz.qat:0");
+});
+
+unitTest(function throwForInvalidSchemeConstructor(): void {
+ assertThrows(
+ () => new URL("invalid_scheme://baz.qat"),
+ TypeError,
+ "Invalid URL."
+ );
+});
+
+unitTest(function doNotOverridePortIfInvalid(): void {
+ const initialPort = "3000";
+ const ports = [
+ // If port is greater than 2^16 − 1, validation error, return failure.
+ `${2 ** 16}`,
+ "-32",
+ "deno",
+ "9land",
+ "10.5",
+ ];
+
+ for (const port of ports) {
+ const url = new URL(`https://deno.land:${initialPort}`);
+ url.port = port;
+ assertEquals(url.port, initialPort);
+ }
+});
+
+unitTest(function emptyPortForSchemeDefaultPort(): void {
+ const nonDefaultPort = "3500";
+ const urls = [
+ { url: "ftp://baz.qat:21", port: "21", protocol: "ftp:" },
+ { url: "https://baz.qat:443", port: "443", protocol: "https:" },
+ { url: "wss://baz.qat:443", port: "443", protocol: "wss:" },
+ { url: "http://baz.qat:80", port: "80", protocol: "http:" },
+ { url: "ws://baz.qat:80", port: "80", protocol: "ws:" },
+ { url: "file://home/index.html", port: "", protocol: "file:" },
+ { url: "/foo", baseUrl: "ftp://baz.qat:21", port: "21", protocol: "ftp:" },
+ {
+ url: "/foo",
+ baseUrl: "https://baz.qat:443",
+ port: "443",
+ protocol: "https:",
+ },
+ {
+ url: "/foo",
+ baseUrl: "wss://baz.qat:443",
+ port: "443",
+ protocol: "wss:",
+ },
+ {
+ url: "/foo",
+ baseUrl: "http://baz.qat:80",
+ port: "80",
+ protocol: "http:",
+ },
+ { url: "/foo", baseUrl: "ws://baz.qat:80", port: "80", protocol: "ws:" },
+ {
+ url: "/foo",
+ baseUrl: "file://home/index.html",
+ port: "",
+ protocol: "file:",
+ },
+ ];
+
+ for (const { url: urlString, baseUrl, port, protocol } of urls) {
+ const url = new URL(urlString, baseUrl);
+ assertEquals(url.port, "");
+
+ url.port = nonDefaultPort;
+ assertEquals(url.port, nonDefaultPort);
+
+ url.port = port;
+ assertEquals(url.port, "");
+
+ // change scheme
+ url.protocol = "sftp:";
+ assertEquals(url.port, port);
+
+ url.protocol = protocol;
+ assertEquals(url.port, "");
+ }
+});
diff --git a/cli/tests/unit/utime_test.ts b/cli/tests/unit/utime_test.ts
new file mode 100644
index 000000000..63727ae62
--- /dev/null
+++ b/cli/tests/unit/utime_test.ts
@@ -0,0 +1,230 @@
+// 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: Date | null, t2: Date): void {
+ assert(t1 instanceof Date);
+ assert(Math.abs(t1.valueOf() - t2.valueOf()) < 10_000);
+}
+
+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.atime, new Date(atime * 1000));
+ assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000));
+ }
+);
+
+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.atime, new Date(atime * 1000));
+ assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function utimeSyncDateSuccess(): void {
+ const testDir = Deno.makeTempDirSync();
+
+ const atime = new Date(1000_000);
+ const mtime = new Date(50000_000);
+ Deno.utimeSync(testDir, atime, mtime);
+
+ const dirInfo = Deno.statSync(testDir);
+ assertFuzzyTimestampEquals(dirInfo.atime, atime);
+ assertFuzzyTimestampEquals(dirInfo.mtime, mtime);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function utimeSyncFileDateSuccess() {
+ const testDir = Deno.makeTempDirSync();
+ const filename = testDir + "/file.txt";
+ Deno.writeFileSync(filename, new TextEncoder().encode("hello"), {
+ mode: 0o666,
+ });
+ const atime = new Date();
+ const mtime = new Date();
+ Deno.utimeSync(filename, atime, mtime);
+
+ const fileInfo = Deno.statSync(filename);
+ assertFuzzyTimestampEquals(fileInfo.atime, atime);
+ assertFuzzyTimestampEquals(fileInfo.mtime, 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.atime, new Date(atime * 1000));
+ assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
+ }
+);
+
+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.atime, new Date(atime * 1000));
+ assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000));
+ }
+);
+
+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.atime, new Date(atime * 1000));
+ assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000));
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function utimeDateSuccess(): Promise<void> {
+ const testDir = Deno.makeTempDirSync();
+
+ const atime = new Date(100_000);
+ const mtime = new Date(5000_000);
+ await Deno.utime(testDir, atime, mtime);
+
+ const dirInfo = Deno.statSync(testDir);
+ assertFuzzyTimestampEquals(dirInfo.atime, atime);
+ assertFuzzyTimestampEquals(dirInfo.mtime, mtime);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function utimeFileDateSuccess(): Promise<void> {
+ const testDir = Deno.makeTempDirSync();
+ const filename = testDir + "/file.txt";
+ Deno.writeFileSync(filename, new TextEncoder().encode("hello"), {
+ mode: 0o666,
+ });
+
+ const atime = new Date();
+ const mtime = new Date();
+ await Deno.utime(filename, atime, mtime);
+
+ const fileInfo = Deno.statSync(filename);
+ assertFuzzyTimestampEquals(fileInfo.atime, atime);
+ assertFuzzyTimestampEquals(fileInfo.mtime, 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/tests/unit/version_test.ts b/cli/tests/unit/version_test.ts
new file mode 100644
index 000000000..0417b27de
--- /dev/null
+++ b/cli/tests/unit/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/tests/unit/write_file_test.ts b/cli/tests/unit/write_file_test.ts
new file mode 100644
index 000000000..80f711dbc
--- /dev/null
+++ b/cli/tests/unit/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 !== "windows") {
+ 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 !== "windows") {
+ 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);
+ }
+);
diff --git a/cli/tests/unit/write_text_file_test.ts b/cli/tests/unit/write_text_file_test.ts
new file mode 100644
index 000000000..321189b0e
--- /dev/null
+++ b/cli/tests/unit/write_text_file_test.ts
@@ -0,0 +1,79 @@
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest(
+ { perms: { read: true, write: true } },
+ function writeTextFileSyncSuccess(): void {
+ const filename = Deno.makeTempDirSync() + "/test.txt";
+ Deno.writeTextFileSync(filename, "Hello");
+ const dataRead = Deno.readTextFileSync(filename);
+ assertEquals("Hello", dataRead);
+ }
+);
+
+unitTest({ perms: { write: true } }, function writeTextFileSyncFail(): void {
+ const filename = "/baddir/test.txt";
+ // The following should fail because /baddir doesn't exist (hopefully).
+ let caughtError = false;
+ try {
+ Deno.writeTextFileSync(filename, "hello");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.NotFound);
+ }
+ assert(caughtError);
+});
+
+unitTest({ perms: { write: false } }, function writeTextFileSyncPerm(): void {
+ const filename = "/baddir/test.txt";
+ // The following should fail due to no write permission
+ let caughtError = false;
+ try {
+ Deno.writeTextFileSync(filename, "Hello");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.PermissionDenied);
+ }
+ assert(caughtError);
+});
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function writeTextFileSuccess(): Promise<void> {
+ const filename = Deno.makeTempDirSync() + "/test.txt";
+ await Deno.writeTextFile(filename, "Hello");
+ const dataRead = Deno.readTextFileSync(filename);
+ assertEquals("Hello", dataRead);
+ }
+);
+
+unitTest(
+ { perms: { read: true, write: true } },
+ async function writeTextFileNotFound(): Promise<void> {
+ const filename = "/baddir/test.txt";
+ // The following should fail because /baddir doesn't exist (hopefully).
+ let caughtError = false;
+ try {
+ await Deno.writeTextFile(filename, "Hello");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.NotFound);
+ }
+ assert(caughtError);
+ }
+);
+
+unitTest(
+ { perms: { write: false } },
+ async function writeTextFilePerm(): Promise<void> {
+ const filename = "/baddir/test.txt";
+ // The following should fail due to no write permission
+ let caughtError = false;
+ try {
+ await Deno.writeTextFile(filename, "Hello");
+ } catch (e) {
+ caughtError = true;
+ assert(e instanceof Deno.errors.PermissionDenied);
+ }
+ assert(caughtError);
+ }
+);