control-freak-ide/server/nodejs/nxapp/manager/PreExecutorEmbedded.js
plastic-hub-dev-node-saturn 538369cff7 latest
2021-05-12 18:35:18 +02:00

594 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

define([
'dojo/aspect',
'dojo/has',
'dojo/lang',
'dojo/Promise',
//'dojo/Deferred',
'dojo/has!host-browser?dojo/request',
'dojo/has!host-browser?dojo/io-query',
'dojo/has!host-node?dojo/node!path',
'../../main',
'../parseArgs',
'../util',
'require',
'module'
], function (aspect, has, lang, Promise,request, ioQuery, pathUtil, main, parseArgs, util, require,module) {
/**
* For testing sessions running through the Intern proxy, tells the remote test system that an error occured when
* attempting to set up this environment.
*
* @function
* @param {Error} error
*/
var sendErrorToConduit = (function () {
var sequence = 0;
return function (error) {
var sessionIdFromUrl = /[?&]sessionId=([^&]+)/.exec(location.search);
if (!sessionIdFromUrl) {
return;
}
var sessionId = decodeURIComponent(sessionIdFromUrl[1]);
// Proxy expects data to be an array of serialized objects
var data = [
JSON.stringify({
sequence: sequence,
sessionId: sessionId,
payload: [
'fatalError',
// Non-standard `sessionId` property is used by ClientSuite in the test runner to associate
// a fatal error with a particular environment
{ name: error.name, message: error.message, stack: error.stack, sessionId: sessionId }
]
})
];
request(require.toUrl('intern/'), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
// The sequence must not be incremented until after the data is successfully serialised, since an error
// during serialisation might occur, which would mean the request is never sent, which would mean the
// dispatcher on the server-side will stall because the sequence numbering will be wrong
++sequence;
};
})();
/**
* The PreExecutor executor handles loading the users configuration and setting up the environment with the proper
* AMD loader.
*
* @constructor
* @param {Object} kwArgs
*/
function PreExecutor(kwArgs) {
this.defaultLoaderOptions = kwArgs.defaultLoaderOptions;
var executorId = kwArgs.executorId;
if (executorId.indexOf('/') === -1) {
executorId = executorId.charAt(0).toUpperCase() + executorId.slice(1);
executorId = require.toAbsMid('./' + executorId);
}
this.executorId = executorId;
this.config = kwArgs.config;
}
PreExecutor.prototype = {
constructor: PreExecutor,
/**
* Default loader configuration that needs to be passed to the new loader.
*
* @type {Object}
*/
defaultLoaderOptions: null,
_earlyErrorHandle: null,
_earlyEvents: null,
/**
* The module ID of the executor to load.
*
* @type {string}
*/
executorId: null,
/**
* Gets arguments from the command-line/query-string.
*
* @returns {Object}
*/
getArguments: function () {
var kwArgs;
if (has('host-browser')) {
kwArgs = parseArgs.fromQueryString(location.search);
}
else if (has('host-node')) {
kwArgs = parseArgs.fromCommandLine(process.argv.slice(2));
}
[ 'environments', 'functionalSuites', 'reporters', 'suites' ].forEach(function (name) {
var value = kwArgs[name];
if (value != null && !Array.isArray(value)) {
kwArgs[name] = value === '' ? [] : [ value ];
}
});
if (kwArgs.excludeInstrumentation === 'true') {
kwArgs.excludeInstrumentation = true;
}
else if (typeof kwArgs.excludeInstrumentation === 'string') {
kwArgs.excludeInstrumentation = new RegExp(kwArgs.excludeInstrumentation);
}
this.getArguments = function () {
return kwArgs;
};
return kwArgs;
},
/**
* Gets the users configuration.
*
* @returns {Promise.<Object>}
*/
getConfig: function (args) {
var self = this;
var config = this.config;
if (!config) {
throw new Error(module.id + ' :Missing missing property "config"');
}
/* jshint maxcomplexity:20 */
config = lang.deepMixin(config, args);
if (typeof config.loader === 'object') {
self._earlyEvents.push([
'deprecated',
'The "loader" configuration option',
'"loaderOptions"'
]);
config.loaderOptions = config.loader;
}
if (typeof config.useLoader === 'object') {
self._earlyEvents.push([
'deprecated',
'The "useLoader" configuration option',
'"loaders"'
]);
config.loaders = config.useLoader;
}
config.loaderOptions = config.loaderOptions || {};
var isAbsoluteBaseUrl;
if (has('host-node')) {
if (config.basePath == null) {
config.basePath = process.cwd();
}
config.basePath = util.normalizePath(config.basePath);
if (config.basePath.charAt(config.basePath.length - 1) !== '/') {
config.basePath += '/';
}
// The crappy fallback function is for Node 0.10; remove it when Node 0.10 is officially dropped
isAbsoluteBaseUrl = pathUtil.isAbsolute || function (path) {
if (pathUtil.sep === '/') {
return path.charAt(0) === '/';
}
else {
return /^\w+:/.test(path);
}
};
}
else if (has('host-browser')) {
(function () {
var defaultBasePath = config.initialBaseUrl ||
// replacing `/node_modules/intern/client.html` with `/`, allowing for directory name
// derivatives
util.normalizePath(location.pathname.replace(/(?:\/+[^\/]*){3}\/?$/, '/'));
if (config.basePath == null) {
config.basePath = defaultBasePath;
}
else if (config.basePath.charAt(0) === '.') {
config.basePath = util.normalizePath(defaultBasePath + config.basePath);
}
if (config.basePath.charAt(config.basePath.length - 1) !== '/') {
config.basePath += '/';
}
})();
isAbsoluteBaseUrl = function (url) {
// Detect both schema-ed and schema-less URLs
return /^(?:\w+:|\/\/\w)/.test(url);
};
if (args.loaders && args.loaders['host-browser']) {
util.assertSafeModuleId(args.loaders['host-browser']);
}
}
// If the baseUrl is unset, then it will be the default from client.html or the cwd, which would be
// inconsistent
if (!config.loaderOptions.baseUrl) {
config.loaderOptions.baseUrl = config.basePath;
}
// non-absolute loader baseUrl needs to be fixed up to be relative to the defined basePath, not to
// client.html or process.cwd()
else if (!isAbsoluteBaseUrl(config.loaderOptions.baseUrl)) {
config.loaderOptions.baseUrl = util.normalizePath(config.basePath + config.loaderOptions.baseUrl);
}
if (config.grep == null) {
config.grep = new RegExp('');
}
else {
var grep = /^\/(.*)\/([gim]*)$/.exec(config.grep);
if (grep) {
config.grep = new RegExp(grep[1], grep[2]);
}
else {
config.grep = new RegExp(config.grep, 'i');
}
}
config.instrumenterOptions = config.instrumenterOptions || {};
if (config.coverageVariable) {
config.instrumenterOptions.coverageVariable = config.coverageVariable;
}
if (config.proxyPort == null) {
config.proxyPort = 9000;
}
else if (typeof config.proxyPort === 'string') {
if (isNaN(config.proxyPort)) {
throw new Error('proxyPort must be a number');
}
config.proxyPort = Number(config.proxyPort);
}
// If the user doesn't specify a proxy URL, construct one using the proxy port.
if (config.proxyUrl == null) {
config.proxyUrl = 'http://localhost:' + config.proxyPort + '/';
}
var promise = new Promise(function(resolve){
resolve(config);
});
this.getConfig = function () {
return promise;
};
return promise;
},
/**
* Handles errors that occur during the pre-execution sequence.
*
* @param {Error} error
*/
_handleError: function (error) {
if (has('host-browser')) {
if (location.pathname.replace(/\/+[^\/]*$/, '/').slice(-10) === '/__intern/') {
sendErrorToConduit(error);
}
var htmlError = util.getErrorMessage(error).replace(/&/g, '&amp;').replace(/</g, '&lt;');
var errorNode = document.createElement('div');
errorNode.style.cssText = 'color: red; font-family: sans-serif;';
errorNode.innerHTML = '<h1>Fatal error during pre-execution stage</h1>' +
'<pre style="padding: 1em; background-color: #f0f0f0;">' + htmlError + '</pre>';
document.body.appendChild(errorNode);
}
else /* istanbul ignore else */ if (typeof console !== 'undefined') {
console.error(util.getErrorMessage(error));
// TODO: The loader needs to be fixed to allow errbacks to `require` calls so we dont just exit on
// early error but can instead propagate loader errors through the `PreExecutor#run` promise chain
if (has('host-node')) {
process.exit(1);
}
}
},
/**
* Loads the constructor for the real executor for this test run via the final loader environment.
*
* @param {string} executorId The module ID of the executor.
* @param {Function} require An AMD loader `require` function.
* @returns {Promise.<Function>} Executor constructor.
*/
_loadExecutorWithLoader: function (executorId, require) {
return new Promise(function (resolve, reject) {
require([ executorId ], resolve, reject);
});
},
/**
* Registers a global error handler.
*
* @param {(error:Error) => void} handler
* @returns {{ remove: () => void }}
*/
registerErrorHandler: function (handler) {
if (this._earlyErrorHandle) {
this._earlyErrorHandle.remove();
this._earlyErrorHandle = null;
}
if (has('host-browser')) {
/* jshint browser:true */
return aspect.before(window, 'onerror', function (message, url, lineNumber, columnNumber, error) {
error = error || new Error(message + ' at ' + url + ':' + lineNumber +
(columnNumber !== undefined ? ':' + columnNumber : ''));
handler(error);
});
}
else if (has('host-node')) {
/* jshint node:true */
process.on('uncaughtException', function (error) {
handler(error);
});
return {
remove: function () {
this.remove = function () {};
process.removeListener('uncaughtException', handler);
}
};
}
},
/**
* Runs the test executor.
*/
run: function () {
var self = this;
var args = this.getArguments();
var config;
var earlyErrorHandler = lang.bind(this, '_handleError');
var executor;
this._earlyErrorHandle = this.registerErrorHandler(earlyErrorHandler);
this._earlyEvents = [];
// TODO: Eliminate main.args, main.config, and main.mode in a future release
var executionMode = (function (id) {
if (id === require.toAbsMid('./Client')) {
return 'client';
}
else if (id === require.toAbsMid('./Runner')) {
return 'runner';
}
else {
return 'custom';
}
})(this.executorId);
// These values must be populated on the main module prior to loading the configuration module because
// the configuration module may depend on them in order to perform configuration
main.args = args;
main.mode = executionMode;
main.config = config;
function getConfig() {
return self.getConfig(args).then(function (_config) {
config = _config;
});
}
function loadExecutorWithLoader(loader) {
return self._loadExecutorWithLoader(self.executorId, loader);
}
function populateMainModule(loader) {
return util.getModule('intern/main').then(function (main) {
// The main module needs to be repopulated here because a loader swap may have occurred,
// in which case this main module is not the same as the main module loaded as a dependency of
// PreExecutor
main.args = args;
main.mode = executionMode;
main.config = config;
return loader;
});
}
/**
* Expand any globs in the suites and functionalSuites lists
*/
function resolveSuites(loader) {
var promise;
if (has('host-node')) {
promise = new Promise(function (resolve) {
config.suites = util.resolveModuleIds(config.suites);
config.functionalSuites = util.resolveModuleIds(config.functionalSuites);
resolve();
});
}
// Only try to g
else if (has('host-browser') && config.suites.some(util.isGlobModuleId)) {
var query = ioQuery.objectToQuery({ suites: JSON.stringify(config.suites) });
var url = require.toUrl('intern/__resolveSuites__') + '?' + query;
promise = request(url, {
method: 'GET'
}).then(function (response) {
if (response.statusCode === 200 && response.data) {
config.suites = JSON.parse(response.data);
}
else {
throw Error('Error resolving suites -- Intern proxy is not available or ' +
'did not return data');
}
});
}
else {
promise = Promise.resolve();
}
return promise.then(function () {
// pass-through the loader argument
return loader;
});
}
function runExecutor(Executor) {
executor = new Executor(config, self);
self._earlyEvents.forEach(function (event) {
executor.reporterManager.emit.apply(executor.reporterManager, event);
});
return executor.run();
}
function swapLoader() {
return self.swapLoader(config.basePath, config.loaders, config.loaderOptions);
}
var promise = Promise.resolve()
.then(getConfig)
.then(swapLoader)
.then(resolveSuites)
.then(populateMainModule)
.then(loadExecutorWithLoader)
.then(runExecutor)
['catch'](function (error) {
// a fatal error hasn't been reported -- ensure the user is notified
if (!error.reported) {
earlyErrorHandler(error);
}
throw error;
});
this.run = function () {
return promise;
};
return promise;
},
/**
* Swaps the current AMD loader with a different AMD loader.
*
* @param {{ host-node?: string, host-browser?: string }} loaders Paths to loaders for different environments,
* relative to the user configuration module ID.
* @param {Object} loaderOptions AMD loader configuration object.
* @returns {Promise.<Function>} A promise that resolves to an AMD `require` function.
*/
swapLoader: function (basePath, loaders, loaderOptions) {
loaders = loaders || {};
var self = this;
var global = (function () {
return this;
})();
return new Promise(function (resolve, reject) {
if (has('host-node') && loaders['host-node']) {
var require = global.require.nodeRequire;
// Someone is attempting to use the loader module that has already been loaded. If we were to try
// loading again without deleting it from `require.cache`, Node.js would not re-execute the loader
// code (the module is cached), so the global `define` that is being undefined below will never be
// redefined. There is no reason to do anything more in this case; just use the already loaded
// loader as-is
if (require.cache[require.resolve(loaders['host-node'])]) {
resolve(global.require);
return;
}
global.require = global.define = undefined;
var id = loaders['host-node'];
var moduleUtil = require('module');
if (moduleUtil._findPath && moduleUtil._nodeModulePaths) {
var localModulePath = moduleUtil._findPath(id, moduleUtil._nodeModulePaths(basePath));
if (localModulePath !== false) {
id = localModulePath;
}
}
var amdRequire = require(id);
// The Dojo 1 loader does not export itself, it only exposes itself globally; in this case
// `amdRequire` is an empty object, not a function. Other loaders return themselves and do not
// expose globally. This hopefully covers all known loader cases
amdRequire = typeof amdRequire === 'function' ? amdRequire : global.require;
// Expose the require globally so dojo/node can hopefully find the original Node.js require;
// this is needed for at least RequireJS 2.1, which does not expose the global require
// to child modules
if (!global.require) {
global.require = amdRequire;
}
resolve(amdRequire);
}
else if (has('host-browser') && loaders['host-browser']) {
global.require = global.define = undefined;
var script = document.createElement('script');
script.onload = function () {
this.onload = this.onerror = null;
resolve(global.curl || global.requirejs || global.require);
};
script.onerror = function () {
this.parentNode.removeChild(this);
this.onload = this.onerror = null;
reject(new Error('Failed to load AMD loader from ' + script.src));
};
var loaderUrl = loaders['host-browser'];
if (!util.isAbsoluteUrl(loaderUrl)) {
loaderUrl = basePath + loaderUrl;
}
script.src = loaderUrl;
document.head.appendChild(script);
}
else {
resolve(global.require);
}
}).then(function (loader) {
var setConfig = loader.config ? loader.config.bind(loader) : loader;
setConfig(self.defaultLoaderOptions);
if (loaderOptions) {
if (
loaderOptions.map && loaderOptions.map['*'] &&
self.defaultLoaderOptions && self.defaultLoaderOptions.map && self.defaultLoaderOptions.map['*']
) {
var userStarMap = loaderOptions.map['*'];
var defaultStarMap = self.defaultLoaderOptions.map['*'];
for (var key in defaultStarMap) {
if (!(key in userStarMap)) {
userStarMap[key] = defaultStarMap[key];
}
}
}
setConfig(loaderOptions);
}
return loader;
});
}
};
return PreExecutor;
});