walkitout
Version:
Run a callback, asynchronously, on all filepaths in a directory tree
182 lines (159 loc) • 4.7 kB
JavaScript
var path = require('path');
/*
A tree data structure for use with the fs mock.
Used so fs mock can send errors to walkitout
on specified paths.
Sets up the following structure:
[-] .
[-] dir1/
[-] dir1/dirA/
[-] dir1/dirA/dirAA/
[x] dir1/dirA/dirAA/fileAA.txt
[x] dir1/dirA/dirAA/fileAB.txt
[x] dir1/fileA.txt
[-] dir2/dirB/
[x] dir2/fileZ.txt
[-] otherdir1 (should be readdir error)
[-] otherdir1/otherfile2.txt (should be unreachable)
[x] file1.txt
[-] otherfile1.txt (should be stat error)
'x' indicates a filepath that should be
sent to the callback handler and without error
Format:
{
dirname: [[stat Error | null,
readdir Error | null], ...filenames],
filename: stat Error | null,
...
}
*/
var defaultTree = {
'.': [[null, null], 'dir1', 'dir2', 'otherdir1', 'file1.txt', 'otherfile1.txt'],
'dir1': [[null, null], 'fileA.txt', 'dirA'],
'dir1/dirA': [[null, null], 'dirAA'],
'dir1/dirA/dirAA': [[null, null], 'fileAA.txt', 'fileAB.txt'],
'dir1/dirA/dirAA/fileAA.txt': null,
'dir1/dirA/dirAA/fileAB.txt': null,
'dir1/fileA.txt': null,
'dir2': [[null, null], 'fileZ.txt', 'dirB'],
'dir2/dirB': [[null, null]],
'dir2/fileZ.txt': null,
'otherdir1': [[null, new Error('Error with otherdir1')]],
'otherdir1/otherfile2.txt': null,
'file1.txt': null,
'otherfile1.txt': new Error('Error with otherfile')
};
// For use in vm context.
// Mock the bare minimum of the fs readir() and stat()
// methods used by walkitout to use the mockTree data
// structure.
function fsMock(tree) {
return {
'_tree': tree,
'readdir': function (path, callback) {
var data = this._tree[path];
callback(data[0][1], data.slice(1));
},
'stat': function (path, callback) {
var data = this._tree[path];
if (Array.isArray(data)) callback(data[0][0], this._stat(path, true));
else callback(data, this._stat(path, false));
},
'_stat': function (path, directory) {
return {
isDirectory: function () { return directory; },
isFile: function () { return !directory; }
};
}
};
}
// For use in vm context.
// Require proxy to give fs mock
// to vm script
var requireMock = function (name) {
if (name === 'fs') return global.fs;
return require(name);
};
// A filename for vm traces
var filename = 'index.js';
exports.getExpectedPaths = getExpectedPaths;
exports.enable = function (tree) {
tree = normalizeTree(tree || defaultTree);
global.fs = fsMock(tree);
global.__realRequire = global.require;
global.__realModule = global.module;
global.__realFs = global.fs;
global.require = requireMock;
global.module = {};
return tree;
};
exports.disable = function () {
global.require = global.__realRequire;
global.module = global.__realModule;
global.fs = global.__realFs;
};
function getError(cfg) {
var e = new Error(cfg.message);
Object.keys(cfg).forEach(function (key) {
e[key] = e[key] || cfg[key];
});
return e;
}
function getExpectedPaths(tree) {
return Object.keys(tree)
.filter(filterForExpectedPaths.bind(null, tree));
}
function getNoEntityError(filename) {
return getError({
message: "ENOENT, stat '" + filename + "'",
errno: 34,
code: 'ENOENT',
path: filename
});
}
// Filters for expected paths, that should
// match the results of what the walkitout
// callback will put into actualResults
// (e.g. the callback skips errors)
function filterForExpectedPaths(tree, filename)
{
var data = tree[filename] !== undefined ?
tree[filename] : getNoEntityError(filename);
var isFile = !Array.isArray(data);
var fileOk;
var pathCheck;
var pathData;
var pathOk;
var pathRegex = new RegExp('\\' + path.sep
+ '[^\\' + path.sep + ']+$');
if (isFile) fileOk = (data === null);
else fileOk = data[0][0] === null &&
data[0][1] === null;
if (!fileOk) return false;
pathCheck = filename;
do
{
pathCheck = pathCheck.replace(pathRegex, '');
pathData = tree[pathCheck];
pathOk = (!Array.isArray(pathData) ?
pathData : (pathData[0][0]) === null &&
pathData[0][1]) === null;
}
while (pathOk && pathCheck.indexOf('/') !== -1)
return pathOk && isFile;
}
function normalizeTree(tree) {
var normalTree = {};
var normalKey;
Object.keys(tree).forEach(function (key) {
normalKey = key;
if (path.sep === '/' && key.indexOf('\\') !== -1) {
normalKey = normalKey.split('\\').join('/');
}
else if (path.sep === '\\' && key.indexOf('/') !== -1) {
normalKey = normalKey.split('/').join('\\');
}
normalTree[normalKey] = tree[key];
});
return normalTree;
}