<commit_before>var http = require('http');
var util = require('util');
var path = require('path');
var ca = require('../https/ca');
var Storage = require('../rules/storage');
var MAX_PORT = 60000;
var curPort = 45000;

function getServer(callback) {
	if (curPort > MAX_PORT) {
		curPort = 40000;
	}

	var server = http.createServer();
	var port = curPort++;
	var next = function() {
		getServer(callback);
	};
	server.on('error', next);
	server.listen(port, function() {
		server.removeListener('error', next);
		callback(server, port);
	});
}

module.exports = function(options, callback) {
	options.getRootCAFile = ca.getRootCAFile;
	options.createCertificate = ca.createCertificate;
	options.storage = new Storage(path.join(options.config.DATA_DIR, '.plugins', options.name));
	if (options.debugMode) {
		var cacheLogs = [];
		console.log = function() {
			var msg = util.format.apply(this, arguments);
			if (cacheLogs) {
				cacheLogs.push(msg);
			} else {
				process.sendData({
					type: 'console.log',
					message: msg
				});
			}
		};
		process.on('data', function(data) {
			if (cacheLogs && data && data.type == 'console.log' && data.status == 'ready') {
				var _cacheLogs = cacheLogs;
				cacheLogs = null;
				_cacheLogs.forEach(function(msg) {
					process.sendData({
						type: 'console.log',
						message: msg
					});
				});
			}
		});
	}

	var execPlugin = require(options.value);
	var port, uiPort, rulesPort, resRulesPort, statusPort;
	var count = 0;
	var callbackHandler = function() {
		if (--count <= 0) {
			callback(null, {
				port: port,
				uiPort: uiPort,
				rulesPort: rulesPort,
				resRulesPort: resRulesPort,
				statusPort: statusPort
			});
		}
	};

	var startServer = typeof execPlugin == 'function' ? execPlugin : (execPlugin && execPlugin.server);
	if (typeof startServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startServer(server, options);
			port = _port;
			callbackHandler();
		});
	}

	var startUIServer = execPlugin.uiServer || execPlugin.innerServer || execPlugin.internalServer;
	if (typeof startUIServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startUIServer(server, options);
			uiPort = _port;
			callbackHandler();
		});
	}

	var startRulesServer = execPlugin.rulesServer || execPlugin.reqRulesServer;
	if (typeof startRulesServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startRulesServer(server, options);
			rulesPort = _port;
			callbackHandler();
		});
	}

	var startResRulesServer = execPlugin.resRulesServer;
	if (typeof startResRulesServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startResRulesServer(server, options);
			resRulesPort = _port;
			callbackHandler();
		});
	}

	var startStatusServer = execPlugin.statusServer || execPlugin.stateServer;
	if (typeof startStatusServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startStatusServer(server, options);
			statusPort = _port;
			callbackHandler();
		});
	}

	if (!count) {
		callbackHandler();
	}
};

<commit_msg>feat: Add plugin.server<commit_after>var http = require('http');
var util = require('util');
var path = require('path');
var ca = require('../https/ca');
var Storage = require('../rules/storage');
var MAX_PORT = 60000;
var curPort = 45000;

function getServer(callback) {
	if (curPort > MAX_PORT) {
		curPort = 40000;
	}

	var server = http.createServer();
	var port = curPort++;
	var next = function() {
		getServer(callback);
	};
	server.on('error', next);
	server.listen(port, function() {
		server.removeListener('error', next);
		callback(server, port);
	});
}

module.exports = function(options, callback) {
	options.getRootCAFile = ca.getRootCAFile;
	options.createCertificate = ca.createCertificate;
	options.storage = new Storage(path.join(options.config.DATA_DIR, '.plugins', options.name));
	if (options.debugMode) {
		var cacheLogs = [];
		console.log = function() {
			var msg = util.format.apply(this, arguments);
			if (cacheLogs) {
				cacheLogs.push(msg);
			} else {
				process.sendData({
					type: 'console.log',
					message: msg
				});
			}
		};
		process.on('data', function(data) {
			if (cacheLogs && data && data.type == 'console.log' && data.status == 'ready') {
				var _cacheLogs = cacheLogs;
				cacheLogs = null;
				_cacheLogs.forEach(function(msg) {
					process.sendData({
						type: 'console.log',
						message: msg
					});
				});
			}
		});
	}

	var execPlugin = require(options.value);
	var port, uiPort, rulesPort, resRulesPort, statusPort;
	var count = 0;
	var callbackHandler = function() {
		if (--count <= 0) {
			callback(null, {
				port: port,
				uiPort: uiPort,
				rulesPort: rulesPort,
				resRulesPort: resRulesPort,
				statusPort: statusPort
			});
		}
	};

	var startServer = execPlugin.server || execPlugin;
	if (typeof startServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startServer(server, options);
			port = _port;
			callbackHandler();
		});
	}

	var startUIServer = execPlugin.uiServer || execPlugin.innerServer || execPlugin.internalServer;
	if (typeof startUIServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startUIServer(server, options);
			uiPort = _port;
			callbackHandler();
		});
	}

	var startRulesServer = execPlugin.rulesServer || execPlugin.reqRulesServer;
	if (typeof startRulesServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startRulesServer(server, options);
			rulesPort = _port;
			callbackHandler();
		});
	}

	var startResRulesServer = execPlugin.resRulesServer;
	if (typeof startResRulesServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startResRulesServer(server, options);
			resRulesPort = _port;
			callbackHandler();
		});
	}

	var startStatusServer = execPlugin.statusServer || execPlugin.stateServer;
	if (typeof startStatusServer == 'function') {
		++count;
		getServer(function(server, _port) {
			startStatusServer(server, options);
			statusPort = _port;
			callbackHandler();
		});
	}

	if (!count) {
		callbackHandler();
	}
};

<|endoftext|>


<commit_before>"use strict";
function isPromise(obj) {
    return obj && typeof obj.then === 'function';
}

function shouldFulfilled(promise) {
    if (!isPromise(promise)) {
        throw new Error("This is not promise object: " + promise);
    }
    return {
        "then": function (fn) {
            return promise.then(function (value) {
                    fn.call(promise, value);
                }, function (reason) {
                    return new promise.constructor(function (resolve, reject) {
                        reject(reason);
                    });
                }
            );
        },
        "catch": function () {
            // == Promise.reject
            return new promise.constructor(function (resolve, reject) {
                reject(new Error("Expected promise to be fulfilled so you should use `shouldFulfilled(promise).then`"));
            });
        }
    };
}
function shouldRejected(promise) {
    if (!isPromise(promise)) {
        throw new Error("This is not promise object: " + promise);
    }
    return {
        "then": function () {
            return new promise.constructor(function (resolve, reject) {
                reject(new Error("Expected promise to be rejected so you should use `shouldRejected(promise).catch`"));
            });
        },
        "catch": function (fn) {
            return promise.then(function () {
                    return new promise.constructor(function (resolve, reject) {
                        reject(new Error("Expected promise to be rejected but it was fulfilled"));
                    });
                }, function (reason) {
                    fn.call(promise, reason);
                }
            );
        }
    };
}
module.exports.shouldFulfilled = shouldFulfilled;
module.exports.shouldRejected = shouldRejected;<commit_msg>docs( promise-test-helper ) : add comment to then/catch<commit_after>"use strict";
function isPromise(obj) {
    return obj && typeof obj.then === 'function';
}

function shouldFulfilled(promise) {
    if (!isPromise(promise)) {
        throw new Error("This is not promise object: " + promise);
    }
    return {
        // when promise is rejected, throw error
        "then": function (fn) {
            return promise.then(function (value) {
                    fn.call(promise, value);
                }, function (reason) {
                    return new promise.constructor(function (resolve, reject) {
                        reject(reason);
                    });
                }
            );
        },
        "catch": function () {
            // == Promise.reject
            return new promise.constructor(function (resolve, reject) {
                reject(new Error("Expected promise to be fulfilled so you should use `shouldFulfilled(promise).then`"));
            });
        }
    };
}
function shouldRejected(promise) {
    if (!isPromise(promise)) {
        throw new Error("This is not promise object: " + promise);
    }
    return {
        "then": function () {
            return new promise.constructor(function (resolve, reject) {
                reject(new Error("Expected promise to be rejected so you should use `shouldRejected(promise).catch`"));
            });
        },
        // when promise is fulfilled, throw error
        "catch": function (fn) {
            return promise.then(function () {
                    return new promise.constructor(function (resolve, reject) {
                        reject(new Error("Expected promise to be rejected but it was fulfilled"));
                    });
                }, function (reason) {
                    fn.call(promise, reason);
                }
            );
        }
    };
}
module.exports.shouldFulfilled = shouldFulfilled;
module.exports.shouldRejected = shouldRejected;<|endoftext|>


