From ddbfc5c57a26396fce71a510aef2870c01d3a9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81=20Cruz?= Date: Sat, 20 Apr 2013 15:42:21 +0100 Subject: [PATCH] Add initial tests for the Resolver, fix some bugs. --- NOTES.md | 8 +- README.md | 10 +- lib/resolve/Resolver.js | 81 +++-- lib/resolve/Worker.js | 2 +- lib/resolve/resolvers/FsResolver.js | 2 +- lib/resolve/resolvers/GitFsResolver.js | 10 +- lib/resolve/resolvers/GitResolver.js | 17 +- lib/util/cmd.js | 2 + package.json | 5 +- test/resolve/resolver.js | 417 +++++++++++++++++++++++++ test/resolve/worker.js | 77 ++--- test/test.js | 3 + 12 files changed, 564 insertions(+), 70 deletions(-) create mode 100644 test/resolve/resolver.js diff --git a/NOTES.md b/NOTES.md index b4cef0db..83e2ac9d 100644 --- a/NOTES.md +++ b/NOTES.md @@ -15,6 +15,7 @@ - But then.. how would versions be handled here?? different versions might have changed the deps - Publish model - Commands + - Abbreviations is supported in nopt.. don't need to install anything else - Bower script x - post-install (only useful for moving files around) - pre-publish @@ -33,7 +34,12 @@ - Allow overrides of the registry for easier fork integration? this need to be discussed as part of the spec, see: https://github.com/twitter/bower/issues/342 - Install only stable versions, see: https://github.com/twitter/bower/issues/266 - Expand also .gz files, see: https://github.com/twitter/bower/issues/347 +- Contributing.md + - Copy from master + - Tell users to learn about promises + - Tell users to not forget to call .done() to throw unhandled promise errors in tests - Inter process locks? two processes running bower (either from the bin or programatically) might mess up the cache on write operations.. and possibly other write operations. Think about a solution for this (make the worker multi-process aware?!) - Use update-notifier!! - Use yeomen insight!! -- bower could setup a git hook on folders that are github repos to make validation of the json (if it conforms with the spec) \ No newline at end of file +- bower could setup a git hook on folders that are github repos to make validation of the json (if it conforms with the spec) +- in prod dont forget to Q.longStackJumpLimit = 0; diff --git a/README.md b/README.md index cc48cb5e..2a9da61f 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,8 @@ Resolvers are responsible for the following: - Based on an endpoint, fetch the contents of the package into a temporary folder (step is implemented by the `_resolveSelf()` method). - After the package is fetched, the `bower.json`/`component.json` (deprecated) file is read, validated and normalised (fill in properties) into a `package meta` object. If the file does not exist, a base one is inferred. Note that this should be done using a node module that is common for both the Bower client and the server. - Update any relevant information based on the `package meta` (e.g. this step may emit a `name_change`). -- Attach any additional meta data to the `package meta`. (e.g. the `UrlResolver` might store some `HTTP` response headers, to aid the `hasNew()` decision later on). - Applying the `ignore` constraint based on the `package meta`. Files are effectively removed in this step. +- Attach any additional meta data to the `package meta`. (e.g. the `UrlResolver` might store some `HTTP` response headers, to aid the `hasNew()` decision later on). - Storing the `package meta` into a `.bower.json` hidden file. @@ -189,14 +189,14 @@ Options: ##### Public functions -`Resolver#getName()`: String - -Returns the name. - `Resolver#getSource()`: String Returns the source. +`Resolver#getName()`: String + +Returns the name. + `Resolver#getTarget()`: String Returns the target. diff --git a/lib/resolve/Resolver.js b/lib/resolve/Resolver.js index 62b76fac..3d5818c9 100644 --- a/lib/resolve/Resolver.js +++ b/lib/resolve/Resolver.js @@ -6,6 +6,9 @@ var Q = require('q'); var tmp = require('tmp'); var mkdirp = require('mkdirp'); var bowerJson = require('bower-json'); +var pathspec = require('pathspec'); +var rimraf = require('rimraf'); +var glob = require('glob'); var config = require('../config'); var createError = require('../util/createError'); @@ -14,8 +17,8 @@ var Resolver = function (source, options) { this._source = source; this._target = options.target || '*'; - this._name = options.name; - this._guessedName = !this.name; + this._name = options.name || this._source; + this._guessedName = !options.name; this._config = options.config || config; }; @@ -24,6 +27,10 @@ util.inherits(Resolver, events.EventEmitter); // ----------------- Resolver.prototype.getSource = function () { + return this._source; +}; + +Resolver.prototype.getName = function () { return this._name; }; @@ -40,25 +47,31 @@ Resolver.prototype.hasNew = function (canonicalPkg) { }; Resolver.prototype.resolve = function () { + var that = this; + // Create temporary dir return this._createTempDir() // Resolve self .then(this._resolveSelf.bind(this)) // Read json, generating the package meta .then(function () { - return this._readJson(this._tempDir); - }.bind(this)) + return that._readJson(that._tempDir); + }) .then(function (meta) { return Q.all([ // Apply package meta - this._applyPkgMeta(meta), + that._applyPkgMeta(meta), // Save package meta - this._savePkgMeta(meta) + that._savePkgMeta(meta) ]); - }.bind(this)) + }) .then(function () { // Resolve with the folder - return this._tempDir; + return that._tempDir; + }, function (err) { + // If something went wrong, unset the temporary dir + that._tempDir = null; + throw err; }); }; @@ -87,9 +100,6 @@ Resolver.prototype._createTempDir = function () { }); }.bind(this)) .then(function (dir) { - // TODO: remove this - require('../util/cmd')('open', [dir]); - this._tempDir = dir; return dir; }.bind(this)); @@ -97,10 +107,10 @@ Resolver.prototype._createTempDir = function () { Resolver.prototype._readJson = function (dir) { return Q.nfcall(bowerJson.find, dir) - .then(function (filename) { + .then(function (filename) { // If it is a component.json, warn about the deprecation if (path.basename(filename) === 'component.json') { - this.emit('warn', 'Package "' + this.name + '" is using the deprecated component.json file'); + this.emit('warn', 'Package "' + this._name + '" is using the deprecated component.json file'); } // Read it @@ -119,16 +129,39 @@ Resolver.prototype._readJson = function (dir) { Resolver.prototype._applyPkgMeta = function (meta) { // Check if name defined in the json is different // If so and if the name was "guessed", assume the json name - if (this._guessedName && meta.name !== this.name) { - this.name = meta.name; - this.emit('name_change', this.name); + if (meta.name !== this._name) { + if (this._guessedName) { + this._name = meta.name; + this.emit('name_change', this._name); + } else { + meta.name = this._name; + } } // Handle ignore property, deleting all files from the temporary directory - return Q.fcall(function () { - // Delete all the files specified in the ignore from the temporary directory - // TODO: - }.bind(this)); + // TODO: Think better about this... some concrete resolvers can handle the ignore property + // in a more efficient way. + // In such cases, this step should be skipped + if (meta.ignore && meta.ignore.length) { + return Q.nfcall(glob, '**/*', { cwd: this._tempDir, dot: true, mark: true }) + .then(function (files) { + var filter = this._createIgnoreFilter(meta.ignores), + promises = []; + + // Foreach file that passes the ignore filter, + // rimraf it + files.forEach(function (file) { + if (filter(file)) { + promises.push(Q.nfcall(rimraf, file)); + } + }); + + // Wait for all the rimraf's to finish + return Q.all(promises); + }.bind(this)); + } + + return Q.resolve(meta); }; Resolver.prototype._savePkgMeta = function (meta) { @@ -139,4 +172,12 @@ Resolver.prototype._savePkgMeta = function (meta) { this._pkgMeta = meta; }.bind(this)); }; + +Resolver.prototype._createIgnoreFilter = function (ignore) { + var list = pathspec.RelPathList.parse(ignore); + + return function (filename) { + return list.matches(filename); + }; +}; module.exports = Resolver; \ No newline at end of file diff --git a/lib/resolve/Worker.js b/lib/resolve/Worker.js index 5b8bf536..a841837c 100644 --- a/lib/resolve/Worker.js +++ b/lib/resolve/Worker.js @@ -65,7 +65,7 @@ Worker.prototype._processQueue = function (type) { length = queue ? queue.length : 0, x; - for (x = 0; x < length; x += 1) { + for (x = 0; x < length; ++x) { if (this._processEntry(queue[x])) { break; } diff --git a/lib/resolve/resolvers/FsResolver.js b/lib/resolve/resolvers/FsResolver.js index 25efa2b9..0ddfe990 100644 --- a/lib/resolve/resolvers/FsResolver.js +++ b/lib/resolve/resolvers/FsResolver.js @@ -10,7 +10,7 @@ util.inherits(FsResolver, Resolver); // ----------------- -FsResolver.prototype.hasNew = function (oldResolution) { +FsResolver.prototype.hasNew = function (canonicalPkg) { // TODO: should we store latest modified files in the resolution and compare? return Q.resolve(true); }; diff --git a/lib/resolve/resolvers/GitFsResolver.js b/lib/resolve/resolvers/GitFsResolver.js index 37fdbf2e..372ee69a 100644 --- a/lib/resolve/resolvers/GitFsResolver.js +++ b/lib/resolve/resolvers/GitFsResolver.js @@ -23,13 +23,16 @@ mout.object.mixIn(GitFsResolver, GitResolver); GitFsResolver.prototype._resolveSelf = function () { return this._findResolution() + .then(this._readJson.bind(this, this._source)) .then(this._copy.bind(this)) .then(this._checkout.bind(this)); }; // ----------------- -GitFsResolver.prototype._copy = function () { +GitFsResolver.prototype._copy = function (meta) { + var ignore = meta.ignore; + // Copy folder permissions return Q.nfcall(fs.stat, this._source) .then(function (stat) { @@ -37,7 +40,10 @@ GitFsResolver.prototype._copy = function () { }.bind(this)) // Copy folder contents .then(function () { - return Q.nfcall(ncp, this._source, this._tempDir); + return Q.nfcall(ncp, this._source, this._tempDir, { + // Don't copy ignored files + filter: ignore && ignore.length ? this._createIgnoreFilter(ignore) : null + }); }.bind(this)); }; diff --git a/lib/resolve/resolvers/GitResolver.js b/lib/resolve/resolvers/GitResolver.js index 92323131..f7d3c0f0 100644 --- a/lib/resolve/resolvers/GitResolver.js +++ b/lib/resolve/resolvers/GitResolver.js @@ -18,8 +18,14 @@ GitResolver.prototype._resolveSelf = function () { .then(this._checkout.bind(this)); }; -GitResolver.prototype.hasNew = function (oldTarget, oldResolution) { - return this._findResolution() +GitResolver.prototype.hasNew = function (canonicalPkg) { + var oldResolution; + + return this._readJson(canonicalPkg) + .then(function (meta) { + oldResolution = meta._resolution || {}; + }) + .then(this._findResolution.bind(this)) .then(function (resolution) { // Check if resolution types are different if (oldResolution.type !== resolution.type) { @@ -102,6 +108,13 @@ GitResolver.prototype._findResolution = function (target) { }.bind(this)); }; +GitResolver.prototype._savePkgMeta = function (meta) { + // Save resolution to be used in hasNew later + meta._resolution = this._resolution; + + return Resolver.prototype._savePkgMeta.call(this, meta); +}; + // ------------------------------ GitResolver.fetchVersions = function (source) { diff --git a/lib/util/cmd.js b/lib/util/cmd.js index 6d8c991c..fb49cc55 100644 --- a/lib/util/cmd.js +++ b/lib/util/cmd.js @@ -16,6 +16,8 @@ function cmd(command, args, options) { process.stdout.on('data', function (data) { stdout += data.toString(); }); process.stderr.on('data', function (data) { stderr += data.toString(); }); + // Listen to the close event instead of exit + // They are similar but close ensures that streams are flushed process.on('close', function (code) { var fullCommand, error; diff --git a/package.json b/package.json index e1762398..e58f0f40 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,10 @@ "mkdirp": "~0.3.5", "nopt": "~2.1.1", "semver": "~1.1.4", - "ncp": "~0.4.2" + "ncp": "~0.4.2", + "pathspec": "~0.9.2", + "glob": "~3.1.21", + "rimraf": "~2.1.4" }, "devDependencies": { "mocha": "~1.8.2", diff --git a/test/resolve/resolver.js b/test/resolve/resolver.js new file mode 100644 index 00000000..6caee12b --- /dev/null +++ b/test/resolve/resolver.js @@ -0,0 +1,417 @@ +var expect = require('expect.js'); +var fs = require('fs'); +var path = require('path'); +var util = require('util'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var Resolver = require('../../lib/resolve/Resolver'); + +describe('Resolver', function () { + describe('.getSource', function () { + it('should return the resolver source', function () { + var resolver = new Resolver('foo'); + + expect(resolver.getSource()).to.equal('foo'); + }); + }); + + describe('.getName', function () { + it('should return the resolver name', function () { + var resolver = new Resolver('foo', { name: 'bar' }); + + expect(resolver.getName()).to.equal('bar'); + }); + + it('should return the resolver source if none is specified (default guess mechanism)', function () { + var resolver = new Resolver('foo'); + + expect(resolver.getName()).to.equal('foo'); + }); + }); + + describe('.getTarget', function () { + it('should return the resolver target', function () { + var resolver = new Resolver('foo', { target: '~2.1.0' }); + + expect(resolver.getTarget()).to.equal('~2.1.0'); + }); + + it('should return * if none was configured', function () { + var resolver = new Resolver('foo'); + + expect(resolver.getTarget()).to.equal('*'); + }); + }); + + describe('.hasNew', function () { + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise = resolver.hasNew('.'); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + + it('should resolve to true by default', function (next) { + var resolver = new Resolver('foo'); + + resolver.hasNew('.') + .then(function (hasNew) { + expect(hasNew).to.equal(true); + next(); + }) + .done(); + }); + }); + + describe('.resolve', function () { + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise = resolver.resolve(); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + + it('should reject the promise if _resolveSelf is not implemented', function (done) { + var resolver = new Resolver('foo'); + + resolver.resolve() + .then(function () { + done(new Error('Should have rejected the promise')); + }, function (err) { + expect(err).to.be.an(Error); + expect(err.message).to.contain('not implemented'); + done(); + }); + }); + + it('should call all the functions necessary to resolve by the correct order', function (next) { + function DummyResolver() { + Resolver.apply(this, arguments); + this._stack = []; + } + + util.inherits(DummyResolver, Resolver); + + DummyResolver.prototype.getStack = function () { + return this._stack; + }; + + DummyResolver.prototype.resolve = function () { + this._stack = []; + return Resolver.prototype.resolve.apply(this, arguments); + }; + + DummyResolver.prototype._createTempDir = function () { + this._stack.push('before _createTempDir'); + return Resolver.prototype._createTempDir.apply(this, arguments) + .then(function (val) { + this._stack.push('after _createTempDir'); + return val; + }.bind(this)); + }; + DummyResolver.prototype._resolveSelf = function () {}; + DummyResolver.prototype._readJson = function () { + this._stack.push('before _readJson'); + return Resolver.prototype._readJson.apply(this, arguments) + .then(function (val) { + this._stack.push('after _readJson'); + return val; + }.bind(this)); + }; + DummyResolver.prototype._applyPkgMeta = function () { + this._stack.push('before _applyPkgMeta'); + return Resolver.prototype._applyPkgMeta.apply(this, arguments) + .then(function (val) { + this._stack.push('after _applyPkgMeta'); + return val; + }.bind(this)); + }; + DummyResolver.prototype._savePkgMeta = function () { + this._stack.push('before _savePkgMeta'); + return Resolver.prototype._savePkgMeta.apply(this, arguments) + .then(function (val) { + this._stack.push('after _savePkgMeta'); + return val; + }.bind(this)); + }; + + var resolver = new DummyResolver('foo'); + + resolver.resolve() + .then(function () { + expect(resolver.getStack()).to.eql([ + 'before _createTempDir', + 'after _createTempDir', + 'before _readJson', + 'after _readJson', + // Both below are called in parallel + 'before _applyPkgMeta', + 'before _savePkgMeta', + 'after _applyPkgMeta', + 'after _savePkgMeta' + ]); + next(); + }) + .done(); + }); + + it('should resolve with the canonical package (folder)', function (next) { + var resolver = new Resolver('foo'); + + resolver._resolveSelf = function () {}; + + resolver.resolve() + .then(function (folder) { + expect(folder).to.be.a('string'); + expect(fs.existsSync(folder)).to.be(true); + next(); + }) + .done(); + }); + }); + + describe('.getTempDir', function () { + it('should return null if resolver is not yet resolved', function () { + var resolver = new Resolver('foo'); + + expect(resolver.getTempDir() == null).to.be(true); + }); + + it('should still return null if resolved failed', function () { + it('should still return null', function (next) { + var resolver = new Resolver('foo'); + + resolver._resolveSelf = function () { + throw new Error('I\'ve failed to resolve'); + }; + + resolver.resolve() + .then(null, function () { + expect(resolver.getTempDir() == null).to.be(true); + next(); + }); + }); + }); + + it('should return the canonical package (folder) if resolve succeeded', function (next) { + var resolver = new Resolver('foo'); + + resolver._resolveSelf = function () {}; + + resolver.resolve() + .then(function () { + var dir = resolver.getTempDir(); + + expect(dir).to.be.a('string'); + expect(fs.existsSync(dir)).to.be(true); + next(); + }) + .done(); + }); + }); + + describe('.getPkgMeta', function () { + it('should return null if resolver is not yet resolved', function () { + var resolver = new Resolver('foo'); + + expect(resolver.getPkgMeta() == null).to.be(true); + }); + + it('should still return null if resolved failed', function () { + it('should still return null', function (next) { + var resolver = new Resolver('foo'); + + resolver._resolveSelf = function () { + throw new Error('I\'ve failed to resolve'); + }; + + resolver.resolve() + .then(null, function () { + expect(resolver.getPkgMeta() == null).to.be(true); + next(); + }); + }); + }); + + it('should return the package meta if resolve succeeded', function (next) { + var resolver = new Resolver('foo'); + + resolver._resolveSelf = function () {}; + + resolver.resolve() + .then(function () { + expect(resolver.getPkgMeta()).to.be.an('object'); + next(); + }) + .done(); + }); + }); + + describe('_createTempDir', function () { + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise = resolver._createTempDir(); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + it.skip('should create a directory inside a bower folder, located within the OS temp folder'); + it.skip('should set the dir mode the same as the process'); + it.skip('should remove the folder after execution'); + it.skip('should set _tempDir with the created directory'); + }); + + describe('_readJson', function () { + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise = resolver._readJson(); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + it.skip('should read the bower.json file'); + it.skip('should fallback to component.json'); + it.skip('should resolve to an inferred json if no json file was found'); + it.skip('should apply normalisation, defaults and validation to the json object'); + }); + + describe('_applyPkgMeta', function () { + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise = resolver._applyPkgMeta({ name: 'foo' }); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + + it('should resolve with the the same package meta', function (next) { + var resolver = new Resolver('foo'), + meta = { name: 'foo' }; + + resolver._applyPkgMeta(meta) + .then(function (retMeta) { + expect(retMeta).to.equal(meta); + next(); + }) + .done(); + }); + + it('should fire the "name_change" event if the json name is different than the guessed one', function (next) { + var resolver = new Resolver('foo'), + newName; + + resolver.on('name_change', function (name) { + newName = name; + }); + + resolver._applyPkgMeta({ name: 'bar' }) + .then(function (retMeta) { + expect(retMeta.name).to.equal('bar'); + expect(resolver.getName()).to.equal('bar'); + expect(newName).to.equal('bar'); + next(); + }) + .done(); + }); + + it('should not fire the "name_change" event if the json name is different but the name was not guessed', function (next) { + var resolver = new Resolver('foo', { name: 'foo' }), + newName; + + resolver.on('name_change', function (name) { + newName = name; + }); + + resolver._applyPkgMeta({ name: 'bar' }) + .then(function (retMeta) { + expect(retMeta.name).to.equal('foo'); + expect(resolver.getName()).to.equal('foo'); + expect(newName).to.not.be.ok(); + next(); + }) + .done(); + }); + + it('should not fire the "name_change" event the json name is the same', function (next) { + var resolver = new Resolver('foo'), + newName; + + resolver.on('name_change', function (name) { + newName = name; + }); + + resolver._applyPkgMeta({ name: 'foo' }) + .then(function (retMeta) { + expect(retMeta.name).to.equal('foo'); + expect(resolver.getName()).to.equal('foo'); + expect(newName).to.not.be.ok(); + + resolver = new Resolver('foo', { name: 'foo' }); + + resolver.on('name_change', function (name) { + newName = name; + }); + + resolver._applyPkgMeta({ name: 'foo' }) + .then(function (retMeta) { + expect(retMeta.name).to.equal('foo'); + expect(resolver.getName()).to.equal('foo'); + expect(newName).to.not.be.ok(); + next(); + }) + .done(); + }) + .done(); + }); + + + describe('handling of ignore property according to the .gitignore spec', function () { + it.skip('A blank line matches no files, so it can serve as a separator for readability.'); + it.skip('A line starting with # serves as a comment.'); + it.skip('An optional prefix ! which negates the pattern; any matching file excluded by a previous pattern will become included again...', function () { + // If a negated pattern matches, this will override lower precedence patterns sources. Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!", for example, "\!important!.txt". + }); + it.skip('If the pattern ends with a slash, it is removed for the purpose of the following description, but it would only find a match with a directory...', function () { + // In other words, foo/ will match a directory foo and paths underneath it, but will not match a regular file or a symbolic link foo (this is consistent with the way how pathspec works in general in git). + }); + it.skip('If the pattern does not contain a slash /, git treats it as a shell glob pattern and checks for a match against the pathname without leading directories.'); + it.skip('Otherwise, git treats the pattern as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag..', function () { + // wildcards in the pattern will not match a / in the pathname. For example, "Documentation/*.html" matches "Documentation/git.html" but not "Documentation/ppc/ppc.html" or "tools/perf/Documentation/perf.html". + }); + }); + }); + + describe('_savePkgMeta', function () { + var tempDir = path.normalize(__dirname + '/../assets/tmp'); + + beforeEach(function (next) { + mkdirp(tempDir, next); + }); + + afterEach(function (next) { + rimraf(tempDir, next); + }); + + it('should return a promise', function (done) { + var resolver = new Resolver('foo'), + promise; + + resolver._tempDir = tempDir; + promise = resolver._savePkgMeta({ name: 'foo' }); + + expect(promise).to.be.an('object'); + expect(promise.then).to.be.an('function'); + promise.then(done.bind(done, null), done.bind(done, null)); + }); + + it.skip('should resolve with the the same package meta'); + it.skip('should save the package meta to the package meta file (.bower.json)'); + }); +}); \ No newline at end of file diff --git a/test/resolve/worker.js b/test/resolve/worker.js index 912071bc..4d1e01d9 100644 --- a/test/resolve/worker.js +++ b/test/resolve/worker.js @@ -1,6 +1,6 @@ var expect = require('expect.js'); var Q = require('Q'); -var Worker = require('../lib/resolve/Worker'); +var Worker = require('../../lib/resolve/Worker'); describe('Worker', function () { var timeout; @@ -19,71 +19,74 @@ describe('Worker', function () { promise = worker.enqueue(function () { return Q.resolve('foo'); }); + expect(promise).to.be.an('object'); expect(promise.then).to.be.a('function'); }); - it('should call the function and resolve', function (done) { + it('should call the function and resolve', function (next) { var worker = new Worker(); worker.enqueue(function () { return Q.resolve('foo'); }) .then(function (ret) { expect(ret).to.equal('foo'); - done(); - }); + next(); + }) + .done(); }); - it('should work with functions that return values syncronously', function (done) { + it('should work with functions that return values syncronously', function (next) { var worker = new Worker(); worker.enqueue(function () { return 'foo'; }) .then(function (ret) { expect(ret).to.equal('foo'); - done(); - }); + next(); + }) + .done(); }); - it('should assume the default concurrency when a type is not specified', function (done) { + it('should assume the default concurrency when a type is not specified', function (next) { var worker = new Worker(1), calls = 0; worker.enqueue(function () { calls++; return Q.defer().promise; }); - worker.enqueue(function () { done(new Error('Should not be called!')); }); + worker.enqueue(function () { next(new Error('Should not be called!')); }); timeout = setTimeout(function () { expect(calls).to.equal(1); - done(); + next(); }, 100); }); - it('should assume the default concurrency when a type is not known', function (done) { + it('should assume the default concurrency when a type is not known', function (next) { var worker = new Worker(1), calls = 0; worker.enqueue(function () { calls++; return Q.defer().promise; }, 'foo_type'); - worker.enqueue(function () { done(new Error('Should not be called!')); }, 'foo_type'); + worker.enqueue(function () { next(new Error('Should not be called!')); }, 'foo_type'); timeout = setTimeout(function () { expect(calls).to.equal(1); - done(); + next(); }, 100); }); - it('should have different slots when type is not passed or is not known', function (done) { + it('should have different slots when type is not passed or is not known', function (next) { var worker = new Worker(1), calls = 0; worker.enqueue(function () { calls++; return Q.defer().promise; }); worker.enqueue(function () { calls++; return Q.defer().promise; }, 'foo_type'); - worker.enqueue(function () { done(new Error('Should not be called!')); }); - worker.enqueue(function () { done(new Error('Should not be called!')); }, 'foo_type'); + worker.enqueue(function () { next(new Error('Should not be called!')); }); + worker.enqueue(function () { next(new Error('Should not be called!')); }, 'foo_type'); timeout = setTimeout(function () { expect(calls).to.equal(2); - done(); + next(); }, 100); }); - it('should use the configured concurrency for the type', function (done) { + it('should use the configured concurrency for the type', function (next) { var worker = new Worker(1, { foo: 2, bar: 3 @@ -95,35 +98,35 @@ describe('Worker', function () { }; worker.enqueue(function () { calls.def++; return Q.defer().promise; }); - worker.enqueue(function () { done(new Error('Should not be called!')); }); + worker.enqueue(function () { next(new Error('Should not be called!')); }); worker.enqueue(function () { calls.foo++; return Q.defer().promise; }, 'foo'); worker.enqueue(function () { calls.foo++; return Q.defer().promise; }, 'foo'); worker.enqueue(function () { calls.bar++; return Q.defer().promise; }, 'bar'); worker.enqueue(function () { calls.bar++; return Q.defer().promise; }, 'bar'); worker.enqueue(function () { calls.bar++; return Q.defer().promise; }, 'bar'); - worker.enqueue(function () { done(new Error('Should not be called!')); }, 'bar'); + worker.enqueue(function () { next(new Error('Should not be called!')); }, 'bar'); timeout = setTimeout(function () { expect(calls.def).to.equal(1); expect(calls.foo).to.equal(2); expect(calls.bar).to.equal(3); - done(); + next(); }, 100); }); }); describe('.abort', function () { - it('should clear the whole queue', function (done) { + it('should clear the whole queue', function (next) { var worker = new Worker(1, { foo: 2 }), calls = 0; worker.enqueue(function () { calls++; return Q.resolve(); }); - worker.enqueue(function () { done(new Error('Should not be called!')); }); + worker.enqueue(function () { next(new Error('Should not be called!')); }); worker.enqueue(function () { calls++; return Q.resolve(); }, 'foo'); worker.enqueue(function () { calls++; return Q.resolve(); }, 'foo'); - worker.enqueue(function () { done(new Error('Should not be called!')); }, 'foo'); + worker.enqueue(function () { next(new Error('Should not be called!')); }, 'foo'); worker.abort(); @@ -131,11 +134,11 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(4); - done(); + next(); }, 100); }); - it('should wait for currently running functions to finish', function (done) { + it('should wait for currently running functions to finish', function (next) { var worker = new Worker(1, { foo: 2 }), @@ -157,7 +160,7 @@ describe('Worker', function () { timeout = setTimeout(function () { worker.abort().then(function () { expect(calls).to.eql([1, 2, 3]); - done(); + next(); }); }, 30); }); @@ -165,7 +168,7 @@ describe('Worker', function () { describe('scheduler', function () { - it('should start remaining tasks when one ends', function (done) { + it('should start remaining tasks when one ends', function (next) { var worker = new Worker(1, { foo: 2 }), @@ -179,11 +182,11 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(5); - done(); + next(); }, 100); }); - it('should respect the enqueue order', function (done) { + it('should respect the enqueue order', function (next) { var worker = new Worker(1), defCalls = [], fooCalls = []; @@ -221,11 +224,11 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(defCalls).to.eql([1, 2, 3]); expect(fooCalls).to.eql([1, 2, 3]); - done(); + next(); }, 100); }); - it('should wait for one slot in every type on a multi-type function', function (done) { + it('should wait for one slot in every type on a multi-type function', function (next) { var worker = new Worker(1, { foo: 1, bar: 2 @@ -236,17 +239,17 @@ describe('Worker', function () { worker.enqueue(function () { return Q.defer().promise; }, 'bar'); worker.enqueue(function () { calls++; return Q.resolve(); }, 'bar'); - worker.enqueue(function () { done(new Error('Should not be called!')); }, ['foo', 'bar']); + worker.enqueue(function () { next(new Error('Should not be called!')); }, ['foo', 'bar']); worker.enqueue(function () { calls++; return Q.resolve(); }, 'bar'); - worker.enqueue(function () { done(new Error('Should not be called!')); }, 'foo'); + worker.enqueue(function () { next(new Error('Should not be called!')); }, 'foo'); timeout = setTimeout(function () { expect(calls).to.equal(2); - done(); + next(); }, 100); }); - it('should free all type slots when finished running a function', function (done) { + it('should free all type slots when finished running a function', function (next) { var worker = new Worker(1, { foo: 1, bar: 2 @@ -260,7 +263,7 @@ describe('Worker', function () { timeout = setTimeout(function () { expect(calls).to.equal(3); - done(); + next(); }, 100); }); }); diff --git a/test/test.js b/test/test.js index 1e9a0f19..a739b884 100644 --- a/test/test.js +++ b/test/test.js @@ -48,4 +48,7 @@ if (process.argv[1] && !/mocha/.test(process.argv[1])) { //testGitFsResolver(); //testGitRemoteResolverNoTags(); +} else { + require('./resolve/resolver'); + require('./resolve/worker'); } \ No newline at end of file