diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 0c19be207..ff86b5849 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -104,6 +104,7 @@ module.exports = (grunt) -> installDir ?= process.env.INSTALL_PREFIX ? '/usr/local' killCommand = 'pkill -9 nylas' + grunt.option('appDir', appDir) installDir = path.resolve(installDir) cjsxConfig = @@ -359,7 +360,7 @@ module.exports = (grunt) -> grunt.registerTask('compile', ['coffee', 'cjsx', 'babel', 'prebuild-less', 'cson', 'peg']) grunt.registerTask('lint', ['coffeelint', 'csslint', 'lesslint', 'nylaslint', 'eslint']) - grunt.registerTask('test', ['shell:kill-n1', 'run-edgehill-specs']) + grunt.registerTask('test', ['shell:kill-n1', 'run-edgehill-specs', 'run-spectron-specs']) grunt.registerTask('docs', ['build-docs', 'render-docs']) ciTasks = ['output-disk-space', 'download-electron', 'build'] diff --git a/build/tasks/spec-task.coffee b/build/tasks/spec-task.coffee index cce09afdc..b237cf6eb 100644 --- a/build/tasks/spec-task.coffee +++ b/build/tasks/spec-task.coffee @@ -1,169 +1,63 @@ fs = require 'fs' path = require 'path' - -_ = require 'underscore' -async = require 'async' request = require 'request' +proc = require 'child_process' -concurrency = 2 +executeTests = (test, grunt, done) -> + testSucceeded = false + testOutput = "" + testProc = proc.spawn(test.cmd, test.args) + testProc.stdout.on 'data', (data) -> + str = data.toString() + testOutput += str + console.log(str) + if str.indexOf(' 0 failures') isnt -1 + testSucceeded = true + + testProc.stderr.on 'data', (data) -> + str = data.toString() + testOutput += str + grunt.log.error(str) + + testProc.on 'error', (err) -> + grunt.log.error("Process error: #{err}") + + testProc.on 'close', (exitCode, signal) -> + if testSucceeded and exitCode is 0 + done() + else + testOutput = testOutput.replace(/\x1b\[[^m]+m/g, '') + url = "https://hooks.slack.com/services/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" + request.post + url: url + json: + username: "Edgehill Builds" + text: "Aghhh somebody broke the build. ```#{testOutput}```" + , (err, httpResponse, body) -> + done(false) module.exports = (grunt) -> - {isNylasPackage, spawn} = require('./task-helpers')(grunt) - packageSpecQueue = null - - logDeprecations = (label, {stderr}={}) -> - return unless process.env.JANKY_SHA1 - stderr ?= '' - deprecatedStart = stderr.indexOf('Calls to deprecated functions') - return if deprecatedStart is -1 - - grunt.log.error(label) - stderr = stderr.substring(deprecatedStart) - stderr = stderr.replace(/^\s*\[[^\]]+\]\s+/gm, '') - stderr = stderr.replace(/source: .*$/gm, '') - stderr = stderr.replace(/^"/gm, '') - stderr = stderr.replace(/",\s*$/gm, '') - grunt.log.error(stderr) - - getAppPath = -> - contentsDir = grunt.config.get('nylasGruntConfig.contentsDir') - switch process.platform - when 'darwin' - path.join(contentsDir, 'MacOS', 'Edgehill') - when 'linux' - path.join(contentsDir, 'edgehill') - when 'win32' - path.join(contentsDir, 'edgehill.exe') - - runPackageSpecs = (callback) -> - failedPackages = [] - rootDir = grunt.config.get('nylasGruntConfig.shellAppDir') - resourcePath = process.cwd() - appPath = getAppPath() - - # Ensure application is executable on Linux - fs.chmodSync(appPath, '755') if process.platform is 'linux' - - packageSpecQueue = async.queue (packagePath, callback) -> - if process.platform in ['darwin', 'linux'] - options = - cmd: appPath - args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}"] - opts: - cwd: packagePath - env: _.extend({}, process.env, ATOM_SHELL_PATH: rootDir) - else if process.platform is 'win32' - options = - cmd: process.env.comspec - args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{path.join(packagePath, 'spec')}", "--log-file=ci.log"] - opts: - cwd: packagePath - env: _.extend({}, process.env, ATOM_SHELL_PATH: rootDir) - - grunt.verbose.writeln "Launching #{path.basename(packagePath)} specs." - spawn options, (error, results, code) -> - if process.platform is 'win32' - if error - process.stderr.write(fs.readFileSync(path.join(packagePath, 'ci.log'))) - fs.unlinkSync(path.join(packagePath, 'ci.log')) - - failedPackages.push path.basename(packagePath) if error - logDeprecations("#{path.basename(packagePath)} Specs", results) - callback() - - modulesDirectory = path.resolve('node_modules') - for packageDirectory in fs.readdirSync(modulesDirectory) - packagePath = path.join(modulesDirectory, packageDirectory) - continue unless grunt.file.isDir(path.join(packagePath, 'spec')) - continue unless isNylasPackage(packagePath) - packageSpecQueue.push(packagePath) - - packageSpecQueue.concurrency = concurrency - 1 - packageSpecQueue.drain = -> callback(null, failedPackages) - - runCoreSpecs = (callback) -> - appPath = getAppPath() - resourcePath = process.cwd() - coreSpecsPath = path.resolve('spec') - - if process.platform in ['darwin', 'linux'] - options = - cmd: appPath - args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"] - else if process.platform is 'win32' - options = - cmd: process.env.comspec - args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", "--log-file=ci.log"] - - spawn options, (error, results, code) -> - if process.platform is 'win32' - process.stderr.write(fs.readFileSync('ci.log')) if error - fs.unlinkSync('ci.log') - else - # TODO: Restore concurrency on Windows - packageSpecQueue.concurrency = concurrency - logDeprecations('Core Specs', results) - - callback(null, error) - - grunt.registerTask 'run-specs', 'Run the specs', -> + grunt.registerTask 'run-spectron-specs', 'Run spectron specs', -> + appPath = path.resolve('./N1.sh') done = @async() - startTime = Date.now() + npmPath = path.resolve "./build/node_modules/.bin/npm" + grunt.log.writeln 'App exists: ' + fs.existsSync(appPath) - # TODO: This should really be parallel on both platforms, however our - # fixtures step on each others toes currently. - if process.platform in ['darwin', 'linux'] - method = async.parallel - else if process.platform is 'win32' - method = async.series - - method [runCoreSpecs, runPackageSpecs], (error, results) -> - [coreSpecFailed, failedPackages] = results - elapsedTime = Math.round((Date.now() - startTime) / 100) / 10 - grunt.log.ok("Total spec time: #{elapsedTime}s using #{concurrency} cores") - failures = failedPackages - failures.push "N1 core" if coreSpecFailed - - grunt.log.error("[Error]".red + " #{failures.join(', ')} spec(s) failed") if failures.length > 0 - - if process.platform is 'win32' and process.env.JANKY_SHA1 - done() + process.chdir('./spectron') + grunt.log.writeln "Current dir: #{process.cwd()}" + installProc = proc.exec "#{npmPath} install", (error) -> + if error? + process.chdir('..') + grunt.log.error('Failed while running npm install in spectron folder') + grunt.fail.warn(error) + done(false) else - done(!coreSpecFailed and failedPackages.length == 0) + executeTests cmd: npmPath, args: ['test', "APP_PATH=#{appPath}"], grunt, (succeeded) -> + process.chdir('..') + done(succeeded) + grunt.registerTask 'run-edgehill-specs', 'Run the specs', -> - proc = require 'child_process' done = @async() - testSucceeded = false - testOutput = "" - testProc = proc.spawn("./N1.sh", ["--test"]) - - testProc.stdout.on 'data', (data) -> - str = data.toString() - testOutput += str - console.log(str) - if str.indexOf(' 0 failures') isnt -1 - testSucceeded = true - - testProc.stderr.on 'data', (data) -> - str = data.toString() - testOutput += str - grunt.log.error(str) - - testProc.on 'error', (err) -> - grunt.log.error("Process error: #{err}") - - testProc.on 'close', (exitCode, signal) -> - if testSucceeded - done() - else - testOutput = testOutput.replace(/\x1b\[[^m]+m/g, '') - url = "https://hooks.slack.com/services/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" - request.post - url: url - json: - username: "Edgehill Builds" - text: "Aghhh somebody broke the build. ```#{testOutput}```" - , (err, httpResponse, body) -> - done(false) + executeTests cmd: './N1.sh', args: ['--test'], grunt, done diff --git a/spectron/app-spec.es6 b/spectron/app-spec.es6 new file mode 100644 index 000000000..d44163fd8 --- /dev/null +++ b/spectron/app-spec.es6 @@ -0,0 +1,23 @@ +import {Application} from 'spectron'; + +describe('Nylas', ()=> { + beforeEach((done)=>{ + this.app = new Application({ + path: jasmine.APP_PATH, + }); + this.app.start().then(done); + }); + + afterEach((done)=> { + if (this.app && this.app.isRunning()) { + this.app.stop().then(done); + } + }); + + it('shows an initial window', ()=> { + this.app.client.getWindowCount().then((count)=> { + expect(count).toEqual(1); + }); + }); +}); + diff --git a/spectron/bootstrap.js b/spectron/bootstrap.js new file mode 100644 index 000000000..7adff1989 --- /dev/null +++ b/spectron/bootstrap.js @@ -0,0 +1,4 @@ +var babelOptions = require('../static/babelrc.json'); +require('babel-core/register')(babelOptions); +jasmine.APP_PATH = process.argv.slice(3)[0].split('APP_PATH=')[1]; +jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; diff --git a/spectron/config.json b/spectron/config.json new file mode 100644 index 000000000..2eb752d52 --- /dev/null +++ b/spectron/config.json @@ -0,0 +1,9 @@ +{ + "spec_dir": ".", + "spec_files": [ + "**/*-spec.{js,es6,es,jsx}" + ], + "helpers": [ + "bootstrap.js" + ] +} diff --git a/spectron/package.json b/spectron/package.json new file mode 100644 index 000000000..9fd5e0dce --- /dev/null +++ b/spectron/package.json @@ -0,0 +1,16 @@ +{ + "name": "nylas-spectron", + "description": "N1 Spectron Test Suite", + "repository": { + "type": "git", + "url": "https://github.com/nylas/N1.git" + }, + "scripts": { + "test": "jasmine JASMINE_CONFIG_PATH=./config.json" + }, + "dependencies": { + "babel-core": "^5.8.21", + "jasmine": "^2.3.2", + "spectron": "^0.34.1" + } +}