diff --git a/cli/js/files.ts b/cli/js/files.ts index d6df8bad03..83922627a0 100644 --- a/cli/js/files.ts +++ b/cli/js/files.ts @@ -21,7 +21,7 @@ import { export { OpenOptions } from "./ops/fs/open.ts"; export function openSync( - path: string, + path: string | URL, options: OpenOptions = { read: true } ): File { checkOpenOptions(options); @@ -30,7 +30,7 @@ export function openSync( } export async function open( - path: string, + path: string | URL, options: OpenOptions = { read: true } ): Promise { checkOpenOptions(options); @@ -38,7 +38,7 @@ export async function open( return new File(rid); } -export function createSync(path: string): File { +export function createSync(path: string | URL): File { return openSync(path, { read: true, write: true, @@ -47,7 +47,7 @@ export function createSync(path: string): File { }); } -export function create(path: string): Promise { +export function create(path: string | URL): Promise { return open(path, { read: true, write: true, diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index 972405680f..c50c71d461 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -419,7 +419,7 @@ declare namespace Deno { * * Requires `allow-read` and/or `allow-write` permissions depending on options. */ - export function openSync(path: string, options?: OpenOptions): File; + export function openSync(path: string | URL, options?: OpenOptions): File; /** Open a file and resolve to an instance of `Deno.File`. The * file does not need to previously exist if using the `create` or `createNew` @@ -434,7 +434,10 @@ declare namespace Deno { * * Requires `allow-read` and/or `allow-write` permissions depending on options. */ - export function open(path: string, options?: OpenOptions): Promise; + export function open( + path: string | URL, + options?: OpenOptions + ): Promise; /** Creates a file if none exists or truncates an existing file and returns * an instance of `Deno.File`. @@ -445,7 +448,7 @@ declare namespace Deno { * * Requires `allow-read` and `allow-write` permissions. */ - export function createSync(path: string): File; + export function createSync(path: string | URL): File; /** Creates a file if none exists or truncates an existing file and resolves to * an instance of `Deno.File`. @@ -456,7 +459,7 @@ declare namespace Deno { * * Requires `allow-read` and `allow-write` permissions. */ - export function create(path: string): Promise; + export function create(path: string | URL): Promise; /** Synchronously read from a resource ID (`rid`) into an array buffer (`buffer`). * @@ -890,7 +893,7 @@ declare namespace Deno { * Defaults to throwing error if the directory already exists. * * Requires `allow-write` permission. */ - export function mkdirSync(path: string, options?: MkdirOptions): void; + export function mkdirSync(path: string | URL, options?: MkdirOptions): void; /** Creates a new directory with the specified path. * @@ -903,7 +906,10 @@ declare namespace Deno { * Defaults to throwing error if the directory already exists. * * Requires `allow-write` permission. */ - export function mkdir(path: string, options?: MkdirOptions): Promise; + export function mkdir( + path: string | URL, + options?: MkdirOptions + ): Promise; export interface MakeTempOptions { /** Directory where the temporary directory should be created (defaults to @@ -1011,7 +1017,7 @@ declare namespace Deno { * NOTE: This API currently throws on Windows * * Requires `allow-write` permission. */ - export function chmodSync(path: string, mode: number): void; + export function chmodSync(path: string | URL, mode: number): void; /** Changes the permission of a specific file/directory of specified path. * Ignores the process's umask. @@ -1041,7 +1047,7 @@ declare namespace Deno { * NOTE: This API currently throws on Windows * * Requires `allow-write` permission. */ - export function chmod(path: string, mode: number): Promise; + export function chmod(path: string | URL, mode: number): Promise; /** Synchronously change owner of a regular file or directory. This functionality * is not available on Windows. @@ -1058,7 +1064,7 @@ declare namespace Deno { * @param uid user id (UID) of the new owner * @param gid group id (GID) of the new owner */ - export function chownSync(path: string, uid: number, gid: number): void; + export function chownSync(path: string | URL, uid: number, gid: number): void; /** Change owner of a regular file or directory. This functionality * is not available on Windows. @@ -1075,7 +1081,11 @@ declare namespace Deno { * @param uid user id (UID) of the new owner * @param gid group id (GID) of the new owner */ - export function chown(path: string, uid: number, gid: number): Promise; + export function chown( + path: string | URL, + uid: number, + gid: number + ): Promise; export interface RemoveOptions { /** Defaults to `false`. If set to `true`, path will be removed even if @@ -1094,7 +1104,7 @@ declare namespace Deno { * directory and the `recursive` option isn't set to `true`. * * Requires `allow-write` permission. */ - export function removeSync(path: string, options?: RemoveOptions): void; + export function removeSync(path: string | URL, options?: RemoveOptions): void; /** Removes the named file or directory. * @@ -1107,7 +1117,10 @@ declare namespace Deno { * directory and the `recursive` option isn't set to `true`. * * Requires `allow-write` permission. */ - export function remove(path: string, options?: RemoveOptions): Promise; + export function remove( + path: string | URL, + options?: RemoveOptions + ): Promise; /** Synchronously renames (moves) `oldpath` to `newpath`. Paths may be files or * directories. If `newpath` already exists and is not a directory, @@ -1152,7 +1165,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function readTextFileSync(path: string): string; + export function readTextFileSync(path: string | URL): string; /** Asynchronously reads and returns the entire contents of a file as a utf8 * encoded string. Reading a directory returns an empty data array. @@ -1163,7 +1176,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function readTextFile(path: string): Promise; + export function readTextFile(path: string | URL): Promise; /** Synchronously reads and returns the entire contents of a file as an array * of bytes. `TextDecoder` can be used to transform the bytes to string if @@ -1176,7 +1189,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function readFileSync(path: string): Uint8Array; + export function readFileSync(path: string | URL): Uint8Array; /** Reads and resolves to the entire contents of a file as an array of bytes. * `TextDecoder` can be used to transform the bytes to string if required. @@ -1189,7 +1202,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function readFile(path: string): Promise; + export function readFile(path: string | URL): Promise; /** A FileInfo describes a file and is returned by `stat`, `lstat`, * `statSync`, `lstatSync`. */ @@ -1307,7 +1320,7 @@ declare namespace Deno { * Throws error if `path` is not a directory. * * Requires `allow-read` permission. */ - export function readDirSync(path: string): Iterable; + export function readDirSync(path: string | URL): Iterable; /** Reads the directory given by `path` and returns an async iterable of * `Deno.DirEntry`. @@ -1321,7 +1334,7 @@ declare namespace Deno { * Throws error if `path` is not a directory. * * Requires `allow-read` permission. */ - export function readDir(path: string): AsyncIterable; + export function readDir(path: string | URL): AsyncIterable; /** Synchronously copies the contents and permissions of one file to another * specified path, by default creating a new file if needed, else overwriting. @@ -1333,7 +1346,10 @@ declare namespace Deno { * * Requires `allow-read` permission on fromPath. * Requires `allow-write` permission on toPath. */ - export function copyFileSync(fromPath: string, toPath: string): void; + export function copyFileSync( + fromPath: string | URL, + toPath: string | URL + ): void; /** Copies the contents and permissions of one file to another specified path, * by default creating a new file if needed, else overwriting. Fails if target @@ -1345,7 +1361,10 @@ declare namespace Deno { * * Requires `allow-read` permission on fromPath. * Requires `allow-write` permission on toPath. */ - export function copyFile(fromPath: string, toPath: string): Promise; + export function copyFile( + fromPath: string | URL, + toPath: string | URL + ): Promise; /** Returns the full path destination of the named symbolic link. * @@ -1382,7 +1401,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function lstat(path: string): Promise; + export function lstat(path: string | URL): Promise; /** Synchronously returns a `Deno.FileInfo` for the specified `path`. If * `path` is a symlink, information for the symlink will be returned instead of @@ -1394,7 +1413,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function lstatSync(path: string): FileInfo; + export function lstatSync(path: string | URL): FileInfo; /** Resolves to a `Deno.FileInfo` for the specified `path`. Will always * follow symlinks. @@ -1406,7 +1425,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function stat(path: string): Promise; + export function stat(path: string | URL): Promise; /** Synchronously returns a `Deno.FileInfo` for the specified `path`. Will * always follow symlinks. @@ -1418,7 +1437,7 @@ declare namespace Deno { * ``` * * Requires `allow-read` permission. */ - export function statSync(path: string): FileInfo; + export function statSync(path: string | URL): FileInfo; /** Options for writing to a file. */ export interface WriteFileOptions { @@ -1448,7 +1467,7 @@ declare namespace Deno { * `false`. */ export function writeFileSync( - path: string, + path: string | URL, data: Uint8Array, options?: WriteFileOptions ): void; @@ -1468,7 +1487,7 @@ declare namespace Deno { * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. */ export function writeFile( - path: string, + path: string | URL, data: Uint8Array, options?: WriteFileOptions ): Promise; @@ -1482,7 +1501,7 @@ declare namespace Deno { * * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. */ - export function writeTextFileSync(path: string, data: string): void; + export function writeTextFileSync(path: string | URL, data: string): void; /** Asynchronously write string `data` to the given `path`, by default creating a new file if needed, * else overwriting. @@ -1493,7 +1512,10 @@ declare namespace Deno { * * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. */ - export function writeTextFile(path: string, data: string): Promise; + export function writeTextFile( + path: string | URL, + data: string + ): Promise; /** Synchronously truncates or extends the specified file, to reach the * specified `len`. If `len` is not specified then the entire file contents diff --git a/cli/js/ops/fs/chmod.ts b/cli/js/ops/fs/chmod.ts index 91e898360c..76a3c8f49d 100644 --- a/cli/js/ops/fs/chmod.ts +++ b/cli/js/ops/fs/chmod.ts @@ -1,10 +1,13 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; -export function chmodSync(path: string, mode: number): void { +export function chmodSync(path: string | URL, mode: number): void { + path = pathFromURL(path); sendSync("op_chmod", { path, mode }); } -export async function chmod(path: string, mode: number): Promise { +export async function chmod(path: string | URL, mode: number): Promise { + path = pathFromURL(path); await sendAsync("op_chmod", { path, mode }); } diff --git a/cli/js/ops/fs/chown.ts b/cli/js/ops/fs/chown.ts index d6e3702c6f..f24ab5e55b 100644 --- a/cli/js/ops/fs/chown.ts +++ b/cli/js/ops/fs/chown.ts @@ -1,14 +1,17 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; -export function chownSync(path: string, uid: number, gid: number): void { +export function chownSync(path: string | URL, uid: number, gid: number): void { + path = pathFromURL(path); sendSync("op_chown", { path, uid, gid }); } export async function chown( - path: string, + path: string | URL, uid: number, gid: number ): Promise { + path = pathFromURL(path); await sendAsync("op_chown", { path, uid, gid }); } diff --git a/cli/js/ops/fs/copy_file.ts b/cli/js/ops/fs/copy_file.ts index 4c8c746678..6bbb3f5994 100644 --- a/cli/js/ops/fs/copy_file.ts +++ b/cli/js/ops/fs/copy_file.ts @@ -1,13 +1,23 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; + +export function copyFileSync( + fromPath: string | URL, + toPath: string | URL +): void { + fromPath = pathFromURL(fromPath); + toPath = pathFromURL(toPath); -export function copyFileSync(fromPath: string, toPath: string): void { sendSync("op_copy_file", { from: fromPath, to: toPath }); } export async function copyFile( - fromPath: string, - toPath: string + fromPath: string | URL, + toPath: string | URL ): Promise { + fromPath = pathFromURL(fromPath); + toPath = pathFromURL(toPath); + await sendAsync("op_copy_file", { from: fromPath, to: toPath }); } diff --git a/cli/js/ops/fs/open.ts b/cli/js/ops/fs/open.ts index afe713db82..3742d0b52a 100644 --- a/cli/js/ops/fs/open.ts +++ b/cli/js/ops/fs/open.ts @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; export interface OpenOptions { read?: boolean; @@ -15,13 +16,18 @@ export interface OpenOptions { mode?: number; } -export function openSync(path: string, options: OpenOptions): number { +export function openSync(path: string | URL, options: OpenOptions): number { const mode: number | undefined = options?.mode; + path = pathFromURL(path); return sendSync("op_open", { path, options, mode }); } -export function open(path: string, options: OpenOptions): Promise { +export function open( + path: string | URL, + options: OpenOptions +): Promise { const mode: number | undefined = options?.mode; + path = pathFromURL(path); return sendAsync("op_open", { path, options, diff --git a/cli/js/ops/fs/read_dir.ts b/cli/js/ops/fs/read_dir.ts index 1e8d79edc7..09c5d1c12e 100644 --- a/cli/js/ops/fs/read_dir.ts +++ b/cli/js/ops/fs/read_dir.ts @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; export interface DirEntry { name: string; @@ -16,11 +17,13 @@ function res(response: ReadDirResponse): DirEntry[] { return response.entries; } -export function readDirSync(path: string): Iterable { +export function readDirSync(path: string | URL): Iterable { + path = pathFromURL(path); return res(sendSync("op_read_dir", { path }))[Symbol.iterator](); } -export function readDir(path: string): AsyncIterable { +export function readDir(path: string | URL): AsyncIterable { + path = pathFromURL(path); const array = sendAsync("op_read_dir", { path }).then(res); return { async *[Symbol.asyncIterator](): AsyncIterableIterator { diff --git a/cli/js/ops/fs/remove.ts b/cli/js/ops/fs/remove.ts index d5af82f9b9..d1a8702f1c 100644 --- a/cli/js/ops/fs/remove.ts +++ b/cli/js/ops/fs/remove.ts @@ -1,17 +1,23 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; +import { pathFromURL } from "../../util.ts"; export interface RemoveOptions { recursive?: boolean; } -export function removeSync(path: string, options: RemoveOptions = {}): void { +export function removeSync( + path: string | URL, + options: RemoveOptions = {} +): void { + path = pathFromURL(path); sendSync("op_remove", { path, recursive: !!options.recursive }); } export async function remove( - path: string, + path: string | URL, options: RemoveOptions = {} ): Promise { + path = pathFromURL(path); await sendAsync("op_remove", { path, recursive: !!options.recursive }); } diff --git a/cli/js/ops/fs/stat.ts b/cli/js/ops/fs/stat.ts index e8fd28218a..93d31fc3f8 100644 --- a/cli/js/ops/fs/stat.ts +++ b/cli/js/ops/fs/stat.ts @@ -1,6 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { sendSync, sendAsync } from "../dispatch_json.ts"; import { build } from "../../build.ts"; +import { pathFromURL } from "../../util.ts"; export interface FileInfo { size: number; @@ -65,7 +66,8 @@ export function parseFileInfo(response: StatResponse): FileInfo { }; } -export async function lstat(path: string): Promise { +export async function lstat(path: string | URL): Promise { + path = pathFromURL(path); const res = (await sendAsync("op_stat", { path, lstat: true, @@ -73,7 +75,8 @@ export async function lstat(path: string): Promise { return parseFileInfo(res); } -export function lstatSync(path: string): FileInfo { +export function lstatSync(path: string | URL): FileInfo { + path = pathFromURL(path); const res = sendSync("op_stat", { path, lstat: true, @@ -81,7 +84,8 @@ export function lstatSync(path: string): FileInfo { return parseFileInfo(res); } -export async function stat(path: string): Promise { +export async function stat(path: string | URL): Promise { + path = pathFromURL(path); const res = (await sendAsync("op_stat", { path, lstat: false, @@ -89,7 +93,8 @@ export async function stat(path: string): Promise { return parseFileInfo(res); } -export function statSync(path: string): FileInfo { +export function statSync(path: string | URL): FileInfo { + path = pathFromURL(path); const res = sendSync("op_stat", { path, lstat: false, diff --git a/cli/js/read_file.ts b/cli/js/read_file.ts index 317401af5c..b8d428b8c9 100644 --- a/cli/js/read_file.ts +++ b/cli/js/read_file.ts @@ -2,14 +2,14 @@ import { open, openSync } from "./files.ts"; import { readAll, readAllSync } from "./buffer.ts"; -export function readFileSync(path: string): Uint8Array { +export function readFileSync(path: string | URL): Uint8Array { const file = openSync(path); const contents = readAllSync(file); file.close(); return contents; } -export async function readFile(path: string): Promise { +export async function readFile(path: string | URL): Promise { const file = await open(path); const contents = await readAll(file); file.close(); diff --git a/cli/js/read_text_file.ts b/cli/js/read_text_file.ts index 3423b26b88..154b01b0ee 100644 --- a/cli/js/read_text_file.ts +++ b/cli/js/read_text_file.ts @@ -1,7 +1,7 @@ import { open, openSync } from "./files.ts"; import { readAll, readAllSync } from "./buffer.ts"; -export function readTextFileSync(path: string): string { +export function readTextFileSync(path: string | URL): string { const decoder = new TextDecoder(); const file = openSync(path); const content = readAllSync(file); @@ -9,7 +9,7 @@ export function readTextFileSync(path: string): string { return decoder.decode(content); } -export async function readTextFile(path: string): Promise { +export async function readTextFile(path: string | URL): Promise { const decoder = new TextDecoder(); const file = await open(path); const content = await readAll(file); diff --git a/cli/js/util.ts b/cli/js/util.ts index 309bfcd0c2..c50a4cdcb5 100644 --- a/cli/js/util.ts +++ b/cli/js/util.ts @@ -1,4 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { build } from "./build.ts"; +import { exposeForTest } from "./internals.ts"; let logDebug = false; let logSource = "JS"; @@ -78,3 +80,49 @@ export function immutableDefine( writable: false, }); } + +function pathFromURLWin32(url: URL): string { + if (url.hostname !== "") { + //TODO(actual-size) Node adds a punycode decoding step, we should consider adding this + return `\\\\${url.hostname}${url.pathname}`; + } + + const validPath = /^\/(?[A-Za-z]):\//; + const matches = validPath.exec(url.pathname); + + if (!matches?.groups?.driveLetter) { + throw new TypeError("A URL with the file schema must be absolute."); + } + + const pathname = url.pathname.replace(/\//g, "\\"); + // we don't want a leading slash on an absolute path in Windows + return pathname.slice(1); +} + +function pathFromURLPosix(url: URL): string { + if (url.hostname !== "") { + throw new TypeError(`Host must be empty.`); + } + + return decodeURIComponent(url.pathname); +} + +export function pathFromURL(pathOrUrl: string | URL): string { + if (typeof pathOrUrl == "string") { + try { + pathOrUrl = new URL(pathOrUrl); + } catch {} + } + if (pathOrUrl instanceof URL) { + if (pathOrUrl.protocol != "file:") { + throw new TypeError("Must be a path string or file URL."); + } + + return build.os == "windows" + ? pathFromURLWin32(pathOrUrl) + : pathFromURLPosix(pathOrUrl); + } + return pathOrUrl; +} + +exposeForTest("pathFromURL", pathFromURL); diff --git a/cli/js/write_file.ts b/cli/js/write_file.ts index 6961b78eae..3106c48ef9 100644 --- a/cli/js/write_file.ts +++ b/cli/js/write_file.ts @@ -12,7 +12,7 @@ export interface WriteFileOptions { } export function writeFileSync( - path: string, + path: string | URL, data: Uint8Array, options: WriteFileOptions = {} ): void { @@ -42,7 +42,7 @@ export function writeFileSync( } export async function writeFile( - path: string, + path: string | URL, data: Uint8Array, options: WriteFileOptions = {} ): Promise { diff --git a/cli/js/write_text_file.ts b/cli/js/write_text_file.ts index d2a6575d7f..4f111edb4b 100644 --- a/cli/js/write_text_file.ts +++ b/cli/js/write_text_file.ts @@ -1,7 +1,7 @@ import { open, openSync } from "./files.ts"; import { writeAll, writeAllSync } from "./buffer.ts"; -export function writeTextFileSync(path: string, data: string): void { +export function writeTextFileSync(path: string | URL, data: string): void { const file = openSync(path, { write: true, create: true, truncate: true }); const enc = new TextEncoder(); const contents = enc.encode(data); @@ -9,7 +9,10 @@ export function writeTextFileSync(path: string, data: string): void { file.close(); } -export async function writeTextFile(path: string, data: string): Promise { +export async function writeTextFile( + path: string | URL, + data: string +): Promise { const file = await open(path, { write: true, create: true, truncate: true }); const enc = new TextEncoder(); const contents = enc.encode(data); diff --git a/cli/tests/unit/chmod_test.ts b/cli/tests/unit/chmod_test.ts index d0d17629d0..02a5fb22e0 100644 --- a/cli/tests/unit/chmod_test.ts +++ b/cli/tests/unit/chmod_test.ts @@ -18,6 +18,25 @@ unitTest( } ); +unitTest( + { ignore: Deno.build.os === "windows", perms: { read: true, write: true } }, + function chmodSyncUrl(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL(`file://${tempDir}/test.txt`); + Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); + + Deno.chmodSync(fileUrl, 0o777); + + const fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + // Check symlink when not on windows unitTest( { @@ -89,6 +108,25 @@ unitTest( } ); +unitTest( + { ignore: Deno.build.os === "windows", perms: { read: true, write: true } }, + async function chmodUrl(): Promise { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL(`file://${tempDir}/test.txt`); + Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); + + await Deno.chmod(fileUrl, 0o777); + + const fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.mode); + assertEquals(fileInfo.mode & 0o777, 0o777); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + // Check symlink when not on windows unitTest( diff --git a/cli/tests/unit/chown_test.ts b/cli/tests/unit/chown_test.ts index 724ea5a211..bcd5ab9fe8 100644 --- a/cli/tests/unit/chown_test.ts +++ b/cli/tests/unit/chown_test.ts @@ -125,6 +125,26 @@ if (Deno.build.os !== "windows") { } ); + unitTest( + { perms: { run: true, write: true } }, + async function chownSyncWithUrl(): Promise { + // TODO: same as chownSyncSucceed + const { uid, gid } = await getUidAndGid(); + + const enc = new TextEncoder(); + const dirPath = Deno.makeTempDirSync(); + const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`); + const fileData = enc.encode("Hello"); + Deno.writeFileSync(fileUrl, 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(fileUrl, uid, gid); + + Deno.removeSync(dirPath, { recursive: true }); + } + ); + unitTest( { perms: { run: true, write: true } }, async function chownSucceed(): Promise { @@ -144,4 +164,24 @@ if (Deno.build.os !== "windows") { Deno.removeSync(dirPath, { recursive: true }); } ); + + unitTest( + { perms: { run: true, write: true } }, + async function chownWithUrl(): Promise { + // TODO: same as chownSyncSucceed + const { uid, gid } = await getUidAndGid(); + + const enc = new TextEncoder(); + const dirPath = await Deno.makeTempDir(); + const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`); + const fileData = enc.encode("Hello"); + await Deno.writeFile(fileUrl, 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(fileUrl, uid, gid); + + Deno.removeSync(dirPath, { recursive: true }); + } + ); } diff --git a/cli/tests/unit/copy_file_test.ts b/cli/tests/unit/copy_file_test.ts index 986bab53bd..5a1492e50d 100644 --- a/cli/tests/unit/copy_file_test.ts +++ b/cli/tests/unit/copy_file_test.ts @@ -1,19 +1,22 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { unitTest, assert, assertEquals } from "./test_util.ts"; -function readFileString(filename: string): string { +function readFileString(filename: string | URL): string { const dataRead = Deno.readFileSync(filename); const dec = new TextDecoder("utf-8"); return dec.decode(dataRead); } -function writeFileString(filename: string, s: string): void { +function writeFileString(filename: string | URL, 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 { +function assertSameContent( + filename1: string | URL, + filename2: string | URL +): void { const data1 = Deno.readFileSync(filename1); const data2 = Deno.readFileSync(filename2); assertEquals(data1, data2); @@ -31,6 +34,29 @@ unitTest( assertEquals(readFileString(fromFilename), "Hello world!"); // Original == Dest assertSameContent(fromFilename, toFilename); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { read: true, write: true } }, + function copyFileSyncByUrl(): void { + const tempDir = Deno.makeTempDirSync(); + const fromUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt` + ); + const toUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt` + ); + writeFileString(fromUrl, "Hello world!"); + Deno.copyFileSync(fromUrl, toUrl); + // No change to original file + assertEquals(readFileString(fromUrl), "Hello world!"); + // Original == Dest + assertSameContent(fromUrl, toUrl); + + Deno.removeSync(tempDir, { recursive: true }); } ); @@ -49,6 +75,8 @@ unitTest( } assert(!!err); assert(err instanceof Deno.errors.NotFound); + + Deno.removeSync(tempDir, { recursive: true }); } ); @@ -94,6 +122,8 @@ unitTest( assertEquals(readFileString(fromFilename), "Hello world!"); // Original == Dest assertSameContent(fromFilename, toFilename); + + Deno.removeSync(tempDir, { recursive: true }); } ); @@ -109,6 +139,29 @@ unitTest( assertEquals(readFileString(fromFilename), "Hello world!"); // Original == Dest assertSameContent(fromFilename, toFilename); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function copyFileByUrl(): Promise { + const tempDir = Deno.makeTempDirSync(); + const fromUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt` + ); + const toUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt` + ); + writeFileString(fromUrl, "Hello world!"); + await Deno.copyFile(fromUrl, toUrl); + // No change to original file + assertEquals(readFileString(fromUrl), "Hello world!"); + // Original == Dest + assertSameContent(fromUrl, toUrl); + + Deno.removeSync(tempDir, { recursive: true }); } ); @@ -127,6 +180,8 @@ unitTest( } assert(!!err); assert(err instanceof Deno.errors.NotFound); + + Deno.removeSync(tempDir, { recursive: true }); } ); @@ -144,6 +199,8 @@ unitTest( assertEquals(readFileString(fromFilename), "Hello world!"); // Original == Dest assertSameContent(fromFilename, toFilename); + + Deno.removeSync(tempDir, { recursive: true }); } ); diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts index 46bcb2173b..e5db0c613d 100644 --- a/cli/tests/unit/files_test.ts +++ b/cli/tests/unit/files_test.ts @@ -197,6 +197,54 @@ unitTest( } ); +unitTest( + { + perms: { read: true, write: true }, + }, + function openSyncUrl(): void { + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test_open.txt` + ); + const file = Deno.openSync(fileUrl, { + write: true, + createNew: true, + mode: 0o626, + }); + file.close(); + const pathInfo = Deno.statSync(fileUrl); + if (Deno.build.os !== "windows") { + assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); + } + + Deno.removeSync(tempDir, { recursive: true }); + } +); + +unitTest( + { + perms: { read: true, write: true }, + }, + async function openUrl(): Promise { + const tempDir = await Deno.makeTempDir(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test_open.txt` + ); + const file = await Deno.open(fileUrl, { + write: true, + createNew: true, + mode: 0o626, + }); + file.close(); + const pathInfo = Deno.statSync(fileUrl); + if (Deno.build.os !== "windows") { + assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask()); + } + + Deno.removeSync(tempDir, { recursive: true }); + } +); + unitTest( { perms: { write: false } }, async function writePermFailure(): Promise { @@ -375,6 +423,71 @@ unitTest( } ); +unitTest( + { perms: { read: true, write: true } }, + async function createFileWithUrl(): Promise { + const tempDir = await Deno.makeTempDir(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + const f = await Deno.create(fileUrl); + let fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.isFile); + assert(fileInfo.size === 0); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + await f.write(data); + fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.size === 5); + f.close(); + + await Deno.remove(tempDir, { recursive: true }); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function createSyncFile(): Promise { + const tempDir = await Deno.makeTempDir(); + const filename = tempDir + "/test.txt"; + const f = Deno.createSync(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 createSyncFileWithUrl(): Promise { + const tempDir = await Deno.makeTempDir(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + const f = Deno.createSync(fileUrl); + let fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.isFile); + assert(fileInfo.size === 0); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + await f.write(data); + fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.size === 5); + f.close(); + + await Deno.remove(tempDir, { recursive: true }); + } +); + unitTest( { perms: { read: true, write: true } }, async function openModeWrite(): Promise { diff --git a/cli/tests/unit/path_from_url_test.ts b/cli/tests/unit/path_from_url_test.ts new file mode 100644 index 0000000000..5e7203a582 --- /dev/null +++ b/cli/tests/unit/path_from_url_test.ts @@ -0,0 +1,31 @@ +import { assertThrows, assertEquals, unitTest } from "./test_util.ts"; + +// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol +const { pathFromURL } = Deno[Deno.internal]; + +unitTest( + { ignore: Deno.build.os === "windows" }, + function pathFromURLPosix(): void { + assertEquals(pathFromURL("file:///test/directory"), "/test/directory"); + assertThrows(() => pathFromURL("file://host/test/directory")); + assertThrows(() => pathFromURL("https://deno.land/welcome.ts")); + } +); + +unitTest( + { ignore: Deno.build.os !== "windows" }, + function pathFromURLWin32(): void { + assertEquals(pathFromURL("file:///c:/windows/test"), "c:\\windows\\test"); + assertThrows(() => pathFromURL("file:///thing/test")); + assertThrows(() => pathFromURL("https://deno.land/welcome.ts")); + /* TODO(ry) Add tests for these situations + * ampersand_&.tx file:///D:/weird_names/ampersand_&.txt + * at_@.txt file:///D:/weird_names/at_@.txt + * emoji_🙃.txt file:///D:/weird_names/emoji_%F0%9F%99%83.txt + * percent_%.txt file:///D:/weird_names/percent_%25.txt + * pound_#.txt file:///D:/weird_names/pound_%23.txt + * space_ .txt file:///D:/weird_names/space_%20.txt + * swapped_surrogate_pair_��.txt file:///D:/weird_names/swapped_surrogate_pair_%EF%BF%BD%EF%BF%BD.txt + */ + } +); diff --git a/cli/tests/unit/read_dir_test.ts b/cli/tests/unit/read_dir_test.ts index 79e2a15072..d9a5244c78 100644 --- a/cli/tests/unit/read_dir_test.ts +++ b/cli/tests/unit/read_dir_test.ts @@ -1,5 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert, assertEquals } from "./test_util.ts"; +import { + unitTest, + assert, + assertEquals, + pathToAbsoluteFileUrl, +} from "./test_util.ts"; function assertSameContent(files: Deno.DirEntry[]): void { let counter = 0; @@ -19,6 +24,11 @@ unitTest({ perms: { read: true } }, function readDirSyncSuccess(): void { assertSameContent(files); }); +unitTest({ perms: { read: true } }, function readDirSyncWithUrl(): void { + const files = [...Deno.readDirSync(pathToAbsoluteFileUrl("cli/tests"))]; + assertSameContent(files); +}); + unitTest({ perms: { read: false } }, function readDirSyncPerm(): void { let caughtError = false; try { @@ -68,6 +78,18 @@ unitTest({ perms: { read: true } }, async function readDirSuccess(): Promise< assertSameContent(files); }); +unitTest({ perms: { read: true } }, async function readDirWithUrl(): Promise< + void +> { + const files = []; + for await (const dirEntry of Deno.readDir( + pathToAbsoluteFileUrl("cli/tests") + )) { + files.push(dirEntry); + } + assertSameContent(files); +}); + unitTest({ perms: { read: false } }, async function readDirPerm(): Promise< void > { diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts index 0d3cdf4223..cae9037abe 100644 --- a/cli/tests/unit/read_file_test.ts +++ b/cli/tests/unit/read_file_test.ts @@ -1,5 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert, assertEquals } from "./test_util.ts"; +import { + unitTest, + assert, + assertEquals, + pathToAbsoluteFileUrl, +} from "./test_util.ts"; unitTest({ perms: { read: true } }, function readFileSyncSuccess(): void { const data = Deno.readFileSync("cli/tests/fixture.json"); @@ -10,6 +15,17 @@ unitTest({ perms: { read: true } }, function readFileSyncSuccess(): void { assertEquals(pkg.name, "deno"); }); +unitTest({ perms: { read: true } }, function readFileSyncUrl(): void { + const data = Deno.readFileSync( + pathToAbsoluteFileUrl("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 { @@ -34,6 +50,19 @@ unitTest({ perms: { read: true } }, function readFileSyncNotFound(): void { assert(data === undefined); }); +unitTest({ perms: { read: true } }, async function readFileUrl(): Promise< + void +> { + const data = await Deno.readFile( + pathToAbsoluteFileUrl("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: true } }, async function readFileSuccess(): Promise< void > { diff --git a/cli/tests/unit/read_text_file_test.ts b/cli/tests/unit/read_text_file_test.ts index 3e7493e4a2..0f1759c541 100644 --- a/cli/tests/unit/read_text_file_test.ts +++ b/cli/tests/unit/read_text_file_test.ts @@ -1,4 +1,9 @@ -import { unitTest, assert, assertEquals } from "./test_util.ts"; +import { + unitTest, + assert, + assertEquals, + pathToAbsoluteFileUrl, +} from "./test_util.ts"; unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void { const data = Deno.readTextFileSync("cli/tests/fixture.json"); @@ -7,6 +12,15 @@ unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void { assertEquals(pkg.name, "deno"); }); +unitTest({ perms: { read: true } }, function readTextFileSyncByUrl(): void { + const data = Deno.readTextFileSync( + pathToAbsoluteFileUrl("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 { @@ -41,6 +55,17 @@ unitTest( } ); +unitTest({ perms: { read: true } }, async function readTextFileByUrl(): Promise< + void +> { + const data = await Deno.readTextFile( + pathToAbsoluteFileUrl("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 > { diff --git a/cli/tests/unit/remove_test.ts b/cli/tests/unit/remove_test.ts index f9095c1c1d..94fbba6825 100644 --- a/cli/tests/unit/remove_test.ts +++ b/cli/tests/unit/remove_test.ts @@ -51,6 +51,36 @@ unitTest( } ); +unitTest( + { perms: { write: true, read: true } }, + async function removeFileByUrl(): Promise { + for (const method of REMOVE_METHODS) { + // REMOVE FILE + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + + Deno.writeFileSync(fileUrl, data, { mode: 0o666 }); + const fileInfo = Deno.statSync(fileUrl); + assert(fileInfo.isFile); // check exist first + await Deno[method](fileUrl); // remove + // We then check again after remove + let err; + try { + Deno.statSync(fileUrl); + } catch (e) { + err = e; + } + // File is gone + assert(err instanceof Deno.errors.NotFound); + } + } +); + unitTest( { perms: { write: true, read: true } }, async function removeFail(): Promise { diff --git a/cli/tests/unit/stat_test.ts b/cli/tests/unit/stat_test.ts index 7eaf73d581..67598a2d79 100644 --- a/cli/tests/unit/stat_test.ts +++ b/cli/tests/unit/stat_test.ts @@ -1,5 +1,10 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { unitTest, assert, assertEquals } from "./test_util.ts"; +import { + unitTest, + assert, + assertEquals, + pathToAbsoluteFileUrl, +} from "./test_util.ts"; unitTest( { perms: { read: true, write: true } }, @@ -18,12 +23,47 @@ unitTest( const tempFile = Deno.makeTempFileSync(); const tempInfo = Deno.statSync(tempFile); - const now = Date.now(); + let 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 ); + + const packageInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("README.md")); + assert(packageInfoByUrl.isFile); + assert(!packageInfoByUrl.isSymlink); + + const modulesInfoByUrl = Deno.statSync( + pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir") + ); + assert(modulesInfoByUrl.isDirectory); + assert(!modulesInfoByUrl.isSymlink); + + const testsInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("cli/tests")); + assert(testsInfoByUrl.isDirectory); + assert(!testsInfoByUrl.isSymlink); + + const tempFileForUrl = Deno.makeTempFileSync(); + const tempInfoByUrl = Deno.statSync( + new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}` + ) + ); + now = Date.now(); + assert( + tempInfoByUrl.atime !== null && now - tempInfoByUrl.atime.valueOf() < 1000 + ); + assert( + tempInfoByUrl.mtime !== null && now - tempInfoByUrl.mtime.valueOf() < 1000 + ); + assert( + tempInfoByUrl.birthtime === null || + now - tempInfoByUrl.birthtime.valueOf() < 1000 + ); + + Deno.removeSync(tempFile, { recursive: true }); + Deno.removeSync(tempFileForUrl, { recursive: true }); } ); @@ -58,13 +98,27 @@ unitTest({ perms: { read: true } }, function lstatSyncSuccess(): void { assert(packageInfo.isFile); assert(!packageInfo.isSymlink); + const packageInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("README.md")); + assert(packageInfoByUrl.isFile); + assert(!packageInfoByUrl.isSymlink); + const modulesInfo = Deno.lstatSync("cli/tests/symlink_to_subdir"); assert(!modulesInfo.isDirectory); assert(modulesInfo.isSymlink); + const modulesInfoByUrl = Deno.lstatSync( + pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir") + ); + assert(!modulesInfoByUrl.isDirectory); + assert(modulesInfoByUrl.isSymlink); + const coreInfo = Deno.lstatSync("core"); assert(coreInfo.isDirectory); assert(!coreInfo.isSymlink); + + const coreInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("core")); + assert(coreInfoByUrl.isDirectory); + assert(!coreInfoByUrl.isSymlink); }); unitTest({ perms: { read: false } }, function lstatSyncPerm(): void { @@ -100,23 +154,60 @@ unitTest( assert(packageInfo.isFile); assert(!packageInfo.isSymlink); + const packageInfoByUrl = await Deno.stat( + pathToAbsoluteFileUrl("README.md") + ); + assert(packageInfoByUrl.isFile); + assert(!packageInfoByUrl.isSymlink); + const modulesInfo = await Deno.stat("cli/tests/symlink_to_subdir"); assert(modulesInfo.isDirectory); assert(!modulesInfo.isSymlink); + const modulesInfoByUrl = await Deno.stat( + pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir") + ); + assert(modulesInfoByUrl.isDirectory); + assert(!modulesInfoByUrl.isSymlink); + const testsInfo = await Deno.stat("cli/tests"); assert(testsInfo.isDirectory); assert(!testsInfo.isSymlink); + const testsInfoByUrl = await Deno.stat(pathToAbsoluteFileUrl("cli/tests")); + assert(testsInfoByUrl.isDirectory); + assert(!testsInfoByUrl.isSymlink); + const tempFile = await Deno.makeTempFile(); const tempInfo = await Deno.stat(tempFile); - const now = Date.now(); + let 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 ); + + const tempFileForUrl = await Deno.makeTempFile(); + const tempInfoByUrl = await Deno.stat( + new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}` + ) + ); + now = Date.now(); + assert( + tempInfoByUrl.atime !== null && now - tempInfoByUrl.atime.valueOf() < 1000 + ); + assert( + tempInfoByUrl.mtime !== null && now - tempInfoByUrl.mtime.valueOf() < 1000 + ); + assert( + tempInfoByUrl.birthtime === null || + now - tempInfoByUrl.birthtime.valueOf() < 1000 + ); + + Deno.removeSync(tempFile, { recursive: true }); + Deno.removeSync(tempFileForUrl, { recursive: true }); } ); @@ -155,13 +246,27 @@ unitTest({ perms: { read: true } }, async function lstatSuccess(): Promise< assert(packageInfo.isFile); assert(!packageInfo.isSymlink); + const packageInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("README.md")); + assert(packageInfoByUrl.isFile); + assert(!packageInfoByUrl.isSymlink); + const modulesInfo = await Deno.lstat("cli/tests/symlink_to_subdir"); assert(!modulesInfo.isDirectory); assert(modulesInfo.isSymlink); + const modulesInfoByUrl = await Deno.lstat( + pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir") + ); + assert(!modulesInfoByUrl.isDirectory); + assert(modulesInfoByUrl.isSymlink); + const coreInfo = await Deno.lstat("core"); assert(coreInfo.isDirectory); assert(!coreInfo.isSymlink); + + const coreInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("core")); + assert(coreInfoByUrl.isDirectory); + assert(!coreInfoByUrl.isSymlink); }); unitTest({ perms: { read: false } }, async function lstatPerm(): Promise { diff --git a/cli/tests/unit/test_util.ts b/cli/tests/unit/test_util.ts index 8b690505a9..660e135114 100644 --- a/cli/tests/unit/test_util.ts +++ b/cli/tests/unit/test_util.ts @@ -1,6 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { assert, assertEquals } from "../../../std/testing/asserts.ts"; +import { resolve } from "../../../std/path/mod.ts"; export { assert, assertThrows, @@ -363,3 +364,9 @@ unitTest( }); } ); + +export function pathToAbsoluteFileUrl(path: string): URL { + path = resolve(path); + + return new URL(`file://${Deno.build.os === "windows" ? "/" : ""}${path}`); +} diff --git a/cli/tests/unit/unit_tests.ts b/cli/tests/unit/unit_tests.ts index 7891ea418f..b16141bdc7 100644 --- a/cli/tests/unit/unit_tests.ts +++ b/cli/tests/unit/unit_tests.ts @@ -40,6 +40,7 @@ import "./mkdir_test.ts"; import "./net_test.ts"; import "./os_test.ts"; import "./permissions_test.ts"; +import "./path_from_url_test.ts"; import "./process_test.ts"; import "./real_path_test.ts"; import "./read_dir_test.ts"; diff --git a/cli/tests/unit/write_file_test.ts b/cli/tests/unit/write_file_test.ts index 80f711dbc7..6c263c32c0 100644 --- a/cli/tests/unit/write_file_test.ts +++ b/cli/tests/unit/write_file_test.ts @@ -15,6 +15,25 @@ unitTest( } ); +unitTest( + { perms: { read: true, write: true } }, + function writeFileSyncUrl(): void { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + Deno.writeFileSync(fileUrl, data); + const dataRead = Deno.readFileSync(fileUrl); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + unitTest({ perms: { write: true } }, function writeFileSyncFail(): void { const enc = new TextEncoder(); const data = enc.encode("Hello"); @@ -125,6 +144,25 @@ unitTest( } ); +unitTest( + { perms: { read: true, write: true } }, + async function writeFileUrl(): Promise { + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const tempDir = await Deno.makeTempDir(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + await Deno.writeFile(fileUrl, data); + const dataRead = Deno.readFileSync(fileUrl); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals("Hello", actual); + + Deno.removeSync(tempDir, { recursive: true }); + } +); + unitTest( { perms: { read: true, write: true } }, async function writeFileNotFound(): Promise { diff --git a/cli/tests/unit/write_text_file_test.ts b/cli/tests/unit/write_text_file_test.ts index 321189b0e2..d72fa722e3 100644 --- a/cli/tests/unit/write_text_file_test.ts +++ b/cli/tests/unit/write_text_file_test.ts @@ -10,6 +10,21 @@ unitTest( } ); +unitTest( + { perms: { read: true, write: true } }, + function writeTextFileSyncByUrl(): void { + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + Deno.writeTextFileSync(fileUrl, "Hello"); + const dataRead = Deno.readTextFileSync(fileUrl); + assertEquals("Hello", dataRead); + + Deno.removeSync(fileUrl, { recursive: true }); + } +); + unitTest({ perms: { write: true } }, function writeTextFileSyncFail(): void { const filename = "/baddir/test.txt"; // The following should fail because /baddir doesn't exist (hopefully). @@ -46,6 +61,21 @@ unitTest( } ); +unitTest( + { perms: { read: true, write: true } }, + async function writeTextFileByUrl(): Promise { + const tempDir = Deno.makeTempDirSync(); + const fileUrl = new URL( + `file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt` + ); + await Deno.writeTextFile(fileUrl, "Hello"); + const dataRead = Deno.readTextFileSync(fileUrl); + assertEquals("Hello", dataRead); + + Deno.removeSync(fileUrl, { recursive: true }); + } +); + unitTest( { perms: { read: true, write: true } }, async function writeTextFileNotFound(): Promise {