<commit_before>/**
 * @fileoverview Main CLI object which makes use of the Linter's API to access functionality
 * @author Raghav Dua <duaraghav8@gmail.com>
 */

'use strict';

var cli = require ('commander'),
	fs = require ('fs'),
	path = require ('path'),
	chokidar = require ('chokidar'),
	traverse = require ('sol-digger'),
	solium = require ('./solium'),
	soliumRules = require ('../config/solium.json').rules,
	sum = require ('lodash/sum'),
	version = require ('../package.json').version;

var CWD = process.cwd (),
	SOLIUMRC_FILENAME = '.soliumrc.json',
	SOLIUMRC_FILENAME_ABSOLUTE = path.join (CWD, SOLIUMRC_FILENAME),
	SOLIUMIGNORE_FILENAME = '.soliumignore',
	SOLIUMIGNORE_FILENAME_ABSOLUTE = path.join (CWD, SOLIUMIGNORE_FILENAME),
	DEFAULT_SOLIUMIGNORE_PATH = __dirname + '/cli-utils/.default-solium-ignore';

var errorCodes = {
	ERRORS_FOUND: 1,
	NO_SOLIUMRC: 3,
	WRITE_FAILED: 4,
	INVALID_PARAMS: 5,
};

/**
 * Create default configuration files in the user's directory
 * @returns {void}
 */
function setupDefaultUserConfig () {
	createDefaultConfigJSON ();
	createDefaultSoliumIgnore ();
}

/**
 * Synchronously write the passed configuration to the file whose absolute path is SOLIUMRC_FILENAME_ABSOLUTE
 * @param {Object} config User Configuration object
 * @returns {void}
 */
function writeConfigFile (config) {
	try {
		fs.writeFileSync (
			SOLIUMRC_FILENAME_ABSOLUTE,
			JSON.stringify (config, null, 2)
		);
	} catch (e) {
		console.error (
			'An error occurred while writing to ' + SOLIUMRC_FILENAME_ABSOLUTE + ':\n' + e
		);
		process.exit (errorCodes.WRITE_FAILED);
	}
}

/**
 * Insert any rules that are present in SoliumRules.json but not in user's .soliumrc.json's rules
 * @param {Object} userConfig User Configuration object
 * @returns {void}
 */
function synchronizeWithSoliumRules (userConfig) {
	Object.keys (soliumRules).filter (function (rulename) {
		return soliumRules [rulename].enabled;
	}).forEach (function (rulename) {
		//only insert those rules that don't already exist. If a rule exists and is disabled, leave it untouched
		if (!userConfig.rules.hasOwnProperty (rulename)) {
			userConfig.rules [rulename] = true;
		}
	});

	writeConfigFile (userConfig);
}

/**
 * Copy data from cli-utils/.default-solium-ignore to (newly created) .soliumignore in user's root directory
 * @returns {void}
 */
function createDefaultSoliumIgnore () {
	try {
		fs.writeFileSync (
			SOLIUMIGNORE_FILENAME_ABSOLUTE,
			fs.readFileSync (DEFAULT_SOLIUMIGNORE_PATH)
		);
	} catch (e) {
		console.error (
			'An error occurred while writing to ' + SOLIUMIGNORE_FILENAME_ABSOLUTE + ':\n' + e
		);
		process.exit (errorCodes.WRITE_FAILED);
	}
}

/**
 * Create default solium configuration JSON in user's current working directory.
 * This file enables all the built-in lint rules
 */
function createDefaultConfigJSON () {
	var config = {
		'custom-rules-filename': null,
		rules: {}
	};

	Object.keys (soliumRules).filter (function (rulename) {
		return soliumRules [rulename].enabled;
	}).forEach (function (rulename) {
		config.rules [rulename] = true;
	});

	writeConfigFile (config);
}

/**
 * Lint a source code string based on user settings
 * @param {String} sourceCode The source code to be linted
 * @param {Object} userConfig User configuration
 * @param {Object} errorReporter The error reporter to use
 * @param {String} fileName (optional) File name to use when reporting errors
 */
function lintString (sourceCode, userConfig, errorReporter, fileName) {
		var lintErrors;

		try {
			lintErrors = solium.lint (sourceCode, userConfig);
		} catch (e) {
			console.error (
				'An error occurred while running the linter on ' + fileName + ':\n' + e.stack
			);
			return;
		}

		//if any lint errors exist, report them
		lintErrors.length && errorReporter.report (fileName || '[stdin]', sourceCode, lintErrors);

		return lintErrors.length;
}

/**
 * Lint a file based on user settings
 * @param {String} fileName The path to the file to be linted
 * @param {Object} userConfig User configuration
 * @param {Object} errorReporter The error reporter to use
 */
function lintFile (fileName, userConfig, errorReporter) {
	var sourceCode = '';

	try {
		sourceCode = fs.readFileSync (fileName, 'utf8');
	} catch (e) {
		console.error (
			'[ERROR] Unable to read ' + fileName + ': ' + e
		);
	}

	return lintString (sourceCode, userConfig, errorReporter, fileName)
}

/**
 * Function that calls Solium object's linter based on user settings
 * @param {Object} userConfig User's configurations that contain information about which linting rules to apply
 * @param {String} filename (optional) The single file to be linted.
 * If not given, we lint the entire directory's (and sub-directories') solidity files
 */
function lint (userConfig, input, ignore, errorReporter) {
	var filesToLint, errorCount;

	//If filename is provided, lint it. Otherwise, lint over current directory & sub-directories
	if (input.file) {
		filesToLint = [input.file];
	} else if (input.dir) {
		filesToLint = traverse (input.dir, ignore);
	}

	if (filesToLint) {
		errorCount = sum (filesToLint.map (function (file) {
			return lintFile (file, userConfig, errorReporter);
		}));
	} else if (input.stdin) {
		var sourceCode = fs.readFileSync ('/dev/stdin', 'utf-8');
		errorCount = lintString (sourceCode, userConfig, errorReporter);
	} else {
		console.error(
			'ERROR! Must specify input for linter using --file, --dir or --stdin'
		);
		process.exit (errorCodes.INVALID_PARAMS);
	}

	errorReporter.finalize && errorReporter.finalize();

	return errorCount;
}

/**
 * Function responsible for defining all the available commandline options & version information
 * @param {Object} cliObject Commander Object handling the cli
 */
function createCliOptions (cliObject) {
	cliObject
		.version (version)
		.usage ('[options] <keyword>')

		.option ('-i, --init', 'Create default rule configuration')
		.option ('-f, --file [filename]', 'Specify a file whose code you wish to lint')
		.option ('-d, --dir [dirname]', 'Specify the directory to look for Solidity files in')
		.option ('-, --stdin', 'Read input file from stdin')
		.option ('--watch, --hot', 'Enable Hot Loading (Hot Swapping)')
		.option ('-s, --sync', 'Make sure that all Solium rules enabled by default are specified in your ' + SOLIUMRC_FILENAME)
		.option ('-R, --reporter <name>', 'Specify the format in which to report the issues found');
}

/**
 * Function that takes a name and returns an error reporter
 * @param {String} name Name of the reporter
 */
function getErrorReporter (name) {
	if (name === 'pretty' || typeof name === 'undefined') {
		return require ('./reporters/pretty');
	} else if (name === 'gcc') {
		return require ('./reporters/gcc');
	}

	throw new Error (
		'Invalid reporter "' + name + '". Valid reporters are "gcc" or "pretty"'
	);
}

/**
 * Entry point to the CLI reponsible for initiating linting process based on command-line arguments
 * @param {Array} programArgs Commandline arguments
 */
function execute (programArgs) {

	var userConfig, ignore;

	createCliOptions (cli);
	cli.parse (programArgs);

	if (cli.init) {
		return setupDefaultUserConfig ();
	}

	try {
		var errorReporter = getErrorReporter (cli.reporter);
	} catch (e) {
		console.error ('ERROR! ' + e.message);
		process.exit (errorCodes.INVALID_PARAMS)
	}

	try {
		userConfig = require (SOLIUMRC_FILENAME_ABSOLUTE);
	} catch (e) {
		console.error (
			'ERROR! Couldn\'t find ' + SOLIUMRC_FILENAME + ' in the current directory.\nUse solium --init to create one.'
		);
		process.exit (errorCodes.NO_SOLIUMRC);
	}

	if (cli.sync) {
		synchronizeWithSoliumRules (userConfig);
		return writeConfigFile (userConfig);
	}

	//if custom rules' file is set, make sure we have its absolute path
	if (
		userConfig ['custom-rules-filename'] &&
		!path.isAbsolute (userConfig ['custom-rules-filename'])
	) {
		userConfig ['custom-rules-filename'] = path.join (
			CWD, userConfig ['custom-rules-filename']
		);
	}

	//get all files & folders to ignore from .soliumignore
	try {
		ignore = fs.readFileSync (SOLIUMIGNORE_FILENAME_ABSOLUTE, 'utf8').split ('\n');
	} catch (e) {
		console.error (
			'There was an error trying to read \'' + SOLIUMIGNORE_FILENAME_ABSOLUTE + '\':\n' + e
		);
	}

	if (cli.hot && cli.stdin) {
		return console.error (
			'ERROR! Cannot watch files when reading from stdin'
		);
	}

	var errorCount = lint (userConfig, { file: cli.file, dir: cli.dir, stdin: cli.stdin }, ignore, errorReporter);

	if (cli.hot) {

		var spy = chokidar.watch (CWD);

		spy.on ('change', function () {
			console.log('\x1Bc'); // clear the console
			console.log ('File change detected. Start linting.\n');
			lint (userConfig, { file: cli.file, dir: cli.dir }, ignore, errorReporter);	//lint on subsequent changes (hot)
			console.log ('Linting complete. Watching for file changes.\n');
		});

	} else {

		if (errorCount > 0) {
			process.exit(errorCodes.ERRORS_FOUND);
		}

	}
}

module.exports = {
	execute: execute
};
<commit_msg>add  option to cli tool<commit_after>/**
 * @fileoverview Main CLI object which makes use of the Linter's API to access functionality
 * @author Raghav Dua <duaraghav8@gmail.com>
 */

'use strict';

var cli = require ('commander'),
	fs = require ('fs'),
	path = require ('path'),
	chokidar = require ('chokidar'),
	traverse = require ('sol-digger'),
	solium = require ('./solium'),
	soliumRules = require ('../config/solium.json').rules,
	sum = require ('lodash/sum'),
	version = require ('../package.json').version;

var CWD = process.cwd (),
	SOLIUMRC_FILENAME = '.soliumrc.json',
	SOLIUMRC_FILENAME_ABSOLUTE = path.join (CWD, SOLIUMRC_FILENAME),
	SOLIUMIGNORE_FILENAME = '.soliumignore',
	SOLIUMIGNORE_FILENAME_ABSOLUTE = path.join (CWD, SOLIUMIGNORE_FILENAME),
	DEFAULT_SOLIUMIGNORE_PATH = __dirname + '/cli-utils/.default-solium-ignore';