<commit_before>var util = require('util'),
    extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
    Relation = require('./Relation'),
    HtmlRelation = require('./HtmlRelation');

function HtmlStyle(config) {
    HtmlRelation.call(this, config);
}

util.inherits(HtmlStyle, HtmlRelation);

extendWithGettersAndSetters(HtmlStyle.prototype, {
    get href() {
        if (this.node.nodeName.toLowerCase() === 'link') {
            return this.node.getAttribute('href');
        }
        // Else return undefined
    },

    set href(href) {
        if (this.node.nodeName === 'link') {
            this.node.setAttribute('href', href);
        } else {
            var document = this.node.ownerDocument,
                link = document.createElement('link'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            if (existingMediaAttributeValue) {
                link.setAttribute('media', existingMediaAttributeValue);
            }
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', href);
            this.node.parentNode.replaceChild(link, this.node);
            this.node = link;
        }
    },

    inline: function () {
        Relation.prototype.inline.call(this);
        if (this.node.nodeName === 'style') {
            while (this.node.firstChild) {
                this.node.removeChild(this.node.firstChild);
            }
            this.node.appendChild(this.from.parseTree.createTextNode(this.to.text));
        } else {
            var style = this.from.parseTree.createElement('style'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            style.setAttribute('type', 'text/css');
            if (existingMediaAttributeValue) {
                style.setAttribute('media', existingMediaAttributeValue);
            }
            style.appendChild(this.from.parseTree.createTextNode(this.to.text));
            this.node.parentNode.replaceChild(style, this.node);
            this.node = style;
        }
        this.from.markDirty();
        return this;
    },

    attach: function (asset, position, adjacentRelation) {
        var parseTree = asset.parseTree;
        this.node = parseTree.createElement('link');
        this.node.setAttribute('rel', 'stylesheet');
        if (position === 'first') {
            if (parseTree.head) {
                for (var i = 0 ; i < parseTree.head.childNodes.length ; i += 1) {
                    var childNode = parseTree.head.childNodes[i],
                        nodeName = childNode.nodeName.toLowerCase(),
                        found = false;
                    if (nodeName === 'style' || (nodeName === 'link' && /^stylesheet$/i.test(childNode.getAttribute('rel')))) {
                        found = true;
                        parseTree.head.insertBefore(this.node, childNode);
                        break;
                    }
                }
                if (!found) {
                    parseTree.head.appendChild(this.node);
                }
            } else {
                // No <head>, maybe the body of a conditional comment.
                parseTree.insertBefore(this.node, parseTree.firstChild);
            }
        } else {
            this.attachNodeBeforeOrAfter(position, adjacentRelation);
        }
        return HtmlRelation.prototype.attach.call(this, asset, position, adjacentRelation);
    }
});

module.exports = HtmlStyle;
<commit_msg>HtmlStyle.js<commit_after>var util = require('util'),
    extendWithGettersAndSetters = require('../util/extendWithGettersAndSetters'),
    Relation = require('./Relation'),
    HtmlRelation = require('./HtmlRelation');

function HtmlStyle(config) {
    HtmlRelation.call(this, config);
}

util.inherits(HtmlStyle, HtmlRelation);

extendWithGettersAndSetters(HtmlStyle.prototype, {
    get href() {
        if (this.node.nodeName.toLowerCase() === 'link') {
            return this.node.getAttribute('href');
        }
        // Else return undefined
    },

    set href(href) {
        if (this.node.nodeName === 'link') {
            this.node.setAttribute('href', href);
        } else {
            var document = this.node.ownerDocument,
                link = document.createElement('link'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            if (existingMediaAttributeValue) {
                link.setAttribute('media', existingMediaAttributeValue);
            }
            link.setAttribute('rel', 'stylesheet');
            link.setAttribute('href', href);
            this.node.parentNode.replaceChild(link, this.node);
            this.node = link;
        }
    },

    inline: function () {
        Relation.prototype.inline.call(this);
        if (this.node.nodeName === 'style') {
            while (this.node.firstChild) {
                this.node.removeChild(this.node.firstChild);
            }
            this.node.appendChild(this.from.parseTree.createTextNode(this.to.text));
        } else {
            var style = this.from.parseTree.createElement('style'),
                existingMediaAttributeValue = this.node.getAttribute('media');
            style.setAttribute('type', 'text/css');
            if (existingMediaAttributeValue) {
                style.setAttribute('media', existingMediaAttributeValue);
            }
            style.appendChild(this.from.parseTree.createTextNode(this.to.text));
            this.node.parentNode.replaceChild(style, this.node);
            this.node = style;
        }
        this.from.markDirty();
        return this;
    },

    attach: function (asset, position, adjacentRelation) {
        var parseTree = asset.parseTree,
            found = false;

        this.node = parseTree.createElement('link');
        this.node.setAttribute('rel', 'stylesheet');
        if (position === 'first') {
            if (parseTree.head) {
                for (var i = 0 ; i < parseTree.head.childNodes.length ; i += 1) {
                    var childNode = parseTree.head.childNodes[i],
                        nodeName = childNode.nodeName.toLowerCase();

                    if (nodeName === 'style' || (nodeName === 'link' && /^stylesheet$/i.test(childNode.getAttribute('rel')))) {
                        found = true;
                        parseTree.head.insertBefore(this.node, childNode);
                        break;
                    }
                }
                if (!found) {
                    parseTree.head.appendChild(this.node);
                }
            } else {
                // No <head>, maybe the body of a conditional comment.
                parseTree.insertBefore(this.node, parseTree.firstChild);
            }
        } else {
            this.attachNodeBeforeOrAfter(position, adjacentRelation);
        }
        return HtmlRelation.prototype.attach.call(this, asset, position, adjacentRelation);
    }
});

module.exports = HtmlStyle;
<|endoftext|>


<commit_before>import m from 'mithril';
import _ from 'underscore';
import moment from 'moment';
import h from '../h';
import projectVM from '../vms/project-vm';
import projectRewardList from './project-reward-list';
import projectGoalsBox from './project-goals-box';
import projectReport from './project-report';
import projectSuggestedContributions from './project-suggested-contributions';

const projectAbout = {
    view(ctrl, args) {
        const project = args.project() || {},
            onlineDays = () => {
                const diff = moment(project.zone_online_date).diff(moment(project.zone_expires_at)),
                    duration = moment.duration(diff);

                return -Math.ceil(duration.asDays());
            };
        const fundingPeriod = () => (project.is_published && h.existy(project.zone_expires_at)) ? m('.funding-period', [
            m('.fontsize-small.fontweight-semibold.u-text-center-small-only', 'Período de campanha'),
            m('.fontsize-small.u-text-center-small-only', `${h.momentify(project.zone_online_date)} - ${h.momentify(project.zone_expires_at)} (${onlineDays()} dias)`)
        ]) : '';

        const buildIfTrue = (condition, buildSteps) => condition ? buildSteps : '';

        const nextStepsCardOptions = () => {
            const isSubscription = projectVM.isSubscription(project);
            const hasRewards = !_.isEmpty(args.rewardDetails());
            const titleText = hasRewards ? 'Recompensas' : 'Sugestões de apoio';

            return [
                isSubscription ? [
                    m('.fontsize-base.fontweight-semibold.u-marginbottom-30', titleText),
                ] : [
                    m('.fontsize-base.u-marginbottom-30.w-hidden-small.w-hidden-tiny', [
                        m('span.fontweight-semibold', titleText),
                        m.trust(' '),
                        m("span.badge.fontsize-smaller.badge-success", "parcele em até 6x")
                    ])
                ],
                hasRewards ? [
                    m.component(projectRewardList, {
                        project: args.project,
                        hasSubscription: args.hasSubscription,
                        rewardDetails: args.rewardDetails
                    })
                ] : [
                    m.component(projectSuggestedContributions, { project: args.project })
                ],
                fundingPeriod()
            ];
        };

        return m('#project-about', [
            m('.project-about.w-col.w-col-8', {
                config: h.UIHelper()
            }, [
                m('p.fontsize-base', [
                    m('strong', 'O projeto'),
                ]),
                m('.fontsize-base[itemprop="about"]', m.trust(h.selfOrEmpty(project.about_html, '...'))),
                project.budget ? [
                    m('p.fontsize-base.fontweight-semibold', 'Orçamento'),
                    m('p.fontsize-base', m.trust(project.budget))
                ] : '',
                m.component(projectReport)
            ]),
            m('.w-col.w-col-4.w-hidden-small.w-hidden-tiny', [
                projectVM.isSubscription(project) ? (args.subscriptionData() ? m(projectGoalsBox, { goalDetails: args.goalDetails, subscriptionData: args.subscriptionData }) : h.loader()) : '',
                nextStepsCardOptions()
            ])
        ]);
    }
};

export default projectAbout;
<commit_msg>remove unused function #158173930<commit_after>import m from 'mithril';
import _ from 'underscore';
import moment from 'moment';
import h from '../h';
import projectVM from '../vms/project-vm';
import projectRewardList from './project-reward-list';
import projectGoalsBox from './project-goals-box';
import projectReport from './project-report';
import projectSuggestedContributions from './project-suggested-contributions';

const projectAbout = {
    view(ctrl, args) {
        const project = args.project() || {},
            onlineDays = () => {
                const diff = moment(project.zone_online_date).diff(moment(project.zone_expires_at)),
                    duration = moment.duration(diff);

                return -Math.ceil(duration.asDays());
            };
        const fundingPeriod = () => (project.is_published && h.existy(project.zone_expires_at)) ? m('.funding-period', [
            m('.fontsize-small.fontweight-semibold.u-text-center-small-only', 'Período de campanha'),
            m('.fontsize-small.u-text-center-small-only', `${h.momentify(project.zone_online_date)} - ${h.momentify(project.zone_expires_at)} (${onlineDays()} dias)`)
        ]) : '';

        const nextStepsCardOptions = () => {
            const isSubscription = projectVM.isSubscription(project);
            const hasRewards = !_.isEmpty(args.rewardDetails());
            const titleText = hasRewards ? 'Recompensas' : 'Sugestões de apoio';

            return [
                isSubscription ? [
                    m('.fontsize-base.fontweight-semibold.u-marginbottom-30', titleText),
                ] : [
                    m('.fontsize-base.u-marginbottom-30.w-hidden-small.w-hidden-tiny', [
                        m('span.fontweight-semibold', titleText),
                        m.trust(' '),
                        m("span.badge.fontsize-smaller.badge-success", "parcele em até 6x")
                    ])
                ],
                hasRewards ? [
                    m.component(projectRewardList, {
                        project: args.project,
                        hasSubscription: args.hasSubscription,
                        rewardDetails: args.rewardDetails
                    })
                ] : [
                    m.component(projectSuggestedContributions, { project: args.project })
                ],
                fundingPeriod()
            ];
        };

        return m('#project-about', [
            m('.project-about.w-col.w-col-8', {
                config: h.UIHelper()
            }, [
                m('p.fontsize-base', [
                    m('strong', 'O projeto'),
                ]),
                m('.fontsize-base[itemprop="about"]', m.trust(h.selfOrEmpty(project.about_html, '...'))),
                project.budget ? [
                    m('p.fontsize-base.fontweight-semibold', 'Orçamento'),
                    m('p.fontsize-base', m.trust(project.budget))
                ] : '',
                m.component(projectReport)
            ]),
            m('.w-col.w-col-4.w-hidden-small.w-hidden-tiny', [
                projectVM.isSubscription(project) ? (args.subscriptionData() ? m(projectGoalsBox, { goalDetails: args.goalDetails, subscriptionData: args.subscriptionData }) : h.loader()) : '',
                nextStepsCardOptions()
            ])
        ]);
    }
};

export default projectAbout;
<|endoftext|>


<commit_before>var browsers = {
  chrome_latest_linux: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest'
  },
  chrome_latest_windows: {
    browserName: 'chrome',
    platform: 'Windows 10',
    version: 'latest'
  },
  chrome_latest_osx: {
    browserName: 'chrome',
    platform: 'OS X 10.11',
    version: 'latest'
  },
  chrome_latest_1: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-1'
  },
  chrome_latest_2: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-2'
  },
  chrome_latest_3: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-3'
  },
  chrome_latest_4: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-4'
  },

  firefox_latest_linux: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest'
  },
  firefox_latest_windows: {
    browserName: 'firefox',
    platform: 'Windows 10',
    version: 'latest'
  },
  firefox_latest_osx: {
    browserName: 'firefox',
    platform: 'OS X 10.11',
    version: 'latest'
  },
  firefox_latest_1: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-1'
  },
  firefox_latest_2: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-2'
  },
  firefox_latest_3: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-3'
  },
  firefox_latest_4: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-4'
  },
  firefox_latest_5: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-5'
  },
  firefox_latest_6: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-6'
  },

  // Safari latest
  safari_latest: {
    browserName: 'safari',
    version: 'latest',
    platform: 'OS X 10.11'
  },
  safari_8: {
    browserName: 'safari',
    version: '8',
    platform: 'OS X 10.10'
  },
  /*
  safari_7: {
    browserName: 'safari',
    version: '7.1.7',
    platform: 'OS X 10.9'
  },
  */
  safari_6: {
    browserName: 'safari',
    version: '6',
    platform: 'OS X 10.8'
  },

  internet_explorer_11: {
    browserName: 'internet explorer',
    version: '11',
    platform: 'Windows 8.1'
  },
  internet_explorer_10: {
    browserName: 'internet explorer',
    version: '10',
    platform: 'Windows 8'
  },
  internet_explorer_9: {
    browserName: 'internet explorer',
    version: '9',
    platform: 'Windows 7'
  },

  microsoftedge_latest: {
    browserName: 'microsoftedge',
    platform: 'Windows 10',
    version: 'latest'
  },

  opera_12: {
    browserName: 'opera',
    platform: 'Windows 7',
    version: '12.12'
  }
};

