API Docs for: 0.9.3
Show:

File: lib/task/client/mocha.js

// Copyright 2014, Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

var AbstractClient = require('../../abstractClient');
var Promise = require('promise');
var _ = require('underscore');
var utils = require('preceptor-core').utils;
var fs = require('fs');
var glob = require('glob');
var path = require('path');

var exists = fs.existsSync || path.existsSync;

Error.stackTraceLimit = Infinity;

/**
 * @class MochaClient
 * @extends AbstractClient
 * @constructor
 */
var MochaClient = AbstractClient.extend(

	{
		/**
		 * Initializes the instance
		 *
		 * @method initialize
		 */
		initialize: function () {

			var self = this;

			this.__super();

			this.getFunctions().push(function () {
				before(function (done) {
					self.processBefore().then(function () {
						done();
					}, function (err) {
						console.error(err.stack);
						done(err);
					});
				});
				after(function (done) {
					self.processAfter().then(function () {
						done();
					}, function (err) {
						console.error(err.stack);
						done(err);
					});
				});
				beforeEach(function (done) {
					self.processBeforeTest().then(function () {
						done();
					}, function (err) {
						console.error(err.stack);
						done(err);
					});
				});
				afterEach(function (done) {
					self.processAfterTest().then(function () {
						done();
					}, function (err) {
						console.error(err.stack);
						done(err);
					});
				});
			});
		},


		/**
		 * Gets the mocha configuration
		 * Overwrite this function if the mocha configuration is found somewhere else.
		 *
		 * @method getMochaConfiguration
		 * @return {object}
		 */
		getMochaConfiguration: function () {
			return this.getOptions();
		},

		/**
		 * Sets the mocha configuration
		 * Overwrite this function if the mocha configuration is found somewhere else.
		 *
		 * @method setMochaConfiguration
		 * @param {object} options
		 */
		setMochaConfiguration: function (options) {
			this._options = options;
		},


		/**
		 * Gets the reporter
		 * Mocha Option: reporter
		 *
		 * @method getReporter
		 * @return {string}
		 */
		getReporter: function () {
			return this.getMochaConfiguration().reporter;
		},

		/**
		 * Gets the UI interface ('tdd', 'bdd')
		 * Mocha Option: ui
		 *
		 * @method getUi
		 * @return {string}
		 */
		getUi: function () {
			return this.getMochaConfiguration().ui;
		},

		/**
		 * Should colors be used in output?
		 * Mocha Option: colors
		 *
		 * @method useColors
		 * @return {boolean}
		 */
		useColors: function () {
			return this.getMochaConfiguration().colors;
		},

		/**
		 * Output inline diffs
		 * Mocha Option: inline-diffs
		 *
		 * @method useInlineDiffs
		 * @return {boolean}
		 */
		useInlineDiffs: function () {
			return this.getMochaConfiguration().inlineDiffs;
		},

		/**
		 * Gets the threshold for slow tests
		 * Mocha Option: slow
		 *
		 * @method getSlowThreshold
		 * @return {int}
		 */
		getSlowThreshold: function () {
			return this.getMochaConfiguration().slow;
		},

		/**
		 * Should time-outs be observed?
		 * Mocha Option: [no-]timeouts
		 *
		 * @method useTimeOuts
		 * @return {boolean}
		 */
		useTimeOuts: function () {
			return this.getMochaConfiguration().timeOuts;
		},

		/**
		 * Gets the threshold for too slow test-suites
		 * Mocha Option: timeout
		 *
		 * @method getTimeOut
		 * @return {int}
		 */
		getTimeOut: function () {
			return this.getMochaConfiguration().timeOut;
		},

		/**
		 * Should mocha bail on first error?
		 * Mocha Option: bail
		 *
		 * @method shouldBail
		 * @return {boolean}
		 */
		shouldBail: function () {
			return this.getMochaConfiguration().bail;
		},

		/**
		 * Gets the test filter
		 * Mocha Option: grep
		 *
		 * @method getGrep
		 * @return {string|boolean}
		 */
		getGrep: function () {
			return this.getMochaConfiguration().grep;
		},

		/**
		 * Should the test sequence inverted?
		 * Mocha Option: invert
		 *
		 * @method shouldInvert
		 * @return {boolean}
		 */
		shouldInvert: function () {
			return this.getMochaConfiguration().invert;
		},

		/**
		 * Should mocha check for leaks
		 * Mocha Option: check-leaks
		 *
		 * @method shouldCheckLeaks
		 * @return {boolean}
		 */
		shouldCheckLeaks: function () {
			return this.getMochaConfiguration().checkLeaks;
		},

		/**
		 * Gets the reporter
		 * Mocha Option: async-only
		 *
		 * @method useAsyncOnly
		 * @return {boolean}
		 */
		useAsyncOnly: function () {
			return this.getMochaConfiguration().asyncOnly;
		},

		/**
		 * Gets the list of defined globals
		 * Mocha Option: globals
		 *
		 * @method getGlobals
		 * @return {string[]}
		 */
		getGlobals: function () {
			return this.getMochaConfiguration().globals;
		},

		/**
		 * Gets the path of all tests
		 * Mocha Option: <last parameters>
		 *
		 * @method getPaths
		 * @return {string[]}
		 */
		getPaths: function () {
			return this.getMochaConfiguration().paths;
		},

		/**
		 * Gets a list of functions to execute before the tests
		 * Mocha Option: <does not exist>
		 *
		 * @method getFunctions
		 * @return {function[]}
		 */
		getFunctions: function () {
			return this.getMochaConfiguration().functions;
		},

		/**
		 * Should the test-search be recursive?
		 * Mocha Option: recursive
		 *
		 * @method getRecursive
		 * @return {boolean}
		 */
		getRecursive: function () {
			return this.getMochaConfiguration().recursive;
		},

		/**
		 * List of files to be required before the tests are run
		 * Mocha Option: require
		 *
		 * @method getRequire
		 * @return {string[]}
		 */
		getRequire: function () {
			return this.getMochaConfiguration().require;
		},

		/**
		 * Should tests be sorted after gathering and before executing?
		 * Mocha Option: sort
		 *
		 * @method shouldSort
		 * @return {boolean}
		 */
		shouldSort: function () {
			return this.getMochaConfiguration().sort;
		},


		/**
		 * Execute client
		 *
		 * @method run
		 * @param {string} parentId
		 * @return {Promise}
		 */
		run: function (parentId) {

			return new Promise(function (resolve, reject) {

				var instance,
					hook,
					done;

				hook = this.getReportManager().loadHook('mocha', parentId);

				this.getReportManager().message().start();
				done = function () {
					this.getReportManager().message().stop();
					this.getReportManager().message().complete();
				}.bind(this);

				instance = this._runMocha(this.getMochaConfiguration(), function () {
					done();
					resolve.apply(this, arguments);
				}, function () {
					done();
					reject.apply(this, arguments);
				});
				hook(instance);

			}.bind(this));
		},

		/**
		 * Runs mocha tests, self-contained
		 *
		 * @method _runMocha
		 * @param {object} options
		 * @param {string} options.reporter
		 * @param {string} options.ui
		 * @param {boolean} options.colors
		 * @param {boolean} options.inlineDiffs
		 * @param {int} options.slow
		 * @param {boolean} options.timeOuts
		 * @param {int} options.timeOut
		 * @param {boolean} options.bail
		 * @param {boolean|string} options.grep
		 * @param {boolean} options.invert
		 * @param {boolean} options.checkLeaks
		 * @param {boolean} options.asyncOnly
		 * @param {string[]} options.globals
		 * @param {string[]} [options.paths=['test']]
		 * @param {functions[]} options.functions
		 * @param {boolean} options.recursive
		 * @param {string|string[]} options.require
		 * @param {boolean} options.sort
		 * @param {function} success
		 * @param {function} failure
		 * @return {*}
		 * @private
		 */
		_runMocha: function (options, success, failure) {

			var files = [],
				mocha,
				runner,
				paths,
				mochaLoadFiles,
				cwd = process.cwd(),
				Mocha = require('mocha');

			options = options || {};

			mocha = new Mocha();
			mochaLoadFiles = mocha.loadFiles;

			module.paths.push(cwd, path.join(cwd, 'node_modules'));

			// --reporter
			mocha.reporter(options.reporter);

			// --ui
			mocha.ui(options.ui);

			// --colors
			if (options.colors === false) {
				mocha.useColors(false);
			} else {
				mocha.useColors(true);
			}

			// --inline-diffs
			mocha.useInlineDiffs(options.inlineDiffs);

			// --slow <ms>
			mocha.suite.slow(options.slow);

			// --[no-]timeouts
			mocha.enableTimeouts(options.timeOuts);

			// --timeout
			mocha.suite.timeout(options.timeOut);

			// --bail
			mocha.suite.bail(options.bail);

			// --grep
			if (options.grep) {
				mocha.grep(new RegExp(options.grep));
			}

			// --invert
			if (options.invert) {
				mocha.invert();
			}

			// --check-leaks
			if (options.checkLeaks) {
				mocha.checkLeaks();
			}

			// --async-only
			if (options.asyncOnly) {
				mocha.asyncOnly();
			}

			// --globals
			mocha.globals(options.globals);

			// Find all test-files
			paths = options.paths;
			if ((paths.length === 0) && (options.functions.length > 0)) {
				paths = ['test'];
			}
			_.each(paths, function(path) {
				files = files.concat(lookupFiles(path, options.recursive));
			});

			if (options.require) {
				_.each(options.require, function (requireFile) {
					require(path.resolve(requireFile));
				});
			}

			// Resolve to full path
			files = files.map(function (filePath) {
				return path.resolve(filePath);
			});

			// --sort
			if (options.sort) {
				files.sort();
			}

			if ((files.length === 0) && (options.functions.length > 0)) {
				files.push(path.join(__dirname, 'resources', 'empty.js'));
			}

			// Load files
			mocha.files = files;

			// Add prepared functions instead of files
			if (options.functions) {
				mocha.loadFiles = function () {

					// Load the functions
					_.each(options.functions, function (currentFn, index) {
						var fileName = currentFn.name || 'function-' + index;
						mocha.suite.emit('pre-require', global, fileName, mocha);
						mocha.suite.emit('require', (_.isString(currentFn) ? require(currentFn) : currentFn()), fileName, mocha);
						mocha.suite.emit('post-require', global, fileName, mocha);
					});

					// Load all the files
					mochaLoadFiles.apply(this, arguments);

					// Add the functions as "files"
					_.each(options.functions, function (currentFn, index) {
						var fileName = currentFn.name || 'function-' + index;
						mocha.files.push(fileName);
					});
				};
			}

			// Run mocha tests
			runner = mocha.run(function (code) {
				if (code === 0) {
					success(code);
				} else {
					failure(new Error('Failed'));
				}
			});

			process.on('SIGINT', function() {
				runner.abort();
			});

			/**
			 * Lookup all files in the path given
			 *
			 * @method lookupFiles
			 * @param {string} currentPath
			 * @param {boolean} [recursive=false]
			 * @return {string[]}
			 * @private
			 */
			function lookupFiles (currentPath, recursive) {

				var files = [],
					stat;

				if (!exists(currentPath)) {

					if (exists(currentPath + '.js')) {
						currentPath += '.js'

					} else {
						files = glob.sync(currentPath);
						if (!files.length) {
							throw new Error("cannot resolve path (or pattern) '" + currentPath + "'");
						}
						return files;
					}
				}

				stat = fs.statSync(currentPath);
				if (stat.isFile()) {
					return [currentPath];

				} else {

					fs.readdirSync(currentPath).forEach(function (file) {
						var stat;

						file = path.join(currentPath, file);

						stat = fs.statSync(file);
						if (stat.isDirectory()) {

							if (recursive) {
								files = files.concat(lookupFiles(file, recursive));
							}

						} else if (stat.isFile() && (path.basename(file)[0] !== '.')) {
							files.push(file);
						}
					});

					return files;
				}
			}

			return runner;
		}
	});

module.exports = MochaClient;