var errorCodes = {
	ERRORS_FOUND: 1,
	NO_SOLIUMRC: 3,
	WRITE_FAILED: 4,
	INVALID_PARAMS: 5,
};

/**
 * Create default configuration files in the user's directory
 * @returns {void}
 */
function setupDefaultUserConfig () {
	createDefaultConfigJSON ();
	createDefaultSoliumIgnore ();
}

/**
 * Synchronously write the passed configuration to the file whose absolute path is SOLIUMRC_FILENAME_ABSOLUTE
 * @param {Object} config User Configuration object
 * @returns {void}
 */
function writeConfigFile (config) {
	try {
		fs.writeFileSync (
			SOLIUMRC_FILENAME_ABSOLUTE,
			JSON.stringify (config, null, 2)
		);
	} catch (e) {
		console.error (
			'An error occurred while writing to ' + SOLIUMRC_FILENAME_ABSOLUTE + ':\n' + e
		);
		process.exit (errorCodes.WRITE_FAILED);
	}
}

/**
 * Insert any rules that are present in SoliumRules.json but not in user's .soliumrc.json's rules
 * @param {Object} userConfig User Configuration object
 * @returns {void}
 */
function synchronizeWithSoliumRules (userConfig) {
	Object.keys (soliumRules).filter (function (rulename) {
		return soliumRules [rulename].enabled;
	}).forEach (function (rulename) {
		//only insert those rules that don't already exist. If a rule exists and is disabled, leave it untouched
		if (!userConfig.rules.hasOwnProperty (rulename)) {
			userConfig.rules [rulename] = true;
		}
	});

	writeConfigFile (userConfig);
}

/**
 * Copy data from cli-utils/.default-solium-ignore to (newly created) .soliumignore in user's root directory
 * @returns {void}
 */
function createDefaultSoliumIgnore () {
	try {
		fs.writeFileSync (
			SOLIUMIGNORE_FILENAME_ABSOLUTE,
			fs.readFileSync (DEFAULT_SOLIUMIGNORE_PATH)
		);
	} catch (e) {
		console.error (
			'An error occurred while writing to ' + SOLIUMIGNORE_FILENAME_ABSOLUTE + ':\n' + e
		);
		process.exit (errorCodes.WRITE_FAILED);
	}
}

/**
 * Create default solium configuration JSON in user's current working directory.
 * This file enables all the built-in lint rules
 */
function createDefaultConfigJSON () {
	var config = {
		'custom-rules-filename': null,
		rules: {}
	};

	Object.keys (soliumRules).filter (function (rulename) {
		return soliumRules [rulename].enabled;
	}).forEach (function (rulename) {
		config.rules [rulename] = true;
	});

	writeConfigFile (config);
}

/**
 * Lint a source code string based on user settings
 * @param {String} sourceCode The source code to be linted
 * @param {Object} userConfig User configuration
 * @param {Object} errorReporter The error reporter to use
 * @param {String} fileName (optional) File name to use when reporting errors
 */
function lintString (sourceCode, userConfig, errorReporter, fileName) {
		var lintErrors;

		try {
			if (userConfig.options.autofix) {
				var result = solium.lintAndFix (sourceCode, userConfig);

				lintErrors = result.errorMessages;
				result.fixesApplied.length && fs.writeFileSync (filename, result.fixedSourceCode);
			} else {
				lintErrors = solium.lint (sourceCode, userConfig);
			}
		} catch (e) {
			console.error (
				'An error occurred while running the linter on ' + fileName + ':\n' + e.stack
			);
			return;
		}

		//if any lint errors exist, report them
		lintErrors.length && errorReporter.report (fileName || '[stdin]', sourceCode, lintErrors);

		return lintErrors.length;
}

/**
 * Lint a file based on user settings
 * @param {String} fileName The path to the file to be linted
 * @param {Object} userConfig User configuration
 * @param {Object} errorReporter The error reporter to use
 */
function lintFile (fileName, userConfig, errorReporter) {
	var sourceCode = '';

	try {
		sourceCode = fs.readFileSync (fileName, 'utf8');
	} catch (e) {
		console.error (
			'[ERROR] Unable to read ' + fileName + ': ' + e
		);
	}

	return lintString (sourceCode, userConfig, errorReporter, fileName)
}

/**
 * Function that calls Solium object's linter based on user settings
 * @param {Object} userConfig User's configurations that contain information about which linting rules to apply
 * @param {String} filename (optional) The single file to be linted.
 * If not given, we lint the entire directory's (and sub-directories') solidity files
 */
function lint (userConfig, input, ignore, errorReporter) {
	var filesToLint, errorCount;

	//If filename is provided, lint it. Otherwise, lint over current directory & sub-directories
	if (input.file) {
		filesToLint = [input.file];
	} else if (input.dir) {
		filesToLint = traverse (input.dir, ignore);
	}

	if (filesToLint) {
		errorCount = sum (filesToLint.map (function (file) {
			return lintFile (file, userConfig, errorReporter);
		}));
	} else if (input.stdin) {
		var sourceCode = fs.readFileSync ('/dev/stdin', 'utf-8');
		errorCount = lintString (sourceCode, userConfig, errorReporter);
	} else {
		console.error(
			'ERROR! Must specify input for linter using --file, --dir or --stdin'
		);
		process.exit (errorCodes.INVALID_PARAMS);
	}

	errorReporter.finalize && errorReporter.finalize();

	return errorCount;
}

/**
 * Function responsible for defining all the available commandline options & version information
 * @param {Object} cliObject Commander Object handling the cli
 */
function createCliOptions (cliObject) {
	cliObject
		.version (version)
		.usage ('[options] <keyword>')

		.option ('-i, --init', 'Create default rule configuration')
		.option ('-f, --file [filename]', 'Specify a file whose code you wish to lint')
		.option ('-d, --dir [dirname]', 'Specify the directory to look for Solidity files in')
		.option ('-, --stdin', 'Read input file from stdin')
		.option ('--watch, --hot', 'Enable Hot Loading (Hot Swapping)')
		.option ('-s, --sync', 'Make sure that all Solium rules enabled by default are specified in your ' + SOLIUMRC_FILENAME)
		.option ('-R, --reporter <name>', 'Specify the format in which to report the issues found')
		.option ('--fix', 'Fix the errors where possible.');
}

/**
 * Function that takes a name and returns an error reporter
 * @param {String} name Name of the reporter
 */
function getErrorReporter (name) {
	if (name === 'pretty' || typeof name === 'undefined') {
		return require ('./reporters/pretty');
	} else if (name === 'gcc') {
		return require ('./reporters/gcc');
	}

	throw new Error (
		'Invalid reporter "' + name + '". Valid reporters are "gcc" or "pretty"'
	);
}

/**
 * Entry point to the CLI reponsible for initiating linting process based on command-line arguments
 * @param {Array} programArgs Commandline arguments
 */
function execute (programArgs) {

	var userConfig, ignore;

	createCliOptions (cli);
	cli.parse (programArgs);

	if (cli.init) {
		return setupDefaultUserConfig ();
	}

	try {
		var errorReporter = getErrorReporter (cli.reporter);
	} catch (e) {
		console.error ('ERROR! ' + e.message);
		process.exit (errorCodes.INVALID_PARAMS)
	}

	try {
		userConfig = require (SOLIUMRC_FILENAME_ABSOLUTE);
	} catch (e) {
		console.error (
			'ERROR! Couldn\'t find ' + SOLIUMRC_FILENAME + ' in the current directory.\nUse solium --init to create one.'
		);
		process.exit (errorCodes.NO_SOLIUMRC);
	}

	if (cli.sync) {
		synchronizeWithSoliumRules (userConfig);
		return writeConfigFile (userConfig);
	}

	//if custom rules' file is set, make sure we have its absolute path
	if (
		userConfig ['custom-rules-filename'] &&
		!path.isAbsolute (userConfig ['custom-rules-filename'])
	) {
		userConfig ['custom-rules-filename'] = path.join (
			CWD, userConfig ['custom-rules-filename']
		);
	}

	// Pass cli arguments that modify the behaviour of upstream functions.
	userConfig.options = { autofix: Boolean (cli.fix) };

	//get all files & folders to ignore from .soliumignore
	try {
		ignore = fs.readFileSync (SOLIUMIGNORE_FILENAME_ABSOLUTE, 'utf8').split ('\n');
	} catch (e) {
		console.error (
			'There was an error trying to read \'' + SOLIUMIGNORE_FILENAME_ABSOLUTE + '\':\n' + e
		);
	}

	if (cli.hot) {
		if (cli.stdin) {
			return console.error (
				'ERROR! Cannot watch files when reading from stdin'
			);
		}

		if (cli.fix) {
			return console.error (
				'ERROR! Autofixing is currently not supported in watch mode.'
			);
		}
	}

	var errorCount = lint (userConfig, { file: cli.file, dir: cli.dir, stdin: cli.stdin }, ignore, errorReporter);

	if (cli.hot) {

		var spy = chokidar.watch (CWD);

		spy.on ('change', function () {
			console.log('\x1Bc'); // clear the console
			console.log ('File change detected. Start linting.\n');
			lint (userConfig, { file: cli.file, dir: cli.dir }, ignore, errorReporter);	//lint on subsequent changes (hot)
			console.log ('Linting complete. Watching for file changes.\n');
		});

	} else {

		if (errorCount > 0) {
			process.exit(errorCodes.ERRORS_FOUND);
		}

	}
}

module.exports = {
	execute: execute
};
<|endoftext|>


<commit_before>var	chai = require('chai'),
    expect = chai.expect,
	sequencer   = require("chai-sequencer"),
	EventEmitter = require('events'),
    machinegun = require('../index.js');

chai.use(sequencer);

describe('machinegun', function () {
	it("should run tasks sequentially by default", function(done) {
		var mg = machinegun();
		[1,2,3].forEach(function (i) {
			mg.load(function (cb) {
				mg.emit('start-' + i);
				process.nextTick(function () {
					mg.emit('finish-' + i);
					cb();
				});
			});
		});

		mg.on('empty', function () {
			expect(mg).to
				.emit('start-1')
				.next('finish-1')
				.next('start-2')
				.next('finish-2')
				.next('start-3')
				.next('finish-3')
				.last('empty');
			done();
		});
	});

	it("should run tasks in parallel if asked to", function(done) {
		var mg = machinegun({barrels: 2});
		[1,2,3].forEach(function (i) {
			mg.load(function (cb) {
				mg.emit('start-' + i);
				process.nextTick(function () {
					mg.emit('finish-' + i);
					cb();
				});
			});
		});
		mg.on('empty', function () {
			expect(mg).to
				.emit('start-1')
				.next('start-2');
			done();
		});
	});

	it("should fail on error", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function (cb) {
			cb('err');
		});
		mg.on('giveUp', done);
	});

	it("should accept a promise instead of a callback", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.resolve();
		});
		mg.on('empty', done);
	});

	it("should emit an error if a promise rejects", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.reject();
		});
		mg.on('error', function() {done();});
	});

	it("should give up if a promise rejects", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function () {
			return Promise.reject();
		});
		mg.on('giveUp', done);
	});

	it("should not start firing if 'fireImmediately' is set to false", function(done) {
		var mg = machinegun({fireImmediately: false});
		mg.load(function (cb) {
			mg.emit('start');
			process.nextTick(function () {
				mg.emit('finish');
				cb();
			});
		});
		process.nextTick(function () {
			mg.emit('initialised');
			mg.fire();
		});
		mg.on('empty', function () {
			expect(mg).to
				.emit('initialised')
				.next('fire')
				.next('start')
				.next('finish')
				.last('empty');
			done();
		});
	});

	it("should return a promise, which resolves on success", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.resolve();
		});
		mg.promise().then(done);
	});

	it("should return a promise, which rejects on failure", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function () {
			return Promise.reject();
		});
		mg.promise().catch(done);
	});
});<commit_msg>Tests<commit_after>var	chai = require('chai'),
    expect = chai.expect,
	sequencer   = require("chai-sequencer"),
	EventEmitter = require('events'),
    machinegun = require('../index.js');

