From f6fcf5fce406db5718235b36509dcd9402c0269e Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Wed, 13 Nov 2013 00:24:08 -0800 Subject: [PATCH] Attempt to make sauce tests public with names, tags, and customized options. --- .travis.yml | 20 +-- test/saucelabs.js | 307 +++++++++++++++++++++++++++++----------------- 2 files changed, 204 insertions(+), 123 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2ea46dcbb..1e371d4b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ node_js: env: global: - BIN="node" BUILD=false COMPAT=false MAKE=false OPTION="" SAUCE_LABS=false - - SAUCE_USERNAME="jdalton" - - secure: "woILQltl1pI3DgadZ5NrcqntdPvnRmQBwIVNZL91Ht5d9snIhgyAixI6xNAS8F8BzD9RzqzVPHay5sHfn+GhNaojcaiHs1nXAbdyclevMyfP+3MQ1HGfMSU0bv1GdT35LJ+C0u4Y3SuuZSbBlNEeLXRPMngPZahf4xL8RsZz/is=" + - SAUCE_USERNAME="lodash" + - secure: "LGVeh7z/x6hxGCtr1BfgCSRva691ChX+1bs4ecJE71QwsDNJwknqmblZm7hFSTkq7msSapSDV5P9sgr6fsmmHuY3311Vl04sneRpz8ge1ZUIbmMODI+tZAasxyvyjZJdOrTFZr89BWN6e/SddDxDKNF4n0O1DgUF4irrT+aUxUE=" matrix: - BUILD="compat" - BUILD="modern" @@ -70,11 +70,11 @@ script: - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || cd ./test" - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || $BIN $OPTION ./test.js ../dist/lodash.$BUILD.js" - "([ $SAUCE_LABS != false ] || [ $BUILD == false ]) && true || $BIN $OPTION ./test.js ../dist/lodash.$BUILD.min.js" - - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js \"test/index.html?build=lodash-$BUILD\"" - - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js \"test/index.html?build=../dist/lodash.$BUILD.js\"" - - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/backbone.html?build=lodash-$BUILD\"" - - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/backbone.html?build=../dist/lodash.$BUILD.js\"" - - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/underscore.html?build=lodash-$BUILD\"" - - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js \"test/underscore.html?build=../dist/lodash.$BUILD.js\"" - - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js \"test/index.html?build=lodash-$BUILD&compat=7\"" - - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js \"test/index.html?build=../dist/lodash.$BUILD.js&compat=7\"" + - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js runner=\"test/index.html?build=lodash-$BUILD\" tags=\"$BUILD,production\"" + - "([ $SAUCE_LABS == false ] || [ $BUILD == 'underscore' ]) && true || node ./test/saucelabs.js runner=\"test/index.html?build=../dist/lodash.$BUILD.js\" tags=\"$BUILD,development\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js name=\"backbone tests\" runner=\"test/backbone.html?build=lodash-$BUILD\" tags=\"$BUILD,production,backbone\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js name=\"backbone tests\" runner=\"test/backbone.html?build=../dist/lodash.$BUILD.js\" tags=\"$BUILD,development,backbone\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js name=\"underscore tests\" runner=\"test/underscore.html?build=lodash-$BUILD\" tags=\"$BUILD,production,underscore\"" + - "[ $SAUCE_LABS == false ] && true || node ./test/saucelabs.js name=\"underscore tests\" runner=\"test/underscore.html?build=../dist/lodash.$BUILD.js\" tags=\"$BUILD,development,underscore\"" + - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js runner=\"test/index.html?build=lodash-$BUILD&compat=7\" tags=\"$BUILD,production,ie-compat-mode\"" + - "([ $SAUCE_LABS == false ] || [ $COMPAT == false ]) && true || node ./test/saucelabs.js runner=\"test/index.html?build=../dist/lodash.$BUILD.js&compat=7\" tags=\"$BUILD,development,ie-compat-mode\"" diff --git a/test/saucelabs.js b/test/saucelabs.js index 63b420936..729bb1930 100644 --- a/test/saucelabs.js +++ b/test/saucelabs.js @@ -1,38 +1,45 @@ ;(function() { 'use strict'; - var ecstatic = require('ecstatic'), - http = require('http'), - path = require('path'), - request = require('request'), - SauceTunnel = require('sauce-tunnel'), - url = require('url'); - - var attempts = -1, - prevLine = ''; - - var port = 8081, - username = process.env.SAUCE_USERNAME, - accessKey = process.env.SAUCE_ACCESS_KEY, - tunnelId = 'lodash_' + process.env.TRAVIS_JOB_NUMBER; - if (isFinite(process.env.TRAVIS_PULL_REQUEST)) { console.error('Testing skipped for pull requests'); process.exit(0); } - var runnerPathname = (function() { - var args = process.argv; - return args.length > 2 - ? '/' + args[args.length - 1].replace(/^\W+/, '') - : '/test/index.html'; - }()); + /** Load Node.js modules */ + var http = require('http'), + path = require('path'), + url = require('url'); - var runnerQuery = url.parse(runnerPathname, true).query, - isBackbone = /\bbackbone\b/i.test(runnerPathname), - isMobile = /\bmobile\b/i.test(runnerQuery.build), - isModern = /\bmodern\b/i.test(runnerQuery.build); + /** Load other modules */ + var _ = require('../lodash'), + ecstatic = require('ecstatic'), + request = require('request'), + SauceTunnel = require('sauce-tunnel'); + /** Used by `logInline` */ + var attempts = -1, + prevLine = ''; + + /** Used as request `auth` and `options` values */ + var port = 8081, + username = process.env.SAUCE_USERNAME, + accessKey = process.env.SAUCE_ACCESS_KEY, + tunnelId = 'lodash_' + process.env.TRAVIS_JOB_NUMBER; + + var runner = process.argv.reduce(function(result, value) { + return optionToValue('runner', value) || result; + }, '/test/index.html'); + + var sessionName = process.argv.reduce(function(result, value) { + return optionToValue('name', value) || result; + }, 'lodash tests'); + + var tags = process.argv.reduce(function(result, value) { + return optionToArray('tags', value) || result; + }, []); + + /** List of platforms to load the runner on */ var platforms = [ ['Windows 7', 'chrome', ''], ['Windows 7', 'firefox', '25'], @@ -53,6 +60,12 @@ ['Windows 7', 'safari', '5'] ]; + /** Used to tailor the `platforms` array */ + var runnerQuery = url.parse(runner, true).query, + isBackbone = /\bbackbone\b/i.test(runner), + isMobile = /\bmobile\b/i.test(runnerQuery.build), + isModern = /\bmodern\b/i.test(runnerQuery.build); + // platforms to test IE compat mode if (runnerQuery.compat) { platforms = [ @@ -91,39 +104,14 @@ }); } - // create a web server for the local dir - var mount = ecstatic({ - 'root': process.cwd(), - 'cache': false - }); - - http.createServer(function(req, res) { - var compat = url.parse(req.url, true).query.compat; - if (compat) { - // see http://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx - res.setHeader('X-UA-Compatible', 'IE=' + compat); - } - mount(req, res); - }).listen(port); - - // set up Sauce Connect so we can use this server from Sauce Labs - var tunnelTimeout = 10000, - tunnel = new SauceTunnel(username, accessKey, tunnelId, true, tunnelTimeout); - - console.log('Opening Sauce Connect tunnel...'); - - tunnel.start(function(success) { - if (success) { - console.log('Sauce Connect tunnel opened'); - runTests(); - } else { - console.error('Failed to open Sauce Connect tunnel'); - process.exit(2); - } - }); - /*--------------------------------------------------------------------------*/ + /** + * Logs an inline message to standard output. + * + * @private + * @param {string} text The text to log. + */ function logInline(text) { var blankLine = repeat(' ', prevLine.length); if (text.length > 40) { @@ -133,67 +121,54 @@ process.stdout.write(text + blankLine.slice(text.length) + '\r'); } - function repeat(text, times) { - return Array(times + 1).join(text); + /** + * Converts a comma separated option value into an array. + * + * @private + * @param {string} name The name of the option to inspect. + * @param {string} string The options string. + * @returns {Array} Returns the new converted array. + */ + function optionToArray(name, string) { + return _.compact(_.isArray(string) + ? string + : _.invoke((optionToValue(name, string) || '').split(/, */), 'trim') + ); + } + + /** + * Extracts the option value from an option string. + * + * @private + * @param {string} name The name of the option to inspect. + * @param {string} string The options string. + * @returns {string|undefined} Returns the option value, else `undefined`. + */ + function optionToValue(name, string) { + var result = (result = string.match(RegExp('^' + name + '=([\\s\\S]+)$'))) && result[1].trim(); + return result || undefined; + } + + /** + * Creates a string with `text` repeated `n` number of times. + * + * @private + * @param {string} text The text to repeat. + * @param {number} n The number of times to repeat `text`. + * @returns {string} The created string. + */ + function repeat(text, n) { + return Array(n + 1).join(text); } /*--------------------------------------------------------------------------*/ - function runTests() { - var testDefinition = { - 'framework': 'qunit', - 'platforms': platforms, - 'tunnel': 'tunnel-identifier:' + tunnelId, - 'url': 'http://localhost:' + port + runnerPathname - }; - - console.log('Starting saucelabs tests: ' + JSON.stringify(testDefinition)); - - request.post('https://saucelabs.com/rest/v1/' + username + '/js-tests', { - 'auth': { 'user': username, 'pass': accessKey }, - 'json': testDefinition - }, function(error, response, body) { - var statusCode = response && response.statusCode; - if (statusCode == 200) { - waitForTestCompletion(body); - } else { - console.error('Failed to submit test to Sauce Labs; status: ' + statusCode + ', body:\n' + JSON.stringify(body)); - if (error) { - console.error(error); - } - process.exit(3); - } - }); - } - - function waitForTestCompletion(testIdentifier) { - request.post('https://saucelabs.com/rest/v1/' + username + '/js-tests/status', { - 'auth': { 'user': username, 'pass': accessKey }, - 'json': testIdentifier - }, function(error, response, body) { - var statusCode = response && response.statusCode; - if (statusCode == 200) { - if (body.completed) { - logInline(''); - handleTestResults(body['js tests']); - } - else { - logInline('Please wait' + repeat('.', (++attempts % 3) + 1)); - setTimeout(function() { - waitForTestCompletion(testIdentifier); - }, 5000); - } - } else { - logInline(''); - console.error('Failed to check test status on Sauce Labs; status: ' + statusCode + ', body:\n' + JSON.stringify(body)); - if (error) { - console.error(error); - } - process.exit(4); - } - }); - } - + /** + * Processes the result object of the test session. + * + * @private + * @param {Object} results The result object to process. + */ function handleTestResults(results) { var failingTests = results.filter(function(test) { var result = test.result; @@ -229,4 +204,110 @@ process.exit(failingTests.length ? 1 : 0); }); } + + /** + * Makes a request for Sauce Labs to start the test session. + * + * @private + */ + function runTests() { + var options = { + 'framework': 'qunit', + 'name': sessionName, + 'public': 'public', + 'platforms': platforms, + 'record-screenshots': false, + 'tags': tags, + 'tunnel': 'tunnel-identifier:' + tunnelId, + 'url': 'http://localhost:' + port + runner, + 'video-upload-on-pass': false + }; + + console.log('Starting saucelabs tests: ' + JSON.stringify(options)); + + request.post('https://saucelabs.com/rest/v1/' + username + '/js-tests', { + 'auth': { 'user': username, 'pass': accessKey }, + 'json': options + }, function(error, response, body) { + var statusCode = response && response.statusCode; + if (statusCode == 200) { + waitForTestCompletion(body); + } else { + console.error('Failed to submit test to Sauce Labs; status: ' + statusCode + ', body:\n' + JSON.stringify(body)); + if (error) { + console.error(error); + } + process.exit(3); + } + }); + } + + /** + * Checks the status of the test session. If the session has completed it + * passes the result object to `handleTestResults`, else it checks the status + * again in five seconds. + * + * @private + * @param {Object} testIdentifier The object used to identify the session. + */ + function waitForTestCompletion(testIdentifier) { + request.post('https://saucelabs.com/rest/v1/' + username + '/js-tests/status', { + 'auth': { 'user': username, 'pass': accessKey }, + 'json': testIdentifier + }, function(error, response, body) { + var statusCode = response && response.statusCode; + if (statusCode == 200) { + if (body.completed) { + logInline(''); + handleTestResults(body['js tests']); + } + else { + logInline('Please wait' + repeat('.', (++attempts % 3) + 1)); + setTimeout(function() { + waitForTestCompletion(testIdentifier); + }, 5000); + } + } else { + logInline(''); + console.error('Failed to check test status on Sauce Labs; status: ' + statusCode + ', body:\n' + JSON.stringify(body)); + if (error) { + console.error(error); + } + process.exit(4); + } + }); + } + + /*--------------------------------------------------------------------------*/ + + // create a web server for the local dir + var mount = ecstatic({ + 'root': process.cwd(), + 'cache': false + }); + + http.createServer(function(req, res) { + var compat = url.parse(req.url, true).query.compat; + if (compat) { + // see http://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx + res.setHeader('X-UA-Compatible', 'IE=' + compat); + } + mount(req, res); + }).listen(port); + + // set up Sauce Connect so we can use this server from Sauce Labs + var tunnelTimeout = 10000, + tunnel = new SauceTunnel(username, accessKey, tunnelId, true, tunnelTimeout); + + console.log('Opening Sauce Connect tunnel...'); + + tunnel.start(function(success) { + if (success) { + console.log('Sauce Connect tunnel opened'); + runTests(); + } else { + console.error('Failed to open Sauce Connect tunnel'); + process.exit(2); + } + }); }());