Object.keys(browsers).forEach(function(key) {
    browsers[key].base = 'SauceLabs';
});

module.exports = browsers;
<commit_msg>Remove old safari from saucelabs launchers<commit_after>var browsers = {
  chrome_latest_linux: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest'
  },
  chrome_latest_windows: {
    browserName: 'chrome',
    platform: 'Windows 10',
    version: 'latest'
  },
  chrome_latest_osx: {
    browserName: 'chrome',
    platform: 'OS X 10.11',
    version: 'latest'
  },
  chrome_latest_1: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-1'
  },
  chrome_latest_2: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-2'
  },
  chrome_latest_3: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-3'
  },
  chrome_latest_4: {
    browserName: 'chrome',
    platform: 'Linux',
    version: 'latest-4'
  },

  firefox_latest_linux: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest'
  },
  firefox_latest_windows: {
    browserName: 'firefox',
    platform: 'Windows 10',
    version: 'latest'
  },
  firefox_latest_osx: {
    browserName: 'firefox',
    platform: 'OS X 10.11',
    version: 'latest'
  },
  firefox_latest_1: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-1'
  },
  firefox_latest_2: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-2'
  },
  firefox_latest_3: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-3'
  },
  firefox_latest_4: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-4'
  },
  firefox_latest_5: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-5'
  },
  firefox_latest_6: {
    browserName: 'firefox',
    platform: 'Linux',
    version: 'latest-6'
  },

  // Safari latest
  safari_latest: {
    browserName: 'safari',
    version: 'latest',
    platform: 'OS X 10.11'
  },
  internet_explorer_11: {
    browserName: 'internet explorer',
    version: '11',
    platform: 'Windows 8.1'
  },
  internet_explorer_10: {
    browserName: 'internet explorer',
    version: '10',
    platform: 'Windows 8'
  },
  internet_explorer_9: {
    browserName: 'internet explorer',
    version: '9',
    platform: 'Windows 7'
  },

  microsoftedge_latest: {
    browserName: 'microsoftedge',
    platform: 'Windows 10',
    version: 'latest'
  },

  opera_12: {
    browserName: 'opera',
    platform: 'Windows 7',
    version: '12.12'
  }
};

Object.keys(browsers).forEach(function(key) {
    browsers[key].base = 'SauceLabs';
});

module.exports = browsers;
<|endoftext|>


<commit_before>/*
 * scheduler.js
 *
 * Copyright (C) 2009-13 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
var crypto = require('crypto');
var fs = require('fs');
var os = require('os');
var events = require('events');
var net = require('net');
var map = require('../core/map');
var crypto = require('crypto');
var path = require('path');
var moment = require('moment');
var util = require('util');
var Q = require('q');
var _ = require('underscore');
require('../core/log');
var fsutil = require('../core/fsutil');
var AppWorkerHandle = require('../worker/app-worker-handle');
var app_worker = require('../worker/app-worker');

/**
 * @param appSpec the appSpec associated
 * with this scheduler. Will be used to identify the scheduler if/when it
 * runs out of workers and needs to be terminated.
 */
var Scheduler = function(eventBus, appSpec) {
  events.EventEmitter.call(this);

  this.$eventBus = eventBus;
  this.$workers = map.create();
  this.$appSpec = appSpec;
};
Scheduler.prototype.__proto__ = events.EventEmitter.prototype;