chai.use(sequencer);

describe('machinegun', function () {
	it("should run tasks sequentially by default", function(done) {
		var mg = machinegun();
		[1,2,3].forEach(function (i) {
			mg.load(function (cb) {
				mg.emit('start-' + i);
				process.nextTick(function () {
					mg.emit('finish-' + i);
					cb();
				});
			});
		});

		mg.on('empty', function () {
			expect(mg).to
				.emit('start-1')
				.next('finish-1')
				.next('start-2')
				.next('finish-2')
				.next('start-3')
				.next('finish-3')
				.last('empty');
			done();
		});
	});

	it("should run tasks in parallel if asked to", function(done) {
		var mg = machinegun({barrels: 2});
		[1,2,3].forEach(function (i) {
			mg.load(function (cb) {
				mg.emit('start-' + i);
				process.nextTick(function () {
					mg.emit('finish-' + i);
					cb();
				});
			});
		});
		mg.on('empty', function () {
			expect(mg).to
				.emit('start-1')
				.next('start-2');
			done();
		});
	});

	it("should fail on error if 'giveupOnError' is truthy", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function (cb) {
			cb('error');
		});
		mg.on('giveUp', (value) => {
			expect(value).to.equal('error');
			done();
		});
	});

	it("should cease fire after emptying if 'ceaseOnEmpty' is truthy", function(done) {
		var mg = machinegun({ceaseOnEmpty: true});
		mg.load(function (cb) {
			cb();
		});
		mg.on('ceaseFire', function () {
			expect(mg).to
				.emit('empty')
				.next('ceaseFire');
			done();
		});
	});

	it("should accept a promise instead of a callback", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.resolve();
		});
		mg.on('empty', done);
	});

	it("should emit an error if a promise rejects", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.reject('error');
		});
		mg.on('error', (value) => {
			expect(value).to.equal('error');
			done();
		});
	});

	it("should give up if a promise rejects", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function () {
			return Promise.reject('error');
		});
		mg.on('giveUp', (value) => {
			expect(value).to.equal('error');
			done();
		});
	});

	it("should not start firing if 'fireImmediately' is set to false", function(done) {
		var mg = machinegun({fireImmediately: false});
		mg.load(function (cb) {
			mg.emit('start');
			process.nextTick(function () {
				mg.emit('finish');
				cb();
			});
		});
		process.nextTick(function () {
			mg.emit('initialised');
			mg.fire();
		});
		mg.on('empty', function () {
			expect(mg).to
				.emit('initialised')
				.next('fire')
				.next('start')
				.next('finish')
				.last('empty');
			done();
		});
	});

	it("should return a promise, which resolves on success", function(done) {
		var mg = machinegun();
		mg.load(function () {
			return Promise.resolve();
		});
		mg.promise('success').then((value) => {
			expect(value).to.equal('success');
			done();
		});
	});

	it("should return a promise, which rejects on failure", function(done) {
		var mg = machinegun({giveupOnError: true});
		mg.load(function () {
			return Promise.reject('error');
		});
		mg.promise().catch((value) => {
			expect(value).to.equal('error');
			done();
		});
	});
});<|endoftext|>


<commit_before>// If you want to make a custom template engine,
//
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
// [2] Override 'renderTemplateSource', supplying a function with this signature:
//
//        function (templateSource, bindingContext, options) {
//            // - templateSource.text() is the text of the template you should render
//            // - bindingContext.$data is the data you should pass into the template
//            //   - you might also want to make bindingContext.$parent, bindingContext.$parents,
//            //     and bindingContext.$root available in the template too
//            // - options gives you access to any other properties set on "data-bind: { template: options }"
//            //
//            // Return value: an array of DOM nodes
//        }
//
// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
//
//        function (script) {
//            // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
//            //               For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
//        }
//
//     This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
//     If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
//     and then you don't need to override 'createJavaScriptEvaluatorBlock'.

ko.templateEngine = function () { };

ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
    throw new Error("Override renderTemplateSource");
};

ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
    throw new Error("Override createJavaScriptEvaluatorBlock");
};

ko.templateEngine.prototype['makeTemplateSource'] = function(template, options) {
    // Named template
    if (typeof template == "string") {
        var doc = (options && options['document']) || document;
        var elem = doc.getElementById(template);
        if (!elem)
            throw new Error("Cannot find template with ID " + template);
        return new ko.templateSources.domElement(elem);
    } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
        // Anonymous template
        return new ko.templateSources.anonymousTemplate(template);
    } else
        throw new Error("Unknown template type: " + template);
};

ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options) {
    var templateSource = this['makeTemplateSource'](template, options);
    return this['renderTemplateSource'](templateSource, bindingContext, options);
};

ko.templateEngine.prototype['isTemplateRewritten'] = function (template, options) {
    // Skip rewriting if requested
    if (this['allowTemplateRewriting'] === false)
        return true;

    // Perf optimisation - see below
    if (isKnownRewrittenTemplate(this, template, options && options['document']))
        return true;

    return this['makeTemplateSource'](template, options)['data']("isRewritten");
};

ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, options) {
    var templateSource = this['makeTemplateSource'](template, options);
    var rewritten = rewriterCallback(templateSource['text']());
    templateSource['text'](rewritten);
    templateSource['data']("isRewritten", true);

    // Perf optimisation - for named templates, track which ones have been rewritten so we can
    // answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
    if (typeof template == "string")
        isKnownRewrittenTemplate(this, template, options && options['document'], true);
};

// Records which templates, in each document, are rewritten
// Underlying storage is on the doc, to avoid leaking memory by holding references to docs
var isKnownRewrittenDomDataKey = "__ko_knownrewritten__";
function isKnownRewrittenTemplate(templateEngine, templateName, doc, value) { // Call with 2 args to read, 3 args to write
    doc = doc || document;
    var knownRewrittenCacheForDoc = ko.utils.domData.get(doc, isKnownRewrittenDomDataKey);
    if (!knownRewrittenCacheForDoc) {
        knownRewrittenCacheForDoc = {};
        ko.utils.domData.set(doc, isKnownRewrittenDomDataKey, knownRewrittenCacheForDoc);
    }
    knownRewrittenCacheForDoc[templateEngine] = knownRewrittenCacheForDoc[templateEngine] || {};

    if (arguments.length > 2)
        knownRewrittenCacheForDoc[templateEngine][templateName] = value;
    return knownRewrittenCacheForDoc[templateEngine][templateName];
}

ko.exportSymbol('templateEngine', ko.templateEngine);
<commit_msg>Fix bug whereby I tried to use an object (not a string) as a hash key. (Bring on EcmaScript 5...)<commit_after>// If you want to make a custom template engine,
//
// [1] Inherit from this class (like ko.nativeTemplateEngine does)
// [2] Override 'renderTemplateSource', supplying a function with this signature:
//
//        function (templateSource, bindingContext, options) {
//            // - templateSource.text() is the text of the template you should render
//            // - bindingContext.$data is the data you should pass into the template
//            //   - you might also want to make bindingContext.$parent, bindingContext.$parents,
//            //     and bindingContext.$root available in the template too
//            // - options gives you access to any other properties set on "data-bind: { template: options }"
//            //
//            // Return value: an array of DOM nodes
//        }
//
// [3] Override 'createJavaScriptEvaluatorBlock', supplying a function with this signature:
//
//        function (script) {
//            // Return value: Whatever syntax means "Evaluate the JavaScript statement 'script' and output the result"
//            //               For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
//        }
//
//     This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
//     If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
//     and then you don't need to override 'createJavaScriptEvaluatorBlock'.

ko.templateEngine = function () { };

ko.templateEngine.prototype['renderTemplateSource'] = function (templateSource, bindingContext, options) {
    throw new Error("Override renderTemplateSource");
};

ko.templateEngine.prototype['createJavaScriptEvaluatorBlock'] = function (script) {
    throw new Error("Override createJavaScriptEvaluatorBlock");
};

ko.templateEngine.prototype['makeTemplateSource'] = function(template, options) {
    // Named template
    if (typeof template == "string") {
        var doc = (options && options['document']) || document;
        var elem = doc.getElementById(template);
        if (!elem)
            throw new Error("Cannot find template with ID " + template);
        return new ko.templateSources.domElement(elem);
    } else if ((template.nodeType == 1) || (template.nodeType == 8)) {
        // Anonymous template
        return new ko.templateSources.anonymousTemplate(template);
    } else
        throw new Error("Unknown template type: " + template);
};

ko.templateEngine.prototype['renderTemplate'] = function (template, bindingContext, options) {
    var templateSource = this['makeTemplateSource'](template, options);
    return this['renderTemplateSource'](templateSource, bindingContext, options);
};

ko.templateEngine.prototype['isTemplateRewritten'] = function (template, options) {
    // Skip rewriting if requested
    if (this['allowTemplateRewriting'] === false)
        return true;

    // Perf optimisation - see below
    if (isKnownRewrittenTemplate(this, template, options && options['document']))
        return true;

    return this['makeTemplateSource'](template, options)['data']("isRewritten");
};

ko.templateEngine.prototype['rewriteTemplate'] = function (template, rewriterCallback, options) {
    var templateSource = this['makeTemplateSource'](template, options);
    var rewritten = rewriterCallback(templateSource['text']());
    templateSource['text'](rewritten);
    templateSource['data']("isRewritten", true);

    // Perf optimisation - for named templates, track which ones have been rewritten so we can
    // answer 'isTemplateRewritten' *without* having to use getElementById (which is slow on IE < 8)
    if (typeof template == "string")
        isKnownRewrittenTemplate(this, template, options && options['document'], true);
};

