From 96893ce615e0029d0d1daf9c6433723dc220a6de Mon Sep 17 00:00:00 2001 From: Geoff Berger Date: Wed, 4 Mar 2015 11:09:50 -0800 Subject: [PATCH] allowed for test directory to be deleted only on initial run removed console.logs from executor allows for both executor and testcase to read from a temp directory of .venus_temp-#PORTNUMBER# verses straight .venus_temp removed unused startingPort variable removed commented out code from getHttpRoot cleaned up static content setup and created venus temp helper put back me original pirate locale, ok that wasnt funny, whatever created port and tempDir constants refined logic and put in error handling for new bits of code created getTempDir and resolveTempDir utilities in fsHelper added new error string to locale file added tests to support changes to executor namely: sendGenericError, setPort, and some other one I forgot about, iono look at the code created fsHelper for newly created fsHelper utilities added fsHelper spec to test_suite resolved rebase conflict on testcase.spec.js moved directory operations over to test helpers to clean up test files removed console.log from test helper adding debug statements and translations updated temp dir allowed for test directory to be deleted right before the test shuts down and strengthened some things Fixing testcase tests updated tests based off of recent code changes added clean command so all temporary directories can be removed yes, I am THAT guy who broke the build added node-uuid to package.json because I dont know how to program, lets actually pass _.isNull some input updated tempDir to point to proper tempDir based off of our discussions in PR 350 updated executor to use a guid instead of the port so we can guarantee the temp directory on the file path will be unique - the same thing is also achieved with the venus server updated testcase to use guid instead of port to make things unique updated docs and variable reference to resolveTempDir in fsHelper.js updated executor test to reflect guid being used in place of port updated testcase test to reflect guid being used in place of port added test for using guid within executor make clean a promise and added tests to support the clean command and its functionality removed reference to dev box corrected failing clean unit test to actually work removed unnecessary call to setMaxListeners made setPort test more efficient removing unused deferred cal updated paths based on testing file structure refactoring added back fsHelper because for some reason evil git rebase removed the file --- Venus.js | 30 ++- lib/constants.js | 7 + lib/executor.js | 306 +++++++++++++++++++-------- lib/testcase.js | 27 ++- lib/util/fsHelper.js | 64 +++++- locales/pirate.js | 19 +- package.json | 1 + test/lib/helpers.js | 42 +++- test/unit/node/Venus.spec.js | 54 ++++- test/unit/node/executor.spec.js | 144 ++++++++++++- test/unit/node/test_suite.js | 1 + test/unit/node/testcase.spec.js | 44 ++-- test/unit/node/util/fsHelper.spec.js | 69 ++++++ test/unit/util/fsHelper.spec.js | 69 ++++++ 14 files changed, 750 insertions(+), 127 deletions(-) create mode 100644 test/unit/node/util/fsHelper.spec.js create mode 100644 test/unit/util/fsHelper.spec.js diff --git a/Venus.js b/Venus.js index f68dc84..1e15e77 100644 --- a/Venus.js +++ b/Venus.js @@ -29,9 +29,11 @@ var _ = require('underscore'), prompt = require('cli-prompt'), wrench = require('wrench'), fs = require('fs'), + fstools = require('fs-tools'), path = require('path'), deferred = require('deferred'), - ps = require('./lib/util/ps'); + ps = require('./lib/util/ps'), + constants = require('./lib/constants'); /** * The Venus application object @@ -106,6 +108,11 @@ Venus.prototype.init = function (args) { .option('--singleton', i18n('Ensures all other Venus processes are killed before starting')) .action(_.bind(this.command(this.run), this)); + program + .command('clean') + .description(i18n('Removes all tempoary directories')) + .action(_.bind(this.command(this.clean), this)); + program.parse(args); // No command (e.g., "init", "demo", "run") was provided in command line arguments, so run venus with defaults @@ -289,5 +296,26 @@ Venus.prototype.initProjectDirectory = function (program) { }; +/** + * Removes all temporary directories from the proper location and send a + * message to the logger. + */ +Venus.prototype.clean = function () { + var dir = constants.baseTempDir || constants.tempDir, + def = deferred(); + + fstools.remove(dir, function(err) { + if (_.isNull(err)) { + logger.warn(i18n('Temp directory at %s does not exist so it could not be removed', dir)); + def.reject(new Error('No temp directory was found')); + } else { + logger.info(i18n('Temp directory at %s was removed', dir)); + def.resolve(dir); + } + }); + + return def.promise; +}; + module.exports = Venus; Object.seal(module.exports); diff --git a/lib/constants.js b/lib/constants.js index 49b0d55..63f453a 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -16,12 +16,19 @@ **/ var os = require('os'), + baseTempDir = '/tmp/venus', constants; /** * Define constants for Venus application */ constants = { + baseTempDir: baseTempDir, + + tempDir: baseTempDir + '/run-', + + port: 2013, + userHome : (process.platform === 'win32') ? process.env['USERPROFILE'] : process.env['HOME'], /** diff --git a/lib/executor.js b/lib/executor.js index ec5b722..1460d05 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -18,8 +18,7 @@ // Test executor that is responsible for serving test content and instantiating clients to run tests 'use strict'; -var PORT = 2013, - express = require('express'), +var express = require('express'), http = require('http'), i18n = require('./util/i18n'), logger = require('./util/logger'), @@ -39,7 +38,10 @@ var PORT = 2013, coverage = require('./coverage'), constants = require('./constants'), environment = require('./environment'), - deferred = require('deferred'); + deferred = require('deferred'), + fsHelper = require('./util/fsHelper'), + uuid = require('node-uuid'), + PORT = constants.port; // Constructor for Executor function Executor(cwd) { @@ -55,17 +57,21 @@ function Executor(cwd) { this.startTime = null; this.endTime = null; this.logger = logger; + this.venusTemp = null; + this.guid = null; } // Initialize Executor.prototype.init = function(options, onStart) { var testgroup, - staticContent, def = deferred(); // set start time this.startTime = new Date(); + // set and determine guid + this.guid = options.guid || uuid.v1(); + // set hostname if desired if (options.hostname) { this.hostname = options.hostname; @@ -81,75 +87,128 @@ Executor.prototype.init = function(options, onStart) { this.requireTestAnnotations = false; } - // prepare static content - staticContent = this.config.static; - - if(staticContent) { - this.prepStaticContent(staticContent); - } - this.initEnvironment(options); - this.initRoutes(); - this.start(this.port, onStart).then(function (port) { - this.port = port; - - // Parse the list of relative paths specified in the command line arguments - // in to an array of testcase objects. - testgroup = this.testgroup = testrun.create(this.parseTests(options.test)); - - // If no tests were selected to run, there is nothing to do. - // log an error and return. - // TODO: we may want to change this to throw an exception. - if(testgroup.testCount === 0) { - logger.error( i18n('No tests specified to run - exiting') ); - process.exit(1); - } + this.start(this.port, onStart) + .then(this.setPort.bind(this), this.sendGenericError.bind(this, 'start')) + .then(this.setupVenusTemp.bind(this), this.sendGenericError.bind(this, 'setPort')) + .then(this.removeExistingTests.bind(this), this.sendGenericError.bind(this, 'setupVenusTemp')) + .then(this.handleStaticContent.bind(this, this.config['static']), this.sendGenericError.bind(this, 'removeExistingTasks')) + .then(this.addTempTestRoute.bind(this), this.sendGenericError.bind(this, 'handleStaticContent')) + .then(function() { + // Parse the list of relative paths specified in the command line arguments + // in to an array of testcase objects. + testgroup = this.testgroup = testrun.create(this.parseTests(options.test)); + + // If no tests were selected to run, there is nothing to do. + // log an error and return. + // TODO: we may want to change this to throw an exception. + if(testgroup.testCount === 0) { + logger.error( i18n('No tests specified to run - exiting') ); + process.exit(1); + } - logger.info('Venus server started at http://' + this.hostname + ':' + this.port + ' and is serving ' + testgroup.testCount.toString().yellow + ' test files'); + logger.info('Venus server started at http://' + this.hostname + ':' + this.port + ' and is serving ' + testgroup.testCount.toString().yellow + ' test files'); - // Print test URL - if (testgroup.testCount <= 5) { - testgroup.urls.forEach(function(url) { - logger.info('Serving test: ' + url.run.yellow); - }); - } + // Print test URL + if (testgroup.testCount <= 5) { + testgroup.urls.forEach(function(url) { + logger.info('Serving test: ' + url.run.yellow); + }); + } - // Create reporter - this.reporter = this.setupReporter(options.reporter || this.config.reporter, options.outputFile); - - // Setup the selected environment - if (options.environment) { - process.nextTick(function () { - logger.verbose(i18n('Using environment ' + options.environment)); - this.env = environment.create(testgroup.testArray[0].config, options.environment, this.reporter); - this.env.start(testgroup).then( - function () { - this.env.shutdown().then( + // Create reporter + this.reporter = this.setupReporter(options.reporter || this.config.reporter, options.outputFile); + + // Setup the selected environment + if (options.environment) { + process.nextTick(function () { + logger.verbose(i18n('Using environment ' + options.environment)); + this.env = environment.create(testgroup.testArray[0].config, options.environment, this.reporter); + this.env.start(testgroup) + // I wish deferred had a notion of .always or something similar, + // maybe it does exist and I don't know it? + .then(this.removeExistingTests.bind(this), this.removeExistingTests.bind(this)) + .then( function () { - this.shutdown(); - }.bind(this) - ); - }.bind(this), + this.env.shutdown().then( + function () { + this.shutdown(); + }.bind(this) + ); + }.bind(this), - function () { - this.env.shutdown().then( function () { - this.shutdown(); + this.env.shutdown().then( + function () { + this.shutdown(); + }.bind(this) + ); }.bind(this) ); - }.bind(this) - ); - }.bind(this)); - } + }.bind(this)); + } - this.reporter.emit('start', testgroup); + this.reporter.emit('start', testgroup); - def.resolve(); + def.resolve(); - }.bind(this)); + }.bind(this)); + + return def.promise; +}; + +/** + * Handles generic error handling based off of the method and the error + * produced. + * @param {String} name The name indicating where the error occurred. + * @param {obj} err the error from the promise + */ +Executor.prototype.sendGenericError = function(name, err) { + logger.error(i18n('Error occurred at %s.', name)); + logger.error(err.toString()); + process.exit(1); +}; + +/** + * Simple setter that allows the setting of a port from a single location. + * @param {number} port the port number for the server + */ +Executor.prototype.setPort = function(port) { + var def = deferred(); + + if (_.isNumber(port)) { + this.port = port; + def.resolve(port); + logger.debug(i18n('Setting port of %d', port)); + } else { + def.reject(new Error(i18n('Gots no port - I go bye now'))); + } + + return def.promise; +}; + +/** + * Remove the existing tests directory if the starting port matches with the + * current port. This will ensure if we are running venus over multiple ports, + * this won't accidentally delete the tests directory. + * @return {promise} + */ +Executor.prototype.removeExistingTests = function() { + var tempRoot = this.venusTemp(), + def = deferred(); + + fstools.remove(tempRoot, function(err) { + if(_.isNull(err)) { + logger.debug(i18n('Could not remove temp directory at %s', tempRoot)); + } else { + logger.debug(i18n('Removing directory found at %s', tempRoot)); + } + + def.resolve(); + }); return def.promise; }; @@ -173,35 +232,94 @@ Executor.prototype.setupReporter = function (reporter, outputFile) { return reporterInstance; }; -// Copy static content to temp folder +/** + * Checks for whether we are dealing with static content within a test and will + * perform actions needed to get static content within a test. + * @param {object} paths paths pointing to the directories under static content + * @return {promise} + */ +Executor.prototype.handleStaticContent = function(paths) { + if (paths) { + return this.prepStaticContent(paths); + } +}; + +/** + * By the time this is called, we have valid port, which allows us to determine + * the proper temp venus directory. + */ +Executor.prototype.setupVenusTemp = function() { + this.venusTemp = fsHelper.resolveTempDir(this.guid); + return this; +}; + +/** + * Symlinks static content to the test directory. + * @param {object} paths paths pointing to the directories under static content + * @return {promise} + */ Executor.prototype.prepStaticContent = function(paths) { - var fspath, httpRoot, httpPath; - - httpRoot = pathm.resolve(constants.userHome, '.venus_temp', 'static'); - - // remove old static content - fstools.remove(httpRoot, function () { - fstools.mkdir(httpRoot, function () { - - Object.keys(paths).forEach(function (key) { - fspath = paths[key]; - httpPath = pathm.resolve(httpRoot + '/' + key); - - // make the symlink to the file - (function (fspath, httpPath) { - // fstools.copy(fspath, httpPath, function (err) { - - fs.symlink(fspath, httpPath, function (err) { - if (err) { - logger.debug( i18n('error creating symlink for static path %s to %s. exception: %s', fspath, httpPath, err) ); - } else { - logger.debug( i18n('created symlink for static path %s to %s', fspath, httpPath) ); - } - }); - }(fspath, httpPath)); - }); + var httpRoot = this.venusTemp('static'); + + function removeExistingStaticContentDir() { + var def = deferred(); + + fstools.remove(httpRoot, function (err) { + if (err) { + logger.debug(i18n('Could not remove %s when preparing static content', httpRoot)); + } else { + logger.debug(i18n('Removing existing static content at %s', httpRoot)); + } + + def.resolve(); }); - }); + + return def.promise; + } + + function makeStaticContentDir() { + var def = deferred(); + + fstools.mkdir(httpRoot, function (err) { + if (err) { + logger.debug(i18n('Could not create directory %s when preparing static content', httpRoot)); + } else { + logger.debug(i18n('Creating static content directory at %s', httpRoot)); + } + + def.resolve(); + }); + + return def.promise; + } + + function createSymlinks() { + var def = deferred(); + + Object.keys(paths).forEach(function(key) { + var fspath = paths[key], + httpPath = pathm.resolve(httpRoot + '/' + key); + + // make the symlink to the file + (function(fspath, httpPath) { + fs.symlink(fspath, httpPath, function (err) { + if (err) { + logger.debug( i18n('error creating symlink for static path %s to %s. exception: %s', fspath, httpPath, err) ); + def.reject(err); + } else { + logger.debug( i18n('created symlink for static path %s to %s', fspath, httpPath) ); + def.resolve(fspath, httpPath); + } + }); + }(fspath, httpPath)); + }); + + return def.promise; + } + + return removeExistingStaticContentDir() + (makeStaticContentDir, this.sendGenericError.bind(this, 'prepStaticContent.removeExistingStaticContentDir')) + (createSymlinks, this.sendGenericError.bind(this, 'prepStaticContent.makeStaticContentDir')); }; // Start test runners! @@ -359,7 +477,8 @@ Executor.prototype.createTestObjects = function (testPaths) { runPath: this.urlNamespace + '/' + testId, instrumentCodeCoverage: this.enableCodeCoverage, config: configHelper.getConfig(), - hotReload: enableHotReload + hotReload: enableHotReload, + guid: this.guid }); if (testObjects[testId]) { @@ -413,12 +532,23 @@ Executor.prototype.initEnvironment = function(config) { app.use('/js', express.static(pathm.resolve(homeFolder, 'js'))); app.use('/css', express.static(pathm.resolve(homeFolder, 'css'))); app.use('/img', express.static(pathm.resolve(homeFolder, 'img'))); - app.use('/temp', express.static(pathm.resolve(constants.userHome, '.venus_temp'))); // port this.port = config.port || PORT; }; +/** + * Sets up the temp directory for the current test based on the port. This is + * separate from when the environment gets created because the port hasn't been + * negotiated yet. There is a chance where venus can be ran over multiple ports + * at the same time. We don't know this until later on in execution. By the + * time this has been called, we know the port we are working with and can + * route traffic to the appropriate location on the file system. + */ +Executor.prototype.addTempTestRoute = function() { + this.app.use('/run-' + this.guid, express.static(this.venusTemp())); +}; + /** * Prepend fixtures to body if they exist for current test * @param {Object} test Test object @@ -497,7 +627,7 @@ Executor.prototype.handleSandboxPage = function(request, response) { Executor.prototype.handleSandboxResource = function(request, response) { var testId = request.params.testid, resource = request.params.resource, - path = pathm.resolve(constants.userHome, '.venus_temp', 'test', testId, 'resources', resource); + path = this.venusTemp('test', testId, 'resources', resource); // Send resource only if it exists if (fs.existsSync(path)) { diff --git a/lib/testcase.js b/lib/testcase.js index c5be098..8224984 100644 --- a/lib/testcase.js +++ b/lib/testcase.js @@ -33,6 +33,7 @@ var fs = require('fs'), events = require('events'), util = require('util'), Instrumenter = require('istanbul').Instrumenter, + fsHelper = require('./util/fsHelper'), annotation = { VENUS_FIXTURE: 'venus-fixture', VENUS_FIXTURE_RESET: 'venus-fixture-reset', @@ -54,12 +55,14 @@ function TestCase() {} util.inherits(TestCase, events.EventEmitter); // Initialize -TestCase.prototype.init = function(path, id, runUrl, runPath, instrumentCodeCoverage, hotReload) { +TestCase.prototype.init = function(path, id, runUrl, runPath, instrumentCodeCoverage, hotReload, guid) { this.path = path; this.id = id; this.url = { run: runUrl, path: runPath }; this.instrumentCodeCoverage = instrumentCodeCoverage; this.hotReload = hotReload; + this.guid = guid; + this.venusTemp = fsHelper.resolveTempDir(guid); this.load(); }; @@ -206,7 +209,11 @@ TestCase.prototype.resolveAnnotations = function(annotations) { * @param {Number} testId the id of this testcase */ TestCase.prototype.getHttpRoot = function (testId) { - return pathm.resolve(constants.userHome, '.venus_temp', 'test', testId.toString()); + if (!this.venusTemp) { + this.venusTemp = fsHelper.resolveTempDir(this.guid); + } + + return this.venusTemp('test', testId.toString()); }; // Prepare testcase includes - there are the files @@ -337,7 +344,7 @@ TestCase.prototype.prepareIncludes = function (annotations) { var filePath = include.path, httpDir = include.httpDir, destination = pathm.resolve(httpRoot + '/' + httpDir + '/' + include.prepend + pathHelper(filePath).file), - httpUrl = '/' + destination.substr(destination.indexOf('temp/test/' + testId)), + httpUrl = '/' + destination.substr(destination.indexOf('run-' + this.guid + '/test/' + testId)), instrumentable = include.instrumentable; // Add to list of file mappings @@ -350,7 +357,7 @@ TestCase.prototype.prepareIncludes = function (annotations) { instrumentable: instrumentable }); - }); + }, this); return fileMappings; }; @@ -478,7 +485,7 @@ TestCase.prototype.prepareResources = function (annotations) { var filePath = resource.path, httpDir = resource.httpDir, destination = pathm.resolve(httpRoot + '/' + httpDir + '/' + resource.prepend + pathHelper(filePath).file), - httpUrl = '/' + destination.substr(destination.indexOf('temp/test/' + testId)), + httpUrl = '/' + destination.substr(destination.indexOf('temp-' + this.guid + '/test/' + testId)), instrumentable = resource.instrumentable; // Add to list of file mappings @@ -603,7 +610,15 @@ TestCase.prototype.parseTestFile = function(file) { function create(config) { var instance = new TestCase(); instance.config = config.config; - instance.init(config.path, config.id, config.runUrl, config.runPath, config.instrumentCodeCoverage, config.hotReload); + instance.init( + config.path, + config.id, + config.runUrl, + config.runPath, + config.instrumentCodeCoverage, + config.hotReload, + config.guid + ); return instance; } diff --git a/lib/util/fsHelper.js b/lib/util/fsHelper.js index ce89ef1..8acedec 100644 --- a/lib/util/fsHelper.js +++ b/lib/util/fsHelper.js @@ -18,10 +18,12 @@ /** * File system helpers */ -var fs = require('fs'), - i18n = require('./i18n'), - pathm = require('path'), - logger = require('./logger'); +var fs = require('fs'), + _ = require('underscore'), + i18n = require('./i18n'), + pathm = require('path'), + constants = require('../constants'), + logger = require('./logger'); /** * Search a specified folder, and all parent folders, for a venus config directory @@ -84,6 +86,60 @@ function getFirstValidPath (paths) { return validPath; } +/** + * Builds out the temp directory for the test. + * @param {object} + * @param {string='.venus_temp'} dirname the name of the directory + * @param {number=0} port the port the test is running on + */ +function getTempDir(options) { + options = options || {}; + _.defaults(options, { + dirname: '.venus_temp', + port: 0 + }); + + var tempDir = options.dirname; + + if (options.port) { + tempDir += options.port; + } + + return tempDir; +} + +/** + * Resolves the location of any sub directories within a test given test with a + * unique id. It does so by taking the unique id, alongside the temp directory, + * and returning a function to minimize the look up of the base temp directory. + * The function returned takes in a string of n arguments containing the rest of + * the path. If no unique id has been provided, it will fallback to the port + * provided in the constants. If for some stupid reason that doesn't exist, + * it'll fallback to an empty string. + * + * @example + * var tempDir = resolveTempDir(1234); + * // outputs: /temp/venus/run-1234/foo/bar + * tempDir('foo', 'bar'); + * + * @param {number} id unique id for the directory - falls back to port constant + * @return {function} the function when executes takes in the full path + */ +function resolveTempDir(id) { + var tempDir = getTempDir({ + dirname: constants.tempDir, + port: id || constants.port || '' + }), + start = [tempDir]; + + return function() { + var end = Array.prototype.slice.call(arguments); + return pathm.resolve.apply(pathm.resolve, start.concat(end)); + } +} + module.exports.getFirstValidPath = getFirstValidPath; module.exports.searchUpwardsForFile = searchUpwardsForFile; +module.exports.getTempDir = getTempDir; +module.exports.resolveTempDir = resolveTempDir; Object.seal(module.exports); diff --git a/locales/pirate.js b/locales/pirate.js index 9af0c93..b8280ad 100644 --- a/locales/pirate.js +++ b/locales/pirate.js @@ -157,5 +157,20 @@ "Ensures all other Venus processes are killed before starting": "Ensures all other Venus processes are killed before starting", "Using environment ie8": "Using environment ie8", "Using environment ie7": "Using environment ie7", - "Use https": "Use https" -} \ No newline at end of file + "Use https": "Use https", + "Error occurred at %s": "Error occurred at %s", + "Setting port of %d": "Setting port of %d", + "Removing directory found at %d": "Removing directory found at %d", + "Could not remove temp directory at %s": "Could not remove temp directory at %s", + "Could not remove %s when preparing static content": "Could not remove %s when preparing static content", + "Removing existing static content at %s": "Removing existing static content at %s", + "Could not create directory %s when preparing static content": "Could not create directory %s when preparing static content", + "Creating static content directory at %s": "Creating static content directory at %s", + "Removing directory found at %s": "Removing directory found at %s", + "Error occurred at %s.": "Error occurred at %s.", + "Gots no port - I go bye now": "Gots no port - I go bye now", + "Removes all tempoary directories": "Removes all tempoary directories", + "Temp directory at %s was removed": "Temp directory at %s was removed", + "Temp directory at %s did not exist so it could not be removed": "Temp directory at %s did not exist so it could not be removed", + "Temp directory at %s does not exist so it could not be removed": "Temp directory at %s does not exist so it could not be removed" +} diff --git a/package.json b/package.json index fc70442..32798bf 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "istanbul": "0.3.4", "json5": "0.1.0", "mkdirp": "0.3.5", + "node-uuid": "^1.4.3", "phantomjs": "1.9.7-8", "portscanner": "0.1.3", "rmrf": "1.0.2", diff --git a/test/lib/helpers.js b/test/lib/helpers.js index 0cbede7..068e01f 100644 --- a/test/lib/helpers.js +++ b/test/lib/helpers.js @@ -3,9 +3,11 @@ */ var path = require('path'), configHelper = require('../../lib/config'), - spawn = require('child_process').spawn, - JSON5 = require('json5'), - fs = require('fs'); + spawn = require('child_process').spawn, + JSON5 = require('json5'), + deferred = require('deferred'), + fstools = require('fs-tools'), + fs = require('fs'); module.exports.fakeCwd = function() { return path.resolve(__dirname + '/../data/sample_fs/projects/webapp/base/'); @@ -50,3 +52,37 @@ module.exports.path = function () { return path.resolve.apply(path, args); }; + +module.exports.dirOps = function(directory) { + return { + remove: function() { + var def = deferred(); + + fstools.remove(directory, function() { + def.resolve(); + }); + + return def.promise; + }, + + make: function() { + var def = deferred(); + + fstools.mkdir(directory, '0755', function() { + def.resolve(); + }); + + return def.promise; + }, + + exists: function() { + var def = deferred(); + + fs.exists(directory, function(exists) { + def.resolve(exists); + }); + + return def.promise; + } + }; +}; diff --git a/test/unit/node/Venus.spec.js b/test/unit/node/Venus.spec.js index 51b4553..8a54293 100644 --- a/test/unit/node/Venus.spec.js +++ b/test/unit/node/Venus.spec.js @@ -3,7 +3,10 @@ */ var expect = require('expect.js'), sinon = require('sinon'), - Venus = require('../../../Venus'); + Venus = require('../../../Venus'), + constants = require('../../../lib/constants'), + logger = require('../../../lib/util/logger'), + helpers = require('../../lib/helpers'); describe('Venus main', function() { it('should instantiate', function() { @@ -77,4 +80,53 @@ describe('Venus main', function() { app.start(argv); }); }); + + describe('cleaning temporary direction', function() { + var dirOps, + sandbox, + app, + dir = constants.baseTempDir; + + beforeEach(function(done) { + app = new Venus(); + sandbox = sinon.sandbox.create(); + sandbox.stub(logger, 'info'); + sandbox.stub(logger, 'warn'); + + dirOps = helpers.dirOps(dir); + dirOps.remove() + .then(function() { + done(); + }); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('should send a clean command', function() { + var argv = ['node', 'venus', 'clean']; + sandbox.spy(app, 'clean'); + app.start(argv); + sinon.assert.calledOnce(app.clean); + }); + + it('should remove the top level temp directory when being called', function(done) { + dirOps.make() + .then(app.clean) + .done(function(tempDir) { + expect(tempDir).to.be(dir); + done(); + }) + }); + + it('should attempt to remove a directory when it does not exist', function(done) { + dirOps.remove() + .then(app.clean) + .then(function() {}, function(e) { + expect(e).to.be.an(Error); + done(); + }); + }); + }); }); diff --git a/test/unit/node/executor.spec.js b/test/unit/node/executor.spec.js index 5b66f09..52c9f6e 100644 --- a/test/unit/node/executor.spec.js +++ b/test/unit/node/executor.spec.js @@ -11,7 +11,10 @@ var expect = require('expect.js'), executor = require('../../../lib/executor'), testcase = require('../../../lib/testcase'), path = require('path'), - fs = require('fs'); + fs = require('fs'), + fstools = require('fs-tools'), + deferred = require('deferred'), + logger = require('../../../lib/util/logger'); describe('lib/executor', function() { @@ -164,8 +167,13 @@ describe('lib/executor', function() { test = testPath('foo.js'), testcaseMock = sinon.mock(testcase), configMock = sinon.mock(config), + fakeGuid = 'abc123', hostname = require('../../../lib/constants').hostname; + // brute force set the guid to make sure what is accepted in the testcase + // will match + exec.guid = fakeGuid; + // Expectations mock.expects('getNextTestId').once().returns(1); configMock.expects('getConfig').once().returns('configFile'); @@ -176,7 +184,8 @@ describe('lib/executor', function() { runPath: exec.urlNamespace + '/1', instrumentCodeCoverate: exec.instrumentCodeCoverage, config: 'configFile', - hotReload: true + hotReload: true, + guid: fakeGuid }); exec.createTestObjects([test]); @@ -215,4 +224,135 @@ describe('lib/executor', function() { expect(process.exit.args[0][0]).to.be(1); }); }); + + describe('sendGenericError', function() { + beforeEach(function() { + sinon.stub(logger, 'error'); + sinon.stub(process, 'exit'); + }); + + afterEach(function() { + logger.error.restore(); + process.exit.restore(); + }); + + it('should send an error and kill the process', function() { + var exec = new executor.Executor(); + exec.sendGenericError('someMethod', new Error('way to go dummy')); + sinon.assert.calledTwice(logger.error); + sinon.assert.calledWith(logger.error, 'Error occurred at someMethod.'); + sinon.assert.calledWith(logger.error, 'Error: way to go dummy'); + sinon.assert.calledWith(process.exit, 1); + }); + }); + + describe('setPort', function() { + var exec; + + beforeEach(function() { + exec = new executor.Executor(); + }); + + it('should accept a valid port number', function(done) { + var port = 1234; + + exec.setPort(port).then(function() { + expect(exec.port).to.be(port); + done(); + }); + }); + + it('should reject the promise when an invalid port is passed in', function(done) { + exec.setPort('I am a bad port').catch(function(e) { + expect(e.message).to.contain('no port'); + done(); + }); + }); + + it('should reject the promise when no port is passed in', function(done) { + exec.setPort().catch(function(e) { + expect(e.message).to.contain('no port'); + done(); + }); + }); + }); + + describe('removeExistingTests', function() { + var exec, dirOps; + + beforeEach(function(done) { + var testDir; + + exec = new executor.Executor(); + exec.port = 1234; + exec.setupVenusTemp(); + testDir = exec.venusTemp('test'); + + dirOps = testHelper.dirOps(testDir); + + dirOps.exists() + .then(dirOps.remove, done) + .then(done); + }); + + afterEach(function(done) { + dirOps.exists() + .then(dirOps.remove, done) + .then(done); + }); + + it('should remove the directory if it already existed', function(done) { + dirOps.make() + .then(dirOps.exists) + .then(function(exists) { + expect(exists).to.be.ok(); + }) + .then(function() { + exec + .removeExistingTests() + .then(dirOps.exists) + .then(function(exists) { + expect(exists).to.not.be.ok(); + done(); + }); + }); + }); + + it('should attempt to remove the directory even if it does not exist', function(done) { + dirOps.remove() + .then(dirOps.exists) + .then(function(exists) { + expect(exists).to.not.be.ok(); + }) + .then(function() { + exec + .removeExistingTests() + .then(dirOps.exists) + .then(function(exists) { + expect(exists).to.not.be.ok(); + done(); + }); + }); + }); + }); + + describe('passing a guid', function() { + it('should contain a guid on a test level', function (done) { + var conf = testHelper.testConfig(), + exec = new executor.Executor(conf), + fakeGuid = 'abc123', + options = { + test: testPath('parse_comments'), + guid: fakeGuid, + homeFolder: path.resolve(__dirname, '..', '..') + }; + + exec.init(options).then(function () { + var guid = exec.testgroup.testArray[0].guid; + expect(exec.guid).to.be(fakeGuid); + expect(guid).to.be(fakeGuid); + done(); + }); + }); + }); }); diff --git a/test/unit/node/test_suite.js b/test/unit/node/test_suite.js index 8db7119..e9dd155 100644 --- a/test/unit/node/test_suite.js +++ b/test/unit/node/test_suite.js @@ -11,3 +11,4 @@ require('./Venus.spec'); require('./util/commentsParser.spec'); require('./util/pathHelper.spec'); +require('./util/fsHelper.spec'); diff --git a/test/unit/node/testcase.spec.js b/test/unit/node/testcase.spec.js index 3fdeb85..0e99dcd 100644 --- a/test/unit/node/testcase.spec.js +++ b/test/unit/node/testcase.spec.js @@ -3,20 +3,22 @@ */ var testHelper = require('../../lib/helpers'), testcase = require('../../../lib/testcase'), - logger = require('../../../lib/util/logger'), - sinon = require('sinon'), + logger = require('../../../lib/util/logger'), + sinon = require('sinon'), annotation = testcase.annotation, path = require('path'), + constants = require('../../../lib/constants'), expect = require('expect.js'); describe('lib/testcase', function () { - var test, files, testData; + var test, files, testData, fakeGuid = 'abc123'; beforeEach(function () { test = new testcase.TestCase(); test.config = testHelper.testConfig(); test.directory = testHelper.sampleTests(); test.id = 1; + test.guid = fakeGuid; }); @@ -104,7 +106,7 @@ describe('lib/testcase', function () { expect(files).to.be.ok(); expect(files).to.be.an('array'); - expect(files[4].url).to.be('/temp/test/1/includes/_.test.js'); + expect(files[4].url).to.be('/run-' + fakeGuid + '/test/1/includes/_.test.js'); expect(files[4].fs).to.contain('/test/data/test.js'); expect(files[6].fs).to.contain('/test/data/sample_tests/bar.js'); expect(files[6].url).to.contain('/bar.js'); @@ -121,7 +123,7 @@ describe('lib/testcase', function () { expect(files).to.be.ok(); expect(files).to.be.an('array'); - expect(files[4].url).to.be('/temp/test/1/includes/_.test-file.js'); + expect(files[4].url).to.be('/run-' + fakeGuid + '/test/1/includes/_.test-file.js'); expect(files[4].fs).to.contain('/test/data/test-file.js'); }); @@ -135,8 +137,8 @@ describe('lib/testcase', function () { expect(files).to.be.ok(); expect(files).to.be.an('array'); - expect(files[2].url).to.be('/temp/test/1/lib/file1.js'); - expect(files[3].url).to.be('/temp/test/1/lib/file2.js'); + expect(files[2].url).to.be('/run-' + fakeGuid + '/test/1/lib/file1.js'); + expect(files[3].url).to.be('/run-' + fakeGuid + '/test/1/lib/file2.js'); }); it('should load group includes', function() { @@ -149,7 +151,7 @@ describe('lib/testcase', function () { expect(files).to.be.ok(); expect(files).to.be.an('array'); - expect(files[4].url).to.be('/temp/test/1/lib/file3.js'); + expect(files[4].url).to.be('/run-' + fakeGuid + '/test/1/lib/file3.js'); }); it('should handle different paths with same filename', function() { @@ -162,9 +164,9 @@ describe('lib/testcase', function () { expect(files).to.be.ok(); expect(files).to.be.an('array'); - expect(files[4].url).to.be('/temp/test/1/includes/fileA.js'); - expect(files[5].url).to.be('/temp/test/1/includes/_.fileA.js'); - expect(files[6].url).to.be('/temp/test/1/includes/_.prod.fileA.js'); + expect(files[4].url).to.be('/run-' + fakeGuid + '/test/1/includes/fileA.js'); + expect(files[5].url).to.be('/run-' + fakeGuid + '/test/1/includes/_.fileA.js'); + expect(files[6].url).to.be('/run-' + fakeGuid + '/test/1/includes/_.prod.fileA.js'); }); it('should handle base paths', function () { @@ -184,16 +186,9 @@ describe('lib/testcase', function () { }); it('should use the correct http root for test files', function () { - var httpRoot = test.getHttpRoot(1), - home; - - if (process.platform === 'win32') { - home = process.env['USERPROFILE']; - } else { - home = process.env['HOME']; - } + var httpRoot = test.getHttpRoot(1); - expect(httpRoot).to.be(path.resolve(home, '.venus_temp', 'test', '1')); + expect(httpRoot).to.be(path.resolve(constants.tempDir + fakeGuid, 'test', '1')); }); }); @@ -223,4 +218,13 @@ describe('lib/testcase', function () { }); }); }); + + describe('getHttpRoot', function() { + it('should get the full http root of a given test', function() { + var httpRoot = test.getHttpRoot(1), + expectedHttpRoot = constants.tempDir + fakeGuid + '/test/1' + + expect(httpRoot).to.be(expectedHttpRoot); + }); + }); }); diff --git a/test/unit/node/util/fsHelper.spec.js b/test/unit/node/util/fsHelper.spec.js new file mode 100644 index 0000000..343336a --- /dev/null +++ b/test/unit/node/util/fsHelper.spec.js @@ -0,0 +1,69 @@ +/** + * @author LinkedIn + */ +var expect = require('expect.js'), + constants = require('../../../../lib/constants'), + fsHelper = require('../../../../lib/util/fsHelper'); + +describe('lib/util/fsHelper', function() { + describe('getTempDir()', function() { + var getTempDir = fsHelper.getTempDir; + + it('should exist as getTempDir', function() { + expect(getTempDir).to.be.ok(); + }); + + it('should send back the defaults frmo getTempDir', function() { + expect(getTempDir()).to.be('.venus_temp'); + }); + + it('should accept a directory name', function() { + var dirname = '.custom_temp_dir'; + expect(getTempDir({dirname: dirname})).to.be(dirname); + }); + + it('should be able to accept a port with the default venus dir', function() { + var port = 1234; + expect(getTempDir({port: port})).to.be('.venus_temp' + 1234); + }); + + it('should be able to accept both a directory name and port', function() { + var dirname = 'leland-', port = 8000; + expect(getTempDir({dirname: dirname, port: port})).to.be(dirname + port); + }); + }); + + describe('resolveTempDir()', function() { + var basePath = constants.tempDir; + + it('should exist as resolveTempDir', function() { + expect(fsHelper.resolveTempDir).to.be.ok(); + }); + + it('should resolve a path when provided no port and no argument', function() { + var resolveTempDir = fsHelper.resolveTempDir(); + expect(resolveTempDir()).to.be(basePath + constants.port); + }); + + it('should resolve a path when provided a port and no arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir()).to.be(basePath + '1234'); + }); + + it('should resolve a path when provided one argument', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland')).to.be(basePath + '1234/leland'); + }); + + it('should resolve a path when provided two arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland', 'fiona')).to.be(basePath + '1234/leland/fiona'); + }); + + it('should resolve a path when provided three arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland', 'fiona', 'erin')).to.be(basePath + '1234/leland/fiona/erin'); + }); + }); +}); + diff --git a/test/unit/util/fsHelper.spec.js b/test/unit/util/fsHelper.spec.js new file mode 100644 index 0000000..04b037d --- /dev/null +++ b/test/unit/util/fsHelper.spec.js @@ -0,0 +1,69 @@ +/** + * @author LinkedIn + */ +var expect = require('expect.js'), + constants = require('../../../lib/constants'), + fsHelper = require('../../../lib/util/fsHelper'); + +describe('lib/util/fsHelper', function() { + describe('getTempDir()', function() { + var getTempDir = fsHelper.getTempDir; + + it('should exist as getTempDir', function() { + expect(getTempDir).to.be.ok(); + }); + + it('should send back the defaults frmo getTempDir', function() { + expect(getTempDir()).to.be('.venus_temp'); + }); + + it('should accept a directory name', function() { + var dirname = '.custom_temp_dir'; + expect(getTempDir({dirname: dirname})).to.be(dirname); + }); + + it('should be able to accept a port with the default venus dir', function() { + var port = 1234; + expect(getTempDir({port: port})).to.be('.venus_temp' + 1234); + }); + + it('should be able to accept both a directory name and port', function() { + var dirname = 'leland-', port = 8000; + expect(getTempDir({dirname: dirname, port: port})).to.be(dirname + port); + }); + }); + + describe('resolveTempDir()', function() { + var basePath = constants.tempDir; + + it('should exist as resolveTempDir', function() { + expect(fsHelper.resolveTempDir).to.be.ok(); + }); + + it('should resolve a path when provided no port and no argument', function() { + var resolveTempDir = fsHelper.resolveTempDir(); + expect(resolveTempDir()).to.be(basePath + constants.port); + }); + + it('should resolve a path when provided a port and no arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir()).to.be(basePath + '1234'); + }); + + it('should resolve a path when provided one argument', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland')).to.be(basePath + '1234/leland'); + }); + + it('should resolve a path when provided two arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland', 'fiona')).to.be(basePath + '1234/leland/fiona'); + }); + + it('should resolve a path when provided three arguments', function() { + var resolveTempDir = fsHelper.resolveTempDir(1234); + expect(resolveTempDir('leland', 'fiona', 'erin')).to.be(basePath + '1234/leland/fiona/erin'); + }); + }); +}); +