From 6c2addca81d78966b13ec1a088d537ff336003ed Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sat, 21 May 2016 17:36:38 -0700 Subject: [PATCH] Support test suite on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on work by Tyler Waters in #1814. This commit contains the following changes: - Add quotation marks to Makefile variables for programs. The variables use POSIX-style paths, which cannot be used on Windows to launch a program except when quoted. Using double quotation marks instead of single since the latter is not available on Windows - Use os-tmpdir module to get an acceptable directory for temporary usage instead of relying on the POSIX /tmp - Use process.execPath as an authoritative path for Node.js executable - Detect whether symbolic links are supported on the platform before testing. On Windows, creating symlinks can fail since it needs additional user permissions - Fix hook tests. The tests parse output of the "dot" reporter to separate output of individual tests. The "dot" reporter uses "·" symbol (U+2024 ONE DOT LEADER) under POSIX environments and "." symbol (U+002E FULL STOP) under Windows, which means that having "." in the echoed message makes it ambiguous to be parsed in Windows. To fix this issue, two separate changes are necessary: - Use a dynamically created regular expression to split the tests based on the specific dot character used on the platform - Replace "." with "-" in echoed messages in fixtures for hook tests to avoid ambiguity with the character output by the reporter Changes from #1814 include: - Rebasing - Use process.execPath as an authoritative path for Node.js executable - Avoid external dependencies for child_process.spawn() - Detect whether symbol links are supported on the platform before testing. On Windows, creating symlinks can fail since it needs additional user permissions Fixes #1813. --- Makefile | 6 +- mocha.js | 20 +++-- package.json | 1 + test/acceptance/fs.js | 6 +- test/acceptance/lookup-files.js | 75 ++++++++++++------ test/color.js | 5 +- .../hooks/multiple.hook.async.error.js | 78 +++++++++---------- .../fixtures/hooks/multiple.hook.error.js | 78 +++++++++---------- test/integration/helpers.js | 11 ++- test/integration/hook.err.js | 47 +++++------ test/integration/hooks.js | 3 +- test/integration/retries.js | 4 +- 12 files changed, 190 insertions(+), 144 deletions(-) diff --git a/Makefile b/Makefile index 2288ae862e..92daf92a18 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -BROWSERIFY := node_modules/.bin/browserify -ESLINT := node_modules/.bin/eslint -KARMA := node_modules/.bin/karma +BROWSERIFY := "node_modules/.bin/browserify" +ESLINT := "node_modules/.bin/eslint" +KARMA := "node_modules/.bin/karma" REPORTER ?= spec TM_BUNDLE = JavaScript\ mocha.tmbundle diff --git a/mocha.js b/mocha.js index af90adac66..df54445862 100644 --- a/mocha.js +++ b/mocha.js @@ -2474,7 +2474,7 @@ function coverageClass(coveragePctg) { return 'terrible'; } -}).call(this,require('_process'),"/lib/reporters") +}).call(this,require('_process'),"/lib\\reporters") },{"./json-cov":23,"_process":58,"fs":43,"jade":43,"path":43}],21:[function(require,module,exports){ (function (global){ /* eslint-env browser */ @@ -4814,7 +4814,13 @@ Runner.prototype.hook = function(name, fn) { } if (err) { if (err instanceof Pending) { - suite.pending = true; + if (name === 'beforeEach' || name === 'afterEach') { + self.test.pending = true; + } else { + suite.tests.forEach(function(test) { + test.pending = true; + }); + } } else { self.failHook(hook, err); @@ -5028,7 +5034,7 @@ Runner.prototype.runTests = function(suite, fn) { // execute test and hook(s) self.emit('test', self.test = test); self.hookDown('beforeEach', function(err, errSuite) { - if (suite.isPending()) { + if (test.isPending()) { self.emit('pending', test); self.emit('test end', test); return next(); @@ -5355,8 +5361,8 @@ function filterLeaks(ok, globals) { } // in firefox - // if runner runs in an iframe, this iframe's window.getInterface method not init at first - // it is assigned in some seconds + // if runner runs in an iframe, this iframe's window.getInterface method + // not init at first it is assigned in some seconds if (global.navigator && (/^getInterface/).test(key)) { return false; } @@ -6117,8 +6123,8 @@ exports.slug = function(str) { exports.clean = function(str) { str = str .replace(/\r\n?|[\n\u2028\u2029]/g, '\n').replace(/^\uFEFF/, '') - .replace(/^function *\(.*\)\s*\{|\(.*\) *=> *\{?/, '') - .replace(/\s+\}$/, ''); + // (traditional)-> space/name parameters body (lambda)-> parameters body multi-statement/single keep body content + .replace(/^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, '$1$2$3'); var spaces = str.match(/^\n?( *)/)[1].length; var tabs = str.match(/^\n?(\t*)/)[1].length; diff --git a/package.json b/package.json index 0aed134ac8..e54c10247e 100644 --- a/package.json +++ b/package.json @@ -324,6 +324,7 @@ "growl": "1.9.2", "jade": "0.26.3", "mkdirp": "0.5.1", + "os-tmpdir": "1.0.1", "supports-color": "1.2.0", "to-iso-string": "0.0.2" }, diff --git a/test/acceptance/fs.js b/test/acceptance/fs.js index cdd32166d5..f4c92ea727 100644 --- a/test/acceptance/fs.js +++ b/test/acceptance/fs.js @@ -1,16 +1,18 @@ var fs = require('fs'); +var path = require('path'); +var tmpFile = path.join.bind(path, require('os-tmpdir')()); describe('fs.readFile()', function(){ describe('when the file exists', function(){ it('should succeed', function(done){ - fs.writeFile('/tmp/mocha', 'wahoo', done) + fs.writeFile(tmpFile('mocha'), 'wahoo', done) }) }) describe('when the file does not exist', function(){ it('should fail', function(done){ // uncomment - // fs.readFile('/tmp/does-not-exist', done); + // fs.readFile(tmpFile('does-not-exist'), done); done(); }) }) diff --git a/test/acceptance/lookup-files.js b/test/acceptance/lookup-files.js index 504d23aab1..7b84d93171 100644 --- a/test/acceptance/lookup-files.js +++ b/test/acceptance/lookup-files.js @@ -1,56 +1,83 @@ var utils = require('../../lib/utils'); describe('lookupFiles', function() { - var fs = require('fs'), path = require('path'), existsSync = fs.existsSync || - path.existsSync; + var fs = require('fs'), + path = require('path'), + existsSync = fs.existsSync || path.existsSync, + tmpDir = require('os-tmpdir')(), + tmpFile = path.join.bind(path, tmpDir), + symlinkSupported = false; + + fs.writeFileSync(tmpFile('mocha-utils.js'), 'yippy skippy ying yang yow'); + try { + fs.symlinkSync(tmpFile('mocha-utils.js'), tmpFile('mocha-utils-link.js')); + symlinkSupported = true; + } catch (ignored) { + } + + cleanup(); beforeEach(function() { - fs.writeFileSync('/tmp/mocha-utils.js', 'yippy skippy ying yang yow'); - fs.symlinkSync('/tmp/mocha-utils.js', '/tmp/mocha-utils-link.js'); + fs.writeFileSync(tmpFile('mocha-utils.js'), 'yippy skippy ying yang yow'); + if (symlinkSupported) { + fs.symlinkSync(tmpFile('mocha-utils.js'), tmpFile('mocha-utils-link.js')); + } }); - it('should not choke on symlinks', function() { - expect(utils.lookupFiles('/tmp', ['js'], false)) + (symlinkSupported ? it : it.skip)('should not choke on symlinks', function() { + expect(utils.lookupFiles(tmpDir, ['js'], false)) .to - .contain('/tmp/mocha-utils-link.js') + .contain(tmpFile('mocha-utils-link.js')) .and - .contain('/tmp/mocha-utils.js') + .contain(tmpFile('mocha-utils.js')) .and .have .length(2); - expect(existsSync('/tmp/mocha-utils-link.js')) + expect(existsSync(tmpFile('mocha-utils-link.js'))) .to .be(true); - fs.renameSync('/tmp/mocha-utils.js', '/tmp/bob'); - expect(existsSync('/tmp/mocha-utils-link.js')) + fs.renameSync(tmpFile('mocha-utils.js'), tmpFile('bob')); + expect(existsSync(tmpFile('mocha-utils-link.js'))) .to .be(false); - expect(utils.lookupFiles('/tmp', ['js'], false)) + expect(utils.lookupFiles(tmpDir, ['js'], false)) .to .eql([]); }); it('should accept a glob "path" value', function() { - expect(utils.lookupFiles('/tmp/mocha-utils*', ['js'], false)) + var res = utils.lookupFiles(tmpFile('mocha-utils*'), ['js'], false) + .map(path.normalize.bind(path)); + + var expectedLength = 0; + var ex = expect(res) .to - .contain('/tmp/mocha-utils-link.js') - .and - .contain('/tmp/mocha-utils.js') - .and + .contain(tmpFile('mocha-utils.js')); + expectedLength++; + + if (symlinkSupported) { + ex = ex.and + .contain(tmpFile('mocha-utils-link.js')); + expectedLength++; + } + + ex.and .have - .length(2); + .length(expectedLength); }); - afterEach(function() { + afterEach(cleanup); + + function cleanup() { [ - '/tmp/mocha-utils.js', - '/tmp/mocha-utils-link.js', - '/tmp/bob' + 'mocha-utils.js', + 'mocha-utils-link.js', + 'bob' ].forEach(function(path) { try { - fs.unlinkSync(path); + fs.unlinkSync(tmpFile(path)); } catch (ignored) { } }); - }); + } }); diff --git a/test/color.js b/test/color.js index 8c7167b0fa..3913c0099c 100644 --- a/test/color.js +++ b/test/color.js @@ -1,12 +1,13 @@ var assert = require('assert'); var child_process = require('child_process'); +var path = require('path'); describe('Mocha', function() { this.timeout(1000); it('should not output colors to pipe', function(cb) { - var command = 'bin/mocha --grep missing-test'; - child_process.exec(command, function(err, stdout, stderr) { + var command = [path.join('bin', 'mocha'), '--grep', 'missing-test']; + child_process.execFile(process.execPath, command, function(err, stdout, stderr) { if (err) return cb(err); assert(stdout.indexOf('[90m') === -1); diff --git a/test/integration/fixtures/hooks/multiple.hook.async.error.js b/test/integration/fixtures/hooks/multiple.hook.async.error.js index c5474ad99b..93ace7ff0d 100644 --- a/test/integration/fixtures/hooks/multiple.hook.async.error.js +++ b/test/integration/fixtures/hooks/multiple.hook.async.error.js @@ -9,54 +9,54 @@ describe('1', function () { console.log('1 before each'); }); - describe('1.1', function () { + describe('1-1', function () { before(function () { - console.log('1.1 before'); + console.log('1-1 before'); }); beforeEach(function (done) { - console.log('1.1 before each'); + console.log('1-1 before each'); process.nextTick(function () { - throw new Error('1.1 before each hook failed'); + throw new Error('1-1 before each hook failed'); }); }); - it('1.1 test 1', function () { - console.log('1.1 test 1'); + it('1-1 test 1', function () { + console.log('1-1 test 1'); }); - it('1.1 test 2', function () { - console.log('1.1 test 2'); + it('1-1 test 2', function () { + console.log('1-1 test 2'); }); afterEach(function () { - console.log('1.1 after each'); + console.log('1-1 after each'); }); after(function (done) { - console.log('1.1 after'); + console.log('1-1 after'); process.nextTick(function () { - throw new Error('1.1 after hook failed'); + throw new Error('1-1 after hook failed'); }); }); }); - describe('1.2', function () { + describe('1-2', function () { before(function () { - console.log('1.2 before'); + console.log('1-2 before'); }); beforeEach(function () { - console.log('1.2 before each'); + console.log('1-2 before each'); }); - it('1.2 test 1', function () { - console.log('1.2 test 1'); + it('1-2 test 1', function () { + console.log('1-2 test 1'); }); - it('1.2 test 2', function () { - console.log('1.2 test 2'); + it('1-2 test 2', function () { + console.log('1-2 test 2'); }); afterEach(function (done) { - console.log('1.2 after each'); + console.log('1-2 after each'); process.nextTick(function () { - throw new Error('1.2 after each hook failed'); + throw new Error('1-2 after each hook failed'); }); }); after(function () { - console.log('1.2 after'); + console.log('1-2 after'); }); }); @@ -77,45 +77,45 @@ describe('2', function () { }); }); - describe('2.1', function () { + describe('2-1', function () { before(function () { - console.log('2.1 before'); + console.log('2-1 before'); }); beforeEach(function () { - console.log('2.1 before each'); + console.log('2-1 before each'); }); - it('2.1 test 1', function () { - console.log('2.1 test 1'); + it('2-1 test 1', function () { + console.log('2-1 test 1'); }); - it('2.1 test 2', function () { - console.log('2.1 test 2'); + it('2-1 test 2', function () { + console.log('2-1 test 2'); }); afterEach(function () { - console.log('2.1 after each'); + console.log('2-1 after each'); }); after(function () { - console.log('2.1 after'); + console.log('2-1 after'); }); }); - describe('2.2', function () { + describe('2-2', function () { before(function () { - console.log('2.2 before'); + console.log('2-2 before'); }); beforeEach(function () { - console.log('2.2 before each'); + console.log('2-2 before each'); }); - it('2.2 test 1', function () { - console.log('2.2 test 1'); + it('2-2 test 1', function () { + console.log('2-2 test 1'); }); - it('2.2 test 2', function () { - console.log('2.2 test 2'); + it('2-2 test 2', function () { + console.log('2-2 test 2'); }); afterEach(function () { - console.log('2.2 after each'); + console.log('2-2 after each'); }); after(function () { - console.log('2.2 after'); + console.log('2-2 after'); }); }); diff --git a/test/integration/fixtures/hooks/multiple.hook.error.js b/test/integration/fixtures/hooks/multiple.hook.error.js index f0983cf42f..53d167b1cf 100644 --- a/test/integration/fixtures/hooks/multiple.hook.error.js +++ b/test/integration/fixtures/hooks/multiple.hook.error.js @@ -9,48 +9,48 @@ describe('1', function () { console.log('1 before each'); }); - describe('1.1', function () { + describe('1-1', function () { before(function () { - console.log('1.1 before'); + console.log('1-1 before'); }); beforeEach(function () { - console.log('1.1 before each'); - throw new Error('1.1 before each hook failed'); + console.log('1-1 before each'); + throw new Error('1-1 before each hook failed'); }); - it('1.1 test 1', function () { - console.log('1.1 test 1'); + it('1-1 test 1', function () { + console.log('1-1 test 1'); }); - it('1.1 test 2', function () { - console.log('1.1 test 2'); + it('1-1 test 2', function () { + console.log('1-1 test 2'); }); afterEach(function () { - console.log('1.1 after each'); + console.log('1-1 after each'); }); after(function () { - console.log('1.1 after'); - throw new Error('1.1 after hook failed'); + console.log('1-1 after'); + throw new Error('1-1 after hook failed'); }); }); - describe('1.2', function () { + describe('1-2', function () { before(function () { - console.log('1.2 before'); + console.log('1-2 before'); }); beforeEach(function () { - console.log('1.2 before each'); + console.log('1-2 before each'); }); - it('1.2 test 1', function () { - console.log('1.2 test 1'); + it('1-2 test 1', function () { + console.log('1-2 test 1'); }); - it('1.2 test 2', function () { - console.log('1.2 test 2'); + it('1-2 test 2', function () { + console.log('1-2 test 2'); }); afterEach(function () { - console.log('1.2 after each'); - throw new Error('1.2 after each hook failed'); + console.log('1-2 after each'); + throw new Error('1-2 after each hook failed'); }); after(function () { - console.log('1.2 after'); + console.log('1-2 after'); }); }); @@ -69,45 +69,45 @@ describe('2', function () { throw new Error('2 before each hook failed'); }); - describe('2.1', function () { + describe('2-1', function () { before(function () { - console.log('2.1 before'); + console.log('2-1 before'); }); beforeEach(function () { - console.log('2.1 before each'); + console.log('2-1 before each'); }); - it('2.1 test 1', function () { - console.log('2.1 test 1'); + it('2-1 test 1', function () { + console.log('2-1 test 1'); }); - it('2.1 test 2', function () { - console.log('2.1 test 2'); + it('2-1 test 2', function () { + console.log('2-1 test 2'); }); afterEach(function () { - console.log('2.1 after each'); + console.log('2-1 after each'); }); after(function () { - console.log('2.1 after'); + console.log('2-1 after'); }); }); - describe('2.2', function () { + describe('2-2', function () { before(function () { - console.log('2.2 before'); + console.log('2-2 before'); }); beforeEach(function () { - console.log('2.2 before each'); + console.log('2-2 before each'); }); - it('2.2 test 1', function () { - console.log('2.2 test 1'); + it('2-2 test 1', function () { + console.log('2-2 test 1'); }); - it('2.2 test 2', function () { - console.log('2.2 test 2'); + it('2-2 test 2', function () { + console.log('2-2 test 2'); }); afterEach(function () { - console.log('2.2 after each'); + console.log('2-2 after each'); }); after(function () { - console.log('2.2 after'); + console.log('2-2 after'); }); }); diff --git a/test/integration/helpers.js b/test/integration/helpers.js index d63756c0a1..2720eb3bda 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -1,6 +1,7 @@ var spawn = require('child_process').spawn; var path = require('path'); var fs = require('fs'); +var baseReporter = require('../../lib/reporters/base'); module.exports = { /** @@ -142,14 +143,20 @@ module.exports = { return diffs.map(function(diff) { return diff.slice(1, -3).join('\n'); }); - } + }, + + /** + * regular expression used for splitting lines based on new line / dot symbol. + */ + splitRegExp: new RegExp('[\\n' + baseReporter.symbols.dot + ']+') }; function invokeMocha(args, fn) { var output, mocha, listener; output = ''; - mocha = spawn('./bin/mocha', args); + args = [path.join('bin', 'mocha')].concat(args); + mocha = spawn(process.execPath, args); listener = function(data) { output += data; diff --git a/test/integration/hook.err.js b/test/integration/hook.err.js index ca1a8fe1bb..cf3d7c7c7b 100644 --- a/test/integration/hook.err.js +++ b/test/integration/hook.err.js @@ -1,5 +1,6 @@ var assert = require('assert'); var runMocha = require('./helpers').runMocha; +var splitRegExp = require('./helpers').splitRegExp; describe('hook error handling', function() { this.timeout(1000); @@ -63,30 +64,30 @@ describe('hook error handling', function() { lines, [ 'root before', - '1.1 before', + '1-1 before', 'root before each', '1 before each', - '1.1 before each', - '1.1 after each', + '1-1 before each', + '1-1 after each', '1 after each', 'root after each', - '1.1 after', - '1.2 before', + '1-1 after', + '1-2 before', 'root before each', '1 before each', - '1.2 before each', - '1.2 test 1', - '1.2 after each', + '1-2 before each', + '1-2 test 1', + '1-2 after each', '1 after each', 'root after each', - '1.2 after', + '1-2 after', '1 after', - '2.1 before', + '2-1 before', 'root before each', '2 before each', '2 after each', 'root after each', - '2.1 after', + '2-1 after', '2 after', 'root after' ] @@ -151,30 +152,30 @@ describe('hook error handling', function() { lines, [ 'root before', - '1.1 before', + '1-1 before', 'root before each', '1 before each', - '1.1 before each', - '1.1 after each', + '1-1 before each', + '1-1 after each', '1 after each', 'root after each', - '1.1 after', - '1.2 before', + '1-1 after', + '1-2 before', 'root before each', '1 before each', - '1.2 before each', - '1.2 test 1', - '1.2 after each', + '1-2 before each', + '1-2 test 1', + '1-2 after each', '1 after each', 'root after each', - '1.2 after', + '1-2 after', '1 after', - '2.1 before', + '2-1 before', 'root before each', '2 before each', '2 after each', 'root after each', - '2.1 after', + '2-1 after', '2 after', 'root after' ] @@ -188,7 +189,7 @@ describe('hook error handling', function() { assert.ifError(err); lines = res.output - .split(/[\n․]+/) + .split(splitRegExp) .map(function(line) { return line.trim(); }) diff --git a/test/integration/hooks.js b/test/integration/hooks.js index 4236c7f82d..127af18965 100644 --- a/test/integration/hooks.js +++ b/test/integration/hooks.js @@ -1,5 +1,6 @@ var assert = require('assert'); var run = require('./helpers').runMocha; +var splitRegExp = require('./helpers').splitRegExp; var args = []; describe('hooks', function() { @@ -11,7 +12,7 @@ describe('hooks', function() { assert(!err); - lines = res.output.split(/[\n․]+/).map(function(line) { + lines = res.output.split(splitRegExp).map(function(line) { return line.trim(); }).filter(function(line) { return line.length; diff --git a/test/integration/retries.js b/test/integration/retries.js index 68a8bca3e2..756cc9dd9b 100644 --- a/test/integration/retries.js +++ b/test/integration/retries.js @@ -11,7 +11,7 @@ describe('retries', function() { assert(!err); - lines = res.output.split(/[\n․]+/).map(function(line) { + lines = res.output.split(helpers.splitRegExp).map(function(line) { return line.trim(); }).filter(function(line) { return line.length; @@ -76,7 +76,7 @@ describe('retries', function() { assert(!err); - lines = res.output.split(/[\n․]+/).map(function(line) { + lines = res.output.split(helpers.splitRegExp).map(function(line) { return line.trim(); }).filter(function(line) { return line.length;