1import * as common from '../common/index.mjs'; 2 3// Test timestamps returned by fsPromises.stat and fs.statSync 4 5import fs from 'node:fs'; 6import fsPromises from 'node:fs/promises'; 7import path from 'node:path'; 8import assert from 'node:assert'; 9import 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. 13const ignoredErrors = new Set(['EINVAL', 'EOVERFLOW']); 14 15tmpdir.refresh(); 16const filepath = path.resolve(tmpdir.path, 'timestamp'); 17 18await (await fsPromises.open(filepath, 'w')).close(); 19 20// Perform a trivial check to determine if filesystem supports setting 21// and retrieving atime and mtime. If it doesn't, skip the test. 22await fsPromises.utimes(filepath, 2, 2); 23const { atimeMs, mtimeMs } = await fsPromises.stat(filepath); 24if (atimeMs !== 2000 || mtimeMs !== 2000) { 25 common.skip(`Unsupported filesystem (atime=${atimeMs}, mtime=${mtimeMs})`); 26} 27 28// Date might round down timestamp 29function closeEnough(actual, expected, margin) { 30 // On ppc64, value is rounded to seconds 31 if (process.arch === 'ppc64') { 32 margin += 1000; 33 } 34 35 // Filesystems without support for timestamps before 1970-01-01, such as NFSv3, 36 // should return 0 for negative numbers. Do not treat it as error. 37 if (actual === 0 && expected < 0) { 38 console.log(`ignored 0 while expecting ${expected}`); 39 return; 40 } 41 42 assert.ok(Math.abs(Number(actual - expected)) < margin, 43 `expected ${expected} ± ${margin}, got ${actual}`); 44} 45 46async function runTest(atime, mtime, margin = 0) { 47 margin += Number.EPSILON; 48 try { 49 await fsPromises.utimes(filepath, new Date(atime), new Date(mtime)); 50 } catch (e) { 51 if (ignoredErrors.has(e.code)) return; 52 throw e; 53 } 54 55 const stats = await fsPromises.stat(filepath); 56 closeEnough(stats.atimeMs, atime, margin); 57 closeEnough(stats.mtimeMs, mtime, margin); 58 closeEnough(stats.atime.getTime(), new Date(atime).getTime(), margin); 59 closeEnough(stats.mtime.getTime(), new Date(mtime).getTime(), margin); 60 61 const statsBigint = await fsPromises.stat(filepath, { bigint: true }); 62 closeEnough(statsBigint.atimeMs, BigInt(atime), margin); 63 closeEnough(statsBigint.mtimeMs, BigInt(mtime), margin); 64 closeEnough(statsBigint.atime.getTime(), new Date(atime).getTime(), margin); 65 closeEnough(statsBigint.mtime.getTime(), new Date(mtime).getTime(), margin); 66 67 const statsSync = fs.statSync(filepath); 68 closeEnough(statsSync.atimeMs, atime, margin); 69 closeEnough(statsSync.mtimeMs, mtime, margin); 70 closeEnough(statsSync.atime.getTime(), new Date(atime).getTime(), margin); 71 closeEnough(statsSync.mtime.getTime(), new Date(mtime).getTime(), margin); 72 73 const statsSyncBigint = fs.statSync(filepath, { bigint: true }); 74 closeEnough(statsSyncBigint.atimeMs, BigInt(atime), margin); 75 closeEnough(statsSyncBigint.mtimeMs, BigInt(mtime), margin); 76 closeEnough(statsSyncBigint.atime.getTime(), new Date(atime).getTime(), margin); 77 closeEnough(statsSyncBigint.mtime.getTime(), new Date(mtime).getTime(), margin); 78} 79 80// Too high/low numbers produce too different results on different platforms 81{ 82 // TODO(LiviaMedeiros): investigate outdated stat time on FreeBSD. 83 // On Windows, filetime is stored and handled differently. Supporting dates 84 // after Y2038 is preferred over supporting dates before 1970-01-01. 85 if (!common.isFreeBSD && !common.isWindows) { 86 await runTest(-40691, -355, 1); // Potential precision loss on 32bit 87 await runTest(-355, -40691, 1); // Potential precision loss on 32bit 88 await runTest(-1, -1); 89 } 90 await runTest(0, 0); 91 await runTest(1, 1); 92 await runTest(355, 40691, 1); // Precision loss on 32bit 93 await runTest(40691, 355, 1); // Precision loss on 32bit 94 await runTest(1713037251360, 1713037251360, 1); // Precision loss 95} 96