diff --git a/cli/standalone/file_system.rs b/cli/standalone/file_system.rs
index 843c7db557..13da0a729d 100644
--- a/cli/standalone/file_system.rs
+++ b/cli/standalone/file_system.rs
@@ -349,4 +349,29 @@ impl FileSystem for DenoCompileFileSystem {
.utime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
.await
}
+
+ fn lutime_sync(
+ &self,
+ path: &Path,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ self.error_if_in_vfs(path)?;
+ RealFs.lutime_sync(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
+ }
+ async fn lutime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ self.error_if_in_vfs(&path)?;
+ RealFs
+ .lutime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
+ .await
+ }
}
diff --git a/ext/fs/30_fs.js b/ext/fs/30_fs.js
index 183e51e505..767ed11f4f 100644
--- a/ext/fs/30_fs.js
+++ b/ext/fs/30_fs.js
@@ -473,8 +473,8 @@ function toUnixTimeFromEpoch(value) {
];
}
- const seconds = value;
- const nanoseconds = 0;
+ const seconds = MathTrunc(value);
+ const nanoseconds = MathTrunc((value * 1e3) - (seconds * 1e3)) * 1e6;
return [
seconds,
diff --git a/ext/fs/in_memory_fs.rs b/ext/fs/in_memory_fs.rs
index 153327ff64..e2babf40aa 100644
--- a/ext/fs/in_memory_fs.rs
+++ b/ext/fs/in_memory_fs.rs
@@ -350,6 +350,27 @@ impl FileSystem for InMemoryFs {
self.utime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
}
+ fn lutime_sync(
+ &self,
+ _path: &Path,
+ _atime_secs: i64,
+ _atime_nanos: u32,
+ _mtime_secs: i64,
+ _mtime_nanos: u32,
+ ) -> FsResult<()> {
+ Err(FsError::NotSupported)
+ }
+ async fn lutime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ self.lutime_sync(&path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
+ }
+
fn write_file_sync(
&self,
path: &Path,
diff --git a/ext/fs/interface.rs b/ext/fs/interface.rs
index 6e3c359bbc..6036f82284 100644
--- a/ext/fs/interface.rs
+++ b/ext/fs/interface.rs
@@ -221,6 +221,23 @@ pub trait FileSystem: std::fmt::Debug + MaybeSend + MaybeSync {
mtime_nanos: u32,
) -> FsResult<()>;
+ fn lutime_sync(
+ &self,
+ path: &Path,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+ async fn lutime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()>;
+
fn write_file_sync(
&self,
path: &Path,
diff --git a/ext/fs/std_fs.rs b/ext/fs/std_fs.rs
index 054f5c9c4b..79f66cc4bf 100644
--- a/ext/fs/std_fs.rs
+++ b/ext/fs/std_fs.rs
@@ -274,6 +274,35 @@ impl FileSystem for RealFs {
.await?
}
+ fn lutime_sync(
+ &self,
+ path: &Path,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ filetime::set_symlink_file_times(path, atime, mtime).map_err(Into::into)
+ }
+
+ async fn lutime_async(
+ &self,
+ path: PathBuf,
+ atime_secs: i64,
+ atime_nanos: u32,
+ mtime_secs: i64,
+ mtime_nanos: u32,
+ ) -> FsResult<()> {
+ let atime = filetime::FileTime::from_unix_time(atime_secs, atime_nanos);
+ let mtime = filetime::FileTime::from_unix_time(mtime_secs, mtime_nanos);
+ spawn_blocking(move || {
+ filetime::set_symlink_file_times(path, atime, mtime).map_err(Into::into)
+ })
+ .await?
+ }
+
fn write_file_sync(
&self,
path: &Path,
@@ -927,9 +956,14 @@ fn open_with_access_check(
};
(*access_check)(true, &path, &options)?;
- // For windows
- #[allow(unused_mut)]
let mut opts: fs::OpenOptions = open_options(options);
+ #[cfg(windows)]
+ {
+ // allow opening directories
+ use std::os::windows::fs::OpenOptionsExt;
+ opts.custom_flags(winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS);
+ }
+
#[cfg(unix)]
{
// Don't follow symlinks on open -- we must always pass fully-resolved files
@@ -943,7 +977,15 @@ fn open_with_access_check(
Ok(opts.open(&path)?)
} else {
- let opts = open_options(options);
+ // for unix
+ #[allow(unused_mut)]
+ let mut opts = open_options(options);
+ #[cfg(windows)]
+ {
+ // allow opening directories
+ use std::os::windows::fs::OpenOptionsExt;
+ opts.custom_flags(winapi::um::winbase::FILE_FLAG_BACKUP_SEMANTICS);
+ }
Ok(opts.open(path)?)
}
}
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index ff979a8ba5..01c464df13 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -315,6 +315,8 @@ deno_core::extension!(deno_node,
ops::fs::op_node_fs_exists_sync
,
ops::fs::op_node_cp_sync
,
ops::fs::op_node_cp
,
+ ops::fs::op_node_lutimes_sync
,
+ ops::fs::op_node_lutimes
,
ops::fs::op_node_statfs
,
ops::winerror::op_node_sys_to_uv_error,
ops::v8::op_v8_cached_data_version_tag,
@@ -426,6 +428,7 @@ deno_core::extension!(deno_node,
"_fs/_fs_futimes.ts",
"_fs/_fs_link.ts",
"_fs/_fs_lstat.ts",
+ "_fs/_fs_lutimes.ts",
"_fs/_fs_mkdir.ts",
"_fs/_fs_mkdtemp.ts",
"_fs/_fs_open.ts",
diff --git a/ext/node/ops/fs.rs b/ext/node/ops/fs.rs
index cfc7606560..304a6c2539 100644
--- a/ext/node/ops/fs.rs
+++ b/ext/node/ops/fs.rs
@@ -222,3 +222,54 @@ where
Err(anyhow!("Unsupported platform."))
}
}
+
+#[op2(fast)]
+pub fn op_node_lutimes_sync
(
+ state: &mut OpState,
+ #[string] path: &str,
+ #[number] atime_secs: i64,
+ #[smi] atime_nanos: u32,
+ #[number] mtime_secs: i64,
+ #[smi] mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ P: NodePermissions + 'static,
+{
+ let path = Path::new(path);
+
+ state
+ .borrow_mut::
()
+ .check_write_with_api_name(path, Some("node:fs.lutimes"))?;
+
+ let fs = state.borrow::();
+ fs.lutime_sync(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)?;
+ Ok(())
+}
+
+#[op2(async)]
+pub async fn op_node_lutimes(
+ state: Rc>,
+ #[string] path: String,
+ #[number] atime_secs: i64,
+ #[smi] atime_nanos: u32,
+ #[number] mtime_secs: i64,
+ #[smi] mtime_nanos: u32,
+) -> Result<(), AnyError>
+where
+ P: NodePermissions + 'static,
+{
+ let path = PathBuf::from(path);
+
+ let fs = {
+ let mut state = state.borrow_mut();
+ state
+ .borrow_mut::()
+ .check_write_with_api_name(&path, Some("node:fs.lutimesSync"))?;
+ state.borrow::().clone()
+ };
+
+ fs.lutime_async(path, atime_secs, atime_nanos, mtime_secs, mtime_nanos)
+ .await?;
+
+ Ok(())
+}
diff --git a/ext/node/polyfills/_fs/_fs_futimes.ts b/ext/node/polyfills/_fs/_fs_futimes.ts
index cc4e35b0b3..98cd1066c3 100644
--- a/ext/node/polyfills/_fs/_fs_futimes.ts
+++ b/ext/node/polyfills/_fs/_fs_futimes.ts
@@ -5,6 +5,9 @@
import type { CallbackWithError } from "ext:deno_node/_fs/_fs_common.ts";
import { FsFile } from "ext:deno_fs/30_fs.js";
+import { validateInteger } from "ext:deno_node/internal/validators.mjs";
+import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
+import { toUnixTimestamp } from "ext:deno_node/internal/fs/utils.mjs";
function getValidTime(
time: number | string | Date,
@@ -23,7 +26,7 @@ function getValidTime(
);
}
- return time;
+ return toUnixTimestamp(time);
}
export function futimes(
@@ -35,6 +38,11 @@ export function futimes(
if (!callback) {
throw new Deno.errors.InvalidData("No callback function supplied");
}
+ if (typeof fd !== "number") {
+ throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
+ }
+
+ validateInteger(fd, "fd", 0, 2147483647);
atime = getValidTime(atime, "atime");
mtime = getValidTime(mtime, "mtime");
@@ -51,6 +59,12 @@ export function futimesSync(
atime: number | string | Date,
mtime: number | string | Date,
) {
+ if (typeof fd !== "number") {
+ throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
+ }
+
+ validateInteger(fd, "fd", 0, 2147483647);
+
atime = getValidTime(atime, "atime");
mtime = getValidTime(mtime, "mtime");
diff --git a/ext/node/polyfills/_fs/_fs_lutimes.ts b/ext/node/polyfills/_fs/_fs_lutimes.ts
new file mode 100644
index 0000000000..2475c57149
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_lutimes.ts
@@ -0,0 +1,85 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+// deno-lint-ignore-file prefer-primordials
+
+import type { CallbackWithError } from "ext:deno_node/_fs/_fs_common.ts";
+import { type Buffer } from "node:buffer";
+import { primordials } from "ext:core/mod.js";
+import { op_node_lutimes, op_node_lutimes_sync } from "ext:core/ops";
+import { promisify } from "ext:deno_node/internal/util.mjs";
+import {
+ getValidatedPath,
+ toUnixTimestamp,
+} from "ext:deno_node/internal/fs/utils.mjs";
+
+const { MathTrunc } = primordials;
+
+type TimeLike = number | string | Date;
+type PathLike = string | Buffer | URL;
+
+function getValidUnixTime(
+ value: TimeLike,
+ name: string,
+): [number, number] {
+ if (typeof value === "string") {
+ value = Number(value);
+ }
+
+ if (
+ typeof value === "number" &&
+ (Number.isNaN(value) || !Number.isFinite(value))
+ ) {
+ throw new Deno.errors.InvalidData(
+ `invalid ${name}, must not be infinity or NaN`,
+ );
+ }
+
+ const unixSeconds = toUnixTimestamp(value);
+
+ const seconds = MathTrunc(unixSeconds);
+ const nanoseconds = MathTrunc((unixSeconds * 1e3) - (seconds * 1e3)) * 1e6;
+
+ return [
+ seconds,
+ nanoseconds,
+ ];
+}
+
+export function lutimes(
+ path: PathLike,
+ atime: TimeLike,
+ mtime: TimeLike,
+ callback: CallbackWithError,
+): void {
+ if (!callback) {
+ throw new Error("No callback function supplied");
+ }
+ const [atimeSecs, atimeNanos] = getValidUnixTime(atime, "atime");
+ const [mtimeSecs, mtimeNanos] = getValidUnixTime(mtime, "mtime");
+
+ path = getValidatedPath(path).toString();
+
+ op_node_lutimes(path, atimeSecs, atimeNanos, mtimeSecs, mtimeNanos).then(
+ () => callback(null),
+ callback,
+ );
+}
+
+export function lutimesSync(
+ path: PathLike,
+ atime: TimeLike,
+ mtime: TimeLike,
+): void {
+ const { 0: atimeSecs, 1: atimeNanos } = getValidUnixTime(atime, "atime");
+ const { 0: mtimeSecs, 1: mtimeNanos } = getValidUnixTime(mtime, "mtime");
+
+ path = getValidatedPath(path).toString();
+
+ op_node_lutimes_sync(path, atimeSecs, atimeNanos, mtimeSecs, mtimeNanos);
+}
+
+export const lutimesPromise = promisify(lutimes) as (
+ path: PathLike,
+ atime: TimeLike,
+ mtime: TimeLike,
+) => Promise;
diff --git a/ext/node/polyfills/_fs/_fs_utimes.ts b/ext/node/polyfills/_fs/_fs_utimes.ts
index 1d0e5c1fff..3fff4a4623 100644
--- a/ext/node/polyfills/_fs/_fs_utimes.ts
+++ b/ext/node/polyfills/_fs/_fs_utimes.ts
@@ -4,13 +4,16 @@
// deno-lint-ignore-file prefer-primordials
import type { CallbackWithError } from "ext:deno_node/_fs/_fs_common.ts";
-import { pathFromURL } from "ext:deno_web/00_infra.js";
import { promisify } from "ext:deno_node/internal/util.mjs";
+import {
+ getValidatedPath,
+ toUnixTimestamp,
+} from "ext:deno_node/internal/fs/utils.mjs";
function getValidTime(
time: number | string | Date,
name: string,
-): number | Date {
+): number {
if (typeof time === "string") {
time = Number(time);
}
@@ -24,7 +27,7 @@ function getValidTime(
);
}
- return time;
+ return toUnixTimestamp(time);
}
export function utimes(
@@ -33,7 +36,7 @@ export function utimes(
mtime: number | string | Date,
callback: CallbackWithError,
) {
- path = path instanceof URL ? pathFromURL(path) : path;
+ path = getValidatedPath(path).toString();
if (!callback) {
throw new Deno.errors.InvalidData("No callback function supplied");
@@ -56,7 +59,7 @@ export function utimesSync(
atime: number | string | Date,
mtime: number | string | Date,
) {
- path = path instanceof URL ? pathFromURL(path) : path;
+ path = getValidatedPath(path).toString();
atime = getValidTime(atime, "atime");
mtime = getValidTime(mtime, "mtime");
diff --git a/ext/node/polyfills/fs.ts b/ext/node/polyfills/fs.ts
index f788f72b5f..6f0c53e4d2 100644
--- a/ext/node/polyfills/fs.ts
+++ b/ext/node/polyfills/fs.ts
@@ -29,6 +29,11 @@ import { ftruncate, ftruncateSync } from "ext:deno_node/_fs/_fs_ftruncate.ts";
import { futimes, futimesSync } from "ext:deno_node/_fs/_fs_futimes.ts";
import { link, linkPromise, linkSync } from "ext:deno_node/_fs/_fs_link.ts";
import { lstat, lstatPromise, lstatSync } from "ext:deno_node/_fs/_fs_lstat.ts";
+import {
+ lutimes,
+ lutimesPromise,
+ lutimesSync,
+} from "ext:deno_node/_fs/_fs_lutimes.ts";
import { mkdir, mkdirPromise, mkdirSync } from "ext:deno_node/_fs/_fs_mkdir.ts";
import {
mkdtemp,
@@ -123,6 +128,7 @@ import {
ReadStream,
WriteStream,
} from "ext:deno_node/internal/fs/streams.mjs";
+import { toUnixTimestamp as _toUnixTimestamp } from "ext:deno_node/internal/fs/utils.mjs";
const {
F_OK,
@@ -170,7 +176,7 @@ const promises = {
// lchown: promisify(lchown),
chown: chownPromise,
utimes: utimesPromise,
- // lutimes = promisify(lutimes),
+ lutimes: lutimesPromise,
realpath: realpathPromise,
mkdtemp: mkdtempPromise,
writeFile: writeFilePromise,
@@ -216,6 +222,8 @@ export default {
linkSync,
lstat,
lstatSync,
+ lutimes,
+ lutimesSync,
mkdir,
mkdirSync,
mkdtemp,
@@ -284,9 +292,13 @@ export default {
WriteStream,
writeSync,
X_OK,
+ // For tests
+ _toUnixTimestamp,
};
export {
+ // For tests
+ _toUnixTimestamp,
access,
accessSync,
appendFile,
@@ -323,6 +335,8 @@ export {
linkSync,
lstat,
lstatSync,
+ lutimes,
+ lutimesSync,
mkdir,
mkdirSync,
mkdtemp,
diff --git a/ext/node/polyfills/internal/fs/utils.mjs b/ext/node/polyfills/internal/fs/utils.mjs
index ec379ed99a..21c5892ce2 100644
--- a/ext/node/polyfills/internal/fs/utils.mjs
+++ b/ext/node/polyfills/internal/fs/utils.mjs
@@ -5,6 +5,8 @@
"use strict";
+import { primordials } from "ext:core/mod.js";
+const { DatePrototypeGetTime } = primordials;
import { Buffer } from "node:buffer";
import {
ERR_FS_EISDIR,
@@ -698,7 +700,7 @@ export function toUnixTimestamp(time, name = "time") {
}
if (isDate(time)) {
// Convert to 123.456 UNIX timestamp
- return Date.getTime(time) / 1000;
+ return DatePrototypeGetTime(time) / 1000;
}
throw new ERR_INVALID_ARG_TYPE(name, ["Date", "Time in seconds"], time);
}
diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc
index 8321173df0..5c0c854a88 100644
--- a/tests/node_compat/config.jsonc
+++ b/tests/node_compat/config.jsonc
@@ -359,6 +359,7 @@
"test-fs-rmdir-recursive-warns-on-file.js",
"test-fs-rmdir-recursive.js",
"test-fs-rmdir-type-check.js",
+ "test-fs-utimes.js",
"test-fs-watchfile.js",
"test-fs-write-buffer.js",
"test-fs-write-file-buffer.js",
diff --git a/tests/node_compat/runner/TODO.md b/tests/node_compat/runner/TODO.md
index 571e3ed66b..a36ad1b634 100644
--- a/tests/node_compat/runner/TODO.md
+++ b/tests/node_compat/runner/TODO.md
@@ -899,7 +899,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
- [parallel/test-fs-util-validateoffsetlength.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-util-validateoffsetlength.js)
- [parallel/test-fs-utils-get-dirents.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-utils-get-dirents.js)
- [parallel/test-fs-utimes-y2K38.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-utimes-y2K38.js)
-- [parallel/test-fs-utimes.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-utimes.js)
- [parallel/test-fs-watch-abort-signal.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-abort-signal.js)
- [parallel/test-fs-watch-close-when-destroyed.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-close-when-destroyed.js)
- [parallel/test-fs-watch-encoding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-watch-encoding.js)
diff --git a/tests/node_compat/test.ts b/tests/node_compat/test.ts
index 939fdf52a7..077abc2290 100644
--- a/tests/node_compat/test.ts
+++ b/tests/node_compat/test.ts
@@ -86,6 +86,7 @@ async function runTest(t: Deno.TestContext, path: string): Promise {
//"--unsafely-ignore-certificate-errors",
"--unstable-unsafe-proto",
"--unstable-bare-node-builtins",
+ "--unstable-fs",
"--v8-flags=" + v8Flags.join(),
];
if (usesNodeTest) {
diff --git a/tests/node_compat/test/parallel/test-fs-utimes.js b/tests/node_compat/test/parallel/test-fs-utimes.js
new file mode 100644
index 0000000000..f24e27f367
--- /dev/null
+++ b/tests/node_compat/test/parallel/test-fs-utimes.js
@@ -0,0 +1,218 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+const util = require('util');
+const fs = require('fs');
+const url = require('url');
+
+const tmpdir = require('../common/tmpdir');
+tmpdir.refresh();
+
+const lpath = `${tmpdir.path}/symlink`;
+fs.symlinkSync('unoent-entry', lpath);
+
+function stat_resource(resource, statSync = fs.statSync) {
+ if (typeof resource === 'string') {
+ return statSync(resource);
+ }
+ const stats = fs.fstatSync(resource);
+ // Ensure mtime has been written to disk
+ // except for directories on AIX where it cannot be synced
+ if ((common.isAIX || common.isIBMi) && stats.isDirectory())
+ return stats;
+ fs.fsyncSync(resource);
+ return fs.fstatSync(resource);
+}
+
+function check_mtime(resource, mtime, statSync) {
+ mtime = fs._toUnixTimestamp(mtime);
+ const stats = stat_resource(resource, statSync);
+ const real_mtime = fs._toUnixTimestamp(stats.mtime);
+ return mtime - real_mtime;
+}
+
+function expect_errno(syscall, resource, err, errno) {
+ assert(
+ err && (err.code === errno || err.code === 'ENOSYS'),
+ `FAILED: expect_errno ${util.inspect(arguments)}`
+ );
+}
+
+function expect_ok(syscall, resource, err, atime, mtime, statSync) {
+ const mtime_diff = check_mtime(resource, mtime, statSync);
+ assert(
+ // Check up to single-second precision.
+ // Sub-second precision is OS and fs dependant.
+ !err && (mtime_diff < 2) || err && err.code === 'ENOSYS',
+ `FAILED: expect_ok ${util.inspect(arguments)}
+ check_mtime: ${mtime_diff}`
+ );
+}
+
+const stats = fs.statSync(tmpdir.path);
+
+const asPath = (path) => path;
+const asUrl = (path) => url.pathToFileURL(path);
+
+const cases = [
+ [asPath, new Date('1982-09-10 13:37')],
+ [asPath, new Date()],
+ [asPath, 123456.789],
+ [asPath, stats.mtime],
+ [asPath, '123456', -1],
+ [asPath, new Date('2017-04-08T17:59:38.008Z')],
+ [asUrl, new Date()],
+];
+
+runTests(cases.values());
+
+function runTests(iter) {
+ const { value, done } = iter.next();
+ if (done) return;
+
+ // Support easy setting same or different atime / mtime values.
+ const [pathType, atime, mtime = atime] = value;
+
+ let fd;
+ //
+ // test async code paths
+ //
+ fs.utimes(pathType(tmpdir.path), atime, mtime, common.mustCall((err) => {
+ expect_ok('utimes', tmpdir.path, err, atime, mtime);
+
+ fs.lutimes(pathType(lpath), atime, mtime, common.mustCall((err) => {
+ expect_ok('lutimes', lpath, err, atime, mtime, fs.lstatSync);
+
+ fs.utimes(pathType('foobarbaz'), atime, mtime, common.mustCall((err) => {
+ expect_errno('utimes', 'foobarbaz', err, 'ENOENT');
+
+ // don't close this fd
+ if (common.isWindows) {
+ fd = fs.openSync(tmpdir.path, 'r+');
+ } else {
+ fd = fs.openSync(tmpdir.path, 'r');
+ }
+
+ fs.futimes(fd, atime, mtime, common.mustCall((err) => {
+ expect_ok('futimes', fd, err, atime, mtime);
+
+ syncTests();
+
+ setImmediate(common.mustCall(runTests), iter);
+ }));
+ }));
+ }));
+ }));
+
+ //
+ // test synchronized code paths, these functions throw on failure
+ //
+ function syncTests() {
+ fs.utimesSync(pathType(tmpdir.path), atime, mtime);
+ expect_ok('utimesSync', tmpdir.path, undefined, atime, mtime);
+
+ fs.lutimesSync(pathType(lpath), atime, mtime);
+ expect_ok('lutimesSync', lpath, undefined, atime, mtime, fs.lstatSync);
+
+ // Some systems don't have futimes
+ // if there's an error, it should be ENOSYS
+ try {
+ fs.futimesSync(fd, atime, mtime);
+ expect_ok('futimesSync', fd, undefined, atime, mtime);
+ } catch (ex) {
+ expect_errno('futimesSync', fd, ex, 'ENOSYS');
+ }
+
+ let err;
+ try {
+ fs.utimesSync(pathType('foobarbaz'), atime, mtime);
+ } catch (ex) {
+ err = ex;
+ }
+ expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT');
+
+ err = undefined;
+ }
+}
+
+const expectTypeError = {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError'
+};
+// utimes-only error cases
+{
+ assert.throws(
+ () => fs.utimes(0, new Date(), new Date(), common.mustNotCall()),
+ expectTypeError
+ );
+ assert.throws(
+ () => fs.utimesSync(0, new Date(), new Date()),
+ expectTypeError
+ );
+}
+
+// shared error cases
+[false, {}, [], null, undefined].forEach((i) => {
+ assert.throws(
+ () => fs.utimes(i, new Date(), new Date(), common.mustNotCall()),
+ expectTypeError
+ );
+ assert.throws(
+ () => fs.utimesSync(i, new Date(), new Date()),
+ expectTypeError
+ );
+ assert.throws(
+ () => fs.futimes(i, new Date(), new Date(), common.mustNotCall()),
+ expectTypeError
+ );
+ assert.throws(
+ () => fs.futimesSync(i, new Date(), new Date()),
+ expectTypeError
+ );
+});
+
+const expectRangeError = {
+ code: 'ERR_OUT_OF_RANGE',
+ name: 'RangeError',
+ message: 'The value of "fd" is out of range. ' +
+ 'It must be >= 0 && <= 2147483647. Received -1'
+};
+// futimes-only error cases
+{
+ assert.throws(
+ () => fs.futimes(-1, new Date(), new Date(), common.mustNotCall()),
+ expectRangeError
+ );
+ assert.throws(
+ () => fs.futimesSync(-1, new Date(), new Date()),
+ expectRangeError
+ );
+}
diff --git a/tests/unit/read_file_test.ts b/tests/unit/read_file_test.ts
index 1b7f2a5513..562bf99698 100644
--- a/tests/unit/read_file_test.ts
+++ b/tests/unit/read_file_test.ts
@@ -172,11 +172,7 @@ Deno.test(
try {
await Deno.readFile("tests/testdata/assets/");
} catch (e) {
- if (Deno.build.os === "windows") {
- assertEquals(e.code, "EPERM");
- } else {
- assertEquals(e.code, "EISDIR");
- }
+ assertEquals(e.code, "EISDIR");
}
},
);