fs: add *timeNs properties to BigInt Stats objects

- Extend the aliased buffer for stats objects to contain
  the entire time spec (seconds and nanoseconds) for the time
  values instead of calculating the milliseconds in C++ and
  lose precision there.
- Calculate the nanosecond-precision time values in JS and expose
  them in BigInt Stats objects as `*timeNs`. The
  millisecond-precision values are now calculated from the
  nanosecond-precision values.

PR-URL: https://github.com/nodejs/node/pull/21387
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Gus Caplan <me@gus.host>
This commit is contained in:
Joyee Cheung 2018-06-19 02:58:49 +08:00
parent 4b1bcae681
commit b245257b70
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
7 changed files with 288 additions and 142 deletions

View File

@ -512,7 +512,8 @@ A `fs.Stats` object provides information about a file.
Objects returned from [`fs.stat()`][], [`fs.lstat()`][] and [`fs.fstat()`][] and Objects returned from [`fs.stat()`][], [`fs.lstat()`][] and [`fs.fstat()`][] and
their synchronous counterparts are of this type. their synchronous counterparts are of this type.
If `bigint` in the `options` passed to those methods is true, the numeric values If `bigint` in the `options` passed to those methods is true, the numeric values
will be `bigint` instead of `number`. will be `bigint` instead of `number`, and the object will contain additional
nanosecond-precision properties suffixed with `Ns`.
```console ```console
Stats { Stats {
@ -539,7 +540,7 @@ Stats {
`bigint` version: `bigint` version:
```console ```console
Stats { BigIntStats {
dev: 2114n, dev: 2114n,
ino: 48064969n, ino: 48064969n,
mode: 33188n, mode: 33188n,
@ -554,6 +555,10 @@ Stats {
mtimeMs: 1318289051000n, mtimeMs: 1318289051000n,
ctimeMs: 1318289051000n, ctimeMs: 1318289051000n,
birthtimeMs: 1318289051000n, birthtimeMs: 1318289051000n,
atimeNs: 1318289051000000000n,
mtimeNs: 1318289051000000000n,
ctimeNs: 1318289051000000000n,
birthtimeNs: 1318289051000000000n,
atime: Mon, 10 Oct 2011 23:24:11 GMT, atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT, mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT, ctime: Mon, 10 Oct 2011 23:24:11 GMT,
@ -726,6 +731,54 @@ added: v8.1.0
The timestamp indicating the creation time of this file expressed in The timestamp indicating the creation time of this file expressed in
milliseconds since the POSIX Epoch. milliseconds since the POSIX Epoch.
### stats.atimeNs
<!-- YAML
added: REPLACEME
-->
* {bigint}
Only present when `bigint: true` is passed into the method that generates
the object.
The timestamp indicating the last time this file was accessed expressed in
nanoseconds since the POSIX Epoch.
### stats.mtimeNs
<!-- YAML
added: REPLACEME
-->
* {bigint}
Only present when `bigint: true` is passed into the method that generates
the object.
The timestamp indicating the last time this file was modified expressed in
nanoseconds since the POSIX Epoch.
### stats.ctimeNs
<!-- YAML
added: REPLACEME
-->
* {bigint}
Only present when `bigint: true` is passed into the method that generates
the object.
The timestamp indicating the last time the file status was changed expressed
in nanoseconds since the POSIX Epoch.
### stats.birthtimeNs
<!-- YAML
added: REPLACEME
-->
* {bigint}
Only present when `bigint: true` is passed into the method that generates
the object.
The timestamp indicating the creation time of this file expressed in
nanoseconds since the POSIX Epoch.
### stats.atime ### stats.atime
<!-- YAML <!-- YAML
added: v0.11.13 added: v0.11.13
@ -765,8 +818,17 @@ The timestamp indicating the creation time of this file.
### Stat Time Values ### Stat Time Values
The `atimeMs`, `mtimeMs`, `ctimeMs`, `birthtimeMs` properties are The `atimeMs`, `mtimeMs`, `ctimeMs`, `birthtimeMs` properties are
[numbers][MDN-Number] that hold the corresponding times in milliseconds. Their numeric values that hold the corresponding times in milliseconds. Their
precision is platform specific. `atime`, `mtime`, `ctime`, and `birthtime` are precision is platform specific. When `bigint: true` is passed into the
method that generates the object, the properties will be [bigints][],
otherwise they will be [numbers][MDN-Number].
The `atimeNs`, `mtimeNs`, `ctimeNs`, `birthtimeNs` properties are
[bigints][] that hold the corresponding times in nanoseconds. They are
only present when `bigint: true` is passed into the method that generates
the object. Their precision is platform specific.
`atime`, `mtime`, `ctime`, and `birthtime` are
[`Date`][MDN-Date] object alternate representations of the various times. The [`Date`][MDN-Date] object alternate representations of the various times. The
`Date` and number values are not connected. Assigning a new number value, or `Date` and number values are not connected. Assigning a new number value, or
mutating the `Date` value, will not be reflected in the corresponding alternate mutating the `Date` value, will not be reflected in the corresponding alternate
@ -4976,6 +5038,7 @@ the file contents.
[`net.Socket`]: net.html#net_class_net_socket [`net.Socket`]: net.html#net_class_net_socket
[`stat()`]: fs.html#fs_fs_stat_path_options_callback [`stat()`]: fs.html#fs_fs_stat_path_options_callback
[`util.promisify()`]: util.html#util_util_promisify_original [`util.promisify()`]: util.html#util_util_promisify_original
[bigints]: https://tc39.github.io/proposal-bigint
[Caveats]: #fs_caveats [Caveats]: #fs_caveats
[Common System Errors]: errors.html#errors_common_system_errors [Common System Errors]: errors.html#errors_common_system_errors
[FS Constants]: #fs_fs_constants_1 [FS Constants]: #fs_fs_constants_1

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const { Reflect } = primordials; const { Object, Reflect } = primordials;
const { Buffer, kMaxLength } = require('buffer'); const { Buffer, kMaxLength } = require('buffer');
const { const {
@ -16,7 +16,8 @@ const {
} = require('internal/errors'); } = require('internal/errors');
const { const {
isUint8Array, isUint8Array,
isDate isDate,
isBigUint64Array
} = require('internal/util/types'); } = require('internal/util/types');
const { once } = require('internal/util'); const { once } = require('internal/util');
const { toPathIfFileURL } = require('internal/url'); const { toPathIfFileURL } = require('internal/url');
@ -230,27 +231,9 @@ function preprocessSymlinkDestination(path, type, linkPath) {
} }
} }
function dateFromNumeric(num) {
return new Date(Number(num) + 0.5);
}
// Constructor for file stats. // Constructor for file stats.
function Stats( function StatsBase(dev, mode, nlink, uid, gid, rdev, blksize,
dev, ino, size, blocks) {
mode,
nlink,
uid,
gid,
rdev,
blksize,
ino,
size,
blocks,
atim_msec,
mtim_msec,
ctim_msec,
birthtim_msec
) {
this.dev = dev; this.dev = dev;
this.mode = mode; this.mode = mode;
this.nlink = nlink; this.nlink = nlink;
@ -261,63 +244,132 @@ function Stats(
this.ino = ino; this.ino = ino;
this.size = size; this.size = size;
this.blocks = blocks; this.blocks = blocks;
this.atimeMs = atim_msec;
this.mtimeMs = mtim_msec;
this.ctimeMs = ctim_msec;
this.birthtimeMs = birthtim_msec;
this.atime = dateFromNumeric(atim_msec);
this.mtime = dateFromNumeric(mtim_msec);
this.ctime = dateFromNumeric(ctim_msec);
this.birthtime = dateFromNumeric(birthtim_msec);
} }
StatsBase.prototype.isDirectory = function() {
return this._checkModeProperty(S_IFDIR);
};
StatsBase.prototype.isFile = function() {
return this._checkModeProperty(S_IFREG);
};
StatsBase.prototype.isBlockDevice = function() {
return this._checkModeProperty(S_IFBLK);
};
StatsBase.prototype.isCharacterDevice = function() {
return this._checkModeProperty(S_IFCHR);
};
StatsBase.prototype.isSymbolicLink = function() {
return this._checkModeProperty(S_IFLNK);
};
StatsBase.prototype.isFIFO = function() {
return this._checkModeProperty(S_IFIFO);
};
StatsBase.prototype.isSocket = function() {
return this._checkModeProperty(S_IFSOCK);
};
const kNsPerMsBigInt = 10n ** 6n;
const kNsPerSecBigInt = 10n ** 9n;
const kMsPerSec = 10 ** 3;
const kNsPerMs = 10 ** 6;
function msFromTimeSpec(sec, nsec) {
return sec * kMsPerSec + nsec / kNsPerMs;
}
function nsFromTimeSpecBigInt(sec, nsec) {
return sec * kNsPerSecBigInt + nsec;
}
function dateFromMs(ms) {
return new Date(Number(ms) + 0.5);
}
function BigIntStats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeNs, mtimeNs, ctimeNs, birthtimeNs) {
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks);
this.atimeMs = atimeNs / kNsPerMsBigInt;
this.mtimeMs = mtimeNs / kNsPerMsBigInt;
this.ctimeMs = ctimeNs / kNsPerMsBigInt;
this.birthtimeMs = birthtimeNs / kNsPerMsBigInt;
this.atimeNs = atimeNs;
this.mtimeNs = mtimeNs;
this.ctimeNs = ctimeNs;
this.birthtimeNs = birthtimeNs;
this.atime = dateFromMs(this.atimeMs);
this.mtime = dateFromMs(this.mtimeMs);
this.ctime = dateFromMs(this.ctimeMs);
this.birthtime = dateFromMs(this.birthtimeMs);
}
Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
Object.setPrototypeOf(BigIntStats, StatsBase);
BigIntStats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
property === S_IFSOCK)) {
return false; // Some types are not available on Windows
}
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
};
function Stats(dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks,
atimeMs, mtimeMs, ctimeMs, birthtimeMs) {
StatsBase.call(this, dev, mode, nlink, uid, gid, rdev, blksize,
ino, size, blocks);
this.atimeMs = atimeMs;
this.mtimeMs = mtimeMs;
this.ctimeMs = ctimeMs;
this.birthtimeMs = birthtimeMs;
this.atime = dateFromMs(atimeMs);
this.mtime = dateFromMs(mtimeMs);
this.ctime = dateFromMs(ctimeMs);
this.birthtime = dateFromMs(birthtimeMs);
}
Object.setPrototypeOf(Stats.prototype, StatsBase.prototype);
Object.setPrototypeOf(Stats, StatsBase);
Stats.prototype._checkModeProperty = function(property) { Stats.prototype._checkModeProperty = function(property) {
if (isWindows && (property === S_IFIFO || property === S_IFBLK || if (isWindows && (property === S_IFIFO || property === S_IFBLK ||
property === S_IFSOCK)) { property === S_IFSOCK)) {
return false; // Some types are not available on Windows return false; // Some types are not available on Windows
} }
if (typeof this.mode === 'bigint') { // eslint-disable-line valid-typeof
return (this.mode & BigInt(S_IFMT)) === BigInt(property);
}
return (this.mode & S_IFMT) === property; return (this.mode & S_IFMT) === property;
}; };
Stats.prototype.isDirectory = function() {
return this._checkModeProperty(S_IFDIR);
};
Stats.prototype.isFile = function() {
return this._checkModeProperty(S_IFREG);
};
Stats.prototype.isBlockDevice = function() {
return this._checkModeProperty(S_IFBLK);
};
Stats.prototype.isCharacterDevice = function() {
return this._checkModeProperty(S_IFCHR);
};
Stats.prototype.isSymbolicLink = function() {
return this._checkModeProperty(S_IFLNK);
};
Stats.prototype.isFIFO = function() {
return this._checkModeProperty(S_IFIFO);
};
Stats.prototype.isSocket = function() {
return this._checkModeProperty(S_IFSOCK);
};
function getStatsFromBinding(stats, offset = 0) { function getStatsFromBinding(stats, offset = 0) {
return new Stats(stats[0 + offset], stats[1 + offset], stats[2 + offset], if (isBigUint64Array(stats)) {
stats[3 + offset], stats[4 + offset], stats[5 + offset], return new BigIntStats(
stats[6 + offset], // blksize stats[0 + offset], stats[1 + offset], stats[2 + offset],
stats[7 + offset], stats[8 + offset], stats[3 + offset], stats[4 + offset], stats[5 + offset],
stats[9 + offset], // blocks stats[6 + offset], stats[7 + offset], stats[8 + offset],
stats[10 + offset], stats[11 + offset], stats[9 + offset],
stats[12 + offset], stats[13 + offset]); nsFromTimeSpecBigInt(stats[10 + offset], stats[11 + offset]),
nsFromTimeSpecBigInt(stats[12 + offset], stats[13 + offset]),
nsFromTimeSpecBigInt(stats[14 + offset], stats[15 + offset]),
nsFromTimeSpecBigInt(stats[16 + offset], stats[17 + offset])
);
}
return new Stats(
stats[0 + offset], stats[1 + offset], stats[2 + offset],
stats[3 + offset], stats[4 + offset], stats[5 + offset],
stats[6 + offset], stats[7 + offset], stats[8 + offset],
stats[9 + offset],
msFromTimeSpec(stats[10 + offset], stats[11 + offset]),
msFromTimeSpec(stats[12 + offset], stats[13 + offset]),
msFromTimeSpec(stats[14 + offset], stats[15 + offset]),
msFromTimeSpec(stats[16 + offset], stats[17 + offset])
);
} }
function stringToFlags(flags) { function stringToFlags(flags) {
@ -453,6 +505,7 @@ function warnOnNonPortableTemplate(template) {
module.exports = { module.exports = {
assertEncoding, assertEncoding,
BigIntStats, // for testing
copyObject, copyObject,
Dirent, Dirent,
getDirents, getDirents,

View File

@ -102,10 +102,32 @@ struct PackageConfig {
}; };
} // namespace loader } // namespace loader
enum class FsStatsOffset {
kDev = 0,
kMode,
kNlink,
kUid,
kGid,
kRdev,
kBlkSize,
kIno,
kSize,
kBlocks,
kATimeSec,
kATimeNsec,
kMTimeSec,
kMTimeNsec,
kCTimeSec,
kCTimeNsec,
kBirthTimeSec,
kBirthTimeNsec,
kFsStatsFieldsNumber
};
// Stat fields buffers contain twice the number of entries in an uv_stat_t // Stat fields buffers contain twice the number of entries in an uv_stat_t
// because `fs.StatWatcher` needs room to store 2 `fs.Stats` instances. // because `fs.StatWatcher` needs room to store 2 `fs.Stats` instances.
constexpr size_t kFsStatsFieldsNumber = 14; constexpr size_t kFsStatsBufferLength =
constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber) * 2;
// PER_ISOLATE_* macros: We have a lot of per-isolate properties // PER_ISOLATE_* macros: We have a lot of per-isolate properties
// and adding and maintaining their getters and setters by hand would be // and adding and maintaining their getters and setters by hand would be

View File

@ -2197,10 +2197,13 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "mkdtemp", Mkdtemp); env->SetMethod(target, "mkdtemp", Mkdtemp);
target->Set(context, target
FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"), ->Set(context,
Integer::New(isolate, kFsStatsFieldsNumber)) FIXED_ONE_BYTE_STRING(isolate, "kFsStatsFieldsNumber"),
.Check(); Integer::New(
isolate,
static_cast<int32_t>(FsStatsOffset::kFsStatsFieldsNumber)))
.Check();
target->Set(context, target->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "statValues"), FIXED_ONE_BYTE_STRING(isolate, "statValues"),

