diff --git a/lib/Local.js b/lib/Local.js index d976320..ce12b0f 100644 --- a/lib/Local.js +++ b/lib/Local.js @@ -1,4 +1,5 @@ var childProcess = require('child_process'), + fs = require('fs'), path = require('path'), running = require('is-running'), LocalBinary = require('./LocalBinary'), @@ -7,16 +8,17 @@ var childProcess = require('child_process'), function Local(){ this.pid = undefined; + this.retriesLeft = 5; this.key = process.env.BROWSERSTACK_ACCESS_KEY; this.logfile = path.join(process.cwd(), 'local.log'); this.opcode = 'start'; this.exitCallback; - this.userArgs = []; this.errorRegex = /\*\*\* Error: [^\r\n]*/i; this.doneRegex = /Press Ctrl-C to exit/i; this.start = function(options, callback){ + this.userArgs = []; var that = this; this.addArgs(options); @@ -29,7 +31,19 @@ function Local(){ that.opcode = 'start'; that.tunnel = childProcess.execFile(that.binaryPath, that.getBinaryArgs(), function(error, stdout, stderr){ - if(error) callback(new LocalError(error.toString())); + if(error) { + console.error('Error while trying to execute binary', error); + if(that.retriesLeft > 0) { + console.log('Retrying Binary Download. Retries Left', that.retriesLeft); + that.retriesLeft -= 1; + fs.unlinkSync(that.binaryPath); + delete(that.binaryPath); + that.start(options, callback); + return; + } else { + callback(new LocalError(error.toString())); + } + } var data = {}; if(stdout) @@ -206,6 +220,7 @@ function Local(){ } this.binary.binaryPath(conf, callback); } else { + console.log('BINARY PATH IS DEFINED'); callback(this.binaryPath); } }; diff --git a/lib/LocalBinary.js b/lib/LocalBinary.js index aa6a503..e075e51 100644 --- a/lib/LocalBinary.js +++ b/lib/LocalBinary.js @@ -22,16 +22,32 @@ function LocalBinary(){ this.httpPath = 'https://s3.amazonaws.com/browserStack/browserstack-local/BrowserStackLocal-linux-ia32'; } - this.download = function(conf, destParentDir, callback){ + this.retryBinaryDownload = function(conf, destParentDir, callback, retries, binaryPath) { + var that = this; + if(retries > 0) { + console.log('Retrying Download. Retries left', retries); + fs.stat(binaryPath, function(err) { + if(err == null) { + fs.unlinkSync(binaryPath); + } + that.download(conf, destParentDir, callback, retries - 1); + }); + } else { + console.error('Number of retries to download exceeded.'); + } + }; + + this.download = function(conf, destParentDir, callback, retries){ + var that = this; if(!this.checkPath(destParentDir)) fs.mkdirSync(destParentDir); var destBinaryName = (this.windows) ? 'BrowserStackLocal.exe' : 'BrowserStackLocal'; var binaryPath = path.join(destParentDir, destBinaryName); - var file = fs.createWriteStream(binaryPath); + var fileStream = fs.createWriteStream(binaryPath); var options = url.parse(this.httpPath); - if(conf.proxyHost && conf.proxyPort){ + if(conf.proxyHost && conf.proxyPort) { options.agent = new HttpsProxyAgent({ host: conf.proxyHost, port: conf.proxyPort @@ -39,12 +55,23 @@ function LocalBinary(){ } https.get(options, function (response) { - response.on('end', function () { + response.pipe(fileStream); + response.on('error', function(err) { + console.error('Got Error in binary download response', err); + that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); + }); + fileStream.on('error', function (err) { + console.error('Got Error while downloading binary file', err); + that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); + }); + fileStream.on('close', function () { fs.chmod(binaryPath, '0755', function() { callback(binaryPath); }); }); - response.pipe(file); + }).on('error', function(err) { + console.error('Got Error in binary downloading request', err); + that.retryBinaryDownload(conf, destParentDir, callback, retries, binaryPath); }); }; @@ -55,7 +82,7 @@ function LocalBinary(){ if(this.checkPath(binaryPath, fs.X_OK)){ callback(binaryPath); } else { - this.download(conf, destParentDir, callback); + this.download(conf, destParentDir, callback, 5); } }; diff --git a/package.json b/package.json index 2314b66..95c16fc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "license": "MIT", "dependencies": { "https-proxy-agent": "^1.0.0", - "is-running": "^2.0.0" + "is-running": "^2.0.0", + "sinon": "^1.17.6", + "temp-fs": "^0.9.9" }, "devDependencies": { "eslint": "1.10.3", diff --git a/test/local.js b/test/local.js index 5fd442d..02a5e6a 100644 --- a/test/local.js +++ b/test/local.js @@ -1,12 +1,17 @@ var expect = require('expect.js'), + sinon = require('sinon'), mocks = require('mocks'), path = require('path'), fs = require('fs'), rimraf = require('rimraf'), Proxy = require('proxy'), + tempfs = require('temp-fs'), browserstack = require('../index'), LocalBinary = require('../lib/LocalBinary'); + +const MAX_TIMEOUT = 600000; + describe('Local', function () { var bsLocal; beforeEach(function () { @@ -157,53 +162,138 @@ describe('Local', function () { }); describe('LocalBinary', function () { - var proxy; - var proxyPort; - var binary; - var tempDownloadPath; - - before(function (done) { - // setup HTTP proxy server - proxy = new Proxy(); - proxy.listen(function () { - proxyPort = proxy.address().port; - done(); + describe('Retries', function() { + var unlinkTmp, + defaultBinaryPath, + validBinaryPath, + sandBox; + + before(function(done) { + this.timeout(MAX_TIMEOUT); + // ensure that we have a valid binary downloaded + + // removeIfInvalid(); + (new LocalBinary()).binaryPath({}, function(binaryPath) { + defaultBinaryPath = binaryPath; + tempfs.mkdir({ + recursive: true + }, function(err, dir) { + if(err) { throw err; } + + validBinaryPath = path.join(dir.path, path.basename(binaryPath)); + fs.rename(defaultBinaryPath, validBinaryPath, function(err) { + if(err) { throw err; } + + unlinkTmp = dir.unlink; + done(); + }); + }); + }); }); - }); - after(function (done) { - proxy.once('close', function () { done(); }); - proxy.close(); - }); + beforeEach(function() { + sandBox = sinon.sandbox.create(); + }); - beforeEach(function () { - binary = new LocalBinary(); - tempDownloadPath = path.join(process.cwd(), 'download'); - }); + it('Tries to download binary if its corrupted', function(done) { + fs.unlink(defaultBinaryPath, function() { + var localBinary = new LocalBinary(); + var downloadStub = sandBox.stub(localBinary, 'download', function() { + downloadStub.callArgWith(2, [ defaultBinaryPath ]); + expect(downloadStub.args[0][3]).to.be(5); + }); - afterEach(function () { - rimraf.sync(tempDownloadPath); - }); + fs.writeFile(defaultBinaryPath, 'Random String', function() { + fs.chmod(defaultBinaryPath, '0755', function() { + localBinary.binaryPath({ + }, function(binaryPath) { + expect(downloadStub.called).to.be.true; + done(); + }); + }); + }); + }); + }); - it('should download binaries without proxy', function (done) { - this.timeout(600000); - var conf = {}; - binary.download(conf, tempDownloadPath, function (result) { - expect(fs.existsSync(result)).to.equal(true); + it('Tries to download binary if its not present', function(done) { + fs.unlink(defaultBinaryPath, function() { + var localBinary = new LocalBinary(); + var downloadStub = sandBox.stub(localBinary, 'download', function() { + downloadStub.callArgWith(2, [ defaultBinaryPath ]); + expect(downloadStub.args[0][3]).to.be(5); + }); + + localBinary.binaryPath({ + }, function(binaryPath) { + expect(downloadStub.called).to.be.true; + done(); + }); + }); + }); + + afterEach(function(done) { + sandBox.restore(); done(); }); + + after(function(done) { + fs.rename(validBinaryPath, defaultBinaryPath, function(err) { + if(err) { throw err; } + + unlinkTmp(done); + }); + }); }); - it('should download binaries with proxy', function (done) { - this.timeout(600000); - var conf = { - proxyHost: '127.0.0.1', - proxyPort: proxyPort - }; - binary.download(conf, tempDownloadPath, function (result) { - // test for file existence - expect(fs.existsSync(result)).to.equal(true); - done(); + describe('Download', function() { + var proxy; + var proxyPort; + var binary; + var tempDownloadPath; + + before(function (done) { + // setup HTTP proxy server + proxy = new Proxy(); + proxy.listen(function () { + proxyPort = proxy.address().port; + done(); + }); + }); + + after(function (done) { + proxy.once('close', function () { done(); }); + proxy.close(); + }); + + beforeEach(function () { + binary = new LocalBinary(); + tempDownloadPath = path.join(process.cwd(), 'download'); + }); + + afterEach(function () { + rimraf.sync(tempDownloadPath); + }); + + it('should download binaries without proxy', function (done) { + this.timeout(MAX_TIMEOUT); + var conf = {}; + binary.download(conf, tempDownloadPath, function (result) { + expect(fs.existsSync(result)).to.equal(true); + done(); + }); + }); + + it('should download binaries with proxy', function (done) { + this.timeout(MAX_TIMEOUT); + var conf = { + proxyHost: '127.0.0.1', + proxyPort: proxyPort + }; + binary.download(conf, tempDownloadPath, function (result) { + // test for file existence + expect(fs.existsSync(result)).to.equal(true); + done(); + }); }); }); });