|
| 1 | +import * as common from '../common/index.mjs'; |
| 2 | + |
| 3 | +// Test timestamps returned by fsPromises.stat and fs.statSync |
| 4 | + |
| 5 | +import fs from 'node:fs'; |
| 6 | +import fsPromises from 'node:fs/promises'; |
| 7 | +import path from 'node:path'; |
| 8 | +import assert from 'node:assert'; |
| 9 | +import tmpdir from '../common/tmpdir.js'; |
| 10 | + |
| 11 | +// On some platforms (for example, ppc64) boundaries are tighter |
| 12 | +// than usual. If we catch these errors, skip corresponding test. |
| 13 | +const ignoredErrors = new Set(['EINVAL', 'EOVERFLOW']); |
| 14 | + |
| 15 | +tmpdir.refresh(); |
| 16 | +const filepath = path.resolve(tmpdir.path, 'timestamp'); |
| 17 | + |
| 18 | +await (await fsPromises.open(filepath, 'w')).close(); |
| 19 | + |
| 20 | +// Date might round down timestamp |
| 21 | +function closeEnough(actual, expected, margin) { |
| 22 | + // On ppc64, value is rounded to seconds |
| 23 | + if (process.arch === 'ppc64') { |
| 24 | + margin += 1000; |
| 25 | + } |
| 26 | + assert.ok(Math.abs(Number(actual - expected)) < margin, |
| 27 | + `expected ${expected} ± ${margin}, got ${actual}`); |
| 28 | +} |
| 29 | + |
| 30 | +async function runTest(atime, mtime, margin = 0) { |
| 31 | + margin += Number.EPSILON; |
| 32 | + try { |
| 33 | + await fsPromises.utimes(filepath, new Date(atime), new Date(mtime)); |
| 34 | + } catch (e) { |
| 35 | + if (ignoredErrors.has(e.code)) return; |
| 36 | + throw e; |
| 37 | + } |
| 38 | + |
| 39 | + const stats = await fsPromises.stat(filepath); |
| 40 | + closeEnough(stats.atimeMs, atime, margin); |
| 41 | + closeEnough(stats.mtimeMs, mtime, margin); |
| 42 | + closeEnough(stats.atime.getTime(), new Date(atime).getTime(), margin); |
| 43 | + closeEnough(stats.mtime.getTime(), new Date(mtime).getTime(), margin); |
| 44 | + |
| 45 | + const statsBigint = await fsPromises.stat(filepath, { bigint: true }); |
| 46 | + closeEnough(statsBigint.atimeMs, BigInt(atime), margin); |
| 47 | + closeEnough(statsBigint.mtimeMs, BigInt(mtime), margin); |
| 48 | + closeEnough(statsBigint.atime.getTime(), new Date(atime).getTime(), margin); |
| 49 | + closeEnough(statsBigint.mtime.getTime(), new Date(mtime).getTime(), margin); |
| 50 | + |
| 51 | + const statsSync = fs.statSync(filepath); |
| 52 | + closeEnough(statsSync.atimeMs, atime, margin); |
| 53 | + closeEnough(statsSync.mtimeMs, mtime, margin); |
| 54 | + closeEnough(statsSync.atime.getTime(), new Date(atime).getTime(), margin); |
| 55 | + closeEnough(statsSync.mtime.getTime(), new Date(mtime).getTime(), margin); |
| 56 | + |
| 57 | + const statsSyncBigint = fs.statSync(filepath, { bigint: true }); |
| 58 | + closeEnough(statsSyncBigint.atimeMs, BigInt(atime), margin); |
| 59 | + closeEnough(statsSyncBigint.mtimeMs, BigInt(mtime), margin); |
| 60 | + closeEnough(statsSyncBigint.atime.getTime(), new Date(atime).getTime(), margin); |
| 61 | + closeEnough(statsSyncBigint.mtime.getTime(), new Date(mtime).getTime(), margin); |
| 62 | +} |
| 63 | + |
| 64 | +// Too high/low numbers produce too different results on different platforms |
| 65 | +{ |
| 66 | + // TODO(LiviaMedeiros): investigate outdated stat time on FreeBSD. |
| 67 | + // On Windows, filetime is stored and handled differently. Supporting dates |
| 68 | + // after Y2038 is preferred over supporting dates before 1970-01-01. |
| 69 | + if (!common.isFreeBSD && !common.isWindows) { |
| 70 | + await runTest(-40691, -355, 1); // Potential precision loss on 32bit |
| 71 | + await runTest(-355, -40691, 1); // Potential precision loss on 32bit |
| 72 | + await runTest(-1, -1); |
| 73 | + } |
| 74 | + await runTest(0, 0); |
| 75 | + await runTest(1, 1); |
| 76 | + await runTest(355, 40691, 1); // Precision loss on 32bit |
| 77 | + await runTest(40691, 355, 1); // Precision loss on 32bit |
| 78 | + await runTest(1713037251360, 1713037251360, 1); // Precision loss |
| 79 | +} |
0 commit comments