API Docs for: 0.9.3
Show:

File: lib/manager.js

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

var Base = require('preceptor-core').Base;
var utils = require('preceptor-core').utils;
var ReportManager = require('preceptor-reporter');
var Promise = require('promise');
var _ = require('underscore');
var log4js = require('log4js');
var path = require('path');
var istanbul = require('istanbul');
var mkdirp = require('mkdirp');
var fs = require('fs');

var Config = require('./config');
var GroupTask = require('./task/group');

var defaultShared = require('./defaults/defaultShared');

var logger = log4js.getLogger(__filename);

/**
 * @class PreceptorManager
 * @extends Base
 *
 * @property {object} _options
 * @property {Config} _configuration
 *
 * @property {object} _taskDecoratorList
 * @property {object} _clientDecoratorList
 * @property {object} _taskList
 *
 * @property {ReportManager} _reportManager
 *
 * @property {Collector} _coverageCollector
 */
var PreceptorManager = Base.extend(

	/**
	 * Web-driver server constructor
	 *
	 * @param {object} options
	 * @constructor
	 */
	function (options) {
		this.__super();

		log4js.setGlobalLogLevel('INFO');
		this._options = options || {};

		this._taskDecoratorList = {};
		this._clientDecoratorList = {};
		this._taskList = {};

		this._reportManager = null;

		this._coverageCollector = new istanbul.Collector();

		this.initialize();
	},

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

			// Initialize registries
			this._initializeTaskDecoratorRegistry();
			this._initializeClientDecoratorRegistry();
			this._initializeTaskRegistry();

			// Make sure the configuration has the correct structure
			this.validate();

			// Augment options with outside data
			this.augment();

			// Create config object
			this._configuration = new Config(this._options.configuration);
			if (this.getConfig().isVerbose()) {
				log4js.setGlobalLogLevel('DEBUG');
			}

			// Set-up the report-manager
			this._reportManager = new ReportManager(this.getConfig().getReportManager());

			// Load custom plugins
			_.each(this.getConfig().getPlugins(), function (pluginPath) {
				var Plugin = require(pluginPath);

				if (Plugin.loader && _.isFunction(Plugin.loader)) {
					Plugin.loader(this);
				} else {
					throw new Error("The plugin " + pluginPath + " doesn't have a loader.");
				}
			}, this);
		},

		/**
		 * Initializes the options-decorator registry
		 *
		 * @method _initializeTaskDecoratorRegistry
		 * @private
		 */
		_initializeTaskDecoratorRegistry: function () {
			var defaultElements = [
				{ name: 'group', fileName: 'group' },
				{ name: 'identifier', fileName: 'identifier' },
				{ name: 'decorator', fileName: 'decorator' }
			];

			_.each(defaultElements, function (entry) {
				entry.path = path.join(__dirname, 'taskDecorator', entry.fileName);
				entry.fn = require(entry.path);
			}, this);

			this.registerTaskDecoratorRange(defaultElements);
		},

		/**
		 * Initializes the client-decorator registry
		 *
		 * @method _initializeClientDecoratorRegistry
		 * @private
		 */
		_initializeClientDecoratorRegistry: function () {
			var defaultElements = [
				{ name: 'plain', fileName: 'plain' }
			];

			_.each(defaultElements, function (entry) {
				entry.path = path.join(__dirname, 'clientDecorator', entry.fileName);
			}, this);

			this.registerClientDecoratorRange(defaultElements);
		},

		/**
		 * Initializes the task registry
		 *
		 * @method _initializeTaskRegistry
		 * @private
		 */
		_initializeTaskRegistry: function () {
			var defaultElements = [
				{ name: 'cucumber', fileName: 'cucumber' },
				{ name: 'group', fileName: 'group' },
				{ name: 'kobold', fileName: 'kobold' },
				{ name: 'loader', fileName: 'loader' },
				{ name: 'mocha', fileName: 'mocha' },
				{ name: 'node', fileName: 'node' },
				{ name: 'shell', fileName: 'shell' }
			];

			_.each(defaultElements, function (entry) {
				entry.path = path.join(__dirname, 'task', entry.fileName);
				entry.fn = require(entry.path);
			}, this);

			this.registerTaskRange(defaultElements);
		},


		/**
		 * Gets the options
		 *
		 * @method getOptions
		 * @return {object}
		 */
		getOptions: function () {
			return this._options;
		},

		/**
		 * Gets the configuration object
		 *
		 * @method getConfig
		 * @return {Config}
		 */
		getConfig: function () {
			return this._configuration;
		},

		/**
		 * Gets all the shared options for tasks
		 *
		 * @method getSharedTaskOptions
		 * @return {object}
		 */
		getSharedTaskOptions: function () {
			return this.getOptions().shared || {};
		},

		/**
		 * Gets a list of task options
		 *
		 * @method getTasks
		 * @return {object[]}
		 */
		getTasks: function () {
			return this.getOptions().tasks || [];
		},


		/**
		 * Gets the report-manager
		 *
		 * @method getReportManager
		 * @return {ReportManager}
		 */
		getReportManager: function () {
			return this._reportManager;
		},


		/**
		 * Validate data given
		 *
		 * @method validate
		 */
		validate: function () {
			if (!_.isObject(this.getOptions())) {
				throw new Error('The options parameter is not an object.');
			}
			if (!_.isObject(this.getSharedTaskOptions())) {
				throw new Error('The shared section is not an object.');
			}
			if (!_.isArray(this.getTasks()) && !_.isObject(this.getTasks())) {
				throw new Error('The shared section is not an object or array.');
			}
		},

		/**
		 * Augment data with default values
		 *
		 * @method augment
		 */
		augment: function () {

			// Inject default values
			this.getOptions().shared = utils.deepExtend({}, [defaultShared, this.getSharedTaskOptions()]);
			logger.debug('Augmented options', this._options);
		},


		/**
		 * Gets a dictionary of registered options-decorator
		 *
		 * @method getTaskDecoratorList
		 * @return {object}
		 */
		getTaskDecoratorList: function () {
			return this._taskDecoratorList;
		},

		/**
		 * Checks if a options-decorator is registered
		 *
		 * @method hasTaskDecorator
		 * @param {string} name
		 * @return {boolean}
		 */
		hasTaskDecorator: function (name) {
			return !!this._taskDecoratorList[name.toLowerCase()];
		},

		/**
		 * Gets a specific registered options-decorator
		 *
		 * @method getTaskDecorator
		 * @param {string} name
		 * @return {function}
		 */
		getTaskDecorator: function (name) {
			return this._taskDecoratorList[name.toLowerCase()];
		},

		/**
		 * Registers a options-decorator
		 *
		 * @method registerTaskDecorator
		 * @param {string} name
		 * @param {function} contr
		 */
		registerTaskDecorator: function (name, contr) {
			this._taskDecoratorList[name.toLowerCase()] = contr;
		},

		/**
		 * Registers a list of options-decorators
		 *
		 * @method registerTaskDecoratorRange
		 * @param {object[]} list `{ name: <string>, fn: <function> }`
		 */
		registerTaskDecoratorRange: function (list) {
			_.each(list, function (entry) {
				this.registerTaskDecorator(entry.name, entry.fn);
			}, this);
		},


		/**
		 * Gets a dictionary of registered client-decorator
		 *
		 * @method getClientDecoratorList
		 * @return {object}
		 */
		getClientDecoratorList: function () {
			return this._clientDecoratorList;
		},

		/**
		 * Checks if a client-decorator is registered
		 *
		 * @method hasClientDecorator
		 * @param {string} name
		 * @return {boolean}
		 */
		hasClientDecorator: function (name) {
			return !!this._clientDecoratorList[name.toLowerCase()];
		},

		/**
		 * Gets a specific registered client-decorator
		 *
		 * @method getClientDecorator
		 * @param {string} name
		 * @return {function}
		 */
		getClientDecorator: function (name) {
			return this._clientDecoratorList[name.toLowerCase()];
		},

		/**
		 * Registers a client-decorator
		 *
		 * @method registerClientDecorator
		 * @param {string} name
		 * @param {string} path
		 */
		registerClientDecorator: function (name, path) {
			this._clientDecoratorList[name.toLowerCase()] = path;
		},

		/**
		 * Registers a list of client-decorators
		 *
		 * @method registerClientDecoratorRange
		 * @param {object[]} list `{ name: <string>, path: <string> }`
		 */
		registerClientDecoratorRange: function (list) {
			_.each(list, function (entry) {
				this.registerClientDecorator(entry.name, entry.path);
			}, this);
		},


		/**
		 * Gets a dictionary of registered tasks
		 *
		 * @method getTaskList
		 * @return {object}
		 */
		getTaskList: function () {
			return this._taskList;
		},

		/**
		 * Checks if a task is registered
		 *
		 * @method hasTask
		 * @param {string} name
		 * @return {boolean}
		 */
		hasTask: function (name) {
			return !!this._taskList[name.toLowerCase()];
		},

		/**
		 * Gets a specific registered task
		 *
		 * @method getTask
		 * @param {string} name
		 * @return {function}
		 */
		getTask: function (name) {
			return this._taskList[name.toLowerCase()];
		},

		/**
		 * Registers a task
		 *
		 * @method registerTask
		 * @param {string} name
		 * @param {function} contr
		 */
		registerTask: function (name, contr) {
			this._taskList[name.toLowerCase()] = contr;
		},

		/**
		 * Registers a list of task
		 *
		 * @method registerTaskRange
		 * @param {object[]} list `{ name: <string>, fn: <function> }`
		 */
		registerTaskRange: function (list) {
			_.each(list, function (entry) {
				this.registerTask(entry.name, entry.fn);
			}, this);
		},


		/**
		 * Run the preceptor application
		 *
		 * @method run
		 * @return {Promise}
		 */
		run: function () {
			var promise = Promise.resolve(),
				config = this.getConfig(),
				reportManager = this.getReportManager(),
				coverageCollector = this._coverageCollector,
				eventReporter;

			logger.debug('Shared', this.getSharedTaskOptions());
			logger.debug('Config', config.toObject());
			logger.debug('Tasks', this.getTasks());

			logger.debug('Create reporter');
			eventReporter = reportManager.addReporter("Event"); // Needed to forward it to the client-decorator
			reportManager.addReporterRange(config.getReporter());
			reportManager.addListenerRange(config.getListener());

			// Output events from reporter
			eventReporter.on('message', function (areaType, messageType, params) {
				logger.debug('Event-Reporter: [' + areaType + '] ' + messageType, params);
			});

			// Start reporter
			reportManager.message().start();

			// Run task
			promise = promise.then(function () {

				var task = new GroupTask(config, coverageCollector, reportManager, {
					taskDecorator: this.getTaskDecoratorList(),
					clientDecorator: this.getClientDecoratorList(),
					task: this.getTaskList(),
					sharedOptions: this.getSharedTaskOptions()
				}, {
					type: 'group',
					taskId: 'root-task',
					name: 'Root Task',
					title: 'Preceptor',
					configuration: {
						parallel: false,
						tasks: this.getTasks()
					}
				});
				return task.run(undefined);

			}.bind(this));

			// Stop reporter before leaving
			promise = promise.then(function () {
				reportManager.message().stop();
				reportManager.message().complete();
				if (this.getConfig().getCoverage().isActive()) {
					this._writeCoverage(this.getConfig().getCoverage());
				}
			}.bind(this), function (err) {
				reportManager.message().stop();
				reportManager.message().complete();
				if (this.getConfig().getCoverage().isActive()) {
					this._writeCoverage(this.getConfig().getCoverage());
				}
				throw err;
			}.bind(this));

			return promise;
		},

		/**
		 * Write the coverage report collected
		 *
		 * @method _writeCoverage
		 * @param {object} coverageConfiguration
		 * @private
		 */
		_writeCoverage: function (coverageConfiguration) {
			var istanbulOverride, istanbulConfiguration, reporter, reportingDir, coverageFile, coverage, reports;

			// Setup configuration
			istanbulOverride = {
				"instrumentation": {
					"root": coverageConfiguration.getRoot()
				},
				"reporting": {
					"dir": coverageConfiguration.getPath()
				}
			};
			istanbulConfiguration = istanbul.config.loadObject(null, istanbulOverride);

			// Get configuration options
			reportingDir = path.resolve(istanbulConfiguration.reporting.dir());
			coverageFile = path.resolve(reportingDir, 'coverage.json');
			reports = coverageConfiguration.getReports() || [];

			// Create folder for reporting if not exists
			mkdirp.sync(reportingDir);

			if (reports.indexOf('file') !== -1) {

				// Write JSON file
				coverage = this._coverageCollector.getFinalCoverage();
				fs.writeFileSync(coverageFile, JSON.stringify(coverage, 4));

				// Filter "file"-report
				reports = reports.filter(function (entry) {
					return entry !== 'file';
				});
			}

			// Write report
			reporter = new istanbul.Reporter(istanbulConfiguration);
			reporter.addAll(reports);
			reporter.write(this._coverageCollector, true, function () {});
		}
	},

	{
		/**
		 * @property TYPE
		 * @type {string}
		 * @static
		 */
		TYPE: 'Preceptor'
	});

// Expose plugin interfaces

/**
 * @property AbstractClient
 * @type {AbstractClient}
 * @static
 */
PreceptorManager.AbstractClient = require('./abstractClient');

/**
 * @property AbstractClientDecorator
 * @type {AbstractClientDecorator}
 * @static
 */
PreceptorManager.AbstractClientDecorator = require('./abstractClientDecorator');

/**
 * @property AbstractForkTask
 * @type {AbstractForkTask}
 * @static
 */
PreceptorManager.AbstractForkTask = require('./abstractForkTask');

/**
 * @property AbstractTaskDecorator
 * @type {AbstractTaskDecorator}
 * @static
 */
PreceptorManager.AbstractTaskDecorator = require('./abstractTaskDecorator');

/**
 * @property AbstractTask
 * @type {AbstractTask}
 * @static
 */
PreceptorManager.AbstractTask = require('./abstractTask');

/**
 * @property version
 * @type {string}
 * @static
 */
PreceptorManager.version = require('../package.json').version;

module.exports = PreceptorManager;