diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index 9813b11fbd3273..7a04776822e75a 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -1316,15 +1316,15 @@ declare namespace Deno { /** The last modification time of the file. This corresponds to the `mtime` * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This * may not be available on all platforms. */ - modified: number | null; + mtime: Date | null; /** The last access time of the file. This corresponds to the `atime` * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not * be available on all platforms. */ - accessed: number | null; + atime: Date | null; /** The creation time of the file. This corresponds to the `birthtime` - * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may not - * be available on all platforms. */ - created: number | null; + * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may + * not be available on all platforms. */ + birthtime: Date | null; /** ID of the device containing the file. * * _Linux/Mac OS only._ */ diff --git a/cli/js/ops/fs/stat.ts b/cli/js/ops/fs/stat.ts index 6b7e5ea93098b8..7f28614d2419f6 100644 --- a/cli/js/ops/fs/stat.ts +++ b/cli/js/ops/fs/stat.ts @@ -4,9 +4,9 @@ import { build } from "../../build.ts"; export interface FileInfo { size: number; - modified: number | null; - accessed: number | null; - created: number | null; + mtime: Date | null; + atime: Date | null; + birthtime: Date | null; dev: number | null; ino: number | null; mode: number | null; @@ -26,9 +26,9 @@ export interface StatResponse { isDirectory: boolean; isSymlink: boolean; size: number; - modified: number; - accessed: number; - created: number; + mtime: number | null; + atime: number | null; + birthtime: number | null; // Null for stat(), but exists for readdir(). name: string | null; // Unix only members @@ -51,9 +51,9 @@ export function parseFileInfo(response: StatResponse): FileInfo { isDirectory: response.isDirectory, isSymlink: response.isSymlink, size: response.size, - modified: response.modified ? response.modified : null, - accessed: response.accessed ? response.accessed : null, - created: response.created ? response.created : null, + mtime: response.mtime != null ? new Date(response.mtime) : null, + atime: response.atime != null ? new Date(response.atime) : null, + birthtime: response.birthtime != null ? new Date(response.birthtime) : null, // Only non-null if on Unix dev: isUnix ? response.dev : null, ino: isUnix ? response.ino : null, diff --git a/cli/js/tests/stat_test.ts b/cli/js/tests/stat_test.ts index e4f4ae61eb604b..70ec5dc2e7caef 100644 --- a/cli/js/tests/stat_test.ts +++ b/cli/js/tests/stat_test.ts @@ -1,21 +1,31 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { unitTest, assert, assertEquals } from "./test_util.ts"; -// TODO Add tests for modified, accessed, and created fields once there is a way -// to create temp files. -unitTest({ perms: { read: true } }, 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); -}); +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; @@ -83,21 +93,32 @@ unitTest({ perms: { read: true } }, function lstatSyncNotFound(): void { assertEquals(badInfo, undefined); }); -unitTest({ perms: { read: true } }, async function statSuccess(): Promise< - void -> { - const packageInfo = await Deno.stat("README.md"); - assert(packageInfo.isFile); - assert(!packageInfo.isSymlink); - - const modulesInfo = await Deno.stat("cli/tests/symlink_to_subdir"); - assert(modulesInfo.isDirectory); - assert(!modulesInfo.isSymlink); - - const testsInfo = await Deno.stat("cli/tests"); - assert(testsInfo.isDirectory); - assert(!testsInfo.isSymlink); -}); +unitTest( + { perms: { read: true, write: true } }, + async function statSuccess(): Promise { + 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 { let caughtError = false; diff --git a/cli/js/tests/utime_test.ts b/cli/js/tests/utime_test.ts index a81600e540429a..63727ae6235353 100644 --- a/cli/js/tests/utime_test.ts +++ b/cli/js/tests/utime_test.ts @@ -4,9 +4,9 @@ import { unitTest, assert } from "./test_util.ts"; // Allow 10 second difference. // Note this might not be enough for FAT (but we are not testing on such fs). // eslint-disable-next-line @typescript-eslint/no-explicit-any -function assertFuzzyTimestampEquals(t1: any, t2: number): void { - assert(typeof t1 === "number"); - assert(Math.abs(t1 - t2) < 10); +function assertFuzzyTimestampEquals(t1: Date | null, t2: Date): void { + assert(t1 instanceof Date); + assert(Math.abs(t1.valueOf() - t2.valueOf()) < 10_000); } unitTest( @@ -23,8 +23,8 @@ unitTest( Deno.utimeSync(filename, atime, mtime); const fileInfo = Deno.statSync(filename); - assertFuzzyTimestampEquals(fileInfo.accessed, atime); - assertFuzzyTimestampEquals(fileInfo.modified, mtime); + assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000)); } ); @@ -38,8 +38,8 @@ unitTest( Deno.utimeSync(testDir, atime, mtime); const dirInfo = Deno.statSync(testDir); - assertFuzzyTimestampEquals(dirInfo.accessed, atime); - assertFuzzyTimestampEquals(dirInfo.modified, mtime); + assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000)); } ); @@ -48,13 +48,13 @@ unitTest( function utimeSyncDateSuccess(): void { const testDir = Deno.makeTempDirSync(); - const atime = 1000; - const mtime = 50000; - Deno.utimeSync(testDir, new Date(atime * 1000), new Date(mtime * 1000)); + const atime = new Date(1000_000); + const mtime = new Date(50000_000); + Deno.utimeSync(testDir, atime, mtime); const dirInfo = Deno.statSync(testDir); - assertFuzzyTimestampEquals(dirInfo.accessed, atime); - assertFuzzyTimestampEquals(dirInfo.modified, mtime); + assertFuzzyTimestampEquals(dirInfo.atime, atime); + assertFuzzyTimestampEquals(dirInfo.mtime, mtime); } ); @@ -71,15 +71,8 @@ unitTest( Deno.utimeSync(filename, atime, mtime); const fileInfo = Deno.statSync(filename); - // atime and mtime must be scaled by a factor of 1000 to be recorded in seconds - assertFuzzyTimestampEquals( - fileInfo.accessed, - Math.trunc(atime.valueOf() / 1000) - ); - assertFuzzyTimestampEquals( - fileInfo.modified, - Math.trunc(mtime.valueOf() / 1000) - ); + assertFuzzyTimestampEquals(fileInfo.atime, atime); + assertFuzzyTimestampEquals(fileInfo.mtime, mtime); } ); @@ -95,8 +88,8 @@ unitTest( Deno.utimeSync(testDir, atime, mtime); const dirInfo = Deno.statSync(testDir); - assertFuzzyTimestampEquals(dirInfo.accessed, atime); - assertFuzzyTimestampEquals(dirInfo.modified, mtime); + assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000)); } ); @@ -148,8 +141,8 @@ unitTest( await Deno.utime(filename, atime, mtime); const fileInfo = Deno.statSync(filename); - assertFuzzyTimestampEquals(fileInfo.accessed, atime); - assertFuzzyTimestampEquals(fileInfo.modified, mtime); + assertFuzzyTimestampEquals(fileInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(fileInfo.mtime, new Date(mtime * 1000)); } ); @@ -163,8 +156,8 @@ unitTest( await Deno.utime(testDir, atime, mtime); const dirInfo = Deno.statSync(testDir); - assertFuzzyTimestampEquals(dirInfo.accessed, atime); - assertFuzzyTimestampEquals(dirInfo.modified, mtime); + assertFuzzyTimestampEquals(dirInfo.atime, new Date(atime * 1000)); + assertFuzzyTimestampEquals(dirInfo.mtime, new Date(mtime * 1000)); } ); @@ -173,13 +166,13 @@ unitTest( async function utimeDateSuccess(): Promise { const testDir = Deno.makeTempDirSync(); - const atime = 1000; - const mtime = 50000; - await Deno.utime(testDir, new Date(atime * 1000), new Date(mtime * 1000)); + 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.accessed, atime); - assertFuzzyTimestampEquals(dirInfo.modified, mtime); + assertFuzzyTimestampEquals(dirInfo.atime, atime); + assertFuzzyTimestampEquals(dirInfo.mtime, mtime); } ); @@ -197,15 +190,8 @@ unitTest( await Deno.utime(filename, atime, mtime); const fileInfo = Deno.statSync(filename); - // The dates must be scaled by a factored of 1000 to make them seconds - assertFuzzyTimestampEquals( - fileInfo.accessed, - Math.trunc(atime.valueOf() / 1000) - ); - assertFuzzyTimestampEquals( - fileInfo.modified, - Math.trunc(mtime.valueOf() / 1000) - ); + assertFuzzyTimestampEquals(fileInfo.atime, atime); + assertFuzzyTimestampEquals(fileInfo.mtime, mtime); } ); diff --git a/cli/op_error.rs b/cli/op_error.rs index a687eed2b7c71f..f319ec90606144 100644 --- a/cli/op_error.rs +++ b/cli/op_error.rs @@ -18,6 +18,7 @@ use crate::import_map::ImportMapError; use deno_core::ErrBox; use deno_core::ModuleResolutionError; use rustyline::error::ReadlineError; +use serde_json; use std::env::VarError; use std::error::Error; use std::fmt; diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 5ab53481092df2..84ac4ed51371d1 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -12,7 +12,9 @@ use deno_core::ZeroCopyBuf; use futures::future::FutureExt; use std::convert::From; use std::env::{current_dir, set_current_dir, temp_dir}; +use std::io; use std::path::{Path, PathBuf}; +use std::time::SystemTime; use std::time::UNIX_EPOCH; use rand::{thread_rng, Rng}; @@ -441,14 +443,19 @@ fn op_copy_file( }) } -macro_rules! to_seconds { - ($time:expr) => {{ - // Unwrap is safe here as if the file is before the unix epoch - // something is very wrong. - $time - .and_then(|t| Ok(t.duration_since(UNIX_EPOCH).unwrap().as_secs())) - .unwrap_or(0) - }}; +fn to_msec(maybe_time: Result) -> serde_json::Value { + match maybe_time { + Ok(time) => { + let msec = time + .duration_since(UNIX_EPOCH) + .map(|t| t.as_secs_f64() * 1000f64) + .unwrap_or_else(|err| err.duration().as_secs_f64() * -1000f64); + serde_json::Number::from_f64(msec) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null) + } + Err(_) => serde_json::Value::Null, + } } #[inline(always)] @@ -477,10 +484,10 @@ fn get_stat_json( "isDirectory": metadata.is_dir(), "isSymlink": metadata.file_type().is_symlink(), "size": metadata.len(), - // In seconds. Available on both Unix or Windows. - "modified":to_seconds!(metadata.modified()), - "accessed":to_seconds!(metadata.accessed()), - "created":to_seconds!(metadata.created()), + // In milliseconds, like JavaScript. Available on both Unix or Windows. + "mtime": to_msec(metadata.modified()), + "atime": to_msec(metadata.accessed()), + "birthtime": to_msec(metadata.created()), // Following are only valid under Unix. "dev": usm!(dev), "ino": usm!(ino),