// Records which templates, in each document, are rewritten
// Underlying storage is on the doc, to avoid leaking memory by holding references to docs
var isKnownRewrittenDomDataKey = "__ko_knownrewritten__", templateEngineLastUniqueId = 0;
function isKnownRewrittenTemplate(templateEngine, templateName, doc, value) { // Call with 3 args to read, 4 args to write
    // Give unique serializable ID so we can give it a named entry in the cache
    var templateEngineId = templateEngine.instanceId;
    if (!templateEngineId)
        templateEngineId = templateEngine.instanceId = ++templateEngineLastUniqueId;

    doc = doc || document;
    var knownRewrittenCacheForDoc = ko.utils.domData.get(doc, isKnownRewrittenDomDataKey);
    if (!knownRewrittenCacheForDoc) {
        knownRewrittenCacheForDoc = {};
        ko.utils.domData.set(doc, isKnownRewrittenDomDataKey, knownRewrittenCacheForDoc);
    }
    knownRewrittenCacheForDoc[templateEngineId] = knownRewrittenCacheForDoc[templateEngineId] || {};

    if (arguments.length > 3)
        knownRewrittenCacheForDoc[templateEngineId][templateName] = value;
    return knownRewrittenCacheForDoc[templateEngineId][templateName];
}

ko.exportSymbol('templateEngine', ko.templateEngine);
<|endoftext|>


<commit_before><commit_msg>toString 末尾少了 __<commit_after><|endoftext|>


<commit_before>/*jslint node: true, nomen:true*/
"use strict";

var _ = require("underscore");
var OpenCV = require("opencv");
var Detector = require("./detector");
var path = require('path');

var hc_path = path.join( path.dirname(require.resolve("opencv")), '..', 'data');
var cascades = {
    "face": path.join(hc_path, "haarcascade_frontalface_alt2.xml") ,
    "mouth": path.join(hc_path, "haarcascade_mcs_mouth.xml") ,
    "nose": path.join(hc_path, "haarcascade_mcs_nose.xml") ,
    "eyeLeft": path.join(hc_path, "haarcascade_mcs_lefteye.xml") ,
    "eyeRight": path.join(hc_path, "haarcascade_mcs_righteye.xml")
};

var Faced = function () {
    var element;

    this.cascades = {};

    _.each(cascades, function (path, element) {
        this.cascades[element] = new OpenCV.CascadeClassifier(path);
    }, this);
};

Faced.prototype.detect = function (path, fn, context) {
    if (!this.cascades) {
        throw new Error("Faced has been destroyed");
    }

    OpenCV.readImage(path, _.bind(function (err, img) {
        var detector, size;

        if (err || typeof img !== "object") {
            return fn.call(context, undefined, undefined, path);
        }

        size = img.size();

        if (size[0] === 0 || size[1] === 0) {
            return fn.call(context, undefined, undefined, path);
        }

        detector = new Detector(this.cascades);
        detector.run(img, function (faces) {
            fn.call(context, faces, img, path);
        });
    }, this));
};

Faced.prototype.destroy = function () {
    delete this.cascades;
}

module.exports = Faced;<commit_msg>Change indentation to spaces<commit_after>/*jslint node: true, nomen:true*/
"use strict";

var _ = require("underscore");
var OpenCV = require("opencv");
var Detector = require("./detector");
var path = require('path');

var hc_path = path.join(path.dirname(require.resolve("opencv")), '..', 'data');
var cascades = {
    "face": path.join(hc_path, "haarcascade_frontalface_alt2.xml"),
    "mouth": path.join(hc_path, "haarcascade_mcs_mouth.xml"),
    "nose": path.join(hc_path, "haarcascade_mcs_nose.xml"),
    "eyeLeft": path.join(hc_path, "haarcascade_mcs_lefteye.xml"),
    "eyeRight": path.join(hc_path, "haarcascade_mcs_righteye.xml")
};

var Faced = function () {
    var element;

    this.cascades = {};

    _.each(cascades, function (path, element) {
        this.cascades[element] = new OpenCV.CascadeClassifier(path);
    }, this);
};

Faced.prototype.detect = function (path, fn, context) {
    if (!this.cascades) {
        throw new Error("Faced has been destroyed");
    }

    OpenCV.readImage(path, _.bind(function (err, img) {
        var detector, size;

        if (err || typeof img !== "object") {
            return fn.call(context, undefined, undefined, path);
        }

        size = img.size();

        if (size[0] === 0 || size[1] === 0) {
            return fn.call(context, undefined, undefined, path);
        }

        detector = new Detector(this.cascades);
        detector.run(img, function (faces) {
            fn.call(context, faces, img, path);
        });
    }, this));
};

Faced.prototype.destroy = function () {
    delete this.cascades;
}

module.exports = Faced;<|endoftext|>


<commit_before>define([
    'jquery',
    'underscore',
    'backbone',
    'app',
    'text!templates/testsuites/confirmDeleteTestSuiteTemplate.html'
], function($, _, Backbone, app, confirmDeleteTestSuiteTemplate) {

    var ConfirmDeleteTestSuiteView = Backbone.View.extend({

        initialize: function() {
            _.bindAll(this, 'render', 'doDelete');
        },

        events: {
            'click #remove-testsuite-btn': 'doDelete'
        },

        render: function(options) {
            $('.item').removeClass('active');
            $('.item a[href="#/specification"]').parent().addClass('active');
            var self = this;
            this.model = options.model;
            this.projectId = options.project_id;
            this.model.fetch({
                success: function(testsuite) {
                    var data = {
                        testsuite: testsuite,
                        _: _
                    }
                    var compiledTemplate = _.template(confirmDeleteTestSuiteTemplate, data);
                    self.$el.html(compiledTemplate);
                },
                error: function() {
                    app.showAlert('Failed to delete Test Suite', 'Error fetching test suite!', 'error');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                }
            });
        },

        doDelete: function(event) {
            event.preventDefault();
            event.stopPropagation();

            var self = this;
            this.model.destroy({
                wait: true,
                success: function(mod, res) {
                    app.showAlert('Success!', 'Test Suite deleted!', 'success');
                    Backbone.trigger('nestor:navigationtree_changed');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                },
                error: function(model, response, options) {
                    var message = _.has(response, 'statusText') ? response.statusText : 'Unknown error!';
                    if (
                        _.has(response, 'responseJSON') &&
                        _.has(response.responseJSON, 'name') &&
                        _.has(response.responseJSON.name, 'length') &&
                        response.responseJSON.name.length > 0
                    ) {
                        message = response.responseJSON.name[0];
                    }
                    app.showAlert('Failed to delete Project', message, 'error');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                }
            });
        }

    });

    return ConfirmDeleteTestSuiteView;

});<commit_msg>Fix message text<commit_after>define([
    'jquery',
    'underscore',
    'backbone',
    'app',
    'text!templates/testsuites/confirmDeleteTestSuiteTemplate.html'
], function($, _, Backbone, app, confirmDeleteTestSuiteTemplate) {

    var ConfirmDeleteTestSuiteView = Backbone.View.extend({

        initialize: function() {
            _.bindAll(this, 'render', 'doDelete');
        },

        events: {
            'click #remove-testsuite-btn': 'doDelete'
        },

        render: function(options) {
            $('.item').removeClass('active');
            $('.item a[href="#/specification"]').parent().addClass('active');
            var self = this;
            this.model = options.model;
            this.projectId = options.project_id;
            this.model.fetch({
                success: function(testsuite) {
                    var data = {
                        testsuite: testsuite,
                        _: _
                    }
                    var compiledTemplate = _.template(confirmDeleteTestSuiteTemplate, data);
                    self.$el.html(compiledTemplate);
                },
                error: function() {
                    app.showAlert('Failed to delete Test Suite', 'Error fetching test suite!', 'error');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                }
            });
        },

        doDelete: function(event) {
            event.preventDefault();
            event.stopPropagation();

            var self = this;
            this.model.destroy({
                wait: true,
                success: function(mod, res) {
                    app.showAlert('Success!', 'Test Suite deleted!', 'success');
                    Backbone.trigger('nestor:navigationtree_changed');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                },
                error: function(model, response, options) {
                    var message = _.has(response, 'statusText') ? response.statusText : 'Unknown error!';
                    if (
                        _.has(response, 'responseJSON') &&
                        _.has(response.responseJSON, 'name') &&
                        _.has(response.responseJSON.name, 'length') &&
                        response.responseJSON.name.length > 0
                    ) {
                        message = response.responseJSON.name[0];
                    }
                    app.showAlert('Failed to delete Test Suite', message, 'error');
                    Backbone.history.navigate("#/specification", {
                        trigger: false
                    });
                }
            });
        }

    });

    return ConfirmDeleteTestSuiteView;

});
<|endoftext|>


<commit_before><commit_msg>很不幸 instanceof String 有时无效<commit_after><|endoftext|>


<commit_before>var validBrowsers = ['chrome', 'firefox', 'phantomjs'];

function help() {
  console.log(' -u the URL to test');
  console.log(' -f result file name');
  console.log(' --harFile HAR file name');
  console.log(' -b the browser to use, use one of the following:' + validBrowsers);
  console.log(' -n the number of runs [3]');
  console.log(' --userAgent the user agent String');
  console.log(' -w the screensize wXh');
  console.log(' --scriptPath the path to an alternative script folder');
  console.log(' --headers request headers {name:value,name2:value2}');
  console.log(' --basicAuth {username:$NAME, password:$password}');
  console.log(' --useProxy use MobProxy or not');
  console.log(' --limit the speed: {downstreamKbps: $X, upstreamKbps: $Y, latency: $Z}');
}

exports.verifyInput = function verifyInput(options) {
  if (options.help) {
    help();
    process.exit(0);
  }

  if (!options.url) {
    console.error('Missing url');
    help();
    process.exit(255);
  }
  if (!options.browser) {
    options.browser = 'chrome';
  } else if (validBrowsers.indexOf(options.browser) < 0) {
    console.error('Browsertime dont support ' + options.browser);
    help();
    process.exit(255);
  }
};<commit_msg>more & better help<commit_after>var validBrowsers = ['chrome', 'firefox', 'phantomjs'];

function help() {
  console.log(' -u the URL to test');
  console.log(
    ' -f Output the result as a file, give the name of the file. If no filename is given, the name will be the domain of the url'
  );
  console.log(' --harFile the HAR file name. If no filename, the name will be $domain.har');
  console.log(' -b The browser to use. Supported values are:' + validBrowsers + ', default being chrome.');
  console.log(' -n the number of times to run the test, default being 3');
  console.log(
    ' --userAgent Set the user agent. Default is the one by the browser you use. Only works with Chrome and Firefox');
  console.log(' -w The size of the browser window: <width>x<height>, e.g. 400x600. Only works with Chrome and Firefox');
  console.log(' --scriptPath the path to an extra script folder. ');
  console.log(' --headers set request headers by setting a JSON of the format {name:value,name2:value2}');
  console.log(' --basicAuth {username:$NAME, password:$password}');
  console.log(' --useProxy use MobProxy or not. Use it to get a HAR file.');
  console.log(
    ' --limit the speed by simulating connection types by setting a JSON like {downstreamKbps: $X, upstreamKbps: $Y, latency: $Z}'
  );
}

exports.verifyInput = function verifyInput(options) {
  if (options.help) {
    help();
    process.exit(0);
  }

  if (!options.url) {
    console.error('Missing url');
    help();
    process.exit(255);
  }
  if (!options.browser) {
    options.browser = 'chrome';
  } else if (validBrowsers.indexOf(options.browser) < 0) {
    console.error('Browsertime dont support ' + options.browser);
    help();
    process.exit(255);
  }
};<|endoftext|>


