Created
February 18, 2012 14:20
-
-
Save VoQn/1859524 to your computer and use it in GitHub Desktop.
prototyping quickcheck for js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var TestEnvironment = (function(){ | |
return { | |
seed: 1, | |
count: 100, | |
current: { | |
args: [], | |
isPassed: false, | |
isSkipped: false, | |
set: function( args, prop ){ | |
var result = prop.apply( this, args ); | |
this.args = args; | |
if ( !!result.wasSkip ) { | |
this.isSkipped = result.wasSkip; | |
} else { | |
this.isSkipped = false; | |
this.isPassed = result; | |
} | |
}, | |
evaluate: function( verbose, score ){ | |
var mark = '', | |
shouldView = !( this.isSkipped || this.isPassed ); | |
if ( this.isSkipped ) { | |
mark = 'Skipped:'; | |
score.skipped++; | |
} | |
else if ( this.isPassed ) { | |
mark = 'Passed:'; | |
score.passed++; | |
} else { | |
mark = 'Faild:'; | |
score.failure++; | |
} | |
if ( verbose || shouldView ) { | |
console.log( mark ); | |
for( var i = 0, l = this.args.length; i < l; i++ ){ | |
console.log( this.args[i] ); | |
} | |
} | |
} | |
}, | |
result: { | |
passed: 0, | |
failure: 0, | |
skipped: 0, | |
get: function() { | |
var _ = this, total = _.passed + _.skipped + _.failure, | |
msg = '', mark = '+++', isPassed = true; | |
if ( total == _.passed ) { | |
msg = 'OK, passed ' + _.passed + ' tests.'; | |
} else if ( total == _.passed + _.skipped ) { | |
msg = 'OK, passed ' + _.passed + ' tests (skipped ' + _.skipped + ' tests)'; | |
} else { | |
mark = '***' | |
msg = 'Faild! ' + _.fail + ' test case.' + | |
'(passed ' + _.passed + | |
(_.skipped ? ' )' : ', skipped ' + _.skipped + ' )'); | |
} | |
return { | |
score: _, | |
mark: mark, | |
msg: msg, | |
isPassed: isPassed | |
} | |
} | |
}, | |
clean: function(){ | |
this.seed = 0; | |
this.current.args = []; | |
this.result.passed = 0; | |
this.result.failure = 0; | |
this.result.skipped = 0; | |
}, | |
getRange: function(){ | |
return Math.pow( 2, Math.round( this.seed / 2 ) ); | |
}, | |
check: function( prop, verbose ) { | |
var i = 0, result, msg; | |
while ( i++ < this.count ) { | |
prop(); | |
this.current.evaluate( verbose, this.result ); | |
this.seed++; | |
} | |
result = this.result.get(); | |
this.clean(); | |
return result; | |
} | |
}; | |
})(); | |
var NotImplementException = function( _interface, _identifier ){ | |
var error = new Error(); | |
error.name = 'NotImplementException'; | |
error.message = _identifier + ' is not instance of ' + _interface; | |
return error; | |
}; | |
var JsCheck = (function(){ | |
var E = TestEnvironment; | |
function createArgs( generator, prop ){ | |
var as = []; | |
for ( var i = 0, l = prop.length; i < l; i++ ) { | |
as[i] = generator.arbitrary(); | |
} | |
return as; | |
}; | |
function check( prop, supplier, verbose ){ | |
if ( !supplier ) { // call forAll | |
return E.check( prop, verbose ); | |
} else { // prop is simple function, supplier is apply generated args for prop | |
return E.check( supplier( prop ), verbose ); | |
} | |
} | |
return { | |
forAll: function( generator, prop ){ | |
if ( !generator.arbitrary ){ | |
throw new NotImplementException('Arbitrary', 'generator'); | |
} | |
return function(){ | |
var args = createArgs( generator, prop ); | |
E.current.set( args, prop ); | |
}; | |
}, | |
quickCheck: function( test, supplier ) { | |
return check( test, supplier ); | |
}, | |
verboseCheck: function( test, supplier ) { | |
return check( test, supplier, true ); | |
} | |
}; | |
})(); | |
var Arbitrary = (function( env ){ | |
return { | |
Integer: fromGen( genInt ), | |
Double: fromGen( genNumber ), | |
create: fromGen | |
}; | |
function genNumber(){ | |
var sign = Math.random() < 0.5 ? (-1) : 1, | |
seed = Math.random(), | |
width = env.getRange(); | |
return sign * seed * width; | |
} | |
function genInt(){ | |
return Math.round( genNumber() ); | |
} | |
function fromGen( generator ) { | |
return { | |
arbitrary: generator | |
}; | |
} | |
})( TestEnvironment ); | |
Arbitrary.Bool = elements_of([false, true]); | |
function elements_of( list ){ | |
var len = list.length; | |
return Arbitrary.create( function(){ | |
return list[ genIndex() ]; | |
}); | |
function genIndex() { | |
var i = Math.floor( Math.random() * len ); | |
return Math.min( i, len - 1 ); | |
} | |
} | |
function one_of( generators ) { | |
var len = generators.length; | |
return Arbitrary.create( function(){ | |
return generators[ genIndex() ].arbitrary(); | |
}); | |
function genIndex() { | |
var i = Math.floor( Math.random() * len ); | |
return Math.min( i, len - 1 ); | |
} | |
} | |
function frequency( weighted ) { | |
return one_of( expand( weighted )); | |
function expand( wg ){ | |
var gs = []; | |
for (var i = 0, l = wg.length; i < l; i++){ | |
var j = 0; | |
while( j < wg[i][0] ){ | |
gs.push(wg[i][1]); | |
j++; | |
} | |
} | |
return gs; | |
} | |
} | |
function where( before, proc ) { | |
if ( shouldSkip( before ) ) { | |
return { wasSkip: true } | |
} | |
return proc(); | |
function shouldSkip( cond ) { | |
if ( cond instanceof Array ) { | |
for (var i = 0, l = cond.length; i < l; i ++){ | |
if ( !cond[i] ) { | |
return true; | |
} | |
} | |
} else { | |
return !cond; | |
} | |
return false; | |
} | |
} | |
function types(/* Arbitrary types */){ | |
var arbs = arguments, args = [], E = TestEnvironment; | |
return function( prop ){ | |
return function() { | |
for (var i = 0, l = arbs.length; i < l; i++) { | |
if ( !arbs[i].arbitrary ) { | |
throw new NotImplementException('Arbitrary', 'Type hinting'); | |
} | |
args[i] = arbs[i].arbitrary(); | |
} | |
E.current.set( args, prop ); | |
} | |
} | |
} | |
function testGroup( label, tests ) { | |
return function(){ | |
var disc = [label + ':']; | |
for( var i = 0, l = tests.length; i < l; i++ ) { | |
disc[i+1] = ' ' + tests[i](); | |
} | |
return disc; | |
} | |
} | |
function testProperty( label, testable, type ){ | |
return function(){ | |
var result = JsCheck.quickCheck( testable, type ); | |
var disc = label + ': [' + result.msg + ']'; | |
return disc; | |
} | |
} | |
function runTest( tests ) { | |
var item = tests(); | |
for( var i = 0, l = item.length; i < l; i ++){ | |
console.log( item[i] ); | |
} | |
} | |
function listOf1( generator ){ | |
var E = TestEnvironment; | |
return Arbitrary.create(function(){ | |
var vs = []; | |
var i = 0; | |
var l = Math.ceil( Math.random() * E.seed * 2 ) + 1; | |
while(i < l){ | |
vs[i] = generator.arbitrary(); | |
i++; | |
} | |
return vs; | |
}); | |
} | |
function fmap( f, g ){ | |
if ( !g.arbitrary ) { | |
throw new NotImplementException('Arbitrary', 'generator'); | |
} | |
return Arbitrary.create( function(){ | |
var generated = g.arbitrary(); | |
return f( generated ); | |
}); | |
} | |
var genPathInfo = (function(){ | |
var alphabet = elements_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); | |
var ascii = elements_of("/#.:-_?="); | |
function concat( cs ){ | |
var str = ''; | |
for( var i = 0, l = cs.length; i < l; i++){ | |
str += cs[i]; | |
} | |
return str; | |
} | |
return fmap( concat, listOf1(frequency( | |
[[7, alphabet], [1, ascii]] | |
))); | |
})(); | |
/** | |
* Test | |
*/ | |
(function() { | |
var C = JsCheck, A = Arbitrary, list, tests; | |
list = "abcdefghijklmnopqrstuvwxyz"; | |
runTest( | |
testGroup('example quickcheck tests', | |
[ testProperty( 'if x != y, x - y != y - x', | |
function( x, y ) { | |
return where( x != y, function(){ | |
return x - y != y - x; | |
}); | |
}, types(A.Integer, A.Integer)) | |
, testProperty( 'any char one of [a-z] is include [a-z]', | |
C.forAll( elements_of( list ), function( x ) { | |
return -1 < list.indexOf( x ); | |
})) | |
]) | |
); | |
// Example: valid PATH INFO String Generator | |
// (Include XSS, SQL Injection Security risk) | |
// C.verboseCheck( C.forAll( genPathInfo, function( p ){ return true; })); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment