Skip to content

Commit

Permalink
Merge pull request #387 from everett1992/node20
Browse files Browse the repository at this point in the history
Fix tests on node 20
  • Loading branch information
tschaub authored Sep 26, 2024
2 parents 6225ba8 + 078360c commit 3e39479
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
node:
- 16
- 18
- 20
- 22

steps:
- name: Clone repository
Expand Down
175 changes: 172 additions & 3 deletions lib/binding.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function maybeCallback(callback, ctx, thisArg, func) {
let err = null;
let val;

if (kUsePromises && callback === kUsePromises) {
if (usePromises(callback)) {
// support nodejs v10+ fs.promises
try {
val = func.call(thisArg);
Expand Down Expand Up @@ -86,6 +86,10 @@ function maybeCallback(callback, ctx, thisArg, func) {
}
}

function usePromises(callback) {
return kUsePromises && callback === kUsePromises;
}

/**
* set syscall property on context object, only for nodejs v10+.
* @param {object} ctx Context object (optional), only for nodejs v10+.
Expand Down Expand Up @@ -306,6 +310,17 @@ Binding.prototype.stat = function (filepath, bigint, callback, ctx) {
});
};

/**
* Stat an item.
* @param {string} filepath Path.
* @param {boolean} bigint Use BigInt.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {Float64Array|BigUint64Array|undefined} Stats or undefined if sync.
*/
Binding.prototype.statSync = function (filepath, bigint, ctx) {
return this.stat(filepath, bigint, undefined, ctx);
};

/**
* Stat an item.
* @param {number} fd File descriptor.
Expand Down Expand Up @@ -341,6 +356,16 @@ Binding.prototype.close = function (fd, callback, ctx) {
});
};

/**
* Close a file descriptor.
* @param {number} fd File descriptor.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return.
*/
Binding.prototype.closeSync = function (fd, ctx) {
return this.close(fd, undefined, ctx);
};

/**
* Open and possibly create a file.
* @param {string} pathname File path.
Expand All @@ -355,7 +380,7 @@ Binding.prototype.open = function (pathname, flags, mode, callback, ctx) {

return maybeCallback(normalizeCallback(callback), ctx, this, function () {
pathname = deBuffer(pathname);
const descriptor = new FileDescriptor(flags);
const descriptor = new FileDescriptor(flags, usePromises(callback));
let item = this._system.getItem(pathname);
while (item instanceof SymbolicLink) {
item = this._system.getItem(
Expand Down Expand Up @@ -410,6 +435,18 @@ Binding.prototype.open = function (pathname, flags, mode, callback, ctx) {
});
};

/**
* Open and possibly create a file.
* @param {string} pathname File path.
* @param {number} flags Flags.
* @param {number} mode Mode.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {string} File descriptor.
*/
Binding.prototype.openSync = function (pathname, flags, mode, ctx) {
return this.open(pathname, flags, mode, undefined, ctx);
};

/**
* Open a file handler. A new api in nodejs v10+ for fs.promises
* @param {string} pathname File path.
Expand Down Expand Up @@ -531,6 +568,18 @@ Binding.prototype.copyFile = function (src, dest, flags, callback, ctx) {
});
};

/**
* Write to a file descriptor given a buffer.
* @param {string} src Source file.
* @param {string} dest Destination file.
* @param {number} flags Modifiers for copy operation.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.copyFileSync = function (src, dest, flags, ctx) {
return this.copyFile(src, dest, flags, undefined, ctx);
};

/**
* Write to a file descriptor given a buffer.
* @param {string} fd File descriptor.
Expand Down Expand Up @@ -632,7 +681,12 @@ Binding.prototype.writeBuffer = function (
);
file.setContent(content);
descriptor.setPosition(newLength);
return written;
// If we're in fs.promises / FileHandle we need to return a promise
// Both fs.promises.open().then(fd => fs.write())
// and fs.openSync().writeSync() use this function
// without a callback, so we have to check if the descriptor was opened
// with kUsePromises
return descriptor.isPromise() ? Promise.resolve(written) : written;
});
};

Expand Down Expand Up @@ -722,6 +776,17 @@ Binding.prototype.rename = function (oldPath, newPath, callback, ctx) {
});
};

/**
* Rename a file.
* @param {string} oldPath Old pathname.
* @param {string} newPath New pathname.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {undefined}
*/
Binding.prototype.renameSync = function (oldPath, newPath, ctx) {
return this.rename(oldPath, newPath, undefined, ctx);
};

/**
* Read a directory.
* @param {string} dirpath Path to directory.
Expand Down Expand Up @@ -779,6 +844,43 @@ Binding.prototype.readdir = function (
});
};

/**
* Read file as utf8 string.
* @param {string} name file to write.
* @param {number} flags Flags.
* @return {string} the file content.
*/
Binding.prototype.readFileUtf8 = function (name, flags) {
const fd = this.open(name, flags);
const descriptor = this.getDescriptorById(fd);

if (!descriptor.isRead()) {
throw new FSError('EBADF');
}
const file = descriptor.getItem();
if (file instanceof Directory) {
throw new FSError('EISDIR');
}
if (!(file instanceof File)) {
// deleted or not a regular file
throw new FSError('EBADF');
}
const content = file.getContent();
return content.toString('utf8');
};

/**
* Write a utf8 string.
* @param {string} filepath file to write.
* @param {string} data data to write to filepath.
* @param {number} flags Flags.
* @param {number} mode Mode.
*/
Binding.prototype.writeFileUtf8 = function (filepath, data, flags, mode) {
const destFd = this.open(filepath, flags, mode);
this.writeBuffer(destFd, data, 0, data.length);
};

/**
* Create a directory.
* @param {string} pathname Path to new directory.
Expand Down Expand Up @@ -1059,6 +1161,16 @@ Binding.prototype.unlink = function (pathname, callback, ctx) {
});
};

/**
* Delete a named item.
* @param {string} pathname Path to item.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.unlinkSync = function (pathname, ctx) {
return this.unlink(pathname, undefined, ctx);
};

/**
* Update timestamps.
* @param {string} pathname Path to item.
Expand Down Expand Up @@ -1241,6 +1353,18 @@ Binding.prototype.symlink = function (srcPath, destPath, type, callback, ctx) {
});
};

/**
* Create a symbolic link.
* @param {string} srcPath Path from link to the source file.
* @param {string} destPath Path for the generated link.
* @param {string} type Ignored (used for Windows only).
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.symlinkSync = function (srcPath, destPath, type, ctx) {
return this.symlink(srcPath, destPath, type, undefined, ctx);
};

/**
* Read the contents of a symbolic link.
* @param {string} pathname Path to symbolic link.
Expand Down Expand Up @@ -1338,6 +1462,51 @@ Binding.prototype.access = function (filepath, mode, callback, ctx) {
});
};

/**
* Tests user permissions.
* @param {string} filepath Path.
* @param {number} mode Mode.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.accessSync = function (filepath, mode, ctx) {
return this.access(filepath, mode, undefined, ctx);
};

/**
* Tests whether or not the given path exists.
* @param {string} filepath Path.
* @param {function(Error)} callback Callback (optional).
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.exists = function (filepath, callback, ctx) {
markSyscall(ctx, 'exists');

return maybeCallback(normalizeCallback(callback), ctx, this, function () {
filepath = deBuffer(filepath);
const item = this._system.getItem(filepath);

if (item) {
if (item instanceof SymbolicLink) {
return this.exists(item.getPath(), callback, ctx);
}
return true;
}
return false;
});
};

/**
* Tests whether or not the given path exists.
* @param {string} filepath Path.
* @param {object} ctx Context object (optional), only for nodejs v10+.
* @return {*} The return if no callback is provided.
*/
Binding.prototype.existsSync = function (filepath, ctx) {
return this.exists(filepath, undefined, ctx);
};

/**
* Not yet implemented.
* @type {function()}
Expand Down
13 changes: 12 additions & 1 deletion lib/descriptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ const constants = require('constants');
/**
* Create a new file descriptor.
* @param {number} flags Flags.
* @param {boolean} isPromise descriptor was opened via fs.promises
* @class
*/
function FileDescriptor(flags) {
function FileDescriptor(flags, isPromise = false) {
/**
* Flags.
* @type {number}
Expand All @@ -25,6 +26,8 @@ function FileDescriptor(flags) {
* @type {number}
*/
this._position = 0;

this._isPromise = isPromise;
}

/**
Expand Down Expand Up @@ -110,6 +113,14 @@ FileDescriptor.prototype.isExclusive = function () {
return (this._flags & constants.O_EXCL) === constants.O_EXCL;
};

/**
* Check if the file descriptor was opened as a promise
* @return {boolean} Opened from fs.promise
*/
FileDescriptor.prototype.isPromise = function () {
return this._isPromise;
};

/**
* Export the constructor.
* @type {function()}
Expand Down
24 changes: 24 additions & 0 deletions lib/filesystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,23 @@ FileSystem.file = function (config) {
}
if (config.hasOwnProperty('atime')) {
file.setATime(config.atime);
} else if (config.hasOwnProperty('atimeMs')) {
file.setATime(new Date(config.atimeMs));
}
if (config.hasOwnProperty('ctime')) {
file.setCTime(config.ctime);
} else if (config.hasOwnProperty('ctimeMs')) {
file.setCTime(new Date(config.ctimeMs));
}
if (config.hasOwnProperty('mtime')) {
file.setMTime(config.mtime);
} else if (config.hasOwnProperty('mtimeMs')) {
file.setMTime(new Date(config.mtimeMs));
}
if (config.hasOwnProperty('birthtime')) {
file.setBirthtime(config.birthtime);
} else if (config.hasOwnProperty('birthtimeMs')) {
file.setBirthtime(new Date(config.birthtimeMs));
}
return file;
};
Expand Down Expand Up @@ -305,15 +313,23 @@ FileSystem.symlink = function (config) {
}
if (config.hasOwnProperty('atime')) {
link.setATime(config.atime);
} else if (config.hasOwnProperty('atimeMs')) {
link.setATime(new Date(config.atimeMs));
}
if (config.hasOwnProperty('ctime')) {
link.setCTime(config.ctime);
} else if (config.hasOwnProperty('ctimeMs')) {
link.setCTime(new Date(config.ctimeMs));
}
if (config.hasOwnProperty('mtime')) {
link.setMTime(config.mtime);
} else if (config.hasOwnProperty('mtimeMs')) {
link.setMTime(new Date(config.mtimeMs));
}
if (config.hasOwnProperty('birthtime')) {
link.setBirthtime(config.birthtime);
} else if (config.hasOwnProperty('birthtimeMs')) {
link.setBirthtime(new Date(config.birthtimeMs));
}
return link;
};
Expand Down Expand Up @@ -344,15 +360,23 @@ FileSystem.directory = function (config) {
}
if (config.hasOwnProperty('atime')) {
dir.setATime(config.atime);
} else if (config.hasOwnProperty('atimeMs')) {
dir.setATime(new Date(config.atimeMs));
}
if (config.hasOwnProperty('ctime')) {
dir.setCTime(config.ctime);
} else if (config.hasOwnProperty('ctimeMs')) {
dir.setCTime(new Date(config.ctimeMs));
}
if (config.hasOwnProperty('mtime')) {
dir.setMTime(config.mtime);
} else if (config.hasOwnProperty('mtimeMs')) {
dir.setMTime(new Date(config.mtimeMs));
}
if (config.hasOwnProperty('birthtime')) {
dir.setBirthtime(config.birthtime);
} else if (config.hasOwnProperty('birthtimeMs')) {
dir.setBirthtime(new Date(config.birthtimeMs));
}
return dir;
};
Expand Down
2 changes: 1 addition & 1 deletion test/lib/bypass.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('mock.bypass()', () => {
it('runs a synchronous function using the real filesystem', () => {
mock({'/path/to/file': 'content'});

assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content');
assert.equal(fs.readFileSync('/path/to/file', 'utf-8'), 'content');
assert.isNotOk(fs.existsSync(__filename));
assert.isOk(mock.bypass(() => fs.existsSync(__filename)));

Expand Down
Loading

0 comments on commit 3e39479

Please sign in to comment.