module.exports = Scheduler;

// Attempt to connect to the endpoint, but don't take longer than `timeout`.
// The shouldContinue callback can be used to short-circuit waiting (in case
// the caller learns some other way that the connect will never succeed, i.e.
// the process we're trying to connect to has exited).
//
// On success, the promise resolves to true.
// On timeout, the promise is rejected with an error.
// On shouldContinue() == false, the promise is rejected with an error.
function connectEndpoint_p(endpoint, timeout, shouldContinue) {
  var deferred = Q.defer();

  // Retry using increasing intervals; when we use them all, use maxInterval
  var intervals = [50, 50, 100, 100, 100, 100, 100, 200, 200, 300, 300, 300];
  var maxInterval = 500;

  // The previously used interval
  var lastInterval = 0;
  var elapsed = 0;
  var timeoutId = null;

  attemptToConnect();

  // Represents a single attempt to connect. If it fails, will try again later
  // unless the elapsed time exceeds the timeout we were given.
  function attemptToConnect() {
    elapsed += lastInterval;

    if (elapsed > timeout) {
      logger.trace('Giving up on connecting to ' + endpoint.toString());
      deferred.reject(new Error('The application took too long to respond.'));
      return;
    }
    if (!deferred.promise.isPending()) {
      return;
    }
    if (!shouldContinue()) {
      logger.trace('Aborting connection attempts to ' + endpoint.toString());
      deferred.reject(new Error('Connection attempt was aborted.'));
      return;
    }

    logger.trace('Attempting to connect to ' + endpoint.toString());
    endpoint.connect_p()
    .then(function(connected) {
      if (connected) {
        logger.trace('Successfully connected to ' + endpoint.toString() +
          ' after ' + elapsed + 'ms');
        clearTimeout(timeoutId);
        deferred.resolve(true);
        return;
      } else {
        logger.trace('Failed to connect to ' + endpoint.toString());
      }
    })
    .eat();

    lastInterval = intervals.shift() || maxInterval;
    setTimeout(attemptToConnect, lastInterval);
  }

  return deferred.promise;
}

