diff --git a/README.md b/README.md
index c579b5b85..849f28ce4 100644
--- a/README.md
+++ b/README.md
@@ -353,7 +353,7 @@ Writing a Test Reporter
---------------------
Nodeunit exports runTest(fn, options), runModule(mod, options) and
-runFiles(paths, options). You'll most likely want to run test suites from
+runFiles(paths, recursive, options). You'll most likely want to run test suites from
files, which can be done using the latter function. The _options_ argument can
contain callbacks which run during testing. Nodeunit provides the following
callbacks:
diff --git a/bin/nodeunit b/bin/nodeunit
index b11cfb1a3..bde63ec57 100755
--- a/bin/nodeunit
+++ b/bin/nodeunit
@@ -29,6 +29,7 @@ var usage = "Usage: nodeunit [options] testmodule1.js testfolder [...] \n" +
" --reporter FILE optional path to a reporter file to customize the output\n" +
" --list-reporters list available build-in reporters\n" +
" -t name, specify a test to run\n" +
+ " -r, --recursive run tests from directories recursively" +
" -h, --help display this help and exit\n" +
" -v, --version output version information and exit";
@@ -80,6 +81,8 @@ args.forEach(function (arg) {
console.log(' * ' + reporter_file + (reporter.info ? ': ' + reporter.info : ''));
});
process.exit(0);
+ } else if ((arg === '-r') || (arg === '--recursive')) {
+ options.recursive = true;
} else if ((arg === '-v') || (arg === '--version')) {
var content = fs.readFileSync(__dirname + '/../package.json', 'utf8');
var pkg = JSON.parse(content);
diff --git a/bin/nodeunit.json b/bin/nodeunit.json
index 5c7778fd9..df3290891 100644
--- a/bin/nodeunit.json
+++ b/bin/nodeunit.json
@@ -6,5 +6,6 @@
"bold_prefix": "\u001B[1m",
"bold_suffix": "\u001B[22m",
"assertion_prefix": "\u001B[35m",
- "assertion_suffix": "\u001B[39m"
+ "assertion_suffix": "\u001B[39m",
+ "recursive": false
}
diff --git a/lib/core.js b/lib/core.js
index 028745e40..0a6ca49e0 100644
--- a/lib/core.js
+++ b/lib/core.js
@@ -175,7 +175,6 @@ exports.runModule = function (name, mod, opt, callback) {
// TODO: add proper unit tests for this function
exports.runModules = function (modules, opt) {
- var all_assertions = [];
var options = types.options(opt);
var start = new Date().getTime();
diff --git a/lib/nodeunit.js b/lib/nodeunit.js
index e20e97402..0d417ae50 100644
--- a/lib/nodeunit.js
+++ b/lib/nodeunit.js
@@ -45,7 +45,7 @@ exports.testrunner = {
for (var k in core) {
exports[k] = core[k];
-};
+}
/**
@@ -55,11 +55,12 @@ for (var k in core) {
* sub-directories.
*
* @param {Array} paths
+ * @param {Boolean} recursive
* @param {Object} opt
* @api public
*/
-exports.runFiles = function (paths, opt) {
+exports.runFiles = function (paths, recursive, opt) {
var all_assertions = [];
var options = types.options(opt);
var start = new Date().getTime();
@@ -68,7 +69,7 @@ exports.runFiles = function (paths, opt) {
return options.done(types.assertionList(all_assertions));
}
- utils.modulePaths(paths, function (err, files) {
+ utils.modulePaths(paths, recursive, function (err, files) {
if (err) throw err;
async.concatSeries(files, function (file, cb) {
var name = path.basename(file);
@@ -76,11 +77,10 @@ exports.runFiles = function (paths, opt) {
},
function (err, all_assertions) {
var end = new Date().getTime();
- exports.done()
+ exports.done();
options.done(types.assertionList(all_assertions, end - start));
});
});
-
};
/* Export all prototypes from events.EventEmitter */
diff --git a/lib/reporters/default.js b/lib/reporters/default.js
index a72a42e5f..e28caf008 100644
--- a/lib/reporters/default.js
+++ b/lib/reporters/default.js
@@ -69,7 +69,7 @@ exports.run = function (files, options, callback) {
}
});
- var opts = {
+ var opts = {
testspec: options.testspec,
moduleStart: function (name) {
console.log('\n' + bold(name));
@@ -119,12 +119,13 @@ exports.run = function (files, options, callback) {
tracker.put(name);
}
};
- if (files && files.length) {
- var paths = files.map(function (p) {
- return path.join(process.cwd(), p);
- });
- nodeunit.runFiles(paths, opts);
- } else {
- nodeunit.runModules(files,opts);
- }
+
+ if (files && files.length) {
+ var paths = files.map(function (p) {
+ return path.join(process.cwd(), p);
+ });
+ nodeunit.runFiles(paths, options.recursive, opts);
+ } else {
+ nodeunit.runModules(files, opts);
+ }
};
diff --git a/lib/reporters/eclipse.js b/lib/reporters/eclipse.js
index 6775ff126..324ec1783 100644
--- a/lib/reporters/eclipse.js
+++ b/lib/reporters/eclipse.js
@@ -52,7 +52,7 @@ exports.run = function (files, options, callback) {
}
});
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: undefined,
moduleStart: function (name) {
console.log('\n' + name);
diff --git a/lib/reporters/html.js b/lib/reporters/html.js
index 2790b580d..2efca0b8d 100644
--- a/lib/reporters/html.js
+++ b/lib/reporters/html.js
@@ -53,7 +53,7 @@ exports.run = function (files, options, callback) {
console.log('');
console.log('');
console.log('
');
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: options.testspec,
moduleStart: function (name) {
console.log('' + name + '
');
diff --git a/lib/reporters/junit.js b/lib/reporters/junit.js
index b49d5e626..90c26b0bc 100644
--- a/lib/reporters/junit.js
+++ b/lib/reporters/junit.js
@@ -97,10 +97,10 @@ exports.run = function (files, opts, callback) {
return path.join(process.cwd(), p);
});
- var modules = {}
+ var modules = {};
var curModule;
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: opts.testspec,
moduleStart: function (name) {
curModule = {
diff --git a/lib/reporters/machineout.js b/lib/reporters/machineout.js
index 4916d4cd2..d141368d5 100644
--- a/lib/reporters/machineout.js
+++ b/lib/reporters/machineout.js
@@ -83,7 +83,7 @@ exports.run = function (files, options, callback) {
}
});
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: options.testspec,
moduleStart: function (name) {},
testDone: function (name, assertions) {
diff --git a/lib/reporters/minimal.js b/lib/reporters/minimal.js
index 8ea6bff8a..5e3776bd6 100644
--- a/lib/reporters/minimal.js
+++ b/lib/reporters/minimal.js
@@ -113,7 +113,7 @@ exports.run = function (files, options, callback) {
var paths = files.map(function (p) {
return path.join(process.cwd(), p);
});
- nodeunit.runFiles(paths, opts);
+ nodeunit.runFiles(paths, options.recursive, opts);
} else {
nodeunit.runModules(files,opts);
}
diff --git a/lib/reporters/nested.js b/lib/reporters/nested.js
index b2d4f9467..725479dda 100644
--- a/lib/reporters/nested.js
+++ b/lib/reporters/nested.js
@@ -164,7 +164,7 @@ exports.run = function (files, options) {
console.log(txt);
};
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: options.testspec,
moduleStart: function (name) {
console.log('\n' + bold(name));
diff --git a/lib/reporters/skip_passed.js b/lib/reporters/skip_passed.js
index b39de41e8..5aad412b2 100644
--- a/lib/reporters/skip_passed.js
+++ b/lib/reporters/skip_passed.js
@@ -55,7 +55,7 @@ exports.run = function (files, options, callback) {
return path.join(process.cwd(), p);
});
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: options.testspec,
moduleStart: function (name) {
console.log('\n' + bold(name));
diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js
index b0574604a..508685225 100644
--- a/lib/reporters/tap.js
+++ b/lib/reporters/tap.js
@@ -36,7 +36,7 @@ exports.run = function (files, options) {
var output = new TapProducer();
output.pipe(process.stdout);
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testStart: function (name) {
output.write(name.toString());
},
diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js
index 6ccb6170e..1347db3f4 100644
--- a/lib/reporters/verbose.js
+++ b/lib/reporters/verbose.js
@@ -72,7 +72,7 @@ exports.run = function (files, options) {
}
});
- nodeunit.runFiles(paths, {
+ nodeunit.runFiles(paths, options.recursive, {
testspec: options.testspec,
moduleStart: function (name) {
console.log('\n' + bold(name));
diff --git a/lib/utils.js b/lib/utils.js
index 8797b4335..b89b5418e 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -12,7 +12,8 @@ var async = require('../deps/async'),
fs = require('fs'),
util = require('util'),
Script = process.binding('evals').Script,
- http = require('http');
+ http = require('http'),
+ path = require('path');
/**
@@ -32,54 +33,111 @@ catch (e) {
/**
* Finds all modules at each path in an array, If a path is a directory, it
- * returns all supported file types inside it. This only reads 1 level deep in
+ * returns all supported file types inside it.
+ *
+ * With recursive=false this function only reads 1 level deep in
* the directory and does not recurse through sub-directories.
*
* The extension (.js, .coffee etc) is stripped from the filenames so they can
* simply be require()'ed.
*
* @param {Array} paths
+ * @param {Boolean} recursive
* @param {Function} callback
* @api public
*/
-exports.modulePaths = function (paths, callback) {
- async.concat(paths, function (p, cb) {
- fs.stat(p, function (err, stats) {
+exports.modulePaths = function (paths, recursive, callback) {
+ async.concatSeries(paths, function (path, cb) {
+ fs.stat(path, function (err, stats) {
if (err) {
- return cb(err);
+ cb(err);
+ return;
}
if (stats.isFile()) {
- return cb(null, [p]);
+ cb(null, [path]);
+ return;
}
if (stats.isDirectory()) {
- fs.readdir(p, function (err, files) {
+ exports.readDirMaybeRecursive(path, recursive, function (err, files) {
if (err) {
- return cb(err);
+ cb(err);
+ return;
}
- // filter out any filenames with unsupported extensions
- var modules = files.filter(function (filename) {
- return extensionPattern.exec(filename);
- });
-
- // remove extension from module name and prepend the
- // directory path
- var fullpaths = modules.map(function (filename) {
- var mod_name = filename.replace(extensionPattern, '');
- return [p, mod_name].join('/');
- });
-
- // sort filenames here, because Array.map changes order
- fullpaths.sort();
-
- cb(null, fullpaths);
+ cb(null, files);
});
+ return;
}
+ cb(null, []);
+ });
+ }, function (err, files) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ // filter out any filenames with unsupported extensions
+ var modules = files.filter(function (filename) {
+ return extensionPattern.exec(filename);
+ });
+
+ // remove extension from module name
+ var paths = modules.map(function (filename) {
+ return filename.replace(extensionPattern, '');
});
- }, callback);
+
+ // sort filenames here, because Array.map changes order
+ paths.sort();
+ callback(null, paths);
+ });
};
+
+/**
+ * Finds all files in directory, maybe recursively
+ *
+ * @param {Array} dir
+ * @param {Boolean} recursive
+ * @param {Function} callback
+ * @api private
+ */
+
+exports.readDirMaybeRecursive = function(dir, recursive, callback) {
+ fs.readdir(dir, function (err, dirEntities) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.concatSeries(dirEntities, function (entityName, cb) {
+ var entityFullName = path.join(dir, entityName);
+ fs.stat(entityFullName, function (err, stats) {
+ if (err) {
+ cb(err);
+ return;
+ }
+ if (stats.isFile()) {
+ cb(null, [entityFullName]);
+ return;
+ }
+ if (stats.isDirectory() && recursive) {
+ exports.readDirMaybeRecursive(entityFullName, recursive, function (err, files) {
+ if (err) {
+ cb(err);
+ return;
+ }
+
+ cb(null, files);
+ });
+ return;
+ }
+ cb(null, []);
+ });
+ }, callback);
+ });
+};
+
+
/**
* Evaluates JavaScript files in a sandbox, returning the context. The first
* argument can either be a single filename or an array of filenames. If
diff --git a/test/fixtures/nested_dirs/a/a1.js b/test/fixtures/nested_dirs/a/a1.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/fixtures/nested_dirs/a/a2.js b/test/fixtures/nested_dirs/a/a2.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/fixtures/nested_dirs/b/b1.js b/test/fixtures/nested_dirs/b/b1.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/fixtures/nested_dirs/c/not_a_js.file b/test/fixtures/nested_dirs/c/not_a_js.file
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/fixtures/nested_dirs/nd.js b/test/fixtures/nested_dirs/nd.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/test/test-runfiles.js b/test/test-runfiles.js
index ce1a4cd00..31ec0a128 100644
--- a/test/test-runfiles.js
+++ b/test/test-runfiles.js
@@ -76,13 +76,14 @@ exports.testRunFiles = setup(function (test) {
[__dirname + '/fixtures/mock_module1.js',
__dirname + '/fixtures/mock_module2.js',
__dirname + '/fixtures/dir'],
+ false,
opts
);
});
exports.testRunFilesEmpty = function (test) {
test.expect(3);
- nodeunit.runFiles([], {
+ nodeunit.runFiles([], false, {
moduleStart: function () {
test.ok(false, 'should not be called');
},
@@ -115,7 +116,7 @@ exports.testEmptyDir = function (test) {
}
// runFiles on empty directory:
- nodeunit.runFiles([dir2], {
+ nodeunit.runFiles([dir2], false, {
moduleStart: function () {
test.ok(false, 'should not be called');
},
@@ -207,7 +208,8 @@ if (CoffeeScript) {
};
nodeunit.runFiles(
- [__dirname + 'fixtures/coffee/mock_coffee_module.coffee'],
+ [__dirname + '/fixtures/coffee/mock_coffee_module.coffee'],
+ false,
opts
);
};
diff --git a/test/test-sandbox.js b/test/test-sandbox.js
index 1b249d7af..09838ad01 100644
--- a/test/test-sandbox.js
+++ b/test/test-sandbox.js
@@ -1,6 +1,4 @@
-var nodeunit = require('../lib/nodeunit');
var sandbox = require('../lib/utils').sandbox;
-var testCase = nodeunit.testCase;
exports.testSimpleSandbox = function (test) {
var raw_jscode1 = sandbox(__dirname + '/fixtures/raw_jscode1.js');
@@ -26,6 +24,6 @@ exports.testSandboxMultiple = function (test) {
__dirname + '/fixtures/raw_jscode3.js',
__dirname + '/fixtures/raw_jscode3.js'
]);
- test.equal(raw_jscode3.t, 3, 'two files loaded');
+ test.equal(raw_jscode3.t, 3, 'three files loaded');
test.done();
};
diff --git a/test/test-utils.js b/test/test-utils.js
new file mode 100644
index 000000000..a45fd87c0
--- /dev/null
+++ b/test/test-utils.js
@@ -0,0 +1,65 @@
+var utils = require('../lib/utils');
+
+var root = __dirname + '/fixtures/nested_dirs/';
+
+exports.readDir = function (test) {
+ utils.readDirMaybeRecursive(root, false, function (err, files) {
+ if (err) {
+ throw err;
+ }
+
+ test.same(
+ files.sort(),
+ [root + 'nd.js'],
+ 'readDirMaybeRecursive(false) ok'
+ );
+ test.done();
+ });
+};
+
+exports.readDirRecursive = function (test) {
+ utils.readDirMaybeRecursive(root, true, function (err, files) {
+ if (err) {
+ throw err;
+ }
+
+ test.same(
+ files.sort(),
+ [root + 'a/a1.js', root + 'a/a2.js', root + 'b/b1.js', root + 'c/not_a_js.file', root + 'nd.js'],
+ 'readDirMaybeRecursive(true) ok'
+ );
+ test.done();
+ });
+};
+
+//delete(exports.readDirMaybeRecursiveTrue);
+
+exports.modulePaths = function (test) {
+ utils.modulePaths([root], false, function (err, paths) {
+ if (err) {
+ throw err;
+ }
+
+ test.same(
+ paths,
+ [root + 'nd'],
+ 'modulePaths(false) ok'
+ );
+ test.done();
+ });
+};
+
+exports.modulePathsRecursive = function (test) {
+ utils.modulePaths([root], true, function (err, paths) {
+ if (err) {
+ throw err;
+ }
+
+ test.same(
+ paths,
+ [root + 'a/a1', root + 'a/a2', root + 'b/b1', root + 'nd'],
+ 'modulePaths(true) ok'
+ );
+ test.done();
+ });
+};