diff --git a/.travis.yml b/.travis.yml index 774199c..7dd40f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,9 @@ addons: packages: - g++-4.8 +services: + - docker + before_install: - export CXX="g++-4.8" - npm install juttle@0.2.x @@ -15,6 +18,10 @@ node_js: - '4.2' - '5.0' +before_script: + - docker run -d -p 4444:4444 --name selenium-hub selenium/hub + - docker run -d --link selenium-hub:hub --name selenium-node-chrome selenium/node-chrome + script: - gulp lint - - gulp test-coverage + - OUTRIGGER_HOST=172.17.42.1 SELENIUM_REMOTE_URL='http://localhost:4444/wd/hub' gulp test-coverage --app diff --git a/gulpfile.js b/gulpfile.js index e02f5fe..1f34656 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -64,16 +64,44 @@ gulp.task('instrument', function () { .pipe(istanbul.hookRequire()); }); +function gulp_test_app() { + return gulp.src([ + ]) + .pipe(mocha({ + log: true, + timeout: 5000, + reporter: 'spec', + ui: 'bdd', + ignoreLeaks: true, + globals: ['should'] + })); +} + function gulp_test() { - return gulp.src('test/**/*.spec.js') - .pipe(mocha({ - log: true, - timeout: 5000, - reporter: 'spec', - ui: 'bdd', - ignoreLeaks: true, - globals: ['should'] - })); + var argv = require('yargs').argv; + var tests = [ + 'test/**/*.spec.js' + ]; + + // by passing the argument `--app` you can also run the app tests + // which require spinning up a browser, by default we do not run + // the app tests + if (!argv.app) { + tests.push( + // exclude app tests + '!test/app/**/*.spec.js' + ); + } + + return gulp.src(tests) + .pipe(mocha({ + log: true, + timeout: 5000, + reporter: 'spec', + ui: 'bdd', + ignoreLeaks: true, + globals: ['should'] + })); } gulp.task('test', function() { @@ -82,17 +110,17 @@ gulp.task('test', function() { gulp.task('test-coverage', ['instrument'], function() { return gulp_test() - .pipe(istanbul.writeReports()) - .pipe(istanbul.enforceThresholds({ - thresholds: { - global: { - statements: 71, - branches: 63, - functions: 61, - lines: 55 - } + .pipe(istanbul.writeReports()) + .pipe(istanbul.enforceThresholds({ + thresholds: { + global: { + statements: 76, + branches: 71, + functions: 69, + lines: 73 } - })); + } + })); }); gulp.task('default', ['test', 'lint']); diff --git a/lib/service-juttled.js b/lib/service-juttled.js index 9724bc7..9786618 100644 --- a/lib/service-juttled.js +++ b/lib/service-juttled.js @@ -17,7 +17,7 @@ var API_PREFIX = '/api/v0'; var JuttledService = Base.extend({ - initialize: function(options) { + initialize: function(options, cb) { var config = read_config(options); Juttle.adapters.load(config.adapters); var self = this; @@ -52,6 +52,7 @@ var JuttledService = Base.extend({ this._server = this._app.listen(options.port, function() { logger.info('Juttled service listening at http://localhost:' + options.port + " with root directory:" + self._root_directory); + cb && cb(); }); }, diff --git a/package.json b/package.json index 6194c75..49ede97 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "gulp-mocha": "^2.1.3", "isparta": "^4.0.0", "json-loader": "^0.5.4", + "nconf": "^0.8.2", "node-libs-browser": "^0.5.3", "node-sass": "^3.4.2", "resolve-url-loader": "^1.4.2", @@ -90,7 +91,8 @@ "selenium-webdriver": "^2.48.2", "style-loader": "^0.13.0", "webpack": "^1.12.6", - "webpack-dev-middleware": "^1.2.0" + "webpack-dev-middleware": "^1.2.0", + "yargs": "^3.31.0" }, "engines": { "node": ">=4.2.0", diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..11b1739 --- /dev/null +++ b/test/README.md @@ -0,0 +1,53 @@ +# Test + +## Running Unit Tests + +To run the built in unit tests simply run: + +``` +gulp test +``` + +## Running App Tests + +To run the unit tests and app tests that uses your local `google-chrome` browser +to run the tests: + +``` +gulp test --app +``` + +The above relies on you having the right version of chrome. To avoid version +problems with chrome you can use docker for testing like so: + +``` +docker run -d -p 4444:4444 --name selenium-hub selenium/hub +docker run -d --link selenium-hub:hub selenium/node-chrome +``` + +Then when running the tests you have to tell selenium to hit the selenium +hub running on the `selenium-hub` container and the tests to hit the +host ip address which is usually `172.17.42.1` but you can check what your +host ip address is with: + +``` +docker network inspect bridge | grep Gateway +``` + +That `Gateway` is your host ip that should be used with the following command: + +``` +OUTRIGGER_HOST=172.17.42.1 SELENIUM_REMOTE_URL='http://localhost:4444/wd/hub' gulp test --app +``` + +That will run the same app tests through the docker selenium setup and verify +everything is working as expected. + +## Helper Scripts + +To simplify bringing up the selenium setup and tearing it down you can use +the helper scripts under `test/scripts`: + + * `test/scripts/start_selenium_setup.sh` + * `test/scripts/stop_selenium_setup.sh` + diff --git a/test/app/README.md b/test/app/README.md deleted file mode 100644 index 02ee100..0000000 --- a/test/app/README.md +++ /dev/null @@ -1,11 +0,0 @@ -These tests test the full flow of loading a juttle file into outriggerd, filling out inputs if there are any, and verifying that the program output is correct. - -They are not fully automated yet and require some manual startup of services. - -* Install [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and start it (starts on default port 9515) -* Make sure outriggerd is running - -To run the tests: -``` -mocha app.spec.js -``` diff --git a/test/app/app.spec.js b/test/app/app.spec.js index 9250ff1..35c036f 100644 --- a/test/app/app.spec.js +++ b/test/app/app.spec.js @@ -1,56 +1,59 @@ -"use strict"; +'use strict'; let DemoAppTester = require('./lib/demo-app-tester'); -let webdriver = require('selenium-webdriver'); let expect = require('chai').expect; let path = require('path'); -const TEST_TIMEOUT = 10000; -const BROWSER = "from"; -const WEBDRIVER_URL = "http://localhost:9515/"; +const TEST_TIMEOUT = 30000; - -// skipped while webdriver is not properly configured in travis -describe.skip("demo-app", function() { +describe('demo-app', function() { this.timeout(TEST_TIMEOUT); - let driver; let demoAppTester; - beforeEach(() => { - driver = new webdriver.Builder() - .forBrowser(BROWSER) - .usingServer(WEBDRIVER_URL) - .build(); - demoAppTester = new DemoAppTester(driver); + + before(function(done) { + demoAppTester = new DemoAppTester(); + demoAppTester.start(done); }); - it("open juttle program with no inputs", () => { - return demoAppTester.loadFile(path.join(__dirname, "juttle", "no-inputs.juttle")) - .then(() => { - return demoAppTester.getLoggerOutput('myLogger'); - }) - .then((value) => { - expect(JSON.parse(value)).to.deep.equal([{time: new Date(0).toISOString(), v: 10}]); - }); + after(() => { + demoAppTester.stop(); }); - it("open juttle program with an input, fill it out, and run", () => { - return demoAppTester.loadFile(path.join(__dirname, "juttle", "one-input.juttle")) - .then(() => { - return demoAppTester.findInputControl("a"); - }) - .then((inputElem) => { - inputElem.sendKeys("AAA"); - }) - .then(demoAppTester.clickPlay) - .then(() => { - return demoAppTester.getLoggerOutput('myLogger'); - }) - .then((value) => { - expect(JSON.parse(value)).to.deep.equal([{time: new Date(0).toISOString(), v: "AAA"}]); - }); + it('open juttle program with no inputs', () => { + return demoAppTester.run({ + path: path.join(__dirname, 'juttle', 'no-inputs.juttle') + }) + .then(() => { + return demoAppTester.getTextOutput('output'); + }) + .then((value) => { + expect(JSON.parse(value)).to.deep.equal([ + { time: new Date(0).toISOString(), value: 10 } + ]); + }); }); - afterEach(() => { - driver.quit(); + it('open juttle program with an input, fill it out, and run', () => { + return demoAppTester.run({ + path: path.join(__dirname, 'juttle', 'one-input.juttle') + }) + .then(() => { + return demoAppTester.findInputControl('a'); + }) + .then((inputElem) => { + inputElem.sendKeys('AAA'); + }) + .then(() => { + demoAppTester.clickPlay(); + }) + .then(() => { + return demoAppTester.getTextOutput('myLogger'); + }) + .then((value) => { + expect(JSON.parse(value)).to.deep.equal([ + { time: new Date(0).toISOString(), value: 'AAA' } + ]); + }); }); + }); diff --git a/test/app/juttle/no-inputs.juttle b/test/app/juttle/no-inputs.juttle index 6222d0f..45e7183 100644 --- a/test/app/juttle/no-inputs.juttle +++ b/test/app/juttle/no-inputs.juttle @@ -1,3 +1,3 @@ emit -from :0: -limit 1 -| put v = 10 -| @logger -display.style 'json' -title 'myLogger' +| put value = 10 +| view text -format 'json' -title 'output' diff --git a/test/app/juttle/one-input.juttle b/test/app/juttle/one-input.juttle index b6d8470..d99f69d 100644 --- a/test/app/juttle/one-input.juttle +++ b/test/app/juttle/one-input.juttle @@ -1,4 +1,5 @@ input a: text -label 'a'; + emit -from :0: -limit 1 -| put v = a -| @logger -display.style 'json' -title 'myLogger' +| put value = a +| view text -format 'json' -title 'myLogger' diff --git a/test/app/lib/demo-app-tester.js b/test/app/lib/demo-app-tester.js index aedf842..c86ba54 100644 --- a/test/app/lib/demo-app-tester.js +++ b/test/app/lib/demo-app-tester.js @@ -1,45 +1,91 @@ -"use strict"; +'use strict'; +let _ = require('underscore'); let webdriver = require('selenium-webdriver'); let By = webdriver.By; let until = webdriver.until; -class DemoAppTester { - constructor(driver) { - this._driver = driver; +let nconf = require('nconf'); +nconf.argv().env(); + +// setup log level to be quiet by default +var logSetup = require('../../../bin/log-setup'); +logSetup.init({ + // set LOGLEVEL=OFF to quiet all logging + 'log-level': nconf.get('LOGLEVEL') || 'INFO' +}); + +if (!nconf.get('SELENIUM_BROWSER')) { + // default to chrome + process.env['SELENIUM_BROWSER'] = 'chrome'; +} + +let JuttledService = require('../../../lib/service-juttled'); +class DemoAppTester { + constructor() { // bind the methods so that they don't need to be bound every time // they are passed to a function for invoking this.clickPlay = this.clickPlay.bind(this); this.findInputControl = this.findInputControl.bind(this); - this.getLoggerOutput = this.getLoggerOutput.bind(this); - this.loadFile = this.loadFile.bind(this); + this.getTextOutput = this.getTextOutput.bind(this); + this.run = this.run.bind(this); + } + + start(cb) { + this.outrigger = new JuttledService({ + port: 2000, + root_directory: '/', + }, cb); + + this.driver = new webdriver.Builder() + .build(); + } + + stop() { + if (!nconf.get('KEEP_BROWSER')) { + this.driver.quit(); + } + + this.outrigger.stop(); } clickPlay() { - return this._driver.findElement(By.name('play')) - .then(function(button) { - return button.click(); - }); + return this.driver.findElement(By.id('btn-run')) + .then(function(button) { + return button.click(); + }); } findInputControl(inputControlLabel) { - return this._driver.wait(until.elementLocated(By.css(`.inputs-view div[data-input-label=${inputControlLabel}]`))) - .then((elem) => { - return elem.findElement(By.css("input")); - }); + var element = until.elementLocated(By.css(`.inputs-view div[data-input-label=${inputControlLabel}]`)); + return this.driver.wait(element) + .then((elem) => { + return elem.findElement(By.css('input')); + }); + } + + findOutputByTitle(title) { + return this.driver.wait(until.elementLocated(By.xpath(`//div[@class='jut-chart-title' and text()='${title}']`))); } - getLoggerOutput(title) { - return this._driver.wait(until.elementLocated(By.xpath(`//div[@class="jut-chart-title" and text()="${title}"]/../../..//textarea`))) - .then(function(elem) { - return elem.getAttribute("value"); - }); + getTextOutput(title) { + return this.findOutputByTitle(title) + .then(function(element) { + return element.findElement(By.xpath('//textarea')); + }) + .then(function(elem) { + return elem.getAttribute('value'); + }); } - loadFile(filepath) { - return this._driver.get(`http://localhost:2000/?path=${filepath}`); + run(options) { + var params = _.map(options, function(value, name) { + return `${name}=${value}`; + }); + var host = nconf.get('OUTRIGGER_HOST') || 'localhost'; + return this.driver.get('http://' + host + ':2000/run?' + params.join('&')); } -}; +} module.exports = DemoAppTester; diff --git a/test/scripts/start_selenium_setup.sh b/test/scripts/start_selenium_setup.sh new file mode 100755 index 0000000..0ff8f34 --- /dev/null +++ b/test/scripts/start_selenium_setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +docker run -d -p 4444:4444 --name selenium-hub selenium/hub +docker run -d --link selenium-hub:hub --name selenium-node-chrome selenium/node-chrome diff --git a/test/scripts/stop_selenium_setup.sh b/test/scripts/stop_selenium_setup.sh new file mode 100755 index 0000000..288ac35 --- /dev/null +++ b/test/scripts/stop_selenium_setup.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +docker stop selenium-hub +docker stop selenium-node-chrome +docker rm selenium-hub +docker rm selenium-node-chrome