From 335a0c41b7a8ee79b9af645a38ecc8ad779aeaac Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Wed, 10 Dec 2014 16:17:08 +0100 Subject: [PATCH 01/64] User can now provide more browser settings --- lib/browser.js | 52 +++++++++++++++++---------- lib/gagarin.js | 4 ++- tests/example/.meteor/cordova-plugins | 1 + tests/example/.meteor/packages | 2 ++ tests/example/.meteor/versions | 1 + tests/specs/capabilities.js | 42 ++++++++++++++++++++++ 6 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 tests/example/.meteor/cordova-plugins create mode 100644 tests/specs/capabilities.js diff --git a/lib/browser.js b/lib/browser.js index 7935f0a..e4abb59 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -14,6 +14,8 @@ function Browser (options) { var dontWaitForMeteor = options.dontWaitForMeteor !== undefined ? !!options.dontWaitForMeteor : false; var meteorLoadTimeout = options.meteorLoadTimeout !== undefined ? options.meteorLoadTimeout : 2000; var browserPromise = null; + var capabilities = options.capabilities || {}; + var windowSize = options.windowSize; self.getBrowserPromise = function () { @@ -26,39 +28,53 @@ function Browser (options) { } return browserPromise = new Promise(function (resolve, reject) { - browser.init(function (err) { + browser.init(capabilities, function (err) { if (err) { return reject(err); } - browser.get(myLocation, function (err) { - if (err) { - return reject(err); - } - if (dontWaitForMeteor) { // already done, so lets just resolve - console.log('not waiting'); - return resolve({ browser: browser, closure: closure }); - } - browser.setAsyncScriptTimeout(meteorLoadTimeout, function (err) { + //--------------- + if (windowSize) { + browser.setWindowSize(windowSize.width, windowSize.height, function (err) { + if (err) { + reject(err); + } + afterResize(); + }); + } else { + afterResize(); + } + //---------------------- + function afterResize() { + browser.get(myLocation, function (err) { if (err) { return reject(err); } - // wait until meteor core packages are loaded ... - browser.waitForConditionInBrowser('!!window.Meteor && !!window.Meteor.startup', function (err) { + if (dontWaitForMeteor) { // already done, so lets just resolve + return resolve({ browser: browser, closure: closure }); + } + browser.setAsyncScriptTimeout(meteorLoadTimeout, function (err) { if (err) { return reject(err); } - // ... and until all other files are loaded as well - browser.executeAsync(function (cb) { - Meteor.startup(cb); - }, function (err) { + // wait until meteor core packages are loaded ... + browser.waitForConditionInBrowser('!!window.Meteor && !!window.Meteor.startup', function (err) { if (err) { return reject(err); } - resolve({ browser: browser, closure: closure }); + // ... and until all other files are loaded as well + browser.executeAsync(function (cb) { + Meteor.startup(cb); + }, function (err) { + if (err) { + return reject(err); + } + resolve({ browser: browser, closure: closure }); + }); }); }); }); - }); + } + }); }); // Promise } diff --git a/lib/gagarin.js b/lib/gagarin.js index 5a09923..579cf9f 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -90,10 +90,12 @@ function Gagarin (gagarinOptions) { mergeHelpers(myHelpers, [ helpers.both, helpers.client ]); mergeHelpers(myHelpers, gagarinOptions.clientHelpers); - var browser = new Browser({ + var browser = new Browser({ helpers : mergeHelpers(myHelpers, options.helpers), location : options.location, webdriver : options.webdriver || gagarinOptions.webdriver, + windowSize : options.windowSize, + capabilities : options.capabilities, dontWaitForMeteor : options.dontWaitForMeteor || gagarinOptions.dontWaitForMeteor, meteorLoadTimeout : options.meteorLoadTimeout || gagarinOptions.meteorLoadTimeout, }); diff --git a/tests/example/.meteor/cordova-plugins b/tests/example/.meteor/cordova-plugins new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/example/.meteor/cordova-plugins @@ -0,0 +1 @@ + diff --git a/tests/example/.meteor/packages b/tests/example/.meteor/packages index b02c674..1674b84 100644 --- a/tests/example/.meteor/packages +++ b/tests/example/.meteor/packages @@ -7,3 +7,5 @@ standard-app-packages autopublish insecure anti:gagarin +mystor:device-detection + diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index 3b2e285..fdbd17a 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -34,6 +34,7 @@ minimongo@1.0.5 mobile-status-bar@1.0.1 mongo@1.0.9 mrt:altimeter@0.0.2 +mystor:device-detection@0.2.0 observe-sequence@1.0.3 ordered-dict@1.0.1 random@1.0.1 diff --git a/tests/specs/capabilities.js b/tests/specs/capabilities.js new file mode 100644 index 0000000..4a7ddc5 --- /dev/null +++ b/tests/specs/capabilities.js @@ -0,0 +1,42 @@ +describe('Browser capabilities', function () { + + var server = meteor(); + + var client1 = browser({ + location: server.location, + dontWaitForMeteor: true, + windowSize: { width: 500, height: 500 }, + }); + + var client2 = browser({ + dontWaitForMeteor: true, + location: server.location, + capabilities: { + browserName: 'chrome', + chromeOptions: { + mobileEmulation: { + deviceName: 'Apple iPhone 5' + } + } + } + }); + + it('should be able to resize window', function () { + return client1.execute(function () { + return [ window.outerWidth, window.outerHeight ]; + }) + .then(function (size) { + expect(size).to.eql([ 500, 500 ]); + }); + }); + + it('should be able to emulate mobile device', function () { + return client2.execute(function () { + return Meteor.Device.isPhone(); + }) + .then(function (isPhone) { + expect(isPhone).to.be.true; + }); + }); + +}); From db5eb189976646b8e3bcd77a8d615c89b246a125 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Wed, 10 Dec 2014 16:17:55 +0100 Subject: [PATCH 02/64] Bumped package version --- package.js | 2 +- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index d2655d5..f1fa2c2 100644 --- a/package.js +++ b/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre4", + version: "0.3.0-pre5", git: "https://github.com/anticoders/gagarin.git", }); diff --git a/package.json b/package.json index 8e838f9..5011433 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre4", + "version": "0.3.0-pre5", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index fdbd17a..683c500 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,4 +1,4 @@ -anti:gagarin@0.3.0-pre4 +anti:gagarin@0.3.0-pre5 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 From 28d7520a98b253f9aea21302a982961aa50e179f Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Thu, 11 Dec 2014 12:35:45 +0100 Subject: [PATCH 03/64] Use DDP instead of raw sockets --- backdoor.js | 299 ++++++++++++++------------------- lib/ddp.js | 91 ++++++++++ lib/meteor.js | 5 +- lib/socket.js | 91 ---------- lib/transponder.js | 37 +--- package.js | 2 +- package.json | 1 + tests/example/.meteor/versions | 1 - versions.json | 60 ++++++- 9 files changed, 291 insertions(+), 296 deletions(-) create mode 100644 lib/ddp.js delete mode 100644 lib/socket.js diff --git a/backdoor.js b/backdoor.js index 52762a9..8be6591 100644 --- a/backdoor.js +++ b/backdoor.js @@ -1,207 +1,164 @@ "use strict"; var vm = Npm.require('vm'); -var net = Npm.require('net'); var Fiber = Npm.require('fibers'); -var server; +var Future = Npm.require('fibers/future'); var waiting = {}; Gagarin = {}; -if (Meteor.isDevelopment && process.env.GAGARIN_SETTINGS) { +if (process.env.GAGARIN_SETTINGS) { - Meteor.startup(function () { - server = net.createServer(function (socket) { - - socket.setEncoding('utf8'); - socket.on('data', function (chunk) { - // TODO: it's also possible that the JSON object is splited into several chunks - - chunk.split('\n').forEach(function (line) { - var data; - - if (line === "" || line === "\r") { - return; - } - - try { - - data = JSON.parse(line); - - // make sure undefined fields are also there - if (data.closure && data.closureKeys) { - data.closureKeys.forEach(function (key) { - if (!data.closure.hasOwnProperty(key)) { - data.closure[key] = undefined; - } - }); - } - - if (data.name && data.code) { - if (data.mode === 'promise') { - waiting[data.name] = evaluateAsPromise(data.name, data.code, data.args, data.closure, socket); - - } else if (data.mode === 'execute') { - waiting[data.name] = evaluate(data.name, data.code, data.args, data.closure, socket); + Meteor.methods({ + '/gagarin/execute': function (code, args, closure) { + // maybe we could avoid creating it multiple times? + var context = vm.createContext(global); + context.Fiber = Fiber; + try { + vm.runInContext("value = " + wrapSourceCode(code, args, closure), context); + } catch (err) { + throw new Meteor.Error(400, err); + } + if (typeof context.value === 'function') { + var feedback; + try { + feedback = context.value.apply(null, values(closure)); + } catch (err) { + feedback = { error: err.message }; + } + return feedback; + } + }, - } else if (data.mode === 'wait') { - waiting[data.name] = evaluateAsWait( - data.name, data.time, data.mesg, data.code, data.args, data.closure, socket - ); + '/gagarin/promise': function (code, args, closure) { + var future = new Future(); + var context = vm.createContext(global); - } else { - writeToSocket(socket, data.name, { - error : 'evaluation mode ' + JSON.stringify(data.mode) + ' is not supported' - }); - } - } else if (data.name && data.mode === 'pong') { - waiting[data.name] && waiting[data.name](data.closure); + context.Fiber = Fiber; - } else { - throw new Error('invalid payload => ' + JSON.stringify(data)); - } + var chunks = []; - } catch (err) { - writeToSocket(socket, null, { error: err }); - } + var keys = Object.keys(closure).map(function (key) { + return stringify(key) + ": " + key; + }).join(','); - }); + args = args.map(stringify); - }); - }).listen(0, function () { - console.log('Gagarin listening at port ' + server.address().port); - }); - }); + args.unshift("(function (cb) { return function (err) { cb({ error : err, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); + args.unshift("(function (cb) { return function (res) { cb({ result : res, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); -} + chunks.push( + "function (" + Object.keys(closure).join(', ') + ") {", + " 'use strict';", + " var either = function (first) {", + " return {", + " or: function (second) {", + " return function (arg1, arg2) {", + " return arg1 ? first(arg1) : second(arg2);", + " };", + " }", + " };", + " };" + ); -function evaluate(name, code, args, closure, socket) { - // maybe we could avoid creating it multiple times? - var context = vm.createContext(global); - var myFiber = null; + chunks.push( + " (" + code + ")(" + args.join(', ') + ");", + "}" + ); - context.Fiber = Fiber; - - function __closure__(values) { - myFiber = Fiber.current; - if (!myFiber) { - throw new Error('you can only call $sync inside a fiber'); - } - if (arguments.length > 0) { - writeToSocket(socket, name, { ping: true, closure: values }); - } else { - writeToSocket(socket, name, { ping: true }); - } - return Fiber.yield(); - } - - function reportError(err) { - writeToSocket(socket, name, { error: err }); - } - - try { - vm.runInContext("value = " + wrapSourceCode(code, args, closure), context); - } catch (err) { - return reportError(err); - } - - if (typeof context.value === 'function') { - Fiber(function () { - var data; try { - data = context.value.apply(null, values(closure, __closure__)); + vm.runInContext("value = " + chunks.join('\n'), context); } catch (err) { - data = { error: err.message }; + throw new Meteor.Error(err); } - data.name = name; - writeToSocket(socket, name, data); - }).run(); - } - - return function (values) { - myFiber && myFiber.run(values); - }; -} - -function evaluateAsPromise(name, code, args, closure, socket) { - // maybe we could avoid creating it multiple times? - var context = vm.createContext(global); - var myFiber = null; - context.Fiber = Fiber; - - function __closure__(values) { - myFiber = Fiber.current; - if (!myFiber) { - throw new Error('you can only call $sync inside a fiber'); - } - if (arguments.length > 0) { - writeToSocket(socket, name, { ping: true, closure: values }); - } else { - writeToSocket(socket, name, { ping: true }); - } - return Fiber.yield(); - } - - function reportError(err) { - writeToSocket(socket, name, { error: err }); - } + if (typeof context.value === 'function') { + try { + context.value.apply(null, values(closure, function (feedback) { + if (feedback.error && typeof feedback.error === 'object') { + feedback.error = feedback.error.message; + } + future['return'](feedback); + })); + } catch (err) { + throw new Meteor.Error(err); + } + return future.wait(); + } + }, - var chunks = []; + '/gagarin/wait': function (timeout, message, code, args, closure) { - var keys = Object.keys(closure).map(function (key) { - return stringify(key) + ": " + key; - }).join(','); + var future = new Future(); + var done = false; + var handle = null; + var handle2 = null; + var context = vm.createContext(global); - args = args.map(stringify); + context.Fiber = Fiber; - args.unshift("(function (cb) { return function (err) { cb({ error : err, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); - args.unshift("(function (cb) { return function (res) { cb({ result : res, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); + function resolve (feedback) { + // TODO: why do we need this sentinel? + if (done) { + return; + } + done = true; + if (!feedback.closure) { + feedback.closure = closure; + } + if (feedback.error && typeof feedback.error === 'object') { + feedback.error = feedback.error.message; + } + future['return'](feedback); + //------------------------- + clearTimeout(handle2); + } - chunks.push( - "function (" + Object.keys(closure).join(', ') + ") {", - " 'use strict';", - " var either = function (first) {", - " return {", - " or: function (second) {", - " return function (arg1, arg2) {", - " return arg1 ? first(arg1) : second(arg2);", - " };", - " }", - " };", - " };" - ); + try { + vm.runInContext("value = " + wrapSourceCode(code, args, closure), context); + } catch (err) { + resolve({ error: err }); + } - addSyncChunks(chunks, closure, "arguments[arguments.length-2]"); + if (!done && typeof context.value === 'function') { - chunks.push( - " (" + code + ")(" + args.join(', ') + ");", - "}" - ); + (function test() { + var feedback; + try { + feedback = context.value.apply(null, values(closure)); + if (feedback.result) { + resolve(feedback); + } + + handle = setTimeout(Meteor.bindEnvironment(test), 50); // repeat after 1/20 sec. + + if (feedback.closure) { + closure = feedback.closure; + } - try { - vm.runInContext("value = " + chunks.join('\n'), context); - } catch (err) { - return reportError(err); - } + } catch (err) { + resolve({ error: err }); + } + }()); + + handle2 = setTimeout(function () { + clearTimeout(handle); + resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); + }, timeout); + } else { + resolve({ err: 'code has to be a function' }) + } - if (typeof context.value === 'function') { - Fiber(function () { + return future.wait(); + }, - try { - context.value.apply(null, values(closure, __closure__, function (data) { - writeToSocket(socket, name, data); - })); - } catch (err) { - reportError(err); - } + }); - }).run(); - } + Meteor.startup(function () { + // this is a fake, we won't need it anymore + console.log('Gagarin ready ...'); + }); - return function (values) { - myFiber && myFiber.run(values); - }; } function evaluateAsWait(name, timeout, message, code, args, closure, socket) { @@ -368,5 +325,5 @@ function writeToSocket(socket, name, data) { data.name = name; } //---------------------------------------- - socket.write(JSON.stringify(data) + '\n'); + socket.write(data); } diff --git a/lib/ddp.js b/lib/ddp.js new file mode 100644 index 0000000..969d01c --- /dev/null +++ b/lib/ddp.js @@ -0,0 +1,91 @@ +var DDPClient = require('ddp'); + +var chalk = require('chalk'); +var Promise = require('es6-promise').Promise; + +module.exports = function makeDDPClientFactory (emitter, requestGagarinConfig) { + "use strict"; + + var self = this; + var ddpClient = null; + var ddpClientPromise = null; + var uniqueToken = null; + + return function ddpClientAsPromise () { + + return requestGagarinConfig().then(function (config) { + + if (uniqueToken === config.uniqueToken && ddpClientPromise) { + return ddpClientPromise; + } + + uniqueToken = config.uniqueToken; + + ddpClientPromise = new Promise(function (resolve, reject) { + // XXX note that we do not reject explicitly + + // TODO: meteroPort may change? + // allow different things than localhost + + ddpClient && ddpClient.close(); + + ddpClient = new DDPClient({ + // All properties optional, defaults shown + host : "localhost", + port : config.meteorPort, + path : "websocket", + ssl : false, + autoReconnect : true, + autoReconnectTimer : 500, + maintainCollections : true, + ddpVersion : '1' // ['1', 'pre2', 'pre1'] available + }); + + ddpClient.connect(function (err, wasReconnected) { + if (err) { + return reject(err); + } + + function callDDPMethod (name, args, cb) { + ddpClient.call(name, args, function (err, feedback) { + if (feedback && feedback.closure) { + config.closure(feedback.closure); + } + if (err) { + return cb(err); + } + if (feedback.error) { + return cb(new Error(feedback.error)); + } + cb(null, feedback.result); + }); + } + + resolve({ + + execute: function (code, args, cb) { + callDDPMethod('/gagarin/execute', [ code.toString(), args, config.closure() ], cb); + }, + + promise: function (code, args, cb) { + callDDPMethod('/gagarin/promise', [ code.toString(), args, config.closure() ], cb); + }, + + wait: function (timeout, message, code, args, cb) { + callDDPMethod('/gagarin/wait', [ timeout, message, code.toString(), args, config.closure() ], cb); + }, + + }); + }); + + //------------------------------------------------------------- + //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + //------------------------------------------------------------- + }); + + return ddpClientPromise; + }); + }; + +}; + diff --git a/lib/meteor.js b/lib/meteor.js index 8dded46..7be7dd7 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -127,14 +127,15 @@ function Meteor (options) { meteor.stdout.on('data', function (data) { //process.stdout.write(data); - var match = /Gagarin listening at port (\d+)/.exec(data.toString()); + var match = /Gagarin ready .../.exec(data.toString()); if (match) { // make sure we won't kill this process by accident clearTimeout(meteorSafetyTimeout); meteorSafetyTimeout = null; resolve({ + meteorPort : options.port, + uniqueToken : Math.random(), exitAsPromise : makeExitAsPromise(meteor, mongoServerPromise, options.dbName, cleanUpThen), - gagarinPort : parseInt(match[1]), closure : function () { // XXX note that accessor may change dynamically return accessor.apply(this, arguments); diff --git a/lib/socket.js b/lib/socket.js deleted file mode 100644 index eba8807..0000000 --- a/lib/socket.js +++ /dev/null @@ -1,91 +0,0 @@ -var net = require('net'); -var chalk = require('chalk'); -var Promise = require('es6-promise').Promise; - -module.exports = function makeSocketFactory (emitter, requestGagarinConfig) { - "use strict"; - - var self = this; - var socket = null; - var socketPort = null; - var socketPromise = null; - - return function socketAsPromise () { - - return requestGagarinConfig().then(function (config) { - - if (socketPort === config.gagarinPort && socketPromise) { - return socketPromise; - } - - socketPort = config.gagarinPort; - - socketPromise = new Promise(function (resolve) { - // XXX note that we do not reject explicitly - - function transmit (payload, callback) { - payload.closure = config.closure(); - payload.closureKeys = Object.keys(payload.closure); - // TODO: use EJSON - socket.write(JSON.stringify(payload) + '\n', callback); - } - - socket && socket.destroy(); - socket = net.createConnection(socketPort, function () { - resolve(transmit); - }); - - //--------------- PARSE RESPONSE FROM SERVER ------------------ - socket.setEncoding('utf8'); - socket.on('data', function (chunk) { - // TODO: it's also possible that the JSON object is splited into several chunks - - chunk.split('\n').forEach(function (line) { - var data; - - // ignore empty lines - if (line === '' || line === '\r') return; - - try { - data = JSON.parse(line); - } catch (err) { // parse error - emitter.emit('error', new Error('while parsing ' + chalk.blue(line) + ' => ' + err.message)); - return; - } - - if (data.closure) { - config.closure(data.closure); - } - - if (data.error) { - - if (data.name) { - emitter.emit(data.name, new Error(data.error)); - } else { - emitter.emit('error', new Error(data.error)); - } - - } else if (data.name) { - - if (data.ping) { - transmit({ name : data.name, mode : 'pong' }); - - } else { - // XXX note that the first argument must be an error - emitter.emit(data.name, null, data.result); - } - - } - }); - }); - //------------------------------------------------------------- - //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ - //------------------------------------------------------------- - }); - - return socketPromise; - }); - }; - -}; - diff --git a/lib/transponder.js b/lib/transponder.js index 2ae3ab6..a083502 100644 --- a/lib/transponder.js +++ b/lib/transponder.js @@ -2,7 +2,8 @@ var EventEmiter = require('events').EventEmitter; var Promise = require('es6-promise').Promise; var util = require('util'); var tools = require('./tools'); -var makeSocketFactory = require('./socket'); +var either = require('./tools').either; +var makeDDPClientFactory = require('./ddp'); function MeteorTransponder(requestGagarinConfig) { "use strict"; @@ -11,11 +12,11 @@ function MeteorTransponder(requestGagarinConfig) { EventEmiter.call(self); // iherits from EventEmitter - var socketAsPromise = makeSocketFactory(self, requestGagarinConfig); + //var socketAsPromise = makeSocketFactory(self, requestGagarinConfig); + var ddpClientAsPromise = makeDDPClientFactory(self, requestGagarinConfig); - function factory(mode) { + function factory(name) { return function (code, args) { - var name = uniqe().toString(); if (arguments.length < 2) { args = []; @@ -24,17 +25,9 @@ function MeteorTransponder(requestGagarinConfig) { } //------------------------------------------------- - return socketAsPromise().then(function (transmit) { - transmit({ - code: code.toString(), - mode: mode, - name: name, - args: args, - }, function (err) { - // do we need this callback (?) - }); + return ddpClientAsPromise().then(function (ddpClient) { return new Promise(function (resolve, reject) { - self.once(name, tools.either(reject).or(resolve)); + ddpClient[name](code, args, either(reject).or(resolve)); }); }); } @@ -44,8 +37,6 @@ function MeteorTransponder(requestGagarinConfig) { self.execute = factory('execute'); self.wait = function (timeout, message, code, args) { - var name = uniqe().toString(); - if (arguments.length < 4) { args = []; } else { @@ -53,19 +44,9 @@ function MeteorTransponder(requestGagarinConfig) { } //------------------------------------------------- - return socketAsPromise().then(function (transmit) { - transmit({ - code: code.toString(), - mode: 'wait', - name: name, - args: args, - time: timeout, - mesg: message, - }, function (err) { - // do we need this callback (?) - }); + return ddpClientAsPromise().then(function (ddpClient) { return new Promise(function (resolve, reject) { - self.once(name, tools.either(reject).or(resolve)); + ddpClient.wait(timeout, message, code, args, either(reject).or(resolve)); }); }); } diff --git a/package.js b/package.js index f1fa2c2..ca3d49a 100644 --- a/package.js +++ b/package.js @@ -8,7 +8,7 @@ Package.describe({ Package.onUse(function (api) { - api.use('mrt:altimeter@0.0.2', 'server'); + api.use('livedata', 'server'); api.addFiles([ 'backdoor.js' diff --git a/package.json b/package.json index 5011433..6fea22a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "chai": "^1.10.0", "chalk": "^0.5.1", "commander": "^2.5.0", + "ddp": "^0.9.2", "debounce": "^1.0.0", "es6-promise": "^1.0.0", "mkdirp": "^0.5.0", diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index 683c500..057cf7c 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -33,7 +33,6 @@ minifiers@1.1.2 minimongo@1.0.5 mobile-status-bar@1.0.1 mongo@1.0.9 -mrt:altimeter@0.0.2 mystor:device-detection@0.2.0 observe-sequence@1.0.3 ordered-dict@1.0.1 diff --git a/versions.json b/versions.json index bed3fa0..ae901b1 100644 --- a/versions.json +++ b/versions.json @@ -1,12 +1,68 @@ { "dependencies": [ + [ + "base64", + "1.0.1" + ], + [ + "callback-hook", + "1.0.1" + ], + [ + "check", + "1.0.2" + ], + [ + "ddp", + "1.0.12" + ], + [ + "ejson", + "1.0.4" + ], + [ + "geojson-utils", + "1.0.1" + ], + [ + "id-map", + "1.0.1" + ], + [ + "json", + "1.0.1" + ], + [ + "livedata", + "1.0.11" + ], + [ + "logging", + "1.0.5" + ], [ "meteor", "1.1.3" ], [ - "mrt:altimeter", - "0.0.2" + "minimongo", + "1.0.5" + ], + [ + "ordered-dict", + "1.0.1" + ], + [ + "random", + "1.0.1" + ], + [ + "retry", + "1.0.1" + ], + [ + "tracker", + "1.0.3" ], [ "underscore", From 25078fee94a30fe72dc12a5cfb1d0e5622e18cad Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 11:47:50 +0100 Subject: [PATCH 04/64] Cleaning up --- backdoor.js | 153 ++++++------------------------------- lib/browserPromiseChain.js | 12 --- lib/ddp.js | 2 +- lib/meteor.js | 2 +- lib/transponder.js | 21 +---- 5 files changed, 30 insertions(+), 160 deletions(-) diff --git a/backdoor.js b/backdoor.js index 8be6591..f7b9406 100644 --- a/backdoor.js +++ b/backdoor.js @@ -3,7 +3,6 @@ var vm = Npm.require('vm'); var Fiber = Npm.require('fibers'); var Future = Npm.require('fibers/future'); -var waiting = {}; Gagarin = {}; @@ -11,7 +10,12 @@ if (process.env.GAGARIN_SETTINGS) { Meteor.methods({ '/gagarin/execute': function (code, args, closure) { - // maybe we could avoid creating it multiple times? + "use strict"; + + check(code, String); + check(args, Array); + check(closure, Object); + var context = vm.createContext(global); context.Fiber = Fiber; try { @@ -31,6 +35,12 @@ if (process.env.GAGARIN_SETTINGS) { }, '/gagarin/promise': function (code, args, closure) { + "use strict"; + + check(code, String); + check(args, Array); + check(closure, Object); + var future = new Future(); var context = vm.createContext(global); @@ -88,17 +98,24 @@ if (process.env.GAGARIN_SETTINGS) { }, '/gagarin/wait': function (timeout, message, code, args, closure) { + "use strict"; + + check(timeout, Number); + check(message, String); + check(code, String); + check(args, Array); + check(closure, Object); var future = new Future(); var done = false; - var handle = null; + var handle1 = null; var handle2 = null; var context = vm.createContext(global); context.Fiber = Fiber; function resolve (feedback) { - // TODO: why do we need this sentinel? + // TODO: can we do away with this sentinel? if (done) { return; } @@ -111,6 +128,7 @@ if (process.env.GAGARIN_SETTINGS) { } future['return'](feedback); //------------------------- + clearTimeout(handle1); clearTimeout(handle2); } @@ -130,7 +148,7 @@ if (process.env.GAGARIN_SETTINGS) { resolve(feedback); } - handle = setTimeout(Meteor.bindEnvironment(test), 50); // repeat after 1/20 sec. + handle1 = setTimeout(Meteor.bindEnvironment(test), 50); // repeat after 1/20 sec. if (feedback.closure) { closure = feedback.closure; @@ -142,7 +160,6 @@ if (process.env.GAGARIN_SETTINGS) { }()); handle2 = setTimeout(function () { - clearTimeout(handle); resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); }, timeout); } else { @@ -155,130 +172,18 @@ if (process.env.GAGARIN_SETTINGS) { }); Meteor.startup(function () { - // this is a fake, we won't need it anymore - console.log('Gagarin ready ...'); + console.log('Поехали!'); // Let's ride! (Gagarin, during the Vostok 1 launch) }); } -function evaluateAsWait(name, timeout, message, code, args, closure, socket) { - "use strict"; - - // maybe we could avoid creating it multiple times? - var context = vm.createContext(global); - var myFiber = null; - var handle = null; - var handle2 = null; - - context.Fiber = Fiber; - - function __closure__(values) { - myFiber = Fiber.current; - if (!myFiber) { - throw new Error('you can only call $sync inside a fiber'); - } - if (arguments.length > 0) { - writeToSocket(socket, name, { ping: true, closure: values }); - } else { - writeToSocket(socket, name, { ping: true }); - } - return Fiber.yield(); - } - - function resolve (data) { - if (!data.closure) { - data.closure = closure; - } - writeToSocket(socket, name, data); - //-------------------------------- - clearTimeout(handle2); - } - - try { - vm.runInContext("value = " + wrapSourceCode(code, args, closure), context); - } catch (err) { - return resolve({ error: err }); - } - - if (typeof context.value === 'function') { - Fiber(function () { - - (function test() { - var data; - try { - data = context.value.apply(null, values(closure, __closure__)); - if (data.result) { - return resolve(data); - } - - handle = setTimeout(Meteor.bindEnvironment(test), 50); // repeat after 1/20 sec. - - if (data.closure) { - closure = data.closure; - } - - //console.log("DATA IS", data); - - } catch (err) { - resolve({ error: err }); - } - }()); - - handle2 = setTimeout(function () { - clearTimeout(handle); - resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); - }, timeout); - - }).run(); - } - - return function (values) { - myFiber && myFiber.run(values); - }; -} - // HELPERS -// TODO: make a note that users cannot use __closure__ variable for syncing - -function addSyncChunks(chunks, closure, accessor) { - - // we don't want this "$sync" for now - return; - - accessor = accessor || "arguments[arguments.length-1]"; - - chunks.push( - - " var $sync = (function (__closure__) {", - " return function () {", - " return (function (__closure__) {", - " console.log('==============================', c);" - ); - - Object.keys(closure).forEach(function (key) { - chunks.push(" " + key + " = __closure__.hasOwnProperty(" + JSON.stringify(key) + ") ? __closure__[" + JSON.stringify(key) + "] : " + key + ";"); - }); - - chunks.push( - " console.log('==============================', c);", - " return __closure__;", - " })(__closure__.apply(this, arguments));", - " }", - " })(" + accessor + ");", - - // TODO: implement this feature - " $sync.stop = function () {};" - ); -} - function wrapSourceCode(code, args, closure, accessor) { var chunks = []; chunks.push("function (" + Object.keys(closure).join(', ') + ") {"); - addSyncChunks(chunks, closure, accessor); - chunks.push( " return (function (result) {", " return {", @@ -317,13 +222,3 @@ function stringify(value) { return value !== undefined ? JSON.stringify(value) : "undefined"; } -function writeToSocket(socket, name, data) { - if (data.error) { - data.error = (typeof data.error === 'object') ? (data.error && data.error.message) : data.error.toString(); - } - if (name) { - data.name = name; - } - //---------------------------------------- - socket.write(data); -} diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index 689714e..88536ed 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -360,18 +360,6 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { }; -// TODO: implement these two guys - -//BrowserPromiseChain.prototype.findElementAndClick = function (selector, keys, cb) { -// "use strict"; -// -//}; - -//BrowserPromiseChain.prototype.findElementAndSendKeys = function (selector, keys, cb) { -// "use strict"; -// -//}; - function values(closure) { "use strict"; diff --git a/lib/ddp.js b/lib/ddp.js index 969d01c..bfa9ffe 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -3,7 +3,7 @@ var DDPClient = require('ddp'); var chalk = require('chalk'); var Promise = require('es6-promise').Promise; -module.exports = function makeDDPClientFactory (emitter, requestGagarinConfig) { +module.exports = function makeDDPClientFactory (requestGagarinConfig) { "use strict"; var self = this; diff --git a/lib/meteor.js b/lib/meteor.js index 7be7dd7..b640864 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -127,7 +127,7 @@ function Meteor (options) { meteor.stdout.on('data', function (data) { //process.stdout.write(data); - var match = /Gagarin ready .../.exec(data.toString()); + var match = /Поехали!/.exec(data.toString()); if (match) { // make sure we won't kill this process by accident clearTimeout(meteorSafetyTimeout); diff --git a/lib/transponder.js b/lib/transponder.js index a083502..f37cedc 100644 --- a/lib/transponder.js +++ b/lib/transponder.js @@ -1,19 +1,18 @@ -var EventEmiter = require('events').EventEmitter; + var Promise = require('es6-promise').Promise; var util = require('util'); var tools = require('./tools'); var either = require('./tools').either; var makeDDPClientFactory = require('./ddp'); +module.exports = MeteorTransponder; + function MeteorTransponder(requestGagarinConfig) { "use strict"; var self = this; - EventEmiter.call(self); // iherits from EventEmitter - - //var socketAsPromise = makeSocketFactory(self, requestGagarinConfig); - var ddpClientAsPromise = makeDDPClientFactory(self, requestGagarinConfig); + var ddpClientAsPromise = makeDDPClientFactory(requestGagarinConfig); function factory(name) { return function (code, args) { @@ -67,15 +66,3 @@ function MeteorTransponder(requestGagarinConfig) { }; -util.inherits(MeteorTransponder, EventEmiter); - -module.exports = MeteorTransponder; - -// HELPERS - -function uniqe() { - "use strict"; - - if (!uniqe.counter) { uniqe.counter = 0; } - return uniqe.counter++; -} From ab7d4dc2dc357fb0b5d3a3693ee321ec4bae0b9d Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 11:53:55 +0100 Subject: [PATCH 05/64] Added some comments and "use strict" --- backdoor.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/backdoor.js b/backdoor.js index f7b9406..7925133 100644 --- a/backdoor.js +++ b/backdoor.js @@ -8,6 +8,9 @@ Gagarin = {}; if (process.env.GAGARIN_SETTINGS) { + // TODO: also protect these methods with some authentication (user/password/token?) + // note that required data my be provided with GAGARIN_SETTINGS + Meteor.methods({ '/gagarin/execute': function (code, args, closure) { "use strict"; @@ -180,9 +183,14 @@ if (process.env.GAGARIN_SETTINGS) { // HELPERS function wrapSourceCode(code, args, closure, accessor) { + "use strict"; + var chunks = []; - chunks.push("function (" + Object.keys(closure).join(', ') + ") {"); + chunks.push( + "function (" + Object.keys(closure).join(', ') + ") {", + " 'use strict';" + ); chunks.push( " return (function (result) {", @@ -206,6 +214,8 @@ function wrapSourceCode(code, args, closure, accessor) { } function values(closure) { + "use strict"; + var values = Object.keys(closure).map(function (key) { return closure[key]; }); @@ -216,6 +226,8 @@ function values(closure) { } function stringify(value) { + "use strict"; + if (typeof value === 'function') { return value.toString(); } From d40fe0ec20d7e5c2c97df7e281d0489bde028827 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 13:38:17 +0100 Subject: [PATCH 06/64] Refactor gagarin to be a custom mocha interface --- lib/{gagarin.js => interface.js} | 77 ++++---------------------------- 1 file changed, 8 insertions(+), 69 deletions(-) rename lib/{gagarin.js => interface.js} (66%) diff --git a/lib/gagarin.js b/lib/interface.js similarity index 66% rename from lib/gagarin.js rename to lib/interface.js index 579cf9f..46180b9 100644 --- a/lib/gagarin.js +++ b/lib/interface.js @@ -1,30 +1,21 @@ var mergeHelpers = require('./tools').mergeHelpers; -var cleanError = require('./tools').cleanError; -var Promise = require('es6-promise').Promise; var Closure = require('./closure'); var Browser = require('./browser'); var helpers = require('./helpers'); var Meteor = require('./meteor'); var Mocha = require('mocha'); -var chalk = require('chalk'); -var path = require('path'); -var util = require('util'); -var wd = require('wd'); -var BuildAsPromise = Meteor.BuildAsPromise; - -module.exports = Gagarin; - -function Gagarin (gagarinOptions) { +Mocha.interfaces['gagarin'] = module.exports = function (suite) { "use strict"; - // TODO: also integrate with other UI's - gagarinOptions.ui = 'bdd'; + // TODO: allow integration with other interfaces + + // use the original bdd intrface + Mocha.interfaces.bdd.apply(this, arguments); - // TODO: filter out our custom options - Mocha.call(this, gagarinOptions); + var gagarinOptions = this.options; - this.suite.on('pre-require', function (context) { + suite.on('pre-require', function (context) { var before = context.before; var after = context.after; @@ -131,52 +122,9 @@ function Gagarin (gagarinOptions) { }; context.wait = wait; - }); -} - -util.inherits(Gagarin, Mocha); - -Gagarin.prototype.run = function (callback) { - "use strict"; - - var pathToApp = this.options.pathToApp || path.resolve('.'); - var self = this; - - process.stdout.write('\n'); - - var title = 'building app => ' + pathToApp; - - var counter = 0; - var spinner = '/-\\|'; - var handle = setInterval(function () { - var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); - process.stdout.write( - chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') - ); - }, 100); - BuildAsPromise(pathToApp).then(function () { - - clearInterval(handle); - process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); - - Mocha.prototype.run.call(self, callback); - - }, function (err) { - // clear the loading spinner - process.stdout.write(new Array(title.length + 12).join(' ')); - clearInterval(handle); - throw err; - }) - .catch(function (err) { - // make sure the error passes through promise - setTimeout(function () { - throw err; - }); - }); - -}; +} function wait(timeout, message, func, args) { "use strict"; @@ -203,13 +151,4 @@ function wait(timeout, message, func, args) { }); } -function defaults(object, defaultValues) { - Object.keys(defaultValues).forEach(function (key) { - if (object[key] === 'undefined') { - object[key] = defaultValues[key]; - } - }) -} - - From 7342fb13b14800ca32f1797e2459c47bea1668f4 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 13:50:10 +0100 Subject: [PATCH 07/64] Started adding some documentation --- backdoor.js | 32 ++++++++++++++++---- lib/gagarin.js | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 lib/gagarin.js diff --git a/backdoor.js b/backdoor.js index 7925133..9188b95 100644 --- a/backdoor.js +++ b/backdoor.js @@ -180,9 +180,15 @@ if (process.env.GAGARIN_SETTINGS) { } -// HELPERS - -function wrapSourceCode(code, args, closure, accessor) { +/** + * Creates a source code of another function, providing the given + * arguments and injecting the given closure variables. + * + * @param {String} code + * @param {Array} args + * @param {Object} closure + */ +function wrapSourceCode(code, args, closure) { "use strict"; var chunks = []; @@ -213,11 +219,17 @@ function wrapSourceCode(code, args, closure, accessor) { return chunks.join('\n'); } -function values(closure) { +/** + * Returns all values of the object, sorted + * alphabetically by corresponding keys. + * + * @param {Object} + */ +function values(object) { "use strict"; - var values = Object.keys(closure).map(function (key) { - return closure[key]; + var values = Object.keys(object).map(function (key) { + return object[key]; }); if (arguments.length > 1) { values.push.apply(values, Array.prototype.slice.call(arguments, 1)); @@ -225,6 +237,14 @@ function values(closure) { return values; } +/** + * A thin wrapper around JSON.stringify: + * + * - `undefined` gets evaluated to "undefined" + * - a function gets evaluated to source code + * + * @param {Object} value + */ function stringify(value) { "use strict"; diff --git a/lib/gagarin.js b/lib/gagarin.js new file mode 100644 index 0000000..fe8eb55 --- /dev/null +++ b/lib/gagarin.js @@ -0,0 +1,80 @@ + +var Meteor = require('./meteor'); +var Mocha = require('mocha'); +var chalk = require('chalk'); +var path = require('path'); +var util = require('util'); + +var BuildAsPromise = Meteor.BuildAsPromise; + +module.exports = Gagarin; + +/** + * Creates Gagarin with `options`. + * + * It inherits everything from Mocha except that the ui + * is always set to "gagarin". + * + * @param {Object} options + */ +function Gagarin (options) { + "use strict"; + + // XXX gagarin user interface is defined here + require('./interface'); + + options.ui = 'gagarin'; + + // TODO: filter out our custom options + Mocha.call(this, options); +} + +util.inherits(Gagarin, Mocha); + +/** + * A not-so-thin wrapper around Mocha.run; first build the + * meteor app, then run the tests. + * + * @param {Function} callback + */ +Gagarin.prototype.run = function (callback) { + "use strict"; + + var pathToApp = this.options.pathToApp || path.resolve('.'); + var self = this; + + process.stdout.write('\n'); + + var title = 'building app => ' + pathToApp; + + var counter = 0; + var spinner = '/-\\|'; + var handle = setInterval(function () { + var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); + process.stdout.write( + chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') + ); + }, 100); + + BuildAsPromise(pathToApp).then(function () { + + clearInterval(handle); + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); + + Mocha.prototype.run.call(self, callback); + + }, function (err) { + // clear the loading spinner + process.stdout.write(new Array(title.length + 12).join(' ')); + clearInterval(handle); + throw err; + }) + .catch(function (err) { + // make sure the error passes through promise + setTimeout(function () { + throw err; + }); + }); + +}; + From 0b4d214440dbea2e0dc3f637e40b48606be1d24f Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 14:18:19 +0100 Subject: [PATCH 08/64] Transponder no longer returns promises --- lib/gagarin.js | 4 ++++ lib/meteor.js | 14 ++++++++++++- lib/meteorPromiseChain.js | 14 +++++++++++-- lib/transponder.js | 43 +++++++++++++++++++++++++++------------ 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/lib/gagarin.js b/lib/gagarin.js index fe8eb55..9a7ac10 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -1,4 +1,8 @@ +/** + * Module dependencies. + */ + var Meteor = require('./meteor'); var Mocha = require('mocha'); var chalk = require('chalk'); diff --git a/lib/meteor.js b/lib/meteor.js index b640864..6f281c4 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -83,6 +83,8 @@ function Meteor (options) { var meteorHasCrashed = false; var meteorSafetyTimeout = null; + + function cleanUpThen(callback) { if (meteor) { meteor.once('exit', callback); @@ -95,7 +97,17 @@ function Meteor (options) { } } - function requestGagarinConfig (meteorNeedRestart, meteorRestartTimeout) { + function requestGagarinConfig (meteorNeedRestart, meteorRestartTimeout) { // and callback + + var callback = arguments[arguments.length-1]; + + if (arguments.length < 3) { + meteorRestartTimeout = undefined; + } + + if (arguments.length < 2) { + meteorNeedRestart = undefined; + } if (meteorHasCrashed && !meteorNeedRestart) { if (lastError) { diff --git a/lib/meteorPromiseChain.js b/lib/meteorPromiseChain.js index 5912360..276b776 100644 --- a/lib/meteorPromiseChain.js +++ b/lib/meteorPromiseChain.js @@ -1,4 +1,5 @@ var Promise = require('es6-promise').Promise; +var either = require('./tools').either; module.exports = MeteorPromiseChain; @@ -104,8 +105,17 @@ MeteorPromiseChain.methods.forEach(function (name) { self._promise = Promise.all([ self._operand, self._promise ]).then(function (all) { - // use the promise returned by operand - return all[0][name].apply({}, args); + return new Promise(function (resolve, reject) { + var operand = all[0]; + if (!operand || typeof operand !== 'object') { + reject(new Error('MeteorPromiseChain: invalid operand')); + } else if (!operand[name]) { + reject(new Error('MeteorPromiseChain: operand does not implement method: ' + name)); + } else { + args.push(either(reject).or(resolve)); + return operand[name].apply(operand, args); + } + }); }); return self; }; diff --git a/lib/transponder.js b/lib/transponder.js index f37cedc..398fa12 100644 --- a/lib/transponder.js +++ b/lib/transponder.js @@ -16,8 +16,9 @@ function MeteorTransponder(requestGagarinConfig) { function factory(name) { return function (code, args) { + var cb = arguments[arguments.length-1]; - if (arguments.length < 2) { + if (arguments.length < 3) { args = []; } else { args = Array.isArray(args) ? args : [ args ]; @@ -25,9 +26,7 @@ function MeteorTransponder(requestGagarinConfig) { //------------------------------------------------- return ddpClientAsPromise().then(function (ddpClient) { - return new Promise(function (resolve, reject) { - ddpClient[name](code, args, either(reject).or(resolve)); - }); + ddpClient[name](code, args, cb); }); } } @@ -36,31 +35,49 @@ function MeteorTransponder(requestGagarinConfig) { self.execute = factory('execute'); self.wait = function (timeout, message, code, args) { + var cb = arguments[arguments.length-1]; + if (arguments.length < 4) { args = []; } else { args = Array.isArray(args) ? args : [ args ]; } - //------------------------------------------------- + //----------------------------------------------------- return ddpClientAsPromise().then(function (ddpClient) { - return new Promise(function (resolve, reject) { - ddpClient.wait(timeout, message, code, args, either(reject).or(resolve)); - }); + ddpClient.wait(timeout, message, code, args, cb); }); } - self.start = function () { - return requestGagarinConfig(); + self.start = function (cb) { + return requestGagarinConfig().then(function (value) { + cb(null, value); + }, function (err) { + cb(err); + }); }; - self.restart = function (restartTimeout) { - return requestGagarinConfig(true, restartTimeout); + self.restart = function (restartTimeout, cb) { + var cb = arguments[arguments.length-1]; + + if (arguments.length < 2) { + restartTimeout = undefined; + } + + return requestGagarinConfig(true, restartTimeout).then(function (value) { + cb(null, value); + }, function (err) { + cb(err); + }); }; - self.exit = function () { + self.exit = function (cb) { return requestGagarinConfig().then(function (config) { return config.exitAsPromise(); + }).then(function (value) { + cb(null, value); + }, function (err) { + cb(err); }); }; From 6546c6b9225151faa55ee72d6fc6a29560ec4404 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 14:29:21 +0100 Subject: [PATCH 09/64] Do not swallow errors from server --- lib/transponder.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/transponder.js b/lib/transponder.js index 398fa12..075efb9 100644 --- a/lib/transponder.js +++ b/lib/transponder.js @@ -26,7 +26,11 @@ function MeteorTransponder(requestGagarinConfig) { //------------------------------------------------- return ddpClientAsPromise().then(function (ddpClient) { - ddpClient[name](code, args, cb); + ddpClient[name](code, args, function (err) { + cb.apply(this, arguments); + }); + }, function (err) { + cb(err); }); } } @@ -37,7 +41,7 @@ function MeteorTransponder(requestGagarinConfig) { self.wait = function (timeout, message, code, args) { var cb = arguments[arguments.length-1]; - if (arguments.length < 4) { + if (arguments.length < 5) { args = []; } else { args = Array.isArray(args) ? args : [ args ]; @@ -57,7 +61,7 @@ function MeteorTransponder(requestGagarinConfig) { }); }; - self.restart = function (restartTimeout, cb) { + self.restart = function (restartTimeout) { var cb = arguments[arguments.length-1]; if (arguments.length < 2) { From 7a08f40e00ed91b249971a8130656a0d4cf30c29 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 16:37:20 +0100 Subject: [PATCH 10/64] Make closure the first argument --- backdoor.js | 12 +++++++++--- lib/ddp.js | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backdoor.js b/backdoor.js index 9188b95..12a9da5 100644 --- a/backdoor.js +++ b/backdoor.js @@ -12,9 +12,11 @@ if (process.env.GAGARIN_SETTINGS) { // note that required data my be provided with GAGARIN_SETTINGS Meteor.methods({ - '/gagarin/execute': function (code, args, closure) { + '/gagarin/execute': function (closure, code, args) { "use strict"; + args = args || []; + check(code, String); check(args, Array); check(closure, Object); @@ -37,9 +39,11 @@ if (process.env.GAGARIN_SETTINGS) { } }, - '/gagarin/promise': function (code, args, closure) { + '/gagarin/promise': function (closure, code, args) { "use strict"; + args = args || []; + check(code, String); check(args, Array); check(closure, Object); @@ -100,9 +104,11 @@ if (process.env.GAGARIN_SETTINGS) { } }, - '/gagarin/wait': function (timeout, message, code, args, closure) { + '/gagarin/wait': function (closure, timeout, message, code, args) { "use strict"; + args = args || []; + check(timeout, Number); check(message, String); check(code, String); diff --git a/lib/ddp.js b/lib/ddp.js index bfa9ffe..a12224c 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -64,15 +64,15 @@ module.exports = function makeDDPClientFactory (requestGagarinConfig) { resolve({ execute: function (code, args, cb) { - callDDPMethod('/gagarin/execute', [ code.toString(), args, config.closure() ], cb); + callDDPMethod('/gagarin/execute', [ config.closure(), code.toString(), args ], cb); }, promise: function (code, args, cb) { - callDDPMethod('/gagarin/promise', [ code.toString(), args, config.closure() ], cb); + callDDPMethod('/gagarin/promise', [ config.closure(), code.toString(), args ], cb); }, wait: function (timeout, message, code, args, cb) { - callDDPMethod('/gagarin/wait', [ timeout, message, code.toString(), args, config.closure() ], cb); + callDDPMethod('/gagarin/wait', [ config.closure(), timeout, message, code.toString(), args ], cb); }, }); From 05151bbab3e39f59e670af1d3c78023549bfb48e Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 16:37:45 +0100 Subject: [PATCH 11/64] Moved process logic no a separated module --- lib/instance.js | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 lib/instance.js diff --git a/lib/instance.js b/lib/instance.js new file mode 100644 index 0000000..b71e03d --- /dev/null +++ b/lib/instance.js @@ -0,0 +1,88 @@ +var spawn = require('child_process').spawn; + +module.exports = function Instance(node, main, env, options) { + + var meteor = null; + var lastError = ""; + var lastErrorAt = "nowhere"; + + setTimeout(function () { + try { + meteor = spawn(node, [ main ], { env: env }); + } catch (err) { + return options.onSpawn && options.onSpawn(err); + } + + meteor.stdout.on('data', function (data) { + var match = /Поехали!/.exec(data.toString()); + if (match) { + options.onSpawn && options.onSpawn(); + } + }); + + // make sure we kill meteor on process exit + process.once('exit', onProcessExit); + + meteor.stderr.on('data', function (data) { // look for errors + data.toString().split('\n').forEach(function (line) { + var hasMatch = [ + { + regExp: /Error\:\s*(.*)/, + action: function (match) { + lastError = match[1]; + lastErrorAt = ''; + }, + }, + { + regExp: /at\s.*/, + action: function (match) { + if (!lastErrorAt) { + lastErrorAt = match[0]; + } + }, + }, + ].some(function (options) { + var match = options.regExp.exec(line); + if (match) { + options.action.call(null, match); + return true; + } + }); + if (lastError && !hasMatch) { + lastError += '\n' + line; + } + }); + }); + + meteor.on('exit', function (code) { + if (options.onExit) { + options.onExit(code, lastError, lastErrorAt); + } + meteor = null; + }); + + }); + + this.kill = function (cb) { + if (!meteor) { + return cb(); + } + meteor.once('exit', function (code) { + if (code === 0 || code === 130) { + cb(); + } else { + cb(new Error('exited with code ' + code)); + } + }); + meteor.kill('SIGINT'); + meteor = null; + //-------------------------------------------- + process.removeListener('exit', onProcessExit); + } + + function onProcessExit() { + meteor && meteor.kill(); + meteor = null; + } + +} From 23f8bc2194132aff8184ed98641b03280afaa8da Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 17:51:36 +0100 Subject: [PATCH 12/64] A big step towards simplicity --- lib/ddp.js | 32 +---- lib/meteor.js | 275 ++++++++++++++++------------------------- lib/remote.js | 127 +++++++++++++++++++ lib/transponder.js | 89 ------------- tests/specs/browser.js | 13 +- 5 files changed, 239 insertions(+), 297 deletions(-) create mode 100644 lib/remote.js delete mode 100644 lib/transponder.js diff --git a/lib/ddp.js b/lib/ddp.js index a12224c..13ae89b 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -45,37 +45,7 @@ module.exports = function makeDDPClientFactory (requestGagarinConfig) { if (err) { return reject(err); } - - function callDDPMethod (name, args, cb) { - ddpClient.call(name, args, function (err, feedback) { - if (feedback && feedback.closure) { - config.closure(feedback.closure); - } - if (err) { - return cb(err); - } - if (feedback.error) { - return cb(new Error(feedback.error)); - } - cb(null, feedback.result); - }); - } - - resolve({ - - execute: function (code, args, cb) { - callDDPMethod('/gagarin/execute', [ config.closure(), code.toString(), args ], cb); - }, - - promise: function (code, args, cb) { - callDDPMethod('/gagarin/promise', [ config.closure(), code.toString(), args ], cb); - }, - - wait: function (timeout, message, code, args, cb) { - callDDPMethod('/gagarin/wait', [ config.closure(), timeout, message, code.toString(), args ], cb); - }, - - }); + resolve(ddpClient); }); //------------------------------------------------------------- diff --git a/lib/meteor.js b/lib/meteor.js index 6f281c4..4887cf0 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -1,8 +1,10 @@ +var makeDDPClientFactory = require('./ddp'); var MongoServerAsPromise = require('./mongo'); var MeteorPromiseChain = require('./meteorPromiseChain'); -var MeteorTransponder = require('./transponder'); var BuildAsPromise = require('./build'); +var Instance = require('./instance'); +var Remote = require('./remote'); var Closure = require('./closure'); var Promise = require('es6-promise').Promise; var either = require('./tools').either; @@ -24,6 +26,16 @@ function Meteor (options) { "use strict"; var mongoServerPromise = null; + var nodePath = tools.getNodePath(options.pathToApp); + var instance = null; + var lastError = ""; + var lastErrorAt = "nowhere"; + var meteorPromise = null; + var meteorHasCrashed = false; + var meteorSafetyTimeout = null; + var ddpClientAsPromise = makeDDPClientFactory(serverControllerProvider); + var meteorRestartTimeout = 100; + var restartRequired = false; if (typeof options === 'string') { options = { pathToApp: options }; @@ -37,12 +49,7 @@ function Meteor (options) { options.location = 'http://localhost:' + options.port; //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ - var accessor = function () { return arguments.length === 0 ? {} : undefined }; - var release = tools.getReleaseConfig(options.pathToApp); - var env = Object.create(process.env); - - env.ROOT_URL = options.location; - env.PORT = options.port; + var remote = new Remote(ddpClientAsPromise, serverControllerProvider); mongoServerPromise = new MongoServerAsPromise(options); @@ -50,210 +57,136 @@ function Meteor (options) { this.helpers = options.helpers || {}; this.useClosure = function (objectOrGetter) { - if (typeof objectOrGetter !== 'function' && typeof objectOrGetter !== 'object') { - throw new Error('closure must be either function or object'); - } - accessor = function (values) { - var closure = (typeof objectOrGetter === 'function') ? objectOrGetter() : objectOrGetter; - if (arguments.length === 0) { - return closure ? closure.getValues() : {}; - } - closure && closure.setValues(values); - } + remote.useClosure(objectOrGetter); }; - this.meteorProcessAsPromise = Promise.all([ - - BuildAsPromise(options.pathToApp), mongoServerPromise - - ]).then(function (all) { + this.meteorRemoteAsPromise = Promise.resolve(remote); - var pathToMain = all[0]; - - env.MONGO_URL = all[1] + '/' + options.dbName; - env.GAGARIN_SETTINGS = "{}"; + function cleanUpThen(callback) { + if (instance) { + instance.kill(callback); + instance = null; + clearTimeout(meteorSafetyTimeout); + meteorSafetyTimeout = null; + } else { + setTimeout(callback); + } + } - return new Promise(function (resolve, reject) { - - var nodePath = tools.getNodePath(options.pathToApp); - var meteor = null; - var lastError = ""; - var lastErrorAt = "nowhere"; - var meteorPromise = null; - var meteorHasCrashed = false; - var meteorSafetyTimeout = null; + function serverControllerProvider () { // and callback - + var callback = arguments[arguments.length-1]; - function cleanUpThen(callback) { - if (meteor) { - meteor.once('exit', callback); - meteor.kill(); - meteor = null; - clearTimeout(meteorSafetyTimeout); - meteorSafetyTimeout = null; - } else { - setTimeout(callback); - } + if (meteorHasCrashed && !restartRequired) { + restartRequired = true; + if (lastError) { + return Promise.reject(new Error(chalk.red(lastError) + chalk.blue(' => ') + chalk.blue(lastErrorAt))); } + return Promise.reject(new Error(chalk.red('Meteor server has crashed due to some unknown reason.'))); + } - function requestGagarinConfig (meteorNeedRestart, meteorRestartTimeout) { // and callback - - var callback = arguments[arguments.length-1]; + if (!meteorHasCrashed && !restartRequired && meteorPromise) { + return meteorPromise; + } - if (arguments.length < 3) { - meteorRestartTimeout = undefined; - } + restartRequired = false; - if (arguments.length < 2) { - meteorNeedRestart = undefined; - } + meteorPromise = new Promise(function (resolve, reject) { - if (meteorHasCrashed && !meteorNeedRestart) { - if (lastError) { - return Promise.reject(new Error(chalk.red(lastError) + chalk.blue(' => ') + chalk.blue(lastErrorAt))); + var instanceOptions = { + onSpawn: function (err) { + if (err) { + return reject(err); } - return Promise.reject(new Error(chalk.red('Meteor server has crashed due to some unknown reason.'))); - } - - if (!meteorHasCrashed && !meteorNeedRestart && meteorPromise) { - return meteorPromise; - } - - meteorPromise = new Promise(function (resolve, reject) { - - cleanUpThen(function respawn() { - - setTimeout(function () { - - meteorSafetyTimeout = setTimeout(function () { - cleanUpThen(function () { - reject(new Error('Gagarin is not there.' + - ' Please make sure you have added it with: meteor add anti:gagarin.')); - }); - }, options.safetyTimeout || 10000); - - meteorHasCrashed = false - - meteor = spawn(nodePath, [ pathToMain ], { env: env }); - - meteor.stdout.on('data', function (data) { - //process.stdout.write(data); - var match = /Поехали!/.exec(data.toString()); - if (match) { - // make sure we won't kill this process by accident - clearTimeout(meteorSafetyTimeout); - meteorSafetyTimeout = null; - resolve({ - meteorPort : options.port, - uniqueToken : Math.random(), - exitAsPromise : makeExitAsPromise(meteor, mongoServerPromise, options.dbName, cleanUpThen), - closure : function () { - // XXX note that accessor may change dynamically - return accessor.apply(this, arguments); - }, - }); + clearTimeout(meteorSafetyTimeout); + meteorSafetyTimeout = null; + resolve({ + meteorPort : options.port, + uniqueToken : Math.random(), + restart : function (cb) { + console.log('restarting'); + restartRequired = true; + return serverControllerProvider(true).then(function () { + cb(); + }, function (err) { + cb(err); + }); + }, + stop : function (cb) { + cleanUpThen(function (err) { + if (err) { + return cb(err); } + mongoServerPromise.connectToDB(options.dbName).then(function (db) { + db.dropDatabase(cb); + }, function (err) { + cb(err); + }); }); + }, + }); + }, + onExit: function (code, _lastError, _lastErrorAt) { + meteorHasCrashed = code !== 0; + //---------------------------- + meteorPromise = null; + instance = null; + lastError = _lastError; + lastErrorAt = _lastErrorAt; + }, + }; - meteor.stderr.on('data', function (data) { + cleanUpThen(function respawn() { - // seek for errors + setTimeout(function () { - data.toString().split('\n').forEach(function (line) { + meteorHasCrashed = false - var hasMatch = [ + Promise.all([ - { - regExp: /Error\:\s*(.*)/, - action: function (match) { - lastError = match[1]; - lastErrorAt = ''; - }, - }, + BuildAsPromise(options.pathToApp), mongoServerPromise - { - regExp: /at\s.*/, - action: function (match) { - if (!lastErrorAt) { - lastErrorAt = match[0]; - } - }, - }, + ]).then(function (all) { - ].some(function (options) { - var match = options.regExp.exec(line); - if (match) { - options.action.call(null, match); - return true; - } - }); + var pathToMain = all[0]; + var env = Object.create(process.env); - if (lastError && !hasMatch) { - lastError += '\n' + line; - } + env.ROOT_URL = options.location; + env.PORT = options.port; + env.MONGO_URL = all[1] + '/' + options.dbName; + env.GAGARIN_SETTINGS = "{}"; - }); + instance = new Instance(nodePath, pathToMain, env, instanceOptions); - // process.stdout.write(chalk.red(data)); + meteorSafetyTimeout = setTimeout(function () { + cleanUpThen(function () { + reject(new Error('Gagarin is not there.' + + ' Please make sure you have added it with: meteor add anti:gagarin.')); }); + }, options.safetyTimeout || 10000); - meteor.on('exit', function (code) { - meteorHasCrashed = code !== 0; - //---------------------------- - meteorPromise = null; - meteor = null; - }); - - }, meteorRestartTimeout); - - // TODO: do we even need this one? - //process.on('exit', function () { - // meteor && meteor.kill(); - // meteor = null; - //}); - + }, function (err) { + reject(err); }); - }); - - return meteorPromise; - } + }, meteorRestartTimeout); - //--------------------------------------------------- - resolve(new MeteorTransponder(requestGagarinConfig)); + }); }); - }); + return meteorPromise; + } // serverControllerProvider + } MeteorPromiseChain.methods.forEach(function (name) { "use strict"; Meteor.prototype[name] = function () { - var chain = new MeteorPromiseChain(this.meteorProcessAsPromise, this.helpers); + var chain = new MeteorPromiseChain(this.meteorRemoteAsPromise, this.helpers); return chain[name].apply(chain, arguments); }; }); -// HELPERS - -function makeExitAsPromise(meteorProcess, mongoServerPromise, databaseName, teardownRoutine) { - "use strict"; - - return function exitAsPromise () { - return new Promise(function (resolve, reject) { - meteorProcess.once('error', reject); - teardownRoutine(resolve); - }).then(function () { - return mongoServerPromise.connectToDB(databaseName).then(function (db) { - return new Promise(function (resolve, reject) { - db.dropDatabase(either(reject).or(resolve)); - }); - }); - }); - }; - -} diff --git a/lib/remote.js b/lib/remote.js new file mode 100644 index 0000000..313ae63 --- /dev/null +++ b/lib/remote.js @@ -0,0 +1,127 @@ + +module.exports = Remote; + +function Remote(ddpClientProvider, serverControllerProvider) { + "use strict"; + + var closure = function () { return arguments.length === 0 ? {} : undefined }; + var self = this; + + function callDDPMethod (ddpClient, name, args, cb) { + if (!ddpClient) { + return cb(new Error('invalid ddpClient')); + } + ddpClient.call(name, args, function (err, feedback) { + if (feedback && feedback.closure) { + closure(feedback.closure); + } + if (err) { + return cb(err); + } + if (feedback.error) { + return cb(new Error(feedback.error)); + } + cb(null, feedback.result); + }); + } + + this.useClosure = function (objectOrGetter) { + if (typeof objectOrGetter !== 'function' && typeof objectOrGetter !== 'object') { + throw new Error('closure must be either function or object'); + } + closure = function (values) { + var closure = (typeof objectOrGetter === 'function') ? objectOrGetter() : objectOrGetter; + if (arguments.length === 0) { + return closure ? closure.getValues() : {}; + } + closure && closure.setValues(values); + } + }; + + this.promise = function (code, args) { + var cb = arguments[arguments.length-1]; + + if (arguments.length < 3) { + args = []; + } else { + args = Array.isArray(args) ? args : [ args ]; + } + + return ddpClientProvider().then(function (ddpClient) { + callDDPMethod(ddpClient, '/gagarin/promise', + [ closure(), code.toString(), args ], cb); + }, function (err) { + cb(err); + }); + } + + this.execute = function (code, args) { + var cb = arguments[arguments.length-1]; + + if (arguments.length < 3) { + args = []; + } else { + args = Array.isArray(args) ? args : [ args ]; + } + + return ddpClientProvider().then(function (ddpClient) { + callDDPMethod(ddpClient, '/gagarin/execute', + [ closure(), code.toString(), args ], cb); + }, function (err) { + cb(err); + }); + } + + this.wait = function (timeout, message, code, args) { + var cb = arguments[arguments.length-1]; + + if (arguments.length < 5) { + args = []; + } else { + args = Array.isArray(args) ? args : [ args ]; + } + + // TODO: verify timeout + + return ddpClientProvider().then(function (ddpClient) { + callDDPMethod(ddpClient, '/gagarin/wait', + [ closure(), timeout, message, code.toString(), args ], cb); + }, function (err) { + cb(err); + }); + } + + self.start = function (cb) { + return serverControllerProvider().then(function (value) { + cb(null, value); + }, function (err) { + cb(err); + }); + }; + + // TODO: retry a few times on error ... + self.restart = function (restartTimeout) { + var cb = arguments[arguments.length-1]; + + if (arguments.length < 2) { + restartTimeout = undefined; + } + + return serverControllerProvider().then(function (controller) { + controller.restart(cb); + }, function (err) { + cb(err); + }); + }; + + self.exit = function (cb) { + return serverControllerProvider().then(function (controller) { + controller.stop(cb); + }, function (err) { + cb(err); + }); + }; + +}; + + diff --git a/lib/transponder.js b/lib/transponder.js deleted file mode 100644 index 075efb9..0000000 --- a/lib/transponder.js +++ /dev/null @@ -1,89 +0,0 @@ - -var Promise = require('es6-promise').Promise; -var util = require('util'); -var tools = require('./tools'); -var either = require('./tools').either; -var makeDDPClientFactory = require('./ddp'); - -module.exports = MeteorTransponder; - -function MeteorTransponder(requestGagarinConfig) { - "use strict"; - - var self = this; - - var ddpClientAsPromise = makeDDPClientFactory(requestGagarinConfig); - - function factory(name) { - return function (code, args) { - var cb = arguments[arguments.length-1]; - - if (arguments.length < 3) { - args = []; - } else { - args = Array.isArray(args) ? args : [ args ]; - } - - //------------------------------------------------- - return ddpClientAsPromise().then(function (ddpClient) { - ddpClient[name](code, args, function (err) { - cb.apply(this, arguments); - }); - }, function (err) { - cb(err); - }); - } - } - - self.promise = factory('promise'); - self.execute = factory('execute'); - - self.wait = function (timeout, message, code, args) { - var cb = arguments[arguments.length-1]; - - if (arguments.length < 5) { - args = []; - } else { - args = Array.isArray(args) ? args : [ args ]; - } - - //----------------------------------------------------- - return ddpClientAsPromise().then(function (ddpClient) { - ddpClient.wait(timeout, message, code, args, cb); - }); - } - - self.start = function (cb) { - return requestGagarinConfig().then(function (value) { - cb(null, value); - }, function (err) { - cb(err); - }); - }; - - self.restart = function (restartTimeout) { - var cb = arguments[arguments.length-1]; - - if (arguments.length < 2) { - restartTimeout = undefined; - } - - return requestGagarinConfig(true, restartTimeout).then(function (value) { - cb(null, value); - }, function (err) { - cb(err); - }); - }; - - self.exit = function (cb) { - return requestGagarinConfig().then(function (config) { - return config.exitAsPromise(); - }).then(function (value) { - cb(null, value); - }, function (err) { - cb(err); - }); - }; - -}; - diff --git a/tests/specs/browser.js b/tests/specs/browser.js index c1fcb85..160f788 100644 --- a/tests/specs/browser.js +++ b/tests/specs/browser.js @@ -97,12 +97,13 @@ describe('Tests with browser', function () { }); it ('another restart shoud work as well', function () { - return server.restart().execute(function () { - return Meteor.release; - }) - .then(function (release) { - expect(release).to.be.ok; - }); + return server.restart().then(function () { + return browser2.waitForConditionInBrowser("reset > 1", 5000) + .execute("return reset;") + .then(function (numberOfResets) { + expect(numberOfResets).to.equal(2); + }); + }); }); }); From d91e09e4ef34f16a96687c2c915b04511445b5d3 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 18:20:19 +0100 Subject: [PATCH 13/64] Clean up --- lib/meteor.js | 107 ++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/lib/meteor.js b/lib/meteor.js index 4887cf0..f6b5ee1 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -1,18 +1,19 @@ + +/** + * Module dependencies. + */ + var makeDDPClientFactory = require('./ddp'); var MongoServerAsPromise = require('./mongo'); var MeteorPromiseChain = require('./meteorPromiseChain'); var BuildAsPromise = require('./build'); var Instance = require('./instance'); -var Remote = require('./remote'); -var Closure = require('./closure'); var Promise = require('es6-promise').Promise; -var either = require('./tools').either; -var spawn = require('child_process').spawn; +var Remote = require('./remote'); var chalk = require('chalk'); var tools = require('./tools'); var path = require('path'); -var fs = require('fs'); module.exports = Meteor; module.exports.BuildAsPromise = BuildAsPromise; @@ -25,35 +26,35 @@ module.exports.MongoAsPromise = MongoServerAsPromise; function Meteor (options) { "use strict"; - var mongoServerPromise = null; - var nodePath = tools.getNodePath(options.pathToApp); - var instance = null; - var lastError = ""; - var lastErrorAt = "nowhere"; - var meteorPromise = null; - var meteorHasCrashed = false; - var meteorSafetyTimeout = null; - var ddpClientAsPromise = makeDDPClientFactory(serverControllerProvider); - var meteorRestartTimeout = 100; - var restartRequired = false; + options = options || {}; if (typeof options === 'string') { options = { pathToApp: options }; } - //\/\/\/\/\/\/\/\/\/\/\/\ DEFAULT OPTIONS \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/ - options = options || {}; - options.pathToApp = options.pathToApp || defaults.pathToApp || path.resolve('.'); - options.dbName = options.dbName || 'gagarin_' + (new Date()).getTime(); - options.port = options.port || 4000 + Math.floor(Math.random() * 1000); - options.location = 'http://localhost:' + options.port; - //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ + var pathToApp = options.pathToApp || path.resolve('.'); - var remote = new Remote(ddpClientAsPromise, serverControllerProvider); + var mongoServerPromise = new MongoServerAsPromise({ pathToApp: pathToApp }); + var ddpClientAsPromise = makeDDPClientFactory(serverControllerProvider); - mongoServerPromise = new MongoServerAsPromise(options); + var instance = null; + var lastError = ""; + var lastErrorAt = "nowhere"; + var meteorPromise = null; + + var meteorHasCrashed = false; + var meteorSafetyTimeout = null; + + var meteorRestartDelay = 100; + var restartRequired = false; + + var meteorPort = options.port || 4000 + Math.floor(Math.random() * 1000); + var meteorLocation = 'http://localhost:' + meteorPort; + var meteorDatabase = options.dbName || 'gagarin_' + (new Date()).getTime(); - this.location = options.location; + var remote = new Remote(ddpClientAsPromise, serverControllerProvider); + + this.location = meteorLocation; this.helpers = options.helpers || {}; this.useClosure = function (objectOrGetter) { @@ -67,7 +68,6 @@ function Meteor (options) { instance.kill(callback); instance = null; clearTimeout(meteorSafetyTimeout); - meteorSafetyTimeout = null; } else { setTimeout(callback); } @@ -99,12 +99,10 @@ function Meteor (options) { return reject(err); } clearTimeout(meteorSafetyTimeout); - meteorSafetyTimeout = null; resolve({ - meteorPort : options.port, + meteorPort : meteorPort, uniqueToken : Math.random(), restart : function (cb) { - console.log('restarting'); restartRequired = true; return serverControllerProvider(true).then(function () { cb(); @@ -117,7 +115,7 @@ function Meteor (options) { if (err) { return cb(err); } - mongoServerPromise.connectToDB(options.dbName).then(function (db) { + mongoServerPromise.connectToDB(meteorDatabase).then(function (db) { db.dropDatabase(cb); }, function (err) { cb(err); @@ -136,40 +134,39 @@ function Meteor (options) { }, }; - cleanUpThen(function respawn() { + cleanUpThen(function () { // respawn - setTimeout(function () { + meteorHasCrashed = false - meteorHasCrashed = false + Promise.all([ - Promise.all([ + BuildAsPromise(pathToApp), mongoServerPromise - BuildAsPromise(options.pathToApp), mongoServerPromise + ]).then(function (all) { - ]).then(function (all) { + var pathToMain = all[0]; + var env = Object.create(process.env); - var pathToMain = all[0]; - var env = Object.create(process.env); + env.ROOT_URL = meteorLocation; + env.PORT = meteorPort; + env.MONGO_URL = all[1] + '/' + meteorDatabase; + env.GAGARIN_SETTINGS = "{}"; - env.ROOT_URL = options.location; - env.PORT = options.port; - env.MONGO_URL = all[1] + '/' + options.dbName; - env.GAGARIN_SETTINGS = "{}"; - instance = new Instance(nodePath, pathToMain, env, instanceOptions); + setTimeout(function () { + instance = new Instance(tools.getNodePath(pathToApp), pathToMain, env, instanceOptions); + }, meteorRestartDelay); - meteorSafetyTimeout = setTimeout(function () { - cleanUpThen(function () { - reject(new Error('Gagarin is not there.' + - ' Please make sure you have added it with: meteor add anti:gagarin.')); - }); - }, options.safetyTimeout || 10000); - - }, function (err) { - reject(err); - }); + meteorSafetyTimeout = setTimeout(function () { + cleanUpThen(function () { + reject(new Error('Gagarin is not there.' + + ' Please make sure you have added it with: meteor add anti:gagarin.')); + }); + }, options.safetyTimeout || 10000); - }, meteorRestartTimeout); + }, function (err) { + reject(err); + }); }); From e4962b1bc966331ea31fc67d52d912c758252667 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 18:33:26 +0100 Subject: [PATCH 14/64] Refactoring onSpawn => onStart, exit => stop --- lib/ddp.js | 14 ++++++++------ lib/instance.js | 4 ++-- lib/interface.js | 2 +- lib/meteor.js | 22 +++++++++++++++++----- lib/meteorPromiseChain.js | 2 +- lib/remote.js | 2 +- tests/specs/benchmark.js | 2 +- 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/ddp.js b/lib/ddp.js index 13ae89b..106a253 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -3,23 +3,25 @@ var DDPClient = require('ddp'); var chalk = require('chalk'); var Promise = require('es6-promise').Promise; -module.exports = function makeDDPClientFactory (requestGagarinConfig) { +module.exports = function makeDDPClientFactory (ddpSetupProvider) { "use strict"; var self = this; var ddpClient = null; var ddpClientPromise = null; - var uniqueToken = null; + var code = null; + var port = null; return function ddpClientAsPromise () { - return requestGagarinConfig().then(function (config) { + return ddpSetupProvider().then(function (setup) { - if (uniqueToken === config.uniqueToken && ddpClientPromise) { + if (code === setup.code && port === setup.port && ddpClientPromise) { return ddpClientPromise; } - uniqueToken = config.uniqueToken; + code = setup.code; + port = setup.port; ddpClientPromise = new Promise(function (resolve, reject) { // XXX note that we do not reject explicitly @@ -32,7 +34,7 @@ module.exports = function makeDDPClientFactory (requestGagarinConfig) { ddpClient = new DDPClient({ // All properties optional, defaults shown host : "localhost", - port : config.meteorPort, + port : port, path : "websocket", ssl : false, autoReconnect : true, diff --git a/lib/instance.js b/lib/instance.js index b71e03d..e895c97 100644 --- a/lib/instance.js +++ b/lib/instance.js @@ -10,13 +10,13 @@ module.exports = function Instance(node, main, env, options) { try { meteor = spawn(node, [ main ], { env: env }); } catch (err) { - return options.onSpawn && options.onSpawn(err); + return options.onStart && options.onStart(err); } meteor.stdout.on('data', function (data) { var match = /Поехали!/.exec(data.toString()); if (match) { - options.onSpawn && options.onSpawn(); + options.onStart && options.onStart(); } }); diff --git a/lib/interface.js b/lib/interface.js index 46180b9..6871836 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -58,7 +58,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { }); after(function () { - return meteor.exit(); + return meteor.stop(); }); return meteor; diff --git a/lib/meteor.js b/lib/meteor.js index f6b5ee1..93151d6 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -35,7 +35,7 @@ function Meteor (options) { var pathToApp = options.pathToApp || path.resolve('.'); var mongoServerPromise = new MongoServerAsPromise({ pathToApp: pathToApp }); - var ddpClientAsPromise = makeDDPClientFactory(serverControllerProvider); + var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider); var instance = null; var lastError = ""; @@ -44,6 +44,7 @@ function Meteor (options) { var meteorHasCrashed = false; var meteorSafetyTimeout = null; + var meteorUniqueCode = null; var meteorRestartDelay = 100; var restartRequired = false; @@ -73,6 +74,15 @@ function Meteor (options) { } } + function ddpSetupProvider() { + return serverControllerProvider().then(function () { + return { + port: meteorPort, + code: meteorUniqueCode, + }; + }); + } + function serverControllerProvider () { // and callback var callback = arguments[arguments.length-1]; @@ -94,14 +104,16 @@ function Meteor (options) { meteorPromise = new Promise(function (resolve, reject) { var instanceOptions = { - onSpawn: function (err) { + onStart: function (err) { + clearTimeout(meteorSafetyTimeout); + if (err) { return reject(err); } - clearTimeout(meteorSafetyTimeout); + + meteorUniqueCode = Math.random(); + resolve({ - meteorPort : meteorPort, - uniqueToken : Math.random(), restart : function (cb) { restartRequired = true; return serverControllerProvider(true).then(function () { diff --git a/lib/meteorPromiseChain.js b/lib/meteorPromiseChain.js index 276b776..ad4317e 100644 --- a/lib/meteorPromiseChain.js +++ b/lib/meteorPromiseChain.js @@ -40,7 +40,7 @@ MeteorPromiseChain.methods = [ 'execute', 'promise', 'wait', - 'exit', + 'stop', 'start', 'restart', ]; diff --git a/lib/remote.js b/lib/remote.js index 313ae63..da13780 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -114,7 +114,7 @@ function Remote(ddpClientProvider, serverControllerProvider) { }); }; - self.exit = function (cb) { + self.stop = function (cb) { return serverControllerProvider().then(function (controller) { controller.stop(cb); }, function (err) { diff --git a/tests/specs/benchmark.js b/tests/specs/benchmark.js index 2df43d9..27ce3cf 100644 --- a/tests/specs/benchmark.js +++ b/tests/specs/benchmark.js @@ -20,7 +20,7 @@ describe('Benchmark test suite', function () { }); after(function () { - return meteor.exit(); + return meteor.stop(); }); // this testdoes not make sense after all From ddd6acf5291368d5ae2bce3d851fa55e55ed1527 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 18:47:34 +0100 Subject: [PATCH 15/64] Moved safetyTimeout to the instance implementation --- lib/instance.js | 33 +++++++++++++++++++++++++++------ lib/meteor.js | 15 +++------------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/instance.js b/lib/instance.js index e895c97..f7f4dc3 100644 --- a/lib/instance.js +++ b/lib/instance.js @@ -1,10 +1,12 @@ var spawn = require('child_process').spawn; -module.exports = function Instance(node, main, env, options) { +module.exports = function Instance (node, main, env, options) { - var meteor = null; - var lastError = ""; - var lastErrorAt = "nowhere"; + var meteor = null; + var lastError = ""; + var lastErrorAt = "nowhere"; + var safetyTimeout = options.safetyTimeout || 5 * 1000; + var safetyHandle = null; setTimeout(function () { try { @@ -13,9 +15,24 @@ module.exports = function Instance(node, main, env, options) { return options.onStart && options.onStart(err); } + safetyHandle = setTimeout(function () { + kill(function (err) { + if (options.onStart) { + if (err) { + options.onStart(err); + } else { + options.onStart(new Error('Gagarin is not there.' + + ' Please make sure you have added it with: meteor add anti:gagarin.')); + } + } + }); + }, safetyTimeout); + meteor.stdout.on('data', function (data) { var match = /Поехали!/.exec(data.toString()); if (match) { + clearTimeout(safetyHandle); + //----------------------------------- options.onStart && options.onStart(); } }); @@ -55,6 +72,8 @@ module.exports = function Instance(node, main, env, options) { }); meteor.on('exit', function (code) { + clearTimeout(safetyHandle); + //------------------------- if (options.onExit) { options.onExit(code, lastError, lastErrorAt); } @@ -63,12 +82,14 @@ module.exports = function Instance(node, main, env, options) { }); - this.kill = function (cb) { + this.kill = kill; + + function kill (cb) { if (!meteor) { return cb(); } meteor.once('exit', function (code) { - if (code === 0 || code === 130) { + if (!code || code === 0 || code === 130) { cb(); } else { cb(new Error('exited with code ' + code)); diff --git a/lib/meteor.js b/lib/meteor.js index 93151d6..fad1c64 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -43,7 +43,6 @@ function Meteor (options) { var meteorPromise = null; var meteorHasCrashed = false; - var meteorSafetyTimeout = null; var meteorUniqueCode = null; var meteorRestartDelay = 100; @@ -68,7 +67,6 @@ function Meteor (options) { if (instance) { instance.kill(callback); instance = null; - clearTimeout(meteorSafetyTimeout); } else { setTimeout(callback); } @@ -105,8 +103,7 @@ function Meteor (options) { var instanceOptions = { onStart: function (err) { - clearTimeout(meteorSafetyTimeout); - + if (err) { return reject(err); } @@ -114,9 +111,10 @@ function Meteor (options) { meteorUniqueCode = Math.random(); resolve({ + start : function (cb) { cb() }, restart : function (cb) { restartRequired = true; - return serverControllerProvider(true).then(function () { + return serverControllerProvider().then(function () { cb(); }, function (err) { cb(err); @@ -169,13 +167,6 @@ function Meteor (options) { instance = new Instance(tools.getNodePath(pathToApp), pathToMain, env, instanceOptions); }, meteorRestartDelay); - meteorSafetyTimeout = setTimeout(function () { - cleanUpThen(function () { - reject(new Error('Gagarin is not there.' + - ' Please make sure you have added it with: meteor add anti:gagarin.')); - }); - }, options.safetyTimeout || 10000); - }, function (err) { reject(err); }); From 94e6f22f06b8740c2ce3c02431bf533e3c3a9bc1 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 19:27:05 +0100 Subject: [PATCH 16/64] Even more cleanup --- lib/meteor.js | 97 +++++++++++++++++++++++---------------------------- lib/remote.js | 4 +-- 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/lib/meteor.js b/lib/meteor.js index fad1c64..d845abd 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -38,8 +38,6 @@ function Meteor (options) { var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider); var instance = null; - var lastError = ""; - var lastErrorAt = "nowhere"; var meteorPromise = null; var meteorHasCrashed = false; @@ -54,6 +52,30 @@ function Meteor (options) { var remote = new Remote(ddpClientAsPromise, serverControllerProvider); + var controller = { + start : function (cb) { cb() }, + restart : function (cb) { + (restartRequired = true) && serverControllerProvider() + .then(function () { + cb(); + }, function (err) { + cb(err); + }); + }, + stop : function (cb) { + cleanUpThen(function (err) { + if (err) { + return cb(err); + } + mongoServerPromise.connectToDB(meteorDatabase).then(function (db) { + db.dropDatabase(cb); + }, function (err) { + cb(err); + }); + }); + }, + }; + this.location = meteorLocation; this.helpers = options.helpers || {}; @@ -64,11 +86,13 @@ function Meteor (options) { this.meteorRemoteAsPromise = Promise.resolve(remote); function cleanUpThen(callback) { + meteorPromise = null; + //------------------- if (instance) { instance.kill(callback); instance = null; } else { - setTimeout(callback); + callback(); } } @@ -81,19 +105,12 @@ function Meteor (options) { }); } - function serverControllerProvider () { // and callback - - var callback = arguments[arguments.length-1]; + function serverControllerProvider () { - if (meteorHasCrashed && !restartRequired) { - restartRequired = true; - if (lastError) { - return Promise.reject(new Error(chalk.red(lastError) + chalk.blue(' => ') + chalk.blue(lastErrorAt))); + if (!restartRequired && !!meteorPromise) { + if (meteorHasCrashed) { + restartRequired = true; } - return Promise.reject(new Error(chalk.red('Meteor server has crashed due to some unknown reason.'))); - } - - if (!meteorHasCrashed && !restartRequired && meteorPromise) { return meteorPromise; } @@ -103,44 +120,23 @@ function Meteor (options) { var instanceOptions = { onStart: function (err) { - if (err) { return reject(err); } - meteorUniqueCode = Math.random(); - - resolve({ - start : function (cb) { cb() }, - restart : function (cb) { - restartRequired = true; - return serverControllerProvider().then(function () { - cb(); - }, function (err) { - cb(err); - }); - }, - stop : function (cb) { - cleanUpThen(function (err) { - if (err) { - return cb(err); - } - mongoServerPromise.connectToDB(meteorDatabase).then(function (db) { - db.dropDatabase(cb); - }, function (err) { - cb(err); - }); - }); - }, - }); + //------------------------------- + resolve(controller); }, - onExit: function (code, _lastError, _lastErrorAt) { - meteorHasCrashed = code !== 0; - //---------------------------- - meteorPromise = null; - instance = null; - lastError = _lastError; - lastErrorAt = _lastErrorAt; + onExit: function (code, lastError, lastErrorAt) { + meteorHasCrashed = (code && code !== 0 && code !== 130); + //------------------------------------------------------ + if (meteorHasCrashed) { + if (lastError) { + meteorPromise = Promise.reject(new Error(chalk.red(lastError) + chalk.blue(' => ') + chalk.blue(lastErrorAt))); + } else { + meteorPromise = Promise.reject(new Error(chalk.red('Meteor server has crashed due to some unknown reason.'))); + } + } }, }; @@ -148,11 +144,7 @@ function Meteor (options) { meteorHasCrashed = false - Promise.all([ - - BuildAsPromise(pathToApp), mongoServerPromise - - ]).then(function (all) { + Promise.all([ BuildAsPromise(pathToApp), mongoServerPromise ]).then(function (all) { var pathToMain = all[0]; var env = Object.create(process.env); @@ -162,7 +154,6 @@ function Meteor (options) { env.MONGO_URL = all[1] + '/' + meteorDatabase; env.GAGARIN_SETTINGS = "{}"; - setTimeout(function () { instance = new Instance(tools.getNodePath(pathToApp), pathToMain, env, instanceOptions); }, meteorRestartDelay); diff --git a/lib/remote.js b/lib/remote.js index da13780..d48bd52 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -92,8 +92,8 @@ function Remote(ddpClientProvider, serverControllerProvider) { } self.start = function (cb) { - return serverControllerProvider().then(function (value) { - cb(null, value); + return serverControllerProvider().then(function (controller) { + controller.start(cb); }, function (err) { cb(err); }); From 82138b6cb921cd176a8f5a751b05b913111b6535 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 19:52:25 +0100 Subject: [PATCH 17/64] Cleanup and comments --- lib/closure.js | 13 +++++++++++ lib/ddp.js | 11 ++------- lib/helpers.js | 4 ++-- lib/instance.js | 20 ++++++++++++++++- lib/remote.js | 60 ++++++++++++++++++++++++------------------------- 5 files changed, 65 insertions(+), 43 deletions(-) diff --git a/lib/closure.js b/lib/closure.js index 8f78fd4..358db1e 100644 --- a/lib/closure.js +++ b/lib/closure.js @@ -1,6 +1,13 @@ module.exports = Closure; +/** + * Creates a new closure manager. + * + * @param {Object} parent + * @param {Array} listOfKeys (names) + * @param {Function} accessor + */ function Closure (parent, listOfKeys, accessor) { "use strict"; @@ -34,7 +41,13 @@ function Closure (parent, listOfKeys, accessor) { } +/** + * Adds closure updater functionality to the given object. + * + * @param {Object} object + */ Closure.mixin = function (object) { + "use strict"; var accessor = function () { return arguments.length === 0 ? {} : undefined }; diff --git a/lib/ddp.js b/lib/ddp.js index 106a253..c74d06f 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -1,6 +1,4 @@ var DDPClient = require('ddp'); - -var chalk = require('chalk'); var Promise = require('es6-promise').Promise; module.exports = function makeDDPClientFactory (ddpSetupProvider) { @@ -24,15 +22,10 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) { port = setup.port; ddpClientPromise = new Promise(function (resolve, reject) { - // XXX note that we do not reject explicitly - - // TODO: meteroPort may change? - // allow different things than localhost - + ddpClient && ddpClient.close(); ddpClient = new DDPClient({ - // All properties optional, defaults shown host : "localhost", port : port, path : "websocket", @@ -40,7 +33,7 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) { autoReconnect : true, autoReconnectTimer : 500, maintainCollections : true, - ddpVersion : '1' // ['1', 'pre2', 'pre1'] available + ddpVersion : '1' }); ddpClient.connect(function (err, wasReconnected) { diff --git a/lib/helpers.js b/lib/helpers.js index 44592ef..9bd6262 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -100,11 +100,11 @@ exports.client = { }, // we should probably call this helper "typeKeys" - //sendKeys: function (selector, keys) { + //typeKeys: function (selector, keys) { // do we need focus and blur? // return this.waitForDOM(selector) // .focus(selector) - // .findElementAndSendKeys(selector, keys) + // .sendKeys(selector, keys) // .blur(selector); //}, diff --git a/lib/instance.js b/lib/instance.js index f7f4dc3..fd11f84 100644 --- a/lib/instance.js +++ b/lib/instance.js @@ -1,6 +1,20 @@ var spawn = require('child_process').spawn; +/** + * Creates a smart meteor instance, suitable for usage with the testing framework. + * + * @param {String} node - path to node executable + * @param {String} main - path to applciation main + * @param {Object} env - environment variables for the new process + * @param {Object} options + * + * Available options are: + * - onStart {Function} + * - onExit {Function} + * - safetyTimeout {Number} + */ module.exports = function Instance (node, main, env, options) { + "use strict"; var meteor = null; var lastError = ""; @@ -83,7 +97,11 @@ module.exports = function Instance (node, main, env, options) { }); this.kill = kill; - + /** + * Kill the meteor process and cleanup. + * + * @param {Function} cb + */ function kill (cb) { if (!meteor) { return cb(); diff --git a/lib/remote.js b/lib/remote.js index d48bd52..31d4379 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -1,42 +1,22 @@ +var Closure = require('./closure'); module.exports = Remote; +/** + * Creates a meteor remote driver, which can be used to send + * commands to meteor server. + * + * @param {Function} ddpClientProvider, should return a promise + * @apram {Function} serverControllerProvider, should return a promise + */ function Remote(ddpClientProvider, serverControllerProvider) { "use strict"; - var closure = function () { return arguments.length === 0 ? {} : undefined }; var self = this; + var closure; - function callDDPMethod (ddpClient, name, args, cb) { - if (!ddpClient) { - return cb(new Error('invalid ddpClient')); - } - ddpClient.call(name, args, function (err, feedback) { - if (feedback && feedback.closure) { - closure(feedback.closure); - } - if (err) { - return cb(err); - } - if (feedback.error) { - return cb(new Error(feedback.error)); - } - cb(null, feedback.result); - }); - } - - this.useClosure = function (objectOrGetter) { - if (typeof objectOrGetter !== 'function' && typeof objectOrGetter !== 'object') { - throw new Error('closure must be either function or object'); - } - closure = function (values) { - var closure = (typeof objectOrGetter === 'function') ? objectOrGetter() : objectOrGetter; - if (arguments.length === 0) { - return closure ? closure.getValues() : {}; - } - closure && closure.setValues(values); - } - }; + Closure.mixin(self); + closure = self.closure.bind(self); this.promise = function (code, args) { var cb = arguments[arguments.length-1]; @@ -122,6 +102,24 @@ function Remote(ddpClientProvider, serverControllerProvider) { }); }; + function callDDPMethod (ddpClient, name, args, cb) { + if (!ddpClient) { + return cb(new Error('invalid ddpClient')); + } + ddpClient.call(name, args, function (err, feedback) { + if (feedback && feedback.closure) { + closure(feedback.closure); + } + if (err) { + return cb(err); + } + if (feedback.error) { + return cb(new Error(feedback.error)); + } + cb(null, feedback.result); + }); + } + }; From 2e97d2f97f070e0b4c6f70cd54a54919ee7f7640 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 12 Dec 2014 19:57:40 +0100 Subject: [PATCH 18/64] Bumped package version [ci skip] --- package.js | 4 +++- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index ca3d49a..9a6d94d 100644 --- a/package.js +++ b/package.js @@ -2,12 +2,14 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre5", + version: "0.3.0-pre6", git: "https://github.com/anticoders/gagarin.git", }); Package.onUse(function (api) { + api.versionsFrom('METEOR@1.0'); + api.use('livedata', 'server'); api.addFiles([ diff --git a/package.json b/package.json index 6fea22a..a8c7c16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre5", + "version": "0.3.0-pre6", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index 057cf7c..4c96372 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,4 +1,4 @@ -anti:gagarin@0.3.0-pre5 +anti:gagarin@0.3.0-pre6 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 From 8833595092211bed0695e9c84409d6ed43598028 Mon Sep 17 00:00:00 2001 From: apendua Date: Fri, 12 Dec 2014 21:53:39 +0100 Subject: [PATCH 19/64] Make sure gagarin can be also used as node module --- index.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..c69d96c --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/gagarin'); From 141bf935a37f3da395edaaf803832e89dbd7b910 Mon Sep 17 00:00:00 2001 From: apendua Date: Fri, 12 Dec 2014 21:57:30 +0100 Subject: [PATCH 20/64] Bumped package version --- package.js | 2 +- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index 9a6d94d..eb1e20d 100644 --- a/package.js +++ b/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre6", + version: "0.3.0-pre7", git: "https://github.com/anticoders/gagarin.git", }); diff --git a/package.json b/package.json index a8c7c16..96b2f73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre6", + "version": "0.3.0-pre7", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index 4c96372..74728f6 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,4 +1,4 @@ -anti:gagarin@0.3.0-pre6 +anti:gagarin@0.3.0-pre7 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 From 261841bd4767e27ee10d2da52df04c0767bcbe28 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 10:11:21 +0100 Subject: [PATCH 21/64] We no longer support travis --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2e0f8ab..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: node_js -node_js: - - "0.10" -before_script: - - curl https://install.meteor.com | /bin/sh - - npm install - - npm install -g meteorite -# - chromedriver --port=9515: -# background: true -script: - - ./test.js From 1c20447dacb493265f6f5d5bd2f11b8556684739 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:07:49 +0100 Subject: [PATCH 22/64] Improve client/server promises --- backdoor.js | 17 ++++++++++------- lib/browserPromiseChain.js | 12 ++++++++---- lib/instance.js | 1 + lib/remote.js | 2 +- tests/specs/closures.js | 24 ++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/backdoor.js b/backdoor.js index 12a9da5..465741a 100644 --- a/backdoor.js +++ b/backdoor.js @@ -61,8 +61,8 @@ if (process.env.GAGARIN_SETTINGS) { args = args.map(stringify); - args.unshift("(function (cb) { return function (err) { cb({ error : err, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); - args.unshift("(function (cb) { return function (res) { cb({ result : res, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); + args.unshift("(function (cb) {\n return function ($) {\n setTimeout(function () { cb({ error : $, closure: {" + keys + "}}); });\n };\n })(arguments[arguments.length-1])"); + args.unshift("(function (cb) {\n return function ($) {\n setTimeout(function () { cb({ value : $, closure: {" + keys + "}}); });\n };\n })(arguments[arguments.length-1])"); chunks.push( "function (" + Object.keys(closure).join(', ') + ") {", @@ -79,10 +79,13 @@ if (process.env.GAGARIN_SETTINGS) { ); chunks.push( - " (" + code + ")(" + args.join(', ') + ");", + " (" + code + ")(", + " " + args.join(', ') + ");", "}" ); + //console.log(chunks.join('\n')); + try { vm.runInContext("value = " + chunks.join('\n'), context); } catch (err) { @@ -153,7 +156,7 @@ if (process.env.GAGARIN_SETTINGS) { var feedback; try { feedback = context.value.apply(null, values(closure)); - if (feedback.result) { + if (feedback.value) { resolve(feedback); } @@ -172,7 +175,7 @@ if (process.env.GAGARIN_SETTINGS) { resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); }, timeout); } else { - resolve({ err: 'code has to be a function' }) + resolve({ error: 'code has to be a function' }) } return future.wait(); @@ -205,7 +208,7 @@ function wrapSourceCode(code, args, closure) { ); chunks.push( - " return (function (result) {", + " return (function ($) {", " return {", " closure: {" ); @@ -216,7 +219,7 @@ function wrapSourceCode(code, args, closure) { chunks.push( " },", - " result: result,", + " value: $,", " };", " })( (" + code + ")(" + args.map(stringify).join(',') + ") );", "}" diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index 88536ed..cb3f0e7 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -243,10 +243,11 @@ BrowserPromiseChain.prototype.promise = function (code, args) { // stringify arguments args = args.map(stringify); - args.unshift("(function (cb) { return function (err) { cb({ error : (err && typeof err === 'object') ? err.message : err.toString()," + - " closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); + args.unshift("(function (cb) {\n return function ($) {\n setTimeout( function () { cb({ error : ($ && typeof $ === 'object') ? $.message : $.toString()," + + " closure: {" + keys + "}}); });\n };\n })(arguments[arguments.length-1])"); - args.unshift("(function (cb) { return function (res) { cb({ value : res, closure: {" + keys + "}}) } })(arguments[arguments.length-1])"); + args.unshift("(function (cb) {\n return function ($) {\n setTimeout( function () { cb({ value : $, closure: {" + + keys + "}}); });\n };\n })(arguments[arguments.length-1])"); chunks.push( "function (" + Object.keys(closure).join(', ') + ") {", @@ -262,10 +263,13 @@ BrowserPromiseChain.prototype.promise = function (code, args) { " };" ); chunks.push( - " (" + code + ")(" + args.join(', ') + ");", + " (" + code + ")(", + " " + args.join(', ') + ");", "}" ); + //console.log(chunks.join('\n')) + code = chunks.join('\n'); // TODO: how come "args" instead of values(closure) was passing as well? diff --git a/lib/instance.js b/lib/instance.js index fd11f84..ad6dbb3 100644 --- a/lib/instance.js +++ b/lib/instance.js @@ -43,6 +43,7 @@ module.exports = function Instance (node, main, env, options) { }, safetyTimeout); meteor.stdout.on('data', function (data) { + //process.stdout.write(data); var match = /Поехали!/.exec(data.toString()); if (match) { clearTimeout(safetyHandle); diff --git a/lib/remote.js b/lib/remote.js index 31d4379..f04543c 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -116,7 +116,7 @@ function Remote(ddpClientProvider, serverControllerProvider) { if (feedback.error) { return cb(new Error(feedback.error)); } - cb(null, feedback.result); + cb(null, feedback.value); }); } diff --git a/tests/specs/closures.js b/tests/specs/closures.js index 1fb6805..db153f4 100644 --- a/tests/specs/closures.js +++ b/tests/specs/closures.js @@ -108,6 +108,18 @@ describe('Closures', function () { }); }); + it('should be able to alter a closure variable right after calling "resolve"', function () { + return server.promise(function (resolve) { + setTimeout(function () { + var value = Math.random(); + resolve(value); + b = value; + }, 100); + }).then(function (value) { + expect(value).to.equal(b); + }); + }); + it.skip('should be able to use sync with promises', function () { var handle = setInterval(function () { b -= 1 }, 10); return server.promise(function (resolve, reject) { @@ -305,6 +317,18 @@ describe('Closures', function () { }); }); + it('should be able to alter a closure variable right after calling "resolve"', function () { + return client.promise(function (resolve) { + setTimeout(function () { + var value = Math.random(); + resolve(value); + b = value; + }, 100); + }).then(function (value) { + expect(value).to.equal(b); + }); + }); + it.skip('should be able to use sync with promises', function () { var handle = setInterval(function () { b -= 1 }, 10); return client.promise(function (resolve, reject) { From 6baba948611e635d2eff23c8ad962fb06c2f45d2 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:23:51 +0100 Subject: [PATCH 23/64] Refactor call/appy => call/applyWebDriver --- lib/browserPromiseChain.js | 22 +++++++++++----------- lib/helpers.js | 4 ++-- tests/specs/fibers.js | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index cb3f0e7..7f74850 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -70,14 +70,14 @@ webdriverMethods.forEach(function (name) { "use strict"; BrowserPromiseChain.prototype[name] = function () { - return this.apply(name, Array.prototype.slice.call(arguments, 0)); + return this.applyWebDriver(name, Array.prototype.slice.call(arguments, 0)); }; }); BrowserPromiseChain.methods = webdriverMethods.concat([ - 'apply', - 'call', + 'applyWebDriver', + 'callWebDriver', 'always', 'sleep', 'expectError', @@ -89,7 +89,7 @@ BrowserPromiseChain.methods = webdriverMethods.concat([ /** * Update the current promise and return this to allow chaining. */ -BrowserPromiseChain.prototype.apply = function (name, args, customApply) { +BrowserPromiseChain.prototype.applyWebDriver = function (name, args, custom) { "use strict"; args = Array.prototype.slice.call(args, 0); // shallow copy the arguments @@ -106,8 +106,8 @@ BrowserPromiseChain.prototype.apply = function (name, args, customApply) { reject(new Error('operand.browser does not implement method: ' + name)); } else { args.push(either(reject).or(resolve)); - if (typeof customApply === 'function') { - return customApply(operand, name, args); + if (typeof custom === 'function') { + return custom(operand, name, args); } return operand.browser[name].apply(operand.browser, args); } @@ -116,10 +116,10 @@ BrowserPromiseChain.prototype.apply = function (name, args, customApply) { return self; } -BrowserPromiseChain.prototype.call = function (name) { +BrowserPromiseChain.prototype.callWebDriver = function (name) { "use strict"; - return this.apply(name, Array.prototype.slice.call(arguments, 1)); + return this.applyWebDriver(name, Array.prototype.slice.call(arguments, 1)); } BrowserPromiseChain.prototype.always = function (callback) { @@ -186,7 +186,7 @@ BrowserPromiseChain.prototype.execute = function (code, args) { code = codeToString(code); - return self.apply('execute', arguments, function (operand, name, myArgs) { + return self.applyWebDriver('execute', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; @@ -230,7 +230,7 @@ BrowserPromiseChain.prototype.promise = function (code, args) { code = codeToString(code); // we could set this 5000 globally, right? - return self.setAsyncScriptTimeout(self._timeout || 5000).apply('executeAsync', arguments, function (operand, name, myArgs) { + return self.setAsyncScriptTimeout(self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; // the last argument is callback @@ -306,7 +306,7 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { code = codeToString(code); - return self.setAsyncScriptTimeout(2 * timeout).apply('executeAsync', arguments, function (operand, name, myArgs) { + return self.setAsyncScriptTimeout(2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; // callback is always the last one diff --git a/lib/helpers.js b/lib/helpers.js index 9bd6262..bae0c19 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -88,7 +88,7 @@ exports.client = { }, click: function (selector, keys) { - return this.call('elementByCssSelectorOrNull', selector) + return this.callWebDriver('elementByCssSelectorOrNull', selector) .then(function (element) { if (!element) { throw new Error('element ' + selector + ' does not exists'); @@ -109,7 +109,7 @@ exports.client = { //}, sendKeys: function (selector, keys) { - return this.call('elementByCssSelectorOrNull', selector) + return this.callWebDriver('elementByCssSelectorOrNull', selector) .then(function (element) { if (!element) { throw new Error('element ' + selector + ' does not exists'); diff --git a/tests/specs/fibers.js b/tests/specs/fibers.js index bf46690..8ebb033 100644 --- a/tests/specs/fibers.js +++ b/tests/specs/fibers.js @@ -1,4 +1,4 @@ -describe('Fibers.', function () { +describe('Fibers', function () { var server = meteor(); From 67e00b9f1640ffa58f027a2fab6d088f0e765231 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:25:43 +0100 Subject: [PATCH 24/64] Lets not expose wait for now --- lib/interface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interface.js b/lib/interface.js index 6871836..b4138e8 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -121,7 +121,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { }); }; - context.wait = wait; + //context.wait = wait; }); } From c262ac78c100e9d117eca2119921feec6e075dde Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:30:05 +0100 Subject: [PATCH 25/64] Update the benchmark test suite --- lib/interface.js | 4 ++-- tests/specs/benchmark.js | 41 +++++----------------------------------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/lib/interface.js b/lib/interface.js index b4138e8..310f174 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -52,7 +52,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { before(function () { return meteor.start().then(function () { if (typeof initialize === 'function') { - initialize.call(meteor); + return meteor.execute(initialize); } }); }); @@ -98,7 +98,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { before(function () { return browser.init().then(function () { if (typeof initialize === 'function') { - initialize.call(browser); + return browser.execute(initialize); } }); }); diff --git a/tests/specs/benchmark.js b/tests/specs/benchmark.js index 27ce3cf..9fc541f 100644 --- a/tests/specs/benchmark.js +++ b/tests/specs/benchmark.js @@ -1,41 +1,10 @@ -var Promise = require('es6-promise').Promise; -var Meteor = require('../../lib/meteor'); -var tools = require('../../lib/tools'); -var path = require('path'); -var expect = require('chai').expect; -var buildAsPromise = require('../../lib/build'); describe('Benchmark test suite', function () { - var pathToApp = path.resolve('./tests/example'); - - var meteor = new Meteor({ - pathToApp: path.resolve('./tests/example') - }); - - before(function () { - // the sleep is not required but - // lets demonstrate that it works :) - return meteor.start().sleep(500); - }); - - after(function () { - return meteor.stop(); - }); - - // this testdoes not make sense after all - it.skip('should be able to find the release config', function () { - var config = tools.getReleaseConfig(pathToApp); - expect(config.tools).to.be.ok; - expect(config.tools).not.to.be.equal('latest'); - }); - - it('should be able to build app', function () { - return buildAsPromise(pathToApp); - }); + var server = meteor(); it('execute should work', function () { - return meteor.execute(function () { + return server.execute(function () { return Meteor.release; }) .then(function (value) { @@ -44,7 +13,7 @@ describe('Benchmark test suite', function () { }); it('db insert should work', function () { - return meteor.execute(function () { + return server.execute(function () { return Items.insert({vostok: Random.id()}); }) .then(function (value) { @@ -53,7 +22,7 @@ describe('Benchmark test suite', function () { }); it('promise should work', function () { - return meteor.promise(function (resolve, reject) { + return server.promise(function (resolve, reject) { Meteor.setTimeout(function () { resolve(Meteor.release); }, 100); @@ -64,7 +33,7 @@ describe('Benchmark test suite', function () { }); it('should throw a descriptive error', function () { - return meteor.execute(function () { + return server.execute(function () { undefined[0]; }).expectError(function (err) { expect(err.toString()).to.contain('property'); From 120e786631ebbd3a820c5c5377a7b094793c5231 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:40:29 +0100 Subject: [PATCH 26/64] Fixes #21 --- lib/interface.js | 4 ++-- tests/specs/initialize.js | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/specs/initialize.js diff --git a/lib/interface.js b/lib/interface.js index 310f174..d9d45b5 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -52,7 +52,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { before(function () { return meteor.start().then(function () { if (typeof initialize === 'function') { - return meteor.execute(initialize); + return initialize.length ? meteor.promise(initialize) : meteor.execute(initialize); } }); }); @@ -98,7 +98,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { before(function () { return browser.init().then(function () { if (typeof initialize === 'function') { - return browser.execute(initialize); + return initialize.length ? browser.promise(initialize) : browser.execute(initialize); } }); }); diff --git a/tests/specs/initialize.js b/tests/specs/initialize.js new file mode 100644 index 0000000..5f9e0a2 --- /dev/null +++ b/tests/specs/initialize.js @@ -0,0 +1,46 @@ + +describe('Initialization', function () { + + var a = 1, b = 1; + + closure(['a', 'b'], function (key, value) { + return eval(key + (arguments.length > 1 ? '=' + JSON.stringify(value) : '')); + }); + + var server = meteor(function () { + a = 2; + Items.insert({_id: 'server'}); + }); + + var client = browser(server.location, function (resolve, reject) { + b = 2; + Items.insert({_id: 'client'}, either(reject).or(resolve)); + }); + + it('initialization should work on server', function () { + return server.execute(function () { + return Items.findOne({_id: 'server'}); + }) + .then(function (value) { + expect(value).not.to.be.empty; + }); + }); + + it('should be able to use closure variables during server init script', function () { + expect(a).to.equal(2); + }); + + it('initialization should work on client', function () { + return server.execute(function () { + return Items.findOne({_id: 'client'}); + }) + .then(function (value) { + expect(value).not.to.be.empty; + }); + }); + + it('should be able to use closure variables during client init script', function () { + expect(b).to.equal(2); + }); + +}); From b53924a09dd3d004d6d13e8b14400ff30bfa4db3 Mon Sep 17 00:00:00 2001 From: apendua Date: Mon, 15 Dec 2014 11:58:06 +0100 Subject: [PATCH 27/64] Do not expose methods which we don't want to support --- find | 2 +- lib/browserPromiseChain.js | 12 ++++++------ tests/specs/browser.js | 12 ++++-------- tests/specs/methods.js | 2 -- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/find b/find index 0dac161..244e0ca 100755 --- a/find +++ b/find @@ -1,2 +1,2 @@ #!/bin/bash -grep -R $1 lib/*.js +grep -R $1 lib/*.js tests/specs/*.js diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index 7f74850..3fff9a3 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -52,8 +52,8 @@ var webdriverMethods = [ 'setWindowSize', 'forward', 'back', - 'waitForConditionInBrowser', - 'setAsyncScriptTimeout', + //'waitForConditionInBrowser', + //'setAsyncScriptTimeout', 'takeScreenshot', 'saveScreenshot', 'title', @@ -61,8 +61,8 @@ var webdriverMethods = [ 'setCookie', 'deleteAllCookies', 'deleteCookie', - 'getOrientation', - 'setOrientation', + //'getOrientation', + //'setOrientation', 'getLocation', ]; @@ -230,7 +230,7 @@ BrowserPromiseChain.prototype.promise = function (code, args) { code = codeToString(code); // we could set this 5000 globally, right? - return self.setAsyncScriptTimeout(self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { + return self.callWebDriver('setAsyncScriptTimeout', self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; // the last argument is callback @@ -306,7 +306,7 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { code = codeToString(code); - return self.setAsyncScriptTimeout(2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { + return self.callWebDriver('setAsyncScriptTimeout', 2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; // callback is always the last one diff --git a/tests/specs/browser.js b/tests/specs/browser.js index 160f788..0f932dc 100644 --- a/tests/specs/browser.js +++ b/tests/specs/browser.js @@ -34,7 +34,6 @@ describe('Tests with browser', function () { describe('Database insertions', function () { before(function () { return browser1 - .setAsyncScriptTimeout(1000) .promise(function (resolve, reject, id) { Items.insert({_id: id}, either(reject).or(resolve)); }, [ id ]) @@ -70,10 +69,6 @@ describe('Tests with browser', function () { var browser2 = browser(server.location); - before(function () { - return browser2.setAsyncScriptTimeout(10000); - }); - before(function () { return server.restart(2000); }); @@ -89,7 +84,7 @@ describe('Tests with browser', function () { it('should recognize that the server was restarted', function () { return browser2 - .waitForConditionInBrowser("reset > 0", 5000) + .wait(5000, 'until reset event is detected', "return reset > 0") .execute("return reset;") .then(function (numberOfResets) { expect(numberOfResets).to.equal(1); @@ -98,11 +93,12 @@ describe('Tests with browser', function () { it ('another restart shoud work as well', function () { return server.restart().then(function () { - return browser2.waitForConditionInBrowser("reset > 1", 5000) + return browser2 + .wait(5000, 'until reset event is detected', "return reset > 1") .execute("return reset;") .then(function (numberOfResets) { expect(numberOfResets).to.equal(2); - }); + }); }); }); diff --git a/tests/specs/methods.js b/tests/specs/methods.js index 9a6704d..e153eb7 100644 --- a/tests/specs/methods.js +++ b/tests/specs/methods.js @@ -88,7 +88,6 @@ describe('Gagarin methods', function () { it('should be able to call promise on the client', function () { return client - .setAsyncScriptTimeout(500) .promise(function (resolve) { setTimeout(resolve, 100); }); @@ -96,7 +95,6 @@ describe('Gagarin methods', function () { it('should be able to pass arguments to client promise', function () { return client - .setAsyncScriptTimeout(500) .promise(function (resolve, reject, a, b) { setTimeout(function () { resolve(a * b); }, 100); }, [ 3, 5 ]) From 26502686966299efb7249bd5e6be0563ede2fba8 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Mon, 15 Dec 2014 13:31:00 +0100 Subject: [PATCH 28/64] Added a few examples --- README.md | 151 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index fb6a94d..8a422de 100644 --- a/README.md +++ b/README.md @@ -6,47 +6,45 @@ For more information on the official testing framework for Meteor, see [Velocity ## How is it different from Velocity? -Gagarin is totally external to meteor. It only takes care of spawning your meteor processes and allows you to execute source code chunks in your app environment from within your test suite. That's it. On the other hand, Velocity will deeply integrate with your app by making your test cases an integral part of your app source code, but only in a special type of builds called mirrors. This is very clever because your tests will run as fast as it can be. The only drawback of using velocity is that you don't have a great control over your meteor processes. In most situations this is acceptable but there are some very specific scenarios when this is not sufficient. In those cases Gagarin is probably a good choice. Gagarin tests will run a little bit slower because the source code is send to your app through a socket, but in most situations in which you would need Gagarin this is acceptable because the bottleneck of your test speed is usually somewhere else. +Gagarin is practically external to meteor. It only takes care of spawning your meteor processes and allows you to execute source code chunks in your app environment from within your test suite and that's it. On the other hand, Velocity will deeply integrate with your app by making your test cases an integral part of your app source code, but only in a special type of builds called mirrors. This is very clever because your tests will run as fast as it can possibly be. The only drawback of using velocity is that you don't have a satisfactory control over your meteor processes. In most situations this is acceptable but there are some very specific scenarios when this is not sufficient. In those cases Gagarin is probably a good choice. Gagarin tests will run a little bit slower because the source code is send to your app through DDP, but in most situations in which you would need Gagarin this is acceptable because the bottleneck of your test speed is usually somewhere else. Another advantage of using DDP is that the tests can be potentially executed on a deployed application which may be useful in some specific cases, though this feature is not fully implemented yet. ## How is it different from Laika? -In needs to be said that Gagarin originates from Laika. You can think of it as Laika 2.0. The main advantages of using Gagarin rather then Laika are the following: +In needs to be said that Gagarin originates from Laika. In some sense, one may think of it as Laika 2.0, though it's not backward compatible. The main advantages of using Gagarin rather then Laika are the following: - it does not depend on `phantomjs` -- it does not depend on injected code, so the test runner does not have to rebuild your app each time you run the tests -- the communication with client is done through a real webdriver API, which means that your tests can visit any web page and are not bound to your app routes +- it does not depend on injected source code, so the test runner does not have to rebuild your app each time you run the tests +- the communication with client is done through a real webDriver API, which means that your tests can visit any web page and are not bound to your app's routes - it does not depend on external mongo processes; the tests runner is clever enough to find mongo executable within your meteor development bundle -## Test runner +# The test runner -Gagarin can be also used as a simple test runner, which in it's essence is very similar to [laika](https://github.com/arunoda/laika), though it's much more flexible and up-to-date and compatible with the latest Meteor versions. +Gagarin can be also used as a simple test runner, which in it's essence is very similar to [laika](https://github.com/arunoda/laika), though it's much more flexible and up-to-date and compatible with the latest Meteor versions. Currently it's implemented as a custom `mocha` interface, which simply extends the standard `bdd` ui. This may change in the future if there's a a demand to support other testing frameworks. ## Installation -First you need to add `gagarin` package to your app: +First you need to add `anti:gagarin` package to your app: meteor add anti:gagarin -It basically adds some backdoor functionality for testing purposes. But don't worry, it's not active when you're running your app from bundle, so in production environment there's no security risk. +It basically adds some backdoor functionality for testing purposes. But don't worry - it's only activate when `GAGARIN_SETTINGS` environment variable is present. For safety, double check it's not there in your production environment. -If you just want to use the test runner, install the cli tool as well: +If you want to use the test runner, install the cli tool as well: npm install -g gagarin -If your app depends on the old atmosphere packages than you also need to make sure that `meteorite` is installed globally. +If your app depends on the old unmigrated atmosphere packages than you also need to make sure that `meteorite` is installed globally. -## Example usage +## The simplest possible test Basically, you run the tests with `gagarin` command within you project root. By default, the script will look for your test definitions inside `tests/gagarin` directory. You can alter this behavior by providing a custom path as the first parameter. For details try `gagarin --help`. The simplest possible test suite may look like this: ```javascript -var expect = require('chai').expect; - describe('Example test suite', function () { var server = meteor(); - it('eval should work', function () { + it('execute should work', function () { // return a promise return server.execute(function () { return Meteor.release; @@ -58,20 +56,14 @@ describe('Example test suite', function () { }); ``` -In the above example `meteor` is a global function provided by the framework, which you can use -to spawn new meteor innstances. Another function of this type is `browser`. +In the above example `meteor` is a global function provided by the framework, which you can use to spawn new meteor instances. Another function of this type is `browser`. For your convenience we've also exposed `expect` from the good old [chai](http://chaijs.com/). ## Testing with browser -Gagarin makes it really easy to coordinate tests for client and server. This idea originated -from laika, but we decided to go for more promise-oriented API. Basically speaking, you can -use the `browser` function to spawn as many clients as you want. The only requirement is that -you have a webdriver running somewhere. You can customize the webdriver url -by providing the corresponding option for the cli tool: +Gagarin makes it really easy to coordinate tests for client and server. This idea originated from Laika, but we decided to go for more promise-oriented API. Basically speaking, you can use the `browser` function to spawn as many clients as you want. The only requirement is that you have a webdriver running somewhere. By default, gagarin will try to find webdrive at port `9515` (chromedriver default). You can customize the webdriver url by providing the corresponding option for the cli tool: ``` gagarin --webdriver http://localhost:9515 ``` - A test suite using both server and client may look like this: ```javascript describe('You can also use browser in your tests', function () { @@ -81,7 +73,7 @@ describe('You can also use browser in your tests', function () { it('should just work', function () { return client.execute(function () { // some code to execute - }, [ /* list of args */ ]).then(function () { + }).then(function () { return server.execute(function () { // some code to execute on server }); @@ -90,6 +82,119 @@ describe('You can also use browser in your tests', function () { }); ``` +# Examples + +Since we don't have a comprehensive documentation yet, please consider the following set of simple examples as a current API reference. Note that this document will evolve in the nearest future. + +## Scope of the a local variable + +It's good to keep in mind that the code which is intended to be executed on either server or client is passed as a string. Of course it does not have an immediate access to you local variable scope. In particular, things like: +```javascript +var a = 1; +it("should be able to access local variable", function () { + return client.execute(function () { + return a + 1; + }); +}); +``` +will throw "a is undefined". Trying to set `a = 1;` will throw as well because the code is implicitly executed in `strict mode`, which does not allow introducing new variables to the global scope. + +## Passing arguments to client and server code + +If you don't need to modify the variables within your "remote" code then probably the easiest way to overcome the problem described above is to pass local scope variables as arguments: + +```javascript +var a = 1; +it("should be able to access local variable", function () { + return client.execute(function (a) { + return a + 1; + }, [ a ]); // array of arguments +}); +``` +Note that this construction will already allow you to do anything you want with your local variables, because you can always update them within `then`, after your client/server computation is done. However, it's not very convenient in more complicated scenarios. + +## Copying closure + +To simplify the interaction between client and server code, we've added an affordance to reuse the declared closure in all three environments: test scope, server and client. To this end, you need to explicitly provide a list of variables to be synced as well as an accessor function: + +```javascript +var a = 1, b = 2, c = 0; +// this is a hack :) +closure(['a', 'b', 'c'], function (key, value) { + return eval(key + (arguments.length > 1 ? '=' + JSON.stringify(value) : '')); +}); +``` +Now this code should work without problems: +```javascript +it("should be able to access local variables", function () { + return client.execute(function (a) { + c = a + b; + }).then(function () { + expect(c).to.equal(3); + }); +}); +``` +The only reserved variable name for closures is `$`, which you probably would not like to use for other reasons. + +## Asynchronous test cases + +On both server and client you can also use asynchronous scripts: +```javascript +it("should be able to do work asynchronously", function () { + return server.promise(function (resolve) { + setTimeout(function () { + resolve(1234); + }, 1000); + }).then(function (value) { + expect(value).to.equal(1234); + }); +}); +``` +The second argument to the `promise` is `reject`, so if you want to throw asynchronously: +```javascript +it("should be able throw asynchronously", function () { + return server.promise(function (resolve, reject) { + setTimeout(function () { + reject(new Error("this is some fake error")); + }, 1000); + }).expectError(function (err) { + expect(err.message).to.contain("fake error"); + }); +}); +``` +If you want to pass additional variables to `promise` method, do it like this: +```javascript +it("should be able to pass arguments", function () { + return server.promise(function (resolve, reject, arg1, arg2) { + setTimeout(function () { + resolve(arg1 + arg2); + }, 1000); + }, [ 1, 2 ]).then(function (value) { + expect(value).to.equal(3); + }); +}); +``` + +## Waiting for conditions + +There's also a useful helper to wait for conditions. Again, it works on both server and client: +```javascript +before(function () { + return client.execute(function () { + Items.insert({_id: 'someFakeId'}); + }); +}); + +it("should be able to wait on server", function () { + return server.wait(1000, 'until data is propagated to the server', function () { + return Items.findOne({_id: 'someFakeId'}); + }).then(function (value) { + expect(value).to.be.ok; + expect(value._id).to.equal('someFakeId'); + }); +}); +``` + ## Disclaimer Gagarin is still in a pretty early development stage. Though it's API will probably change. I have based most of the design decisions on experience with Meteor apps testing but I understand that there are always people who are more experienced and have some nice ideas. I am always opened for discussion and please, feel welcome if you want to contribute. From 573707678aff367f971b9e54e9367596ecf5ab46 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Tue, 16 Dec 2014 19:31:00 +0100 Subject: [PATCH 29/64] Create a special gagarin user on startup --- backdoor.js | 5 ++- package.js | 6 +++- settings.js | 52 +++++++++++++++++++++++++++++++ tests/example/.meteor/packages | 1 + tests/example/.meteor/versions | 8 +++++ tests/example/config/testing.json | 6 ++++ 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 settings.js create mode 100644 tests/example/config/testing.json diff --git a/backdoor.js b/backdoor.js index 465741a..812e00d 100644 --- a/backdoor.js +++ b/backdoor.js @@ -4,14 +4,13 @@ var vm = Npm.require('vm'); var Fiber = Npm.require('fibers'); var Future = Npm.require('fibers/future'); -Gagarin = {}; - -if (process.env.GAGARIN_SETTINGS) { +if (Gagarin.isActive) { // TODO: also protect these methods with some authentication (user/password/token?) // note that required data my be provided with GAGARIN_SETTINGS Meteor.methods({ + '/gagarin/execute': function (closure, code, args) { "use strict"; diff --git a/package.js b/package.js index eb1e20d..71ec28c 100644 --- a/package.js +++ b/package.js @@ -11,9 +11,13 @@ Package.onUse(function (api) { api.versionsFrom('METEOR@1.0'); api.use('livedata', 'server'); + api.use('accounts-password', 'server', { weak: true }); api.addFiles([ - 'backdoor.js' + + 'settings.js', + 'backdoor.js', + ], 'server'); api.export('Gagarin'); diff --git a/settings.js b/settings.js new file mode 100644 index 0000000..e4b99e4 --- /dev/null +++ b/settings.js @@ -0,0 +1,52 @@ + +var settings; + +Gagarin = {}; + +if (process.env.GAGARIN_SETTINGS) { + try { + settings = JSON.parse(process.env.GAGARIN_SETTINGS); + } catch (err) { + console.warn('invalid Gagarin settings\n', err); + } +} + +settings = settings || Meteor.settings.gagarin; + +Gagarin.isActive = !!settings; + +Meteor.startup(function () { + + if (!Gagarin.isActive) { + return; + } + + maybeCreateUser(settings); +}); + +function maybeCreateUser (settings) { + + var userId = null; + + if (!Package['accounts-password']) { + return; + } + + if (!settings.username || !settings.password) { + return; + } + + Meteor.users.remove({ username: settings.username }); + + userId = Accounts.createUser({ + username : settings.username, + password : settings.password, + }); + + Meteor.users.update({_id: userId}, { $set: { + gagarin : true + }}); + +} + + diff --git a/tests/example/.meteor/packages b/tests/example/.meteor/packages index 1674b84..0bf299b 100644 --- a/tests/example/.meteor/packages +++ b/tests/example/.meteor/packages @@ -8,4 +8,5 @@ autopublish insecure anti:gagarin mystor:device-detection +accounts-password diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index 74728f6..f337442 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,3 +1,5 @@ +accounts-base@1.1.2 +accounts-password@1.0.4 anti:gagarin@0.3.0-pre7 application-configuration@1.0.3 autopublish@1.0.1 @@ -14,6 +16,7 @@ ctl@1.0.2 ddp@1.0.12 deps@1.0.5 ejson@1.0.4 +email@1.0.4 fastclick@1.0.1 follower-livedata@1.0.2 geojson-utils@1.0.1 @@ -26,6 +29,7 @@ jquery@1.0.1 json@1.0.1 launch-screen@1.0.0 livedata@1.0.11 +localstorage@1.0.1 logging@1.0.5 meteor-platform@1.2.0 meteor@1.1.3 @@ -34,6 +38,7 @@ minimongo@1.0.5 mobile-status-bar@1.0.1 mongo@1.0.9 mystor:device-detection@0.2.0 +npm-bcrypt@0.7.7 observe-sequence@1.0.3 ordered-dict@1.0.1 random@1.0.1 @@ -42,9 +47,12 @@ reactive-var@1.0.3 reload@1.1.1 retry@1.0.1 routepolicy@1.0.2 +service-configuration@1.0.2 session@1.0.4 +sha@1.0.1 spacebars-compiler@1.0.3 spacebars@1.0.3 +srp@1.0.1 standard-app-packages@1.0.3 templating@1.0.9 tracker@1.0.3 diff --git a/tests/example/config/testing.json b/tests/example/config/testing.json new file mode 100644 index 0000000..eb969be --- /dev/null +++ b/tests/example/config/testing.json @@ -0,0 +1,6 @@ +{ + "gagarin": { + "username": "admin", + "password": "password" + } +} From f34cb53c2058cb004ac203f7755b1ada538945ad Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 11:55:25 +0100 Subject: [PATCH 30/64] Added helper to load settings from file --- lib/tools.js | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/tools.js b/lib/tools.js index 3a9755b..011f8ef 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -152,6 +152,8 @@ module.exports = { }, mergeHelpers: function (helpers, moreHelpers) { + "use strict"; + helpers = helpers || {}; if (moreHelpers) { @@ -171,7 +173,38 @@ module.exports = { }); } return helpers; - } + }, + + getSettings: function (settings) { + "use strict"; + + var content; + + if (!settings) { + return; + } + + if (typeof settings === 'object') { + return settings; + } + + if (typeof settings !== 'string') { + logError('settings should be either object or path to a file'); + return; + } + + try { + content = fs.readFileSync(settings); + } catch (err) { + logError('error while reading file ' + settings, err); + return; + } + try { + return JSON.parse(content); + } catch (err) { + logError('error while parsing settings file', err); + } + }, }; @@ -188,3 +221,11 @@ function initial(array) { return array.slice(0, array.length - 1); } +function logError(context, error) { + "use strict"; + + console.warn(chalk.inverse('[gagarin] ' + context)); + console.warn(chalk.yellow(err.stack)); +} + + From cc18444f43f9c812ae6e8c047c7b60fefe7d0e60 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 12:02:38 +0100 Subject: [PATCH 31/64] Moved example test to a different location --- tests/example/example.js | 3 +++ tests/example/tests/{gagarin.js => gagarin/example.js} | 0 2 files changed, 3 insertions(+) rename tests/example/tests/{gagarin.js => gagarin/example.js} (100%) diff --git a/tests/example/example.js b/tests/example/example.js index 6ca08ae..547c333 100644 --- a/tests/example/example.js +++ b/tests/example/example.js @@ -28,6 +28,9 @@ if (Meteor.isClient) { } if (Meteor.isServer) { + + console.log('settings are:', Meteor.settings); + Meteor.startup(function () { // code to run on server at startup }); diff --git a/tests/example/tests/gagarin.js b/tests/example/tests/gagarin/example.js similarity index 100% rename from tests/example/tests/gagarin.js rename to tests/example/tests/gagarin/example.js From fbab1c6d9408f63e0ef25e4b4d2a098ea8574ae3 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 12:09:03 +0100 Subject: [PATCH 32/64] Added support for settings and verbose options --- bin/gagarin | 3 ++- lib/gagarin.js | 4 +++- lib/instance.js | 7 +++++- lib/interface.js | 56 ++++++++++++++++++++++++++++++------------------ lib/meteor.js | 49 +++++++++++++++++++++++++++++++++++++++--- settings.js | 25 +++++++++++++++------ 6 files changed, 110 insertions(+), 34 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index a0e4e96..a74ee71 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -20,9 +20,10 @@ program //.option('-gc', '--expose-gc', 'expose gc extension') .option('-i, --invert', 'inverts --grep matches') .option('-s, --slow ', '"slow" test threshold in milliseconds [75]') + .option('-S, --settings ', 'use meteor settings from the given file') .option('-t, --timeout ', 'set test-case timeout in milliseconds [2000]') //.option('-u, --ui ', 'specify user-interface (bdd|tdd|exports)', 'bdd') - //.option('-V, --verbose', 'run with verbose mode with logs from client/server', false) + .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]', 2000) diff --git a/lib/gagarin.js b/lib/gagarin.js index 9a7ac10..d55d3f7 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -4,6 +4,7 @@ */ var Meteor = require('./meteor'); +var tools = require('./tools'); var Mocha = require('mocha'); var chalk = require('chalk'); var path = require('path'); @@ -31,6 +32,8 @@ function Gagarin (options) { // TODO: filter out our custom options Mocha.call(this, options); + + this.settings = tools.getSettings(options.settings); } util.inherits(Gagarin, Mocha); @@ -81,4 +84,3 @@ Gagarin.prototype.run = function (callback) { }); }; - diff --git a/lib/instance.js b/lib/instance.js index ad6dbb3..0666646 100644 --- a/lib/instance.js +++ b/lib/instance.js @@ -43,7 +43,9 @@ module.exports = function Instance (node, main, env, options) { }, safetyTimeout); meteor.stdout.on('data', function (data) { - //process.stdout.write(data); + + options.onData && options.onData(data); + var match = /Поехали!/.exec(data.toString()); if (match) { clearTimeout(safetyHandle); @@ -56,6 +58,9 @@ module.exports = function Instance (node, main, env, options) { process.once('exit', onProcessExit); meteor.stderr.on('data', function (data) { // look for errors + + options.onData && options.onData(data, { isError: true }); + data.toString().split('\n').forEach(function (line) { var hasMatch = [ { diff --git a/lib/interface.js b/lib/interface.js index d9d45b5..b92e43f 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -1,10 +1,18 @@ -var mergeHelpers = require('./tools').mergeHelpers; -var Closure = require('./closure'); -var Browser = require('./browser'); -var helpers = require('./helpers'); -var Meteor = require('./meteor'); -var Mocha = require('mocha'); +/** + * Module dependencies. + */ + +var Closure = require('./closure'); +var Browser = require('./browser'); +var helpers = require('./helpers'); +var Meteor = require('./meteor'); +var tools = require('./tools'); +var Mocha = require('mocha'); + +/** + * Custom Mocha interface. + */ Mocha.interfaces['gagarin'] = module.exports = function (suite) { "use strict"; @@ -13,13 +21,14 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { // use the original bdd intrface Mocha.interfaces.bdd.apply(this, arguments); - var gagarinOptions = this.options; + var gagarin_options = this.options; + var gagarin_settings = tools.getSettings(this.options.settings) || {}; // make sure it's not undefined suite.on('pre-require', function (context) { - var before = context.before; - var after = context.after; - var stack = []; + var before = context.before; + var after = context.after; + var stack = []; context.expect = require('chai').expect; @@ -37,12 +46,14 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { options = { pathToApp: options }; } - mergeHelpers(myHelpers, [ helpers.both, helpers.server ]); - mergeHelpers(myHelpers, gagarinOptions.serverHelpers); + tools.mergeHelpers(myHelpers, [ helpers.both, helpers.server ]); + tools.mergeHelpers(myHelpers, gagarin_options.serverHelpers); var meteor = new Meteor({ - pathToApp : options.pathToApp || gagarinOptions.pathToApp, - helpers : mergeHelpers(myHelpers, options.helpers), + pathToApp : options.pathToApp || gagarin_options.pathToApp, + helpers : tools.mergeHelpers(myHelpers, options.helpers), + settings : tools.getSettings(options.settings) || gagarin_settings, + verbose : gagarin_options.verbose, }); meteor.useClosure(function () { @@ -78,17 +89,17 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { options = { location: options }; } - mergeHelpers(myHelpers, [ helpers.both, helpers.client ]); - mergeHelpers(myHelpers, gagarinOptions.clientHelpers); + tools.mergeHelpers(myHelpers, [ helpers.both, helpers.client ]); + tools.mergeHelpers(myHelpers, gagarin_options.clientHelpers); var browser = new Browser({ - helpers : mergeHelpers(myHelpers, options.helpers), + helpers : tools.mergeHelpers(myHelpers, options.helpers), location : options.location, - webdriver : options.webdriver || gagarinOptions.webdriver, + webdriver : options.webdriver || gagarin_options.webdriver, windowSize : options.windowSize, capabilities : options.capabilities, - dontWaitForMeteor : options.dontWaitForMeteor || gagarinOptions.dontWaitForMeteor, - meteorLoadTimeout : options.meteorLoadTimeout || gagarinOptions.meteorLoadTimeout, + dontWaitForMeteor : options.dontWaitForMeteor || gagarin_options.dontWaitForMeteor, + meteorLoadTimeout : options.meteorLoadTimeout || gagarin_options.meteorLoadTimeout, }); browser.useClosure(function () { @@ -121,11 +132,14 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { }); }; + context.settings = JSON.parse(JSON.stringify(gagarin_settings)); // deep copy :P + //context.wait = wait; }); } +/* function wait(timeout, message, func, args) { "use strict"; @@ -150,5 +164,5 @@ function wait(timeout, message, func, args) { }, timeout); }); } - +*/ diff --git a/lib/meteor.js b/lib/meteor.js index d845abd..b49560e 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -33,6 +33,7 @@ function Meteor (options) { } var pathToApp = options.pathToApp || path.resolve('.'); + var verbose = !!options.verbose; var mongoServerPromise = new MongoServerAsPromise({ pathToApp: pathToApp }); var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider); @@ -40,8 +41,9 @@ function Meteor (options) { var instance = null; var meteorPromise = null; - var meteorHasCrashed = false; - var meteorUniqueCode = null; + var meteorHasCrashed = false; + var meteorUniqueCode = null; + var meteorSettings = tools.getSettings(options.settings); var meteorRestartDelay = 100; var restartRequired = false; @@ -138,6 +140,13 @@ function Meteor (options) { } } }, + onData: function (data, options) { + if (options && options.isError) { + logServerError(data); + } else { + logServerOutput(data); + } + }, }; cleanUpThen(function () { // respawn @@ -149,10 +158,14 @@ function Meteor (options) { var pathToMain = all[0]; var env = Object.create(process.env); + if (meteorSettings) { + env.METEOR_SETTINGS = JSON.stringify(meteorSettings); + } + env.ROOT_URL = meteorLocation; env.PORT = meteorPort; env.MONGO_URL = all[1] + '/' + meteorDatabase; - env.GAGARIN_SETTINGS = "{}"; + env.GAGARIN_SETTINGS = "{}"; // only used if METEOR_SETTINGS does not contain gagarin field setTimeout(function () { instance = new Instance(tools.getNodePath(pathToApp), pathToMain, env, instanceOptions); @@ -169,6 +182,36 @@ function Meteor (options) { return meteorPromise; } // serverControllerProvider + function logServerOutput(data) { + if (!verbose) { + return; + } + process.stdout.write(data.toString().split('\n').map(function (line) { + if (line === '\r') { + return; + } + if (line.length === 0) { + return ""; + } + return chalk.blue('[server] ') + line; + }).join('\n')); + } + + function logServerError(data) { + if (!verbose) { + return; + } + process.stdout.write(data.toString().split('\n').map(function (line) { + if (line === '\r') { + return; + } + if (line.length === 0) { + return ""; + } + return chalk.red('[server] ') + line; + }).join('\n')); + } + } MeteorPromiseChain.methods.forEach(function (name) { diff --git a/settings.js b/settings.js index e4b99e4..d38e861 100644 --- a/settings.js +++ b/settings.js @@ -1,20 +1,31 @@ -var settings; - -Gagarin = {}; - -if (process.env.GAGARIN_SETTINGS) { +if (!Meteor.settings.gagarin && process.env.GAGARIN_SETTINGS) { try { - settings = JSON.parse(process.env.GAGARIN_SETTINGS); + Meteor.settings.gagarin = JSON.parse(process.env.GAGARIN_SETTINGS); } catch (err) { console.warn('invalid Gagarin settings\n', err); } } -settings = settings || Meteor.settings.gagarin; +var settings = Meteor.settings.gagarin; + +Gagarin = {}; Gagarin.isActive = !!settings; +if (Gagarin.isActive) { + Gagarin.settings = settings; +} + +Meteor.startup(function () { + + if (!Gagarin.isActive) { + return; + } + + maybeCreateUser(settings); +}); + Meteor.startup(function () { if (!Gagarin.isActive) { From 8d461ac50c0ac25cec2549109efa668abf18c218 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 12:13:02 +0100 Subject: [PATCH 33/64] Make sure we don't throw strings --- lib/browser.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/browser.js b/lib/browser.js index e4abb59..8eacf0f 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -6,6 +6,7 @@ var wd = require('wd'); module.exports = Browser; function Browser (options) { + "use strict"; var self = this; var closure = null; @@ -28,15 +29,23 @@ function Browser (options) { } return browserPromise = new Promise(function (resolve, reject) { + + var _reject = function (err) { + if (typeof err === 'string') { + return reject(new Error(err)); + } + reject(err); + } + browser.init(capabilities, function (err) { if (err) { - return reject(err); + return _reject(err); } //--------------- if (windowSize) { browser.setWindowSize(windowSize.width, windowSize.height, function (err) { if (err) { - reject(err); + _reject(err); } afterResize(); }); @@ -47,26 +56,26 @@ function Browser (options) { function afterResize() { browser.get(myLocation, function (err) { if (err) { - return reject(err); + return _reject(err); } if (dontWaitForMeteor) { // already done, so lets just resolve return resolve({ browser: browser, closure: closure }); } browser.setAsyncScriptTimeout(meteorLoadTimeout, function (err) { if (err) { - return reject(err); + return _reject(err); } // wait until meteor core packages are loaded ... browser.waitForConditionInBrowser('!!window.Meteor && !!window.Meteor.startup', function (err) { if (err) { - return reject(err); + return _reject(err); } // ... and until all other files are loaded as well browser.executeAsync(function (cb) { Meteor.startup(cb); }, function (err) { if (err) { - return reject(err); + return _reject(err); } resolve({ browser: browser, closure: closure }); }); From 9037f05f09196627451c907ba3326be036df6b01 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 12:25:09 +0100 Subject: [PATCH 34/64] Fixes #19 --- bin/gagarin | 9 ++++++++- tests/example/tests/README.md | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/example/tests/README.md diff --git a/bin/gagarin b/bin/gagarin index a0e4e96..e18ffc8 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -44,7 +44,14 @@ if (!fs.existsSync(pathToTests)) { if (fs.lstatSync(pathToTests).isDirectory()) { fs.readdirSync(pathToTests).forEach(function (file) { - gagarin.addFile(path.join(pathToTests, file)); + var pathToFile = path.join(pathToTests, file); + if (path.extname(file) !== '.js') { + return; + } + if (fs.lstatSync(pathToFile).isDirectory()) { + return; + } + gagarin.addFile(pathToFile); }); } else { gagarin.addFile(pathToTests); diff --git a/tests/example/tests/README.md b/tests/example/tests/README.md new file mode 100644 index 0000000..ae15261 --- /dev/null +++ b/tests/example/tests/README.md @@ -0,0 +1 @@ +This directory contains example tests. \ No newline at end of file From 7ab1d738ee4f243536f41a9d4381018327365417 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 13:31:22 +0100 Subject: [PATCH 35/64] Tests can now run against remote server as well --- bin/gagarin | 4 ++-- lib/ddp.js | 9 ++++++++- lib/interface.js | 9 +++++---- lib/meteor.js | 44 +++++++++++++++++++++++++++++++++++++---- tests/specs/browser.js | 2 ++ tests/specs/remote.js | 45 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 tests/specs/remote.js diff --git a/bin/gagarin b/bin/gagarin index 2155faf..8425b6b 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -27,13 +27,13 @@ program .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]', 2000) + .option('-p, --path-to-app ', 'path to a meteor application', path.resolve('.')) + .option('-r, --remote-server ', 'run tests on a remote server') program.name = 'gagarin'; program.parse(process.argv); -program.pathToApp = path.resolve('.'); - var gagarin = new Gagarin(program); var pathToTests = program.args[0] || path.join(program.pathToApp, 'tests', 'gagarin'); diff --git a/lib/ddp.js b/lib/ddp.js index c74d06f..71c1c16 100644 --- a/lib/ddp.js +++ b/lib/ddp.js @@ -9,6 +9,7 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) { var ddpClientPromise = null; var code = null; var port = null; + var host = null; return function ddpClientAsPromise () { @@ -20,13 +21,14 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) { code = setup.code; port = setup.port; + host = setup.host || 'localhost'; ddpClientPromise = new Promise(function (resolve, reject) { ddpClient && ddpClient.close(); ddpClient = new DDPClient({ - host : "localhost", + host : host, port : port, path : "websocket", ssl : false, @@ -43,6 +45,11 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) { resolve(ddpClient); }); + // TODO: re-enable this feature when we make timeout configurable + //setTimeout(function () { + // reject(new Error('timeout while waiting to establish ddp connection')); + //}, 2000); + //------------------------------------------------------------- //\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ //------------------------------------------------------------- diff --git a/lib/interface.js b/lib/interface.js index b92e43f..d8480ba 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -50,10 +50,11 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { tools.mergeHelpers(myHelpers, gagarin_options.serverHelpers); var meteor = new Meteor({ - pathToApp : options.pathToApp || gagarin_options.pathToApp, - helpers : tools.mergeHelpers(myHelpers, options.helpers), - settings : tools.getSettings(options.settings) || gagarin_settings, - verbose : gagarin_options.verbose, + pathToApp : options.pathToApp || gagarin_options.pathToApp, + helpers : tools.mergeHelpers(myHelpers, options.helpers), + settings : tools.getSettings(options.settings) || gagarin_settings, + verbose : gagarin_options.verbose, + remoteServer : options.remoteServer || gagarin_options.remoteServer, }); meteor.useClosure(function () { diff --git a/lib/meteor.js b/lib/meteor.js index b49560e..1182add 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -14,6 +14,7 @@ var Remote = require('./remote'); var chalk = require('chalk'); var tools = require('./tools'); var path = require('path'); +var url = require('url'); module.exports = Meteor; module.exports.BuildAsPromise = BuildAsPromise; @@ -32,10 +33,15 @@ function Meteor (options) { options = { pathToApp: options }; } - var pathToApp = options.pathToApp || path.resolve('.'); - var verbose = !!options.verbose; + var pathToApp = options.pathToApp || path.resolve('.'); + var remoteServer = options.remoteServer; + var verbose = !!options.verbose; - var mongoServerPromise = new MongoServerAsPromise({ pathToApp: pathToApp }); + if (remoteServer) { + remoteServer = url.parse(remoteServer); + } + + var mongoServerPromise = !remoteServer && new MongoServerAsPromise({ pathToApp: pathToApp }); var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider); var instance = null; @@ -49,10 +55,20 @@ function Meteor (options) { var restartRequired = false; var meteorPort = options.port || 4000 + Math.floor(Math.random() * 1000); - var meteorLocation = 'http://localhost:' + meteorPort; + var meteorLocation = remoteServer ? url.format(remoteServer) : 'http://localhost:' + meteorPort; var meteorDatabase = options.dbName || 'gagarin_' + (new Date()).getTime(); + if (meteorLocation.charAt(meteorLocation.length - 1) === "/") { + meteorLocation = meteorLocation.substr(0, meteorLocation.length - 1); + } + var remote = new Remote(ddpClientAsPromise, serverControllerProvider); + + var dummyController = { + start : noop, + restart : noop, + stop : noop, + }; var controller = { start : function (cb) { cb() }, @@ -99,6 +115,14 @@ function Meteor (options) { } function ddpSetupProvider() { + + if (remoteServer) { + return Promise.resolve({ + host: remoteServer.host, + port: 443, + }); + } + return serverControllerProvider().then(function () { return { port: meteorPort, @@ -118,6 +142,10 @@ function Meteor (options) { restartRequired = false; + if (remoteServer) { + return meteorPromise = Promise.resolve(dummyController); + } + meteorPromise = new Promise(function (resolve, reject) { var instanceOptions = { @@ -212,6 +240,14 @@ function Meteor (options) { }).join('\n')); } + function deny (cb) { + cb(new Error('action not allowed')); + } + + function noop (cb) { + cb(); + } + } MeteorPromiseChain.methods.forEach(function (name) { diff --git a/tests/specs/browser.js b/tests/specs/browser.js index 0f932dc..3869cdb 100644 --- a/tests/specs/browser.js +++ b/tests/specs/browser.js @@ -69,6 +69,8 @@ describe('Tests with browser', function () { var browser2 = browser(server.location); + this.timeout(10000); + before(function () { return server.restart(2000); }); diff --git a/tests/specs/remote.js b/tests/specs/remote.js new file mode 100644 index 0000000..1a57b00 --- /dev/null +++ b/tests/specs/remote.js @@ -0,0 +1,45 @@ + +describe('Running code on a remote server', function () { + + var server = meteor({ + remoteServer: 'https://vostok-1.meteor.com' + }); + + it('execute should work', function () { + return server.execute(function () { + return Meteor.release; + }) + .then(function (value) { + expect(value).not.to.be.empty; + }); + }); + + it('db insert should work', function () { + return server.execute(function () { + return Items.insert({vostok: Random.id()}); + }) + .then(function (value) { + expect(value).not.to.be.empty; + }); + }); + + it('promise should work', function () { + return server.promise(function (resolve, reject) { + Meteor.setTimeout(function () { + resolve(Meteor.release); + }, 100); + }) + .then(function (value) { + expect(value).not.to.be.empty; + }); + }); + + it('should throw a descriptive error', function () { + return server.execute(function () { + undefined[0]; + }).expectError(function (err) { + expect(err.toString()).to.contain('property'); + }); + }); + +}); From ad24381266b62d4f0f8f643b08b79e24881a5c4d Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 13:35:48 +0100 Subject: [PATCH 36/64] Make sure we can use localhost as remote --- lib/meteor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/meteor.js b/lib/meteor.js index 1182add..022fb9f 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -118,8 +118,8 @@ function Meteor (options) { if (remoteServer) { return Promise.resolve({ - host: remoteServer.host, - port: 443, + host: remoteServer.hostname, + port: remoteServer.port || 443, }); } From 52d34c764f74f3d99a628f54dd482c4f1e92460f Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 13:57:52 +0100 Subject: [PATCH 37/64] Bumped package version --- package.js | 2 +- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index 71ec28c..900b5c4 100644 --- a/package.js +++ b/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre7", + version: "0.3.0-pre8", git: "https://github.com/anticoders/gagarin.git", }); diff --git a/package.json b/package.json index 96b2f73..23cf675 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre7", + "version": "0.3.0-pre8", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index f337442..f3b38be 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,6 +1,6 @@ accounts-base@1.1.2 accounts-password@1.0.4 -anti:gagarin@0.3.0-pre7 +anti:gagarin@0.3.0-pre8 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 From 04d34dccda62fbc90c563530b113852515db7921 Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 14:54:58 +0100 Subject: [PATCH 38/64] Allow running tests on an arbitrary web driver --- test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test.js b/test.js index 036a9b9..c10df12 100755 --- a/test.js +++ b/test.js @@ -9,11 +9,13 @@ var helpers = require('./lib/helpers'); program .option('-g, --grep ', 'only run tests matching ') + .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') program.parse(process.argv); var gagarin = new Gagarin({ pathToApp : pathToApp, + webdriver : program.webdriver, reporter : 'spec', timeout : 5000, grep : program.grep, From 9ccbc64f4de3ccee297ac4c68b202c2490ad344e Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 14:55:20 +0100 Subject: [PATCH 39/64] Fixed problem with late timeout in wait --- backdoor.js | 10 +++++++--- lib/browserPromiseChain.js | 19 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/backdoor.js b/backdoor.js index 812e00d..80ab4a7 100644 --- a/backdoor.js +++ b/backdoor.js @@ -151,6 +151,13 @@ if (Gagarin.isActive) { if (!done && typeof context.value === 'function') { + // XXX this should be defined prior to the fist call to test, because + // the latter can return immediatelly + + handle2 = setTimeout(function () { + resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); + }, timeout); + (function test() { var feedback; try { @@ -170,9 +177,6 @@ if (Gagarin.isActive) { } }()); - handle2 = setTimeout(function () { - resolve({ error: 'I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.' }); - }, timeout); } else { resolve({ error: 'code has to be a function' }) } diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index 3fff9a3..11167e5 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -323,27 +323,26 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { chunks.push( ' "use strict";', ' var cb = arguments[arguments.length - 1];', - ' var handle = null;', - ' var handle2 = null;', + ' var handle1 = null;', + ' var handle2 = window.setTimeout(function () {', + ' window.clearTimeout(handle1);', + ' cb({ closure: {' + keys + '}, error: ' + JSON.stringify('I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.') + ' });', + ' }, ' + JSON.stringify(timeout) + ');', ' (function test() {', ' var value;', ' try {', ' value = (' + code.toString() + ')(' + args.join(', ') + ');', ' if (value) {', - ' clearTimeout(handle2);', + ' window.clearTimeout(handle2);', ' cb({ value: value, closure: {' + keys + '} });', ' } else {', - ' handle = setTimeout(test, 50);', // repeat after 1/20 sec. + ' handle1 = window.setTimeout(test, 50);', // repeat after 1/20 sec. ' }', ' } catch (err) {', - ' clearTimeout(handle2);', + ' window.clearTimeout(handle2);', ' cb({ error: err.message, closure: {' + keys + '} });', ' }', - ' }());', - ' setTimeout(function () {', - ' clearTimeout(handle);', - ' handle2 = cb({ closure: {' + keys + '}, error: ' + JSON.stringify('I have been waiting for ' + timeout + ' ms ' + message + ', but it did not happen.') + ' });', - ' }, ' + JSON.stringify(timeout) + ');' + ' }());' ); chunks.push('}'); From 8bfa6d49599f5040e7e19a159d590371c951cf4a Mon Sep 17 00:00:00 2001 From: apendua Date: Wed, 17 Dec 2014 15:06:03 +0100 Subject: [PATCH 40/64] Bumped package version --- package.js | 2 +- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index 900b5c4..7f0345d 100644 --- a/package.js +++ b/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre8", + version: "0.3.0-pre9", git: "https://github.com/anticoders/gagarin.git", }); diff --git a/package.json b/package.json index 23cf675..2ab2b77 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre8", + "version": "0.3.0-pre9", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index f3b38be..b20d1a2 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,6 +1,6 @@ accounts-base@1.1.2 accounts-password@1.0.4 -anti:gagarin@0.3.0-pre8 +anti:gagarin@0.3.0-pre9 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 From c9d9ecc3455b47ae8f1ae6014742c0e3eb094897 Mon Sep 17 00:00:00 2001 From: pociej Date: Wed, 17 Dec 2014 21:49:11 +0100 Subject: [PATCH 41/64] parsing -m option --- .gitignore | 1 + bin/gagarin | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1a8789f..8b3ffcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .build* +npm-debug.log diff --git a/bin/gagarin b/bin/gagarin index 8425b6b..03491ce 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -5,6 +5,12 @@ var Gagarin = require('../lib/gagarin'); var path = require('path'); var fs = require('fs'); +//TODO check why we can simply pass parseInt as argument ? + +var intParse = function(v){ + return parseInt(v); +}; + program .version(require('../package.json').version) .usage('[debug] [options] [files]') @@ -64,5 +70,3 @@ gagarin.run(function (failedCount) { } process.exit(0); }); - - From 663fc776c2bd7326f31aa43ff73f6b6673c1f85d Mon Sep 17 00:00:00 2001 From: pociej Date: Thu, 18 Dec 2014 00:19:44 +0100 Subject: [PATCH 42/64] reject with info when no webdriver found on given location --- bin/gagarin | 2 +- lib/browser.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index 03491ce..e08e6f0 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -32,7 +32,7 @@ program .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') - .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]', 2000) + .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]',intParse, 2000) .option('-p, --path-to-app ', 'path to a meteor application', path.resolve('.')) .option('-r, --remote-server ', 'run tests on a remote server') diff --git a/lib/browser.js b/lib/browser.js index 8eacf0f..eaf56d5 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -7,17 +7,19 @@ module.exports = Browser; function Browser (options) { "use strict"; - + var self = this; var closure = null; - var browser = wd.remote(options.webdriver || "http://localhost:9515"); // default to chromedriver + var driverLocation = options.webdriver || "127.0.0.1:9515"; + var browser = wd.remote(driverLocation); // default to chromedriver var myLocation = options.location || "http://localhost:3000"; // default to meteor var dontWaitForMeteor = options.dontWaitForMeteor !== undefined ? !!options.dontWaitForMeteor : false; var meteorLoadTimeout = options.meteorLoadTimeout !== undefined ? options.meteorLoadTimeout : 2000; var browserPromise = null; var capabilities = options.capabilities || {}; var windowSize = options.windowSize; - + var portscanner = require('portscanner'); + var URL = require('url'); self.getBrowserPromise = function () { // XXX theoretically we could use promiseChainRemote here to simplify the code @@ -36,6 +38,12 @@ function Browser (options) { } reject(err); } + var driverLocationParsed = URL.parse(driverLocation); + portscanner.checkPortStatus(driverLocationParsed.port, driverLocationParsed.hostname, function(error, status) { + if(status != 'open'){ + _reject('webdriver not found on ' + driverLocation); + } + }); browser.init(capabilities, function (err) { if (err) { From b5596b824632dd724b1138d08e406f9fc10a04cd Mon Sep 17 00:00:00 2001 From: pociej Date: Thu, 18 Dec 2014 00:22:51 +0100 Subject: [PATCH 43/64] portscanner package added to dependencies --- lib/browser.js | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/browser.js b/lib/browser.js index eaf56d5..894b385 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -20,6 +20,7 @@ function Browser (options) { var windowSize = options.windowSize; var portscanner = require('portscanner'); var URL = require('url'); + self.getBrowserPromise = function () { // XXX theoretically we could use promiseChainRemote here to simplify the code diff --git a/package.json b/package.json index 2ab2b77..1b3b3a1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "homepage": "https://github.com/anticoders/gagarin", "devDependencies": {}, "dependencies": { + "portscanner" : "1.0.0", "chai": "^1.10.0", "chalk": "^0.5.1", "commander": "^2.5.0", From 212af5d5893157ad33a983a80ac3ef49a2c5394e Mon Sep 17 00:00:00 2001 From: apendua Date: Thu, 18 Dec 2014 01:10:49 +0100 Subject: [PATCH 44/64] Increased timeout for remote test suite --- tests/specs/remote.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/specs/remote.js b/tests/specs/remote.js index 1a57b00..fcfa208 100644 --- a/tests/specs/remote.js +++ b/tests/specs/remote.js @@ -1,6 +1,8 @@ describe('Running code on a remote server', function () { + this.timeout(10000); + var server = meteor({ remoteServer: 'https://vostok-1.meteor.com' }); From 6c13b837e5459ee7a475c84e9aaaaa2c6c7fc03d Mon Sep 17 00:00:00 2001 From: pociej Date: Thu, 18 Dec 2014 16:49:14 +0100 Subject: [PATCH 45/64] check gagarin verion compatibility --- lib/browser.js | 76 ++++++++++++++++++++++++-------------------------- lib/gagarin.js | 73 +++++++++++++++++++++++++++++++----------------- 2 files changed, 85 insertions(+), 64 deletions(-) diff --git a/lib/browser.js b/lib/browser.js index 894b385..8030b24 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -20,7 +20,7 @@ function Browser (options) { var windowSize = options.windowSize; var portscanner = require('portscanner'); var URL = require('url'); - + self.getBrowserPromise = function () { // XXX theoretically we could use promiseChainRemote here to simplify the code @@ -41,59 +41,57 @@ function Browser (options) { } var driverLocationParsed = URL.parse(driverLocation); portscanner.checkPortStatus(driverLocationParsed.port, driverLocationParsed.hostname, function(error, status) { - if(status != 'open'){ + if(status != 'open') _reject('webdriver not found on ' + driverLocation); - } - }); - - browser.init(capabilities, function (err) { - if (err) { - return _reject(err); - } - //--------------- - if (windowSize) { - browser.setWindowSize(windowSize.width, windowSize.height, function (err) { - if (err) { - _reject(err); - } + browser.init(capabilities, function (err) { + if (err) { + return _reject(err); + } + //--------------- + if (windowSize) { + browser.setWindowSize(windowSize.width, windowSize.height, function (err) { + if (err) { + _reject(err); + } + afterResize(); + }); + } else { afterResize(); - }); - } else { - afterResize(); - } - //---------------------- - function afterResize() { - browser.get(myLocation, function (err) { - if (err) { - return _reject(err); - } - if (dontWaitForMeteor) { // already done, so lets just resolve - return resolve({ browser: browser, closure: closure }); - } - browser.setAsyncScriptTimeout(meteorLoadTimeout, function (err) { + } + //---------------------- + function afterResize() { + browser.get(myLocation, function (err) { if (err) { return _reject(err); } - // wait until meteor core packages are loaded ... - browser.waitForConditionInBrowser('!!window.Meteor && !!window.Meteor.startup', function (err) { + if (dontWaitForMeteor) { // already done, so lets just resolve + return resolve({ browser: browser, closure: closure }); + } + browser.setAsyncScriptTimeout(meteorLoadTimeout, function (err) { if (err) { return _reject(err); } - // ... and until all other files are loaded as well - browser.executeAsync(function (cb) { - Meteor.startup(cb); - }, function (err) { + // wait until meteor core packages are loaded ... + browser.waitForConditionInBrowser('!!window.Meteor && !!window.Meteor.startup', function (err) { if (err) { return _reject(err); } - resolve({ browser: browser, closure: closure }); + // ... and until all other files are loaded as well + browser.executeAsync(function (cb) { + Meteor.startup(cb); + }, function (err) { + if (err) { + return _reject(err); + } + resolve({ browser: browser, closure: closure }); + }); }); }); }); - }); - } + } - }); + }); + }); }); // Promise } diff --git a/lib/gagarin.js b/lib/gagarin.js index d55d3f7..d8d9749 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -9,7 +9,7 @@ var Mocha = require('mocha'); var chalk = require('chalk'); var path = require('path'); var util = require('util'); - +var fs = require('fs'); var BuildAsPromise = Meteor.BuildAsPromise; module.exports = Gagarin; @@ -44,43 +44,66 @@ util.inherits(Gagarin, Mocha); * * @param {Function} callback */ + + Gagarin.prototype.run = function (callback) { "use strict"; var pathToApp = this.options.pathToApp || path.resolve('.'); var self = this; - process.stdout.write('\n'); + var checkVersionsComplatibility = function(callback){ + fs.readFile(pathToApp+'/.meteor/versions','utf-8', function read(err, data) { + var meteorPackageVersion = data.match(/anti:gagarin@(.*)/)[1]; + if(self.options._version == meteorPackageVersion){ + process.stdout.write( + chalk.red('Versions of node and meteor gagarin packages are not compatible please update \n') + ); + process.kill(); + }else{ + callback(); + } + }); + }; + + var run = function(){ + process.stdout.write('\n'); + + var title = 'building app => ' + pathToApp; - var title = 'building app => ' + pathToApp; + var counter = 0; + var spinner = '/-\\|'; + var handle = setInterval(function () { + var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); + process.stdout.write( + chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') + ); + }, 100); - var counter = 0; - var spinner = '/-\\|'; - var handle = setInterval(function () { - var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); - process.stdout.write( - chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') - ); - }, 100); + BuildAsPromise(pathToApp).then(function () { - BuildAsPromise(pathToApp).then(function () { - clearInterval(handle); - process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); + //throw new Error('test'); + clearInterval(handle); + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); - Mocha.prototype.run.call(self, callback); + Mocha.prototype.run.call(self, callback); - }, function (err) { - // clear the loading spinner - process.stdout.write(new Array(title.length + 12).join(' ')); - clearInterval(handle); - throw err; - }) - .catch(function (err) { - // make sure the error passes through promise - setTimeout(function () { + }, function (err) { + // clear the loading spinner + process.stdout.write(new Array(title.length + 12).join(' ')); + clearInterval(handle); throw err; + }) + .catch(function (err) { + // make sure the error passes through promise + setTimeout(function () { + throw err; + }); }); - }); + } + + + checkVersionsComplatibility(run); }; From 5911e6dfb80049ed7d749d61780c749b84477e95 Mon Sep 17 00:00:00 2001 From: pociej Date: Thu, 18 Dec 2014 17:15:24 +0100 Subject: [PATCH 46/64] typo fix --- lib/gagarin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gagarin.js b/lib/gagarin.js index d8d9749..be1443c 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -48,14 +48,14 @@ util.inherits(Gagarin, Mocha); Gagarin.prototype.run = function (callback) { "use strict"; - + console.log(Gagarin.prototype); var pathToApp = this.options.pathToApp || path.resolve('.'); var self = this; var checkVersionsComplatibility = function(callback){ fs.readFile(pathToApp+'/.meteor/versions','utf-8', function read(err, data) { var meteorPackageVersion = data.match(/anti:gagarin@(.*)/)[1]; - if(self.options._version == meteorPackageVersion){ + if(self.options._version != meteorPackageVersion){ process.stdout.write( chalk.red('Versions of node and meteor gagarin packages are not compatible please update \n') ); From e8036d8bf72b66088a1a02db3fdff0a7d633268b Mon Sep 17 00:00:00 2001 From: pociej Date: Fri, 19 Dec 2014 01:42:24 +0100 Subject: [PATCH 47/64] #37 --- bin/gagarin | 1 + lib/browserPromiseChain.js | 20 +++++++++----------- lib/gagarin.js | 2 +- lib/remote.js | 4 +--- tests/example/tests/gagarin/example.js | 10 ++++++++++ 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index e08e6f0..ee9c883 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -50,6 +50,7 @@ if (!fs.existsSync(pathToTests)) { } if (fs.lstatSync(pathToTests).isDirectory()) { + fs.readdirSync(pathToTests).forEach(function (file) { var pathToFile = path.join(pathToTests, file); if (path.extname(file) !== '.js') { diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index 11167e5..f5c3bc1 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -9,6 +9,7 @@ module.exports = BrowserPromiseChain; //---------------------- function BrowserPromiseChain (operand, helpers) { + "use strict"; var self = this; @@ -91,7 +92,7 @@ BrowserPromiseChain.methods = webdriverMethods.concat([ */ BrowserPromiseChain.prototype.applyWebDriver = function (name, args, custom) { "use strict"; - + args = Array.prototype.slice.call(args, 0); // shallow copy the arguments var self = this; @@ -186,18 +187,12 @@ BrowserPromiseChain.prototype.execute = function (code, args) { code = codeToString(code); + eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); + return self.applyWebDriver('execute', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; - if (typeof code === 'string' && !/^function\s+\(/.test(code)) { - code = 'function () {\n' + code + '\n}'; - } - - if (typeof code === 'function') { - code = code.toString(); - } - code = wrapSourceCode(code, args, closure); operand.browser.execute("return (" + code + ").apply(null, arguments)", @@ -229,6 +224,8 @@ BrowserPromiseChain.prototype.promise = function (code, args) { code = codeToString(code); + eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); + // we could set this 5000 globally, right? return self.callWebDriver('setAsyncScriptTimeout', self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { @@ -306,6 +303,8 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { code = codeToString(code); + eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); + return self.callWebDriver('setAsyncScriptTimeout', 2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; @@ -388,7 +387,7 @@ function codeToString(code) { "use strict"; if (typeof code === 'string' && !/^function\s+\(/.test(code)) { - return 'function () {\n' + code + '\n}'; + return 'function () {\n' +'try{\n'+code +'\n}' + 'catch(ex){\n return ex}' + '\n}'; } if (typeof code === 'function') { return code.toString(); @@ -442,4 +441,3 @@ function wrapSourceCode(code, args, closure) { return chunks.join('\n'); } - diff --git a/lib/gagarin.js b/lib/gagarin.js index be1443c..12d4003 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -48,7 +48,7 @@ util.inherits(Gagarin, Mocha); Gagarin.prototype.run = function (callback) { "use strict"; - console.log(Gagarin.prototype); + var pathToApp = this.options.pathToApp || path.resolve('.'); var self = this; diff --git a/lib/remote.js b/lib/remote.js index f04543c..763ca5f 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -43,7 +43,7 @@ function Remote(ddpClientProvider, serverControllerProvider) { } else { args = Array.isArray(args) ? args : [ args ]; } - + return ddpClientProvider().then(function (ddpClient) { callDDPMethod(ddpClient, '/gagarin/execute', [ closure(), code.toString(), args ], cb); @@ -121,5 +121,3 @@ function Remote(ddpClientProvider, serverControllerProvider) { } }; - - diff --git a/tests/example/tests/gagarin/example.js b/tests/example/tests/gagarin/example.js index 43a5878..99dcc07 100644 --- a/tests/example/tests/gagarin/example.js +++ b/tests/example/tests/gagarin/example.js @@ -19,4 +19,14 @@ describe('An example Gagarin test suite', function () { }); }); + it("should be able to do work asynchronously", function () { + return server.promise(function (resolve) { + setTimeout(function () { + resolve(1234); + }, 1000); + }).then(function (value) { + expect(value).to.equal(1234); + }); + }); + }); From 09a70600a1bc9823942f55a9bf5ef9073fb07098 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 10:40:52 +0100 Subject: [PATCH 48/64] Update options for BuildAsPromise --- lib/build.js | 34 +++++++++++++++++++++++++--------- lib/closure.js | 4 ++-- lib/gagarin.js | 2 +- lib/meteor.js | 2 +- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/lib/build.js b/lib/build.js index e6cc9d0..ede348f 100644 --- a/lib/build.js +++ b/lib/build.js @@ -8,9 +8,14 @@ var fs = require('fs'); var myBuildPromises = {}; -module.exports = function BuildAsPromise (pathToApp, timeout) { +module.exports = function BuildAsPromise (options) { "use strict"; + options = options || {}; + + var pathToApp = options.pathToApp || path.resolve('.'); + var timeout = options.timeout || 60000; + var pathToSmartJson = path.join(pathToApp, 'smart.json'); var pathToMongoLock = path.join(pathToApp, '.meteor', 'local', 'db', 'mongod.lock'); var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); @@ -28,12 +33,18 @@ module.exports = function BuildAsPromise (pathToApp, timeout) { if (fs.existsSync(pathToSmartJson)) { myBuildPromises[pathToApp] = tools.smartPackagesAsPromise(pathToApp).then(function () { return MongoServerAsPromise({ pathToApp: pathToApp }).then(function (mongoUrl) { - return BuildPromise(pathToApp, mongoUrl); + return BuildPromise({ + pathToApp : pathToApp, + mongoUrl : mongoUrl, + }); }); }); } else { myBuildPromises[pathToApp] = MongoServerAsPromise({ pathToApp: pathToApp }).then(function (mongoUrl) { - return BuildPromise(pathToApp, mongoUrl); + return BuildPromise({ + pathToApp : pathToApp, + mongoUrl : mongoUrl, + }); }); } @@ -42,15 +53,20 @@ module.exports = function BuildAsPromise (pathToApp, timeout) { // PRIVATE BUILD PROMISE IMPLEMENTATION -function BuildPromise(pathToApp, mongoUrl, timeout) { +function BuildPromise(options) { "use strict"; - // XXX this is redundant but we don't want to depend on the outer scope + options = options || {}; + + var pathToApp = options.pathToApp || path.resolve('.'); + var mongoUrl = options.mongoUrl || "http://localhost:27017"; + var timeout = options.timeout || 60000; + var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); - var env = Object.create(process.env); - var port = 4000 + Math.floor(Math.random() * 1000); + var env = Object.create(process.env); + var port = 4000 + Math.floor(Math.random() * 1000); - // TODO: eventually drop this database + // TODO: in the end, drop this database env.MONGO_URL = mongoUrl + '/' + 'gagarin_build'; return new Promise(function (resolve, reject) { @@ -153,7 +169,7 @@ function BuildPromise(pathToApp, mongoUrl, timeout) { reject(new Error('Timeout while wating for meteor to start.')); }); meteor.kill('SIGINT') - }, timeout || 60000); + }, timeout); }); // new Promise } // BuildPromise diff --git a/lib/closure.js b/lib/closure.js index 358db1e..88fb32a 100644 --- a/lib/closure.js +++ b/lib/closure.js @@ -14,8 +14,8 @@ function Closure (parent, listOfKeys, accessor) { var closure = {}; listOfKeys = listOfKeys || []; - accessor = accessor || function () {}; - parent = parent || {}; + accessor = accessor || function () {}; + parent = parent || {}; Object.keys(parent).forEach(function (key) { closure[key] = parent[key]; diff --git a/lib/gagarin.js b/lib/gagarin.js index d55d3f7..28c526b 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -63,7 +63,7 @@ Gagarin.prototype.run = function (callback) { ); }, 100); - BuildAsPromise(pathToApp).then(function () { + BuildAsPromise({ pathToApp: pathToApp }).then(function () { clearInterval(handle); process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); diff --git a/lib/meteor.js b/lib/meteor.js index 022fb9f..f9e48dd 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -181,7 +181,7 @@ function Meteor (options) { meteorHasCrashed = false - Promise.all([ BuildAsPromise(pathToApp), mongoServerPromise ]).then(function (all) { + Promise.all([ BuildAsPromise({ pathToApp: pathToApp }), mongoServerPromise ]).then(function (all) { var pathToMain = all[0]; var env = Object.create(process.env); From 84c1fc327617ace7a5db690efe153316162c3ac3 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 11:40:41 +0100 Subject: [PATCH 49/64] Each app has their own mongo server --- lib/mongo.js | 26 +++++++++++++++----------- tests/build_error/.gagarin/.gitignore | 1 + 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 tests/build_error/.gagarin/.gitignore diff --git a/lib/mongo.js b/lib/mongo.js index 22ed5c5..8496abf 100644 --- a/lib/mongo.js +++ b/lib/mongo.js @@ -8,28 +8,32 @@ var tools = require('./tools'); var either = tools.either; var fs = require('fs'); -var mongoServerPromise = null; +var myMongoServerPromises = {}; module.exports = function MongoServerAsPromise (options) { "use strict"; - if (mongoServerPromise) { + options = options || {}; + + var pathToApp = options.pathToApp || path.resolve('.'); + + if (myMongoServerPromises[pathToApp]) { // XXX there can be only one mongo process using the given database path - return Promise.resolve(mongoServerPromise); + return Promise.resolve(myMongoServerPromises[pathToApp]); } - var pathToGitIgnore = tools.getPathToGitIgnore(options.pathToApp); + var pathToGitIgnore = tools.getPathToGitIgnore(pathToApp); var mongoPort = options.dbPort || 27018 + Math.floor(Math.random() * 1000); - var mongoPath = tools.getMongoPath(options.pathToApp); - var pathToDB = options.dbPath || tools.getPathToDB(options.pathToApp); + var mongoPath = tools.getMongoPath(pathToApp); + var pathToDB = options.dbPath || tools.getPathToDB(pathToApp); var mongoUrl = 'mongodb://127.0.0.1:' + mongoPort; if (!fs.existsSync(mongoPath)) { return Promise.reject(new Error('file ' + mongoPath + ' does not exists')); } - //----------------------------------------------------------- - mongoServerPromise = new Promise(function (resolve, reject) { + //------------------------------------------------------------------------- + myMongoServerPromises[pathToApp] = new Promise(function (resolve, reject) { var mongoArgs = [ '--port', mongoPort, '--smallfiles', '--nojournal', '--noprealloc' ]; try { @@ -66,13 +70,13 @@ module.exports = function MongoServerAsPromise (options) { }); - mongoServerPromise.connectToDB = function (dbName) { - return mongoServerPromise.then(function (mongoUrl) { + myMongoServerPromises[pathToApp].connectToDB = function (dbName) { + return myMongoServerPromises[pathToApp].then(function (mongoUrl) { return new Promise(function (resolve, reject) { MongoClient.connect(mongoUrl + '/' + dbName, either(reject).or(resolve)); }); }); }; - return mongoServerPromise; + return myMongoServerPromises[pathToApp]; }; diff --git a/tests/build_error/.gagarin/.gitignore b/tests/build_error/.gagarin/.gitignore new file mode 100644 index 0000000..c2c027f --- /dev/null +++ b/tests/build_error/.gagarin/.gitignore @@ -0,0 +1 @@ +local \ No newline at end of file From b49d58bc098a52eb420798e13ecbf1fcd3cb9dbb Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 12:07:25 +0100 Subject: [PATCH 50/64] Do not necessarily spawn mongod MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if it’s not required --- lib/database.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/meteor.js | 19 +++++++------- 2 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 lib/database.js diff --git a/lib/database.js b/lib/database.js new file mode 100644 index 0000000..f50325a --- /dev/null +++ b/lib/database.js @@ -0,0 +1,68 @@ + +/** + * Module dependencies. + */ + +var MongoServerAsPromise = require('./mongo'); +var MongoClient = require('mongodb').MongoClient; +var Promise = require('es6-promise').Promise; +var url = require('url'); + +/** + * Creates a new database promise. + * @param {Object} options + * + * Options: + * - {String} mongoUrl + * - {String} pathToApp + * - {String} dbName + */ +module.exports = function DatabaseAsPromise (options) { + "use strict"; + + options = options || {}; + + var dbName = options.dbName || getRandomName(); + var mongoUrl = options.mongoUrl || ''; + var pathToApp = options.pathToApp || path.resolve('.'); + var parsedUrl = null; + var getMongoUrl = null; + var needsCleanup = false; + + if (mongoUrl) { + parsedUrl = url.parse(mongoUrl); + parsedUrl.path = parsedUrl.path || dbName; + //--------------------------------------------------- + getMongoUrl = Promise.resolve(url.format(parsedUrl)); + } else { + needsCleanup = true; + //-------------------------------------------------------------- + getMongoUrl = new MongoServerAsPromise({ pathToApp: pathToApp }) + .then(function (mongoUrl) { + return mongoUrl + '/' + dbName; + }); + } + + return getMongoUrl.then(function (mongoUrl) { + return new Promise(function (resolve, reject) { + MongoClient.connect(mongoUrl, function (err, db) { + if (err) { + return reject(err); + } + db.cleanUp = function (cb) { + if (needsCleanup) { + db.dropDatabase(cb); + } else { + db.close(cb); + } + } + resolve(db); + }); + }); + }); + +}; + +function getRandomName() { + return 'gagarin_' + Math.floor(1000 * Math.random()) + '_' + (new Date()).getTime(); +} diff --git a/lib/meteor.js b/lib/meteor.js index f9e48dd..9662e1b 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -5,8 +5,8 @@ */ var makeDDPClientFactory = require('./ddp'); -var MongoServerAsPromise = require('./mongo'); var MeteorPromiseChain = require('./meteorPromiseChain'); +var DatabaseAsPromise = require('./database'); var BuildAsPromise = require('./build'); var Instance = require('./instance'); var Promise = require('es6-promise').Promise; @@ -18,7 +18,6 @@ var url = require('url'); module.exports = Meteor; module.exports.BuildAsPromise = BuildAsPromise; -module.exports.MongoAsPromise = MongoServerAsPromise; //------- // METEOR @@ -41,7 +40,7 @@ function Meteor (options) { remoteServer = url.parse(remoteServer); } - var mongoServerPromise = !remoteServer && new MongoServerAsPromise({ pathToApp: pathToApp }); + var databasePromise = new DatabaseAsPromise({ pathToApp: pathToApp }); var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider); var instance = null; @@ -76,7 +75,7 @@ function Meteor (options) { (restartRequired = true) && serverControllerProvider() .then(function () { cb(); - }, function (err) { + }).catch(function (err) { cb(err); }); }, @@ -85,9 +84,9 @@ function Meteor (options) { if (err) { return cb(err); } - mongoServerPromise.connectToDB(meteorDatabase).then(function (db) { - db.dropDatabase(cb); - }, function (err) { + databasePromise.then(function (db) { + db.cleanUp(cb); + }).catch(function (err) { cb(err); }); }); @@ -181,9 +180,11 @@ function Meteor (options) { meteorHasCrashed = false - Promise.all([ BuildAsPromise({ pathToApp: pathToApp }), mongoServerPromise ]).then(function (all) { + Promise.all([ BuildAsPromise({ pathToApp: pathToApp }), databasePromise ]).then(function (all) { var pathToMain = all[0]; + var database = all[1]; + var env = Object.create(process.env); if (meteorSettings) { @@ -192,7 +193,7 @@ function Meteor (options) { env.ROOT_URL = meteorLocation; env.PORT = meteorPort; - env.MONGO_URL = all[1] + '/' + meteorDatabase; + env.MONGO_URL = database.options.url; env.GAGARIN_SETTINGS = "{}"; // only used if METEOR_SETTINGS does not contain gagarin field setTimeout(function () { From 80c014abc2f174bc5f3129c64722dcb88dfec1ab Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 12:54:25 +0100 Subject: [PATCH 51/64] Added --skip-build option --- bin/gagarin | 1 + lib/build.js | 14 ++++++++++---- lib/gagarin.js | 5 +++-- lib/interface.js | 1 + lib/meteor.js | 14 ++++++++++++-- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index 8425b6b..68ee44f 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -23,6 +23,7 @@ program .option('-S, --settings ', 'use meteor settings from the given file') .option('-t, --timeout ', 'set test-case timeout in milliseconds [2000]') //.option('-u, --ui ', 'specify user-interface (bdd|tdd|exports)', 'bdd') + .option('-B, --skip-build', 'do not build, just run the tests') .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') diff --git a/lib/build.js b/lib/build.js index ede348f..b3a5252 100644 --- a/lib/build.js +++ b/lib/build.js @@ -17,14 +17,14 @@ module.exports = function BuildAsPromise (options) { var timeout = options.timeout || 60000; var pathToSmartJson = path.join(pathToApp, 'smart.json'); - var pathToMongoLock = path.join(pathToApp, '.meteor', 'local', 'db', 'mongod.lock'); var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); + var skipBuild = !!options.skipBuild; - if (fs.existsSync(pathToMongoLock) && fs.readFileSync(pathToMongoLock).toString('utf8')) { + if (skipBuild || isLocked(pathToApp)) { if (fs.existsSync(pathToMain)) { return Promise.resolve(pathToMain); } else { - return Promise.reject(new Error('The meteor build does not seem to exist even though the meteor is running.')); + return Promise.reject(new Error('File: ' + pathToMain + ' does not exist.')); } } @@ -174,5 +174,11 @@ function BuildPromise(options) { }); // new Promise } // BuildPromise - +/** + * Guess if meteor is currently running. + */ +function isLocked(pathToApp) { + var pathToMongoLock = path.join(pathToApp, '.meteor', 'local', 'db', 'mongod.lock'); + return fs.existsSync(pathToMongoLock) && fs.readFileSync(pathToMongoLock).toString('utf8'); +} diff --git a/lib/gagarin.js b/lib/gagarin.js index 28c526b..d7375e8 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -48,11 +48,12 @@ Gagarin.prototype.run = function (callback) { "use strict"; var pathToApp = this.options.pathToApp || path.resolve('.'); + var skipBuild = !!this.options.skipBuild; var self = this; process.stdout.write('\n'); - var title = 'building app => ' + pathToApp; + var title = (skipBuild ? 'skipped ' : '') + 'building app => ' + pathToApp; var counter = 0; var spinner = '/-\\|'; @@ -63,7 +64,7 @@ Gagarin.prototype.run = function (callback) { ); }, 100); - BuildAsPromise({ pathToApp: pathToApp }).then(function () { + BuildAsPromise({ pathToApp: pathToApp, skipBuild: skipBuild }).then(function () { clearInterval(handle); process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); diff --git a/lib/interface.js b/lib/interface.js index d8480ba..2369aee 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -55,6 +55,7 @@ Mocha.interfaces['gagarin'] = module.exports = function (suite) { settings : tools.getSettings(options.settings) || gagarin_settings, verbose : gagarin_options.verbose, remoteServer : options.remoteServer || gagarin_options.remoteServer, + skipBuild : options.skipBuild || gagarin_options.skipBuild, }); meteor.useClosure(function () { diff --git a/lib/meteor.js b/lib/meteor.js index 9662e1b..1418918 100644 --- a/lib/meteor.js +++ b/lib/meteor.js @@ -35,6 +35,7 @@ function Meteor (options) { var pathToApp = options.pathToApp || path.resolve('.'); var remoteServer = options.remoteServer; var verbose = !!options.verbose; + var skipBuild = !!options.skipBuild; if (remoteServer) { remoteServer = url.parse(remoteServer); @@ -180,12 +181,21 @@ function Meteor (options) { meteorHasCrashed = false - Promise.all([ BuildAsPromise({ pathToApp: pathToApp }), databasePromise ]).then(function (all) { + Promise.all([ + + BuildAsPromise({ + pathToApp: pathToApp, + skipBuild: skipBuild, + }), + + databasePromise, + + ]).then(function (all) { var pathToMain = all[0]; var database = all[1]; - var env = Object.create(process.env); + var env = Object.create(process.env); if (meteorSettings) { env.METEOR_SETTINGS = JSON.stringify(meteorSettings); From 9f5a221125c24faa0ec926e75ffdd207730116ab Mon Sep 17 00:00:00 2001 From: pociej Date: Fri, 19 Dec 2014 13:20:52 +0100 Subject: [PATCH 52/64] code cleaning --- lib/browserPromiseChain.js | 3 ++- tests/example/tests/gagarin/example.js | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index f5c3bc1..a09b59b 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -189,6 +189,7 @@ BrowserPromiseChain.prototype.execute = function (code, args) { eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); + return self.applyWebDriver('execute', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; @@ -387,7 +388,7 @@ function codeToString(code) { "use strict"; if (typeof code === 'string' && !/^function\s+\(/.test(code)) { - return 'function () {\n' +'try{\n'+code +'\n}' + 'catch(ex){\n return ex}' + '\n}'; + return 'function () {'+code +'}' + '\n}'; } if (typeof code === 'function') { return code.toString(); diff --git a/tests/example/tests/gagarin/example.js b/tests/example/tests/gagarin/example.js index 99dcc07..43a5878 100644 --- a/tests/example/tests/gagarin/example.js +++ b/tests/example/tests/gagarin/example.js @@ -19,14 +19,4 @@ describe('An example Gagarin test suite', function () { }); }); - it("should be able to do work asynchronously", function () { - return server.promise(function (resolve) { - setTimeout(function () { - resolve(1234); - }, 1000); - }).then(function (value) { - expect(value).to.equal(1234); - }); - }); - }); From 4d93b00434e4159d02a89e6694ba8f80c8ece476 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 13:23:25 +0100 Subject: [PATCH 53/64] Added verbose option for build promise --- lib/build.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/build.js b/lib/build.js index b3a5252..7c31256 100644 --- a/lib/build.js +++ b/lib/build.js @@ -15,6 +15,7 @@ module.exports = function BuildAsPromise (options) { var pathToApp = options.pathToApp || path.resolve('.'); var timeout = options.timeout || 60000; + var verbose = options.verbose !== undefined ? !!options.verbose : false; var pathToSmartJson = path.join(pathToApp, 'smart.json'); var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); @@ -36,6 +37,7 @@ module.exports = function BuildAsPromise (options) { return BuildPromise({ pathToApp : pathToApp, mongoUrl : mongoUrl, + verbose : verbose, }); }); }); @@ -44,6 +46,7 @@ module.exports = function BuildAsPromise (options) { return BuildPromise({ pathToApp : pathToApp, mongoUrl : mongoUrl, + verbose : verbose, }); }); } @@ -61,6 +64,7 @@ function BuildPromise(options) { var pathToApp = options.pathToApp || path.resolve('.'); var mongoUrl = options.mongoUrl || "http://localhost:27017"; var timeout = options.timeout || 60000; + var verbose = options.verbose !== undefined ? !!options.verbose : false; var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); var env = Object.create(process.env); @@ -85,6 +89,7 @@ function BuildPromise(options) { meteor.stdout.on('data', function (data) { //process.stdout.write(data); + logMeteorOutput(data); data.toString().split('\n').forEach(function (line) { @@ -172,6 +177,22 @@ function BuildPromise(options) { }, timeout); }); // new Promise + + function logMeteorOutput(data) { + if (!verbose) { + return; + } + process.stdout.write(data.toString().split('\n').map(function (line) { + if (line === '\r') { + return; + } + if (line.length === 0) { + return ""; + } + return chalk.green('<---> ') + line; + }).join('\n')); + } + } // BuildPromise /** From eafe62d43e1e465c22125dc97b7c249759472c90 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 13:23:50 +0100 Subject: [PATCH 54/64] Added --build-only option --- bin/gagarin | 1 + lib/gagarin.js | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index 68ee44f..ed82f69 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -24,6 +24,7 @@ program .option('-t, --timeout ', 'set test-case timeout in milliseconds [2000]') //.option('-u, --ui ', 'specify user-interface (bdd|tdd|exports)', 'bdd') .option('-B, --skip-build', 'do not build, just run the tests') + .option('-o, --build-only', 'just build, do not run the tests') .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') diff --git a/lib/gagarin.js b/lib/gagarin.js index d7375e8..28ddfbc 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -49,6 +49,8 @@ Gagarin.prototype.run = function (callback) { var pathToApp = this.options.pathToApp || path.resolve('.'); var skipBuild = !!this.options.skipBuild; + var buildOnly = !!this.options.buildOnly; + var verbose = buildOnly || (this.options.verbose !== undefined ? !!this.options.verbose : false); var self = this; process.stdout.write('\n'); @@ -57,19 +59,36 @@ Gagarin.prototype.run = function (callback) { var counter = 0; var spinner = '/-\\|'; - var handle = setInterval(function () { + var handle = !verbose && setInterval(function () { var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); process.stdout.write( chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') ); }, 100); - BuildAsPromise({ pathToApp: pathToApp, skipBuild: skipBuild }).then(function () { + if (verbose) { + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\n\n')); + } - clearInterval(handle); - process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); + BuildAsPromise({ + + pathToApp : pathToApp, + skipBuild : skipBuild, + verbose : verbose, + + }).then(function () { + + if (!verbose) { + clearInterval(handle); + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); + } - Mocha.prototype.run.call(self, callback); + if (buildOnly) { + process.stdout.write(chalk.green('\n done ...\n\n')); + callback(0); + } else { + Mocha.prototype.run.call(self, callback); + } }, function (err) { // clear the loading spinner From 764a0dc095bab52483ad73f24e19e1356bc092ec Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 13:28:41 +0100 Subject: [PATCH 55/64] Add more options to the main testing script --- test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test.js b/test.js index c10df12..521654b 100755 --- a/test.js +++ b/test.js @@ -10,6 +10,9 @@ var helpers = require('./lib/helpers'); program .option('-g, --grep ', 'only run tests matching ') .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') + .option('-B, --skip-build', 'do not build, just run the tests') + .option('-o, --build-only', 'just build, do not run the tests') + .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) program.parse(process.argv); @@ -19,6 +22,9 @@ var gagarin = new Gagarin({ reporter : 'spec', timeout : 5000, grep : program.grep, + skipBuild : program.skipBuild, + buildOnly : program.buildOnly, + verbose : program.verbose, }); fs.readdirSync(path.join(__dirname, 'tests', 'specs')).forEach(function (file) { From 2dcdf1f108017b8d4390e3a4e36df31bd9068a38 Mon Sep 17 00:00:00 2001 From: pociej Date: Fri, 19 Dec 2014 13:46:30 +0100 Subject: [PATCH 56/64] one more fix --- lib/gagarin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gagarin.js b/lib/gagarin.js index 317b280..c48a489 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -55,7 +55,7 @@ Gagarin.prototype.run = function (callback) { var verbose = buildOnly || (this.options.verbose !== undefined ? !!this.options.verbose : false); var self = this; - var checkVersionsComplatibility = function(callback){ + var checkVersionsComplatibility = function(){ fs.readFile(pathToApp+'/.meteor/versions','utf-8', function read(err, data) { var meteorPackageVersion = data.match(/anti:gagarin@(.*)/)[1]; if(self.options._version != meteorPackageVersion){ @@ -64,14 +64,14 @@ Gagarin.prototype.run = function (callback) { ); process.kill(); }else{ - callback(); + run(); } }); }; var run = function(){ process.stdout.write('\n'); - + var title = (skipBuild ? 'skipped ' : '') + 'building app => ' + pathToApp; var counter = 0; @@ -123,6 +123,6 @@ Gagarin.prototype.run = function (callback) { } - checkVersionsComplatibility(run); + checkVersionsComplatibility(); }; From dcc103156ea24af4ebaae2291da658dc5486f8c6 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 18:51:23 +0100 Subject: [PATCH 57/64] Added version check to the build promise --- lib/build.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/build.js b/lib/build.js index 7c31256..61d1a33 100644 --- a/lib/build.js +++ b/lib/build.js @@ -5,6 +5,7 @@ var spawn = require('child_process').spawn; var tools = require('./tools'); var path = require('path'); var fs = require('fs'); +var version = require('../package.json').version; var myBuildPromises = {}; @@ -17,6 +18,7 @@ module.exports = function BuildAsPromise (options) { var timeout = options.timeout || 60000; var verbose = options.verbose !== undefined ? !!options.verbose : false; + var pathToVersions = path.join(pathToApp, '.meteor', 'versions'); var pathToSmartJson = path.join(pathToApp, 'smart.json'); var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); var skipBuild = !!options.skipBuild; @@ -29,6 +31,19 @@ module.exports = function BuildAsPromise (options) { } } + var versions = fs.readFileSync(pathToVersions, 'utf-8'); + var versionMatch = versions.match(/anti:gagarin@(.*)/); + + if (!versionMatch) { + return Promise.reject(new Error('Please add anti:gagarin to your app before running tests.')); + } else if (versionMatch[1] !== version) { + return Promise.reject(new Error( + 'Versions of node package (' + version + + ') and meteor packages (' + versionMatch[1] + + ') are not compatible; please update.' + )); + } + if (myBuildPromises[pathToApp]) return myBuildPromises[pathToApp]; if (fs.existsSync(pathToSmartJson)) { From 280fad032299cabd8246f965a6f167e73ceeecaa Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Fri, 19 Dec 2014 18:51:41 +0100 Subject: [PATCH 58/64] Added tests for version checking --- tests/build_error/.meteor/versions | 3 +- tests/build_error/packages/anti:gagarin | 1 + tests/incompatible/.gagarin/.gitignore | 1 + .../incompatible/.meteor/.finished-upgraders | 7 +++ tests/incompatible/.meteor/.gitignore | 1 + tests/incompatible/.meteor/.id | 7 +++ tests/incompatible/.meteor/cordova-plugins | 1 + tests/incompatible/.meteor/packages | 10 ++++ tests/incompatible/.meteor/platforms | 2 + tests/incompatible/.meteor/release | 1 + tests/incompatible/.meteor/versions | 54 +++++++++++++++++++ tests/incompatible/incompatible.html | 14 +++++ tests/incompatible/incompatible.js | 23 ++++++++ tests/no_gagarin/.gagarin/.gitignore | 1 + tests/no_gagarin/.meteor/.finished-upgraders | 7 +++ tests/no_gagarin/.meteor/.gitignore | 1 + tests/no_gagarin/.meteor/.id | 7 +++ tests/no_gagarin/.meteor/packages | 8 +++ tests/no_gagarin/.meteor/platforms | 2 + tests/no_gagarin/.meteor/release | 1 + tests/no_gagarin/.meteor/versions | 52 ++++++++++++++++++ tests/no_gagarin/no_gagarin.html | 14 +++++ tests/no_gagarin/no_gagarin.js | 23 ++++++++ tests/specs/exceptions.js | 52 ++++++++++++++++++ 24 files changed, 291 insertions(+), 2 deletions(-) create mode 120000 tests/build_error/packages/anti:gagarin create mode 100644 tests/incompatible/.gagarin/.gitignore create mode 100644 tests/incompatible/.meteor/.finished-upgraders create mode 100644 tests/incompatible/.meteor/.gitignore create mode 100644 tests/incompatible/.meteor/.id create mode 100644 tests/incompatible/.meteor/cordova-plugins create mode 100644 tests/incompatible/.meteor/packages create mode 100644 tests/incompatible/.meteor/platforms create mode 100644 tests/incompatible/.meteor/release create mode 100644 tests/incompatible/.meteor/versions create mode 100644 tests/incompatible/incompatible.html create mode 100644 tests/incompatible/incompatible.js create mode 100644 tests/no_gagarin/.gagarin/.gitignore create mode 100644 tests/no_gagarin/.meteor/.finished-upgraders create mode 100644 tests/no_gagarin/.meteor/.gitignore create mode 100644 tests/no_gagarin/.meteor/.id create mode 100644 tests/no_gagarin/.meteor/packages create mode 100644 tests/no_gagarin/.meteor/platforms create mode 100644 tests/no_gagarin/.meteor/release create mode 100644 tests/no_gagarin/.meteor/versions create mode 100644 tests/no_gagarin/no_gagarin.html create mode 100644 tests/no_gagarin/no_gagarin.js diff --git a/tests/build_error/.meteor/versions b/tests/build_error/.meteor/versions index f5adf2c..8b4d7ae 100644 --- a/tests/build_error/.meteor/versions +++ b/tests/build_error/.meteor/versions @@ -1,4 +1,4 @@ -anti:gagarin@0.2.2 +anti:gagarin@0.3.0-pre9 application-configuration@1.0.3 autopublish@1.0.1 autoupdate@1.1.3 @@ -33,7 +33,6 @@ minifiers@1.1.2 minimongo@1.0.5 mobile-status-bar@1.0.1 mongo@1.0.9 -mrt:altimeter@0.0.2 observe-sequence@1.0.3 ordered-dict@1.0.1 random@1.0.1 diff --git a/tests/build_error/packages/anti:gagarin b/tests/build_error/packages/anti:gagarin new file mode 120000 index 0000000..a8a4f8c --- /dev/null +++ b/tests/build_error/packages/anti:gagarin @@ -0,0 +1 @@ +../../.. \ No newline at end of file diff --git a/tests/incompatible/.gagarin/.gitignore b/tests/incompatible/.gagarin/.gitignore new file mode 100644 index 0000000..c2c027f --- /dev/null +++ b/tests/incompatible/.gagarin/.gitignore @@ -0,0 +1 @@ +local \ No newline at end of file diff --git a/tests/incompatible/.meteor/.finished-upgraders b/tests/incompatible/.meteor/.finished-upgraders new file mode 100644 index 0000000..68df3d8 --- /dev/null +++ b/tests/incompatible/.meteor/.finished-upgraders @@ -0,0 +1,7 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file diff --git a/tests/incompatible/.meteor/.gitignore b/tests/incompatible/.meteor/.gitignore new file mode 100644 index 0000000..4083037 --- /dev/null +++ b/tests/incompatible/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tests/incompatible/.meteor/.id b/tests/incompatible/.meteor/.id new file mode 100644 index 0000000..0a23026 --- /dev/null +++ b/tests/incompatible/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +axf5jcnlw01o1jst1p4 diff --git a/tests/incompatible/.meteor/cordova-plugins b/tests/incompatible/.meteor/cordova-plugins new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/incompatible/.meteor/cordova-plugins @@ -0,0 +1 @@ + diff --git a/tests/incompatible/.meteor/packages b/tests/incompatible/.meteor/packages new file mode 100644 index 0000000..e08adb3 --- /dev/null +++ b/tests/incompatible/.meteor/packages @@ -0,0 +1,10 @@ +# Meteor packages used by this project, one per line. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +autopublish +insecure +anti:gagarin + diff --git a/tests/incompatible/.meteor/platforms b/tests/incompatible/.meteor/platforms new file mode 100644 index 0000000..efeba1b --- /dev/null +++ b/tests/incompatible/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/tests/incompatible/.meteor/release b/tests/incompatible/.meteor/release new file mode 100644 index 0000000..f1b6255 --- /dev/null +++ b/tests/incompatible/.meteor/release @@ -0,0 +1 @@ +METEOR@1.0.1 diff --git a/tests/incompatible/.meteor/versions b/tests/incompatible/.meteor/versions new file mode 100644 index 0000000..f5adf2c --- /dev/null +++ b/tests/incompatible/.meteor/versions @@ -0,0 +1,54 @@ +anti:gagarin@0.2.2 +application-configuration@1.0.3 +autopublish@1.0.1 +autoupdate@1.1.3 +base64@1.0.1 +binary-heap@1.0.1 +blaze-tools@1.0.1 +blaze@2.0.3 +boilerplate-generator@1.0.1 +callback-hook@1.0.1 +check@1.0.2 +ctl-helper@1.0.4 +ctl@1.0.2 +ddp@1.0.12 +deps@1.0.5 +ejson@1.0.4 +fastclick@1.0.1 +follower-livedata@1.0.2 +geojson-utils@1.0.1 +html-tools@1.0.2 +htmljs@1.0.2 +http@1.0.8 +id-map@1.0.1 +insecure@1.0.1 +jquery@1.0.1 +json@1.0.1 +launch-screen@1.0.0 +livedata@1.0.11 +logging@1.0.5 +meteor-platform@1.2.0 +meteor@1.1.3 +minifiers@1.1.2 +minimongo@1.0.5 +mobile-status-bar@1.0.1 +mongo@1.0.9 +mrt:altimeter@0.0.2 +observe-sequence@1.0.3 +ordered-dict@1.0.1 +random@1.0.1 +reactive-dict@1.0.4 +reactive-var@1.0.3 +reload@1.1.1 +retry@1.0.1 +routepolicy@1.0.2 +session@1.0.4 +spacebars-compiler@1.0.3 +spacebars@1.0.3 +templating@1.0.9 +tracker@1.0.3 +ui@1.0.4 +underscore@1.0.1 +url@1.0.2 +webapp-hashing@1.0.1 +webapp@1.1.4 diff --git a/tests/incompatible/incompatible.html b/tests/incompatible/incompatible.html new file mode 100644 index 0000000..cfc6903 --- /dev/null +++ b/tests/incompatible/incompatible.html @@ -0,0 +1,14 @@ + + incompatible + + + +

Welcome to Meteor!

+ + {{> hello}} + + + diff --git a/tests/incompatible/incompatible.js b/tests/incompatible/incompatible.js new file mode 100644 index 0000000..f8bee6b --- /dev/null +++ b/tests/incompatible/incompatible.js @@ -0,0 +1,23 @@ +if (Meteor.isClient) { + // counter starts at 0 + Session.setDefault("counter", 0); + + Template.hello.helpers({ + counter: function () { + return Session.get("counter"); + } + }); + + Template.hello.events({ + 'click button': function () { + // increment the counter when button is clicked + Session.set("counter", Session.get("counter") + 1); + } + }); +} + +if (Meteor.isServer) { + Meteor.startup(function () { + // code to run on server at startup + }); +} diff --git a/tests/no_gagarin/.gagarin/.gitignore b/tests/no_gagarin/.gagarin/.gitignore new file mode 100644 index 0000000..c2c027f --- /dev/null +++ b/tests/no_gagarin/.gagarin/.gitignore @@ -0,0 +1 @@ +local \ No newline at end of file diff --git a/tests/no_gagarin/.meteor/.finished-upgraders b/tests/no_gagarin/.meteor/.finished-upgraders new file mode 100644 index 0000000..68df3d8 --- /dev/null +++ b/tests/no_gagarin/.meteor/.finished-upgraders @@ -0,0 +1,7 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file diff --git a/tests/no_gagarin/.meteor/.gitignore b/tests/no_gagarin/.meteor/.gitignore new file mode 100644 index 0000000..4083037 --- /dev/null +++ b/tests/no_gagarin/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tests/no_gagarin/.meteor/.id b/tests/no_gagarin/.meteor/.id new file mode 100644 index 0000000..e2ea5ab --- /dev/null +++ b/tests/no_gagarin/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +vn5ojh50ubk1dryrm5 diff --git a/tests/no_gagarin/.meteor/packages b/tests/no_gagarin/.meteor/packages new file mode 100644 index 0000000..e893668 --- /dev/null +++ b/tests/no_gagarin/.meteor/packages @@ -0,0 +1,8 @@ +# Meteor packages used by this project, one per line. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-platform +autopublish +insecure diff --git a/tests/no_gagarin/.meteor/platforms b/tests/no_gagarin/.meteor/platforms new file mode 100644 index 0000000..efeba1b --- /dev/null +++ b/tests/no_gagarin/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/tests/no_gagarin/.meteor/release b/tests/no_gagarin/.meteor/release new file mode 100644 index 0000000..f1b6255 --- /dev/null +++ b/tests/no_gagarin/.meteor/release @@ -0,0 +1 @@ +METEOR@1.0.1 diff --git a/tests/no_gagarin/.meteor/versions b/tests/no_gagarin/.meteor/versions new file mode 100644 index 0000000..2c204e2 --- /dev/null +++ b/tests/no_gagarin/.meteor/versions @@ -0,0 +1,52 @@ +application-configuration@1.0.3 +autopublish@1.0.1 +autoupdate@1.1.3 +base64@1.0.1 +binary-heap@1.0.1 +blaze-tools@1.0.1 +blaze@2.0.3 +boilerplate-generator@1.0.1 +callback-hook@1.0.1 +check@1.0.2 +ctl-helper@1.0.4 +ctl@1.0.2 +ddp@1.0.12 +deps@1.0.5 +ejson@1.0.4 +fastclick@1.0.1 +follower-livedata@1.0.2 +geojson-utils@1.0.1 +html-tools@1.0.2 +htmljs@1.0.2 +http@1.0.8 +id-map@1.0.1 +insecure@1.0.1 +jquery@1.0.1 +json@1.0.1 +launch-screen@1.0.0 +livedata@1.0.11 +logging@1.0.5 +meteor-platform@1.2.0 +meteor@1.1.3 +minifiers@1.1.2 +minimongo@1.0.5 +mobile-status-bar@1.0.1 +mongo@1.0.9 +observe-sequence@1.0.3 +ordered-dict@1.0.1 +random@1.0.1 +reactive-dict@1.0.4 +reactive-var@1.0.3 +reload@1.1.1 +retry@1.0.1 +routepolicy@1.0.2 +session@1.0.4 +spacebars-compiler@1.0.3 +spacebars@1.0.3 +templating@1.0.9 +tracker@1.0.3 +ui@1.0.4 +underscore@1.0.1 +url@1.0.2 +webapp-hashing@1.0.1 +webapp@1.1.4 diff --git a/tests/no_gagarin/no_gagarin.html b/tests/no_gagarin/no_gagarin.html new file mode 100644 index 0000000..95c7e85 --- /dev/null +++ b/tests/no_gagarin/no_gagarin.html @@ -0,0 +1,14 @@ + + no_gagarin + + + +

Welcome to Meteor!

+ + {{> hello}} + + + diff --git a/tests/no_gagarin/no_gagarin.js b/tests/no_gagarin/no_gagarin.js new file mode 100644 index 0000000..f8bee6b --- /dev/null +++ b/tests/no_gagarin/no_gagarin.js @@ -0,0 +1,23 @@ +if (Meteor.isClient) { + // counter starts at 0 + Session.setDefault("counter", 0); + + Template.hello.helpers({ + counter: function () { + return Session.get("counter"); + } + }); + + Template.hello.events({ + 'click button': function () { + // increment the counter when button is clicked + Session.set("counter", Session.get("counter") + 1); + } + }); +} + +if (Meteor.isServer) { + Meteor.startup(function () { + // code to run on server at startup + }); +} diff --git a/tests/specs/exceptions.js b/tests/specs/exceptions.js index abdccac..765876d 100644 --- a/tests/specs/exceptions.js +++ b/tests/specs/exceptions.js @@ -30,6 +30,58 @@ describe('Reporting Exceptions', function () { }); + describe('Given gagarin is not installed,', function () { + + // TODO: check if the process is properly killed + + this.timeout(20000); + + var message = ""; + + var server = new Meteor({ + pathToApp: path.resolve(__dirname, '..', 'no_gagarin') + }); + + it('should throw an error', function () { + return server + .start() + .expectError(function (err) { + message = err.message; + }); + }); + + it('the error should contain useful information', function () { + expect(message).to.contain("anti:gagarin"); + }); + + }); + + describe('Given gagarin is in incompatible version,', function () { + + // TODO: check if the process is properly killed + + this.timeout(20000); + + var message = ""; + + var server = new Meteor({ + pathToApp: path.resolve(__dirname, '..', 'incompatible') + }); + + it('should throw an error', function () { + return server + .start() + .expectError(function (err) { + message = err.message; + }); + }); + + it('the error should contain useful information', function () { + expect(message).to.contain("please update"); + }); + + }); + describe('Given the app is properly built,', function () { // SERVER SIDE ERRORS From ece47492adbf538a286bb08b42778bae04725e6c Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sat, 20 Dec 2014 16:36:48 +0100 Subject: [PATCH 59/64] Cleaning up --- bin/gagarin | 11 ++--- lib/gagarin.js | 108 ++++++++++++++++++++----------------------------- 2 files changed, 49 insertions(+), 70 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index 1eb37a5..f5e5423 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -7,10 +7,6 @@ var fs = require('fs'); //TODO check why we can simply pass parseInt as argument ? -var intParse = function(v){ - return parseInt(v); -}; - program .version(require('../package.json').version) .usage('[debug] [options] [files]') @@ -34,7 +30,7 @@ program .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') - .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]',intParse, 2000) + .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]', intParse, 2000) .option('-p, --path-to-app ', 'path to a meteor application', path.resolve('.')) .option('-r, --remote-server ', 'run tests on a remote server') @@ -73,3 +69,8 @@ gagarin.run(function (failedCount) { } process.exit(0); }); + +function intParse(v) { + return parseInt(v); +}; + diff --git a/lib/gagarin.js b/lib/gagarin.js index c48a489..28ddfbc 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -9,7 +9,7 @@ var Mocha = require('mocha'); var chalk = require('chalk'); var path = require('path'); var util = require('util'); -var fs = require('fs'); + var BuildAsPromise = Meteor.BuildAsPromise; module.exports = Gagarin; @@ -44,8 +44,6 @@ util.inherits(Gagarin, Mocha); * * @param {Function} callback */ - - Gagarin.prototype.run = function (callback) { "use strict"; @@ -55,74 +53,54 @@ Gagarin.prototype.run = function (callback) { var verbose = buildOnly || (this.options.verbose !== undefined ? !!this.options.verbose : false); var self = this; - var checkVersionsComplatibility = function(){ - fs.readFile(pathToApp+'/.meteor/versions','utf-8', function read(err, data) { - var meteorPackageVersion = data.match(/anti:gagarin@(.*)/)[1]; - if(self.options._version != meteorPackageVersion){ - process.stdout.write( - chalk.red('Versions of node and meteor gagarin packages are not compatible please update \n') - ); - process.kill(); - }else{ - run(); - } - }); - }; + process.stdout.write('\n'); - var run = function(){ - process.stdout.write('\n'); + var title = (skipBuild ? 'skipped ' : '') + 'building app => ' + pathToApp; - var title = (skipBuild ? 'skipped ' : '') + 'building app => ' + pathToApp; + var counter = 0; + var spinner = '/-\\|'; + var handle = !verbose && setInterval(function () { + var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); + process.stdout.write( + chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') + ); + }, 100); - var counter = 0; - var spinner = '/-\\|'; - var handle = !verbose && setInterval(function () { - var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); - process.stdout.write( - chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') - ); - }, 100); + if (verbose) { + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\n\n')); + } - if (verbose) { - process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\n\n')); - } + BuildAsPromise({ - BuildAsPromise({ - - pathToApp : pathToApp, - skipBuild : skipBuild, - verbose : verbose, - - }).then(function () { - - if (!verbose) { - clearInterval(handle); - process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); - } - - if (buildOnly) { - process.stdout.write(chalk.green('\n done ...\n\n')); - callback(0); - } else { - Mocha.prototype.run.call(self, callback); - } - - - }, function (err) { - // clear the loading spinner - process.stdout.write(new Array(title.length + 12).join(' ')); - clearInterval(handle); - throw err; - }) - .catch(function (err) { - // make sure the error passes through promise - setTimeout(function () { - throw err; - }); - }); - } + pathToApp : pathToApp, + skipBuild : skipBuild, + verbose : verbose, + + }).then(function () { + + if (!verbose) { + clearInterval(handle); + process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); + } + if (buildOnly) { + process.stdout.write(chalk.green('\n done ...\n\n')); + callback(0); + } else { + Mocha.prototype.run.call(self, callback); + } - checkVersionsComplatibility(); + }, function (err) { + // clear the loading spinner + process.stdout.write(new Array(title.length + 12).join(' ')); + clearInterval(handle); + throw err; + }) + .catch(function (err) { + // make sure the error passes through promise + setTimeout(function () { + throw err; + }); + }); }; From 3f0c8bbdfb5674141423f3da2a5bba2db5383d7c Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sat, 20 Dec 2014 16:45:22 +0100 Subject: [PATCH 60/64] More robust closures --- backdoor.js | 40 ++++++++++++++++++------- lib/browserPromiseChain.js | 60 +++++++++++++++++++++++--------------- tests/specs/closures.js | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 34 deletions(-) diff --git a/backdoor.js b/backdoor.js index 80ab4a7..b51d1fe 100644 --- a/backdoor.js +++ b/backdoor.js @@ -74,12 +74,16 @@ if (Gagarin.isActive) { " };", " }", " };", - " };" - ); - - chunks.push( - " (" + code + ")(", - " " + args.join(', ') + ");", + " };", + " try {", + " (" + code + ")(", + " " + args.join(', ') + ");", + " } catch ($) {", + " arguments[arguments.length-1]({", + " error : $.message,", + " closure : { " + keys + " }", + " });", + " }", "}" ); @@ -162,7 +166,8 @@ if (Gagarin.isActive) { var feedback; try { feedback = context.value.apply(null, values(closure)); - if (feedback.value) { + + if (feedback.value || feedback.error) { resolve(feedback); } @@ -211,7 +216,22 @@ function wrapSourceCode(code, args, closure) { ); chunks.push( - " return (function ($) {", + " try {", + " return (function ($) {", + " return {", + " closure: {" + ); + + Object.keys(closure).forEach(function (key) { + chunks.push(" " + stringify(key) + ": " + key + ","); + }); + + chunks.push( + " },", + " value: $,", + " };", + " })( (" + code + ")(" + args.map(stringify).join(',') + ") );", + " } catch (err) {", " return {", " closure: {" ); @@ -222,9 +242,9 @@ function wrapSourceCode(code, args, closure) { chunks.push( " },", - " value: $,", + " error: err.message", " };", - " })( (" + code + ")(" + args.map(stringify).join(',') + ") );", + " }", "}" ); diff --git a/lib/browserPromiseChain.js b/lib/browserPromiseChain.js index a09b59b..3c533fe 100644 --- a/lib/browserPromiseChain.js +++ b/lib/browserPromiseChain.js @@ -185,16 +185,11 @@ BrowserPromiseChain.prototype.execute = function (code, args) { throw new Error('`args` has to be an array'); } - code = codeToString(code); - - eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); - - return self.applyWebDriver('execute', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; var cb = myArgs[myArgs.length-1]; - code = wrapSourceCode(code, args, closure); + code = wrapSourceCode(codeToString(code), args, closure); operand.browser.execute("return (" + code + ").apply(null, arguments)", values(closure), feedbackProcessor(operand.closure.bind(operand), cb)); @@ -223,10 +218,6 @@ BrowserPromiseChain.prototype.promise = function (code, args) { throw new Error('`args` has to be an array'); } - code = codeToString(code); - - eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); - // we could set this 5000 globally, right? return self.callWebDriver('setAsyncScriptTimeout', self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { @@ -258,11 +249,16 @@ BrowserPromiseChain.prototype.promise = function (code, args) { " };", " }", " };", - " };" - ); - chunks.push( - " (" + code + ")(", - " " + args.join(', ') + ");", + " };", + " try {", + " (" + codeToString(code) + ")(", + " " + args.join(', ') + ");", + " } catch ($) {", + " arguments[arguments.length-1]({", + " error : $.message,", + " closure : { " + keys + " }", + " });", + " }", "}" ); @@ -302,10 +298,6 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { var self = this; - code = codeToString(code); - - eval( '(' + code + ').apply(null,[' + args.toString() + '])' ); - return self.callWebDriver('setAsyncScriptTimeout', 2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) { var closure = operand.closure ? operand.closure() : {}; @@ -331,7 +323,7 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) { ' (function test() {', ' var value;', ' try {', - ' value = (' + code.toString() + ')(' + args.join(', ') + ');', + ' value = (' + codeToString(code) + ')(' + args.join(', ') + ');', ' if (value) {', ' window.clearTimeout(handle2);', ' cb({ value: value, closure: {' + keys + '} });', @@ -387,12 +379,17 @@ function stringify(value) { function codeToString(code) { "use strict"; + var test; + if (typeof code === 'string' && !/^function\s+\(/.test(code)) { - return 'function () {'+code +'}' + '\n}'; + return 'function () {\n' + code + '\n}'; } if (typeof code === 'function') { return code.toString(); } + + eval("test = " + code); // XXX it may throw on syntax error + return code; } @@ -423,7 +420,22 @@ function wrapSourceCode(code, args, closure) { //addSyncChunks(chunks, closure, accessor); chunks.push( - " return (function (value) {", + " try {", + " return (function ($) {", + " return {", + " closure: {" + ); + + Object.keys(closure).forEach(function (key) { + chunks.push(" " + stringify(key) + ": " + key + ","); + }); + + chunks.push( + " },", + " value: $,", + " };", + " })( (" + code + ")(" + args.map(stringify).join(',') + ") );", + " } catch (err) {", " return {", " closure: {" ); @@ -434,9 +446,9 @@ function wrapSourceCode(code, args, closure) { chunks.push( " },", - " value: value,", + " error: err.message", " };", - " })( (" + code + ")(" + args.map(stringify).join(',') + ") );", + " }", "}" ); diff --git a/tests/specs/closures.js b/tests/specs/closures.js index db153f4..7154c6c 100644 --- a/tests/specs/closures.js +++ b/tests/specs/closures.js @@ -30,6 +30,10 @@ describe('Closures', function () { describe('When using server.execute', function () { + beforeEach(function () { + b = Math.random().toString(); + }); + it('should be able to access a closure variable', function () { return server.execute(function () { return a; @@ -46,6 +50,17 @@ describe('Closures', function () { }); }); + it('even if the code throws', function () { + return server.execute(function () { + a = b; + throw new Error('another fake error') + }).expectError(function (err) { + expect(err.message).to.contain('another fake err'); + }).then(function (value) { + expect(a).to.equal(b); + }); + }); + it.skip('should be able to update closure with sync routine', function () { return server.execute(function () { var value = Math.random(); @@ -76,6 +91,7 @@ describe('Closures', function () { beforeEach(function () { b = 10; + c = Math.random().toString(); }); it('should be able to access a closure variable', function () { @@ -108,6 +124,17 @@ describe('Closures', function () { }); }); + it('and if the promise code throws', function () { + return server.promise(function () { + a = c; + throw new Error('another fake error') + }).expectError(function (err) { + expect(err.message).to.contain('another fake err'); + }).then(function (value) { + expect(a).to.equal(c); + }); + }); + it('should be able to alter a closure variable right after calling "resolve"', function () { return server.promise(function (resolve) { setTimeout(function () { @@ -222,6 +249,10 @@ describe('Closures', function () { var client = browser(server.location); + beforeEach(function () { + b = Math.random().toString(); + }); + describe('Value persitance', function () { it('zero value should not be interpreted as undefined', function () { @@ -252,6 +283,17 @@ describe('Closures', function () { }); }); + it('even if the code throws', function () { + return client.execute(function () { + a = b; + throw new Error('another fake error') + }).expectError(function (err) { + expect(err.message).to.contain('another fake err'); + }).then(function (value) { + expect(a).to.equal(b); + }); + }); + it.skip('should be able to update closure with sync routine', function () { return client.execute(function () { var value = Math.random(); @@ -282,6 +324,7 @@ describe('Closures', function () { beforeEach(function () { b = 10; + c = Math.random().toString(); }); it('should be able to access a closure variable', function () { @@ -317,6 +360,18 @@ describe('Closures', function () { }); }); + it('and if the promise code throws', function () { + return client.promise(function () { + b = c; + throw new Error('another fake error') + }).expectError(function (err) { + expect(err.message).to.contain('another fake err'); + }).then(function (value) { + expect(b).to.equal(c); + }); + }); + + it('should be able to alter a closure variable right after calling "resolve"', function () { return client.promise(function (resolve) { setTimeout(function () { From 4a6a41c28629cb03368e6b21229618b4651713dc Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sat, 20 Dec 2014 22:26:36 +0100 Subject: [PATCH 61/64] Now using pty to expose the output from build --- bin/gagarin | 3 ++- lib/build.js | 17 ++++++----------- lib/gagarin.js | 14 +++++++++----- package.json | 3 ++- test.js | 2 ++ tests/specs/exceptions.js | 2 +- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/bin/gagarin b/bin/gagarin index f5e5423..16f3484 100755 --- a/bin/gagarin +++ b/bin/gagarin @@ -30,9 +30,10 @@ program .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) .option('-w, --webdriver ', 'webdriver url [default: http://127.0.0.1:9515]', 'http://127.0.0.1:9515') .option('-M, --dont-wait-for-meteor', 'do not wait until meteor is loaded') - .option('-m, --meteor-load-timeout ', 'meteor load timeout [2000]', intParse, 2000) + .option('-l, --meteor-load-timeout ', 'meteor load timeout [2000]', intParse, 2000) .option('-p, --path-to-app ', 'path to a meteor application', path.resolve('.')) .option('-r, --remote-server ', 'run tests on a remote server') + .option('-m, --mute-build', 'do not show build logs', false) program.name = 'gagarin'; diff --git a/lib/build.js b/lib/build.js index 61d1a33..4f98c8d 100644 --- a/lib/build.js +++ b/lib/build.js @@ -6,6 +6,7 @@ var tools = require('./tools'); var path = require('path'); var fs = require('fs'); var version = require('../package.json').version; +var pty = require('pty.js'); var myBuildPromises = {}; @@ -78,19 +79,21 @@ function BuildPromise(options) { var pathToApp = options.pathToApp || path.resolve('.'); var mongoUrl = options.mongoUrl || "http://localhost:27017"; - var timeout = options.timeout || 60000; + var timeout = options.timeout || 120000; var verbose = options.verbose !== undefined ? !!options.verbose : false; var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js'); var env = Object.create(process.env); var port = 4000 + Math.floor(Math.random() * 1000); + var spawnMe = verbose ? pty.spawn : spawn; + // TODO: in the end, drop this database env.MONGO_URL = mongoUrl + '/' + 'gagarin_build'; return new Promise(function (resolve, reject) { - var meteor = spawn('meteor', [ + var meteor = spawnMe('meteor', [ '--production', '--port', port ], { cwd: pathToApp, env: env }); @@ -197,15 +200,7 @@ function BuildPromise(options) { if (!verbose) { return; } - process.stdout.write(data.toString().split('\n').map(function (line) { - if (line === '\r') { - return; - } - if (line.length === 0) { - return ""; - } - return chalk.green('<---> ') + line; - }).join('\n')); + process.stdout.write(chalk.gray(chalk.stripColor(data))); } } // BuildPromise diff --git a/lib/gagarin.js b/lib/gagarin.js index 28ddfbc..0b04793 100644 --- a/lib/gagarin.js +++ b/lib/gagarin.js @@ -50,6 +50,7 @@ Gagarin.prototype.run = function (callback) { var pathToApp = this.options.pathToApp || path.resolve('.'); var skipBuild = !!this.options.skipBuild; var buildOnly = !!this.options.buildOnly; + var muteBuild = !!this.options.muteBuild; var verbose = buildOnly || (this.options.verbose !== undefined ? !!this.options.verbose : false); var self = this; @@ -59,14 +60,14 @@ Gagarin.prototype.run = function (callback) { var counter = 0; var spinner = '/-\\|'; - var handle = !verbose && setInterval(function () { + var handle = muteBuild && setInterval(function () { var animated = chalk.yellow(spinner.charAt(counter++ % spinner.length)); process.stdout.write( chalk.yellow(' -') + animated + chalk.yellow('- ') + title + chalk.yellow(' -') + animated + chalk.yellow('-\r') ); }, 100); - if (verbose) { + if (!muteBuild) { process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\n\n')); } @@ -74,17 +75,20 @@ Gagarin.prototype.run = function (callback) { pathToApp : pathToApp, skipBuild : skipBuild, - verbose : verbose, + verbose : !muteBuild, }).then(function () { - if (!verbose) { + if (muteBuild) { clearInterval(handle); process.stdout.write(chalk.green(' --- ') + chalk.gray(title) + chalk.green(' ---\r')); } + if (!muteBuild) { + process.stdout.write(chalk.green('\n done building ...\n\n')); + } + if (buildOnly) { - process.stdout.write(chalk.green('\n done ...\n\n')); callback(0); } else { Mocha.prototype.run.call(self, callback); diff --git a/package.json b/package.json index 1b3b3a1..c1f76a4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "homepage": "https://github.com/anticoders/gagarin", "devDependencies": {}, "dependencies": { - "portscanner" : "1.0.0", "chai": "^1.10.0", "chalk": "^0.5.1", "commander": "^2.5.0", @@ -39,6 +38,8 @@ "mkdirp": "^0.5.0", "mocha": "^1.20.1", "mongodb": "^1.4.7", + "portscanner": "1.0.0", + "pty.js": "git://github.com/chjj/pty.js", "wd": "^0.3.11" } } diff --git a/test.js b/test.js index 521654b..0c223c1 100755 --- a/test.js +++ b/test.js @@ -13,6 +13,7 @@ program .option('-B, --skip-build', 'do not build, just run the tests') .option('-o, --build-only', 'just build, do not run the tests') .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) + .option('-m, --mute-build', 'do not show build logs', false) program.parse(process.argv); @@ -24,6 +25,7 @@ var gagarin = new Gagarin({ grep : program.grep, skipBuild : program.skipBuild, buildOnly : program.buildOnly, + muteBuild : program.muteBuild, verbose : program.verbose, }); diff --git a/tests/specs/exceptions.js b/tests/specs/exceptions.js index 765876d..c90affa 100644 --- a/tests/specs/exceptions.js +++ b/tests/specs/exceptions.js @@ -4,7 +4,7 @@ var path = require('path'); describe('Reporting Exceptions', function () { - describe('Given the app does not built properly,', function () { + describe('Given the app does not build properly,', function () { // TODO: check if the process is properly killed From 8a491ee927412567306f65ac172863fb5381c3d5 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sat, 20 Dec 2014 22:31:20 +0100 Subject: [PATCH 62/64] Updated tests applications --- test.js | 3 +- tests/build_error/.meteor/release | 2 +- tests/build_error/.meteor/versions | 102 ++++++++++++------------ tests/example/.meteor/release | 2 +- tests/example/.meteor/versions | 118 ++++++++++++++-------------- tests/incompatible/.meteor/release | 2 +- tests/incompatible/.meteor/versions | 102 ++++++++++++------------ tests/no_gagarin/.meteor/release | 2 +- tests/no_gagarin/.meteor/versions | 102 ++++++++++++------------ 9 files changed, 213 insertions(+), 222 deletions(-) diff --git a/test.js b/test.js index 0c223c1..e4e32c1 100755 --- a/test.js +++ b/test.js @@ -13,7 +13,6 @@ program .option('-B, --skip-build', 'do not build, just run the tests') .option('-o, --build-only', 'just build, do not run the tests') .option('-V, --verbose', 'run with verbose mode with logs from client/server', false) - .option('-m, --mute-build', 'do not show build logs', false) program.parse(process.argv); @@ -25,7 +24,7 @@ var gagarin = new Gagarin({ grep : program.grep, skipBuild : program.skipBuild, buildOnly : program.buildOnly, - muteBuild : program.muteBuild, + muteBuild : !program.verbose, verbose : program.verbose, }); diff --git a/tests/build_error/.meteor/release b/tests/build_error/.meteor/release index f1b6255..b3841a0 100644 --- a/tests/build_error/.meteor/release +++ b/tests/build_error/.meteor/release @@ -1 +1 @@ -METEOR@1.0.1 +METEOR@1.0.2 diff --git a/tests/build_error/.meteor/versions b/tests/build_error/.meteor/versions index 8b4d7ae..de157ef 100644 --- a/tests/build_error/.meteor/versions +++ b/tests/build_error/.meteor/versions @@ -1,53 +1,51 @@ anti:gagarin@0.3.0-pre9 -application-configuration@1.0.3 -autopublish@1.0.1 -autoupdate@1.1.3 -base64@1.0.1 -binary-heap@1.0.1 -blaze-tools@1.0.1 -blaze@2.0.3 -boilerplate-generator@1.0.1 -callback-hook@1.0.1 -check@1.0.2 -ctl-helper@1.0.4 -ctl@1.0.2 -ddp@1.0.12 -deps@1.0.5 -ejson@1.0.4 -fastclick@1.0.1 -follower-livedata@1.0.2 -geojson-utils@1.0.1 -html-tools@1.0.2 -htmljs@1.0.2 -http@1.0.8 -id-map@1.0.1 -insecure@1.0.1 -jquery@1.0.1 -json@1.0.1 -launch-screen@1.0.0 -livedata@1.0.11 -logging@1.0.5 -meteor-platform@1.2.0 -meteor@1.1.3 -minifiers@1.1.2 -minimongo@1.0.5 -mobile-status-bar@1.0.1 -mongo@1.0.9 -observe-sequence@1.0.3 -ordered-dict@1.0.1 -random@1.0.1 -reactive-dict@1.0.4 -reactive-var@1.0.3 -reload@1.1.1 -retry@1.0.1 -routepolicy@1.0.2 -session@1.0.4 -spacebars-compiler@1.0.3 -spacebars@1.0.3 -templating@1.0.9 -tracker@1.0.3 -ui@1.0.4 -underscore@1.0.1 -url@1.0.2 -webapp-hashing@1.0.1 -webapp@1.1.4 +application-configuration@1.0.4 +autopublish@1.0.2 +autoupdate@1.1.4 +base64@1.0.2 +binary-heap@1.0.2 +blaze@2.0.4 +blaze-tools@1.0.2 +boilerplate-generator@1.0.2 +callback-hook@1.0.2 +check@1.0.3 +ddp@1.0.13 +deps@1.0.6 +ejson@1.0.5 +fastclick@1.0.2 +follower-livedata@1.0.3 +geojson-utils@1.0.2 +html-tools@1.0.3 +htmljs@1.0.3 +http@1.0.9 +id-map@1.0.2 +insecure@1.0.2 +jquery@1.0.2 +json@1.0.2 +launch-screen@1.0.1 +livedata@1.0.12 +logging@1.0.6 +meteor@1.1.4 +meteor-platform@1.2.1 +minifiers@1.1.3 +minimongo@1.0.6 +mobile-status-bar@1.0.2 +mongo@1.0.10 +observe-sequence@1.0.4 +ordered-dict@1.0.2 +random@1.0.2 +reactive-dict@1.0.5 +reactive-var@1.0.4 +reload@1.1.2 +retry@1.0.2 +routepolicy@1.0.3 +session@1.0.5 +spacebars@1.0.4 +spacebars-compiler@1.0.4 +templating@1.0.10 +tracker@1.0.4 +ui@1.0.5 +underscore@1.0.2 +url@1.0.3 +webapp@1.1.5 +webapp-hashing@1.0.2 diff --git a/tests/example/.meteor/release b/tests/example/.meteor/release index f1b6255..b3841a0 100644 --- a/tests/example/.meteor/release +++ b/tests/example/.meteor/release @@ -1 +1 @@ -METEOR@1.0.1 +METEOR@1.0.2 diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index b20d1a2..dc2811c 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,63 +1,61 @@ -accounts-base@1.1.2 -accounts-password@1.0.4 +accounts-base@1.1.3 +accounts-password@1.0.5 anti:gagarin@0.3.0-pre9 -application-configuration@1.0.3 -autopublish@1.0.1 -autoupdate@1.1.3 -base64@1.0.1 -binary-heap@1.0.1 -blaze-tools@1.0.1 -blaze@2.0.3 -boilerplate-generator@1.0.1 -callback-hook@1.0.1 -check@1.0.2 -ctl-helper@1.0.4 -ctl@1.0.2 -ddp@1.0.12 -deps@1.0.5 -ejson@1.0.4 -email@1.0.4 -fastclick@1.0.1 -follower-livedata@1.0.2 -geojson-utils@1.0.1 -html-tools@1.0.2 -htmljs@1.0.2 -http@1.0.8 -id-map@1.0.1 -insecure@1.0.1 -jquery@1.0.1 -json@1.0.1 -launch-screen@1.0.0 -livedata@1.0.11 -localstorage@1.0.1 -logging@1.0.5 -meteor-platform@1.2.0 -meteor@1.1.3 -minifiers@1.1.2 -minimongo@1.0.5 -mobile-status-bar@1.0.1 -mongo@1.0.9 +application-configuration@1.0.4 +autopublish@1.0.2 +autoupdate@1.1.4 +base64@1.0.2 +binary-heap@1.0.2 +blaze@2.0.4 +blaze-tools@1.0.2 +boilerplate-generator@1.0.2 +callback-hook@1.0.2 +check@1.0.3 +ddp@1.0.13 +deps@1.0.6 +ejson@1.0.5 +email@1.0.5 +fastclick@1.0.2 +follower-livedata@1.0.3 +geojson-utils@1.0.2 +html-tools@1.0.3 +htmljs@1.0.3 +http@1.0.9 +id-map@1.0.2 +insecure@1.0.2 +jquery@1.0.2 +json@1.0.2 +launch-screen@1.0.1 +livedata@1.0.12 +localstorage@1.0.2 +logging@1.0.6 +meteor@1.1.4 +meteor-platform@1.2.1 +minifiers@1.1.3 +minimongo@1.0.6 +mobile-status-bar@1.0.2 +mongo@1.0.10 mystor:device-detection@0.2.0 npm-bcrypt@0.7.7 -observe-sequence@1.0.3 -ordered-dict@1.0.1 -random@1.0.1 -reactive-dict@1.0.4 -reactive-var@1.0.3 -reload@1.1.1 -retry@1.0.1 -routepolicy@1.0.2 -service-configuration@1.0.2 -session@1.0.4 -sha@1.0.1 -spacebars-compiler@1.0.3 -spacebars@1.0.3 -srp@1.0.1 -standard-app-packages@1.0.3 -templating@1.0.9 -tracker@1.0.3 -ui@1.0.4 -underscore@1.0.1 -url@1.0.2 -webapp-hashing@1.0.1 -webapp@1.1.4 +observe-sequence@1.0.4 +ordered-dict@1.0.2 +random@1.0.2 +reactive-dict@1.0.5 +reactive-var@1.0.4 +reload@1.1.2 +retry@1.0.2 +routepolicy@1.0.3 +service-configuration@1.0.3 +session@1.0.5 +sha@1.0.2 +spacebars@1.0.4 +spacebars-compiler@1.0.4 +srp@1.0.2 +standard-app-packages@1.0.4 +templating@1.0.10 +tracker@1.0.4 +ui@1.0.5 +underscore@1.0.2 +url@1.0.3 +webapp@1.1.5 +webapp-hashing@1.0.2 diff --git a/tests/incompatible/.meteor/release b/tests/incompatible/.meteor/release index f1b6255..b3841a0 100644 --- a/tests/incompatible/.meteor/release +++ b/tests/incompatible/.meteor/release @@ -1 +1 @@ -METEOR@1.0.1 +METEOR@1.0.2 diff --git a/tests/incompatible/.meteor/versions b/tests/incompatible/.meteor/versions index f5adf2c..878c4d0 100644 --- a/tests/incompatible/.meteor/versions +++ b/tests/incompatible/.meteor/versions @@ -1,54 +1,52 @@ anti:gagarin@0.2.2 -application-configuration@1.0.3 -autopublish@1.0.1 -autoupdate@1.1.3 -base64@1.0.1 -binary-heap@1.0.1 -blaze-tools@1.0.1 -blaze@2.0.3 -boilerplate-generator@1.0.1 -callback-hook@1.0.1 -check@1.0.2 -ctl-helper@1.0.4 -ctl@1.0.2 -ddp@1.0.12 -deps@1.0.5 -ejson@1.0.4 -fastclick@1.0.1 -follower-livedata@1.0.2 -geojson-utils@1.0.1 -html-tools@1.0.2 -htmljs@1.0.2 -http@1.0.8 -id-map@1.0.1 -insecure@1.0.1 -jquery@1.0.1 -json@1.0.1 -launch-screen@1.0.0 -livedata@1.0.11 -logging@1.0.5 -meteor-platform@1.2.0 -meteor@1.1.3 -minifiers@1.1.2 -minimongo@1.0.5 -mobile-status-bar@1.0.1 -mongo@1.0.9 +application-configuration@1.0.4 +autopublish@1.0.2 +autoupdate@1.1.4 +base64@1.0.2 +binary-heap@1.0.2 +blaze@2.0.4 +blaze-tools@1.0.2 +boilerplate-generator@1.0.2 +callback-hook@1.0.2 +check@1.0.3 +ddp@1.0.13 +deps@1.0.6 +ejson@1.0.5 +fastclick@1.0.2 +follower-livedata@1.0.3 +geojson-utils@1.0.2 +html-tools@1.0.3 +htmljs@1.0.3 +http@1.0.9 +id-map@1.0.2 +insecure@1.0.2 +jquery@1.0.2 +json@1.0.2 +launch-screen@1.0.1 +livedata@1.0.12 +logging@1.0.6 +meteor@1.1.4 +meteor-platform@1.2.1 +minifiers@1.1.3 +minimongo@1.0.6 +mobile-status-bar@1.0.2 +mongo@1.0.10 mrt:altimeter@0.0.2 -observe-sequence@1.0.3 -ordered-dict@1.0.1 -random@1.0.1 -reactive-dict@1.0.4 -reactive-var@1.0.3 -reload@1.1.1 -retry@1.0.1 -routepolicy@1.0.2 -session@1.0.4 -spacebars-compiler@1.0.3 -spacebars@1.0.3 -templating@1.0.9 -tracker@1.0.3 -ui@1.0.4 -underscore@1.0.1 -url@1.0.2 -webapp-hashing@1.0.1 -webapp@1.1.4 +observe-sequence@1.0.4 +ordered-dict@1.0.2 +random@1.0.2 +reactive-dict@1.0.5 +reactive-var@1.0.4 +reload@1.1.2 +retry@1.0.2 +routepolicy@1.0.3 +session@1.0.5 +spacebars@1.0.4 +spacebars-compiler@1.0.4 +templating@1.0.10 +tracker@1.0.4 +ui@1.0.5 +underscore@1.0.2 +url@1.0.3 +webapp@1.1.5 +webapp-hashing@1.0.2 diff --git a/tests/no_gagarin/.meteor/release b/tests/no_gagarin/.meteor/release index f1b6255..b3841a0 100644 --- a/tests/no_gagarin/.meteor/release +++ b/tests/no_gagarin/.meteor/release @@ -1 +1 @@ -METEOR@1.0.1 +METEOR@1.0.2 diff --git a/tests/no_gagarin/.meteor/versions b/tests/no_gagarin/.meteor/versions index 2c204e2..149aa49 100644 --- a/tests/no_gagarin/.meteor/versions +++ b/tests/no_gagarin/.meteor/versions @@ -1,52 +1,50 @@ -application-configuration@1.0.3 -autopublish@1.0.1 -autoupdate@1.1.3 -base64@1.0.1 -binary-heap@1.0.1 -blaze-tools@1.0.1 -blaze@2.0.3 -boilerplate-generator@1.0.1 -callback-hook@1.0.1 -check@1.0.2 -ctl-helper@1.0.4 -ctl@1.0.2 -ddp@1.0.12 -deps@1.0.5 -ejson@1.0.4 -fastclick@1.0.1 -follower-livedata@1.0.2 -geojson-utils@1.0.1 -html-tools@1.0.2 -htmljs@1.0.2 -http@1.0.8 -id-map@1.0.1 -insecure@1.0.1 -jquery@1.0.1 -json@1.0.1 -launch-screen@1.0.0 -livedata@1.0.11 -logging@1.0.5 -meteor-platform@1.2.0 -meteor@1.1.3 -minifiers@1.1.2 -minimongo@1.0.5 -mobile-status-bar@1.0.1 -mongo@1.0.9 -observe-sequence@1.0.3 -ordered-dict@1.0.1 -random@1.0.1 -reactive-dict@1.0.4 -reactive-var@1.0.3 -reload@1.1.1 -retry@1.0.1 -routepolicy@1.0.2 -session@1.0.4 -spacebars-compiler@1.0.3 -spacebars@1.0.3 -templating@1.0.9 -tracker@1.0.3 -ui@1.0.4 -underscore@1.0.1 -url@1.0.2 -webapp-hashing@1.0.1 -webapp@1.1.4 +application-configuration@1.0.4 +autopublish@1.0.2 +autoupdate@1.1.4 +base64@1.0.2 +binary-heap@1.0.2 +blaze@2.0.4 +blaze-tools@1.0.2 +boilerplate-generator@1.0.2 +callback-hook@1.0.2 +check@1.0.3 +ddp@1.0.13 +deps@1.0.6 +ejson@1.0.5 +fastclick@1.0.2 +follower-livedata@1.0.3 +geojson-utils@1.0.2 +html-tools@1.0.3 +htmljs@1.0.3 +http@1.0.9 +id-map@1.0.2 +insecure@1.0.2 +jquery@1.0.2 +json@1.0.2 +launch-screen@1.0.1 +livedata@1.0.12 +logging@1.0.6 +meteor@1.1.4 +meteor-platform@1.2.1 +minifiers@1.1.3 +minimongo@1.0.6 +mobile-status-bar@1.0.2 +mongo@1.0.10 +observe-sequence@1.0.4 +ordered-dict@1.0.2 +random@1.0.2 +reactive-dict@1.0.5 +reactive-var@1.0.4 +reload@1.1.2 +retry@1.0.2 +routepolicy@1.0.3 +session@1.0.5 +spacebars@1.0.4 +spacebars-compiler@1.0.4 +templating@1.0.10 +tracker@1.0.4 +ui@1.0.5 +underscore@1.0.2 +url@1.0.3 +webapp@1.1.5 +webapp-hashing@1.0.2 From d2f90c7c664123d8a61642b312f3af5b5112a5bb Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sun, 21 Dec 2014 14:54:49 +0100 Subject: [PATCH 63/64] Bumped package version --- package.js | 2 +- package.json | 2 +- tests/example/.meteor/versions | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.js b/package.js index 7f0345d..41137fd 100644 --- a/package.js +++ b/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Gagarin, a Meteor testing framework", name: "anti:gagarin", - version: "0.3.0-pre9", + version: "0.3.0", git: "https://github.com/anticoders/gagarin.git", }); diff --git a/package.json b/package.json index c1f76a4..f3c37fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gagarin", - "version": "0.3.0-pre9", + "version": "0.3.0", "description": "another testing framework for your meteor apps", "main": "gagarin.js", "repository": { diff --git a/tests/example/.meteor/versions b/tests/example/.meteor/versions index dc2811c..86a0f45 100644 --- a/tests/example/.meteor/versions +++ b/tests/example/.meteor/versions @@ -1,6 +1,6 @@ accounts-base@1.1.3 accounts-password@1.0.5 -anti:gagarin@0.3.0-pre9 +anti:gagarin@0.3.0 application-configuration@1.0.4 autopublish@1.0.2 autoupdate@1.1.4 From 84fb4aa1336779c5d6b7e96bdcee01bfc19ed1a7 Mon Sep 17 00:00:00 2001 From: Tomasz Lenarcik Date: Sun, 21 Dec 2014 15:06:07 +0100 Subject: [PATCH 64/64] Updated package version --- tests/build_error/.meteor/versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/build_error/.meteor/versions b/tests/build_error/.meteor/versions index de157ef..13d1aae 100644 --- a/tests/build_error/.meteor/versions +++ b/tests/build_error/.meteor/versions @@ -1,4 +1,4 @@ -anti:gagarin@0.3.0-pre9 +anti:gagarin@0.3.0 application-configuration@1.0.4 autopublish@1.0.2 autoupdate@1.1.4