Skip to content

Instantly share code, notes, and snippets.

@VoQn
Created February 18, 2012 14:20
Show Gist options
  • Save VoQn/1859524 to your computer and use it in GitHub Desktop.
Save VoQn/1859524 to your computer and use it in GitHub Desktop.
prototyping quickcheck for js
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