<commit_before>var http = require("http"),
    https = require("https"),
    urllib = require("url"),
    utillib = require("util"),
    zlib = require('zlib'),
    dns = require('dns'),
    Stream = require("stream").Stream,
    CookieJar = require("./cookiejar").CookieJar,
    encodinglib = require("encoding");

/* LongBM: By Default maxSockets = 5. We need to incre to 200 */

http.globalAgent.maxSockets = 200;

/*

maxRedirects : 10
disableRedirects : false
headers: {}
maxResponseLength : Infinity
method: GET
payload: str
disableGzip: false
asyncDnsLoookup: false
timeout: null
agent: undefined

cookies: ['name=val']

outputEncoding: utf-8 | latin-1 jne

disableDecoding: false
*/

//TODO:
// - meta infosse faili nimi, juhul kui tegu downloadiga
// - wrapper fetch(url, callback)
// - wrapperile options.setEncoding = true | "UTF-8"
// - dokumentatsioon

exports.FetchStream = FetchStream;
exports.CookieJar = CookieJar;

exports.fetchUrl = fetchUrl;

function FetchStream(url, options){
    Stream.call(this);

    options = options || {};

    this.url = url;
    if(!this.url){
        return this.emit("error", new Error("url not defined"));
    }

    this.userAgent = options.userAgent || "FetchStream";

    this._redirect_count = 0;

    this.options = options || {};
    this.normalizeOptions();

    this.runStream(url);
}
utillib.inherits(FetchStream, Stream);

FetchStream.prototype.normalizeOptions = function(){

    // cookiejar
    this.cookieJar = this.options.cookieJar || new CookieJar();

    // default redirects - 10
    // if disableRedirect is set, then 0
    if(!this.options.disableRedirect && typeof this.options.maxRedirects != "number" &&
      !(this.options.maxRedirects instanceof Number)){
        this.options.maxRedirects = 10;
    }else if(this.options.disableRedirects){
        this.options.maxRedirects = 0;
    }

    // normalize header keys
    // HTTP and HTTPS takes in key names in case insensitive but to find
    // an exact value from an object key name needs to be case sensitive
    // so we're just lowercasing all input keys
    this.options.headers = this.options.headers || {};

    var keys = Object.keys(this.options.headers),
        newheaders = {},
        i;

    for(i=keys.length-1; i>=0; i--){
        newheaders[keys[i].toLowerCase().trim()] = this.options.headers[keys[i]];
    }

    this.options.headers = newheaders;

    if(!this.options.headers["user-agent"]){
        this.options.headers["user-agent"] = this.userAgent;
    }

    if(!this.options.headers["pragma"]){
        this.options.headers["pragma"] = "no-cache";
    }

    if(!this.options.headers["cache-control"]){
        this.options.headers["cache-control"] = "no-cache";
    }

    if(!this.options.disableGzip){
        this.options.headers['accept-encoding'] = 'gzip, deflate';
    }else{
        delete this.options.headers['accept-encoding'];
    }

    // max length for the response,
    // if not set, default is Infinity
    if(!this.options.maxResponseLength){
        this.options.maxResponseLength = Infinity;
    }

    // method:
    // defaults to GET, or when payload present to POST
    if(!this.options.method){
        this.options.method = this.options.payload || this.options.payloadSize?"POST":"GET";
    }

    // set cookies
    // takes full cookie definition strings as params
    if(this.options.cookies){
        for(var i=0; i<this.options.cookies.length; i++){
            this.cookieJar.setCookie(this.options.cookies[i], this.url);
        }
    }

    // rejectUnauthorized
    if (typeof this.options.rejectUnauthorized === 'undefined') {
      this.options.rejectUnauthorized = true;
    }
}

FetchStream.prototype.parseUrl = function(url){
    var urlparts = urllib.parse(url, false, true),
        transport,
        urloptions = {
            host: this.options.hostname || urlparts.hostname || urlparts.host,
            port: this.options.port || urlparts.port,
            path: url,
            method: this.options.method,
            rejectUnauthorized: this.options.rejectUnauthorized
        };

    if("agent" in this.options){
        urloptions.agent = this.options.agent;
    }

    if (this.options.hostname) {
        transport = http;
    } else {
        switch(urlparts.protocol){
            case "https:":
                transport = https;
                break;
            case "http:":
            default:
                transport = http;
                break;
        }
    }

    if(!urloptions.port){
        switch(urlparts.protocol){
            case "https:":
                urloptions.port = 443;
                break;
            case "http:":
            default:
                urloptions.port = 80;
                break;
        }
    }

    urloptions.headers = this.options.headers;

    return {
        urloptions: urloptions,
        transport: transport
    }
}

FetchStream.prototype.setEncoding = function(encoding){
    this.options.encoding = encoding;
}

FetchStream.prototype.absoluteUrl = function(url, base){

    var target_url = urllib.parse(url, false, true),
        base_url = urllib.parse(base || "", false, true),
        base_path, target_path, final_path;

    // if protocol is set, then it's good to go
    if(target_url.protocol){
        return url;
    }

    // the url might be int the form of "//www.example.com" with leading slashes -
    // the protocol from the base url must be used, defaults to http
    if(target_url.hostname){
        return (base_url.protocol || "http:") + (url.substr(0,2)!="//"?"//":"") + url;
    }

    // this is absolute path for relative domain
    if((target_url.pathname || "/").substr(0,1)=="/"){
        return (base_url.protocol || "http:") + "//" + (base_url.hostname || "") + url;
    }

    // relative path
    // remove also .. and . directory references
    base_path = (base_url.pathname || "/").split("/");
    base_path.pop(); // ditch the last element, empty for dir or a file name

    target_path = (target_url.pathname || "/").split("/");

    target_path = base_path.concat(target_path);
    final_path = [];

    target_path.forEach(function(dir){
        if(dir=="."){
            return;
        }

        if(dir==".."){
            final_path.pop();
            return;
        }

        if(dir){
            final_path.push(dir);
        }
    });

    return (base_url.protocol || "http:") + "//" + (base_url.hostname || "") + "/" +
        final_path.join("/") + (target_url.search || "");
}

FetchStream.prototype.runStream = function(url){
    var url_data = this.parseUrl(url),
        cookies = this.cookieJar.getCookies(url);

    if(cookies){
        url_data.urloptions.headers.cookie = cookies;
    }else{
        delete url_data.urloptions.headers.cookie;
    }

    if(this.options.payload){
        url_data.urloptions.headers['content-length'] = Buffer.byteLength(this.options.payload || "","utf-8");
    }

    if(this.options.payloadSize){
        url_data.urloptions.headers['content-length'] = this.options.payloadSize;
    }

    if (this.options.asyncDnsLoookup) {
        var dnsCallback = (function (err, adresses){
            if (err) {
                this.emit("error", err);
                return;
            }

            url_data.urloptions.headers['host'] = url_data.urloptions.hostname || url_data.urloptions.host;
            url_data.urloptions.hostname = adresses[0];
            url_data.urloptions.host = url_data.urloptions.headers['host'] + (url_data.urloptions.port? ':' + url_data.urloptions.port: '');

            this._runStream(url_data, url);
        }).bind(this);

        dns.resolve4(url_data.urloptions.host, dnsCallback);
    } else {
        this._runStream(url_data, url);
    }
}

FetchStream.prototype._runStream = function(url_data, url){

    var req = url_data.transport.request(url_data.urloptions, (function(res) {

        // catch new cookies before potential redirect
        if(Array.isArray(res.headers['set-cookie'])){
            for(var i=0; i<res.headers['set-cookie'].length; i++){
                this.cookieJar.setCookie(res.headers['set-cookie'][i], url)
            }
        }

        if([301, 302, 303, 307, 308].indexOf(res.statusCode)>=0){
            if(!this.options.disableRedirects && this.options.maxRedirects>this._redirect_count && res.headers.location){
                this._redirect_count++;
                this.runStream(this.absoluteUrl(res.headers.location, url));
                return;
            }
        }

        this.meta = {
            status: res.statusCode,
            responseHeaders: res.headers,
            finalUrl: url,
            redirectCount: this._redirect_count,
            cookieJar: this.cookieJar
        }

        var curlen = 0,
            maxlen,

            receive = (function(chunk){

                if(curlen + chunk.length > this.options.maxResponseLength){
                    maxlen = this.options.maxResponseLength - curlen;
                }else{
                    maxlen = chunk.length;
                }
                if(maxlen<=0)return;

                curlen += Math.min(maxlen, chunk.length);

                if(maxlen>=chunk.length){
                    if(this.options.encoding){
                        this.emit("data", chunk.toString(this.options.encoding));
                    }else{
                        this.emit("data", chunk);
                    }
                }else{
                    if(this.options.encoding){
                        this.emit("data", chunk.slice(0, maxlen).toString(this.options.encoding));
                    }else{
                        this.emit("data", chunk.slice(0, maxlen));
                    }
                }
            }).bind(this),

            error = (function(e){
                this.emit("error", e);
            }).bind(this),

            end = (function(){
                this.emit("end");
            }).bind(this),

            unpack = (function(type, res){
                var z = zlib["create"+type]();
                z.on("data", receive);
                z.on("error", error);
                z.on("end", end);
                res.pipe(z);
            }).bind(this);

        this.emit("meta", this.meta);

        if(res.headers['content-encoding']){
            switch(res.headers['content-encoding'].toLowerCase().trim()){
                case "gzip":
                    return unpack("Gunzip", res);
                case "deflate":
                    return unpack("InflateRaw", res);
            }
        }

        res.on('data', receive);
        res.on('end', end);

    }).bind(this));

    req.on('error', (function(e){
        this.emit("error", e);
    }).bind(this));

    if (this.options.timeout) {
        req.setTimeout(this.options.timeout, req.abort.bind(req));
    }

    if(this.options.payload){
        req.end(this.options.payload);
    }else if(this.options.payloadStream){
        this.options.payloadStream.pipe(req);
        this.options.payloadStream.resume();
    }else{
        req.end();
    }
}

function fetchUrl(url, options, callback){
    if(!callback && typeof options=="function"){
        callback = options;
        options = undefined;
    }
    options = options || {};

    var fetchstream = new FetchStream(url, options),
        response_data, chunks = [], length=0, curpos=0, buffer,
        content_type,
        callbackFired = false;

    fetchstream.on("meta", function(meta){
        response_data = meta;
        content_type = _parseContentType(meta.responseHeaders['content-type']);
    });

    fetchstream.on("data", function(chunk){
        if(chunk){
            chunks.push(chunk);
            length += chunk.length;
        }
    });

    fetchstream.on("error", function(error){
        if(error && error.code == 'HPE_INVALID_CONSTANT'){
            // skip invalid formatting errors
            return;
        }
        if(callbackFired){
            return;
        }
        callbackFired = true;
        callback(error);
    });

    fetchstream.on("end", function(error){
        if(callbackFired){
            return;
        }
        callbackFired = true;

        buffer = new Buffer(length);
        for(var i=0, len = chunks.length; i<len; i++){
            chunks[i].copy(buffer, curpos);
            curpos += chunks[i].length;
        }

        if(content_type.mimeType == "text/html"){
            content_type.charset = _findHTMLCharset(buffer) || content_type.charset;
        }

        content_type.charset = (options.overrideCharset || content_type.charset || "utf-8").trim().toLowerCase();

        if(!this.options.disableDecoding && !content_type.charset.match(/^utf-?8$/i)){
            buffer = encodinglib.convert(buffer, "UTF-8", content_type.charset);
        }

        if(this.options.outputEncoding){
            callback(null, response_data, buffer.toString(this.options.outputEncoding));
        }else{
            callback(null, response_data, buffer);
        }

    });
}