View File

@ -6,6 +6,7 @@
#include "node.h" #include "node.h"
#include "stream_base.h" #include "stream_base.h"
#include "req_wrap-inl.h" #include "req_wrap-inl.h"
#include <iostream>
namespace node { namespace node {
@ -149,74 +150,48 @@ class FSReqCallback : public FSReqBase {
FSReqCallback& operator=(const FSReqCallback&) = delete; FSReqCallback& operator=(const FSReqCallback&) = delete;
}; };
// Wordaround a GCC4.9 bug that C++14 N3652 was not implemented
// Refs: https://www.gnu.org/software/gcc/projects/cxx-status.html#cxx14
// Refs: https://isocpp.org/files/papers/N3652.html
#if __cpp_constexpr < 201304
# define constexpr inline
#endif
template <typename NativeT,
// SFINAE limit NativeT to arithmetic types
typename = std::enable_if<std::is_arithmetic<NativeT>::value>>
constexpr NativeT ToNative(uv_timespec_t ts) {
// This template has exactly two specializations below.
static_assert(std::is_arithmetic<NativeT>::value == false, "Not implemented");
return NativeT();
}
template <>
constexpr double ToNative(uv_timespec_t ts) {
// We need to do a static_cast since the original FS values are ulong.
/* NOLINTNEXTLINE(runtime/int) */
const auto u_sec = static_cast<unsigned long>(ts.tv_sec);
const double full_sec = u_sec * 1000.0;
/* NOLINTNEXTLINE(runtime/int) */
const auto u_nsec = static_cast<unsigned long>(ts.tv_nsec);
const double full_nsec = u_nsec / 1000'000.0;
return full_sec + full_nsec;
}
template <>
constexpr uint64_t ToNative(uv_timespec_t ts) {
// We need to do a static_cast since the original FS values are ulong.
/* NOLINTNEXTLINE(runtime/int) */
const auto u_sec = static_cast<unsigned long>(ts.tv_sec);
const auto full_sec = static_cast<uint64_t>(u_sec) * 1000UL;
/* NOLINTNEXTLINE(runtime/int) */
const auto u_nsec = static_cast<unsigned long>(ts.tv_nsec);
const auto full_nsec = static_cast<uint64_t>(u_nsec) / 1000'000UL;
return full_sec + full_nsec;
}
#undef constexpr // end N3652 bug workaround
template <typename NativeT, typename V8T> template <typename NativeT, typename V8T>
constexpr void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields, constexpr void FillStatsArray(AliasedBufferBase<NativeT, V8T>* fields,
const uv_stat_t* s, const uv_stat_t* s,
const size_t offset = 0) { const size_t offset = 0) {
fields->SetValue(offset + 0, static_cast<NativeT>(s->st_dev)); #define SET_FIELD_WITH_STAT(stat_offset, stat) \
fields->SetValue(offset + 1, static_cast<NativeT>(s->st_mode)); fields->SetValue(offset + static_cast<size_t>(FsStatsOffset::stat_offset), \
fields->SetValue(offset + 2, static_cast<NativeT>(s->st_nlink)); static_cast<NativeT>(stat))
fields->SetValue(offset + 3, static_cast<NativeT>(s->st_uid));
fields->SetValue(offset + 4, static_cast<NativeT>(s->st_gid)); #define SET_FIELD_WITH_TIME_STAT(stat_offset, stat) \
fields->SetValue(offset + 5, static_cast<NativeT>(s->st_rdev)); /* NOLINTNEXTLINE(runtime/int) */ \
fields->SetValue(offset + 6, static_cast<NativeT>(s->st_blksize)); SET_FIELD_WITH_STAT(stat_offset, static_cast<unsigned long>(stat))
fields->SetValue(offset + 7, static_cast<NativeT>(s->st_ino));
fields->SetValue(offset + 8, static_cast<NativeT>(s->st_size)); SET_FIELD_WITH_STAT(kDev, s->st_dev);
fields->SetValue(offset + 9, static_cast<NativeT>(s->st_blocks)); SET_FIELD_WITH_STAT(kMode, s->st_mode);
// Dates. SET_FIELD_WITH_STAT(kNlink, s->st_nlink);
fields->SetValue(offset + 10, ToNative<NativeT>(s->st_atim)); SET_FIELD_WITH_STAT(kUid, s->st_uid);
fields->SetValue(offset + 11, ToNative<NativeT>(s->st_mtim)); SET_FIELD_WITH_STAT(kGid, s->st_gid);
fields->SetValue(offset + 12, ToNative<NativeT>(s->st_ctim)); SET_FIELD_WITH_STAT(kRdev, s->st_rdev);
fields->SetValue(offset + 13, ToNative<NativeT>(s->st_birthtim)); SET_FIELD_WITH_STAT(kBlkSize, s->st_blksize);
SET_FIELD_WITH_STAT(kIno, s->st_ino);
SET_FIELD_WITH_STAT(kSize, s->st_size);
SET_FIELD_WITH_STAT(kBlocks, s->st_blocks);
SET_FIELD_WITH_TIME_STAT(kATimeSec, s->st_atim.tv_sec);
SET_FIELD_WITH_TIME_STAT(kATimeNsec, s->st_atim.tv_nsec);
SET_FIELD_WITH_TIME_STAT(kMTimeSec, s->st_mtim.tv_sec);
SET_FIELD_WITH_TIME_STAT(kMTimeNsec, s->st_mtim.tv_nsec);
SET_FIELD_WITH_TIME_STAT(kCTimeSec, s->st_ctim.tv_sec);
SET_FIELD_WITH_TIME_STAT(kCTimeNsec, s->st_ctim.tv_nsec);
SET_FIELD_WITH_TIME_STAT(kBirthTimeSec, s->st_birthtim.tv_sec);
SET_FIELD_WITH_TIME_STAT(kBirthTimeNsec, s->st_birthtim.tv_nsec);
#undef SET_FIELD_WITH_TIME_STAT
#undef SET_FIELD_WITH_STAT
} }
inline Local<Value> FillGlobalStatsArray(Environment* env, inline Local<Value> FillGlobalStatsArray(Environment* env,
const bool use_bigint, const bool use_bigint,
const uv_stat_t* s, const uv_stat_t* s,
const bool second = false) { const bool second = false) {
const ptrdiff_t offset = second ? kFsStatsFieldsNumber : 0; const ptrdiff_t offset =
second ? static_cast<ptrdiff_t>(FsStatsOffset::kFsStatsFieldsNumber) : 0;
if (use_bigint) { if (use_bigint) {
auto* const arr = env->fs_stats_field_bigint_array(); auto* const arr = env->fs_stats_field_bigint_array();
FillStatsArray(arr, s, offset); FillStatsArray(arr, s, offset);
@ -302,7 +277,9 @@ class FSReqPromise : public FSReqBase {
private: private:
FSReqPromise(Environment* env, v8::Local<v8::Object> obj, bool use_bigint) FSReqPromise(Environment* env, v8::Local<v8::Object> obj, bool use_bigint)
: FSReqBase(env, obj, AsyncWrap::PROVIDER_FSREQPROMISE, use_bigint), : FSReqBase(env, obj, AsyncWrap::PROVIDER_FSREQPROMISE, use_bigint),
stats_field_array_(env->isolate(), kFsStatsFieldsNumber) {} stats_field_array_(
env->isolate(),
static_cast<size_t>(FsStatsOffset::kFsStatsFieldsNumber)) {}
bool finished_ = false; bool finished_ = false;
AliasedBufferT stats_field_array_; AliasedBufferT stats_field_array_;

View File

@ -59,6 +59,26 @@ function verifyStats(bigintStats, numStats) {
bigintStats.isSymbolicLink(), bigintStats.isSymbolicLink(),
numStats.isSymbolicLink() numStats.isSymbolicLink()
); );
} else if (key.endsWith('Ms')) {
const nsKey = key.replace('Ms', 'Ns');
const msFromBigInt = bigintStats[key];
const nsFromBigInt = bigintStats[nsKey];
const msFromBigIntNs = Number(nsFromBigInt / (10n ** 6n));
const msFromNum = numStats[key];
// The difference between the millisecond-precision values should be
// smaller than 2
assert(
Math.abs(msFromNum - Number(msFromBigInt)) < 2,
`Number version ${key} = ${msFromNum}, ` +
`BigInt version ${key} = ${msFromBigInt}n`);
// The difference between the millisecond-precision value and the
// nanosecond-precision value scaled down to milliseconds should be
// smaller than 2
assert(
Math.abs(msFromNum - Number(msFromBigIntNs)) < 2,
`Number version ${key} = ${msFromNum}, ` +
`BigInt version ${nsKey} = ${nsFromBigInt}n` +
` = ${msFromBigIntNs}ms`);
} else if (Number.isSafeInteger(val)) { } else if (Number.isSafeInteger(val)) {
assert.strictEqual( assert.strictEqual(
bigintStats[key], BigInt(val), bigintStats[key], BigInt(val),

View File

@ -1,14 +1,18 @@
'use strict'; 'use strict';
// Flags: --expose-internals
const common = require('../common'); const common = require('../common');
const assert = require('assert'); const assert = require('assert');
const { BigIntStats } = require('internal/fs/utils');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const tmpdir = require('../common/tmpdir'); const tmpdir = require('../common/tmpdir');
const enoentFile = path.join(tmpdir.path, 'non-existent-file'); const enoentFile = path.join(tmpdir.path, 'non-existent-file');
const expectedStatObject = new fs.Stats( const expectedStatObject = new BigIntStats(
0n, // dev 0n, // dev
0n, // mode 0n, // mode
0n, // nlink 0n, // nlink
@ -19,10 +23,14 @@ const expectedStatObject = new fs.Stats(
0n, // ino 0n, // ino
0n, // size 0n, // size
0n, // blocks 0n, // blocks
0n, // atim_msec 0n, // atimeMs
0n, // mtim_msec 0n, // mtimeMs
0n, // ctim_msec 0n, // ctimeMs
0n // birthtim_msec 0n, // birthtimeMs
0n, // atimeNs
0n, // mtimeNs
0n, // ctimeNs
0n // birthtimeNs
); );
tmpdir.refresh(); tmpdir.refresh();