Adding real time logging and other improvements This closes #1
Project: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/commit/b1aa699d Tree: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/tree/b1aa699d Diff: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/diff/b1aa699d Branch: refs/heads/master Commit: b1aa699dfa56d442e75294448e3253ac6c7f0995 Parents: 8615f31 Author: Vladimir Kotikov <kotikov.vladi...@gmail.com> Authored: Fri Mar 25 13:54:21 2016 +0300 Committer: sgrebnov <sergei.greb...@gmail.com> Committed: Fri Mar 25 17:04:26 2016 +0300 ---------------------------------------------------------------------- .jshintrc | 14 + .travis.yml | 1 + README.md | 164 +++++++++- lib/LocalServer.js | 99 ++++++ lib/ParamedicConfig.js | 83 +++++ lib/PluginsManager.js | 51 ++++ lib/Target.js | 9 + lib/TestsRunner.js | 121 ++++++++ lib/Tunnel.js | 19 ++ lib/logger.js | 150 +++++++++ lib/paramedic.js | 156 ++++++++++ lib/portScanner.js | 54 ++++ lib/reporters/ConsoleReporter.js | 153 ++++++++++ lib/reporters/ParamedicReporter.js | 43 +++ lib/specReporters.js | 68 +++++ lib/utils.js | 22 ++ main.js | 69 +++-- package.json | 24 +- paramedic-plugin/JasmineParamedicProxy.js | 56 ++++ paramedic-plugin/paramedic.js | 74 +++++ paramedic-plugin/plugin.xml | 37 +++ paramedic-plugin/socket.io.js | 4 + paramedic.js | 305 ------------------- sample-config/.paramedic-core-plugins.config.js | 51 ++++ sample-config/.paramedic.config.js | 33 ++ 25 files changed, 1502 insertions(+), 358 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/.jshintrc ---------------------------------------------------------------------- diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..e11705f --- /dev/null +++ b/.jshintrc @@ -0,0 +1,14 @@ +{ + "node" : true + , "devel": true + , "bitwise": true + , "undef": true + , "trailing": true + , "quotmark": false + , "indent": 4 + , "unused": "vars" + , "expr": true + , "latedef": "nofunc" + , "globals": { + } +} http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/.travis.yml ---------------------------------------------------------------------- diff --git a/.travis.yml b/.travis.yml index 3c02467..aa2e054 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ install: - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - npm install cordova - npm install ios-sim + - npm install ios-deploy - npm install - npm link script: http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 305feeb..4dcc055 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,167 @@ Runs cordova medic/buildbot tests locally. ... provides advanced levels of care at the point of illness or injury, including out of hospital treatment, and diagnostic services -To install : +# To install : ``` $npm install cordova-paramedic ``` -Usage : +## Supported Cordova Platforms + +- Android +- iOS +- Windows Phone 8 +- Windows (Windows 8.1, Windows Phone 8.1, Windows 10 Tablet/PC) +- Browser + +# Usage + +Paramedic parameters could be passed via command line arguments or via separate configuration file: + +``` +cordova-paramedic --platform PLATFORM --plugin PATH <other parameters> +cordova-paramedic --config ./sample-config/.paramedic.config.js +``` + +## Command Line Interface + +####`--platform` (required) + +Specifies target cordova platform (could refer to local directory, npm or git) + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser +cordova-paramedic --platform ios@4.0 --plugin cordova-plugin-inappbrowser +cordova-paramedic --platform ios@../cordova-ios --plugin cordova-plugin-inappbrowser +cordova-paramedic --platform ios@https://github.com/apache/cordova-ios.git#4.1.0 --plugin cordova-plugin-inappbrowser +``` + +####`--plugin` (required) + +Specifies test plugin, you may specify multiple --plugin flags and they will all be installed and tested together. Similat to `platform` parameter you can refer to local (or absolute) path, npm registry or git repo. ``` -cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --port PORTNUM --browserify --verbose ]`PLATFORM` : the platform id, currently only supports 'ios' -`PATH` : the relative or absolute path to a plugin folder - expected to have a 'tests' folder. - You may specify multiple --plugin flags and they will all - be installed and tested together. -`MSECS` : (optional) time in millisecs to wait for tests to pass|fail - (defaults to 10 minutes) -`PORTNUM` : (optional) port to use for posting results from emulator back to paramedic server ---justbuild : (optional) just builds the project, without running the tests ---browserify : (optional) plugins are browserified into cordova.js ---verbose : (optional) verbose mode. Display more information output +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser +cordova-paramedic --platform ios --plugin ../cordova-plugin-inappbrowser +cordova-paramedic --platform ios --plugin https://github.com/apache/cordova-plugin-inappbrowser +// several plugins +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --plugin cordova-plugin-contacts +``` +####--justbuild (optional) + +Just builds the project, without running the tests. + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --justbuild +``` + +####--device (optional) + +Tests must be run on connected device instead of emulator. + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --device +``` + +####--externalServerUrl (optional) + +Useful when testing on real device (`--device` parameter) so that tests results from device could be posted back to paramedic server. + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --externalServerUrl http://10.0.8.254 +``` + +####--useTunnel (optional) + +Use [tunneling](https://www.npmjs.com/package/localtunnel) instead of local address (default is false). +Useful when testing on real devices and don't want to specify external ip address (see `--externalServerUrl` above) of paramedic server. ``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --useTunnel +``` + +####--browserify (optional) + +Plugins are browserified into cordova.js. + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --browserify +``` + +####--port (optional) + +Port to use for posting results from emulator back to paramedic server (default is from `8008`). You can also specify a range using `--startport` and `endport` and paramedic will select the first available. + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --port 8010 +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --startport 8000 endport 8020 +``` + +####--verbose (optional) + +Verbose mode. Display more information output + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --verbose +``` + +####--timeout (optional) + +Time in millisecs to wait for tests to pass|fail (defaults to 10 minutes). + +``` +cordova-paramedic --platform ios --plugin cordova-plugin-inappbrowser --timeout 30000 +``` + +## Paramedic configuration file + +Configuration file is used when no parameters are passed to `cordova-paramedic` call or explicitly specified via `--config` parameter: +``` +cordova-paramedic <- paramedic will attempt to find .paramedic.config.js in working directory +cordova-paramedic --config ./sample-config/.paramedic.config.js +``` +Example configuration file is showed below. It supports similar arguments and has the following advantages over `Command Line Approach`: + +- Supports extra arguments which could be passed to cordova so that you have full control over build and run target. +- Supports several test platforms (targets) to be executed as single paramedic run (results will be aggregated) so you don't need to re-install test plugins, create local server and do other steps several times. + +``` +module.exports = { + // "externalServerUrl": "http://10.0.8.254", + "useTunnel": true, + "plugins": [ + "https://github.com/apache/cordova-plugin-inappbrowser" + ], + "targets": [ + { + "platform": "ios@https://github.com/apache/cordova-ios.git", + "action": "run", + "args": "--device" + }, + { + "platform": "android@https://github.com/apache/cordova-android.git", + "action": "run", + "args": "--device" + }, + { // Windows 8.1 Desktop(anycpu) + "platform": "windows@https://github.com/apache/cordova-windows.git", + "action": "run" + }, + { // Windows 10 Desktop(x64) + "platform": "windows@https://github.com/apache/cordova-windows.git", + "action": "run", + "args": "--archs=x64 -- --appx=uap" + } + ] +} +``` +More configuration file examples could be found in `sample-config` folder. + +## API Interface You can also use cordova-paramedic as a module directly : ``` var paramedic = require('cordova-paramedic'); - paramedic.run('ios', '../cordova-plugin-device', onCompleteCallback,justBuild,portNum,msTimeout, useBrowserify, beSilent, beVerbose); + paramedic.run(config); ``` http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/LocalServer.js ---------------------------------------------------------------------- diff --git a/lib/LocalServer.js b/lib/LocalServer.js new file mode 100644 index 0000000..a7bf42c --- /dev/null +++ b/lib/LocalServer.js @@ -0,0 +1,99 @@ +var Q = require('q'); +var io = require('socket.io'); + +var logger = require('./logger').get(); + +var specReporters = require('./specReporters'); + + +function LocalServer(port, externalServerUrl, tunneledUrl) { + this.port = port; + this.tunneledUrl = tunneledUrl; + this.externalServerUrl = externalServerUrl; + this.onTestsResults = null; +} + +LocalServer.startServer = function(port, externalServerUrl, tunneledUrl) { + var localServer = new LocalServer(port, externalServerUrl, tunneledUrl); + localServer.createSocketListener(); + return Q.resolve(localServer); +}; + +LocalServer.prototype.createSocketListener = function() { + var listener = io.listen(this.port, { + pingTimeout: 60000, // how many ms without a pong packet to consider the connection closed + pingInterval: 25000 // how many ms before sending a new ping packet + }); + + var me = this; + + var routes = { + 'log': me.onDeviceLog.bind(me), + 'disconnect': me.onTestsCompletedOrDisconnected.bind(me), + 'deviceInfo': me.onDeviceInfo.bind(me), + 'jasmineStarted': specReporters.jasmineStarted.bind(specReporters), + 'specStarted': specReporters.specStarted.bind(specReporters), + 'specDone': specReporters.specDone.bind(specReporters), + 'suiteStarted': specReporters.suiteStarted.bind(specReporters), + 'suiteDone': specReporters.suiteDone.bind(specReporters), + 'jasmineDone': me.onJasmineDone.bind(me) + }; + + listener.on('connection', function(socket) { + logger.info('local-server: new socket connection'); + me.connection = socket; + + for (var routeType in routes) { + socket.on(routeType, routes[routeType]); + } + }); +}; + +LocalServer.prototype.haveConnectionUrl = function() { + return !!(this.tunneledUrl || this.externalServerUrl); +}; + +LocalServer.prototype.getConnectionUrl = function() { + return this.tunneledUrl || this.externalServerUrl + ":" + this.port; +}; + +LocalServer.prototype.reset = function() { + this.onTestsResults = null; + if (this.connection) { + this.connection.disconnect(); + this.connection = null; + } + + specReporters.reset(); +}; + +LocalServer.prototype.onDeviceLog = function(data) { + logger.verbose('device|console.'+data.type + ': ' + data.msg[0]); +}; + +LocalServer.prototype.onDeviceInfo = function(data) { + logger.info('cordova-paramedic: Device info: ' + JSON.stringify(data)); +}; + +LocalServer.prototype.onTestsCompleted = function(msg) { + logger.normal('local-server: tests completed'); + this.lastMobileSpecResults = specReporters.getResults(); +}; + +LocalServer.prototype.onJasmineDone = function(data) { + specReporters.jasmineDone(data); + // save results to report them later + this.onTestsCompleted(); + // disconnect because all tests have been completed + this.connection.disconnect(); +}; + +LocalServer.prototype.onTestsCompletedOrDisconnected = function() { + logger.info('local-server: tests have been completed or test device has disconnected'); + if (this.onTestsResults) { + this.onTestsResults(this.lastMobileSpecResults); + } + this.lastMobileSpecResults = null; +}; + +module.exports = LocalServer; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/ParamedicConfig.js ---------------------------------------------------------------------- diff --git a/lib/ParamedicConfig.js b/lib/ParamedicConfig.js new file mode 100644 index 0000000..d9acdec --- /dev/null +++ b/lib/ParamedicConfig.js @@ -0,0 +1,83 @@ +var Target = require('./Target'); + +var DEFAULT_START_PORT = 8008; +var DEFAULT_END_PORT = 8018; +var DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes in msec - this will become a param + +function ParamedicConfig(json) { + this._config = json; +} + +ParamedicConfig.parseFromArguments = function (argv) { + return new ParamedicConfig({ + "targets": [{ + platform: argv.platform, + action: !!argv.justbuild ? 'build' : 'run', + args: (!!argv.browserify ? '--browserify ' : '') + (!!argv.device ? '--device' : '') + }], + "plugins": Array.isArray(argv.plugin) ? argv.plugin : [argv.plugin], + "useTunnel": !!argv.useTunnel, + "verbose": !!argv.verbose, + "startPort": argv.startport || argv.port, + "endPort": argv.endport || argv.port, + "externalServerUrl": argv.externalServerUrl, + "reportSavePath": !!argv.reportSavePath? argv.reportSavePath: undefined, + "cleanUpAfterRun": !!argv.cleanUpAfterRun? true: false + }); +}; + +ParamedicConfig.parseFromFile = function (paramedicConfigPath) { + return new ParamedicConfig(require(paramedicConfigPath)); +}; + +ParamedicConfig.prototype.useTunnel = function () { + return this._config.useTunnel; + +}; + +ParamedicConfig.prototype.getReportSavePath = function () { + return this._config.reportSavePath; +}; + +ParamedicConfig.prototype.shouldCleanUpAfterRun = function () { + return this._config.cleanUpAfterRun; +}; + +ParamedicConfig.prototype.getTargets = function () { + return this._config.targets.map(function(target) { + return new Target(target); + }); +}; + +ParamedicConfig.prototype.getPlugins = function () { + return this._config.plugins; +}; + +ParamedicConfig.prototype.getExternalServerUrl= function () { + return this._config.externalServerUrl; +}; + +ParamedicConfig.prototype.isVerbose = function() { + return this._config.verbose; +}; + +ParamedicConfig.prototype.isJustBuild = function() { + return this._config.justbuild; +}; + +ParamedicConfig.prototype.isBrowserify = function() { + return this._config.browserify; +}; + +ParamedicConfig.prototype.getPorts = function() { + return { + start: this._config.startPort || DEFAULT_START_PORT, + end: this._config.endPort || DEFAULT_END_PORT + }; +}; + +ParamedicConfig.prototype.getTimeout = function() { + return DEFAULT_TIMEOUT; +}; + +module.exports = ParamedicConfig; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/PluginsManager.js ---------------------------------------------------------------------- diff --git a/lib/PluginsManager.js b/lib/PluginsManager.js new file mode 100644 index 0000000..3d4c441 --- /dev/null +++ b/lib/PluginsManager.js @@ -0,0 +1,51 @@ +var path = require('path'); +var fs = require('fs'); +var logger = require('./logger').get(); +var exec = require('./utils').exec; +var PluginInfoProvider = require('cordova-common').PluginInfoProvider; + +function PluginsManager(appRoot, storedCWD) { + this.appRoot = appRoot; + this.storedCWD = storedCWD; +} + +PluginsManager.prototype.installPlugins = function(plugins) { + for(var n = 0; n < plugins.length; n++) { + var plugin = plugins[n]; + this.installSinglePlugin(plugin); + } +}; + +PluginsManager.prototype.installTestsForExistingPlugins = function() { + var installedPlugins = new PluginInfoProvider().getAllWithinSearchPath(path.join(this.appRoot, 'plugins')); + var me = this; + installedPlugins.forEach(function(plugin) { + // there is test plugin available + if (fs.existsSync(path.join(plugin.dir, 'tests', 'plugin.xml'))) { + me.installSinglePlugin(path.join(plugin.dir, 'tests')); + } + }); + this.showPluginsVersions(); +}; + +PluginsManager.prototype.installSinglePlugin = function(plugin) { + if (fs.existsSync(path.resolve(this.storedCWD, plugin))) { + plugin = path.resolve(this.storedCWD, plugin); + } + + logger.normal("cordova-paramedic: installing " + plugin); + // var pluginPath = path.resolve(this.storedCWD, plugin); + + var plugAddCmd = exec('cordova plugin add ' + plugin); + if(plugAddCmd.code !== 0) { + logger.error('Failed to install plugin : ' + plugin); + throw new Error('Failed to install plugin : ' + plugin); + } +}; + +PluginsManager.prototype.showPluginsVersions = function() { + logger.verbose("cordova-paramedic: versions of installed plugins: "); + exec('cordova plugins'); +}; + +module.exports = PluginsManager; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/Target.js ---------------------------------------------------------------------- diff --git a/lib/Target.js b/lib/Target.js new file mode 100644 index 0000000..0cb5c53 --- /dev/null +++ b/lib/Target.js @@ -0,0 +1,9 @@ + +function Target(config) { + this.platform = config.platform; + this.action = config.action || 'run'; + this.args = config.args || null; + this.platformId = this.platform.split("@")[0]; +} + +module.exports = Target; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/TestsRunner.js ---------------------------------------------------------------------- diff --git a/lib/TestsRunner.js b/lib/TestsRunner.js new file mode 100644 index 0000000..8515b58 --- /dev/null +++ b/lib/TestsRunner.js @@ -0,0 +1,121 @@ +var Q = require('q'); +var fs = require('fs'); +var path = require('path'); +var exec = require('./utils').exec; +var logger = require('./logger').get(); + +var MAX_PENDING_TIME = 60000; + +function TestsRunner (server) { + this.server = server; +} + +TestsRunner.prototype.runSingleTarget = function (target, savePath) { + logger.info("Running target: " + target.platform); + + var me = this; + + this.server.reset(savePath); + + return this.prepareAppToRunTarget(target).then(function() { + return me.installPlatform(target); + }).then(function() { + return me.checkPlatform(target); + }).then(function() { + return me.runTests(target); + }) + .then(function (results) { + logger.normal("Removing platform: " + target.platformId); + exec('cordova platform rm ' + target.platformId); + + return results; + }) + .catch(function(error) {return error;}); +}; + +TestsRunner.prototype.prepareAppToRunTarget = function(target) { + var me = this; + return Q.Promise(function(resolve, reject) { + // if we know external url we can safely use it + if (me.server.haveConnectionUrl()) { + me.writeMedicLogUrl(me.server.getConnectionUrl()); + } else { + // otherwise, we assume we use local PC and platforms emulators + switch(target.platformId) { + case "android": + me.writeMedicLogUrl("http://10.0.2.2:" + me.server.port); + break; + case "ios" : + case "browser" : + case "windows" : + /* falls through */ + default: + me.writeMedicLogUrl("http://127.0.0.1:" + me.server.port); + break; + } + } + resolve(); + }); +}; + +TestsRunner.prototype.installPlatform = function(target) { + return Q.Promise(function(resolve, reject) { + logger.normal("cordova-paramedic: adding platform : " + target.platform); + exec('cordova platform add ' + target.platform); + resolve(); + }); +}; + +TestsRunner.prototype.checkPlatform = function(target) { + return Q.Promise(function(resolve, reject) { + logger.normal("cordova-paramedic: checking requirements for platform " + target.platformId); + var result = exec('cordova requirements ' + target.platformId); + if (result.code !== 0) { + reject(new Error('Platform requirements check has failed! Skipping...')); + } else resolve(); + }); +}; + +TestsRunner.prototype.writeMedicLogUrl = function(url) { + logger.normal("cordova-paramedic: writing medic log url to project " + url); + var obj = {logurl:url}; + fs.writeFileSync(path.join("www","medic.json"), JSON.stringify(obj)); +}; + +TestsRunner.prototype.runTests = function(target) { + logger.normal('Starting tests'); + var self = this; + + var cmd = "cordova " + target.action + " " + target.platformId; + if (target.args) { + cmd += " " + target.args; + } + + logger.normal('cordova-paramedic: running command ' + cmd); + + return Q.Promise(function (resolve, reject) { + logger.normal('Waiting for tests result'); + + self.server.onTestsResults = function (results) { + resolve(results); + }; + + exec(cmd, function(code, output){ + if(code) { + reject(new Error("cordova build returned error code " + code)); + } + + var waitForTestResults = target.action === 'run' || target.action === 'emulate'; + + if (!waitForTestResults) resolve({passed: true}); // skip tests if it was justbuild + + setTimeout(function(){ + if (!self.server.connection) + reject(new Error("Seems like device not connected to local server in " + MAX_PENDING_TIME / 1000 + " secs. Skipping this platform...")); + }, MAX_PENDING_TIME); + } + ); + }); +}; + +module.exports = TestsRunner; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/Tunnel.js ---------------------------------------------------------------------- diff --git a/lib/Tunnel.js b/lib/Tunnel.js new file mode 100644 index 0000000..bed31e5 --- /dev/null +++ b/lib/Tunnel.js @@ -0,0 +1,19 @@ + var exec = require('./utils').exec; + var path = require('path'); + var Q = require('Q'); + +function Tunnel(port) { + this.port = port; +} + +Tunnel.prototype.createTunnel = function() { + var self = this; + //TODO: use localtunnel module instead of shell + return Q.Promise(function(resolve, reject) { + exec(path.resolve(__dirname, '../node_modules/.bin/lt') + ' --port ' + self.port, null, function(output) { + resolve(output.split(' ')[3]); + }); + }); +}; + +module.exports = Tunnel; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/logger.js ---------------------------------------------------------------------- diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..6a525c9 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,150 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +var ansi = require('ansi'); +var EventEmitter = require('events').EventEmitter; +var CordovaError = require('cordova-common').CordovaError; +var EOL = require('os').EOL; + +var INSTANCE; + +function CordovaLogger () { + this.levels = {}; + this.colors = {}; + this.stdout = process.stdout; + this.stderr = process.stderr; + + this.stdoutCursor = ansi(this.stdout); + this.stderrCursor = ansi(this.stderr); + + this.addLevel('verbose', 1000, 'grey'); + this.addLevel('normal' , 2000); + this.addLevel('warn' , 2000, 'yellow'); + this.addLevel('info' , 3000, 'blue'); + this.addLevel('error' , 5000, 'red'); + this.addLevel('results' , 10000); + + this.setLevel('normal'); +} + +CordovaLogger.get = function () { + return INSTANCE || (INSTANCE = new CordovaLogger()); +}; + +CordovaLogger.VERBOSE = 'verbose'; +CordovaLogger.NORMAL = 'normal'; +CordovaLogger.WARN = 'warn'; +CordovaLogger.INFO = 'info'; +CordovaLogger.ERROR = 'error'; +CordovaLogger.RESULTS = 'results'; + +CordovaLogger.prototype.log = function (logLevel, message) { + // if there is no such logLevel defined, or provided level has + // less severity than active level, then just ignore this call and return + if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) + // return instance to allow to chain calls + return this; + var isVerbose = this.logLevel === 'verbose'; + var cursor = this.stdoutCursor; + + if(message instanceof Error || logLevel === CordovaLogger.ERROR) { + message = formatError(message, isVerbose); + cursor = this.stderrCursor; + } + + var color = this.colors[logLevel]; + if (color) { + cursor.bold().fg[color](); + } + + cursor.write(message).reset().write(EOL); + + return this; +}; + +CordovaLogger.prototype.addLevel = function (level, severity, color) { + + this.levels[level] = severity; + + if (color) { + this.colors[level] = color; + } + + // Define own method with corresponding name + if (!this[level]) { + this[level] = this.log.bind(this, level); + } + + return this; +}; + +CordovaLogger.prototype.setLevel = function (logLevel) { + this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL; + + return this; +}; + +CordovaLogger.prototype.subscribe = function (eventEmitter) { + + if (!(eventEmitter instanceof EventEmitter)) + throw new Error('Must provide a valid EventEmitter instance to subscribe CordovaLogger to'); + + var self = this; + + process.on('uncaughtException', function(err) { + self.error(err); + process.exit(1); + }); + + eventEmitter.on('verbose', self.verbose) + .on('log', self.normal) + .on('info', self.info) + .on('warn', self.warn) + .on('warning', self.warn) + // Set up event handlers for logging and results emitted as events. + .on('results', self.results); + + return this; +}; + +function formatError(error, isVerbose) { + var message = ''; + + if(error instanceof CordovaError) { + message = error.toString(isVerbose); + } else if(error instanceof Error) { + if(isVerbose) { + message = error.stack; + } else { + message = error.message; + } + } else { + // Plain text error message + message = error; + } + + if(message.toUpperCase().indexOf('ERROR:') !== 0) { + // Needed for backward compatibility with external tools + message = 'Error: ' + message; + } + + return message; +} + +module.exports = CordovaLogger; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/paramedic.js ---------------------------------------------------------------------- diff --git a/lib/paramedic.js b/lib/paramedic.js new file mode 100644 index 0000000..3828cf2 --- /dev/null +++ b/lib/paramedic.js @@ -0,0 +1,156 @@ +#!/usr/bin/env node + +var exec = require('./utils').exec, + shell = require('shelljs'), + Server = require('./LocalServer'), + Q = require('q'), + tmp = require('tmp'), + PluginsManager = require('./PluginsManager'), + specReporters = require('./specReporters'), + TestsRunner = require('./TestsRunner'), + portScanner = require('./portScanner'), + path = require('path'), + Tunnel = require('./Tunnel'); + +var Q = require('q'); +var logger = require('./logger').get(); + +var TESTS_PASS = 0; +var TESTS_FAILURE = 1; +var NONTESTS_FAILURE = 2; + +function ParamedicRunner(config, _callback) { + this.tunneledUrl = ""; + this.tempFolder = null; + this.pluginsManager = null; + this.testsPassed = true; + + this.config = config; + + exec.setVerboseLevel(config.isVerbose()); + logger.setLevel(config.isVerbose() ? 'verbose' : 'normal'); +} + +ParamedicRunner.prototype = { + run: function() { + var cordovaVersion = exec('cordova --version'); + var npmVersion = exec('npm -v'); + + if (cordovaVersion.code || npmVersion.code) { + logger.error(cordovaVersion.output + npmVersion.output); + process.exit(1); + } + + logger.normal("cordova-paramedic: using cordova version " + cordovaVersion.output.replace('\n', '')); + logger.normal("cordova-paramedic: using npm version " + npmVersion.output.replace('\n', '')); + + var self = this; + + this.createTempProject(); + this.installPlugins(); + + // Set up start page for tests + logger.normal("cordova-paramedic: setting app start page to test page"); + shell.sed('-i', 'src="index.html"', 'src="cdvtests/index.html"', 'config.xml'); + + var startPort = this.config.getPorts().start, + endPort = this.config.getPorts().end; + logger.info("cordova-paramedic: scanning ports from " + startPort + " to " + endPort); + + // Initialize test reporters + specReporters.initialize(this.config); + + return portScanner.getFirstAvailablePort(startPort, endPort).then(function(port) { + self.port = port; + + logger.info("cordova-paramedic: port " + port + " is available"); + + if (self.config.useTunnel()) { + self.tunnel = new Tunnel(port); + logger.info('cordova-paramedic: attempt to create local tunnel'); + return self.tunnel.createTunnel(); + } + }).then(function(url) { + if (url) { + logger.info('cordova-paramedic: using tunneled url ' + url); + self.tunneledUrl = url; + } + + logger.info("cordova-paramedic: starting local medic server"); + return Server.startServer(self.port, self.config.getExternalServerUrl(), self.tunneledUrl); + }).then(function (server) { + + var testsRunner = new TestsRunner(server); + return self.config.getTargets().reduce(function (promise, target) { + return promise.then( function() { + return testsRunner.runSingleTarget(target).then(function(results) { + if (results instanceof Error) { + self.testsPassed = false; + return logger.error(results.message); + } + + logger.info("cordova-paramedic: tests done for platform " + target.platform); + + var targetTestsPassed = results && results.passed; + self.testsPassed = self.testsPassed && targetTestsPassed; + + if (!results) { + logger.error("Result: tests has not been completed in time, crashed or there is connectivity issue."); + } else if (targetTestsPassed) { + logger.info("Result: passed"); + } else { + logger.error("Result: passed=" + results.passed + ", failures=" + results.mobilespec.failures); + } + }); + }); + }, Q()); + }).then(function(res) { + if (self.testsPassed) { + logger.info("All tests have been passed."); + return TESTS_PASS; + } else { + logger.error("There are tests failures."); + return TESTS_FAILURE; + } + }, function(err) { + logger.error("Failed: " + err); + return NONTESTS_FAILURE; + }).then(function(exitCode){ + if(self.config.shouldCleanUpAfterRun()) { + logger.info("cordova-paramedic: Deleting the application: " + self.tempFolder.name); + shell.popd(); + shell.rm('-rf', self.tempFolder.name); + } + process.exit(exitCode); + }); + }, + createTempProject: function() { + this.tempFolder = tmp.dirSync(); + tmp.setGracefulCleanup(); + logger.info("cordova-paramedic: creating temp project at " + this.tempFolder.name); + exec('cordova create ' + this.tempFolder.name); + shell.pushd(this.tempFolder.name); + }, + installPlugins: function() { + logger.info("cordova-paramedic: installing plugins"); + this.pluginsManager = new PluginsManager(this.tempFolder.name, this.storedCWD); + this.pluginsManager.installPlugins(this.config.getPlugins()); + this.pluginsManager.installTestsForExistingPlugins(); + this.pluginsManager.installSinglePlugin('cordova-plugin-test-framework'); + this.pluginsManager.installSinglePlugin('cordova-plugin-device'); + this.pluginsManager.installSinglePlugin(path.join(__dirname, '../paramedic-plugin')); + } +}; + +var storedCWD = null; + +exports.run = function(paramedicConfig) { + + storedCWD = storedCWD || process.cwd(); + + var runner = new ParamedicRunner(paramedicConfig, null); + runner.storedCWD = storedCWD; + + return runner.run() + .timeout(paramedicConfig.getTimeout(), "This test seems to be blocked :: timeout exceeded. Exiting ..."); +}; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/portScanner.js ---------------------------------------------------------------------- diff --git a/lib/portScanner.js b/lib/portScanner.js new file mode 100644 index 0000000..df0cf15 --- /dev/null +++ b/lib/portScanner.js @@ -0,0 +1,54 @@ +var net = require('net'); +var Q = require('q'); +var PORT_NOT_AVAILABLE = 'EADDRINUSE'; + +var isPortAvailable = function (port) { + return new Q.Promise(function(resolve, reject) { + var testServer = net.createServer() + .once('error', function(err) { + if (err.code === PORT_NOT_AVAILABLE) { + reject(new Error('Port is not available')); + } else { + reject(err); + } + }) + .once('listening', function() { + testServer.once('close', function() { + resolve(port); + }).close(); + }) + .listen(port); + }); +}; + +var checkPorts = function(startPort, endPort, onFoundPort, onError) { + var currentPort = startPort; + + isPortAvailable(currentPort) + .then(function(port) { + onFoundPort(port); + }, function(error) { + if (error.message === 'Port is not available') { + currentPort++; + if (currentPort > endPort) { + onError(new Error('All ports are unavailable!')); + } else { + checkPorts(currentPort, endPort, onFoundPort, onError); + } + } else onError(error); + }); + }; + +var getFirstAvailablePort = function (startPort, endPort) { + if (startPort > endPort) { + var buffer = startPort; + startPort = endPort; + endPort = buffer; + } + + return new Q.Promise(function(resolve, reject) { + checkPorts(startPort, endPort, resolve, reject); + }); +}; + +module.exports.getFirstAvailablePort = getFirstAvailablePort; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/reporters/ConsoleReporter.js ---------------------------------------------------------------------- diff --git a/lib/reporters/ConsoleReporter.js b/lib/reporters/ConsoleReporter.js new file mode 100644 index 0000000..84f447e --- /dev/null +++ b/lib/reporters/ConsoleReporter.js @@ -0,0 +1,153 @@ +var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function ConsoleReporter(options) { + var print = options.print, + showColors = options.showColors || false, + onComplete = options.onComplete || function() {}, + timer = options.timer || noopTimer, + specCount, + failureCount, + failedSpecs = [], + pendingCount, + ansi = { + green: '\x1B[32m', + red: '\x1B[31m', + yellow: '\x1B[33m', + none: '\x1B[0m' + }, + failedSuites = []; + + this.jasmineStarted = function() { + specCount = 0; + failureCount = 0; + pendingCount = 0; + print('Started'); + printNewline(); + timer.start(); + }; + + this.jasmineDone = function() { + printNewline(); + for (var i = 0; i < failedSpecs.length; i++) { + specFailureDetails(failedSpecs[i]); + } + + if(specCount > 0) { + printNewline(); + + var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + + failureCount + ' ' + plural('failure', failureCount); + + if (pendingCount) { + specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); + } + + print(specCounts); + } else { + print('No specs found'); + } + + printNewline(); + var seconds = timer.elapsed() / 1000; + print('Finished in ' + seconds + ' ' + plural('second', seconds)); + printNewline(); + + for(i = 0; i < failedSuites.length; i++) { + suiteFailureDetails(failedSuites[i]); + } + + onComplete(failureCount === 0); + }; + + this.specStarted = function(){}; + this.suiteStarted = function(){}; + + this.specDone = function(result) { + specCount++; + + if (result.status == 'pending') { + pendingCount++; + print(colored('yellow', '*')); + return; + } + + if (result.status == 'passed') { + print(colored('green', '.')); + return; + } + + if (result.status == 'failed') { + failureCount++; + failedSpecs.push(result); + print(colored('red', 'F')); + } + }; + + this.suiteDone = function(result) { + if (result.failedExpectations && result.failedExpectations.length > 0) { + failureCount++; + failedSuites.push(result); + } + }; + + return this; + + function printNewline() { + print('\n'); + } + + function colored(color, str) { + return showColors ? (ansi[color] + str + ansi.none) : str; + } + + function plural(str, count) { + return count == 1 ? str : str + 's'; + } + + function repeat(thing, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(thing); + } + return arr; + } + + function indent(str, spaces) { + var lines = (str || '').split('\n'); + var newArr = []; + for (var i = 0; i < lines.length; i++) { + newArr.push(repeat(' ', spaces).join('') + lines[i]); + } + return newArr.join('\n'); + } + + function specFailureDetails(result) { + printNewline(); + print(result.fullName); + + for (var i = 0; i < result.failedExpectations.length; i++) { + var failedExpectation = result.failedExpectations[i]; + printNewline(); + print(indent(failedExpectation.message, 2)); + print(indent(failedExpectation.stack, 2)); + } + + printNewline(); + } + + function suiteFailureDetails(result) { + for (var i = 0; i < result.failedExpectations.length; i++) { + printNewline(); + print(colored('red', 'An error was thrown in an afterAll')); + printNewline(); + print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); + + } + printNewline(); + } + } + +module.exports = ConsoleReporter; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/reporters/ParamedicReporter.js ---------------------------------------------------------------------- diff --git a/lib/reporters/ParamedicReporter.js b/lib/reporters/ParamedicReporter.js new file mode 100644 index 0000000..2742e67 --- /dev/null +++ b/lib/reporters/ParamedicReporter.js @@ -0,0 +1,43 @@ + +function ParamedicReporter() { + var results = [], + specsExecuted = 0, + failureCount = 0, + pendingSpecCount = 0, + cordovaInfo = null; + + this.specDone = function(result) { + if (result.status != "disabled") { + specsExecuted++; + } + if (result.status == "failed") { + failureCount++; + results.push(result); + } + if (result.status == "pending") { + pendingSpecCount++; + } + }; + + this.jasmineDone = function(data) { + cordovaInfo = data.cordova; + }; + + this.getResults = function() { + return { + passed: failureCount === 0, + mobilespec: { + specs:specsExecuted, + failures:failureCount, + results: results + }, + platform: cordovaInfo.platform, + version: cordovaInfo.version, + model: cordovaInfo.model + }; + }; + + return this; +} + +module.exports = ParamedicReporter; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/specReporters.js ---------------------------------------------------------------------- diff --git a/lib/specReporters.js b/lib/specReporters.js new file mode 100644 index 0000000..21e2eab --- /dev/null +++ b/lib/specReporters.js @@ -0,0 +1,68 @@ + +// not currently used +// var ConsoleReporter = require('./reporters/ConsoleReporter'); + +var ParamedicReporter = require('./reporters/ParamedicReporter'); +var JasmineSpecReporter = require('jasmine-spec-reporter'); +var jasmineReporters = require('jasmine-reporters'); + +// default reporter which is used to determine whether tests pass or not +var paramedicReporter = null; + +// all reporters including additional reporters to trace results to console, etc +var reporters = []; + +// specifies path to save Junit results file +var reportSavePath = null; + +function reset() { + paramedicReporter = new ParamedicReporter(); + // default paramedic reporter + reporters = [paramedicReporter]; + // extra reporters + reporters.push(new JasmineSpecReporter({displayPendingSummary: false, displaySuiteNumber: true})); + if(reportSavePath){ + reporters.push(new jasmineReporters.JUnitXmlReporter({savePath: reportSavePath, consolidateAll: false})); + } +} + +module.exports = { + reset: reset, + initialize: function(config) { + reportSavePath = config.getReportSavePath(); + reset(); + }, + getResults: function() { + return paramedicReporter.getResults(); + }, + jasmineStarted: function(data) { + reporters.forEach(function (reporter) { + reporter.jasmineStarted && reporter.jasmineStarted(data); + }); + }, + specStarted: function(data) { + reporters.forEach(function (reporter) { + reporter.specStarted && reporter.specStarted(data); + }); + }, + specDone: function(data) { + reporters.forEach(function (reporter) { + reporter.specDone && reporter.specDone(data); + }); + }, + suiteStarted: function(data) { + reporters.forEach(function (reporter) { + reporter.suiteStarted && reporter.suiteStarted(data); + }); + }, + suiteDone: function(data) { + reporters.forEach(function (reporter) { + reporter.suiteDone && reporter.suiteDone(data); + }); + }, + jasmineDone: function(data) { + reporters.forEach(function (reporter) { + reporter.jasmineDone && reporter.jasmineDone(data); + }); + } +}; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/lib/utils.js ---------------------------------------------------------------------- diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..35ce7c7 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,22 @@ +var shelljs = require('shelljs'); +var verbose; + +function exec(cmd, onFinish, onData) { + if (onFinish instanceof Function || onFinish === null) { + var result = shelljs.exec(cmd, {async: true, silent: !verbose}, onFinish); + + if (onData instanceof Function) { + result.stdout.on('data', onData); + } + } else { + return shelljs.exec(cmd, {silent: !verbose}); + } +} + +exec.setVerboseLevel = function(_verbose) { + verbose = _verbose; +}; + +module.exports = { + exec: exec +}; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/main.js ---------------------------------------------------------------------- diff --git a/main.js b/main.js index b772665..a538c94 100755 --- a/main.js +++ b/main.js @@ -1,43 +1,52 @@ #!/usr/bin/env node var parseArgs = require('minimist'), - paramedic = require('./paramedic'); - -var plugins, - platformId; + path = require('path'), + paramedic = require('./lib/paramedic'), + ParamedicConfig = require('./lib/ParamedicConfig'); var USAGE = "Error missing args. \n" + - "cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --port PORTNUM --browserify]\n" + - "`PLATFORM` : the platform id, currently only supports 'ios'\n" + - "`PATH` : the relative or absolute path to a plugin folder\n" + - "\texpected to have a 'tests' folder.\n" + - "\tYou may specify multiple --plugin flags and they will all\n" + - "\tbe installed and tested together.\n" + - "`MSECS` : (optional) time in millisecs to wait for tests to pass|fail \n" + - "\t(defaults to 10 minutes) \n" + - "`PORTNUM` : (optional) port to use for posting results from emulator back to paramedic server\n" + - "--justbuild : (optional) just builds the project, without running the tests \n" + + "cordova-paramedic --platform PLATFORM --plugin PATH [--justbuild --timeout MSECS --startport PORTNUM --endport PORTNUM --browserify]\n" + + "`PLATFORM` : the platform id. Currently supports 'ios', 'browser', 'windows', 'android', 'wp8'.\n" + + "\tPath to platform can be specified as link to git repo like:\n" + + "\twindows@https://github.com/apache/cordova-windows.git\n" + + "\tor path to local copied git repo like:\n" + + "\twindows@../cordova-windows/\n" + + "`PATH` : the relative or absolute path to a plugin folder\n" + + "\texpected to have a 'tests' folder.\n" + + "\tYou may specify multiple --plugin flags and they will all\n" + + "\tbe installed and tested together.\n" + + "`MSECS` : (optional) time in millisecs to wait for tests to pass|fail \n" + + "\t(defaults to 10 minutes) \n" + + "`PORTNUM` : (optional) ports to find available and use for posting results from emulator back to paramedic server(default is from 8008 to 8009)\n" + + "--justbuild : (optional) just builds the project, without running the tests \n" + "--browserify : (optional) plugins are browserified into cordova.js \n" + "--verbose : (optional) verbose mode. Display more information output\n" + - "--platformPath : (optional) path to install platform from, git or local file uri"; + "--useTunnel : (optional) use tunneling instead of local address. default is false\n" + + "--config : (optional) read configuration from paramedic configuration file\n" + + "--reportSavePath: (optional) path to save Junit results file\n" + + "--cleanUpAfterRun: (optional) cleans up the application after the run."; var argv = parseArgs(process.argv.slice(2)); +var pathToParamedicConfig = argv.config && path.resolve(argv.config); -if(!argv.platform) { - console.log(USAGE); - process.exit(1); -} +if (pathToParamedicConfig || // --config + argv.platform && argv.plugin) { // or --platform and --plugin -var onComplete = function(resCode,resObj,logStr) { - console.log("result code is : " + resCode); - if(resObj) { - console.log(JSON.stringify(resObj)); - } - if(logStr) { - console.log(logStr); - } - process.exit(resCode); -}; + var paramedicConfig = pathToParamedicConfig ? + ParamedicConfig.parseFromFile(pathToParamedicConfig): + ParamedicConfig.parseFromArguments(argv); -paramedic.run(argv.platform, argv.plugin, onComplete, argv.justbuild, argv.port, argv.timeout, argv.browserify, false, argv.verbose, argv.platformPath); + paramedic.run(paramedicConfig) + .catch(function (error) { + console.log(JSON.stringify(error)); + process.exit(1); + }) + .done(function (result) { + console.log(JSON.stringify(result)); + }); +} else { + console.log(USAGE); + process.exit(1); +} http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/package.json ---------------------------------------------------------------------- diff --git a/package.json b/package.json index 8eddb4f..77e6ce0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "cordova-paramedic", "version": "0.5.0", + "license": "Apache-2.0", "description": "Use medic to test a cordova plugin locally", "main": "paramedic.js", "bin": { @@ -11,15 +12,14 @@ "url": "git://github.com/apache/cordova-paramedic.git" }, "scripts": { - "test":"npm run jshint & npm run test-ios", - "jshint":"node node_modules/jshint/bin/jshint *.js", - "test-appveyor":"npm run test-windows", - "test-travis":"npm run test-ios", - "test-android":"node main.js --platform android --plugin ./spec/testable-plugin/", + "test": "npm run jshint & npm run test-ios", + "jshint": "node node_modules/jshint/bin/jshint lib/", + "test-appveyor": "npm run test-windows", + "test-travis": "npm run test-ios", + "test-android": "node main.js --platform android --plugin ./spec/testable-plugin/", "test-ios": "node main.js --platform ios --plugin ./spec/testable-plugin/ --verbose", - "test-windows" : "node main.js --platform windows --plugin ./spec/testable-plugin/", + "test-windows": "node main.js --platform windows --plugin ./spec/testable-plugin/", "test-wp8": "node main.js --platform wp8 --plugin ./spec/testable-plugin/" - }, "keywords": [ "cordova", @@ -29,14 +29,20 @@ "author": "Jesse MacFadyen", "license": "Apache 2.0", "dependencies": { + "ansi": "^0.3.1", + "cordova-common": "^1.0.0", + "jasmine-spec-reporter": "^2.4.0", + "jasmine-reporters": "^2.1.1", "localtunnel": "~1.5.0", "minimist": "~1.1.0", + "q": "^1.4.1", "request": "^2.53.0", "shelljs": "~0.3.0", + "socket.io": "^1.4.5", "tmp": "0.0.25" }, "devDependencies": { - "jasmine-node": "~1", - "jshint": "^2.6.0" + "jasmine-node": "~1", + "jshint": "^2.6.0" } } http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/JasmineParamedicProxy.js ---------------------------------------------------------------------- diff --git a/paramedic-plugin/JasmineParamedicProxy.js b/paramedic-plugin/JasmineParamedicProxy.js new file mode 100644 index 0000000..854efc6 --- /dev/null +++ b/paramedic-plugin/JasmineParamedicProxy.js @@ -0,0 +1,56 @@ +function JasmineParamedicProxy(socket) { + this.socket = socket; + //jasmineRequire.JsApiReporter.apply(this, arguments); +} + +// JasmineParamedicProxy.prototype = jasmineRequire.JsApiReporter.prototype; +// JasmineParamedicProxy.prototype.constructor = JasmineParamedicProxy; + +JasmineParamedicProxy.prototype.jasmineStarted = function (o) { + this.socket.emit('jasmineStarted', o); +}; + +JasmineParamedicProxy.prototype.specStarted = function (o) { + this.socket.emit('specStarted', o); +}; + +JasmineParamedicProxy.prototype.specDone = function (o) { + this.socket.emit('specDone', o); +}; + +JasmineParamedicProxy.prototype.suiteStarted = function (o) { + this.socket.emit('suiteStarted', o); +}; + +JasmineParamedicProxy.prototype.suiteDone = function (o) { + this.socket.emit('suiteDone', o); +}; + +JasmineParamedicProxy.prototype.jasmineDone = function (o) { + var p = 'Desktop'; + var devmodel='none'; + var version = cordova.version; + if(typeof device != 'undefined') { + p = device.platform.toLowerCase(); + devmodel=device.model || device.name; + version = device.version.toLowerCase(); + } + + o = o || {}; + + // include platform info + o.cordova = { + platform:(platformMap.hasOwnProperty(p) ? platformMap[p] : p), + version:version, + model:devmodel + } + + this.socket.emit('jasmineDone', o); +}; + +var platformMap = { + 'ipod touch':'ios', + 'iphone':'ios' +}; + +module.exports = JasmineParamedicProxy; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/paramedic.js ---------------------------------------------------------------------- diff --git a/paramedic-plugin/paramedic.js b/paramedic-plugin/paramedic.js new file mode 100644 index 0000000..6c210e6 --- /dev/null +++ b/paramedic-plugin/paramedic.js @@ -0,0 +1,74 @@ +var io = cordova.require('cordova-plugin-paramedic.socket.io'); + +var PARAMEDIC_SERVER_DEFAULT_URL = 'http://127.0.0.1:8008'; + +function Paramedic() { + +} + +Paramedic.prototype.initialize = function() { + var me = this; + var connectionUri = loadParamedicServerUrl(); + this.socket = io.connect(connectionUri); + + this.socket.on('connect', function () { + console.log("Paramedic has been susccessfully connected to server"); + if (typeof device != 'undefined') me.socket.emit('deviceInfo', device); + }); + + this.overrideConsole(); + this.injectJasmineReporter(); +}; + + +Paramedic.prototype.overrideConsole = function () { + + var origConsole = window.console; + var me = this; + + function createCustomLogger(type) { + return function () { + origConsole[type].apply(origConsole, arguments); + + me.socket.emit('log', { type: type, msg: Array.prototype.slice.apply(arguments) }); + }; + } + window.console = { + log: createCustomLogger('log'), + warn: createCustomLogger('warn'), + error: createCustomLogger('error'), + }; + console.log('Paramedic console has been installed.'); +}; + +Paramedic.prototype.injectJasmineReporter = function () { + var JasmineParamedicProxy = require('cordova-plugin-paramedic.JasmineParamedicProxy'); + var jasmineProxy = new JasmineParamedicProxy(this.socket); + var testsModule = cordova.require("cordova-plugin-test-framework.cdvtests"); + var defineAutoTestsOriginal = testsModule.defineAutoTests; + + testsModule.defineAutoTests = function () { + defineAutoTestsOriginal(); + jasmine.getEnv().addReporter(jasmineProxy); + }; +}; + +new Paramedic().initialize(); + +function loadParamedicServerUrl() { + + try { + // attempt to synchronously load medic config + var xhr = new XMLHttpRequest(); + xhr.open("GET", "../medic.json", false); + xhr.send(null); + var cfg = JSON.parse(xhr.responseText); + + return cfg.logurl || PARAMEDIC_SERVER_DEFAULT_URL; + + } catch (ex) {} + + return PARAMEDIC_SERVER_DEFAULT_URL; +} + +module.exports = Paramedic; http://git-wip-us.apache.org/repos/asf/cordova-paramedic/blob/b1aa699d/paramedic-plugin/plugin.xml ---------------------------------------------------------------------- diff --git a/paramedic-plugin/plugin.xml b/paramedic-plugin/plugin.xml new file mode 100644 index 0000000..af4d54a --- /dev/null +++ b/paramedic-plugin/plugin.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + +<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" + id="cordova-plugin-paramedic" + version="1.2.1-dev"> + + <name>Paramedic</name> + <description>Cordova Paramedic Plugin</description> + <license>Apache 2.0</license> + + <js-module src="socket.io.js" name="socket.io" /> + + <js-module src="JasmineParamedicProxy.js" name="JasmineParamedicProxy" /> + + <js-module src="paramedic.js" name="paramedic"> + <runs/> + </js-module> + +</plugin> --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org