diff --git a/lib/dependencies.js b/lib/dependencies.js index 838d00b..4198bb0 100644 --- a/lib/dependencies.js +++ b/lib/dependencies.js @@ -119,6 +119,35 @@ var dependencies = module.exports = function (options, callback) { }); }; +// +// ### function of (system, tree) +// #### @system {Object|string} System to check if it is a dependency of. +// #### @tree {Object} Dependency tree to check `system` against. +// Returns a list of systems in `dependencies` which depend on the +// specified `system`. +// +dependencies.of = function (system, tree) { + var target = system.name || system, + dependent; + + dependent = Object.keys(tree).filter(function (name) { + var dep = tree[name], + children = Object.keys(dep.dependencies || {}); + + if (!children.length) { + return false; + } + + return !~children.indexOf(target) + ? dependencies.of(target, dep.dependencies) + : true; + }); + + return dependent.length + ? dependent + : false; +}; + // // ### function maxSatisfying (callback) // #### @options {Object} Options for calculating maximum satisfying set. diff --git a/test/dependencies-test.js b/test/dependencies-test.js index 02372d1..520b1b1 100644 --- a/test/dependencies-test.js +++ b/test/dependencies-test.js @@ -15,6 +15,10 @@ var assert = require('assert'), mock = require('./helpers/mock'), systemJson = require('../lib'); +var shouldAnalyzeDeps = macros.shouldAnalyzeDeps, + shouldBeDependent = macros.shouldBeDependent, + shouldNotBeDependent = macros.shouldNotBeDependent; + // // Asserts the remote runlist for `hello-remote-deps`. // @@ -60,10 +64,38 @@ function shouldMakeRemoteRunlist(assertFn) { vows.describe('system.json/dependencies').addBatch({ "When using system.json": { - "calculating dependencies": macros.shouldAnalyzeAllDeps(), - "the remote.runlist() method": { + "calculating dependencies": { + "with a no dependencies": shouldAnalyzeDeps('no-deps'), + "with a single dependency (implicit runlist)": shouldAnalyzeDeps('single-dep'), + "with a single dependency (empty runlist)": shouldAnalyzeDeps('empty-runlist'), + "with multiple dependencies": shouldAnalyzeDeps('depends-on-a-b'), + "with remoteDependencies": shouldAnalyzeDeps('hello-remote-deps'), + "with indirect remoteDependencies": shouldAnalyzeDeps('indirect-remote-deps'), + "with duplicate nested dependencies": shouldAnalyzeDeps('dep-in-dep'), + "with nested dependencies":  shouldAnalyzeDeps('nested-dep'), + "with a single OS dependency": shouldAnalyzeDeps('single-ubuntu-dep', 'ubuntu') + }, + "remote.runlist()": { "hello-remote-deps": shouldMakeRemoteRunlist(assertHelloRemoteDeps), "indirect-remote-deps": shouldMakeRemoteRunlist(assertHelloRemoteDeps) + }, + "dependencies.of()": { + "when dependedent:": { + "fixture-one": shouldBeDependent( + 'hello-world', + { name: 'ubuntu-dep', os: 'ubuntu' } + ), + "b": shouldBeDependent( + 'dep-in-dep', 'c', 'nested-dep', + { name: 'single-ubuntu-dep', os: 'ubuntu' } + ) + }, + "when not dependent": { + "fixture-one": shouldNotBeDependent( + 'a', 'b', 'c', + { name: 'ubuntu-dep', os: 'smartos' } + ) + } } } }).export(module); \ No newline at end of file diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 21b8417..6ee665a 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -27,6 +27,18 @@ var systems = module.exports = [ } } }, + { + name: 'hello-world', + version: '0.0.0', + versions: { + '0.0.0': { + dependencies: { + 'fixture-one': '0.0.x', + 'fixture-two': '0.0.x' + } + } + } + }, { name: 'indirect-remote-deps', version: '0.0.0', @@ -102,6 +114,29 @@ var systems = module.exports = [ } } }, + { + name: 'nested-dep', + version: '1.0.2', + versions: { + '1.0.2': { + runlist: ['c', 'b', 'a'], + dependencies: { + a: '0.0.1', + c: '0.3.0' + } + } + } + }, + { + name: 'ubuntu-dep', + version: '0.0.0', + versions: { + '0.0.0': {} + }, + os: { + ubuntu: { 'fixture-one': '0.0.0' } + } + }, { name: 'single-ubuntu-dep', version: '0.0.1', diff --git a/test/fixtures/trees.js b/test/fixtures/trees.js index 1ada294..68271fb 100644 --- a/test/fixtures/trees.js +++ b/test/fixtures/trees.js @@ -132,6 +132,7 @@ trees['depends-on-a-b'] = { // // Dependency tree with dependency in a dependency +// that is also required by the top-level // trees['dep-in-dep'] = { tree: { @@ -176,6 +177,45 @@ trees['dep-in-dep'] = { list: ['b@0.2.0', 'c@0.3.0', 'a@0.0.1', 'dep-in-dep@1.0.2'] }; +// +// Dependency tree with dependency in a dependency +// +trees['nested-dep'] = { + tree: { + 'nested-dep': { + name: 'nested-dep', + runlist: [ 'c', 'b', 'a' ], + dependencies: { + a: { + name: 'a', + runlist: [], + required: '0.0.1', + dependencies: {}, + version: '0.0.1' + }, + c: { + name: 'c', + runlist: [ 'b' ], + dependencies: { + b: { + name: 'b', + runlist: [], + required: '0.2.0', + dependencies: {}, + version: '0.2.0' + } + }, + required: '0.3.0', + version: '0.3.0' + } + }, + required: '*', + version: '1.0.2' + } + }, + list: ['b@0.2.0', 'c@0.3.0', 'a@0.0.1', 'nested-dep@1.0.2'] +}; + // // Dependency with an implied runlist // diff --git a/test/helpers/index.js b/test/helpers/index.js new file mode 100644 index 0000000..82fe019 --- /dev/null +++ b/test/helpers/index.js @@ -0,0 +1,36 @@ +/* + * index.js: Test helpers for `system.json`. + * + * (C) 2010, Nodejitsu Inc. + * + */ + +var assert = require('assert'), + fs = require('fs'), + path = require('path'), + conservatory = require('conservatory-api'), + nock = require('nock'), + utile = require('utile'), + mock = require('./mock'), + systemJson = require('../../lib'), + trees = require('../fixtures/trees'); + +// +// Helper function that fetches all dependencies for +// `system` and `os` using a mocked `conservatory-api` client. +// +exports.dependencies = function (system, os, callback) { + var api = nock('http://api.testquill.com'); + + mock.systems.all(api); + systemJson.dependencies({ + client: conservatory.createClient('composer', { + protocol: 'http', + host: 'api.testquill.com', + port: 80, + auth: {} + }).systems, + systems: system, + os: os + }, callback); +}; \ No newline at end of file diff --git a/test/helpers/macros.js b/test/helpers/macros.js index 44e8086..ea5547e 100644 --- a/test/helpers/macros.js +++ b/test/helpers/macros.js @@ -1,61 +1,42 @@ +/* + * macros.js: Test macros for `system.json`. + * + * (C) 2010, Nodejitsu Inc. + * + */ var assert = require('assert'), fs = require('fs'), path = require('path'), conservatory = require('conservatory-api'), nock = require('nock'), + utile = require('utile'), mock = require('./mock'), + helpers = require('./index'), systemJson = require('../../lib'), trees = require('../fixtures/trees'); -exports.shouldAnalyzeAllDeps = function () { - var shouldAnalyzeDeps = exports.shouldAnalyzeDeps; - - return { - "with a no dependencies": shouldAnalyzeDeps('no-deps'), - "with a single dependency (implicit runlist)": shouldAnalyzeDeps('single-dep'), - "with a single dependency (empty runlist)": shouldAnalyzeDeps('empty-runlist'), - "with multiple dependencies": shouldAnalyzeDeps('depends-on-a-b'), - "with remoteDependencies": shouldAnalyzeDeps('hello-remote-deps'), - "with indirect remoteDependencies": shouldAnalyzeDeps('indirect-remote-deps'), - "with a dependency in a dependency": shouldAnalyzeDeps('dep-in-dep'), - "with a single OS dependency": shouldAnalyzeDeps('single-ubuntu-dep', 'ubuntu') - }; -}; - // // ### function shouldFindDeps (system, os) // -// Setups mock API endpoints for the `systems`, invokes +// Setups mock API endpoints for the `systems`, invokes // `systemJson.dependencies()` and asserts the result // is equal to `tree`. // exports.shouldAnalyzeDeps = function (system, os) { - var api = nock('http://api.testquill.com'), - fixture = trees[system], + var fixture = trees[system], tree = fixture.tree; - - mock.systems.all(api); - + return { "the dependencies() method": { topic: function () { - systemJson.dependencies({ - client: conservatory.createClient('composer', { - protocol: 'http', - host: 'api.testquill.com', - port: 80, - auth: {} - }).systems, - systems: system, - os: os - }, this.callback); + helpers.dependencies(system, os, this.callback); }, "should respond with the correct dependency tree": function (err, actual) { assert.isNull(err); assert.deepEqual(actual, tree); }, - "the runlist() method": exports.shouldMakeRunlist(system, os) + "when used by runlist()": exports.shouldMakeRunlist(system, os) } }; }; @@ -63,7 +44,7 @@ exports.shouldAnalyzeDeps = function (system, os) { // // ### function shouldMakeRunlist (system, os) // -// Setups mock API endpoints for the `systems`, invokes +// Setups mock API endpoints for the `systems`, invokes // `systemJson.runlist()` and asserts the result // is equal to `list`. // @@ -87,4 +68,72 @@ exports.shouldMakeRunlist = function (system, os) { ); } } +}; + +// +// ### function shouldBeDependent (system0, system1, ...) +// Asserts that all arguments depend on the +// context name. +// +exports.shouldBeDependent = function () { + return exports.shouldHaveRelationship( + 'should be dependent', + function (tree) { + assert.include( + systemJson.dependencies.of(this.target, tree), + this.system + ); + } + ).apply(null, arguments); +}; + +// +// ### function shouldNotBeDependent (system0, system1, ...) +// Asserts that all arguments do not depend on the +// context name. +// +exports.shouldNotBeDependent = function () { + return exports.shouldHaveRelationship( + 'should not be dependent', + function (tree) { + assert.isFalse( + systemJson.dependencies.of(this.target, tree) + ); + } + ).apply(null, arguments); +}; + +// +// ### function shouldHaveRelationship (msg, assertFn) +// Asserts that all arguments have the `assertFn` relationship +// with the context name. +// +exports.shouldHaveRelationship = function (msg, assertFn) { + return function () { + return Array.prototype.slice.call(arguments) + .reduce(function (tests, system) { + var name = system, + os; + + if (typeof system === 'object') { + name = system.name; + os = system.os + } + + tests['by ' + name] = { + topic: function (target) { + this.target = target; + this.system = name; + helpers.dependencies(name, os, this.callback); + } + }; + + tests['by ' + name][msg] = assertFn; + return tests; + }, { + topic: function () { + return this.context.name; + } + }); + }; }; \ No newline at end of file diff --git a/test/helpers/mock.js b/test/helpers/mock.js index 4931123..e6d9c4f 100644 --- a/test/helpers/mock.js +++ b/test/helpers/mock.js @@ -1,5 +1,5 @@ /* - * mock.js: Mock helpers for `quill`. + * mock.js: Mock helpers for `system.json`. * * (C) 2010, Nodejitsu Inc. *