diff --git a/README.md b/README.md index ae738c4c2b..854c01e358 100755 --- a/README.md +++ b/README.md @@ -25,6 +25,16 @@ To run the app in development: - `npm run release` +### Unit Tests + +- `npm test` + +### Integration Tests + +Note that integration tests need to be run a Mac and _will_ remove your existing Boot2Docker VM, containers etc. + +- `npm run test:integration` + ## Uninstalling - Remove Kitematic.app diff --git a/browser/main.js b/browser/main.js index 8c31209cdd..ee51d8d578 100644 --- a/browser/main.js +++ b/browser/main.js @@ -11,10 +11,18 @@ var BrowserWindow = require('browser-window'); var ipc = require('ipc'); var argv = require('minimist')(process.argv); +var saveVMOnQuit = false; process.env.NODE_PATH = __dirname + '/../node_modules'; +process.env.RESOURCES_PATH = __dirname + '/../resources'; process.chdir(path.join(__dirname, '..')); +if (argv.integration) { + process.env.TEST_TYPE = 'integration'; +} else { + process.env.TEST_TYPE = 'test'; +} + app.on('activate-with-no-open-windows', function () { if (mainWindow) { mainWindow.show(); @@ -23,38 +31,35 @@ app.on('activate-with-no-open-windows', function () { }); app.on('ready', function() { - var windowOptions = { + mainWindow = new BrowserWindow({ width: 1000, height: 700, 'min-width': 1000, 'min-height': 700, resizable: true, - frame: false - }; + frame: false, + show: false + }); - mainWindow = new BrowserWindow(windowOptions); - mainWindow.hide(); if (argv.test) { - mainWindow.loadUrl('file://' + __dirname + '/../specs/specs.html'); + mainWindow.loadUrl('file://' + __dirname + '/../tests/tests.html'); } else { mainWindow.loadUrl('file://' + __dirname + '/../build/index.html'); + app.on('will-quit', function (e) { + if (saveVMOnQuit) { + exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); + } + }); } - process.on('uncaughtException', app.quit); - - var saveVMOnQuit = false; - app.on('will-quit', function (e) { - if (saveVMOnQuit) { - exec('VBoxManage controlvm boot2docker-vm savestate', function (stderr, stdout, code) {}); - } - }); - mainWindow.webContents.on('new-window', function (e) { e.preventDefault(); }); mainWindow.webContents.on('did-finish-load', function() { - mainWindow.show(); + if (!argv.test) { + mainWindow.show(); + } mainWindow.focus(); mainWindow.setTitle(''); @@ -92,12 +97,12 @@ app.on('ready', function() { autoUpdater.quitAndInstall(); } }); + + autoUpdater.checkForUpdates(); } ipc.on('vm', function (event, arg) { saveVMOnQuit = arg; }); - - autoUpdater.checkForUpdates(); }); }); diff --git a/deps b/deps index e01dfa4f77..7e1cbc3cc2 100755 --- a/deps +++ b/deps @@ -1,36 +1,15 @@ #!/bin/bash - BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PLATFORM='unknown' -unamestr=`uname` -if [[ "$unamestr" == 'Linux' ]]; then - PLATFORM='linux' -elif [[ "$unamestr" == 'Darwin' ]]; then - PLATFORM='darwin' -fi - -export BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") -export BOOT2DOCKER_CLI_VERSION_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION -export ATOM_SHELL_VERSION=$(node -pe "JSON.parse(process.argv[1])['atom-shell-version']" "$(cat $BASE/package.json)") -export ATOM_SHELL_VERSION_FILE=atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip - -mkdir -p $BASE/cache/$PLATFORM -pushd $BASE/cache/$PLATFORM > /dev/null -if [ ! -f $ATOM_SHELL_VERSION_FILE ]; then - echo "-----> Downloading atom-shell..." - rm -rf atom-shell-* - curl -L -o $ATOM_SHELL_VERSION_FILE https://github.com/atom/atom-shell/releases/download/v$ATOM_SHELL_VERSION/atom-shell-v$ATOM_SHELL_VERSION-$PLATFORM-x64.zip - unzip $ATOM_SHELL_VERSION_FILE -fi -popd > /dev/null +BOOT2DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['boot2docker-version']" "$(cat $BASE/package.json)") +BOOT2DOCKER_CLI_FILE=boot2docker-$BOOT2DOCKER_CLI_VERSION pushd $BASE/resources > /dev/null -if [ ! -f $BOOT2DOCKER_CLI_VERSION_FILE ]; then +if [ ! -f $BOOT2DOCKER_CLI_FILE ]; then echo "-----> Downloading Boot2docker CLI..." rm -rf boot2docker-* - curl -L -o $BOOT2DOCKER_CLI_VERSION_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v${BOOT2DOCKER_CLI_VERSION}/boot2docker-v${BOOT2DOCKER_CLI_VERSION}-darwin-amd64 - chmod +x $BOOT2DOCKER_CLI_VERSION_FILE + curl -L -o $BOOT2DOCKER_CLI_FILE https://github.com/boot2docker/boot2docker-cli/releases/download/v${BOOT2DOCKER_CLI_VERSION}/boot2docker-v${BOOT2DOCKER_CLI_VERSION}-darwin-amd64 + chmod +x $BOOT2DOCKER_CLI_FILE fi popd > /dev/null diff --git a/resources/virtualbox-4.3.18.pkg b/gulp similarity index 100% rename from resources/virtualbox-4.3.18.pkg rename to gulp diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000000..546108ce42 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,170 @@ +var concat = require('gulp-concat'); +var cssmin = require('gulp-cssmin'); +var downloadatomshell = require('gulp-download-atom-shell'); +var fs = require('fs'); +var gulp = require('gulp'); +var gulpif = require('gulp-if'); +var gutil = require('gulp-util'); +var http = require('http'); +var less = require('gulp-less'); +var livereload = require('gulp-livereload'); +var plumber = require('gulp-plumber'); +var react = require('gulp-react'); +var runSequence = require('run-sequence'); +var shell = require('gulp-shell'); +var sourcemaps = require('gulp-sourcemaps'); +var packagejson = require('./package.json'); + +var dependencies = Object.keys(packagejson.dependencies); +var devDependencies = Object.keys(packagejson.devDependencies); +var options = { + dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1, + test: process.argv.indexOf('test') !== -1, + integration: process.argv.indexOf('--integration') !== -1, + filename: 'Kitematic.app', + name: 'Kitematic' +}; + +gulp.task('js', function () { + gulp.src('src/**/*.js') + .pipe(plumber(function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); + // emit the end event, to properly end the task + this.emit('end'); + })) + .pipe(react()) + .pipe(gulp.dest((options.dev || options.test) ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('images', function() { + return gulp.src('images/*') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('styles', function () { + return gulp.src('styles/main.less') + .pipe(plumber(function(error) { + gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message)); + // emit the end event, to properly end the task + this.emit('end'); + })) + .pipe(gulpif(options.dev, sourcemaps.init())) + .pipe(less()) + .pipe(gulpif(options.dev, sourcemaps.write())) + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(!options.dev, cssmin())) + .pipe(concat('main.css')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('download', function (cb) { + downloadatomshell({ + version: packagejson['atom-shell-version'], + outputDir: 'cache' + }, cb); +}); + +gulp.task('copy', function () { + gulp.src('index.html') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); + + gulp.src('fonts/**') + .pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.filename + '/Contents/Resources/app/build')) + .pipe(gulpif(options.dev, livereload())); +}); + +gulp.task('dist', function (cb) { + var stream = gulp.src('').pipe(shell([ + 'rm -Rf ./dist', + 'mkdir -p ./dist/osx', + 'cp -R ./cache/Atom.app ./dist/osx/<%= filename %>', + 'mv ./dist/osx/<%= filename %>/Contents/MacOS/Atom ./dist/osx/<%= filename %>/Contents/MacOS/<%= name %>', + 'mkdir -p ./dist/osx/<%= filename %>/Contents/Resources/app', + 'cp -R browser dist/osx/<%= filename %>/Contents/Resources/app', + 'cp package.json dist/osx/<%= filename %>/Contents/Resources/app/', + 'mkdir -p dist/osx/<%= filename %>/Contents/Resources/app/resources', + 'cp -v resources/* dist/osx/<%= filename %>/Contents/Resources/app/resources/ || :', + 'cp kitematic.icns dist/osx/<%= filename %>/Contents/Resources/atom.icns', + '/usr/libexec/PlistBuddy -c "Set :CFBundleVersion <%= version %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier <%= bundle %>" dist/osx/<%= filename %>/Contents/Info.plist', + '/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist' + ], { + templateData: { + filename: options.filename, + name: options.name, + version: packagejson.version, + bundle: 'com.kitematic.app' + } + })); + + dependencies.forEach(function (d) { + stream = stream.pipe(shell([ + 'cp -R node_modules/' + d + ' dist/osx/<%= filename %>/Contents/Resources/app/node_modules/' + ], { + templateData: { + filename: options.filename + } + })); + }); + + return stream; +}); + +gulp.task('sign', function () { + try { + var signing_identity = fs.readFileSync('./identity', 'utf8').trim(); + return gulp.src('').pipe(shell([ + 'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.filename + ], { + cwd: './dist/osx/' + })); + } catch (error) { + gutil.log(gutil.colors.red('Error: ' + error.message)); + } +}); + +gulp.task('zip', function () { + return gulp.src('').pipe(shell([ + 'ditto -c -k --sequesterRsrc --keepParent ' + options.filename + ' ' + options.name + '-' + packagejson.version + '.zip' + ], { + cwd: './dist/osx/' + })); +}); + +gulp.task('release', function () { + runSequence('download', 'dist', ['copy', 'images', 'js', 'styles'], 'sign', 'zip'); +}); + +gulp.task('test', ['download', 'copy', 'js'], function () { + var env = process.env; + env.NODE_ENV = 'test'; + if (options.integration) { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test --integration'], { + env: env + })); + } else { + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom . --test'], { + env: env + })); + } +}); + +gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () { + gulp.watch('src/**/*.js', ['js']); + gulp.watch('index.html', ['copy']); + gulp.watch('styles/**/*.less', ['styles']); + gulp.watch('images/**', ['images']); + + livereload.listen(); + + var env = process.env; + env.NODE_ENV = 'development'; + gulp.src('').pipe(shell(['./cache/Atom.app/Contents/MacOS/Atom .'], { + env: env + })); +}); diff --git a/package.json b/package.json index de2dd005a1..9371f56922 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,12 @@ }, "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { - "start": "rsync ./index.html ./build/ && rsync ./fonts/* ./build/ && rsync ./images/* ./build && jsx --watch src/ build/ & wess -w -m -i ./styles/main.less -o ./build/main.css & wiper -w ./build/**/*.* & npm run start:$(uname) && kill $(pgrep node)", - "start:Darwin": "NODE_ENV=development ./cache/darwin/Atom.app/Contents/MacOS/Atom .", - "start:Linux": "NODE_ENV=development ./cache/linux/atom .", - "test": "jsx --watch src build/ & rsync ./images/* ./build && wess -m -i ./styles/main.less -o ./build/main.css && npm run test:$(uname) && kill $(pgrep node)", - "test:Darwin": "NODE_ENV=test ./cache/darwin/Atom.app/Contents/MacOS/Atom . --test", - "test:Linux": "NODE_ENV=test ./cache/darwin/atom .", - "release": "./release" + "start": "gulp", + "test": "gulp test --silent", + "test:integration": "gulp test --silent --integration", + "all-tests": "npm test && npm run integration-tests", + "release": "gulp run release", + "preinstall": "./deps" }, "licenses": [ { @@ -32,15 +31,17 @@ "debug" ] }, - "boot2docker-version": "1.3.2", + "boot2docker-version": "1.4.1", "atom-shell-version": "0.21.0", + "virtualbox-version": "4.3.20", + "virtualbox-filename": "VirtualBox-4.3.20-96996-OSX.dmg", + "virtualbox-required-version": "4.3.18", "dependencies": { "ansi-to-html": "0.2.0", "async": "^0.9.0", - "bugsnag-js": "^2.4.6", + "bugsnag-js": "git+https://git@github.com/bugsnag/bugsnag-js", "dockerode": "2.0.4", "exec": "0.1.2", - "gulp-react": "^2.0.0", "jest-cli": "^0.2.1", "jquery": "^2.1.3", "minimist": "^1.1.0", @@ -57,15 +58,33 @@ "underscore": "^1.7.0" }, "devDependencies": { + "browserify": "^6.2.0", "ecstatic": "^0.5.8", "glob": "^4.0.6", - "jasmine-node": "^1.14.5", - "jasmine-reporters": "^1.0.1", - "jasmine-tagged": "^1.1.2", - "jasmine-terminal-reporter": "^0.9.1", - "react-tools": "^0.12.2", - "wess": "^0.2.2", - "wiper": "^1.0.2", + "gulp": "^3.8.10", + "gulp-atom": "0.0.5", + "gulp-concat": "^2.3.4", + "gulp-cssmin": "^0.1.6", + "gulp-download-atom-shell": "0.0.4", + "gulp-if": "^1.2.4", + "gulp-imagemin": "^2.0.0", + "gulp-less": "^2.0.1", + "gulp-livereload": "^2.1.1", + "gulp-notify": "^1.4.2", + "gulp-plumber": "^0.6.6", + "gulp-react": "^2.0.0", + "gulp-shell": "^0.2.11", + "gulp-sourcemaps": "^1.2.8", + "gulp-streamify": "0.0.5", + "gulp-uglify": "^0.3.1", + "gulp-uglifyjs": "^0.5.0", + "gulp-util": "^3.0.0", + "reactify": "^0.15.2", + "rimraf": "^2.2.8", + "run-sequence": "^1.0.2", + "time-require": "^0.1.2", + "vinyl-source-stream": "^0.1.1", + "watchify": "^2.1.1", "zombie": "^2.5.1" } } diff --git a/release b/release deleted file mode 100755 index 7aa58dac37..0000000000 --- a/release +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -BASE="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export NAME=$(node -pe "JSON.parse(process.argv[1])['name']" "$(cat $BASE/package.json)") -export VERSION=$(node -pe "JSON.parse(process.argv[1])['version']" "$(cat $BASE/package.json)") -export FILENAME=$NAME.app -export BUNDLE=com.kitematic.app - -rm -Rf ./dist -mkdir -p ./dist/osx -cp -R ./cache/Atom.app ./dist/osx/$FILENAME -mv ./dist/osx/$FILENAME/Contents/MacOS/Atom ./dist/osx/$FILENAME/Contents/MacOS/$NAME -mkdir -p ./dist/osx/$FILENAME/Contents/Resources/app -cp -R browser dist/osx/$FILENAME/Contents/Resources/app -cp package.json dist/osx/$FILENAME/Contents/Resources/app/ -mkdir -p dist/osx/$FILENAME/Contents/Resources/app/resources -mkdir -p dist/osx/$FILENAME/Contents/Resources/app/node_modules -cp -v resources/* dist/osx/$FILENAME/Contents/Resources/app/resources/ || : -cp kitematic.icns dist/osx/$FILENAME/Contents/Resources/atom.icns -/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $NAME" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleName $NAME" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE" dist/osx/$FILENAME/Contents/Info.plist -/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable $NAME" dist/osx/$FILENAME/Contents/Info.plist - -rsync ./index.html ./dist/osx/$FILENAME/Contents/Resources/app/build/ -rsync ./fonts/* ./dist/osx/$FILENAME/Contents/Resources/app/build/ -rsync ./images/* ./dist/osx/$FILENAME/Contents/Resources/app/build/ -jsx src/ ./dist/osx/$FILENAME/Contents/Resources/app/build/ -wess -m -i ./styles/main.less -o ./dist/osx/$FILENAME/Contents/Resources/app/build/main.css - -if [ -f $BASE/identity ]; then - codesign --deep --force --verbose --sign "$(cat $BASE/identity)" ./dist/osx/$FILENAME -fi - -ditto -c -k --sequesterRsrc --keepParent ./dist/osx/$FILENAME ./dist/osx/$NAME-$VERSION.zip diff --git a/resources/cocoasudo b/resources/cocoasudo new file mode 100755 index 0000000000..ccf0bf8aa1 Binary files /dev/null and b/resources/cocoasudo differ diff --git a/specs/ContainerStore-spec.js b/specs/ContainerStore-spec.js deleted file mode 100755 index 5c8edb3a4c..0000000000 --- a/specs/ContainerStore-spec.js +++ /dev/null @@ -1,8 +0,0 @@ -var ContainerStore = require('../build/ContainerStore'); -var TestUtils = require('react/addons').TestUtils; - -describe('ContainerStore', function() { - it('returns an empty array initially', function() { - expect(ContainerStore.containers()).toEqual({}); - }); -}); diff --git a/src/Boot2Docker.js b/src/Boot2Docker.js index 8e64baedc4..669a3f8f9f 100644 --- a/src/Boot2Docker.js +++ b/src/Boot2Docker.js @@ -84,7 +84,13 @@ var Boot2Docker = { cmdExec([Boot2Docker.command(), 'upgrade'], callback); }, ip: function (callback) { - cmdExec([Boot2Docker.command(), 'ip'], callback); + exec([Boot2Docker.command(), 'ip'], function (stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + callback(null, stdout.trim().replace('\n', '')); + } + }); }, erase: function (callback) { var VMFileLocation = path.join(homeDir(), 'VirtualBox\\ VMs/boot2docker-vm'); diff --git a/src/ContainerDetails.react.js b/src/ContainerDetails.react.js index d753ef7c05..8ee442168b 100644 --- a/src/ContainerDetails.react.js +++ b/src/ContainerDetails.react.js @@ -76,7 +76,7 @@ var ContainerDetails = React.createClass({ var $viewPopover = $(this.getDOMNode()).find('.popover-view'); var $volumePopover = $(this.getDOMNode()).find('.popover-volume'); - if ($viewDropdown && $volumeDropdown && $viewPopover && $volumePopover) { + if ($viewDropdown.offset() && $volumeDropdown.offset()) { $viewPopover.offset({ top: $viewDropdown.offset().top + 32, left: $viewDropdown.offset().left - ($viewPopover.outerWidth() / 2) + 14 @@ -94,16 +94,19 @@ var ContainerDetails = React.createClass({ return; } this.setState({ + progress: ContainerStore.progress(this.getParams().name), env: ContainerUtil.env(container), }); var ports = ContainerUtil.ports(container); var webPorts = ['80', '8000', '8080', '3000', '5000', '2368']; + console.log(ports); this.setState({ ports: ports, defaultPort: _.find(_.keys(ports), function (port) { return webPorts.indexOf(port) !== -1; }) }); + console.log(this.state); this.updateLogs(); }, updateLogs: function (name) { @@ -134,6 +137,7 @@ var ContainerDetails = React.createClass({ handleView: function () { if (this.state.defaultPort) { console.log(this.state.defaultPort); + console.log(this.state.ports[this.state.defaultPort].url); exec(['open', this.state.ports[this.state.defaultPort].url], function (err) { if (err) { throw err; } }); @@ -159,11 +163,6 @@ var ContainerDetails = React.createClass({ console.log(err); }); }, - handleRestart: function () { - ContainerStore.restart(this.props.container.Name, function (err) { - console.log(err); - }); - }, handleTerminal: function () { var container = this.props.container; var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ '); @@ -310,6 +309,13 @@ var ContainerDetails = React.createClass({ disabled: !this.props.container.State.Running }); + var restartButtonClass = React.addons.classSet({ + btn: true, + 'btn-action': true, + 'with-icon': true, + disabled: this.props.container.State.Restarting + }); + var viewButtonClass = React.addons.classSet({ btn: true, 'btn-action': true, @@ -444,7 +450,7 @@ var ContainerDetails = React.createClass({ Volumes
- Restart + Restart
Terminal diff --git a/src/ContainerModal.react.js b/src/ContainerModal.react.js index b9d862a7dd..64543d6f5f 100644 --- a/src/ContainerModal.react.js +++ b/src/ContainerModal.react.js @@ -83,6 +83,9 @@ var ContainerModal = React.createClass({ }, handleClick: function (name, event) { ContainerStore.create(name, 'latest', function (err, containerName) { + if (err) { + throw err; + } this.props.onRequestHide(); }.bind(this)); }, diff --git a/src/ContainerStore.js b/src/ContainerStore.js index 3f304fad0a..82f3eaa4e2 100644 --- a/src/ContainerStore.js +++ b/src/ContainerStore.js @@ -18,7 +18,6 @@ var _progress = {}; var _logs = {}; var _streams = {}; var _muted = {}; -var _config = {}; var ContainerStore = assign(EventEmitter.prototype, { CLIENT_CONTAINER_EVENT: 'client_container', @@ -250,7 +249,12 @@ var ContainerStore = assign(EventEmitter.prototype, { init: function (callback) { // TODO: Load cached data from db on loading this.fetchAllContainers(function (err) { - callback(); + if (err) { + callback(err); + return; + } else { + callback(); + } this.emit(this.CLIENT_CONTAINER_EVENT); this._resumePulling(); this._startListeningToEvents(); @@ -366,7 +370,6 @@ var ContainerStore = assign(EventEmitter.prototype, { }); stream.on('end', function () { delete _streams[name]; - console.log('end', name); }); }); }, @@ -381,6 +384,10 @@ var ContainerStore = assign(EventEmitter.prototype, { if (!data) { // Pull image self._createPlaceholderContainer(imageName, containerName, function (err, container) { + if (err) { + callback(err); + return; + } _containers[containerName] = container; self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create'); _muted[containerName] = true; diff --git a/src/ContainerUtil.js b/src/ContainerUtil.js index 86aebd701a..61159f7186 100644 --- a/src/ContainerUtil.js +++ b/src/ContainerUtil.js @@ -15,9 +15,8 @@ var ContainerUtil = { ports: function (container, callback) { var res = {}; var ip = docker.host; - console.log(container); _.each(container.NetworkSettings.Ports, function (value, key) { - var dockerPort = key; + var dockerPort = key.split('/')[0]; var localUrl = null; var localUrlDisplay = null; if (value && value.length) { diff --git a/src/Docker.js b/src/Docker.js index ac9701546a..6c8768bf2e 100644 --- a/src/Docker.js +++ b/src/Docker.js @@ -12,8 +12,7 @@ var Docker = { return; } this._client = new dockerode({ - protocol: 'https', - host: this.host, + host: '192.168.59.103', port: 2376, ca: fs.readFileSync(path.join(certDir, 'ca.pem')), cert: fs.readFileSync(path.join(certDir, 'cert.pem')), diff --git a/src/Main.js b/src/Main.js index ae2d5bc526..e58c34486a 100644 --- a/src/Main.js +++ b/src/Main.js @@ -2,23 +2,22 @@ var React = require('react'); var Router = require('react-router'); var RetinaImage = require('react-retina-image'); var async = require('async'); -var docker = require('./docker'); +var docker = require('./Docker'); var router = require('./router'); var boot2docker = require('./boot2docker'); var ContainerStore = require('./ContainerStore'); +var SetupStore = require('./ContainerStore'); var Menu = require('./Menu'); var remote = require('remote'); var app = remote.require('app'); var ipc = require('ipc'); - var Route = Router.Route; var NotFoundRoute = Router.NotFoundRoute; var DefaultRoute = Router.DefaultRoute; var Link = Router.Link; var RouteHandler = Router.RouteHandler; - if (process.env.NODE_ENV === 'development') { var script = document.createElement('script'); script.type = 'text/javascript'; @@ -28,13 +27,25 @@ if (process.env.NODE_ENV === 'development') { } if (!window.location.hash.length || window.location.hash === '#/') { - router.run(function (Handler) { - React.render(, document.body); + SetupStore.run(function (err) { + boot2docker.ip(function (err, ip) { + if (err) console.log(err); + docker.setHost(ip); + router.transitionTo('containers'); + ContainerStore.init(function (err) { + if (err) console.log(err); + router.run(function (Handler) { + React.render(, document.body); + }); + }); + }); }); } else { boot2docker.ip(function (err, ip) { + if (err) console.log(err); docker.setHost(ip); - ContainerStore.init(function () { + ContainerStore.init(function (err) { + if (err) console.log(err); router.run(function (Handler) { React.render(, document.body); }); diff --git a/src/Setup.react.js b/src/Setup.react.js index 5a77893b5a..c86f389ed4 100644 --- a/src/Setup.react.js +++ b/src/Setup.react.js @@ -5,133 +5,9 @@ var async = require('async'); var assign = require('object-assign'); var fs = require('fs'); var path = require('path'); -var boot2docker = require('./Boot2Docker'); var virtualbox = require('./Virtualbox'); var util = require('./Util'); -var docker = require('./Docker'); -var ContainerStore = require('./ContainerStore'); - -var setupSteps = [ - { - run: function (callback, progressCallback) { - console.log(util.supportDir()); - var installed = virtualbox.installed(); - if (!installed) { - util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(util.supportDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (!virtualbox.installed()) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - // Download a newer version of Virtualbox - util.downloadFile(Setup.BASE_URL + virtualbox.INSTALLER_FILENAME, path.join(util.getResourceDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { - if (err) {callback(err); return;} - virtualbox.kill(function (err) { - if (err) {callback(err); return;} - virtualbox.install(function (err) { - if (err) {callback(err); return;} - virtualbox.version(function (err, installedVersion) { - if (err) {callback(err); return;} - if (util.compareVersions(installedVersion, virtualbox.REQUIRED_VERSION) < 0) { - callback('VirtualBox could not be installed. The installation either failed or was cancelled. Please try closing all VirtualBox instances and try again.'); - } else { - callback(err); - } - }); - }); - }); - }, function (progress) { - progressCallback(progress); - }); - } else { - callback(); - } - }); - } - }, - message: 'Downloading VirtualBox...' - }, - { - run: function (callback) { - virtualbox.deleteVM('kitematic-vm', function (err, removed) { - if (err) { - console.log(err); - } - callback(); - }); - }, - message: 'Cleaning up existing Docker VM...' - }, - - // Initialize Boot2Docker if necessary. - { - run: function (callback) { - boot2docker.exists(function (err, exists) { - if (err) { callback(err); return; } - if (!exists) { - boot2docker.init(function (err) { - callback(err); - }); - } else { - if (!boot2docker.sshKeyExists()) { - callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); - } else { - boot2docker.isoVersion(function (err, version) { - if (err || util.compareVersions(version, boot2docker.version()) < 0) { - boot2docker.stop(function(err) { - boot2docker.upgrade(function (err) { - callback(err); - }); - }); - } else { - callback(); - } - }); - } - } - }); - }, - message: 'Setting up the Docker VM...' - }, - { - run: function (callback) { - boot2docker.waitWhileStatus('saving', function (err) { - boot2docker.status(function (err, status) { - if (err) {callback(err); return;} - if (status !== 'running') { - boot2docker.start(function (err) { - callback(err); - }); - } else { - callback(); - } - }); - }); - }, - message: 'Starting the Docker VM...' - }, - { - run: function (callback) { - boot2docker.ip(function (err, ip) { - if (err) {callback(err); return;} - console.log('Setting host IP to: ' + ip); - docker.setHost(ip); - callback(err); - }); - }, - message: 'Detecting Docker VM...' - } -]; +var SetupStore = require('./SetupStore'); var Setup = React.createClass({ mixins: [ Router.Navigation ], @@ -140,6 +16,14 @@ var Setup = React.createClass({ message: '', progress: 0 }; + }, + componentWillMount: function () { + SetupStore.on(SetupStore.PROGRESS_EVENT, this.update); + }, + componentDidMount: function () { + }, + update: function () { + }, render: function () { var radial; @@ -165,53 +49,6 @@ var Setup = React.createClass({
); } - }, - componentWillMount: function () { - this.setState({}); - }, - componentDidMount: function () { - var self = this; - this.setup(function (err) { - if (!err) { - boot2docker.ip(function (err, ip) { - docker.setHost(ip); - ContainerStore.init(function () { - self.transitionTo('containers'); - }); - }); - } - }); - }, - setup: function (callback) { - var self = this; - var currentStep = 0; - async.eachSeries(setupSteps, function (step, callback) { - console.log('Performing step ' + currentStep); - self.setState({progress: 0}); - self.setState({message: step.message}); - step.run(function (err) { - if (err) { - callback(err); - } else { - currentStep += 1; - callback(); - } - }, function (progress) { - self.setState({progress: progress}); - }); - }, function (err) { - if (err) { - // if any of the steps fail - console.log('Kitematic setup failed at step ' + currentStep); - console.log(err); - self.setState({error: err.message}); - callback(err); - } else { - // Setup Finished - console.log('Setup finished.'); - callback(); - } - }); } }); diff --git a/src/SetupStore.js b/src/SetupStore.js new file mode 100644 index 0000000000..3dcf5fbdb1 --- /dev/null +++ b/src/SetupStore.js @@ -0,0 +1,201 @@ +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var exec = require('exec'); +var boot2docker = require('./Boot2Docker'); +var virtualbox = require('./Virtualbox'); +var setupUtil = require('./SetupUtil'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +var _currentStep = null; +var _error = null; +var _progress = null; + +var SetupStore = assign(EventEmitter.prototype, { + PROGRESS_EVENT: 'setup_progress', + STEP_EVENT: 'setup_step', + ERROR_EVENT: 'setup_error', + downloadVirtualboxStep: { + _download: function (callback, progressCallback) { + setupUtil.virtualboxSHA256(packagejson['virtualbox-version'], packagejson['virtualbox-filename'], function (err, checksum) { + if (err) { + callback(err); + return; + } + var url = 'http://download.virtualbox.org/virtualbox/' + packagejson['virtualbox-version'] + '/' + packagejson['virtualbox-filename']; + var downloadPath = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + setupUtil.download(url, downloadPath, checksum, function (err) { + callback(err); + }, function (progress) { + progressCallback(progress); + }); + }); + }, + run: function (callback, progressCallback) { + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (err) {callback(err); return;} + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + this._download(callback, progressCallback); + } else { + callback(); + } + }); + } else { + this._download(callback, progressCallback); + } + }, + name: 'downloading_virtualbox', + message: 'Downloading Virtualbox', + }, + installVirtualboxStep: { + _install: function (callback) { + exec(['hdiutil', 'attach', path.join(setupUtil.supportDir(), 'VirtualBox-4.3.20-96996-OSX.dmg')], function (stderr, stdout, code) { + if (code) { + callback(stderr); + return; + } + var iconPath = path.join(setupUtil.resourceDir(), 'kitematic.icns'); + setupUtil.isSudo(function (err, isSudo) { + sudoCmd = isSudo ? ['sudo'] : [path.join(setupUtil.resourceDir(), 'cocoasudo'), '--icon=' + iconPath, '--prompt=Kitematic requires administrative privileges to install VirtualBox and copy itself to the Applications folder.']; + sudoCmd.push.apply(sudoCmd, ['installer', '-pkg', '/Volumes/VirtualBox/VirtualBox.pkg', '-target', '/']); + exec(sudoCmd, function (stderr, stdout, code) { + if (code) { + console.log(stderr); + console.log(stdout); + callback('Could not install virtualbox.'); + } else { + exec(['hdiutil', 'detach', '/Volumes/VirtualBox'], function(stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + callback(); + } + }); + } + }); + }); + }); + }, + run: function (callback) { + var self = this; + if (virtualbox.installed()) { + virtualbox.version(function (err, version) { + if (setupUtil.compareVersions(version, packagejson['virtualbox-required-version']) < 0) { + virtualbox.kill(function (err) { + if (err) {callback(err); return;} + self._install(function(err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } else { + self._install(function(err) { + callback(err); + }); + } + }, + name: 'installing_virtualbox', + message: 'Installing VirtualBox', + }, + cleanupKitematicStep: { + run: function (callback) { + virtualbox.vmdestroy('kitematic-vm', function (err, removed) { + if (err) { + console.log(err); + } + callback(); + }); + }, + name: 'cleanup_kitematic', + message: 'Cleaning up existing Kitematic install...' + }, + initBoot2DockerStep: { + run: function (callback) { + boot2docker.exists(function (err, exists) { + if (err) { callback(err); return; } + if (!exists) { + boot2docker.init(function (err) { + callback(err); + }); + } else { + if (!boot2docker.sshKeyExists()) { + callback('Boot2Docker SSH key doesn\'t exist. Fix by removing the existing Boot2Docker VM and re-run the installer. This usually occurs because an old version of Boot2Docker is installed.'); + } else { + boot2docker.isoVersion(function (err, version) { + if (err || setupUtil.compareVersions(version, boot2docker.version()) < 0) { + boot2docker.stop(function(err) { + boot2docker.upgrade(function (err) { + callback(err); + }); + }); + } else { + callback(); + } + }); + } + } + }); + }, + name: 'init_boot2docker', + message: 'Setting up the Docker VM...' + }, + startBoot2DockerStep: { + run: function (callback) { + boot2docker.waitWhileStatus('saving', function (err) { + boot2docker.status(function (err, status) { + if (err) {callback(err); return;} + if (status !== 'running') { + boot2docker.start(function (err) { + callback(err); + }); + } else { + callback(); + } + }); + }); + }, + name: 'start_boot2docker', + message: 'Starting the Docker VM...' + }, + step: function () { + return _currentStep; + }, + progress: function () { + return _progress; + }, + run: function (callback) { + var self = this; + var steps = [this.downloadVirtualboxStep, this.installVirtualboxStep, this.cleanupKitematicStep, this.initBoot2DockerStep, this.startBoot2DockerStep]; + async.eachSeries(steps, function (step, callback) { + console.log(step.name); + _currentStep = step; + _progress = null; + step.run(function (err) { + if (err) { + callback(err); + } else { + self.emit(self.STEP_EVENT); + callback(); + } + }, function (progress) { + self.emit(self.PROGRESS_EVENT, progress); + _progress = progress; + }); + }, function (err) { + if (err) { + self.emit(self.ERROR_EVENT, _error); + callback(err); + } else { + callback(); + } + }); + } +}); + +module.exports = SetupStore; diff --git a/src/Util.js b/src/SetupUtil.js similarity index 66% rename from src/Util.js rename to src/SetupUtil.js index 78a4c61035..d3b8d696e9 100644 --- a/src/Util.js +++ b/src/SetupUtil.js @@ -1,13 +1,13 @@ -var path = require('path'); -var fs = require('fs'); -var nodeCrypto = require('crypto'); var request = require('request'); var progress = require('request-progress'); +var path = require('path'); +var crypto = require('crypto'); +var fs = require('fs'); var exec = require('exec'); -var Util = { +var SetupUtil = { supportDir: function (callback) { - var dirs = ['Application\ Support', 'Kitematic']; + var dirs = ['Library', 'Application\ Support', 'Kitematic']; var acc = process.env.HOME; dirs.forEach(function (d) { acc = path.join(acc, d); @@ -17,6 +17,19 @@ var Util = { }); return acc; }, + resourceDir: function (callback) { + return process.env.RESOURCES_PATH; + }, + isSudo: function (callback) { + exec(['sudo', '-n', '-u', 'root', 'true'], function (stderr, stdout, code) { + if (code) { + callback(stderr); + } else { + var isSudo = stderr.indexOf('a password is required') === -1; + callback(null, isSudo); + } + }); + }, download: function (url, filename, checksum, callback, progressCallback) { var doDownload = function () { progress(request({ @@ -37,8 +50,7 @@ var Util = { // Compare checksum to see if it already exists first if (fs.existsSync(filename)) { - var existingChecksum = nodeCrypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); - console.log(existingChecksum); + var existingChecksum = crypto.createHash('sha256').update(fs.readFileSync(filename), 'utf8').digest('hex'); if (existingChecksum !== checksum) { fs.unlinkSync(filename); doDownload(); @@ -49,6 +61,22 @@ var Util = { doDownload(); } }, + virtualboxSHA256: function (version, filename, callback) { + var checksumUrl = 'http://dlc-cdn.sun.com/virtualbox/' + version + '/SHA256SUMS'; + request(checksumUrl, function (error, response, body) { + if (error) { + callback(error); + return; + } + var checksums = body.split('\n').map(function (line) { + return line.split(' *'); + }).reduce(function (obj, pair) { + obj[pair[1]] = pair[0]; + return obj; + }, {}); + callback(null, checksums[filename]); + }); + }, compareVersions: function (v1, v2, options) { var lexicographical = options && options.lexicographical, zeroExtend = options && options.zeroExtend, @@ -100,4 +128,4 @@ var Util = { } }; -module.exports = Util; +module.exports = SetupUtil; diff --git a/src/Virtualbox.js b/src/Virtualbox.js index 503d4efad2..446537c461 100644 --- a/src/Virtualbox.js +++ b/src/Virtualbox.js @@ -5,32 +5,18 @@ var async = require('async'); var util = require('./Util'); var VirtualBox = { - REQUIRED_VERSION: '4.3.18', - INCLUDED_VERSION: '4.3.18', - INSTALLER_FILENAME: 'virtualbox-4.3.18.pkg', - INSTALLER_CHECKSUM: '5836c94481c460c648b9216386591a2915293ac86b9bb6c57746637796af6af2', command: function () { return '/usr/bin/VBoxManage'; }, installed: function () { return fs.existsSync('/usr/bin/VBoxManage') && fs.existsSync('/Applications/VirtualBox.app/Contents/MacOS/VirtualBox'); }, - install: function (callback) { - // -W waits for the process to close before finishing. - exec('open -W ' + path.join(util.supportDir(), this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) { - if (code) { - callback(stderr); - return; - } - callback(null); - }); - }, version: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('/usr/bin/VBoxManage -v', function (stderr, stdout, code) { + exec([this.command(), '-v'], function (stderr, stdout, code) { if (code) { callback(stderr); return; @@ -44,12 +30,12 @@ var VirtualBox = { callback(null, match[1]); }); }, - saveVMs: function (callback) { + poweroff: function (callback) { if (!this.installed()) { callback('VirtualBox not installed.'); return; } - exec('/usr/bin/VBoxManage list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} /usr/bin/VBoxManage controlvm {} savestate', function (stderr, stdout, code) { + exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} acpipowerbutton', function (stderr, stdout, code) { if (code) { callback(stderr); } else { @@ -58,7 +44,7 @@ var VirtualBox = { }); }, kill: function (callback) { - this.saveRunningVMs(function (err) { + this.poweroff(function (err) { if (err) {callback(err); return;} exec('pkill VirtualBox', function (stderr, stdout, code) { if (code) {callback(stderr); return;} @@ -69,7 +55,7 @@ var VirtualBox = { }); }); }, - vmState: function (name, callback) { + vmstate: function (name, callback) { exec(this.command() + ' showvminfo ' + name + ' --machinereadable', function (stderr, stdout, code) { if (code) { callback(stderr); return; } var match = stdout.match(/VMState="(\w+)"/); @@ -80,8 +66,9 @@ var VirtualBox = { callback(null, match[1]); }); }, - deleteVM:function (name, callback) { - VirtualBox.vmState(name, function (err, state) { + vmdestroy: function (name, callback) { + var self = this; + this.vmstate(name, function (err, state) { // No VM found if (err) { callback(null, false); return; } exec('/usr/bin/VBoxManage controlvm ' + name + ' acpipowerbutton', function (stderr, stdout, code) { @@ -91,7 +78,7 @@ var VirtualBox = { async.until(function () { return state === 'poweroff'; }, function (callback) { - VirtualBox.vmState(name, function (err, newState) { + self.vmstate(name, function (err, newState) { if (err) { callback(err); return; } state = newState; setTimeout(callback, 250); diff --git a/tests/SetupStore-integration.js b/tests/SetupStore-integration.js new file mode 100644 index 0000000000..2dde66b446 --- /dev/null +++ b/tests/SetupStore-integration.js @@ -0,0 +1,99 @@ +var virtualbox = require('../build/Virtualbox'); +var SetupStore = require('../build/SetupStore'); +var setupUtil = require('../build/SetupUtil'); +var path = require('path'); +var fs = require('fs'); +var child_process = require('child_process'); +var exec = require('exec'); +var rimraf = require('rimraf'); +var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 300000; // 5 minutes + +describe('Setup', function () { + describe('without virtualbox installed or downloaded', function () { + var virtualboxFile = path.join(setupUtil.supportDir(), packagejson['virtualbox-filename']); + beforeEach(function () { + if (fs.existsSync(virtualboxFile)) { + fs.unlinkSync(virtualboxFile); + } + spyOn(virtualbox, 'installed').and.returnValue(false); + }); + + it('downloads virtualbox from the official website', function (done) { + SetupStore.downloadVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + expect(fs.existsSync(virtualboxFile)).toBe(true); + done(); + }, function (progress) { + + }); + }); + }); + + describe('with virtualbox downloaded but not installed', function () { + beforeEach(function (done) { + // 5 minute timeout per test + + SetupStore.downloadVirtualboxStep.run(function (err) { + if (virtualbox.installed()) { + virtualbox.kill(function (callback) { + done(); + }); + } else { + done(); + } + }, function (progress) {}); + }); + + it('does install virtualbox', function (done) { + SetupStore.installVirtualboxStep.run(function (err) { + expect(err).toBeFalsy(); + expect(fs.existsSync(virtualbox.command())).toBe(true); + done(); + }); + }); + }); + + describe('with virtualbox installed', function () { + + // Before each teardown the boot2docker VM, keys and anything else + + describe('and with a kitematic vm', function () { + + }); + + describe('and without a boot2docker vm', function () { + + }); + + describe('and with an old boot2docker vm', function () { + + }); + + describe('and with a very old boot2docker vm', function () { + + }); + + describe('and with a boot2docker vm running', function () { + + }); + + describe('and with a boot2docker vm but with no ssh keys', function () { + + }); + + describe('and with a boot2docker vm being powered off', function () { + + }); + + describe('and with a boot2docker vm being removed', function () { + + }); + + describe('and with a boot2docker vm initialized but not running', function () { + + }); + }); + +}); diff --git a/tests/SetupUtil-integration.js b/tests/SetupUtil-integration.js new file mode 100644 index 0000000000..b56ef2e2e0 --- /dev/null +++ b/tests/SetupUtil-integration.js @@ -0,0 +1,10 @@ +var setupUtil = require('../build/SetupUtil'); + +describe('SetupUtils', function() { + it('returns live sha256 checksum for a given virtualbox version & filename', function (done) { + setupUtil.virtualboxSHA256('4.3.20', 'VirtualBox-4.3.20-96996-OSX.dmg', function (err, checksum) { + expect(checksum).toBe('744e77119a640a5974160213c9912568a3d88dbd06a2fc6b6970070941732705'); + done(); + }); + }); +}); diff --git a/specs/jasmine-2.1.3/boot.js b/tests/jasmine-2.1.3/boot.js similarity index 100% rename from specs/jasmine-2.1.3/boot.js rename to tests/jasmine-2.1.3/boot.js diff --git a/specs/jasmine-2.1.3/console.js b/tests/jasmine-2.1.3/console.js similarity index 98% rename from specs/jasmine-2.1.3/console.js rename to tests/jasmine-2.1.3/console.js index a65876e911..96026ab63a 100755 --- a/specs/jasmine-2.1.3/console.js +++ b/tests/jasmine-2.1.3/console.js @@ -57,8 +57,6 @@ getJasmineRequireObj().ConsoleReporter = function() { }, failedSuites = []; - print('ConsoleReporter is deprecated and will be removed in a future version.'); - this.jasmineStarted = function() { specCount = 0; failureCount = 0; diff --git a/specs/jasmine-2.1.3/jasmine-html.js b/tests/jasmine-2.1.3/jasmine-html.js similarity index 100% rename from specs/jasmine-2.1.3/jasmine-html.js rename to tests/jasmine-2.1.3/jasmine-html.js diff --git a/specs/jasmine-2.1.3/jasmine.css b/tests/jasmine-2.1.3/jasmine.css similarity index 100% rename from specs/jasmine-2.1.3/jasmine.css rename to tests/jasmine-2.1.3/jasmine.css diff --git a/specs/jasmine-2.1.3/jasmine.js b/tests/jasmine-2.1.3/jasmine.js similarity index 100% rename from specs/jasmine-2.1.3/jasmine.js rename to tests/jasmine-2.1.3/jasmine.js diff --git a/specs/jasmine-2.1.3/jasmine_favicon.png b/tests/jasmine-2.1.3/jasmine_favicon.png similarity index 100% rename from specs/jasmine-2.1.3/jasmine_favicon.png rename to tests/jasmine-2.1.3/jasmine_favicon.png diff --git a/specs/specs.html b/tests/tests.html similarity index 59% rename from specs/specs.html rename to tests/tests.html index 6cef94b504..0d298c493c 100755 --- a/specs/specs.html +++ b/tests/tests.html @@ -4,7 +4,6 @@ - - + diff --git a/specs/specs.js b/tests/tests.js similarity index 69% rename from specs/specs.js rename to tests/tests.js index 153e947977..e21261d882 100644 --- a/specs/specs.js +++ b/tests/tests.js @@ -2,19 +2,22 @@ window.jasmineRequire = require('./jasmine-2.1.3/jasmine'); require('./jasmine-2.1.3/jasmine-html'); require('./jasmine-2.1.3/boot'); var consoleReporter = require('./jasmine-2.1.3/console'); +var app = require('remote').require('app'); jasmine.getEnv().addReporter(new consoleReporter.ConsoleReporter()({ showColors: true, timer: new jasmine.Timer(), print: function() { - console.log(arguments); process.stdout.write.apply(process.stdout, arguments); + }, + onComplete: function () { + app.quit(); } })); var fs = require('fs'); -var tests = fs.readdirSync('./specs').filter(function (f) { - return f.indexOf('-spec') !== -1; +var tests = fs.readdirSync('./tests').filter(function (f) { + return f.indexOf('-' + process.env.TEST_TYPE) !== -1; }); tests.forEach(function (t) {