(function() {
  this.setTransport = function(transport) {
    this.$transport = transport;
  };

  /**
   * @param preemptive true if we're creating this worker before we're actually
   * allocating a request to it. false if we're creating this request as we
   * allocate a request to it. This is needed to maintain a proper count of
   * how many connections are active for a worker.
   */
  this.spawnWorker_p = function(appSpec, workerData, preemptive){
    var self = this;

    // Because appSpec will no longer uniquely identify a worker, assign a
    // random ID to each worker for addressing purposes.
    var workerId = crypto.randomBytes(8).toString('hex');

    var defer = Q.defer();

    this.$workers[workerId] = {
      promise : defer.promise,
      data : workerData || map.create()
    }

    //initialize open connections counters.
    this.$workers[workerId].data['sockConn'] = 0;
    this.$workers[workerId].data['httpConn'] = 0;
    this.$workers[workerId].data['pendingConn'] = 0;
    if (!preemptive){
      this.$workers[workerId].data['pendingConn']++;
    }

    var logFilePath = null;
    var doReject = function(err) {
      err.consoleLogFile = logFilePath;
      defer.reject(err);
    };
    // Once defer is fulfilled, doReject is essentially a no-op. The following
    // (making doReject *actually* a no-op) may seem redundant, but it is
    // necessary to prevent a memory leak!
    // jcheng 8/22/2013: I'm not sure we still need this; the leak might have
    // been because connectEndpoint_p (er, its predecessor) did not resolve or
    // reject the deferred when shouldContinue was false.
    defer.promise
    .fin(function() {
      doReject = function() {};
    })
    .eat();

    this.$transport.alloc_p()
    .then(function(endpoint) {

      logFilePath = self.getLogFilePath(appSpec, endpoint);

      var deleteLogFileOnExit = false;
      logger.trace('Launching ' + appSpec.appDir + ' as ' + appSpec.runAs +
        ' with on ' + endpoint.toString());
      var workerPromise = app_worker.launchWorker_p(
        appSpec, endpoint, logFilePath, workerId);

      var exitPromise = workerPromise.invoke('getExit_p');
      exitPromise
      .fin(function() {
        delete self.$workers[workerId];
        if (_.size(self.$workers) === 0) {
          // There aren't any workers left, kill this scheduler.
          self.$eventBus.emit('vacantSched', self.$appSpec.getKey());
        }
        endpoint.free();
        logger.trace(endpoint.ToString() + ' returned');
      })
      .then(function(status) {
        if (deleteLogFileOnExit) {
          if (logFilePath === '/dev/null'){
            logger.trace("Refusing to delete /dev/null.");
            return;
          }
          logger.trace('Normal exit, deleting log file ' + logFilePath);
          fs.unlink(logFilePath, function(err) {
            if (err)
              logger.warn('Failed to delete log file ' + logFilePath + ': ' + err.message);
          });
        }
      })
      .eat();

      return workerPromise.then(function(appWorker) {
        var appWorkerHandle = new AppWorkerHandle(appSpec, endpoint,
          logFilePath, exitPromise,
          _.bind(appWorker.kill, appWorker));

        var initTimeout = 60 * 1000;
        if (appSpec.settings.appDefaults && appSpec.settings.appDefaults.initTimeout){
          initTimeout = appSpec.settings.appDefaults.initTimeout * 1000;
        }

        var connectPromise = connectEndpoint_p(endpoint,
            initTimeout, _.bind(exitPromise.isPending, exitPromise));

        connectPromise
        .then(function() {
          /**
           * Supplement the workerHandle with acquire and release functions.
           */
          (function() {
            this.acquire = function(type){
              if(type == 'http'){
                self.$workers[workerId].data['httpConn']++;
              } else if(type == 'sock'){
                self.$workers[workerId].data['sockConn']++;
              } else{
                throw Error('Unrecognized type to be acquired: "' + type + '"');
              }

              //We just realized a pending connection. Decrement the counter.
              if (self.$workers[workerId].data.pendingConn > 0){
                self.$workers[workerId].data.pendingConn--;
              }

              logger.trace('Worker #'+workerId+' acquiring '+type+' port. ' +
                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' +
                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')

              //clear the timer to ensure this process doesn't get destroyed.
              var timerId = self.$workers[workerId].data['timer'];
              if (timerId){
                clearTimeout(timerId);
                self.$workers[workerId].data['timer'] = null;
              }
            };

            var idleTimeout = 5 * 1000;
            if (appSpec.settings.appDefaults && (appSpec.settings.appDefaults.idleTimeout || appSpec.settings.appDefaults.idleTimeout === 0)){
              idleTimeout = appSpec.settings.appDefaults.idleTimeout * 1000;

              if (idleTimeout > Math.pow(2,31) - 1) {
                // Node currently only supports 32-bit setTimeouts
                // http://stackoverflow.com/questions/16314750/settimeout-fires-immediately-if-the-delay-more-than-2147483648-milliseconds
                logger.warn('Idle timeout value "' + appSpec.settings.appDefaults.idleTimeout + '" too high. Using the maximum of 2147483 seconds instead. Consider using a negative value if you want to disable the timeout altogether.');
                idleTimeout = 2147483;
              }
            }

            this.release = function(type){
              if (!_.has(self.$workers, workerId)){
                // Must have already been deleted. Don't need to do any work.
                return;
              }

              if(type == 'http'){
                self.$workers[workerId].data['httpConn']--;
                self.$workers[workerId].data['httpConn'] =
                  Math.max(0, self.$workers[workerId].data['httpConn']);
              } else if(type == 'sock'){
                self.$workers[workerId].data['sockConn']--;
                self.$workers[workerId].data['sockConn'] =
                  Math.max(0, self.$workers[workerId].data['sockConn']);
              } else{
                throw Error('Unrecognized type to be released: "' + type + '"');
              }

              logger.trace('Worker #'+workerId+' releasing '+type+' port. ' +
                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' +
                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')

              if (self.$workers[workerId].data['sockConn'] +
                    self.$workers[workerId].data['httpConn'] === 0) {
                if (idleTimeout >= 0){
                  logger.trace("No clients connected to worker #" + workerId + ". Starting timer");
                  self.$workers[workerId].data['timer'] = global.setTimeout(function() {
                    logger.trace("Timeout expired. Killing process.");
                    deleteLogFileOnExit = true;
                    if (!appWorker.isRunning()) {
                      logger.trace('Process on ' + endpoint.toString() + ' is already gone');
                    } else {
                      logger.trace('Interrupting process on socket ' + endpoint.toString());
                      try {
                        appWorker.kill();
                      } catch (err) {
                        logger.error('Failed to kill process on ' + endpoint.toString() + ': ' + err.message);
                      }
                    }
                  }, idleTimeout);
                }
                else {
                  logger.trace("No clients connected to worker #" + workerId + ", but refusing to reap due to negative idle_timeout.");
                }
              }

            };

          }).call(appWorkerHandle);

          // Call release initially. This won't change the value of the counter,
          // as they have a minimum of 0, but it will trigger a timer to ensure
          // that this worker doesn't spin up, never get used, and run
          // indefinitely.
          appWorkerHandle.release('http');

          return defer.resolve(appWorkerHandle);
          },
          doReject
        )
        .done();

        exitPromise.fin(function() {
          doReject(new Error('The application exited during initialization.'));
        })
        .eat();
      });
    })
    .fail(function(error) {
      doReject(error);
    })
    .done();

    return defer.promise;
  };

  this.getLogFilePath = function(appSpec, endpoint) {
    if (!appSpec.logDir)
      return '/dev/null'; // TODO: Windows-compatible equivalent?

    var timestamp = moment().format('YYYYMMDD-HHmmss');
    var filename = path.basename(appSpec.appDir) + '-' +
      appSpec.runAs + '-' + timestamp + '-' + endpoint.getLogFileSuffix() +
      '.log';
    return path.join(appSpec.logDir, filename);
  };

  this.shutdown = function() {
    return _.each(_.values(this.$workers), function(worker) {
      if (worker.promise.isFulfilled()) {
        try {
          worker.promise.inspect().value.kill();
        } catch (err) {
          logger.error('Failed to kill process: ' + err.message);
        }
      }
    });
  };

  function summarizeWorker(worker) {
    return {
      appSpec: worker.appSpec,
      endpoint: worker.endpoint.ToString(),
      logFilePath: worker.logFilePath
    };
  }

  this.dump = function() {
    logger.info('Dumping up to ' + _.size(this.$workers) + ' worker(s)');
    _.each(_.values(this.$workers), function(worker) {
      if (worker.promise.isFulfilled()) {
        console.log(util.inspect(summarizeWorker(worker.promise.inspect().value),
          false, null, true
        ));
      }
      else {
        console.log('[unresolved promise]');
      }
    });
    logger.info('Dump completed')
  };
}).call(Scheduler.prototype);
<commit_msg>Treat 0 app_idle timeout as disabled.<commit_after>/*
 * scheduler.js
 *
 * Copyright (C) 2009-13 by RStudio, Inc.
 *
 * This program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
var crypto = require('crypto');
var fs = require('fs');
var os = require('os');
var events = require('events');
var net = require('net');
var map = require('../core/map');
var crypto = require('crypto');
var path = require('path');
var moment = require('moment');
var util = require('util');
var Q = require('q');
var _ = require('underscore');
require('../core/log');
var fsutil = require('../core/fsutil');
var AppWorkerHandle = require('../worker/app-worker-handle');
var app_worker = require('../worker/app-worker');

/**
 * @param appSpec the appSpec associated
 * with this scheduler. Will be used to identify the scheduler if/when it
 * runs out of workers and needs to be terminated.
 */
var Scheduler = function(eventBus, appSpec) {
  events.EventEmitter.call(this);

  this.$eventBus = eventBus;
  this.$workers = map.create();
  this.$appSpec = appSpec;
};
Scheduler.prototype.__proto__ = events.EventEmitter.prototype;

module.exports = Scheduler;

// Attempt to connect to the endpoint, but don't take longer than `timeout`.
// The shouldContinue callback can be used to short-circuit waiting (in case
// the caller learns some other way that the connect will never succeed, i.e.
// the process we're trying to connect to has exited).
//
// On success, the promise resolves to true.
// On timeout, the promise is rejected with an error.
// On shouldContinue() == false, the promise is rejected with an error.
function connectEndpoint_p(endpoint, timeout, shouldContinue) {
  var deferred = Q.defer();

  // Retry using increasing intervals; when we use them all, use maxInterval
  var intervals = [50, 50, 100, 100, 100, 100, 100, 200, 200, 300, 300, 300];
  var maxInterval = 500;

  // The previously used interval
  var lastInterval = 0;
  var elapsed = 0;
  var timeoutId = null;

  attemptToConnect();

  // Represents a single attempt to connect. If it fails, will try again later
  // unless the elapsed time exceeds the timeout we were given.
  function attemptToConnect() {
    elapsed += lastInterval;

    if (elapsed > timeout) {
      logger.trace('Giving up on connecting to ' + endpoint.toString());
      deferred.reject(new Error('The application took too long to respond.'));
      return;
    }
    if (!deferred.promise.isPending()) {
      return;
    }
    if (!shouldContinue()) {
      logger.trace('Aborting connection attempts to ' + endpoint.toString());
      deferred.reject(new Error('Connection attempt was aborted.'));
      return;
    }

    logger.trace('Attempting to connect to ' + endpoint.toString());
    endpoint.connect_p()
    .then(function(connected) {
      if (connected) {
        logger.trace('Successfully connected to ' + endpoint.toString() +
          ' after ' + elapsed + 'ms');
        clearTimeout(timeoutId);
        deferred.resolve(true);
        return;
      } else {
        logger.trace('Failed to connect to ' + endpoint.toString());
      }
    })
    .eat();

    lastInterval = intervals.shift() || maxInterval;
    setTimeout(attemptToConnect, lastInterval);
  }

  return deferred.promise;
}

(function() {
  this.setTransport = function(transport) {
    this.$transport = transport;
  };

  /**
   * @param preemptive true if we're creating this worker before we're actually
   * allocating a request to it. false if we're creating this request as we
   * allocate a request to it. This is needed to maintain a proper count of
   * how many connections are active for a worker.
   */
  this.spawnWorker_p = function(appSpec, workerData, preemptive){
    var self = this;

    // Because appSpec will no longer uniquely identify a worker, assign a
    // random ID to each worker for addressing purposes.
    var workerId = crypto.randomBytes(8).toString('hex');

    var defer = Q.defer();

    this.$workers[workerId] = {
      promise : defer.promise,
      data : workerData || map.create()
    }

    //initialize open connections counters.
    this.$workers[workerId].data['sockConn'] = 0;
    this.$workers[workerId].data['httpConn'] = 0;
    this.$workers[workerId].data['pendingConn'] = 0;
    if (!preemptive){
      this.$workers[workerId].data['pendingConn']++;
    }

    var logFilePath = null;
    var doReject = function(err) {
      err.consoleLogFile = logFilePath;
      defer.reject(err);
    };
    // Once defer is fulfilled, doReject is essentially a no-op. The following
    // (making doReject *actually* a no-op) may seem redundant, but it is
    // necessary to prevent a memory leak!
    // jcheng 8/22/2013: I'm not sure we still need this; the leak might have
    // been because connectEndpoint_p (er, its predecessor) did not resolve or
    // reject the deferred when shouldContinue was false.
    defer.promise
    .fin(function() {
      doReject = function() {};
    })
    .eat();

    this.$transport.alloc_p()
    .then(function(endpoint) {

      logFilePath = self.getLogFilePath(appSpec, endpoint);

      var deleteLogFileOnExit = false;
      logger.trace('Launching ' + appSpec.appDir + ' as ' + appSpec.runAs +
        ' with on ' + endpoint.toString());
      var workerPromise = app_worker.launchWorker_p(
        appSpec, endpoint, logFilePath, workerId);

      var exitPromise = workerPromise.invoke('getExit_p');
      exitPromise
      .fin(function() {
        delete self.$workers[workerId];
        if (_.size(self.$workers) === 0) {
          // There aren't any workers left, kill this scheduler.
          self.$eventBus.emit('vacantSched', self.$appSpec.getKey());
        }
        endpoint.free();
        logger.trace(endpoint.ToString() + ' returned');
      })
      .then(function(status) {
        if (deleteLogFileOnExit) {
          if (logFilePath === '/dev/null'){
            logger.trace("Refusing to delete /dev/null.");
            return;
          }
          logger.trace('Normal exit, deleting log file ' + logFilePath);
          fs.unlink(logFilePath, function(err) {
            if (err)
              logger.warn('Failed to delete log file ' + logFilePath + ': ' + err.message);
          });
        }
      })
      .eat();

      return workerPromise.then(function(appWorker) {
        var appWorkerHandle = new AppWorkerHandle(appSpec, endpoint,
          logFilePath, exitPromise,
          _.bind(appWorker.kill, appWorker));

        var initTimeout = 60 * 1000;
        if (appSpec.settings.appDefaults && appSpec.settings.appDefaults.initTimeout){
          initTimeout = appSpec.settings.appDefaults.initTimeout * 1000;
        }

        var connectPromise = connectEndpoint_p(endpoint,
            initTimeout, _.bind(exitPromise.isPending, exitPromise));

        connectPromise
        .then(function() {
          /**
           * Supplement the workerHandle with acquire and release functions.
           */
          (function() {
            this.acquire = function(type){
              if(type == 'http'){
                self.$workers[workerId].data['httpConn']++;
              } else if(type == 'sock'){
                self.$workers[workerId].data['sockConn']++;
              } else{
                throw Error('Unrecognized type to be acquired: "' + type + '"');
              }

              //We just realized a pending connection. Decrement the counter.
              if (self.$workers[workerId].data.pendingConn > 0){
                self.$workers[workerId].data.pendingConn--;
              }

              logger.trace('Worker #'+workerId+' acquiring '+type+' port. ' +
                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' +
                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')

              //clear the timer to ensure this process doesn't get destroyed.
              var timerId = self.$workers[workerId].data['timer'];
              if (timerId){
                clearTimeout(timerId);
                self.$workers[workerId].data['timer'] = null;
              }
            };

            var idleTimeout = 5 * 1000;
            if (appSpec.settings.appDefaults && (appSpec.settings.appDefaults.idleTimeout || appSpec.settings.appDefaults.idleTimeout === 0)){
              idleTimeout = appSpec.settings.appDefaults.idleTimeout * 1000;

              if (idleTimeout > Math.pow(2,31) - 1) {
                // Node currently only supports 32-bit setTimeouts
                // http://stackoverflow.com/questions/16314750/settimeout-fires-immediately-if-the-delay-more-than-2147483648-milliseconds
                logger.warn('Idle timeout value "' + appSpec.settings.appDefaults.idleTimeout + '" too high. Using the maximum of 2147483 seconds instead. Consider using a negative value if you want to disable the timeout altogether.');
                idleTimeout = 2147483;
              }
            }

            this.release = function(type){
              if (!_.has(self.$workers, workerId)){
                // Must have already been deleted. Don't need to do any work.
                return;
              }

              if(type == 'http'){
                self.$workers[workerId].data['httpConn']--;
                self.$workers[workerId].data['httpConn'] =
                  Math.max(0, self.$workers[workerId].data['httpConn']);
              } else if(type == 'sock'){
                self.$workers[workerId].data['sockConn']--;
                self.$workers[workerId].data['sockConn'] =
                  Math.max(0, self.$workers[workerId].data['sockConn']);
              } else{
                throw Error('Unrecognized type to be released: "' + type + '"');
              }

              logger.trace('Worker #'+workerId+' releasing '+type+' port. ' +
                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' +
                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')

              if (self.$workers[workerId].data['sockConn'] +
                    self.$workers[workerId].data['httpConn'] === 0) {
                if (idleTimeout > 0){
                  logger.trace("No clients connected to worker #" + workerId + ". Starting timer");
                  self.$workers[workerId].data['timer'] = global.setTimeout(function() {
                    logger.trace("Timeout expired. Killing process.");
                    deleteLogFileOnExit = true;
                    if (!appWorker.isRunning()) {
                      logger.trace('Process on ' + endpoint.toString() + ' is already gone');
                    } else {
                      logger.trace('Interrupting process on socket ' + endpoint.toString());
                      try {
                        appWorker.kill();
                      } catch (err) {
                        logger.error('Failed to kill process on ' + endpoint.toString() + ': ' + err.message);
                      }
                    }
                  }, idleTimeout);
                }
                else {
                  logger.trace("No clients connected to worker #" + workerId + ", but refusing to reap due to non-positive idle_timeout.");
                }
              }

            };

          }).call(appWorkerHandle);

          // Call release initially. This won't change the value of the counter,
          // as they have a minimum of 0, but it will trigger a timer to ensure
          // that this worker doesn't spin up, never get used, and run
          // indefinitely.
          appWorkerHandle.release('http');

          return defer.resolve(appWorkerHandle);
          },
          doReject
        )
        .done();

        exitPromise.fin(function() {
          doReject(new Error('The application exited during initialization.'));
        })
        .eat();
      });
    })
    .fail(function(error) {
      doReject(error);
    })
    .done();

    return defer.promise;
  };

  this.getLogFilePath = function(appSpec, endpoint) {
    if (!appSpec.logDir)
      return '/dev/null'; // TODO: Windows-compatible equivalent?

    var timestamp = moment().format('YYYYMMDD-HHmmss');
    var filename = path.basename(appSpec.appDir) + '-' +
      appSpec.runAs + '-' + timestamp + '-' + endpoint.getLogFileSuffix() +
      '.log';
    return path.join(appSpec.logDir, filename);
  };

  this.shutdown = function() {
    return _.each(_.values(this.$workers), function(worker) {
      if (worker.promise.isFulfilled()) {
        try {
          worker.promise.inspect().value.kill();
        } catch (err) {
          logger.error('Failed to kill process: ' + err.message);
        }
      }
    });
  };

  function summarizeWorker(worker) {
    return {
      appSpec: worker.appSpec,
      endpoint: worker.endpoint.ToString(),
      logFilePath: worker.logFilePath
    };
  }

  this.dump = function() {
    logger.info('Dumping up to ' + _.size(this.$workers) + ' worker(s)');
    _.each(_.values(this.$workers), function(worker) {
      if (worker.promise.isFulfilled()) {
        console.log(util.inspect(summarizeWorker(worker.promise.inspect().value),
          false, null, true
        ));
      }
      else {
        console.log('[unresolved promise]');
      }
    });
    logger.info('Dump completed')
  };
}).call(Scheduler.prototype);
<|endoftext|>


<commit_before>module.exports = function Tokenizer(data) {
  var whatsNext = function(context) {
    var cursor = context.cursor;
    var mode = context.mode;
    var closest;

    if (mode == 'body') {
      closest = data.indexOf('}', cursor);
      return closest > -1 ?
        [closest, 'bodyEnd'] :
        null;
    }

    var nextSpecial = data.indexOf('@', cursor);
    var nextEscape = mode == 'top' ? data.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', cursor) : -1;
    var nextBodyStart = data.indexOf('{', cursor);
    var nextBodyEnd = data.indexOf('}', cursor);

    closest = nextSpecial;
    if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
      closest = nextEscape;
    if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
      closest = nextBodyStart;
    if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
      closest = nextBodyEnd;

    if (closest == -1)
      return;
    if (nextEscape === closest)
      return [closest, 'escape'];
    if (nextBodyStart === closest)
      return [closest, 'bodyStart'];
    if (nextBodyEnd === closest)
      return [closest, 'bodyEnd'];
    if (nextSpecial === closest)
      return [closest, 'special'];
  };

  var tokenize = function(context) {
    var tokenized = [];

    context = context || { cursor: 0, mode: 'top' };

    while (true) {
      var next = whatsNext(context);
      if (!next) {
        var whatsLeft = data.substring(context.cursor);
        if (whatsLeft.length > 0) {
          tokenized.push(whatsLeft);
          context.cursor += whatsLeft.length;
        }
        break;
      }

      var nextSpecial = next[0];
      var what = next[1];
      var nextEnd, oldMode;

      if (what == 'special') {
        var fragment = data.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
        var isSingle = fragment.indexOf('@import') === 0 || fragment.indexOf('@charset') === 0;
        if (isSingle) {
          nextEnd = data.indexOf(';', nextSpecial + 1);
          tokenized.push(data.substring(context.cursor, nextEnd + 1));

          context.cursor = nextEnd + 1;
        } else {
          nextEnd = data.indexOf('{', nextSpecial + 1);
          var block = data.substring(context.cursor, nextEnd).trim();

          var isFlat = fragment.indexOf('@font-face') === 0;
          oldMode = context.mode;
          context.cursor = nextEnd + 1;
          context.mode = isFlat ? 'body' : 'block';
          var specialBody = tokenize(context);
          context.mode = oldMode;

          tokenized.push({ block: block, body: specialBody });
        }
      } else if (what == 'escape') {
        nextEnd = data.indexOf('__', nextSpecial + 1);
        var escaped = data.substring(context.cursor, nextEnd + 2);
        tokenized.push(escaped);

        context.cursor = nextEnd + 2;
      } else if (what == 'bodyStart') {
        var selector = data.substring(context.cursor, nextSpecial).trim();

        oldMode = context.mode;
        context.cursor = nextSpecial + 1;
        context.mode = 'body';
        var body = tokenize(context);
        context.mode = oldMode;

        tokenized.push({ selector: selector, body: body });
      } else if (what == 'bodyEnd') {
        // extra closing brace at the top level can be safely ignored
        if (context.mode == 'top' && data[context.cursor] == '}') {
          context.cursor += 1;
          continue;
        }

        if (context.mode != 'block') {
          tokenized = data.substring(context.cursor, nextSpecial);
        }
        context.cursor = nextSpecial + 1;

        break;
      }
    }

    return tokenized;
  };

  return {
    process: function() {
      return tokenize();
    }
  };
};
<commit_msg>Style consistency changes.<commit_after>module.exports = function Tokenizer(data) {
  var whatsNext = function(context) {
    var cursor = context.cursor;
    var mode = context.mode;
    var closest;

    if (mode == 'body') {
      closest = data.indexOf('}', cursor);
      return closest > -1 ?
        [closest, 'bodyEnd'] :
        null;
    }

    var nextSpecial = data.indexOf('@', cursor);
    var nextEscape = mode == 'top' ? data.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', cursor) : -1;
    var nextBodyStart = data.indexOf('{', cursor);
    var nextBodyEnd = data.indexOf('}', cursor);

    closest = nextSpecial;
    if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
      closest = nextEscape;
    if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
      closest = nextBodyStart;
    if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
      closest = nextBodyEnd;

    if (closest == -1)
      return;
    if (nextEscape === closest)
      return [closest, 'escape'];
    if (nextBodyStart === closest)
      return [closest, 'bodyStart'];
    if (nextBodyEnd === closest)
      return [closest, 'bodyEnd'];
    if (nextSpecial === closest)
      return [closest, 'special'];
  };

  var tokenize = function(context) {
    var tokenized = [];

    context = context || { cursor: 0, mode: 'top' };

    while (true) {
      var next = whatsNext(context);
      if (!next) {
        var whatsLeft = data.substring(context.cursor);
        if (whatsLeft.length > 0) {
          tokenized.push(whatsLeft);
          context.cursor += whatsLeft.length;
        }
        break;
      }

      var nextSpecial = next[0];
      var what = next[1];
      var nextEnd;
      var oldMode;

      if (what == 'special') {
        var fragment = data.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
        var isSingle = fragment.indexOf('@import') === 0 || fragment.indexOf('@charset') === 0;
        if (isSingle) {
          nextEnd = data.indexOf(';', nextSpecial + 1);
          tokenized.push(data.substring(context.cursor, nextEnd + 1));

          context.cursor = nextEnd + 1;
        } else {
          nextEnd = data.indexOf('{', nextSpecial + 1);
          var block = data.substring(context.cursor, nextEnd).trim();

          var isFlat = fragment.indexOf('@font-face') === 0;
          oldMode = context.mode;
          context.cursor = nextEnd + 1;
          context.mode = isFlat ? 'body' : 'block';
          var specialBody = tokenize(context);
          context.mode = oldMode;

          tokenized.push({ block: block, body: specialBody });
        }
      } else if (what == 'escape') {
        nextEnd = data.indexOf('__', nextSpecial + 1);
        var escaped = data.substring(context.cursor, nextEnd + 2);
        tokenized.push(escaped);

        context.cursor = nextEnd + 2;
      } else if (what == 'bodyStart') {
        var selector = data.substring(context.cursor, nextSpecial).trim();

        oldMode = context.mode;
        context.cursor = nextSpecial + 1;
        context.mode = 'body';
        var body = tokenize(context);
        context.mode = oldMode;

        tokenized.push({ selector: selector, body: body });
      } else if (what == 'bodyEnd') {
        // extra closing brace at the top level can be safely ignored
        if (context.mode == 'top' && data[context.cursor] == '}') {
          context.cursor += 1;
          continue;
        }

        if (context.mode != 'block')
          tokenized = data.substring(context.cursor, nextSpecial);

        context.cursor = nextSpecial + 1;

        break;
      }
    }

    return tokenized;
  };

  return {
    process: function() {
      return tokenize();
    }
  };
};
<|endoftext|>


<commit_before>'use strict';

let _ = require('lodash');
let Promise = require('bluebird');
let Component = require('./component');
let child_process = require('child_process');
let DigsEmitter = require('digs-common/digs-emitter');
let slugify = require('digs-common/slugify');
let pipeEvent = require('pipe-event');
let Boom = require('boom');

const RETRY_MAX_TRIES = 3;
const READY_TIMEOUT_MS = 1e4;
const RETRY_TIMEOUT_MS = 3e4;
const J5_CLIENT_PATH = require.resolve('./j5-client');

/**
 * @typedef {Object} SerialDeviceConfig
 * @property {?string} id Unique identifier
 * @property {?string} port Port local device is on.  If not specified, J5 will
 *     auto-detect.
 */

class SerialDevice extends DigsEmitter {

  /**
   * Sets _opts and gives the SerialDevice a unique identifier, if necessary.
   * @summary Represents a development local connected to the digs server.
   * @param {Digs} server Digs instance
   * @param {ascoltatore} ascoltatore Ascoltatore instance
   * @param {SerialDeviceConfig} [opts] Local device configuration
   * @constructor
   */
  constructor(server, ascoltatore, opts) {
    super();

    _.defaults(opts, {
      readyTimeout: READY_TIMEOUT_MS,
      retryInterval: RETRY_TIMEOUT_MS,
      retryMaxTries: RETRY_MAX_TRIES,
      board: {}
    });

    this.id =
      opts.id ?
        slugify(opts.id) : _.uniqueId(`${this.constructor.name}-`);
    this.name = opts.name || opts.id;
    this.description = opts.description || this.name;

    _.defaults(opts.board, {
      debug: false,
      repl: false,
      id: this.id
    });

    this._opts = opts;
    this._boardOpts = opts.board;
    this._ascoltatore = ascoltatore;
    this._server = server;

    this.components = {};

    this.log(`${this}: instantiated w/ options`, opts);
  }

  log(data) {
    this._server.log(['digs-serial', 'serialdevice'], data);
  }

  /**
   * Starts the local by forking J5.
   * @param {Function} [callback] Optional callback if not using Promises
   * @returns {Promise.<SerialDevice>} This SerialDevice
   */
  start(callback) {
    if (this._starting) {
      return this._starting;
    }
    this.log(`${this}: starting`);

    return (this._starting = this._fork()
      .bind(this)
      .then(function() {
        let components = this._opts.components;
        let size = _.size(components);
        if (size) {
          this.log(`${this}: instantiating ${size} component(s)`);
          return _.map(components, function(_opts, idx) {
            if (!_.isNumber(idx)) {
              _opts.id = idx;
            }
            return this.component(_opts.class, _.omit(_opts, 'class'))
              .bind(this)
              .catch(function(err) {
                this.emit('warning', '"%s" component with ID "%s" failed to ' +
                  'initialize: %j', err);
                return Promise.reject(err);
              });
          }, this);
        }
        this.warn('No components configured!  Not much to do.');
      })
      .then(function() {
        this._server.log('digs-serial started');
      }))
      .return(this)
      .nodeify(callback);
  }

  /**
   * Disconnects the J5 process.
   */
  stop() {
    if (this._proc) {
      this._proc.kill();
      this._proc = null;
      this.log(`${this}: disconnected`, this);
      return true;
    }
    return false;
  }

  /**
   * Readies a Component with specified J5 class and _opts.
   * @param {string} componentClass J5 class
   * @param {(Object|Array)} [opts] Options to constructor
   * @returns {Component} New Component instance
   */
  component(componentClass, opts) {
    this.log(`${this}: instantiating a "${componentClass}" component ` +
      `w/ _opts: ${opts}`);

    let component = new Component(this._server, this, componentClass, opts);
    pipeEvent('error', component, this);
    return component.instantiate()
      .bind(this)
      .then(function(component) {
        return (this.components[component.id] = component);
      });
  }

  /**
   * Forks a ChildProcess; attaches event listeners
   * @param {number} [attempt] Number of forking attempts
   * @returns {Worker} Worker process
   */
  _fork(attempt) {
    // TODO retry attempts
    attempt = attempt || 1;

    if (this._proc) {
      return Promise.reject(new Error('Fork in progress'));
    }

    let proc = this._proc = child_process.fork(J5_CLIENT_PATH, {
      env: {
        DIGS_ID: this.id,
        DIGS_PROJECT: this._server.project,
        DIGS_NAMESPACE: this._server.namespace,
        DIGS_FSQ_DIR: this._opts.ascoltatoriOpts.fsq_dir,
        DIGS_BOARD_CONFIG: JSON.stringify(this._boardOpts)
      }
    });

    proc.on('message', function(message) {
      switch (message.type) {
        case 'log':
          switch (message.level) {
            case 'debug':
              this.log(message.msg[0]);
              break;
          }
          break;
      }
    }.bind(this));

    this.subscribe(`${this.id}/+`, function(topic, data) {
      let event = topic.split('/').slice(1).join('/');
      this.log(`${this}: Received "${topic}"; emitting event "${event}" ` +
        `with data: ${JSON.stringify(data)}`);
      this.emit(event, data);
    });

    return this.when('ready', 'error')
      .bind(this)
      .then(function(data) {
        this.port = data.port;
        this._ready = true;

        //this._digs.subscribe({
        //  clientId: this.id,
        //  wildcard: '+'
        //}, function(topic, message) {
        //  this.publishAsync(topic, message)
        //    .then(function() {
        //      debug(`${this}: Published topic "${topic}" across qlobber-fsq`);
        //    });
        //}.bind(this._ascoltatore));

        this.log(`${this}: Board "${data.id}" is ready on port ${this.port}`);
      });
  }

  subscribe(topic, callback) {
    return this._ascoltatore.subscribe(topic, callback.bind(this));
  }

  publish(topic, data) {
    topic = `${this.id}-local/${topic}`;
    this.log(`${this} (FSQ): Publishing topic "${topic}" with data`, data);
    return this._ascoltatore.publishAsync(topic, data);
  }

  get online() {
    let starting = this._starting;
    let proc = this._proc;
    return proc && proc.connected && starting && starting.isFulfilled();
  }

  get starting() {
    let starting = this._starting;
    let proc = this._proc;
    return proc && proc.connected && starting &&
      !(starting.isFulfilled() && starting.isRejected());
  }

  get status() {
    if (this.online) {
      return 'online';
    } else if (this.starting) {
      return 'starting';
    }
    return 'stopped';
  }

  toJSON() {
    return {
      id: this.id,
      name: this.name,
      description: this.description,
      port: this.port,
      components: this.components
    };
  }

}

module.exports = SerialDevice;
<commit_msg>fix component initialization<commit_after>'use strict';

let _ = require('lodash');
let Promise = require('bluebird');
let Component = require('./component');
let child_process = require('child_process');
let DigsEmitter = require('digs-common/digs-emitter');
let slugify = require('digs-common/slugify');
let pipeEvent = require('pipe-event');
let Boom = require('boom');

const RETRY_MAX_TRIES = 3;
const READY_TIMEOUT_MS = 1e4;
const RETRY_TIMEOUT_MS = 3e4;
const J5_CLIENT_PATH = require.resolve('./j5-client');

/**
 * @typedef {Object} SerialDeviceConfig
 * @property {?string} id Unique identifier
 * @property {?string} port Port local device is on.  If not specified, J5 will
 *     auto-detect.
 */

class SerialDevice extends DigsEmitter {

  /**
   * Sets _opts and gives the SerialDevice a unique identifier, if necessary.
   * @summary Represents a development local connected to the digs server.
   * @param {Digs} server Digs instance
   * @param {ascoltatore} ascoltatore Ascoltatore instance
   * @param {SerialDeviceConfig} [opts] Local device configuration
   * @constructor
   */
  constructor(server, ascoltatore, opts) {
    super();

    _.defaults(opts, {
      readyTimeout: READY_TIMEOUT_MS,
      retryInterval: RETRY_TIMEOUT_MS,
      retryMaxTries: RETRY_MAX_TRIES,
      board: {}
    });

    this.id =
      opts.id ?
        slugify(opts.id) : _.uniqueId(`${this.constructor.name}-`);
    this.name = opts.name || opts.id;
    this.description = opts.description || this.name;

    _.defaults(opts.board, {
      debug: false,
      repl: false,
      id: this.id
    });

    this._opts = opts;
    this._boardOpts = opts.board;
    this._ascoltatore = ascoltatore;
    this._server = server;

    this.components = {};

    this.log(`${this}: instantiated w/ options`, opts);
  }

  log(data) {
    this._server.log(['digs-serial', 'serialdevice'], data);
  }

  /**
   * Starts the local by forking J5.
   * @param {Function} [callback] Optional callback if not using Promises
   * @returns {Promise.<SerialDevice>} This SerialDevice
   */
  start(callback) {
    if (this._starting) {
      return this._starting;
    }
    this.log(`${this}: starting`);

    return (this._starting = this._fork()
      .bind(this)
      .then(function() {
        let components = this._opts.components;
        let size = _.size(components);
        if (size) {
          this.log(`${this}: instantiating ${size} component(s)`);
          return Promise.all(_.map(components, function(opts, componentName) {
            if (_.isString(componentName)) {
              opts.id = slugify(componentName);
            }
            return this.component(opts.class, _.omit(opts, 'class'))
              .bind(this)
              .catch(function(err) {
                this.emit('warning', '"%s" component with ID "%s" failed to ' +
                  'initialize: %j', err);
                return Promise.reject(err);
              });
          }, this));
        }
        this.warn('No components configured!  Not much to do.');
      })
      .tap(function() {
        this._server.log('digs-serial', `Serial device "${this.id}" started`);
        return this;
      }))
      .nodeify(callback);
  }

  /**
   * Disconnects the J5 process.
   */
  stop() {
    if (this._proc) {
      this._proc.kill();
      this._proc = null;
      this.log(`${this}: disconnected`, this);
      return true;
    }
    return false;
  }

  /**
   * Readies a Component with specified J5 class and _opts.
   * @param {string} componentClass J5 class
   * @param {(Object|Array)} [opts] Options to constructor
   * @returns {Component} New Component instance
   */
  component(componentClass, opts) {
    this.log(`${this}: instantiating a "${componentClass}" component ` +
      `w/ _opts: ${opts}`);

    let component = new Component(this._server, this, componentClass, opts);
    pipeEvent('error', component, this);
    return component.instantiate()
      .bind(this)
      .then(function(component) {
        return (this.components[component.id] = component);
      });
  }

  /**
   * Forks a ChildProcess; attaches event listeners
   * @param {number} [attempt] Number of forking attempts
   * @returns {Worker} Worker process
   */
  _fork(attempt) {
    // TODO retry attempts
    attempt = attempt || 1;

    if (this._proc) {
      return Promise.reject(new Error('Fork in progress'));
    }

    let proc = this._proc = child_process.fork(J5_CLIENT_PATH, {
      env: {
        DIGS_ID: this.id,
        DIGS_PROJECT: this._server.project,
        DIGS_NAMESPACE: this._server.namespace,
        DIGS_FSQ_DIR: this._opts.ascoltatoriOpts.fsq_dir,
        DIGS_BOARD_CONFIG: JSON.stringify(this._boardOpts)
      }
    });

    proc.on('message', function(message) {
      switch (message.type) {
        case 'log':
          switch (message.level) {
            case 'debug':
              this.log(message.msg[0]);
              break;
          }
          break;
      }
    }.bind(this));

    this.subscribe(`${this.id}/+`, function(topic, data) {
      let event = topic.split('/').slice(1).join('/');
      this.log(`${this}: Received "${topic}"; emitting event "${event}" ` +
        `with data: ${JSON.stringify(data)}`);
      this.emit(event, data);
    });

    return this.when('ready', 'error')
      .bind(this)
      .then(function(data) {
        this.port = data.port;
        this._ready = true;

        //this._digs.subscribe({
        //  clientId: this.id,
        //  wildcard: '+'
        //}, function(topic, message) {
        //  this.publishAsync(topic, message)
        //    .then(function() {
        //      debug(`${this}: Published topic "${topic}" across qlobber-fsq`);
        //    });
        //}.bind(this._ascoltatore));

        this.log(`${this}: Board "${data.id}" is ready on port ${this.port}`);
      });
  }

  subscribe(topic, callback) {
    return this._ascoltatore.subscribe(topic, callback.bind(this));
  }

  publish(topic, data) {
    topic = `${this.id}-local/${topic}`;
    this.log(`${this} (FSQ): Publishing topic "${topic}" with data`, data);
    return this._ascoltatore.publishAsync(topic, data);
  }

  get online() {
    let starting = this._starting;
    let proc = this._proc;
    return proc && proc.connected && starting && starting.isFulfilled();
  }

  get starting() {
    let starting = this._starting;
    let proc = this._proc;
    return proc && proc.connected && starting &&
      !(starting.isFulfilled() && starting.isRejected());
  }

  get status() {
    if (this.online) {
      return 'online';
    } else if (this.starting) {
      return 'starting';
    }
    return 'stopped';
  }

  toJSON() {
    return {
      id: this.id,
      name: this.name,
      description: this.description,
      port: this.port,
      components: this.components
    };
  }

}

module.exports = SerialDevice;
<|endoftext|>