Skip to content

Instantly share code, notes, and snippets.

@VoQn
Created February 18, 2012 14:20

Revisions

  1. VoQn revised this gist Feb 18, 2012. 1 changed file with 68 additions and 2 deletions.
    70 changes: 68 additions & 2 deletions quickcheck.js
    Original file line number Diff line number Diff line change
    @@ -153,7 +153,9 @@ var Arbitrary = (function( env ){
    }

    function fromGen( generator ) {
    return { arbitrary: generator };
    return {
    arbitrary: generator
    };
    }

    })( TestEnvironment );
    @@ -173,7 +175,29 @@ function elements_of( list ){
    }

    function one_of( generators ) {
    return elements_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 ) {
    @@ -236,6 +260,45 @@ function runTest( tests ) {
    }
    }

    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
    */
    @@ -257,4 +320,7 @@ function runTest( tests ) {
    }))
    ])
    );
    // Example: valid PATH INFO String Generator
    // (Include XSS, SQL Injection Security risk)
    // C.verboseCheck( C.forAll( genPathInfo, function( p ){ return true; }));
    })();
  2. VoQn revised this gist Feb 18, 2012. 1 changed file with 52 additions and 21 deletions.
    73 changes: 52 additions & 21 deletions quickcheck.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@

    var TestEnvironment = (function(){
    return {
    seed: 1,
    @@ -75,16 +76,15 @@ var TestEnvironment = (function(){
    return Math.pow( 2, Math.round( this.seed / 2 ) );
    },
    check: function( prop, verbose ) {
    var i = 0, result;// _ = this;
    var i = 0, result, msg;
    while ( i++ < this.count ) {
    prop();
    this.current.evaluate( verbose, this.result );
    this.seed++;
    }
    result = this.result.get();
    console.log( result.mark + ' ' + result.msg );

    this.clean();
    return result;
    }
    };

    @@ -109,9 +109,9 @@ var JsCheck = (function(){

    function check( prop, supplier, verbose ){
    if ( !supplier ) { // call forAll
    E.check( prop, verbose );
    return E.check( prop, verbose );
    } else { // prop is simple function, supplier is apply generated args for prop
    E.check( supplier( prop ), verbose );
    return E.check( supplier( prop ), verbose );
    }
    }

    @@ -125,8 +125,12 @@ var JsCheck = (function(){
    E.current.set( args, prop );
    };
    },
    quickCheck: function( test, supplier ) { check( test, supplier ); },
    verboseCheck: function( test, supplier ) { check( test, supplier, true ); }
    quickCheck: function( test, supplier ) {
    return check( test, supplier );
    },
    verboseCheck: function( test, supplier ) {
    return check( test, supplier, true );
    }
    };
    })();

    @@ -207,23 +211,50 @@ function types(/* Arbitrary types */){
    }
    }

    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] );
    }
    }

    /**
    * Test
    */
    (function() {
    var C = JsCheck, A = Arbitrary;

    C.quickCheck(
    function( x, y ) {
    return where( x != y, function(){
    return x - y != y - x;
    });
    }, types(A.Integer, A.Integer));

    var list = "abcdefghijklmnopqrstuvwxyz";
    C.quickCheck(
    C.forAll( elements_of( list ), function( x ) {
    return -1 < list.indexOf( x );
    }));
    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 );
    }))
    ])
    );
    })();
  3. VoQn revised this gist Feb 18, 2012. 1 changed file with 0 additions and 9 deletions.
    9 changes: 0 additions & 9 deletions quickcheck.js
    Original file line number Diff line number Diff line change
    @@ -1,13 +1,4 @@

    var TestEnvironment = (function(){
    function createArgs( generator, prop ){
    var as = [];
    for ( var i = 0, l = prop.length; i < l; i++ ) {
    as[i] = generator.arbitrary();
    }
    return as;
    };

    return {
    seed: 1,
    count: 100,
  4. VoQn revised this gist Feb 18, 2012. 1 changed file with 46 additions and 14 deletions.
    60 changes: 46 additions & 14 deletions quickcheck.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,4 @@
    /*
    * quickcheck.js Prototyping
    */

    var TestEnvironment = (function(){
    function createArgs( generator, prop ){
    var as = [];
    @@ -17,8 +15,7 @@ var TestEnvironment = (function(){
    args: [],
    isPassed: false,
    isSkipped: false,
    set: function( generator, prop ){
    var args = createArgs( generator, prop );
    set: function( args, prop ){
    var result = prop.apply( this, args );
    this.args = args;
    if ( !!result.wasSkip ) {
    @@ -111,17 +108,34 @@ var NotImplementException = function( _interface, _identifier ){

    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
    E.check( prop, verbose );
    } else { // prop is simple function, supplier is apply generated args for prop
    E.check( supplier( prop ), verbose );
    }
    }

    return {
    forAll: function( generator, prop ){
    if ( !generator.arbitrary ){
    throw new NotImplementException('Arbitrary', 'generator');
    }
    return function(){
    E.current.set( generator, prop );
    var args = createArgs( generator, prop );
    E.current.set( args, prop );
    };
    },
    quickCheck: function( test ) { E.check( test ); },
    verboseCheck: function( test ) { E.check( test, true ); }
    quickCheck: function( test, supplier ) { check( test, supplier ); },
    verboseCheck: function( test, supplier ) { check( test, supplier, true ); }
    };
    })();

    @@ -187,20 +201,38 @@ function where( before, proc ) {
    }
    }

    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 );
    }
    }
    }

    /**
    * Test
    */
    (function() {
    var jc = JsCheck;
    jc.quickCheck(
    jc.forAll( Arbitrary.Integer, function( x, y ) {
    var C = JsCheck, A = Arbitrary;

    C.quickCheck(
    function( x, y ) {
    return where( x != y, function(){
    return x - y != y - x;
    });
    }));
    }, types(A.Integer, A.Integer));

    var list = "abcdefghijklmnopqrstuvwxyz";
    jc.quickCheck(
    jc.forAll( elements_of( list ), function( x ) {
    C.quickCheck(
    C.forAll( elements_of( list ), function( x ) {
    return -1 < list.indexOf( x );
    }));

    })();
  5. VoQn created this gist Feb 18, 2012.
    206 changes: 206 additions & 0 deletions quickcheck.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,206 @@
    /*
    * quickcheck.js Prototyping
    */
    var TestEnvironment = (function(){
    function createArgs( generator, prop ){
    var as = [];
    for ( var i = 0, l = prop.length; i < l; i++ ) {
    as[i] = generator.arbitrary();
    }
    return as;
    };

    return {
    seed: 1,
    count: 100,
    current: {
    args: [],
    isPassed: false,
    isSkipped: false,
    set: function( generator, prop ){
    var args = createArgs( generator, 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;// _ = this;
    while ( i++ < this.count ) {
    prop();
    this.current.evaluate( verbose, this.result );
    this.seed++;
    }
    result = this.result.get();
    console.log( result.mark + ' ' + result.msg );

    this.clean();
    }
    };

    })();

    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;
    return {
    forAll: function( generator, prop ){
    if ( !generator.arbitrary ){
    throw new NotImplementException('Arbitrary', 'generator');
    }
    return function(){
    E.current.set( generator, prop );
    };
    },
    quickCheck: function( test ) { E.check( test ); },
    verboseCheck: function( test ) { E.check( test, 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 ) {
    return elements_of( generators );
    }

    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;
    }
    }

    /**
    * Test
    */
    (function() {
    var jc = JsCheck;
    jc.quickCheck(
    jc.forAll( Arbitrary.Integer, function( x, y ) {
    return where( x != y, function(){
    return x - y != y - x;
    });
    }));
    var list = "abcdefghijklmnopqrstuvwxyz";
    jc.quickCheck(
    jc.forAll( elements_of( list ), function( x ) {
    return -1 < list.indexOf( x );
    }));
    })();