macgyver
Version:
[](http://travis-ci.org/dominictarr/macgyver) [](http://ci.testling.com/dominictarr/macgyver) declarative assertion framework for invocation ordering.
119 lines (106 loc) • 3.15 kB
JavaScript
var inherits = require('util').inherits
var rules = require('./rules')
function createId () {
return (
Math.random().toString(16).substring(2)
+ Math.random().toString(16).substring(2)
)
}
function find (array, id) {
if(!id) return
for (var k in array) {
if(array[k].id == id) return array[k]
}
}
//create a macgyver context
var exports = module.exports = function () {
var contracts = {}
var macgyver = function wrap(funx, name) {
//if this is already wrapped don't wrap it.
if('string' === typeof funx)
name = funx
if('function' !== typeof funx)
funx = function noOp() {} //default to empty function
if(funx.id) return funx
var id = createId()
var contract = {
called: 0, //counter of calls
returned: 0, //counter of returns
throws: 0,
function: funx,
name: name,
get: function (id) {
return contracts[typeof id == 'string' ? id : id.id]
}
}
contract.rules = wrapped.rules = []
contract.wrapped = wrapped
contracts[id] = contract
function wrapped () {
contract.called ++
var args = [].slice.call(arguments)
contract.rules.forEach(function (rule) {
if(rule.before) rule.before.call(contract, args)
})
//actually call the function...
var i = contract.rules.length - 1, threw, returned
function next () {
var args = [].slice.call(arguments)
var around
while(~i && !(around = contract.rules[i--].around))
;
return ( around
? around.call(contract, next, this, args)
: (function () {
try { return returned = funx.apply(this, args) }
catch (err) { threw = true; throw err }
}).call(this)
)
}
next.apply(this, args)
//increment count of returns
//(this is useful for asserting when something may happen before a call ends)
if(threw) { contract.throws ++; return }
contract.returned ++
//after
contract.rules.forEach(function (rule) {
if(rule.after) rule.after.call(contract, returned)
})
return returned
//also, have after, have around...
}
wrapped.id = id
//install rules.
for (var k in rules) {
;(function (k){
wrapped[k] = function () {
var args = [].slice.call(arguments)
//some rules don't make sense to duplicate.
var rule = find(contract.rules, k)
if(rule) return rule.update.apply(contract, args)
rule = rules[k].apply(contract, args)
if(rule) contract.rules.push(rule)
return this
}
})(k)
}
return wrapped
}
macgyver.validate = function () {
for (var k in contracts) {
var con = contracts[k]
con.rules.forEach(function (rule) {
if(rule.validate) rule.validate.call(con)
})
}
}
macgyver.autoValidate = function (timeout) {
if(process.on)
process.on('exit', macgyver.validate)
else
setTimeout(macgyver.validate, timeout || 10e3)
return macgyver
}
return macgyver
}
;