From 46bd9eff89d04c7812393c6bb9653bb12be682bb Mon Sep 17 00:00:00 2001 From: "Aurelio A. Heckert" Date: Wed, 26 Jan 2011 23:36:56 -0300 Subject: [PATCH 1/2] make util.modulePaths() to get files recursively --- lib/utils.js | 56 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 8797b4335..11b117ac2 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'); /** @@ -53,33 +54,44 @@ exports.modulePaths = function (paths, callback) { return cb(null, [p]); } if (stats.isDirectory()) { - fs.readdir(p, function (err, files) { - if (err) { - return cb(err); - } - - // 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); + var files = exports.readDirRecursive(p); + + // 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); } }); }, callback); }; +exports.readDirRecursive = function(dir) { + var list = []; + fs.readdirSync(dir).forEach(function(entityName) { + var entityPath = path.join(dir, entityName); + if ( fs.statSync(entityPath).isDirectory() ) { + exports.readDirRecursive(entityPath).forEach(function(file) { + list.push(path.join(entityName, file)); + }); + } else { + list.push(entityName); + } + }); + return list; +}; + /** * Evaluates JavaScript files in a sandbox, returning the context. The first * argument can either be a single filename or an array of filenames. If From 1f54ad32cc7dfcdf6bbcce90c38311eeb0b6d829 Mon Sep 17 00:00:00 2001 From: Oleg Efimov Date: Wed, 16 Nov 2011 16:01:49 +0700 Subject: [PATCH 2/2] Optionaly run tests recursively when -r or --recursive, refs #54 --- README.md | 2 +- bin/nodeunit | 3 + bin/nodeunit.json | 3 +- lib/core.js | 1 - lib/nodeunit.js | 10 +- lib/reporters/default.js | 19 ++-- lib/reporters/eclipse.js | 2 +- lib/reporters/html.js | 2 +- lib/reporters/junit.js | 4 +- lib/reporters/machineout.js | 2 +- lib/reporters/minimal.js | 2 +- lib/reporters/nested.js | 2 +- lib/reporters/skip_passed.js | 2 +- lib/reporters/tap.js | 2 +- lib/reporters/verbose.js | 2 +- lib/utils.js | 112 +++++++++++++++------- test/fixtures/nested_dirs/a/a1.js | 0 test/fixtures/nested_dirs/a/a2.js | 0 test/fixtures/nested_dirs/b/b1.js | 0 test/fixtures/nested_dirs/c/not_a_js.file | 0 test/fixtures/nested_dirs/nd.js | 0 test/test-runfiles.js | 8 +- test/test-sandbox.js | 4 +- test/test-utils.js | 65 +++++++++++++ 24 files changed, 181 insertions(+), 66 deletions(-) create mode 100644 test/fixtures/nested_dirs/a/a1.js create mode 100644 test/fixtures/nested_dirs/a/a2.js create mode 100644 test/fixtures/nested_dirs/b/b1.js create mode 100644 test/fixtures/nested_dirs/c/not_a_js.file create mode 100644 test/fixtures/nested_dirs/nd.js create mode 100644 test/test-utils.js 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 11b117ac2..b89b5418e 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -33,65 +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()) { - var files = exports.readDirRecursive(p); - - // filter out any filenames with unsupported extensions - var modules = files.filter(function (filename) { - return extensionPattern.exec(filename); - }); + exports.readDirMaybeRecursive(path, recursive, function (err, files) { + if (err) { + cb(err); + return; + } - // 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('/'); + cb(null, files); }); + return; + } + cb(null, []); + }); + }, function (err, files) { + if (err) { + callback(err); + return; + } - // sort filenames here, because Array.map changes order - fullpaths.sort(); + // filter out any filenames with unsupported extensions + var modules = files.filter(function (filename) { + return extensionPattern.exec(filename); + }); - cb(null, fullpaths); - } + // 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); + }); }; -exports.readDirRecursive = function(dir) { - var list = []; - fs.readdirSync(dir).forEach(function(entityName) { - var entityPath = path.join(dir, entityName); - if ( fs.statSync(entityPath).isDirectory() ) { - exports.readDirRecursive(entityPath).forEach(function(file) { - list.push(path.join(entityName, file)); - }); - } else { - list.push(entityName); + +/** + * 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); }); - return list; }; + /** * 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(); + }); +};