function _parseContentType(str){
    if(!str){
        return {};
    }
    var parts = str.split(";"),
        mimeType = parts.shift(),
        charset, chparts;

    for(var i=0, len = parts.length; i<len; i++){
        chparts = parts[i].split("=");
        if(chparts.length>1){
            if(chparts[0].trim().toLowerCase() == "charset"){
                charset = chparts[1];
            }
        }
    }

    return {
        mimeType: (mimeType || "").trim().toLowerCase(),
        charset: (charset || "UTF-8").trim().toLowerCase() // defaults to UTF-8
    }
}

function _findHTMLCharset(htmlbuffer){

    var body = htmlbuffer.toString("ascii"),
        input, meta, charset;

    if(meta = body.match(/<meta\s+http-equiv=["']content-type["'][^>]*?>/i)){
        input = meta[0];
    }

    if(input){
        charset = input.match(/charset\s?=\s?([a-zA-Z\-0-9]*);?/);
        if(charset){
            charset = (charset[1] || "").trim().toLowerCase();
        }
    }

    if(!charset && (meta = body.match(/<meta\s+charset=["'](.*?)["']/i))){
        charset = (meta[1] || "").trim().toLowerCase();
    }

    return charset;
}

<commit_msg>Fix bug path of http<commit_after>var http = require("http"),
    https = require("https"),
    urllib = require("url"),
    utillib = require("util"),
    zlib = require('zlib'),
    dns = require('dns'),
    Stream = require("stream").Stream,
    CookieJar = require("./cookiejar").CookieJar,
    encodinglib = require("encoding");

/* LongBM: By Default maxSockets = 5. We need to incre to 200 */

http.globalAgent.maxSockets = 200;

/*

maxRedirects : 10
disableRedirects : false
headers: {}
maxResponseLength : Infinity
method: GET
payload: str
disableGzip: false
asyncDnsLoookup: false
timeout: null
agent: undefined

cookies: ['name=val']

outputEncoding: utf-8 | latin-1 jne

disableDecoding: false
*/

//TODO:
// - meta infosse faili nimi, juhul kui tegu downloadiga
// - wrapper fetch(url, callback)
// - wrapperile options.setEncoding = true | "UTF-8"
// - dokumentatsioon

exports.FetchStream = FetchStream;
exports.CookieJar = CookieJar;

exports.fetchUrl = fetchUrl;

function FetchStream(url, options){
    Stream.call(this);

    options = options || {};

    this.url = url;
    if(!this.url){
        return this.emit("error", new Error("url not defined"));
    }

    this.userAgent = options.userAgent || "FetchStream";

    this._redirect_count = 0;

    this.options = options || {};
    this.normalizeOptions();

    this.runStream(url);
}
utillib.inherits(FetchStream, Stream);

FetchStream.prototype.normalizeOptions = function(){

    // cookiejar
    this.cookieJar = this.options.cookieJar || new CookieJar();

    // default redirects - 10
    // if disableRedirect is set, then 0
    if(!this.options.disableRedirect && typeof this.options.maxRedirects != "number" &&
      !(this.options.maxRedirects instanceof Number)){
        this.options.maxRedirects = 10;
    }else if(this.options.disableRedirects){
        this.options.maxRedirects = 0;
    }

    // normalize header keys
    // HTTP and HTTPS takes in key names in case insensitive but to find
    // an exact value from an object key name needs to be case sensitive
    // so we're just lowercasing all input keys
    this.options.headers = this.options.headers || {};

    var keys = Object.keys(this.options.headers),
        newheaders = {},
        i;

    for(i=keys.length-1; i>=0; i--){
        newheaders[keys[i].toLowerCase().trim()] = this.options.headers[keys[i]];
    }

    this.options.headers = newheaders;

    if(!this.options.headers["user-agent"]){
        this.options.headers["user-agent"] = this.userAgent;
    }

    if(!this.options.headers["pragma"]){
        this.options.headers["pragma"] = "no-cache";
    }

    if(!this.options.headers["cache-control"]){
        this.options.headers["cache-control"] = "no-cache";
    }

    if(!this.options.disableGzip){
        this.options.headers['accept-encoding'] = 'gzip, deflate';
    }else{
        delete this.options.headers['accept-encoding'];
    }

    // max length for the response,
    // if not set, default is Infinity
    if(!this.options.maxResponseLength){
        this.options.maxResponseLength = Infinity;
    }

    // method:
    // defaults to GET, or when payload present to POST
    if(!this.options.method){
        this.options.method = this.options.payload || this.options.payloadSize?"POST":"GET";
    }

    // set cookies
    // takes full cookie definition strings as params
    if(this.options.cookies){
        for(var i=0; i<this.options.cookies.length; i++){
            this.cookieJar.setCookie(this.options.cookies[i], this.url);
        }
    }

    // rejectUnauthorized
    if (typeof this.options.rejectUnauthorized === 'undefined') {
      this.options.rejectUnauthorized = true;
    }
}

FetchStream.prototype.parseUrl = function(url){
    var urlparts = urllib.parse(url, false, true),
        transport,
        urloptions = {
            host: this.options.hostname || urlparts.hostname || urlparts.host,
            port: this.options.port || urlparts.port,
            path: urlparts.path,
            method: this.options.method,
            rejectUnauthorized: this.options.rejectUnauthorized
        };

    if("agent" in this.options){
        urloptions.agent = this.options.agent;
    }

    if (this.options.hostname) {
        transport = http;
    } else {
        switch(urlparts.protocol){
            case "https:":
                transport = https;
                break;
            case "http:":
            default:
                transport = http;
                break;
        }
    }

    if(!urloptions.port){
        switch(urlparts.protocol){
            case "https:":
                urloptions.port = 443;
                break;
            case "http:":
            default:
                urloptions.port = 80;
                break;
        }
    }

    urloptions.headers = this.options.headers;

    return {
        urloptions: urloptions,
        transport: transport
    }
}

FetchStream.prototype.setEncoding = function(encoding){
    this.options.encoding = encoding;
}

FetchStream.prototype.absoluteUrl = function(url, base){

    var target_url = urllib.parse(url, false, true),
        base_url = urllib.parse(base || "", false, true),
        base_path, target_path, final_path;

    // if protocol is set, then it's good to go
    if(target_url.protocol){
        return url;
    }

    // the url might be int the form of "//www.example.com" with leading slashes -
    // the protocol from the base url must be used, defaults to http
    if(target_url.hostname){
        return (base_url.protocol || "http:") + (url.substr(0,2)!="//"?"//":"") + url;
    }

    // this is absolute path for relative domain
    if((target_url.pathname || "/").substr(0,1)=="/"){
        return (base_url.protocol || "http:") + "//" + (base_url.hostname || "") + url;
    }

    // relative path
    // remove also .. and . directory references
    base_path = (base_url.pathname || "/").split("/");
    base_path.pop(); // ditch the last element, empty for dir or a file name

    target_path = (target_url.pathname || "/").split("/");

    target_path = base_path.concat(target_path);
    final_path = [];

    target_path.forEach(function(dir){
        if(dir=="."){
            return;
        }

        if(dir==".."){
            final_path.pop();
            return;
        }

        if(dir){
            final_path.push(dir);
        }
    });

    return (base_url.protocol || "http:") + "//" + (base_url.hostname || "") + "/" +
        final_path.join("/") + (target_url.search || "");
}

FetchStream.prototype.runStream = function(url){
    var url_data = this.parseUrl(url),
        cookies = this.cookieJar.getCookies(url);

    if(cookies){
        url_data.urloptions.headers.cookie = cookies;
    }else{
        delete url_data.urloptions.headers.cookie;
    }

    if(this.options.payload){
        url_data.urloptions.headers['content-length'] = Buffer.byteLength(this.options.payload || "","utf-8");
    }

    if(this.options.payloadSize){
        url_data.urloptions.headers['content-length'] = this.options.payloadSize;
    }

    if (this.options.asyncDnsLoookup) {
        var dnsCallback = (function (err, adresses){
            if (err) {
                this.emit("error", err);
                return;
            }

            url_data.urloptions.headers['host'] = url_data.urloptions.hostname || url_data.urloptions.host;
            url_data.urloptions.hostname = adresses[0];
            url_data.urloptions.host = url_data.urloptions.headers['host'] + (url_data.urloptions.port? ':' + url_data.urloptions.port: '');

            this._runStream(url_data, url);
        }).bind(this);

        dns.resolve4(url_data.urloptions.host, dnsCallback);
    } else {
        this._runStream(url_data, url);
    }
}

FetchStream.prototype._runStream = function(url_data, url){

    var req = url_data.transport.request(url_data.urloptions, (function(res) {

        // catch new cookies before potential redirect
        if(Array.isArray(res.headers['set-cookie'])){
            for(var i=0; i<res.headers['set-cookie'].length; i++){
                this.cookieJar.setCookie(res.headers['set-cookie'][i], url)
            }
        }

        if([301, 302, 303, 307, 308].indexOf(res.statusCode)>=0){
            if(!this.options.disableRedirects && this.options.maxRedirects>this._redirect_count && res.headers.location){
                this._redirect_count++;
                this.runStream(this.absoluteUrl(res.headers.location, url));
                return;
            }
        }

        this.meta = {
            status: res.statusCode,
            responseHeaders: res.headers,
            finalUrl: url,
            redirectCount: this._redirect_count,
            cookieJar: this.cookieJar
        }

        var curlen = 0,
            maxlen,

            receive = (function(chunk){

                if(curlen + chunk.length > this.options.maxResponseLength){
                    maxlen = this.options.maxResponseLength - curlen;
                }else{
                    maxlen = chunk.length;
                }
                if(maxlen<=0)return;

                curlen += Math.min(maxlen, chunk.length);

                if(maxlen>=chunk.length){
                    if(this.options.encoding){
                        this.emit("data", chunk.toString(this.options.encoding));
                    }else{
                        this.emit("data", chunk);
                    }
                }else{
                    if(this.options.encoding){
                        this.emit("data", chunk.slice(0, maxlen).toString(this.options.encoding));
                    }else{
                        this.emit("data", chunk.slice(0, maxlen));
                    }
                }
            }).bind(this),

            error = (function(e){
                this.emit("error", e);
            }).bind(this),

            end = (function(){
                this.emit("end");
            }).bind(this),

            unpack = (function(type, res){
                var z = zlib["create"+type]();
                z.on("data", receive);
                z.on("error", error);
                z.on("end", end);
                res.pipe(z);
            }).bind(this);

        this.emit("meta", this.meta);

        if(res.headers['content-encoding']){
            switch(res.headers['content-encoding'].toLowerCase().trim()){
                case "gzip":
                    return unpack("Gunzip", res);
                case "deflate":
                    return unpack("InflateRaw", res);
            }
        }

        res.on('data', receive);
        res.on('end', end);

    }).bind(this));

    req.on('error', (function(e){
        this.emit("error", e);
    }).bind(this));

    if (this.options.timeout) {
        req.setTimeout(this.options.timeout, req.abort.bind(req));
    }

    if(this.options.payload){
        req.end(this.options.payload);
    }else if(this.options.payloadStream){
        this.options.payloadStream.pipe(req);
        this.options.payloadStream.resume();
    }else{
        req.end();
    }
}

function fetchUrl(url, options, callback){
    if(!callback && typeof options=="function"){
        callback = options;
        options = undefined;
    }
    options = options || {};

    var fetchstream = new FetchStream(url, options),
        response_data, chunks = [], length=0, curpos=0, buffer,
        content_type,
        callbackFired = false;

    fetchstream.on("meta", function(meta){
        response_data = meta;
        content_type = _parseContentType(meta.responseHeaders['content-type']);
    });

    fetchstream.on("data", function(chunk){
        if(chunk){
            chunks.push(chunk);
            length += chunk.length;
        }
    });

    fetchstream.on("error", function(error){
        if(error && error.code == 'HPE_INVALID_CONSTANT'){
            // skip invalid formatting errors
            return;
        }
        if(callbackFired){
            return;
        }
        callbackFired = true;
        callback(error);
    });

    fetchstream.on("end", function(error){
        if(callbackFired){
            return;
        }
        callbackFired = true;

        buffer = new Buffer(length);
        for(var i=0, len = chunks.length; i<len; i++){
            chunks[i].copy(buffer, curpos);
            curpos += chunks[i].length;
        }

        if(content_type.mimeType == "text/html"){
            content_type.charset = _findHTMLCharset(buffer) || content_type.charset;
        }

        content_type.charset = (options.overrideCharset || content_type.charset || "utf-8").trim().toLowerCase();

        if(!this.options.disableDecoding && !content_type.charset.match(/^utf-?8$/i)){
            buffer = encodinglib.convert(buffer, "UTF-8", content_type.charset);
        }

        if(this.options.outputEncoding){
            callback(null, response_data, buffer.toString(this.options.outputEncoding));
        }else{
            callback(null, response_data, buffer);
        }

    });
}

function _parseContentType(str){
    if(!str){
        return {};
    }
    var parts = str.split(";"),
        mimeType = parts.shift(),
        charset, chparts;

    for(var i=0, len = parts.length; i<len; i++){
        chparts = parts[i].split("=");
        if(chparts.length>1){
            if(chparts[0].trim().toLowerCase() == "charset"){
                charset = chparts[1];
            }
        }
    }

    return {
        mimeType: (mimeType || "").trim().toLowerCase(),
        charset: (charset || "UTF-8").trim().toLowerCase() // defaults to UTF-8
    }
}

function _findHTMLCharset(htmlbuffer){

    var body = htmlbuffer.toString("ascii"),
        input, meta, charset;

    if(meta = body.match(/<meta\s+http-equiv=["']content-type["'][^>]*?>/i)){
        input = meta[0];
    }

    if(input){
        charset = input.match(/charset\s?=\s?([a-zA-Z\-0-9]*);?/);
        if(charset){
            charset = (charset[1] || "").trim().toLowerCase();
        }
    }

    if(!charset && (meta = body.match(/<meta\s+charset=["'](.*?)["']/i))){
        charset = (meta[1] || "").trim().toLowerCase();
    }

    return charset;
}

<|endoftext|>


<commit_before>/*
 * Copyright 2012 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var optimist = require('optimist');
var colors = require('colors');
var portfinder = require('portfinder');

var TestCampaign = require('./test-campaign/campaign.js');
var TestServer = require('./test-server/server.js');
var Logger = require('./logging/logger.js');
var ConsoleLogger = require('./logging/console-logger.js');
var ConsoleReport = require('./reports/console-report.js');
var JsonConsole = require('./reports/json-console.js');
var JsonReport = require('./reports/json-report.js');
var JsonLogReport = require('./reports/json-log-report.js');

var writeReports = require('./reports/write-reports.js');
var childProcesses = require('./child-processes.js');
var exitProcess = require('./exit-process.js');
var config = require('./config.js');
var optimizeParallel = require('./optimize-parallel.js');

var run = function () {
    var opt = optimist.usage('Usage: $0 [options] [config.yml|config.json]').boolean(['flash-policy-server', 'json-console', 'help', 'server-only', 'version', 'colors', 'ignore-errors', 'ignore-failures']).string(['phantomjs-path']).describe({
        'browser': 'Path to any browser executable to execute the tests. Can be repeated multiple times.',
        'colors': 'Uses colors (disable with --no-colors).',
        'env': 'Environment configuration file. This file is available in the configuration object under env.',
        'flash-policy-port': 'Port used for the built-in Flash policy server (needs --flash-policy-server). Can be 0 for a random port.',
        'flash-policy-server': 'Whether to enable the built-in Flash policy server.',
        'heartbeats': 'Delay (in ms) for heartbeats messages sent when --json-console is enabled. Use 0 to disable them.',
        'help': 'Displays this help message and exits.',
        'ignore-errors': 'When enabled, test errors (not including failures) will not cause this process to return a non-zero value.',
        'ignore-failures': 'When enabled, test failures (anticipated errors) will not cause this process to return a non-zero value.',
        'json-console': 'When enabled, JSON objects will be sent to stdout to provide information about tests.',
        'log-level': 'Level of logging: integer from 0 (no logging) to 4 (debug).',
        'phantomjs-instances': 'Number of instances of PhantomJS to start.',
        'phantomjs-path': 'Path to PhantomJS executable.',
        'port': 'Port used for the web server. If set to 0, an available port is automatically selected.',
        'server-only': 'Only starts the web server, and configure it for the test campaign but do not start the campaign.',
        'slow-test-threshold': 'Threshold (in milliseconds) to mark long-running tests in the console report. Use 0 to disable.',
        'task-timeout': 'Timeout of a task execution in milliseconds.',
        'version': 'Displays the version number and exits.'
    }).alias({
        'j': 'json-console',
        'p': 'port'
    })['default']({
        'colors': true,
        'flash-policy-port': 0,
        'flash-policy-server': false,
        'heartbeats': 2000,
        'ignore-errors': false,
        'ignore-failures': false,
        'log-level': 3,
        'phantomjs-instances': process.env.npm_package_config_phantomjsInstances || 0,
        'phantomjs-path': 'phantomjs',
        'port': 7777,
        'slow-test-threshold': 2500,
        'task-timeout': 5 * 60 * 1000 // 5 minutes
    });
    var argv = opt.argv;
    var reports = [];
    if (argv.help) {
        opt.showHelp();
        exitProcess(0);
        return;
    }
    if (argv.version) {
        console.log(require('../package.json').version);
        exitProcess(0);
        return;
    }

    var testServer = null;
    var endProcess = function (success) {
        if (testServer) {
            testServer.close();
        }
        childProcesses.closeAll();
        exitProcess(success ? 0 : 1);
    };

    if (argv['json-console']) {
        var jsonConsole = new JsonConsole(process.stdout, argv.heartbeats);
        process.stdout.on('error', function () {
            // the process listening on this stream was closed
            endProcess();
        });
        process.stdout = process.stderr;
        console.log = console.warn;
        reports.push(jsonConsole);
    }
    colors.mode = argv.colors ? "console" : "none";

    var logger = new Logger("attester");
    logger.logLevels['.'] = argv['log-level'];
    var consoleLogger = new ConsoleLogger(process.stdout);
    consoleLogger.attach(logger);

    var configData = config.readConfig(argv._[0], argv.config, argv.env, logger);

    var campaign = new TestCampaign(configData, logger);

    var jsonReport = new JsonReport();
    reports.push(jsonReport);
    reports.push(new ConsoleReport(logger, argv['slow-test-threshold']));
    var logReports = configData['test-reports']['json-log-file'];
    var i, l;
    for (i = 0, l = logReports.length; i < l; i++) {
        reports.push(new JsonLogReport(logReports[i]));
    }

    for (i = 0, l = reports.length; i < l; i++) {
        var curReport = reports[i];
        campaign.on('result', curReport.addResult.bind(curReport));
    }

    // so that (phantomjs-instances + the number of browsers) can be > 10:
    process.stdout.setMaxListeners(256);
    process.stderr.setMaxListeners(256);

    var suggestedInstances = argv['phantomjs-instances'];
    var phantomJSinstances = optimizeParallel({
        memoryPerInstance: 60,
        maxInstances: suggestedInstances
    }, logger);
    if (phantomJSinstances > 0) {
        campaign.on('result-serverAttached', function (event) {
            var path = argv['phantomjs-path'];
            var args = event.phantomJS;
            var spawn = childProcesses.spawn;
            var checkPhantomjsSpawnExitCode = function (code) {
                if (code === 127) {
                    logger.logError("Spawn: exited with code 127. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + path + "'");
                } else if (code === 126) {
                    logger.logError("Spawn: exited with code 126. Unable to execute PhantomJS. Make sure to have proper read & execute permissions set.\nUsed command: '" + path + "'");
                } else if (code !== 0) {
                    logger.logError("Spawn: PhantomJS exited with code " + code);
                }
            };
            for (var i = 0; i < phantomJSinstances; i++) {
                var curProcess = spawn(path, args, {
                    stdio: "pipe"
                });
                curProcess.stdout.pipe(process.stdout);
                curProcess.stderr.pipe(process.stderr);
                curProcess.on('exit', checkPhantomjsSpawnExitCode);
            }
        });
    }

    var browsers = argv.browser;
    if (browsers) {
        if (!Array.isArray(browsers)) {
            browsers = [browsers];
        }
        campaign.on('result-serverAttached', function (event) {
            var args = [event.slaveURL];
            var spawn = childProcesses.spawn;
            for (var i = 0, l = browsers.length; i < l; i++) {
                var curProcess = spawn(browsers[i], args, {
                    stdio: "pipe"
                });
                curProcess.stdout.pipe(process.stdout);
                curProcess.stderr.pipe(process.stderr);
            }
        });
    }

    campaign.on('finished', function () {
        writeReports({
            test: configData['test-reports'],
            coverage: configData['coverage-reports']
        }, {
            test: jsonReport,
            coverage: campaign.getCoverageResult()
        }, function (err) {
            if (err) {
                logger.logError('An error occurred while writing reports: %s', err);
                endProcess();
                return;
            }
            var ignoreErrors = argv['ignore-errors'];
            var ignoreFailures = argv['ignore-failures'];
            var stats = jsonReport.stats;
            var success = (ignoreErrors || stats.errors === 0) && (ignoreFailures || stats.failures === 0);

            var msg = 'Tests run: ' + stats.testCases + ', ';
            var msgFailures = stats.failures ? ('Failures: ' + stats.failures + ', ').red.bold : 'Failures: 0, '.green;
            var msgErrors = stats.errors ? ('Errors: ' + stats.errors + ', ').red.bold : 'Errors: 0, '.green;
            var msgSkipped = stats.tasksIgnored ? ('Skipped: ' + stats.tasksIgnored).yellow.bold : 'Skipped: 0'.green;
            logger.logInfo(msg + msgFailures + msgErrors + msgSkipped);

            endProcess(success);
        });
    });

    campaign.init(function () {
        testServer = new TestServer({
            frozen: argv['server-only'],
            flashPolicyPort: argv['flash-policy-port'],
            flashPolicyServer: argv['flash-policy-server'],
            taskTimeout: argv['task-timeout']
        }, logger);
        testServer.server.on('error', function (error) {
            testServer.logger.logError('Web server error: %s', [error]);
            endProcess();
        });

        portfinder.basePort = argv.port;
        portfinder.getPort(function (err, port) {
            if (err) {
                logger.logError("Can't start the server: " + err);
                endProcess();
                return;
            }
            if (port != argv.port && argv.port > 0) {
                // logging error instead of a warning so it's more visible in the console
                logger.logError("Port %d unavailable; using %d instead.", [argv.port, port]);
            }
            testServer.server.listen(port, function () {
                testServer.addCampaign(campaign);
            });
        });
    });

};

module.exports = run;<commit_msg>Node 0.10 spawn error messsage<commit_after>/*
 * Copyright 2012 Amadeus s.a.s.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var optimist = require('optimist');
var colors = require('colors');
var portfinder = require('portfinder');

var TestCampaign = require('./test-campaign/campaign.js');
var TestServer = require('./test-server/server.js');
var Logger = require('./logging/logger.js');
var ConsoleLogger = require('./logging/console-logger.js');
var ConsoleReport = require('./reports/console-report.js');
var JsonConsole = require('./reports/json-console.js');
var JsonReport = require('./reports/json-report.js');
var JsonLogReport = require('./reports/json-log-report.js');

var writeReports = require('./reports/write-reports.js');
var childProcesses = require('./child-processes.js');
var exitProcess = require('./exit-process.js');
var config = require('./config.js');
var optimizeParallel = require('./optimize-parallel.js');

var run = function () {
    var opt = optimist.usage('Usage: $0 [options] [config.yml|config.json]').boolean(['flash-policy-server', 'json-console', 'help', 'server-only', 'version', 'colors', 'ignore-errors', 'ignore-failures']).string(['phantomjs-path']).describe({
        'browser': 'Path to any browser executable to execute the tests. Can be repeated multiple times.',
        'colors': 'Uses colors (disable with --no-colors).',
        'env': 'Environment configuration file. This file is available in the configuration object under env.',
        'flash-policy-port': 'Port used for the built-in Flash policy server (needs --flash-policy-server). Can be 0 for a random port.',
        'flash-policy-server': 'Whether to enable the built-in Flash policy server.',
        'heartbeats': 'Delay (in ms) for heartbeats messages sent when --json-console is enabled. Use 0 to disable them.',
        'help': 'Displays this help message and exits.',
        'ignore-errors': 'When enabled, test errors (not including failures) will not cause this process to return a non-zero value.',
        'ignore-failures': 'When enabled, test failures (anticipated errors) will not cause this process to return a non-zero value.',
        'json-console': 'When enabled, JSON objects will be sent to stdout to provide information about tests.',
        'log-level': 'Level of logging: integer from 0 (no logging) to 4 (debug).',
        'phantomjs-instances': 'Number of instances of PhantomJS to start.',
        'phantomjs-path': 'Path to PhantomJS executable.',
        'port': 'Port used for the web server. If set to 0, an available port is automatically selected.',
        'server-only': 'Only starts the web server, and configure it for the test campaign but do not start the campaign.',
        'slow-test-threshold': 'Threshold (in milliseconds) to mark long-running tests in the console report. Use 0 to disable.',
        'task-timeout': 'Timeout of a task execution in milliseconds.',
        'version': 'Displays the version number and exits.'
    }).alias({
        'j': 'json-console',
        'p': 'port'
    })['default']({
        'colors': true,
        'flash-policy-port': 0,
        'flash-policy-server': false,
        'heartbeats': 2000,
        'ignore-errors': false,
        'ignore-failures': false,
        'log-level': 3,
        'phantomjs-instances': process.env.npm_package_config_phantomjsInstances || 0,
        'phantomjs-path': 'phantomjs',
        'port': 7777,
        'slow-test-threshold': 2500,
        'task-timeout': 5 * 60 * 1000 // 5 minutes
    });
    var argv = opt.argv;
    var reports = [];
    if (argv.help) {
        opt.showHelp();
        exitProcess(0);
        return;
    }
    if (argv.version) {
        console.log(require('../package.json').version);
        exitProcess(0);
        return;
    }

    var testServer = null;
    var endProcess = function (success) {
        if (testServer) {
            testServer.close();
        }
        childProcesses.closeAll();
        exitProcess(success ? 0 : 1);
    };

    if (argv['json-console']) {
        var jsonConsole = new JsonConsole(process.stdout, argv.heartbeats);
        process.stdout.on('error', function () {
            // the process listening on this stream was closed
            endProcess();
        });
        process.stdout = process.stderr;
        console.log = console.warn;
        reports.push(jsonConsole);
    }
    colors.mode = argv.colors ? "console" : "none";

    var logger = new Logger("attester");
    logger.logLevels['.'] = argv['log-level'];
    var consoleLogger = new ConsoleLogger(process.stdout);
    consoleLogger.attach(logger);

    var configData = config.readConfig(argv._[0], argv.config, argv.env, logger);

    var campaign = new TestCampaign(configData, logger);

    var jsonReport = new JsonReport();
    reports.push(jsonReport);
    reports.push(new ConsoleReport(logger, argv['slow-test-threshold']));
    var logReports = configData['test-reports']['json-log-file'];
    var i, l;
    for (i = 0, l = logReports.length; i < l; i++) {
        reports.push(new JsonLogReport(logReports[i]));
    }

    for (i = 0, l = reports.length; i < l; i++) {
        var curReport = reports[i];
        campaign.on('result', curReport.addResult.bind(curReport));
    }

    // so that (phantomjs-instances + the number of browsers) can be > 10:
    process.stdout.setMaxListeners(256);
    process.stderr.setMaxListeners(256);

    var suggestedInstances = argv['phantomjs-instances'];
    var phantomJSinstances = optimizeParallel({
        memoryPerInstance: 60,
        maxInstances: suggestedInstances
    }, logger);
    if (phantomJSinstances > 0) {
        campaign.on('result-serverAttached', function (event) {
            var path = argv['phantomjs-path'];
            var args = event.phantomJS;
            var spawn = childProcesses.spawn;
            // BACKWARD-COMPAT node 0.8 start
            var checkPhantomjsSpawnExitCode = function (code) {
                if (code === 127) {
                    logger.logError("Spawn: exited with code 127. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + path + "'");
                } else if (code === 126) {
                    logger.logError("Spawn: exited with code 126. Unable to execute PhantomJS. Make sure to have proper read & execute permissions set.\nUsed command: '" + path + "'");
                } else if (code !== 0) {
                    logger.logError("Spawn: PhantomJS exited with code " + code);
                }
            };
            // BACKWARD-COMPAT node 0.8 end
            var onPhantomJsSpawnError = function (err) {
                if (err.code === "ENOENT") {
                    logger.logError("Spawn: exited with code ENOENT. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + path + "'");
                } else {
                    logger.logError("Unable to spawn PhantomJS; error code " + code);
                }
            };
            for (var i = 0; i < phantomJSinstances; i++) {
                var curProcess = spawn(path, args, {
                    stdio: "pipe"
                });
                curProcess.stdout.pipe(process.stdout);
                curProcess.stderr.pipe(process.stderr);
                // BACKWARD-COMPAT node 0.8 start
                curProcess.on('exit', checkPhantomjsSpawnExitCode); // node 0.8
                // BACKWARD-COMPAT node 0.8 end
                curProcess.on('error', onPhantomJsSpawnError); // node 0.10
            }
        });
    }

    var browsers = argv.browser;
    if (browsers) {
        if (!Array.isArray(browsers)) {
            browsers = [browsers];
        }
        campaign.on('result-serverAttached', function (event) {
            var args = [event.slaveURL];
            var spawn = childProcesses.spawn;
            for (var i = 0, l = browsers.length; i < l; i++) {
                var curProcess = spawn(browsers[i], args, {
                    stdio: "pipe"
                });
                curProcess.stdout.pipe(process.stdout);
                curProcess.stderr.pipe(process.stderr);
            }
        });
    }

    campaign.on('finished', function () {
        writeReports({
            test: configData['test-reports'],
            coverage: configData['coverage-reports']
        }, {
            test: jsonReport,
            coverage: campaign.getCoverageResult()
        }, function (err) {
            if (err) {
                logger.logError('An error occurred while writing reports: %s', err);
                endProcess();
                return;
            }
            var ignoreErrors = argv['ignore-errors'];
            var ignoreFailures = argv['ignore-failures'];
            var stats = jsonReport.stats;
            var success = (ignoreErrors || stats.errors === 0) && (ignoreFailures || stats.failures === 0);

            var msg = 'Tests run: ' + stats.testCases + ', ';
            var msgFailures = stats.failures ? ('Failures: ' + stats.failures + ', ').red.bold : 'Failures: 0, '.green;
            var msgErrors = stats.errors ? ('Errors: ' + stats.errors + ', ').red.bold : 'Errors: 0, '.green;
            var msgSkipped = stats.tasksIgnored ? ('Skipped: ' + stats.tasksIgnored).yellow.bold : 'Skipped: 0'.green;
            logger.logInfo(msg + msgFailures + msgErrors + msgSkipped);

            endProcess(success);
        });
    });

    campaign.init(function () {
        testServer = new TestServer({
            frozen: argv['server-only'],
            flashPolicyPort: argv['flash-policy-port'],
            flashPolicyServer: argv['flash-policy-server'],
            taskTimeout: argv['task-timeout']
        }, logger);
        testServer.server.on('error', function (error) {
            testServer.logger.logError('Web server error: %s', [error]);
            endProcess();
        });

        portfinder.basePort = argv.port;
        portfinder.getPort(function (err, port) {
            if (err) {
                logger.logError("Can't start the server: " + err);
                endProcess();
                return;
            }
            if (port != argv.port && argv.port > 0) {
                // logging error instead of a warning so it's more visible in the console
                logger.logError("Port %d unavailable; using %d instead.", [argv.port, port]);
            }
            testServer.server.listen(port, function () {
                testServer.addCampaign(campaign);
            });
        });
    });

};

module.exports = run;<|endoftext|>