From 2e9e1451a38b641e002ef0396ac9fc4653c59bc9 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Thu, 15 May 2014 20:11:26 +0100 Subject: [PATCH 01/12] need a .gitignore to deal with npm install locally --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23ed173 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/* + From be90bca7380c54eb73d00cd9228676678560ac2f Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Thu, 15 May 2014 20:11:55 +0100 Subject: [PATCH 02/12] refactor bootstrap.coffee to make it more easily testable --- lib/warlock/bootstrap.coffee | 67 +++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/lib/warlock/bootstrap.coffee b/lib/warlock/bootstrap.coffee index b2118f5..3598787 100644 --- a/lib/warlock/bootstrap.coffee +++ b/lib/warlock/bootstrap.coffee @@ -6,24 +6,46 @@ PATH = require 'path' # ngbo libraries warlock = require './../warlock' -module.exports = ( options ) -> + +bootstrap = module.exports = ( options ) -> warlock.debug.log "Bootstrapping warlock..." # Store the options provided for system-wide access warlock.options.init options - - # Locate the configuration file and save its path for later use + configPath = FINDUP warlock.options( 'configPath' ), { cwd: process.cwd() } + pkgPath = FINDUP "package.json", { cwd: process.cwd() } + promise = warlock.util.q "{}" + bootstrap.readConfig configPath, pkgPath, promise + + promise + .then () -> + bootstrap.loadPlugins + .then () -> + bootstrap.setDefaultTask + + warlock.util.q true + + + +### +# Find the warlock configuration and package.json, using defaults as necessary, +# read them in, and initialise the configuration using them +### +bootstrap.readConfig = ( configPath, pkgPath, promise ) -> + # Locate the configuration file and save its path for later use if not configPath? warlock.log.warning "No config file found. Using empty configuration instead." else warlock.debug.log "Config found: #{configPath}" promise = warlock.file.readFile( configPath ) - promise + return promise .then ( file ) -> # Save the config file location for later use warlock.options 'configFile', configPath || 'warlock.json' + + # TODO (pml): is it always true that the projectPath is the dir part of configPath? warlock.options 'projectPath', PATH.dirname( configPath ) # Parse its contents @@ -31,12 +53,11 @@ module.exports = ( options ) -> , ( err ) -> warlock.fatal "Could not read config file: #{err.toString()}." .then ( config ) -> - warlock.debug.log "Config loaded." + warlock.debug.log "Config loaded, content was:\n" + JSON.stringify( config ) # Load the config into warlock warlock.config.init config # Read in the package.json. It's required. - pkgPath = FINDUP "package.json", { cwd: process.cwd() } warlock.file.readFile( pkgPath ) , ( err ) -> warlock.fatal "Could not parse config file: #{err.toString()}" @@ -48,11 +69,19 @@ module.exports = ( options ) -> .then ( pkg ) -> warlock.debug.log "package.json loaded to `pkg`." warlock.config "pkg", pkg - - # Load all the plugins - warlock.plugins.load() , ( err ) -> warlock.fatal "Could not parse package.json: #{err.toString()}" + + + +### +# Load all plugins +### +bootstrap.loadPlugins = () -> + # Load all the plugins + warlock.debug.log "commencing loading plugins" + + warlock.plugins.load() .then () -> warlock.flow.validate() flows = warlock.flow.all() @@ -92,14 +121,18 @@ module.exports = ( options ) -> warlock.util.mout.object.forOwn metaTasks, ( deps, task ) -> warlock.task.add task, deps + warlock.debug.log "plugins loaded" + warlock.util.q true - .then () -> - # Ensure we have a default task defined, if none has been specified elsewhere - tasks = warlock.config( "default" ) - if tasks? and tasks?.length - warlock.task.add "default", tasks - else - warlock.log.warning "There is no default task defined. This is usually handed by plugins or spells." - warlock.util.q true + +bootstrap.setDefaultTask = () -> + # Set the default task(s) + tasks = warlock.config( "default" ) + + if tasks? and tasks?.length + warlock.task.add "default", tasks + else + warlock.log.warning "There is no default task defined. This is usually handed by plugins or spells." + From 21ab1e107f6b53f1961591c644e5c069e692599b Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Thu, 15 May 2014 20:12:13 +0100 Subject: [PATCH 03/12] unit tests for bootstrap module --- test/bootstrap.coffee | 53 ++++++++++++++++++++++++++++ test/fixtures/bootstrap/empty.json | 2 ++ test/fixtures/bootstrap/package.json | 15 ++++++++ test/fixtures/bootstrap/user.json | 7 ++++ test/fixtures/config/package.json | 15 ++++++++ test/fixtures/config/user.json | 7 ++++ 6 files changed, 99 insertions(+) create mode 100644 test/bootstrap.coffee create mode 100644 test/fixtures/bootstrap/empty.json create mode 100644 test/fixtures/bootstrap/package.json create mode 100644 test/fixtures/bootstrap/user.json create mode 100644 test/fixtures/config/package.json create mode 100644 test/fixtures/config/user.json diff --git a/test/bootstrap.coffee b/test/bootstrap.coffee new file mode 100644 index 0000000..b17171b --- /dev/null +++ b/test/bootstrap.coffee @@ -0,0 +1,53 @@ +'use strict' +should = require 'should' +join = ( require 'path' ).join +options = require '../lib/warlock/options' +warlock = require '../lib/warlock' +bootstrap = require '../lib/warlock/bootstrap' +config = require '../lib/warlock/config' + +require 'mocha' + +beforeEach -> + options.init { 'debug': false } + +describe 'warlock bootstrap: ', -> + describe 'read_config ', -> + # these tests focus on the fact that config routines are called, detailed testing of config is in the config module + it 'config, empty package.json, loaded correctly', -> + promise = bootstrap.readConfig 'test/fixtures/bootstrap/user.json', 'test/fixtures/bootstrap/empty.json' + + promise + .then -> + config.get( 'standard_values.one' ).should.eql false + config.get( 'standard_values.extra_1' ).should.eql 1 + config.get( 'standard_values.extra_2' ).should.eql 'fred' + + + it 'empty config, package.json, loaded correctly', -> + promise = bootstrap.readConfig 'test/fixtures/bootstrap/empty.json', 'test/fixtures/bootstrap/package.json' + + promise + .then -> + config.get( 'pkg.standard_values.one' ).should.eql true + config.get( 'pkg.standard_values.two' ).should.eql false + config.get( 'pkg.standard_values.three' ).should.eql "three" + config.get( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } + config.get( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + + # TODO(pml): not clear how to test exceptions when asynch - mocha doesn't have great support + # ideally we'd test a file that cannot parse, a missing file etc + + + describe 'loadPlugins ', -> + # these tests focus on the fact that load plugins is called, detailed testing of plugins is in the plugin module + # TODO(pml): still need to work out what this actually does so as to verify it + + describe 'setDefaultTask', -> + it 'establishes default tasks', -> + config.set( 'default', [ 'a_task' ] ) + + bootstrap.setDefaultTask() + + # TODO(pml): this doesn't work + # warlock.task.getTasks().should.eql( [ 'a_task' ] ) diff --git a/test/fixtures/bootstrap/empty.json b/test/fixtures/bootstrap/empty.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/test/fixtures/bootstrap/empty.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/test/fixtures/bootstrap/package.json b/test/fixtures/bootstrap/package.json new file mode 100644 index 0000000..96a9659 --- /dev/null +++ b/test/fixtures/bootstrap/package.json @@ -0,0 +1,15 @@ +{ + "standard_values": { + "one": true, + "two": false, + "three": "three" + }, + "structures": { + "a_hash": { + "value_1": "value", + "value_2": true, + "value_3": 3 + }, + "an_array": ["test", 1, true, "john"] + } +} \ No newline at end of file diff --git a/test/fixtures/bootstrap/user.json b/test/fixtures/bootstrap/user.json new file mode 100644 index 0000000..fe7b311 --- /dev/null +++ b/test/fixtures/bootstrap/user.json @@ -0,0 +1,7 @@ +{ + "standard_values": { + "one": false, + "extra_1": 1, + "extra_2": "fred" + } +} \ No newline at end of file diff --git a/test/fixtures/config/package.json b/test/fixtures/config/package.json new file mode 100644 index 0000000..96a9659 --- /dev/null +++ b/test/fixtures/config/package.json @@ -0,0 +1,15 @@ +{ + "standard_values": { + "one": true, + "two": false, + "three": "three" + }, + "structures": { + "a_hash": { + "value_1": "value", + "value_2": true, + "value_3": 3 + }, + "an_array": ["test", 1, true, "john"] + } +} \ No newline at end of file diff --git a/test/fixtures/config/user.json b/test/fixtures/config/user.json new file mode 100644 index 0000000..55941da --- /dev/null +++ b/test/fixtures/config/user.json @@ -0,0 +1,7 @@ +{ + "standard_values": { + "one": false, + "extra_1": 1, + "extra_2": true + } +} \ No newline at end of file From 5da31d205abc8361120f11b7d8d6ecab4f33ad96 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Fri, 16 May 2014 14:43:10 +0100 Subject: [PATCH 04/12] fix(4): config merge: values after booleans ignored. mout.forIn terminates processing if the function returns false. No return value was specified, if the value processed equalled 'false' then the function would default to returning false. Added an explicit true return --- lib/warlock/util.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/warlock/util.coffee b/lib/warlock/util.coffee index 84cc2f3..4c777e9 100644 --- a/lib/warlock/util.coffee +++ b/lib/warlock/util.coffee @@ -124,7 +124,8 @@ util.forEveryProperty = ( value, fn, fnContinue ) -> obj = {} MOUT.object.forIn value, ( val, key ) -> obj[ key ] = util.forEveryProperty val, fn, fnContinue - + return true + obj else # Otherwise pass value into fn and return. From a3a816b3d78027d76915a1f81deae94245c10890 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Fri, 16 May 2014 14:44:47 +0100 Subject: [PATCH 05/12] tidy: removed testing values, extended comment on usage of config --- lib/warlock/config.coffee | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/warlock/config.coffee b/lib/warlock/config.coffee index e67349e..445f8c7 100644 --- a/lib/warlock/config.coffee +++ b/lib/warlock/config.coffee @@ -21,7 +21,7 @@ _defaultConfig = plugins: [] # Tasks to prevent from running. - prevent: [ 'something1' ] + prevent: [] # Tasks to inject into one of the flows. inject: [] @@ -29,8 +29,6 @@ _defaultConfig = # The default tasks to run when none are specified. default: [] - myval: "Hello!" - ### # The current warlock-wide configuration. ### @@ -41,6 +39,15 @@ _config = {} ### _userConfig = {} + +### +# A multi-purpose function, allows merging config into the cached config, setting +# a key to a given value, or retrieving a value given a key +# +# key should always be provided. If val is provided, then key is set to val. +# if merge is provided, then the content of merge is merged into the config +# In all instances, the value associated with the requested key is returned. +### config = module.exports = ( key, val, merge ) -> if key if val From 00ec6b4c0998d3d5900970520d0ec0a6c1c26ec1 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Fri, 16 May 2014 19:03:13 +0200 Subject: [PATCH 06/12] Config unit tests --- lib/warlock/bootstrap.coffee | 17 +++---- lib/warlock/config.coffee | 2 +- package.json | 4 ++ ...bootstrap.coffee => bootstrap.spec.coffee} | 22 ++++----- test/config.spec.coffee | 49 +++++++++++++++++++ test/fixtures/config/user.json | 8 +-- 6 files changed, 77 insertions(+), 25 deletions(-) rename test/{bootstrap.coffee => bootstrap.spec.coffee} (75%) create mode 100644 test/config.spec.coffee diff --git a/lib/warlock/bootstrap.coffee b/lib/warlock/bootstrap.coffee index 3598787..bb4d20c 100644 --- a/lib/warlock/bootstrap.coffee +++ b/lib/warlock/bootstrap.coffee @@ -15,14 +15,11 @@ bootstrap = module.exports = ( options ) -> configPath = FINDUP warlock.options( 'configPath' ), { cwd: process.cwd() } pkgPath = FINDUP "package.json", { cwd: process.cwd() } - promise = warlock.util.q "{}" - bootstrap.readConfig configPath, pkgPath, promise - - promise + bootstrap._readConfig( configPath, pkgPath ) .then () -> - bootstrap.loadPlugins + bootstrap._loadPlugins .then () -> - bootstrap.setDefaultTask + bootstrap._setDefaultTask warlock.util.q true @@ -32,8 +29,10 @@ bootstrap = module.exports = ( options ) -> # Find the warlock configuration and package.json, using defaults as necessary, # read them in, and initialise the configuration using them ### -bootstrap.readConfig = ( configPath, pkgPath, promise ) -> +bootstrap._readConfig = ( configPath, pkgPath, promise ) -> # Locate the configuration file and save its path for later use + promise = warlock.util.q "{}" + if not configPath? warlock.log.warning "No config file found. Using empty configuration instead." else @@ -77,7 +76,7 @@ bootstrap.readConfig = ( configPath, pkgPath, promise ) -> ### # Load all plugins ### -bootstrap.loadPlugins = () -> +bootstrap._loadPlugins = () -> # Load all the plugins warlock.debug.log "commencing loading plugins" @@ -126,7 +125,7 @@ bootstrap.loadPlugins = () -> warlock.util.q true -bootstrap.setDefaultTask = () -> +bootstrap._setDefaultTask = () -> # Set the default task(s) tasks = warlock.config( "default" ) diff --git a/lib/warlock/config.coffee b/lib/warlock/config.coffee index 445f8c7..82a2f10 100644 --- a/lib/warlock/config.coffee +++ b/lib/warlock/config.coffee @@ -45,7 +45,7 @@ _userConfig = {} # a key to a given value, or retrieving a value given a key # # key should always be provided. If val is provided, then key is set to val. -# if merge is provided, then the content of merge is merged into the config +# if merge is true, then the content of merge is merged into the config # In all instances, the value associated with the requested key is returned. ### config = module.exports = ( key, val, merge ) -> diff --git a/package.json b/package.json index 718c73d..442ffa0 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,9 @@ "q": "~0.9.7", "rimraf": "~2.2.6", "vinyl-fs": "0.0.2" + }, + "devDependencies": { + "mocha": "^1.18.2", + "should": "^3.3.1" } } diff --git a/test/bootstrap.coffee b/test/bootstrap.spec.coffee similarity index 75% rename from test/bootstrap.coffee rename to test/bootstrap.spec.coffee index b17171b..e9ee511 100644 --- a/test/bootstrap.coffee +++ b/test/bootstrap.spec.coffee @@ -14,26 +14,24 @@ beforeEach -> describe 'warlock bootstrap: ', -> describe 'read_config ', -> # these tests focus on the fact that config routines are called, detailed testing of config is in the config module - it 'config, empty package.json, loaded correctly', -> - promise = bootstrap.readConfig 'test/fixtures/bootstrap/user.json', 'test/fixtures/bootstrap/empty.json' - - promise + it 'config, empty package.json, loaded correctly', ( done )-> + bootstrap._readConfig 'test/fixtures/bootstrap/user.json', 'test/fixtures/bootstrap/empty.json' .then -> config.get( 'standard_values.one' ).should.eql false config.get( 'standard_values.extra_1' ).should.eql 1 config.get( 'standard_values.extra_2' ).should.eql 'fred' + done() - it 'empty config, package.json, loaded correctly', -> - promise = bootstrap.readConfig 'test/fixtures/bootstrap/empty.json', 'test/fixtures/bootstrap/package.json' - - promise + it 'empty config, package.json, loaded correctly', ( done ) -> + bootstrap._readConfig 'test/fixtures/bootstrap/empty.json', 'test/fixtures/bootstrap/package.json' .then -> config.get( 'pkg.standard_values.one' ).should.eql true config.get( 'pkg.standard_values.two' ).should.eql false config.get( 'pkg.standard_values.three' ).should.eql "three" config.get( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } config.get( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + done() # TODO(pml): not clear how to test exceptions when asynch - mocha doesn't have great support # ideally we'd test a file that cannot parse, a missing file etc @@ -44,10 +42,12 @@ describe 'warlock bootstrap: ', -> # TODO(pml): still need to work out what this actually does so as to verify it describe 'setDefaultTask', -> - it 'establishes default tasks', -> + it 'establishes default tasks', ( done ) -> config.set( 'default', [ 'a_task' ] ) - bootstrap.setDefaultTask() + bootstrap._setDefaultTask() + done() - # TODO(pml): this doesn't work + # TODO(pml): this doesn't work - task setup needs more content # warlock.task.getTasks().should.eql( [ 'a_task' ] ) + diff --git a/test/config.spec.coffee b/test/config.spec.coffee new file mode 100644 index 0000000..b5910b5 --- /dev/null +++ b/test/config.spec.coffee @@ -0,0 +1,49 @@ +'use strict' +should = require 'should' +join = ( require 'path' ).join +options = require '../lib/warlock/options' +warlock = require '../lib/warlock' +bootstrap = require '../lib/warlock/bootstrap' +config = require '../lib/warlock/config' + +require 'mocha' + +beforeEach -> + options.init { 'debug': false } + +describe 'warlock config: ', -> + describe 'config base export: ', -> + # these tests focus on the fact that config routines are called, detailed testing of config is in the config module + it 'get values from config that has been loaded', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + config( 'blah.one' ).should.eql true + config( 'blah.two' ).should.eql false + config( 'blah.three' ).should.eql 'three' + config( 'blah' ).should.eql { "one": true, "two": false, "three": "three" } + + config( 'pkg.standard_values.one' ).should.eql true + config( 'pkg.standard_values.two' ).should.eql false + config( 'pkg.standard_values.three' ).should.eql "three" + config( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } + config( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + + done() + + it 'add a value to the loaded config, it\'s still there when asked for again', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + config( 'blah.four', 'four' ).should.eql 'four' + config( 'blah.four' ).should.eql 'four' + + done() + + it 'use merge to add a value to the loaded config, values have been persisted into the config', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + config( 'blah.four', 'four', true ).should.eql 'four' + config( 'blah.four' ).should.eql 'four' + + done() + + diff --git a/test/fixtures/config/user.json b/test/fixtures/config/user.json index 55941da..4b9237a 100644 --- a/test/fixtures/config/user.json +++ b/test/fixtures/config/user.json @@ -1,7 +1,7 @@ { - "standard_values": { - "one": false, - "extra_1": 1, - "extra_2": true + "blah": { + "one": true, + "two": false, + "three": "three" } } \ No newline at end of file From e5604febc13d3bbb1baab0d0d4618ab2152e6da8 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Fri, 16 May 2014 23:11:55 +0200 Subject: [PATCH 07/12] Add mocha opts, allowing coffee without command line options --- test/mocha.opts | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/mocha.opts diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..8bd4127 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +--compilers coffee:coffee-script/register From cb26b8d6f60d0590f4527e7de74ac6bafd1a25ce Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Sat, 17 May 2014 10:45:51 +0200 Subject: [PATCH 08/12] Removed .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 23ed173..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/* - From 69a1448422fe223f424667b864c5ab738d4ce7ee Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Sat, 17 May 2014 10:46:19 +0200 Subject: [PATCH 09/12] Add try/catch, as otherwise exceptions aren't reported, and extend config unit tests --- test/bootstrap.spec.coffee | 37 ++++++++++------ test/config.spec.coffee | 80 ++++++++++++++++++++++++++-------- test/fixtures/config/user.json | 7 ++- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/test/bootstrap.spec.coffee b/test/bootstrap.spec.coffee index e9ee511..fe82fcd 100644 --- a/test/bootstrap.spec.coffee +++ b/test/bootstrap.spec.coffee @@ -17,21 +17,27 @@ describe 'warlock bootstrap: ', -> it 'config, empty package.json, loaded correctly', ( done )-> bootstrap._readConfig 'test/fixtures/bootstrap/user.json', 'test/fixtures/bootstrap/empty.json' .then -> - config.get( 'standard_values.one' ).should.eql false - config.get( 'standard_values.extra_1' ).should.eql 1 - config.get( 'standard_values.extra_2' ).should.eql 'fred' - done() + try + config.get( 'standard_values.one' ).should.eql false + config.get( 'standard_values.extra_1' ).should.eql 1 + config.get( 'standard_values.extra_2' ).should.eql 'fred' + done() + catch err + done( err ) it 'empty config, package.json, loaded correctly', ( done ) -> bootstrap._readConfig 'test/fixtures/bootstrap/empty.json', 'test/fixtures/bootstrap/package.json' .then -> - config.get( 'pkg.standard_values.one' ).should.eql true - config.get( 'pkg.standard_values.two' ).should.eql false - config.get( 'pkg.standard_values.three' ).should.eql "three" - config.get( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } - config.get( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] - done() + try + config.get( 'pkg.standard_values.one' ).should.eql true + config.get( 'pkg.standard_values.two' ).should.eql false + config.get( 'pkg.standard_values.three' ).should.eql "three" + config.get( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } + config.get( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + done() + catch err + done( err ) # TODO(pml): not clear how to test exceptions when asynch - mocha doesn't have great support # ideally we'd test a file that cannot parse, a missing file etc @@ -43,10 +49,13 @@ describe 'warlock bootstrap: ', -> describe 'setDefaultTask', -> it 'establishes default tasks', ( done ) -> - config.set( 'default', [ 'a_task' ] ) - - bootstrap._setDefaultTask() - done() + try + config.set( 'default', [ 'a_task' ] ) + + bootstrap._setDefaultTask() + done() + catch err + done( err ) # TODO(pml): this doesn't work - task setup needs more content # warlock.task.getTasks().should.eql( [ 'a_task' ] ) diff --git a/test/config.spec.coffee b/test/config.spec.coffee index b5910b5..63da156 100644 --- a/test/config.spec.coffee +++ b/test/config.spec.coffee @@ -17,33 +17,75 @@ describe 'warlock config: ', -> it 'get values from config that has been loaded', ( done )-> bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' .then -> - config( 'blah.one' ).should.eql true - config( 'blah.two' ).should.eql false - config( 'blah.three' ).should.eql 'three' - config( 'blah' ).should.eql { "one": true, "two": false, "three": "three" } + try + config( 'blah.one' ).should.eql true + config( 'blah.two' ).should.eql false + config( 'blah.three' ).should.eql 'three' + config( 'blah.an_array' ).should.eql [ 'item_1', 'item_3' ] + config( 'blah.an_object' ).should.eql { "value_1": 1, "value_2": 'two' } + config( 'blah' ).should.eql { "one": true, "two": false, "three": "three", "an_array": [ 'item_1', 'item_3' ], "an_object": { "value_1": 1, "value_2": 'two' } } + + config( 'pkg.standard_values.one' ).should.eql true + config( 'pkg.standard_values.two' ).should.eql false + config( 'pkg.standard_values.three' ).should.eql "three" + config( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } + config( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + + done() + catch err + done( err ) - config( 'pkg.standard_values.one' ).should.eql true - config( 'pkg.standard_values.two' ).should.eql false - config( 'pkg.standard_values.three' ).should.eql "three" - config( 'pkg.structures.a_hash' ).should.eql { "value_1": "value", "value_2": true, "value_3": 3 } - config( 'pkg.structures.an_array' ).should.eql ["test", 1, true, "john"] + it 'add a value to the loaded config, it\'s still there when asked for again', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + try + config( 'blah.four', 'four' ).should.eql 'four' + config( 'blah.four' ).should.eql 'four' - done() + done() + catch err + done( err ) - it 'add a value to the loaded config, it\'s still there when asked for again', ( done )-> + it 'override an array value in the loaded config, it\'s still there when asked for again', ( done )-> bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' .then -> - config( 'blah.four', 'four' ).should.eql 'four' - config( 'blah.four' ).should.eql 'four' + try + config( 'blah.an_array', 'item_2', false ).should.eql 'item_2' + config( 'blah.an_array' ).should.eql 'item_2' - done() + done() + catch err + done( err ) - it 'use merge to add a value to the loaded config, values have been persisted into the config', ( done )-> + it 'use merge to add a value to an array in the loaded config, values have been persisted into the config', ( done )-> bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' .then -> - config( 'blah.four', 'four', true ).should.eql 'four' - config( 'blah.four' ).should.eql 'four' - - done() + try + config( 'blah.an_array', 'item_2', true ).should.eql [ 'item_2', 'item_1', 'item_3' ] + config( 'blah.an_array' ).should.eql [ 'item_2', 'item_1', 'item_3' ] + + done() + catch err + done( err ) + it 'override an object value in the loaded config, it\'s still there when asked for again', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + try + config( 'blah.an_object', { 'value_3': 'three' }, false ).should.eql { value_3: 'three' } + config( 'blah.an_object' ).should.eql { value_3: 'three' } + + done() + catch err + done( err ) + it 'use merge to add a value to an object in the loaded config, values have been persisted into the config', ( done )-> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + try + config( 'blah.an_object', { 'value_3': 'three' }, true ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } + config( 'blah.an_object' ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } + + done() + catch err + done( err ) diff --git a/test/fixtures/config/user.json b/test/fixtures/config/user.json index 4b9237a..70c4b29 100644 --- a/test/fixtures/config/user.json +++ b/test/fixtures/config/user.json @@ -2,6 +2,11 @@ "blah": { "one": true, "two": false, - "three": "three" + "three": "three", + "an_array": [ "item_1", "item_3"], + "an_object": { + "value_1": 1, + "value_2": "two" + } } } \ No newline at end of file From 59e61931b94aa195bbc540e18dee485b209ac077 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Sat, 17 May 2014 15:38:27 +0200 Subject: [PATCH 10/12] Tidy comments --- lib/warlock/config.coffee | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/warlock/config.coffee b/lib/warlock/config.coffee index 82a2f10..4e7983d 100644 --- a/lib/warlock/config.coffee +++ b/lib/warlock/config.coffee @@ -44,8 +44,10 @@ _userConfig = {} # A multi-purpose function, allows merging config into the cached config, setting # a key to a given value, or retrieving a value given a key # -# key should always be provided. If val is provided, then key is set to val. -# if merge is true, then the content of merge is merged into the config +# key should always be provided. If val is provided, then key is set to val +# overwriting any previous value. If merge is true, then val is merged into the config, +# being added to any array or object already in the config at that key. +# # In all instances, the value associated with the requested key is returned. ### config = module.exports = ( key, val, merge ) -> @@ -68,7 +70,10 @@ config.init = ( conf ) -> _config = warlock.util.merge {}, _defaultConfig ### -# Process every property of an object recursively as a template. +# Process every property of an object recursively as a template, if the +# attribute is a string, and in the format <%= xxxxx %> then get the value +# of xxx from the config, and substitute it in to the resulting object. +# # Ripped from Grunt nearly directly. ### propStringTmplRe = /^<%=\s*([a-z0-9_$]+(?:\.[a-z0-9_$]+)*)\s*%>$/i @@ -117,7 +122,7 @@ config.getRaw = ( key ) -> if key? then MOUT.object.get( _getCombinedConfig(), key ) else _getCombinedConfig() ### -# The user's configuration, containing only those variables set of manipulated by the user. All of +# The user's configuration, containing only those variables set or manipulated by the user. All of # these are also in _config, but this is what should be synchronized to warlock.json. ### From 730687c2bc83d13049d9317cff816aa3a6377602 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Sat, 17 May 2014 15:38:38 +0200 Subject: [PATCH 11/12] Remaining tests --- test/config.spec.coffee | 190 ++++++++++++++++++++++++++++++---------- 1 file changed, 146 insertions(+), 44 deletions(-) diff --git a/test/config.spec.coffee b/test/config.spec.coffee index 63da156..1729ae0 100644 --- a/test/config.spec.coffee +++ b/test/config.spec.coffee @@ -10,11 +10,11 @@ require 'mocha' beforeEach -> options.init { 'debug': false } + config.init( {} ) describe 'warlock config: ', -> - describe 'config base export: ', -> - # these tests focus on the fact that config routines are called, detailed testing of config is in the config module - it 'get values from config that has been loaded', ( done )-> + describe 'config: ', -> + it 'get values from config that has been loaded', ( done ) -> bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' .then -> try @@ -35,57 +35,159 @@ describe 'warlock config: ', -> catch err done( err ) - it 'add a value to the loaded config, it\'s still there when asked for again', ( done )-> - bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' - .then -> - try - config( 'blah.four', 'four' ).should.eql 'four' - config( 'blah.four' ).should.eql 'four' + it 'add a value to the loaded config, it\'s still there when asked for again', -> + config( 'blah.four', 'four' ).should.eql 'four' + config( 'blah.four' ).should.eql 'four' - done() - catch err - done( err ) + it 'add an an array value then override it, it\'s still there when asked for again', -> + config( 'blah.a_new_array', [ 'item_1', 'item_3' ], false ).should.eql [ 'item_1', 'item_3' ] + config( 'blah.a_new_array', [ 'item_2' ], false ).should.eql [ 'item_2' ] + config( 'blah.a_new_array' ).should.eql [ 'item_2' ] - it 'override an array value in the loaded config, it\'s still there when asked for again', ( done )-> - bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' - .then -> - try - config( 'blah.an_array', 'item_2', false ).should.eql 'item_2' - config( 'blah.an_array' ).should.eql 'item_2' + it 'use merge to add a value to an array, values have been persisted into the config', -> + config( 'blah.a_new_array', [ 'item_1', 'item_3' ], false ).should.eql [ 'item_1', 'item_3' ] + config( 'blah.a_new_array', [ 'item_2' ], true ).should.eql [ 'item_1', 'item_3', 'item_2' ] + config( 'blah.a_new_array' ).should.eql [ 'item_1', 'item_3', 'item_2' ] - done() - catch err - done( err ) + it 'add an object then override it, it\'s still there when asked for again', -> + config( 'blah.a_new_object', { value_1: 1, value_2: 'two', }, false ).should.eql { value_1: 1, value_2: 'two', } + config( 'blah.a_new_object', { 'value_3': 'three' }, false ).should.eql { value_3: 'three' } + config( 'blah.a_new_object' ).should.eql { value_3: 'three' } - it 'use merge to add a value to an array in the loaded config, values have been persisted into the config', ( done )-> - bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' - .then -> - try - config( 'blah.an_array', 'item_2', true ).should.eql [ 'item_2', 'item_1', 'item_3' ] - config( 'blah.an_array' ).should.eql [ 'item_2', 'item_1', 'item_3' ] - - done() - catch err - done( err ) + it 'use merge to add a value to an object in the loaded config, values have been persisted into the config', -> + config( 'blah.a_new_object', { value_1: 1, value_2: 'two', }, false ).should.eql { value_1: 1, value_2: 'two', } + config( 'blah.a_new_object', { 'value_3': 'three' }, true ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } + config( 'blah.a_new_object' ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } - it 'override an object value in the loaded config, it\'s still there when asked for again', ( done )-> - bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' - .then -> - try - config( 'blah.an_object', { 'value_3': 'three' }, false ).should.eql { value_3: 'three' } - config( 'blah.an_object' ).should.eql { value_3: 'three' } - done() - catch err - done( err ) + describe 'config.init: ', -> + it 'init works', -> + # this test will need updating whenever the default structure is changed + conf = { value_1: 'one' } + config.init( conf ) + config.getRaw().should.eql { + globs: {} + paths: {} + plugins: [] + prevent: [] + inject: [] + default: [] + value_1: 'one' + } + + describe 'config.process: ', -> + it 'an object with no templates returns that same object', -> + obj = { + value_1: 'one' + value_2: 1, + value_3: false, + array: [ 'two', 2, true, { item: 'item_value' } ], + object: { attr1: 'attr', attr2: [ 1, 2, 3, 'string' ], attr3: false } + } + + config.process( obj ).should.eql( obj ) + + it 'an object with a template returns that same object with the template value replaced', -> + test_obj = { + value_1: '<%= blah.three %>' + value_2: 1, + value_3: false, + array: [ 'two', 2, true, { item: 'item_value' } ], + object: { attr1: 'attr', attr2: [ 1, 2, 3, 'string' ], attr3: false } + } - it 'use merge to add a value to an object in the loaded config, values have been persisted into the config', ( done )-> + result_obj = { + value_1: 'three' + value_2: 1, + value_3: false, + array: [ 'two', 2, true, { item: 'item_value' } ], + object: { attr1: 'attr', attr2: [ 1, 2, 3, 'string' ], attr3: false } + } + + config( 'blah.three', 'three' ) + config.process( test_obj ).should.eql( result_obj ) + + it 'mix of template values, including some that refer to arrays and objects', -> + test_obj = { + value_1: '<%= blah.three %>' + value_2: 1, + value_3: false, + array: [ 'two', 2, true, { item: '<%= blah.two %>' } ], + object: { attr1: 'attr', attr2: [ 1, 2, 3, '<%= blah.one %>' ], attr3: false } + } + + result_obj = { + value_1: 'three' + value_2: 1, + value_3: false, + array: [ 'two', 2, true, { item: { a: 'a', b: 'b', c: false } } ], + object: { attr1: 'attr', attr2: [ 1, 2, 3, [ 1, 2, 3 ] ], attr3: false } + } + + config( 'blah.one', [ 1, 2, 3 ] ) + config( 'blah.two', { a: 'a', b: 'b', c: false } ) + config( 'blah.three', 'three' ) + + config.process( test_obj ).should.eql( result_obj ) + + # PML: merge, set, get and getRaw are implicitly tested in the earlier tests, and + # have no complex logic. In the interests of maintainability, not explicitly tested + # here. + + describe 'config: ', -> + it 'get values from config that has been loaded', ( done ) -> bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' .then -> try - config( 'blah.an_object', { 'value_3': 'three' }, true ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } - config( 'blah.an_object' ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } - + config.user( 'blah.one' ).should.eql true + config.user( 'blah.two' ).should.eql false + config.user( 'blah.three' ).should.eql 'three' + config.user( 'blah.an_array' ).should.eql [ 'item_1', 'item_3' ] + config.user( 'blah.an_object' ).should.eql { "value_1": 1, "value_2": 'two' } + config.user( 'blah' ).should.eql { "one": true, "two": false, "three": "three", "an_array": [ 'item_1', 'item_3' ], "an_object": { "value_1": 1, "value_2": 'two' } } + + # without merge - all + config.user( 'blah.an_array', 'item_2' ).should.eql 'item_2' + config.user( 'blah.an_array' ).should.eql 'item_2' + + # with merge + config.user( 'blah.an_object', { "value_4": 4 }, true ).should.eql { "value_1": 1, "value_2": 'two', "value_4": 4 } + config.user( 'blah.an_object' ).should.eql { "value_1": 1, "value_2": 'two', "value_4": 4 } + done() catch err done( err ) + + it 'add a value to the loaded config, it\'s still there when asked for again', -> + config.user( 'blah.four', 'four' ).should.eql 'four' + config.user( 'blah.four' ).should.eql 'four' + + it 'add an an array value then override it, it\'s still there when asked for again', -> + config.user( 'blah.a_new_array', [ 'item_1', 'item_3' ], false ).should.eql [ 'item_1', 'item_3' ] + config.user( 'blah.a_new_array', [ 'item_2' ], false ).should.eql [ 'item_2' ] + config.user( 'blah.a_new_array' ).should.eql [ 'item_2' ] + + it 'use merge to add a value to an array, values have been persisted into the config', -> + config.user( 'blah.a_new_array', [ 'item_1', 'item_3' ], false ).should.eql [ 'item_1', 'item_3' ] + config.user( 'blah.a_new_array', [ 'item_2' ], true ).should.eql [ 'item_1', 'item_3', 'item_2' ] + config.user( 'blah.a_new_array' ).should.eql [ 'item_1', 'item_3', 'item_2' ] + + it 'add an object then override it, it\'s still there when asked for again', -> + config.user( 'blah.a_new_object', { value_1: 1, value_2: 'two', }, false ).should.eql { value_1: 1, value_2: 'two', } + config.user( 'blah.a_new_object', { 'value_3': 'three' }, false ).should.eql { value_3: 'three' } + config.user( 'blah.a_new_object' ).should.eql { value_3: 'three' } + + it 'use merge to add a value to an object in the loaded config, values have been persisted into the config', -> + config.user( 'blah.a_new_object', { value_1: 1, value_2: 'two', }, false ).should.eql { value_1: 1, value_2: 'two', } + config.user( 'blah.a_new_object', { 'value_3': 'three' }, true ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } + config.user( 'blah.a_new_object' ).should.eql { value_1: 1, value_2: 'two', value_3: 'three' } + + # PML: merge, set, get and getRaw are implicitly tested in the earlier tests, and + # have no complex logic. In the interests of maintainability, not explicitly tested + # here. + + + describe 'config.write: ', -> + # TODO(pml): didn't feel like dealing with file handling today + + From 86150bdbb1f18f0472ef8798c79585412768b4c5 Mon Sep 17 00:00:00 2001 From: Paul Lambert Date: Sat, 17 May 2014 16:50:44 +0200 Subject: [PATCH 12/12] Include some documentation on how to write test cases --- docs/test/unit_test.md | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 docs/test/unit_test.md diff --git a/docs/test/unit_test.md b/docs/test/unit_test.md new file mode 100644 index 0000000..4606e96 --- /dev/null +++ b/docs/test/unit_test.md @@ -0,0 +1,80 @@ +# Unit tests for warlock + +Unit tests will be provided over time for all major warlock functions, with continuous integration +as new commits are added. Unit tests use mocha and should.js, with all test scripts written in +coffeescript. + + +## Running unit tests + +To run the unit tests from a newly cloned repository + +```sh +npm install -g mocha +npm install +mocha +``` + + +## Writing new unit tests +All newly contributed functions of warlock should have unit tests created. The warlock team recognise +that most open source developers would prefer to write code than to write tests, so as a general rule +the aim is to provide maximum coverage with the minimum number of test cases, and to provide +test cases that don't require ongoing maintenance when minor changes to the API or logic occur. This +will allow future contributors to minimise the amount of testing code that needs modification for any +given extension to the logic. + +Test cases generally fall into two categories: synchronous cases and asynchronous cases. + +Synchronous cases are easier to write. The general pattern is: + +```coffeescript +'use strict' +should = require 'should' +config = require '../lib/warlock/config' + +describe 'warlock config: ', -> + describe 'config: ', -> + it 'add a value to the loaded config, it\'s still there when asked for again', -> + config( 'blah.four', 'four' ).should.eql 'four' + config( 'blah.four' ).should.eql 'four' +``` + +By preference we use eql rather than equal, as this verifies that the content of an object +matches rather than that the instance matches, and is generally good practice. + +Describe lines always end with a :, and the outermost describe should provide some indication +of the coffeescript file that is being tested - this is to allow a future developer to quickly +ascertain where they can find a given test that is failing. + +It lines should describe what it is that you're testing and what the expected result is, again +so that a future developer can quickly understand what a failing test was intending to prove. + +Asynchronous cases leverage mocha's ability to accept a "done()" callback once asynchronous processing +is complete. This requires both ( done ) on the 'it' line and an explicit invocation of done() at +the end of the test case. + +Unfortunately at time of writing there was an outstanding issue with mocha and should where failed +expectations in an asynchronous test case result in a timeout rather than an error message - refer: + https://github.com/visionmedia/mocha/pull/278 + https://github.com/visionmedia/mocha/pull/985 + +This can be worked around by enclosing the expectations in a try-catch block, with the catch propogating +the exception to the done call. + +The basic pattern is therefore: + +```coffeescript +describe 'warlock config: ', -> + describe 'config: ', -> + it 'get values from config that has been loaded', ( done ) -> + bootstrap._readConfig 'test/fixtures/config/user.json', 'test/fixtures/config/package.json' + .then -> + try + config( 'blah.one' ).should.eql true + + done() + catch err + done( err ) +``` +