Permalink
Browse files

ability to warp time and visit the future

  • Loading branch information...
1 parent 2c56dfc commit f479d3ba19296f3404f90126cc75173f758b572b @jlipps jlipps committed Jan 30, 2013
Showing with 185 additions and 18 deletions.
  1. +1 −1 app/appium.js
  2. +40 −0 app/helpers.js
  3. +6 −1 app/ios.js
  4. +4 −0 app/parser.js
  5. +1 −0 grunt-helpers.js
  6. +1 −1 logger.js
  7. +33 −15 server.js
  8. +1 −0 test/functional/appium/appiumutils.js
  9. +98 −0 warp.js
View
@@ -153,7 +153,7 @@ Appium.prototype.invoke = function() {
// in future all the blackberries go here.
this.active = 'iOS';
if (typeof this.devices[this.active] === 'undefined') {
- this.devices[this.active] = ios(this.rest, this.args.app, this.args.udid, this.args.verbose, this.args.remove, this.args.logFile);
+ this.devices[this.active] = ios(this.rest, this.args.app, this.args.udid, this.args.verbose, this.args.remove, this.args.warp);
}
this.device = this.devices[this.active];
View
@@ -5,6 +5,7 @@ var logger = require('../logger').get('appium')
, request = require('request')
, path = require('path')
, exec = require('child_process').exec
+ , inTimeWarp = false
, temp = require('temp');
exports.downloadFile = function(fileUrl, cb) {
@@ -91,3 +92,42 @@ exports.delay = function(secs) {
do { curDate = new Date(); }
while(curDate-date < (secs * 1000.0));
};
+
+var pad0 = function(x) {
+ if (x.toString().length == 1) {
+ x = '0' + x;
+ }
+ return x;
+};
+
+exports.timeWarp = function(period, warp) {
+ logger.info("Starting time warp");
+ period = typeof period === "undefined" ? 100 : period;
+ warp = typeof warp === "undefined" ? 1000 : warp;
+ var numHops = 0;
+ var makeJump = function() {
+ if (inTimeWarp) {
+ var curMs = Date.now();
+ var newDate = new Date(curMs + warp);
+ var dateStr = [pad0(newDate.getHours()),
+ pad0(newDate.getMinutes()),
+ '.', pad0(newDate.getSeconds())]
+ .join('');
+ exec('sudo /bin/date ' + dateStr, function(err, stdout, stderr) {
+ numHops++;
+ setTimeout(makeJump, period);
+ });
+ } else {
+ var realTime = period * numHops / 1000;
+ var fakeTime = (warp * numHops / 1000) + realTime;
+ var info = "Moved forward " + fakeTime + " secs in " + realTime + " actual seconds";
+ logger.info("Stopping time warp: " + info);
+ }
+ };
+ inTimeWarp = true;
+ makeJump();
+};
+
+exports.stopTimeWarp = function() {
+ inTimeWarp = false;
+};
View
@@ -7,6 +7,8 @@ var path = require('path')
, instruments = require('../instruments/instruments')
, delay = require("./helpers.js").delay
, uuid = require('uuid-js')
+ , timeWarp = require('../warp.js').timeWarp
+ , stopTimeWarp = require('../warp.js').stopTimeWarp
, status = require("./uiauto/lib/status");
var UnknownError = function(message) {
@@ -19,11 +21,12 @@ var ProtocolError = function(message) {
this.name = "ProtocolError";
};
-var IOS = function(rest, app, udid, verbose, removeTraceDir) {
+var IOS = function(rest, app, udid, verbose, removeTraceDir, warp) {
this.rest = rest;
this.app = app;
this.udid = udid;
this.verbose = verbose;
+ this.warp = warp;
this.instruments = null;
this.queue = [];
this.progress = 0;
@@ -81,6 +84,7 @@ IOS.prototype.start = function(cb, onDie) {
};
if (this.instruments === null) {
+ timeWarp(50, 1000);
this.instruments = instruments(
this.app
, this.udid
@@ -95,6 +99,7 @@ IOS.prototype.start = function(cb, onDie) {
};
IOS.prototype.stop = function(cb) {
+ stopTimeWarp();
if (this.instruments === null) {
logger.info("Trying to stop instruments but it already exited");
// we're already stopped
View
@@ -40,5 +40,9 @@ module.exports = function() {
, { defaultValue: null, required: false, help: 'log to a file'
});
+ parser.addArgument([ '-w', '--warp' ]
+ , { defaultValue: false, required: false, help: 'use time warp to speed up test execution (WARNING, this modifies system clock, could be bad news bears!)'
+ });
+
return parser;
};
View
@@ -24,6 +24,7 @@ module.exports.startAppium = function(appName, verbose, readyCb, doneCb) {
, udid: null
, verbose: verbose
, port: 4723
+ , warp: false
, launch: app ? true : false
, log: path.resolve(__dirname, "appium.log")
, address: '127.0.0.1'
View
@@ -38,7 +38,7 @@ module.exports.setLogFile = function(logger, filename) {
, colorize: false
, level: 'debug'
, maxsize: 1000000
- , maxFiles: 4
+ , maxFiles: 1
, json: false
});
} catch (e) {
View
@@ -5,14 +5,30 @@ var http = require('http')
, logger = require('./logger').get('appium')
, appium = require('./app/appium')
, bodyParser = require('./middleware').parserWrap
+ , checkWarpDrive = require('./warp.js').checkWarpDrive
, parser = require('./app/parser');
+var doWarpCheck = function(wantWarp, cb) {
+ if (wantWarp) {
+ checkWarpDrive(function(hasWarp) {
+ if (hasWarp) {
+ cb();
+ } else {
+ process.exit(1);
+ }
+ });
+ } else {
+ cb();
+ }
+};
+
var main = function(args, readyCb, doneCb) {
var rest = express()
, server = http.createServer(rest);
if (typeof doneCb === "undefined") {
doneCb = function() {};
}
+
// in case we'll support blackberry at some point
args.device = 'iOS';
@@ -43,22 +59,24 @@ var main = function(args, readyCb, doneCb) {
// Hook up REST http interface
appiumServer.attachTo(rest);
- // Start the server either now or after pre-launching device
- var next = function(appiumServer) {
- server.listen(args.port, args.address, function() {
- var logMessage = "Appium REST http interface listener started on "+args.address+":"+args.port;
- logger.info(logMessage.cyan);
- });
- if (readyCb) {
- readyCb(appiumServer);
+ doWarpCheck(args.warp, function() {
+ // Start the server either now or after pre-launching device
+ var next = function(appiumServer) {
+ server.listen(args.port, args.address, function() {
+ var logMessage = "Appium REST http interface listener started on "+args.address+":"+args.port;
+ logger.info(logMessage.cyan);
+ });
+ if (readyCb) {
+ readyCb(appiumServer);
+ }
+ };
+ if (args.launch) {
+ logger.info("Starting Appium in pre-launch mode".cyan);
+ appiumServer.preLaunch(next);
+ } else {
+ next(appiumServer);
}
- };
- if (args.launch) {
- logger.info("Starting Appium in pre-launch mode".cyan);
- appiumServer.preLaunch(next);
- } else {
- next(appiumServer);
- }
+ });
server.on('close', doneCb);
return server;
@@ -19,6 +19,7 @@ describe("appiumutils", function() {
, verbose: false
, udid: null
, launch: false
+ , log: path.resolve(__dirname, "../../../appium.log")
, port: 4723
, address: '127.0.0.1'
, remove: true }
View
98 warp.js
@@ -0,0 +1,98 @@
+"use strict";
+
+var logger = require('./logger').get('appium')
+ , inTimeWarp = false
+ , timeWarpDoneCb = function() {}
+ , warpFactor = 1
+ , exec = require('child_process').exec;
+
+var pad0 = function(x) {
+ if (x.toString().length == 1) {
+ x = '0' + x;
+ }
+ return x;
+};
+
+var getDateStr = function(dateObj) {
+ return [pad0(dateObj.getHours()),
+ pad0(dateObj.getMinutes()),
+ '.',
+ pad0(dateObj.getSeconds())].join('');
+};
+
+exports.checkWarpDrive = function(cb) {
+ var dateStr = getDateStr(new Date());
+ exec('echo "\n" | sudo -S /bin/date ' + dateStr, function(err) {
+ if (err) {
+ logger.error("You can't access the warp drive; make sure you're the " +
+ "Captain or Scotty");
+ cb(false);
+ } else {
+ cb(true);
+ }
+ });
+};
+
+exports.timeWarp = function(period, warp, resetTime) {
+ resetTime = resetTime === "undefined" ? false : resetTime;
+ var warpStartTime = Date.now();
+ if (!inTimeWarp) {
+ var numHops = 0;
+ logger.info("Starting time warp");
+ period = typeof period === "undefined" ? 50 : period;
+ warp = typeof warp === "undefined" ? 1000 : warp;
+ warpFactor = (warp + period) / period;
+ var makeJump = function() {
+ var curMs, newDate, dateStr, realTime, fakeTime, info;
+ if (inTimeWarp) {
+ curMs = Date.now();
+ newDate = new Date(curMs + warp);
+ dateStr = getDateStr(newDate);
+ exec('sudo -S /bin/date ' + dateStr, function(err, stdout, stderr) {
+ if (err) {
+ logger.error("Could not set date, here's stderr:");
+ logger.error(stderr);
+ throw new Error("time warp failed, not enough power to make jump");
+ } else {
+ numHops++;
+ setTimeout(makeJump, period);
+ }
+ });
+ } else {
+ warpFactor = 1;
+ realTime = period * numHops / 1000;
+ fakeTime = (warp * numHops / 1000) + realTime;
+ info = "Moved forward " + fakeTime + " secs in " + realTime + " actual seconds. That's a warp factor of " + (fakeTime / realTime).toFixed(1) + "!";
+ logger.info("Stopping time warp: " + info);
+ if (resetTime) {
+ newDate = new Date(warpStartTime + ((period + 8) * numHops));
+ var newDateStr = getDateStr(newDate);
+ exec('sudo /bin/date ' + newDateStr, function() {
+ logger.info("Attempted to reset system time to something " +
+ "reasonable. Hope you enjoyed your trip to the future");
+ timeWarpDoneCb(period * numHops);
+ });
+ } else {
+ timeWarpDoneCb(period * numHops);
+ }
+ }
+ };
+ inTimeWarp = true;
+ makeJump();
+ } else {
+ logger.info("You asked for a time warp but we're already in the middle " +
+ "of a series of jumps...");
+ }
+};
+
+exports.stopTimeWarp = function(cb) {
+ inTimeWarp = false;
+ if (typeof cb === "function") {
+ timeWarpDoneCb = cb;
+ }
+};
+
+exports.setWarpAwareTimeout = function(fn, ms) {
+ ms *= warpFactor;
+ setTimeout(fn, ms);
+};

0 comments on commit f479d3b

Please sign in to comment.