Beta 7 with some repo names changed.

This commit is contained in:
Luis Fernando Molina committed 2020-11-15 13:05:19 +01:00
1 parent 0f7b40cca7
commit 963c19493a
3408 files changed
+462217 -15516

No files matched your search

+1516
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -0,0 +1,14 @@
exports.from =
Buffer.from ||
function(value, encoding) {
if (encoding) {
return new Buffer(value, encoding);
}
return new Buffer(value);
};
exports.alloc =
Buffer.alloc ||
function(size) {
return new Buffer(size);
};
+50
View File
@@ -0,0 +1,50 @@
const realBinding = process.binding('fs');
let storedBinding;
/**
* Perform action, bypassing mock FS
* @example
* // This file exists on the real FS, not on the mocked FS
* const filePath = '/path/file.json';
* const data = mock.bypass(() => fs.readFileSync(filePath, 'utf-8'));
*/
exports = module.exports = function bypass(fn) {
if (typeof fn !== 'function') {
throw new Error(`Must provide a function to perform for mock.bypass()`);
}
disable();
let result;
try {
result = fn();
} finally {
if (result && typeof result.finally === 'function') {
result.finally(enable);
} else {
enable();
}
}
return result;
};
/**
* Temporarily disable Mocked FS
*/
function disable() {
if (realBinding._mockedBinding) {
storedBinding = realBinding._mockedBinding;
delete realBinding._mockedBinding;
}
}
/**
* Enables Mocked FS after being disabled by disable()
*/
function enable() {
if (storedBinding) {
realBinding._mockedBinding = storedBinding;
storedBinding = undefined;
}
}
+117
View File
@@ -0,0 +1,117 @@
'use strict';
const constants = require('constants');
/**
* Create a new file descriptor.
* @param {number} flags Flags.
* @constructor
*/
function FileDescriptor(flags) {
/**
* Flags.
* @type {number}
*/
this._flags = flags;
/**
* File system item.
* @type {Item}
*/
this._item = null;
/**
* Current file position.
* @type {number}
*/
this._position = 0;
}
/**
* Set the item.
* @param {Item} item File system item.
*/
FileDescriptor.prototype.setItem = function(item) {
this._item = item;
};
/**
* Get the item.
* @return {Item} File system item.
*/
FileDescriptor.prototype.getItem = function() {
return this._item;
};
/**
* Get the current file position.
* @return {number} File position.
*/
FileDescriptor.prototype.getPosition = function() {
return this._position;
};
/**
* Set the current file position.
* @param {number} position File position.
*/
FileDescriptor.prototype.setPosition = function(position) {
this._position = position;
};
/**
* Check if file opened for appending.
* @return {boolean} Opened for appending.
*/
FileDescriptor.prototype.isAppend = function() {
return (this._flags & constants.O_APPEND) === constants.O_APPEND;
};
/**
* Check if file opened for creation.
* @return {boolean} Opened for creation.
*/
FileDescriptor.prototype.isCreate = function() {
return (this._flags & constants.O_CREAT) === constants.O_CREAT;
};
/**
* Check if file opened for reading.
* @return {boolean} Opened for reading.
*/
FileDescriptor.prototype.isRead = function() {
return (this._flags & constants.O_WRONLY) !== constants.O_WRONLY;
};
/**
* Check if file opened for writing.
* @return {boolean} Opened for writing.
*/
FileDescriptor.prototype.isWrite = function() {
return (
(this._flags & constants.O_WRONLY) === constants.O_WRONLY ||
(this._flags & constants.O_RDWR) === constants.O_RDWR
);
};
/**
* Check if file opened for truncating.
* @return {boolean} Opened for truncating.
*/
FileDescriptor.prototype.isTruncate = function() {
return (this._flags & constants.O_TRUNC) === constants.O_TRUNC;
};
/**
* Check if file opened with exclusive flag.
* @return {boolean} Opened with exclusive.
*/
FileDescriptor.prototype.isExclusive = function() {
return (this._flags & constants.O_EXCL) === constants.O_EXCL;
};
/**
* Export the constructor.
* @type {function()}
*/
exports = module.exports = FileDescriptor;
+110
View File
@@ -0,0 +1,110 @@
'use strict';
const util = require('util');
const Item = require('./item');
const constants = require('constants');
/**
* A directory.
* @constructor
*/
function Directory() {
Item.call(this);
/**
* Items in this directory.
* @type {Object.<string, Item>}
*/
this._items = {};
/**
* Permissions.
*/
this._mode = 511; // 0777
}
util.inherits(Directory, Item);
/**
* Add an item to the directory.
* @param {string} name The name to give the item.
* @param {Item} item The item to add.
* @return {Item} The added item.
*/
Directory.prototype.addItem = function(name, item) {
if (this._items.hasOwnProperty(name)) {
throw new Error('Item with the same name already exists: ' + name);
}
this._items[name] = item;
++item.links;
if (item instanceof Directory) {
// for '.' entry
++item.links;
// for subdirectory
++this.links;
}
this.setMTime(new Date());
return item;
};
/**
* Get a named item.
* @param {string} name Item name.
* @return {Item} The named item (or null if none).
*/
Directory.prototype.getItem = function(name) {
let item = null;
if (this._items.hasOwnProperty(name)) {
item = this._items[name];
}
return item;
};
/**
* Remove an item.
* @param {string} name Name of item to remove.
* @return {Item} The orphan item.
*/
Directory.prototype.removeItem = function(name) {
if (!this._items.hasOwnProperty(name)) {
throw new Error('Item does not exist in directory: ' + name);
}
const item = this._items[name];
delete this._items[name];
--item.links;
if (item instanceof Directory) {
// for '.' entry
--item.links;
// for subdirectory
--this.links;
}
this.setMTime(new Date());
return item;
};
/**
* Get list of item names in this directory.
* @return {Array.<string>} Item names.
*/
Directory.prototype.list = function() {
return Object.keys(this._items).sort();
};
/**
* Get directory stats.
* @return {Object} Stats properties.
*/
Directory.prototype.getStats = function() {
const stats = Item.prototype.getStats.call(this);
stats.mode = this.getMode() | constants.S_IFDIR;
stats.size = 1;
stats.blocks = 1;
return stats;
};
/**
* Export the constructor.
* @type {function()}
*/
exports = module.exports = Directory;
+58
View File
@@ -0,0 +1,58 @@
'use strict';
const uvBinding = process.binding('uv');
/**
* Error codes from libuv.
* @enum {number}
*/
const codes = {};
if (uvBinding.errmap) {
// nodejs v8+
uvBinding.errmap.forEach(function(value, errno) {
const code = value[0];
const message = value[1];
codes[code] = {errno: errno, message: message};
});
} else {
// nodejs v4 and v6
Object.keys(uvBinding).forEach(function(key) {
if (key.startsWith('UV_')) {
const code = key.slice(3);
const errno = uvBinding[key];
codes[code] = {errno: errno, message: key};
}
});
}
/**
* Create an error.
* @param {string} code Error code.
* @param {string} path Path (optional).
* @constructor
*/
function FSError(code, path) {
if (!codes.hasOwnProperty(code)) {
throw new Error('Programmer error, invalid error code: ' + code);
}
Error.call(this);
const details = codes[code];
let message = code + ', ' + details.message;
if (path) {
message += " '" + path + "'";
}
this.message = message;
this.code = code;
this.errno = details.errno;
if (path !== undefined) {
this.path = path;
}
Error.captureStackTrace(this, FSError);
}
FSError.prototype = new Error();
FSError.codes = codes;
/**
* Error constructor.
*/
exports = module.exports = FSError;
+69
View File
@@ -0,0 +1,69 @@
'use strict';
const util = require('util');
const Item = require('./item');
const bufferFrom = require('./buffer').from;
const bufferAlloc = require('./buffer').alloc;
const EMPTY = bufferAlloc(0);
const constants = require('constants');
/**
* A directory.
* @constructor
*/
function File() {
Item.call(this);
/**
* File content.
* @type {Buffer}
*/
this._content = EMPTY;
}
util.inherits(File, Item);
/**
* Get the file contents.
* @return {Buffer} File contents.
*/
File.prototype.getContent = function() {
this.setATime(new Date());
return this._content;
};
/**
* Set the file contents.
* @param {string|Buffer} content File contents.
*/
File.prototype.setContent = function(content) {
if (typeof content === 'string') {
content = bufferFrom(content);
} else if (!Buffer.isBuffer(content)) {
throw new Error('File content must be a string or buffer');
}
this._content = content;
const now = Date.now();
this.setCTime(new Date(now));
this.setMTime(new Date(now));
};
/**
* Get file stats.
* @return {Object} Stats properties.
*/
File.prototype.getStats = function() {
const size = this._content.length;
const stats = Item.prototype.getStats.call(this);
stats.mode = this.getMode() | constants.S_IFREG;
stats.size = size;
stats.blocks = Math.ceil(size / 512);
return stats;
};
/**
* Export the constructor.
* @type {function()}
*/
exports = module.exports = File;
+332
View File
@@ -0,0 +1,332 @@
'use strict';
const os = require('os');
const path = require('path');
const Directory = require('./directory');
const File = require('./file');
const FSError = require('./error');
const SymbolicLink = require('./symlink');
const isWindows = process.platform === 'win32';
function toNamespacedPath(filePath) {
return path.toNamespacedPath
? path.toNamespacedPath(filePath)
: path._makeLong(filePath);
}
function getPathParts(filepath) {
const parts = toNamespacedPath(path.resolve(filepath)).split(path.sep);
parts.shift();
if (isWindows) {
// parts currently looks like ['', '?', 'c:', ...]
parts.shift();
const q = parts.shift(); // should be '?'
const base = '\\\\' + q + '\\' + parts.shift().toLowerCase();
parts.unshift(base);
}
if (parts[parts.length - 1] === '') {
parts.pop();
}
return parts;
}
/**
* Create a new file system.
* @param {Object} options Any filesystem options.
* @param {boolean} options.createCwd Create a directory for `process.cwd()`
* (defaults to `true`).
* @param {boolean} options.createTmp Create a directory for `os.tmpdir()`
* (defaults to `true`).
* @constructor
*/
function FileSystem(options) {
options = options || {};
const createCwd = 'createCwd' in options ? options.createCwd : true;
const createTmp = 'createTmp' in options ? options.createTmp : true;
const root = new Directory();
// populate with default directories
const defaults = [];
if (createCwd) {
defaults.push(process.cwd());
}
if (createTmp) {
defaults.push((os.tmpdir && os.tmpdir()) || os.tmpDir());
}
defaults.forEach(function(dir) {
const parts = getPathParts(dir);
let directory = root;
for (let i = 0, ii = parts.length; i < ii; ++i) {
const name = parts[i];
const candidate = directory.getItem(name);
if (!candidate) {
directory = directory.addItem(name, new Directory());
} else if (candidate instanceof Directory) {
directory = candidate;
} else {
throw new Error('Failed to create directory: ' + dir);
}
}
});
/**
* Root directory.
* @type {Directory}
*/
this._root = root;
}
/**
* Get the root directory.
* @return {Directory} The root directory.
*/
FileSystem.prototype.getRoot = function() {
return this._root;
};
/**
* Get a file system item.
* @param {string} filepath Path to item.
* @return {Item} The item (or null if not found).
*/
FileSystem.prototype.getItem = function(filepath) {
const parts = getPathParts(filepath);
const currentParts = getPathParts(process.cwd());
let item = this._root;
let itemPath = '/';
for (let i = 0, ii = parts.length; i < ii; ++i) {
const name = parts[i];
while (item instanceof SymbolicLink) {
// Symbolic link being traversed as a directory --- If link targets
// another symbolic link, resolve target's path relative to the original
// link's target, otherwise relative to the current item.
itemPath = path.resolve(path.dirname(itemPath), item.getPath());
item = this.getItem(itemPath);
}
if (item) {
if (item instanceof Directory && name !== currentParts[i]) {
// make sure traversal is allowed
// This fails for Windows directories which do not have execute permission, by default. It may be a good idea
// to change this logic to windows-friendly. See notes in mock.createDirectoryInfoFromPaths()
if (!item.canExecute()) {
throw new FSError('EACCES', filepath);
}
}
if (item instanceof File) {
throw new FSError('ENOTDIR', filepath);
}
item = item.getItem(name);
}
if (!item) {
break;
}
itemPath = path.resolve(itemPath, name);
}
return item;
};
/**
* Populate a directory with an item.
* @param {Directory} directory The directory to populate.
* @param {string} name The name of the item.
* @param {string|Buffer|function|Object} obj Instructions for creating the
* item.
*/
function populate(directory, name, obj) {
let item;
if (typeof obj === 'string' || Buffer.isBuffer(obj)) {
// contents for a file
item = new File();
item.setContent(obj);
} else if (typeof obj === 'function') {
// item factory
item = obj();
} else if (typeof obj === 'object') {
// directory with more to populate
item = new Directory();
for (const key in obj) {
populate(item, key, obj[key]);
}
} else {
throw new Error('Unsupported type: ' + typeof obj + ' of item ' + name);
}
/**
* Special exception for redundant adding of empty directories.
*/
if (
item instanceof Directory &&
item.list().length === 0 &&
directory.getItem(name) instanceof Directory
) {
// pass
} else {
directory.addItem(name, item);
}
}
/**
* Configure a mock file system.
* @param {Object} paths Config object.
* @param {Object} options Any filesystem options.
* @param {boolean} options.createCwd Create a directory for `process.cwd()`
* (defaults to `true`).
* @param {boolean} options.createTmp Create a directory for `os.tmpdir()`
* (defaults to `true`).
* @return {FileSystem} Mock file system.
*/
FileSystem.create = function(paths, options) {
const system = new FileSystem(options);
for (const filepath in paths) {
const parts = getPathParts(filepath);
let directory = system._root;
for (let i = 0, ii = parts.length - 1; i < ii; ++i) {
const name = parts[i];
const candidate = directory.getItem(name);
if (!candidate) {
directory = directory.addItem(name, new Directory());
} else if (candidate instanceof Directory) {
directory = candidate;
} else {
throw new Error('Failed to create directory: ' + filepath);
}
}
populate(directory, parts[parts.length - 1], paths[filepath]);
}
return system;
};
/**
* Generate a factory for new files.
* @param {Object} config File config.
* @return {function():File} Factory that creates a new file.
*/
FileSystem.file = function(config) {
config = config || {};
return function() {
const file = new File();
if (config.hasOwnProperty('content')) {
file.setContent(config.content);
}
if (config.hasOwnProperty('mode')) {
file.setMode(config.mode);
} else {
file.setMode(438); // 0666
}
if (config.hasOwnProperty('uid')) {
file.setUid(config.uid);
}
if (config.hasOwnProperty('gid')) {
file.setGid(config.gid);
}
if (config.hasOwnProperty('atime')) {
file.setATime(config.atime);
}
if (config.hasOwnProperty('ctime')) {
file.setCTime(config.ctime);
}
if (config.hasOwnProperty('mtime')) {
file.setMTime(config.mtime);
}
if (config.hasOwnProperty('birthtime')) {
file.setBirthtime(config.birthtime);
}
return file;
};
};
/**
* Generate a factory for new symbolic links.
* @param {Object} config File config.
* @return {function():File} Factory that creates a new symbolic link.
*/
FileSystem.symlink = function(config) {
config = config || {};
return function() {
const link = new SymbolicLink();
if (config.hasOwnProperty('mode')) {
link.setMode(config.mode);
} else {
link.setMode(438); // 0666
}
if (config.hasOwnProperty('uid')) {
link.setUid(config.uid);
}
if (config.hasOwnProperty('gid')) {
link.setGid(config.gid);
}
if (config.hasOwnProperty('path')) {
link.setPath(config.path);
} else {
throw new Error('Missing "path" property');
}
if (config.hasOwnProperty('atime')) {
link.setATime(config.atime);
}
if (config.hasOwnProperty('ctime')) {
link.setCTime(config.ctime);
}
if (config.hasOwnProperty('mtime')) {
link.setMTime(config.mtime);
}
if (config.hasOwnProperty('birthtime')) {
link.setBirthtime(config.birthtime);
}
return link;
};
};
/**
* Generate a factory for new directories.
* @param {Object} config File config.
* @return {function():Directory} Factory that creates a new directory.
*/
FileSystem.directory = function(config) {
config = config || {};
return function() {
const dir = new Directory();
if (config.hasOwnProperty('mode')) {
dir.setMode(config.mode);
}
if (config.hasOwnProperty('uid')) {
dir.setUid(config.uid);
}
if (config.hasOwnProperty('gid')) {
dir.setGid(config.gid);
}
if (config.hasOwnProperty('items')) {
for (const name in config.items) {
populate(dir, name, config.items[name]);
}
}
if (config.hasOwnProperty('atime')) {
dir.setATime(config.atime);
}
if (config.hasOwnProperty('ctime')) {
dir.setCTime(config.ctime);
}
if (config.hasOwnProperty('mtime')) {
dir.setMTime(config.mtime);
}
if (config.hasOwnProperty('birthtime')) {
dir.setBirthtime(config.birthtime);
}
return dir;
};
};
/**
* Module exports.
* @type {function}
*/
exports = module.exports = FileSystem;
exports.getPathParts = getPathParts;
exports.toNamespacedPath = toNamespacedPath;
+199
View File
@@ -0,0 +1,199 @@
'use strict';
const Binding = require('./binding');
const FSError = require('./error');
const FileSystem = require('./filesystem');
const realBinding = process.binding('fs');
const path = require('path');
const loader = require('./loader');
const bypass = require('./bypass');
const fs = require('fs');
const toNamespacedPath = FileSystem.toNamespacedPath;
const realProcessProps = {
cwd: process.cwd,
chdir: process.chdir
};
const realCreateWriteStream = fs.createWriteStream;
const realStats = realBinding.Stats;
const realStatWatcher = realBinding.StatWatcher;
/**
* Pre-patch fs binding.
* This allows mock-fs to work properly under nodejs v10+ readFile
* As ReadFileContext nodejs v10+ implementation traps original binding methods:
* const { FSReqWrap, close, read } = process.binding('fs');
* Note this patch only solves issue for readFile, as the require of
* ReadFileContext is delayed by readFile implementation.
* if (!ReadFileContext) ReadFileContext = require('internal/fs/read_file_context')
*
* @param {string} key Property name.
*/
function patch(key) {
const existingMethod = realBinding[key];
realBinding[key] = function() {
if (this._mockedBinding) {
return this._mockedBinding[key].apply(this._mockedBinding, arguments);
} else {
return existingMethod.apply(this, arguments);
}
}.bind(realBinding);
}
for (const key in Binding.prototype) {
if (typeof realBinding[key] === 'function') {
// Stats and StatWatcher are constructors
if (key !== 'Stats' && key !== 'StatWatcher') {
patch(key);
}
}
}
function overrideBinding(binding) {
realBinding._mockedBinding = binding;
}
function overrideProcess(cwd, chdir) {
process.cwd = cwd;
process.chdir = chdir;
}
/**
* Have to disable write stream _writev on nodejs v10+.
*
* nodejs v8 lib/fs.js
* note binding.writeBuffers will use mock-fs patched writeBuffers.
*
* const binding = process.binding('fs');
* function writev(fd, chunks, position, callback) {
* // ...
* binding.writeBuffers(fd, chunks, position, req);
* }
*
* nodejs v10+ lib/internal/fs/streams.js
* note it uses original writeBuffers, bypassed mock-fs patched writeBuffers.
*
* const {writeBuffers} = internalBinding('fs');
* function writev(fd, chunks, position, callback) {
* // ...
* writeBuffers(fd, chunks, position, req);
* }
*
* Luckily _writev is an optional method on Writeable stream implementation.
* When _writev is missing, it will fall back to make multiple _write calls.
*/
function overrideCreateWriteStream() {
fs.createWriteStream = function(path, options) {
const output = realCreateWriteStream(path, options);
// disable _writev, this will over shadow WriteStream.prototype._writev
if (realBinding._mockedBinding) {
output._writev = undefined;
}
return output;
};
}
function restoreBinding() {
delete realBinding._mockedBinding;
realBinding.Stats = realStats;
realBinding.StatWatcher = realStatWatcher;
}
function restoreProcess() {
for (const key in realProcessProps) {
process[key] = realProcessProps[key];
}
}
function restoreCreateWriteStream() {
fs.createWriteStream = realCreateWriteStream;
}
/**
* Swap out the fs bindings for a mock file system.
* @param {Object} config Mock file system configuration.
* @param {Object} options Any filesystem options.
* @param {boolean} options.createCwd Create a directory for `process.cwd()`
* (defaults to `true`).
* @param {boolean} options.createTmp Create a directory for `os.tmpdir()`
* (defaults to `true`).
*/
exports = module.exports = function mock(config, options) {
const system = FileSystem.create(config, options);
const binding = new Binding(system);
overrideBinding(binding);
let currentPath = process.cwd();
overrideProcess(
function cwd() {
if (realBinding._mockedBinding) {
return currentPath;
}
return realProcessProps.cwd();
},
function chdir(directory) {
if (realBinding._mockedBinding) {
if (!binding.stat(toNamespacedPath(directory)).isDirectory()) {
throw new FSError('ENOTDIR');
}
currentPath = path.resolve(currentPath, directory);
} else {
return realProcessProps.chdir(directory);
}
}
);
overrideCreateWriteStream();
};
/**
* Get hold of the mocked filesystem's 'root'
* If fs hasn't currently been replaced, this will return an empty object
*/
exports.getMockRoot = function() {
if (realBinding._mockedBinding) {
return realBinding._mockedBinding.getSystem().getRoot();
} else {
return {};
}
};
/**
* Restore the fs bindings for the real file system.
*/
exports.restore = function() {
restoreBinding();
restoreProcess();
restoreCreateWriteStream();
};
/**
* Create a file factory.
*/
exports.file = FileSystem.file;
/**
* Create a directory factory.
*/
exports.directory = FileSystem.directory;
/**
* Create a symbolic link factory.
*/
exports.symlink = FileSystem.symlink;
/**
* Automatically maps specified paths (for use with `mock()`)
*/
exports.load = loader.load;
/**
* Perform action, bypassing mock FS
* @example
* // This file exists on the real FS, not on the mocked FS
* const filePath = '/path/file.json';
* const data = mock.bypass(() => fs.readFileSync(filePath, 'utf-8'));
*/
exports.bypass = bypass;
+318
View File
@@ -0,0 +1,318 @@
'use strict';
let counter = 0;
/**
* Permissions.
* @enum {number}
*/
const permissions = {
USER_READ: 256, // 0400
USER_WRITE: 128, // 0200
USER_EXEC: 64, // 0100
GROUP_READ: 32, // 0040
GROUP_WRITE: 16, // 0020
GROUP_EXEC: 8, // 0010
OTHER_READ: 4, // 0004
OTHER_WRITE: 2, // 0002
OTHER_EXEC: 1 // 0001
};
function getUid() {
// force NaN on windows.
return process.getuid ? process.getuid() : NaN;
}
function getGid() {
// force NaN on windows.
return process.getgid ? process.getgid() : NaN;
}
/**
* A filesystem item.
* @constructor
*/
function Item() {
const now = Date.now();
/**
* Access time.
* @type {Date}
*/
this._atime = new Date(now);
/**
* Change time.
* @type {Date}
*/
this._ctime = new Date(now);
/**
* Birth time.
* @type {Date}
*/
this._birthtime = new Date(now);
/**
* Modification time.
* @type {Date}
*/
this._mtime = new Date(now);
/**
* Permissions.
*/
this._mode = 438; // 0666
/**
* User id.
* @type {number}
*/
this._uid = getUid();
/**
* Group id.
* @type {number}
*/
this._gid = getGid();
/**
* Item number.
* @type {number}
*/
this._id = ++counter;
/**
* Number of links to this item.
*/
this.links = 0;
}
/**
* Add execute if read allowed
* See notes in index.js -> mapping#addDir
*/
// prettier-ignore
Item.fixWin32Permissions = mode =>
(process.platform !== 'win32')
? mode
: mode |
((mode & permissions.USER_READ) && permissions.USER_EXEC) |
((mode & permissions.GROUP_READ) && permissions.GROUP_EXEC) |
((mode & permissions.OTHER_READ) && permissions.OTHER_EXEC);
/**
* Determine if the current user has read permission.
* @return {boolean} The current user can read.
*/
Item.prototype.canRead = function() {
const uid = getUid();
const gid = getGid();
let can = false;
if (uid === 0) {
can = true;
} else if (uid === this._uid || uid !== uid) {
// (uid !== uid) means uid is NaN, only for windows
can = (permissions.USER_READ & this._mode) === permissions.USER_READ;
} else if (gid === this._gid) {
can = (permissions.GROUP_READ & this._mode) === permissions.GROUP_READ;
} else {
can = (permissions.OTHER_READ & this._mode) === permissions.OTHER_READ;
}
return can;
};
/**
* Determine if the current user has write permission.
* @return {boolean} The current user can write.
*/
Item.prototype.canWrite = function() {
const uid = getUid();
const gid = getGid();
let can = false;
if (uid === 0) {
can = true;
} else if (uid === this._uid || uid !== uid) {
// (uid !== uid) means uid is NaN, only for windows
can = (permissions.USER_WRITE & this._mode) === permissions.USER_WRITE;
} else if (gid === this._gid) {
can = (permissions.GROUP_WRITE & this._mode) === permissions.GROUP_WRITE;
} else {
can = (permissions.OTHER_WRITE & this._mode) === permissions.OTHER_WRITE;
}
return can;
};
/**
* Determine if the current user has execute permission.
* @return {boolean} The current user can execute.
*/
Item.prototype.canExecute = function() {
const uid = getUid();
const gid = getGid();
let can = false;
if (uid === 0) {
can = true;
} else if (uid === this._uid || isNaN(uid)) {
// NaN occurs on windows
can = (permissions.USER_EXEC & this._mode) === permissions.USER_EXEC;
} else if (gid === this._gid) {
can = (permissions.GROUP_EXEC & this._mode) === permissions.GROUP_EXEC;
} else {
can = (permissions.OTHER_EXEC & this._mode) === permissions.OTHER_EXEC;
}
return can;
};
/**
* Get access time.
* @return {Date} Access time.
*/
Item.prototype.getATime = function() {
return this._atime;
};
/**
* Set access time.
* @param {Date} atime Access time.
*/
Item.prototype.setATime = function(atime) {
this._atime = atime;
};
/**
* Get change time.
* @return {Date} Change time.
*/
Item.prototype.getCTime = function() {
return this._ctime;
};
/**
* Set change time.
* @param {Date} ctime Change time.
*/
Item.prototype.setCTime = function(ctime) {
this._ctime = ctime;
};
/**
* Get birth time.
* @return {Date} Birth time.
*/
Item.prototype.getBirthtime = function() {
return this._birthtime;
};
/**
* Set change time.
* @param {Date} birthtime Birth time.
*/
Item.prototype.setBirthtime = function(birthtime) {
this._birthtime = birthtime;
};
/**
* Get modification time.
* @return {Date} Modification time.
*/
Item.prototype.getMTime = function() {
return this._mtime;
};
/**
* Set modification time.
* @param {Date} mtime Modification time.
*/
Item.prototype.setMTime = function(mtime) {
this._mtime = mtime;
};
/**
* Get mode (permission only, e.g 0666).
* @return {number} Mode.
*/
Item.prototype.getMode = function() {
return this._mode;
};
/**
* Set mode (permission only, e.g 0666).
* @param {Date} mode Mode.
*/
Item.prototype.setMode = function(mode) {
this.setCTime(new Date());
this._mode = mode;
};
/**
* Get user id.
* @return {number} User id.
*/
Item.prototype.getUid = function() {
return this._uid;
};
/**
* Set user id.
* @param {number} uid User id.
*/
Item.prototype.setUid = function(uid) {
this.setCTime(new Date());
this._uid = uid;
};
/**
* Get group id.
* @return {number} Group id.
*/
Item.prototype.getGid = function() {
return this._gid;
};
/**
* Set group id.
* @param {number} gid Group id.
*/
Item.prototype.setGid = function(gid) {
this.setCTime(new Date());
this._gid = gid;
};
/**
* Get item stats.
* @return {Object} Stats properties.
*/
Item.prototype.getStats = function() {
return {
dev: 8675309,
nlink: this.links,
uid: this.getUid(),
gid: this.getGid(),
rdev: 0,
blksize: 4096,
ino: this._id,
atime: this.getATime(),
mtime: this.getMTime(),
ctime: this.getCTime(),
birthtime: this.getBirthtime(),
atimeMs: +this.getATime(),
mtimeMs: +this.getMTime(),
ctimeMs: +this.getCTime(),
birthtimeMs: +this.getBirthtime()
};
};
/**
* Get the item's string representation.
* @return {string} String representation.
*/
Item.prototype.toString = function() {
return '[' + this.constructor.name + ']';
};
/**
* Export the constructor.
* @type {function()}
*/
exports = module.exports = Item;
+118
View File
@@ -0,0 +1,118 @@
const {fixWin32Permissions} = require('./item');
const path = require('path');
const FileSystem = require('./filesystem');
const fs = require('fs');
const bypass = require('./bypass');
const createContext = ({output, options = {}, target}, newContext) =>
Object.assign(
{
// Assign options and set defaults if needed
options: {
recursive: options.recursive !== false,
lazy: options.lazy !== false
},
output,
target
},
newContext
);
function addFile(context, stats, isRoot) {
const {output, target} = context;
const {lazy} = context.options;
if (!stats.isFile()) {
throw new Error(`${target} is not a valid file!`);
}
const outputPropKey = isRoot ? target : path.basename(target);
output[outputPropKey] = () => {
const content = !lazy ? fs.readFileSync(target) : '';
const file = FileSystem.file(Object.assign({}, stats, {content}))();
if (lazy) {
Object.defineProperty(file, '_content', {
get() {
const res = bypass(() => fs.readFileSync(target));
Object.defineProperty(file, '_content', {
value: res,
writable: true
});
return res;
},
set(data) {
Object.defineProperty(file, '_content', {
value: data,
writable: true
});
},
configurable: true
});
}
return file;
};
return output[outputPropKey];
}
function addDir(context, stats, isRoot) {
const {target, output} = context;
const {recursive} = context.options;
if (!stats.isDirectory()) {
throw new Error(`${target} is not a valid directory!`);
}
stats = Object.assign({}, stats);
const outputPropKey = isRoot ? target : path.basename(target);
// On windows platforms, directories do not have the executable flag, which causes FileSystem.prototype.getItem
// to think that the directory cannot be traversed. This is a workaround, however, a better solution may be to
// re-think the logic in FileSystem.prototype.getItem
// This workaround adds executable privileges if read privileges are found
stats.mode = fixWin32Permissions(stats.mode);
// Create directory factory
const directoryItems = {};
output[outputPropKey] = FileSystem.directory(
Object.assign(stats, {items: directoryItems})
);
fs.readdirSync(target).forEach(p => {
const absPath = path.join(target, p);
const stats = fs.statSync(absPath);
const newContext = createContext(context, {
target: absPath,
output: directoryItems
});
if (recursive && stats.isDirectory()) {
addDir(newContext, stats);
} else if (stats.isFile()) {
addFile(newContext, stats);
}
});
return output[outputPropKey];
}
/**
* Load directory or file from real FS
*/
exports.load = function(p, options) {
return bypass(() => {
p = path.resolve(p);
const stats = fs.statSync(p);
const context = createContext({output: {}, options, target: p});
if (stats.isDirectory()) {
return addDir(context, stats, true);
} else if (stats.isFile()) {
return addFile(context, stats, true);
}
});
};
+57
View File
@@ -0,0 +1,57 @@
'use strict';
const util = require('util');
const Item = require('./item');
const constants = require('constants');
/**
* A directory.
* @constructor
*/
function SymbolicLink() {
Item.call(this);
/**
* Relative path to source.
* @type {string}
*/
this._path = undefined;
}
util.inherits(SymbolicLink, Item);
/**
* Set the path to the source.
* @param {string} pathname Path to source.
*/
SymbolicLink.prototype.setPath = function(pathname) {
this._path = pathname;
};
/**
* Get the path to the source.
* @return {string} Path to source.
*/
SymbolicLink.prototype.getPath = function() {
return this._path;
};
/**
* Get symbolic link stats.
* @return {Object} Stats properties.
*/
SymbolicLink.prototype.getStats = function() {
const size = this._path.length;
const stats = Item.prototype.getStats.call(this);
stats.mode = this.getMode() | constants.S_IFLNK;
stats.size = size;
stats.blocks = Math.ceil(size / 512);
return stats;
};
/**
* Export the constructor.
* @type {function()}
*/
exports = module.exports = SymbolicLink;
+50
View File
@@ -0,0 +1,50 @@
{
"name": "mock-fs",
"description": "A configurable mock file system. You know, for testing.",
"version": "4.13.0",
"main": "lib/index.js",
"homepage": "https://github.com/tschaub/mock-fs",
"author": {
"name": "Tim Schaub",
"url": "http://tschaub.net/"
},
"keywords": [
"mock",
"fs",
"test",
"fixtures",
"file system",
"memory"
],
"repository": {
"type": "git",
"url": "git://github.com/tschaub/mock-fs.git"
},
"bugs": {
"url": "https://github.com/tschaub/mock-fs/issues"
},
"license": "MIT",
"files": [
"lib"
],
"scripts": {
"lint": "eslint benchmarks lib test",
"pretest": "npm run lint",
"test": "mocha --recursive test"
},
"eslintConfig": {
"extends": "tschaub"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^5.16.0",
"eslint-config-tschaub": "^13.1.0",
"mocha": "^6.1.4",
"rimraf": "^2.6.3",
"semver": "^6.0.0"
}
,"_resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.13.0.tgz"
,"_integrity": "sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA=="
,"_from": "mock-fs@^4.1.0"
}