├── .gitignore ├── Gruntfile.js ├── README.md ├── benchmarks ├── largetable │ ├── angular.js │ ├── app.js │ ├── bp.conf.js │ ├── jquery-noop.js │ ├── main.html │ ├── scripts.json │ └── underscore-min.js └── render-table │ ├── bp.conf.js │ ├── main.html │ └── table-render.js ├── bin └── benchpress ├── dashboard ├── HomeController.js ├── HomeController.spec.js ├── app.js ├── benchmark │ ├── BenchmarkController.js │ ├── BenchmarkController.spec.js │ └── benchmark.html ├── bower.json ├── components │ ├── benchmarks-service │ │ ├── benchmarks-service.js │ │ └── benchmarks-service.spec.js │ ├── iframe-runner-directive │ │ ├── iframe-runner-controller.js │ │ ├── iframe-runner-directive.html │ │ ├── iframe-runner-directive.js │ │ ├── iframe-runner-directive.spec.js │ │ └── iframe-runner.js │ ├── iframe-streamer-directive │ │ ├── iframe-streamer-directive.js │ │ └── iframe-streamer-directive.spec.js │ ├── run-contexts-service │ │ └── run-contexts-service.js │ ├── run-state-service │ │ ├── run-state-service.js │ │ └── run-state-service.spec.js │ ├── scripts-service │ │ ├── scripts-service.js │ │ └── scripts-service.spec.js │ ├── stats-service │ │ ├── stats-service.js │ │ └── stats-service.spec.js │ └── table-report-directive │ │ ├── table-report-directive.html │ │ └── table-report-directive.js ├── home.html ├── index.html ├── karma.conf.js └── test │ └── mock-api.js ├── dist.js ├── dist ├── auto-bp.js └── bp.js ├── grunt-benchmarks └── sample-folder │ ├── bp.conf.js │ └── main.html ├── karma.conf.js ├── lib ├── Aggregator.js ├── AutoRunner.js ├── ClientScripts.js ├── Document.js ├── Globals.js ├── HtmlReport.js ├── Logger.js ├── Measure.js ├── RunState.js ├── Runner.js ├── Statistics.js ├── Steps.js ├── Utils.js ├── Variables.js ├── auto-bp.js ├── auto-template.html ├── bp.js ├── cli.js ├── node │ ├── api.js │ ├── expressMock.js │ ├── httpMock.js │ ├── server.js │ └── server.spec.js └── template.html ├── package.json ├── tasks ├── bp_build.js ├── bp_launch_chrome.js └── bp_run.js ├── test ├── Aggregator.spec.js ├── AutoRunner.spec.js ├── MockGlobals.js ├── RunState.spec.js └── bp.spec.js └── test_node.sh /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | benchpress-build/ 4 | build/ 5 | dashboard/bower_components/ 6 | 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var cli = require('./lib/cli'); 2 | 3 | /** 4 | * This file is only for testing and demonstration of the included grunt tasks. 5 | */ 6 | 7 | module.exports = function (grunt) { 8 | grunt.initConfig({ 9 | bp_build: { 10 | options: { 11 | buildPath: 'build/benchmarks', 12 | benchmarksPath: 'grunt-benchmarks' 13 | } 14 | } 15 | }); 16 | 17 | grunt.loadTasks('./tasks'); 18 | }; 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project has been moved to [angular/angular](https://github.com/angular/angular) in 2 | [`packages/benchpress`](https://github.com/angular/angular/tree/master/packages/benchpress). 3 | -------------------------------------------------------------------------------- /benchmarks/largetable/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('largetableBenchmark', []); 2 | 3 | app.config(function($compileProvider) { 4 | if ($compileProvider.debugInfoEnabled) { 5 | $compileProvider.debugInfoEnabled(false); 6 | } 7 | }); 8 | 9 | app.filter('noop', function() { 10 | return function(input) { 11 | return input; 12 | }; 13 | }); 14 | 15 | app.controller('DataController', function($scope, $rootScope, $window) { 16 | var totalRows = 1000; 17 | var totalColumns = 20; 18 | var ctrl = this; 19 | var data = $scope.data = []; 20 | $scope.digestDuration = '?'; 21 | $scope.numberOfBindings = totalRows*totalColumns*2 + totalRows + 1; 22 | $scope.numberOfWatches = '?'; 23 | 24 | function iGetter() { return this.i; } 25 | function jGetter() { return this.j; } 26 | 27 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 16 |
17 |
18 |
19 |

20 | Large table rendered with AngularJS 21 |

22 | 23 |
{{state.label}}:
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |

baseline binding

33 |
34 | 35 | :| 36 | 37 |
38 |
39 |
40 |

baseline binding once

41 |
42 | 43 | :| 44 | 45 |
46 |
47 |
48 |

baseline interpolation

49 |
50 | {{column.i}}:{{column.j}}| 51 |
52 |
53 |
54 |

bindings with functions

55 |
56 | :| 57 |
58 |
59 |
60 |

interpolation with functions

61 |
62 | {{column.iFn()}}:{{column.jFn()}}| 63 |
64 |
65 |
66 |

bindings with filter

67 |
68 | :| 69 |
70 |
71 |
72 |

interpolation with filter

73 |
74 | {{column.i | noop}}:{{column.j | noop}}| 75 |
76 |
77 |
78 |
79 |
80 |
81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /benchmarks/largetable/scripts.json: -------------------------------------------------------------------------------- 1 | {"scripts":[{"id":"jquery","src":"jquery-noop.js"},{"id":"angular","src":"angular.js"},{"src":"app.js"}]} -------------------------------------------------------------------------------- /benchmarks/largetable/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.6.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /benchmarks/render-table/bp.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | scripts: [ 4 | { 5 | id: 'tableRender', 6 | src: 'table-render.js' 7 | } 8 | ] 9 | }) 10 | }; 11 | -------------------------------------------------------------------------------- /benchmarks/render-table/main.html: -------------------------------------------------------------------------------- 1 |

Variables

2 | (selected: Baseline) 3 | 4 | 5 | 6 |

Table:

7 |
8 | 9 | -------------------------------------------------------------------------------- /benchmarks/render-table/table-render.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var element = document.querySelector('#tableContainer'); 3 | bp.variables.addMany([{ 4 | value: 'baseline' 5 | },{ 6 | value: 'colorful' 7 | }]); 8 | 9 | window.benchmarkSteps.push({ 10 | name: 'cleanup', 11 | fn: function() { 12 | element.innerHTML = ''; 13 | } 14 | }); 15 | 16 | window.benchmarkSteps.push({ 17 | name: 'table renderer', 18 | description: 'Create a table with 1000 rows and 20 columns', 19 | fn: function() { 20 | var table, tr, td; 21 | table = document.createElement('table'); 22 | element.appendChild(table); 23 | for (var i=0; i<1000; i++) { 24 | tr = document.createElement('tr'); 25 | table.appendChild(tr); 26 | for (var j=0; j<20; j++) { 27 | td = document.createElement('td'); 28 | td.innerText = '' + i + j; 29 | if (bp.variables.selected && bp.variables.selected.value === 'colorful') { 30 | td.style.backgroundColor = 31 | 'rgb(' + 32 | Math.round(Math.random() * 255) + ', ' + 33 | Math.round(Math.random() * 255) + ', ' + 34 | Math.round(Math.random() * 255) + ')'; 35 | } 36 | tr.appendChild(td); 37 | } 38 | } 39 | } 40 | }); 41 | })(); 42 | -------------------------------------------------------------------------------- /bin/benchpress: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli').exec(); 4 | -------------------------------------------------------------------------------- /dashboard/HomeController.js: -------------------------------------------------------------------------------- 1 | angular.module('benchpressDashboard'). 2 | controller('HomeController', ['$scope', 'benchmarksService', function($scope, benchmarksService){ 3 | benchmarksService.get().then(function(data){ 4 | $scope.benchmarks = data.benchmarks; 5 | }, function(res) { 6 | $scope.responseError = { 7 | code: res.status, 8 | body: res.data 9 | } 10 | }); 11 | }]); -------------------------------------------------------------------------------- /dashboard/HomeController.spec.js: -------------------------------------------------------------------------------- 1 | describe('HomeController', function() { 2 | var benchmarksService; 3 | var $controller; 4 | var mockAPI 5 | 6 | beforeEach(function(){ 7 | module('benchpressDashboard', 'bpdBenchmarksService', 'bpdMockAPI'); 8 | inject(function(_benchmarksService_, _mockAPI_, _$controller_) { 9 | benchmarksService = _benchmarksService_; 10 | spyOn(benchmarksService, 'get').andCallThrough(); 11 | $controller = _$controller_; 12 | mockAPI = _mockAPI_; 13 | }); 14 | }); 15 | 16 | function controllerFactory(scope) { 17 | return $controller('HomeController', { 18 | $scope: scope 19 | }); 20 | } 21 | 22 | it('should load the available benchmarks from the server', inject(function($httpBackend, $rootScope) { 23 | var scope = $rootScope.$new(); 24 | $httpBackend.whenGET('/api/benchmarks').respond(mockAPI['/api/benchmarks']); 25 | var controller = controllerFactory(scope); 26 | scope.$digest(); 27 | expect(benchmarksService.get).toHaveBeenCalled(); 28 | $httpBackend.flush(); 29 | expect(scope.benchmarks).toEqual(mockAPI['/api/benchmarks'].benchmarks); 30 | })); 31 | 32 | 33 | it('should add an error object to the scope if the request fails', inject(function($httpBackend, $rootScope) { 34 | var scope = $rootScope.$new(); 35 | $httpBackend.whenGET('/api/benchmarks').respond(400, 'Request failed'); 36 | var controller = controllerFactory(scope); 37 | scope.$digest(); 38 | $httpBackend.flush(); 39 | expect(scope.responseError.code).toBe(400); 40 | expect(scope.responseError.body).toBe('Request failed'); 41 | })); 42 | }); 43 | -------------------------------------------------------------------------------- /dashboard/app.js: -------------------------------------------------------------------------------- 1 | angular.module('benchpressDashboard', [ 2 | 'bpdBenchmarksService', 3 | 'bpdIframeRunnerDirective', 4 | 'bpdIframeStreamerDirective', 5 | 'bpdRunContextsService', 6 | 'bpdRunStateService', 7 | 'bpdStatsService', 8 | 'bpdTableReportDirective', 9 | 'bpdScriptsService', 10 | 'ngRoute' 11 | ]). 12 | config(['$routeProvider', function($routeProvider) { 13 | $routeProvider. 14 | when('/', { 15 | templateUrl: 'home.html', 16 | controller: 'HomeController' 17 | }). 18 | when('/benchmark/:name', { 19 | templateUrl: 'benchmark/benchmark.html', 20 | controller: 'BenchmarkController', 21 | controllerAs: 'ctrl' 22 | }). 23 | otherwise({redirectTo: '/'}); 24 | }]); 25 | -------------------------------------------------------------------------------- /dashboard/benchmark/BenchmarkController.js: -------------------------------------------------------------------------------- 1 | angular.module('benchpressDashboard'). 2 | controller('BenchmarkController', [ 3 | '$routeParams', '$scope', 'runContexts', 'runState', 'benchmarksService', 'stats', 'scripts', 4 | function($routeParams, $scope, runContexts, runState, benchmarksService, stats, scripts) { 5 | $scope.runContexts = runContexts; 6 | $scope.runState = runState; 7 | $scope.stats = stats; 8 | $scope.benchmarkName = $routeParams.name; 9 | $scope.selectedTab = 'Controls'; 10 | $scope.tabs = [ 11 | 'Controls', 12 | 'Scripts', 13 | 'Tips' 14 | ]; 15 | 16 | $scope.runBtns = [{ 17 | label: 'Loop', 18 | value: -1 19 | },{ 20 | label: 'Once', 21 | value: 1 22 | },{ 23 | label: 'Loop 25x', 24 | value: 25 25 | },{ 26 | //TODO: make profile work 27 | label: 'Profile', 28 | value: 'profile' 29 | }]; 30 | 31 | benchmarksService.get({cacheOk: true}).then(function() { 32 | benchmarksService.select($routeParams.name); 33 | }); 34 | 35 | scripts.get().then(function(data) { 36 | $scope.overrideScripts = data.scripts; 37 | }); 38 | 39 | //TODO: support profiling 40 | this.runBenchmark = function(val) { 41 | runState.iterations = val; 42 | runState.running = true; 43 | }; 44 | }]); -------------------------------------------------------------------------------- /dashboard/benchmark/BenchmarkController.spec.js: -------------------------------------------------------------------------------- 1 | describe('BenchmarkController', function() { 2 | var $controller, $rootScope, $httpBackend, mockAPI, benchmarksService; 3 | 4 | beforeEach(function(){ 5 | module( 6 | 'ngRoute', 7 | 'benchpressDashboard', 8 | 'bpdRunStateService', 9 | 'bpdRunContextsService', 10 | 'bpdMockAPI', 11 | 'bpdBenchmarksService', 12 | 'bpdScriptsService'); 13 | inject(function(_$controller_, _$rootScope_, $routeParams, _mockAPI_, _benchmarksService_, _$httpBackend_) { 14 | $controller = _$controller_; 15 | $routeParams.name = 'foo-benchmark'; 16 | $rootScope = _$rootScope_; 17 | mockAPI = _mockAPI_; 18 | benchmarksService = _benchmarksService_; 19 | $httpBackend = _$httpBackend_; 20 | $httpBackend.whenGET('/api/benchmarks').respond(mockAPI['/api/benchmarks']); 21 | $httpBackend.whenGET('/benchmarks/largetable/scripts.json').respond(mockAPI['/benchmarks/largetable/scripts.json']); 22 | }); 23 | }); 24 | 25 | function controllerFactory(scope) { 26 | return $controller('BenchmarkController', { 27 | $scope: scope 28 | }); 29 | } 30 | 31 | it('should set the benchmark name to the scope', function() { 32 | var scope = $rootScope.$new(); 33 | controllerFactory(scope); 34 | expect(scope.benchmarkName).toBe('foo-benchmark'); 35 | }); 36 | 37 | it('should select controls tab by default', function() { 38 | var scope = $rootScope.$new(); 39 | controllerFactory(scope); 40 | expect(scope.selectedTab).toBe('Controls'); 41 | }); 42 | 43 | 44 | it('should set the selected benchmark to the benchmarksService', function() { 45 | var scope = $rootScope.$new(); 46 | var controller = $controller('BenchmarkController', { 47 | $scope: scope, 48 | $routeParams: {name: mockAPI['/api/benchmarks'].benchmarks[0].name} 49 | }); 50 | $httpBackend.flush(); 51 | expect(benchmarksService.selected()).toEqual(mockAPI['/api/benchmarks'].benchmarks[0]); 52 | }); 53 | 54 | 55 | it('should set scripts to scope.overrideScripts', function() { 56 | var scope = $rootScope.$new(); 57 | var controller = $controller('BenchmarkController', { 58 | $scope: scope 59 | }); 60 | $httpBackend.flush(); 61 | expect(scope.overrideScripts).toEqual(mockAPI['/benchmarks/largetable/scripts.json'].scripts); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /dashboard/benchmark/benchmark.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Benchpress: {{benchmarkName}}

« all benchmarks 5 |
6 |
7 |
8 |
9 | 10 | 17 | 18 |
19 |
20 |
21 |
22 | 25 | 26 |
27 |
28 |
29 |
30 | 33 | 34 | 35 |
36 | 37 |

38 | The benchmark will run within a visible iframe within this same window, and will 39 | stream result data from the benchmark into the reporting portion of this window. 40 |

41 |

42 | Each test run will create a new iframe, and the iframe will be removed when the 43 | test is complete. 44 |

45 |
46 | 47 |

48 | The benchmark will run within a visible iframe within this same window, and will 49 | stream result data from the benchmark into the reporting portion of this window. 50 |

51 |

52 | Each test run will create a new window, and the window will be closed when the 53 | test is complete. 54 |

55 |
56 |
57 |
58 |
59 |
60 | 65 |
66 |
67 |
68 |
69 |

70 | The following scripts can be overridden by adding query params in the current window 71 | where key is the script's id, and value is the path to the new script to test with. 72 |

73 | 74 | 75 | 76 | 79 | 82 | 83 | 84 | 85 | 86 | 88 | 89 | 90 | 91 |
77 | id to override 78 | 80 | current path 81 |
87 | ?{{script.id}}=/custom/path{{script.currentPath}}
92 |
93 |
94 |

Incognito Recommended

95 |

96 | It's recommended to open a new Incognito Window before each test run/loop. 97 | Running in Incognito prevents user-added extensions from running, and provides more 98 | accurate data since optimizations and artifacts that the JavaScript engine may have 99 | collected from previous test runs will not be present. 100 |

101 | 102 |

Don't Touch Anything During Runs

103 |

104 | Any user activity, such as scrolling or backgrounding the browser window, could affect the performance of the code under test. Most consistent samples are collected by kicking off a test and keeping hands away from the keyboard and mouse until the test run is finished. 105 |

106 |
107 |
108 |
109 |
110 |
111 | 112 |
113 |
114 | 115 |
116 |
117 |
-------------------------------------------------------------------------------- /dashboard/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard", 3 | "version": "0.2.1", 4 | "homepage": "https://github.com/jeffbcross/benchpress", 5 | "authors": [ 6 | "Jeff Cross " 7 | ], 8 | "description": "Benchpress dashboard", 9 | "main": "index.html", 10 | "license": "Apache 2", 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "angular": "~1.3.7", 20 | "angular-route": "~1.3.7", 21 | "bootstrap": "~3.3.1" 22 | }, 23 | "devDependencies": { 24 | "angular-mocks": "~1.3.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dashboard/components/benchmarks-service/benchmarks-service.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdBenchmarksService', []). 2 | service('benchmarksService', ['$http', function($http) { 3 | var selectedName; 4 | 5 | this.get = function(options) { 6 | if (options && options.cacheOk && this._benchmarksCache) { 7 | return this._benchmarksCache; 8 | } 9 | return $http.get('/api/benchmarks').then(function(res) { 10 | this._benchmarksCache = res.data; 11 | return this._benchmarksCache; 12 | }.bind(this)); 13 | }; 14 | 15 | this.select = function (name) { 16 | selectedName = name; 17 | }; 18 | 19 | // By calculating at get-time, the object is guaranteed to be latest cached 20 | // version of benchmark 21 | this.selected = function () { 22 | if (!this._benchmarksCache || !this._benchmarksCache.benchmarks) return; 23 | var benchmarks = this._benchmarksCache.benchmarks; 24 | 25 | for (i in benchmarks) { 26 | if (benchmarks[i] && benchmarks[i].name === selectedName) { 27 | return benchmarks[i]; 28 | } 29 | } 30 | } 31 | }]); 32 | -------------------------------------------------------------------------------- /dashboard/components/benchmarks-service/benchmarks-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('benchmarksService', function() { 2 | var benchmarksService, $httpBackend, mockAPI, $rootScope; 3 | 4 | beforeEach(function(){ 5 | module('bpdBenchmarksService', 'bpdMockAPI'); 6 | inject(function(_$httpBackend_, _$rootScope_, _benchmarksService_, _mockAPI_) { 7 | benchmarksService = _benchmarksService_; 8 | $httpBackend = _$httpBackend_; 9 | $rootScope = _$rootScope_; 10 | mockAPI = _mockAPI_; 11 | }); 12 | }); 13 | 14 | afterEach(function() { 15 | $rootScope.$digest(); 16 | $httpBackend.verifyNoOutstandingRequest(); 17 | $httpBackend.verifyNoOutstandingExpectation(); 18 | }); 19 | 20 | describe('.get()', function() { 21 | it('should fetch benchmarks from the server', function() { 22 | var results; 23 | $httpBackend.whenGET('/api/benchmarks').respond(mockAPI['/api/benchmarks']); 24 | benchmarksService.get().then(function(val) { 25 | results = val; 26 | }); 27 | $httpBackend.flush(); 28 | expect(results).toEqual(mockAPI['/api/benchmarks']); 29 | }); 30 | 31 | 32 | it('should return request fresh data from the server on each call', function() { 33 | $httpBackend.whenGET('/api/benchmarks').respond(200); 34 | benchmarksService.get(); 35 | $httpBackend.flush(); 36 | benchmarksService.get(); 37 | $httpBackend.flush(); 38 | }); 39 | 40 | 41 | it('should return cached value if specified in options object', inject(function($http) { 42 | $httpBackend.whenGET('/api/benchmarks').respond(mockAPI['/api/benchmarks']); 43 | benchmarksService.get(); 44 | $httpBackend.flush(); 45 | benchmarksService.get({cacheOk: true}); 46 | })); 47 | }); 48 | 49 | 50 | describe('select', function() { 51 | it('should allow setting the current benchmark', function() { 52 | expect(benchmarksService.selected()).toBeUndefined(); 53 | $httpBackend.whenGET('/api/benchmarks').respond(mockAPI['/api/benchmarks']); 54 | benchmarksService.get(); 55 | $httpBackend.flush(); 56 | benchmarksService.select(mockAPI['/api/benchmarks'].benchmarks[0].name); 57 | expect(benchmarksService.selected()).toEqual(mockAPI['/api/benchmarks'].benchmarks[0]); 58 | }); 59 | 60 | 61 | it('should not complain if benchmarks have not yet been fetched', function() { 62 | expect(benchmarksService.selected()).toBeUndefined(); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /dashboard/components/iframe-runner-directive/iframe-runner-controller.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdIframeRunnerDirective'). 2 | controller('IframeRunnerController', 3 | ['$scope', 'benchmarksService', 'runState', 4 | function($scope, benchmarksService, runState) { 5 | 6 | this.frameSrc = function () { 7 | var selected = benchmarksService.selected(); 8 | if (!selected || typeof selected.name !== 'string') return; 9 | //TODO: support adding scripts and variables 10 | return [ 11 | '/benchmarks/', 12 | selected.name, 13 | '/main.html', 14 | this.allOrNothing('numSamples', [runState.numSamples], true), 15 | this.allOrNothing('iterations', [runState.iterations]), 16 | this.allOrNothing('__bpAutoClose__', [true]) 17 | ].join(''); 18 | }; 19 | 20 | this.allOrNothing = function (name, values, first) { 21 | var retValue = ''; 22 | //undefined values or empty arrays 23 | if (Array.isArray(values) && values.length > 0) { 24 | values.forEach(function(val) { 25 | if (['string', 'boolean', 'number'].indexOf(typeof val) > -1) { 26 | retValue += (first?'?':'&')+name+'='+val; 27 | first = false; 28 | } 29 | }); 30 | } 31 | return retValue; 32 | }; 33 | }]); 34 | -------------------------------------------------------------------------------- /dashboard/components/iframe-runner-directive/iframe-runner-directive.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{frameSrc()}} 4 | 6 |
7 | IFrame will be rendered here when the test begins. 8 |
9 |
-------------------------------------------------------------------------------- /dashboard/components/iframe-runner-directive/iframe-runner-directive.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdIframeRunnerDirective'). 2 | directive('bpdIframeRunner', ['runContexts', 'runState', '$templateCache', function(runContexts, runState, $templateCache) { 3 | return { 4 | restrict: 'E', 5 | link: function(scope) { 6 | scope.runContexts = runContexts; 7 | scope.runState = runState; 8 | }, 9 | controller: 'IframeRunnerController', 10 | controllerAs: 'iframeCtrl', 11 | templateUrl: 'components/iframe-runner-directive/iframe-runner-directive.html' 12 | } 13 | }]); 14 | -------------------------------------------------------------------------------- /dashboard/components/iframe-runner-directive/iframe-runner-directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('iframeRunnerDirective', function() { 2 | var $compile, $rootScope, benchmarksService, runState; 3 | 4 | beforeEach(function(){ 5 | module( 6 | 'bpdIframeRunnerDirective', 7 | 'components/iframe-runner-directive/iframe-runner-directive.html'); 8 | inject(function(_$compile_, _$rootScope_, _benchmarksService_, _runContexts_, _runState_) { 9 | $compile = _$compile_; 10 | $rootScope = _$rootScope_; 11 | runContexts = _runContexts_; 12 | runState = _runState_; 13 | benchmarksService = _benchmarksService_; 14 | }); 15 | }); 16 | 17 | it('should render an iframe if runState.running is true and runState.context is IFRAME', function() { 18 | var scope = $rootScope.$new(); 19 | runState.running = true; 20 | var compiled = $compile('')(scope); 21 | scope.$digest(); 22 | expect(compiled.html()).toContain('')(scope); 30 | scope.$digest(); 31 | expect(compiled.html()).not.toContain('')(scope); 39 | scope.$digest(); 40 | expect(compiled.html()).not.toContain('= timeout) { 22 | throw new Error('Timed out waiting for iframe.contentWindow to be defined'); 23 | } 24 | }, 5, timeout / 5, false); 25 | 26 | el.on('$destroy', function() { 27 | $interval.cancel(scope.waitForContentWindow); 28 | }); 29 | } 30 | } 31 | }]). 32 | controller('IframeStreamerController', ['$rootScope', 'runState', 'stats', function($rootScope, runState, stats) { 33 | this.onComplete = function(evt) { 34 | runState.running = false; 35 | stats.current = evt.result; 36 | $rootScope.$digest(); 37 | }; 38 | 39 | this.onProgress = function(evt) { 40 | stats.current = evt.result; 41 | console.log(stats.current); 42 | $rootScope.$digest(); 43 | } 44 | }]); 45 | }()); 46 | -------------------------------------------------------------------------------- /dashboard/components/iframe-streamer-directive/iframe-streamer-directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('bpdIframeStreamer', function() { 2 | var playground, mockIframe, runState, scope, stats, $compile, $interval; 3 | 4 | beforeEach(function() { 5 | module('bpdIframeStreamerDirective', 'bpdRunStateService'); 6 | 7 | inject(function(_$rootScope_, _$compile_, _$interval_, _runState_, _stats_) { 8 | $compile = _$compile_; 9 | $rootScope = _$rootScope_; 10 | $interval = _$interval_; 11 | runState = _runState_; 12 | stats = _stats_; 13 | scope = $rootScope.$new(); 14 | playground = document.createElement('div'); 15 | document.body.appendChild(playground); 16 | }); 17 | }); 18 | 19 | afterEach(function() { 20 | document.body.removeChild(playground); 21 | }); 22 | 23 | function compileElement(options) { 24 | element = $compile('')(scope); 25 | scope.$digest(); 26 | if (!options || !options.doNotAdd) playground.appendChild(element[0]); 27 | } 28 | 29 | 30 | it('should set runState.running to false when done the complete event from the iframe content window', function() { 31 | runState.running = true; 32 | compileElement(); 33 | $interval.flush(50); 34 | element[0].contentWindow.dispatchEvent(new Event('benchpressComplete')); 35 | expect(runState.running).toBe(false); 36 | }); 37 | 38 | 39 | it('should set runState.running to false when done the complete event from the iframe content window', function() { 40 | runState.running = true; 41 | compileElement(); 42 | $interval.flush(50); 43 | element[0].contentWindow.dispatchEvent(new Event('benchpressComplete')); 44 | expect(runState.running).toBe(false); 45 | }); 46 | 47 | 48 | it('should complain if it cannot find the contentWindow within 100ms', function() { 49 | compileElement({doNotAdd:true}); 50 | expect(function() { 51 | $interval.flush(100) 52 | }).toThrow(new Error('Timed out waiting for iframe.contentWindow to be defined')); 53 | }); 54 | 55 | 56 | it('should cancel the timer when element is destroyed', function() { 57 | compileElement(); 58 | var timer = scope.waitForContentWindow; 59 | expect(timer.value).toBeUndefined(); 60 | element.remove(); 61 | scope.$digest(); 62 | expect(timer.$$state.value).toBe('canceled'); 63 | }); 64 | 65 | 66 | it('should add the stats to the stats service when done', function() { 67 | compileElement(); 68 | $interval.flush(50); 69 | var evt = new Event('benchpressComplete'); 70 | evt.result = {}; 71 | element[0].contentWindow.dispatchEvent(evt); 72 | expect(stats.current).toEqual(evt.result); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /dashboard/components/run-contexts-service/run-contexts-service.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdRunContextsService', []). 2 | value('runContexts', { 3 | WINDOW: 1, 4 | IFRAME: 2 5 | }); 6 | 7 | -------------------------------------------------------------------------------- /dashboard/components/run-state-service/run-state-service.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | angular.module('bpdRunStateService', ['bpdRunContextsService']). 4 | service('runState', ['runContexts', RunStateService]); 5 | 6 | function RunStateService(runContexts) { 7 | this._defaultIterations = 25; 8 | this._defaultNumSamples = 20; 9 | this._running = false; 10 | this.recentResult = {}; 11 | this._context = runContexts.IFRAME; 12 | this._runContexts = runContexts; 13 | } 14 | 15 | RunStateService.prototype = { 16 | get defaults () { 17 | return this._defaults; 18 | }, 19 | set defaults (defaults) { 20 | ['iterations','numSamples'].forEach(function(prop) { 21 | var propVal = parseInt(defaults[prop],10); 22 | if (typeof propVal === 'number' && !isNaN(propVal)) { 23 | this[prop] = propVal; 24 | this._defaults[prop] = propVal; 25 | } 26 | else if (typeof defaults[prop] !== 'undefined') { 27 | throw new Error(prop + ' must be of type number, got: ' + typeof defaults[prop]); 28 | } 29 | }.bind(this)); 30 | }, 31 | get defaultIterations() { 32 | return this._defaultIterations; 33 | }, 34 | set defaultIterations(value) { 35 | if (typeof value !== 'number') throw new Error('iterations must be of type number, got: '+typeof value); 36 | this._defaultIterations = value; 37 | }, 38 | get defaultNumSamples() { 39 | return this._defaultNumSamples; 40 | }, 41 | set defaultNumSamples(value) { 42 | if (typeof value !== 'number') throw new Error('numSamples must be of type number, got: '+typeof value); 43 | this._defaultNumSamples = value; 44 | }, 45 | get iterations() { 46 | return this._iterations || this.defaultIterations; 47 | }, 48 | set iterations(value) { 49 | if (typeof value !== 'number') throw new Error('iterations value must be a number'); 50 | this._iterations = value; 51 | }, 52 | get numSamples () { 53 | return this._numSamples || this.defaultNumSamples; 54 | }, 55 | set numSamples (value) { 56 | if (typeof value !== 'number') throw new Error('numSamples value must be a number'); 57 | if (value < 0) throw new Error('must collect at least one sample'); 58 | this._numSamples = value; 59 | }, 60 | get running () { 61 | return this._running; 62 | }, 63 | set running (value) { 64 | if (typeof value !== 'boolean') throw new Error('"running" must be a boolean value'); 65 | this._running = value; 66 | }, 67 | get context() { 68 | return this._context; 69 | }, 70 | set context(value) { 71 | var valid = false; 72 | for (var item in this._runContexts) { 73 | if (this._runContexts.hasOwnProperty(item) && this._runContexts[item] === value) { 74 | valid = true; 75 | break; 76 | } 77 | } 78 | if (!valid) throw new Error(value+' is not a valid running context enumerable value'); 79 | this._context = value; 80 | }, 81 | resetDefaults: function () { 82 | this.iterations = this.defaultIterations; 83 | this.numSamples = this.defaultNumSamples; 84 | } 85 | } 86 | 87 | }()); 88 | -------------------------------------------------------------------------------- /dashboard/components/run-state-service/run-state-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('runStateService', function() { 2 | var runState; 3 | 4 | beforeEach(function() { 5 | module('bpdRunStateService'); 6 | inject(function(_runState_) { 7 | runState = _runState_; 8 | }); 9 | }); 10 | 11 | 12 | it('should set default properties', function() { 13 | expect(runState.iterations).toBe(25); 14 | expect(runState.numSamples).toBe(20); 15 | expect(runState.recentResult).toEqual({}); 16 | }); 17 | 18 | 19 | describe('.setIterations()', function() { 20 | it('should set provided arguments to runState object', function() { 21 | runState.iterations = 15; 22 | expect(runState.iterations).toBe(15); 23 | }); 24 | }); 25 | 26 | 27 | describe('.resetDefaults()', function() { 28 | it('should set runState values to defaults', function() { 29 | runState.iterations = 99; 30 | runState.numSamples = 98; 31 | expect(runState.iterations).toBe(99); 32 | expect(runState.numSamples).toBe(98); 33 | runState.resetDefaults(); 34 | expect(runState.iterations).toBe(25); 35 | expect(runState.numSamples).toBe(20); 36 | }); 37 | }); 38 | 39 | 40 | describe('.defaultIterations', function() { 41 | it('should override defaults if set directly', function() { 42 | runState.defaultIterations = 10; 43 | expect(runState.iterations).toBe(10); 44 | expect(runState.defaultIterations).toBe(10); 45 | }); 46 | 47 | 48 | it('should throw if provided non number values', function() { 49 | expect(function() { 50 | runState.defaultIterations = 'foo'; 51 | }).toThrow('iterations must be of type number, got: string'); 52 | }); 53 | }); 54 | 55 | 56 | describe('.defaultNumSamples', function() { 57 | it('should override defaults if set directly', function() { 58 | runState.defaultNumSamples = 10; 59 | expect(runState.numSamples).toBe(10); 60 | expect(runState.defaultNumSamples).toBe(10); 61 | }); 62 | 63 | 64 | it('should throw if provided non number values', function() { 65 | expect(function() { 66 | runState.defaultNumSamples = 'bar'; 67 | }).toThrow('numSamples must be of type number, got: string'); 68 | }); 69 | }); 70 | 71 | 72 | describe('.context', function() { 73 | describe('getter', function() { 74 | it('should return IFRAME by default', function() { 75 | expect(runState.context).toBe(runContexts.IFRAME); 76 | }); 77 | 78 | 79 | it('should return correct value after being set', function() { 80 | expect(runState.context).toBe(runContexts.IFRAME); 81 | runState.context = runContexts.WINDOW; 82 | expect(runState.context).toBe(runContexts.WINDOW); 83 | runState.context = runContexts.IFRAME; 84 | expect(runState.context).toBe(runContexts.IFRAME); 85 | }); 86 | }); 87 | 88 | 89 | describe('setter', function() { 90 | it('should set the value', function() { 91 | expect(runState.context).toBe(runContexts.IFRAME); 92 | runState.context = runContexts.WINDOW; 93 | expect(runState.context).toBe(runContexts.WINDOW); 94 | expect(runState._context).toBe(runContexts.WINDOW); 95 | }); 96 | 97 | 98 | it('should throw if value other than acceptable enumerable is given', function() { 99 | expect(function(){ 100 | runState.context = 3; 101 | }).toThrow(new Error('3 is not a valid running context enumerable value')); 102 | }); 103 | }); 104 | }); 105 | 106 | describe('.running', function() { 107 | describe('getter', function() { 108 | it('should return false by default', function() { 109 | expect(runState.running).toBe(false); 110 | }); 111 | 112 | 113 | it('should return correct value after being set', function() { 114 | expect(runState.running).toBe(false); 115 | runState.running = true; 116 | expect(runState.running).toBe(true); 117 | runState.running = false; 118 | expect(runState.running).toBe(false); 119 | }); 120 | }); 121 | 122 | 123 | describe('setter', function() { 124 | it('should set the value', function() { 125 | expect(runState.running).toBe(false); 126 | runState.running = true; 127 | expect(runState.running).toBe(true); 128 | expect(runState._running).toBe(true); 129 | }); 130 | 131 | 132 | it('should throw if value other than boolean is given', function() { 133 | expect(function(){ 134 | runState.running = 'yep'; 135 | }).toThrow(new Error('"running" must be a boolean value')); 136 | }); 137 | }); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /dashboard/components/scripts-service/scripts-service.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdScriptsService', []). 2 | service('scripts', ['$http', function($http) { 3 | var selectedName; 4 | 5 | this.get = function(options) { 6 | if (options && options.cacheOk && this._scriptsCache) { 7 | return this._scriptsCache; 8 | } 9 | return $http.get('/benchmarks/largetable/scripts.json').then(function(res) { 10 | this._scriptsCache = res.data; 11 | return this._scriptsCache; 12 | }.bind(this)); 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /dashboard/components/scripts-service/scripts-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('bpdScriptsService', function() { 2 | var scripts, $httpBackend, $rootScope, mockAPI; 3 | 4 | beforeEach(function(){ 5 | module('bpdScriptsService', 'bpdMockAPI'); 6 | inject(function(_$httpBackend_, _$rootScope_, _scripts_, _mockAPI_) { 7 | scripts = _scripts_; 8 | $httpBackend = _$httpBackend_; 9 | $rootScope = _$rootScope_; 10 | mockAPI = _mockAPI_; 11 | }); 12 | }); 13 | 14 | afterEach(function() { 15 | $rootScope.$digest(); 16 | $httpBackend.verifyNoOutstandingRequest(); 17 | $httpBackend.verifyNoOutstandingExpectation(); 18 | }); 19 | 20 | describe('.get()', function() { 21 | it('should fetch benchmarks from the server', function() { 22 | var results; 23 | $httpBackend.whenGET('/benchmarks/largetable/scripts.json'). 24 | respond(mockAPI['/benchmarks/largetable/scripts.json']); 25 | scripts.get().then(function(val) { 26 | results = val; 27 | }); 28 | $httpBackend.flush(); 29 | expect(results).toEqual(mockAPI['/benchmarks/largetable/scripts.json']); 30 | }); 31 | 32 | 33 | it('should return request fresh data from the server on each call', function() { 34 | $httpBackend.whenGET('/benchmarks/largetable/scripts.json').respond(200); 35 | scripts.get(); 36 | $httpBackend.flush(); 37 | scripts.get(); 38 | $httpBackend.flush(); 39 | }); 40 | 41 | 42 | it('should return cached value if specified in options object', inject(function($http) { 43 | $httpBackend.whenGET('/benchmarks/largetable/scripts.json'). 44 | respond(mockAPI['/benchmarks/largetable/scripts.json']); 45 | scripts.get(); 46 | $httpBackend.flush(); 47 | scripts.get({cacheOk: true}); 48 | })); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /dashboard/components/stats-service/stats-service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | angular.module('bpdStatsService', []). 4 | service('stats', ['escapeDollarSigns', StatsService]). 5 | factory('escapeDollarSigns', function() { 6 | return function(stats) { 7 | for (var key in stats) { 8 | if (stats.hasOwnProperty(key) && key.indexOf('$') === 0) { 9 | //Add a space at the front so ngRepeat won't skip this property 10 | stats[key.replace('$', ' $')] = stats[key]; 11 | delete stats[key]; 12 | } 13 | } 14 | return stats; 15 | } 16 | 17 | }); 18 | 19 | function StatsService (escapeDollarSigns) { 20 | this._stats = {}; 21 | this._escapeDollarSigns = escapeDollarSigns; 22 | } 23 | 24 | //TODO: support streaming stats 25 | StatsService.prototype = { 26 | get current() { 27 | return this._stats; 28 | }, 29 | set current(value) { 30 | this._stats = this._escapeDollarSigns(value); 31 | } 32 | }; 33 | 34 | }()); 35 | -------------------------------------------------------------------------------- /dashboard/components/stats-service/stats-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('bpdStatsService', function() { 2 | var stats, escapeDollarSigns; 3 | 4 | beforeEach(function() { 5 | module('bpdStatsService'); 6 | inject(function(_escapeDollarSigns_, _stats_) { 7 | stats = _stats_; 8 | escapeDollarSigns = _escapeDollarSigns_; 9 | }); 10 | }); 11 | 12 | it('should start with an empty object', function() { 13 | expect(stats.current).toEqual({}); 14 | }); 15 | 16 | 17 | describe('escapeDollarSigns', function() { 18 | it('should rename keys', function() { 19 | expect(escapeDollarSigns({'$apply':{}})).toEqual({' $apply':{}}); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /dashboard/components/table-report-directive/table-report-directive.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Results:

4 |
5 |
6 |
7 |
8 | test time (ms) 9 |
10 |
11 | gc time (ms) 12 |
13 |
14 | garbage (KB) 15 |
16 |
17 | retained memory (KB) 18 |
19 |
20 |
21 |
22 |
23 |
24 |

{{key}}

25 |
26 |
27 | mean: {{ val.testTime.avg.mean | number:2 }}ms 28 | ± {{ val.testTime.avg.coefficientOfVariation * 100 | number:0 }}% 29 |
30 | (stddev {{ val.testTime.avg.stdDev | number:2 }}) 31 |
32 | (min {{ val.testTime.min | number:2 }} / 33 | max {{ val.testTime.max | number:2 }}) 34 |
35 |
36 | mean: {{ val.gcTime.avg.mean | number:2 }}ms 37 |
38 | (combined: {{ (val.testTime.avg.mean + val.gcTime.avg.mean) | number:2 }}ms) 39 |
40 |
41 | mean: {{ (val.garbageCount.avg.mean / 1e3) | number:2 }}KB 42 |
43 |
44 | mean: {{ (val.retainedCount.avg.mean / 1e3) | number:2 }}KB 45 |
46 |
47 |
48 |
51 |
52 | 53 | {{ result | number:2 }} 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /dashboard/components/table-report-directive/table-report-directive.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdTableReportDirective', ['bpdStatsService']). 2 | directive('bpdTableReport', ['stats', function(stats) { 3 | return { 4 | restrict: 'E', 5 | scope: {}, 6 | controller: ['$scope', function($scope) { 7 | $scope.stats = stats; 8 | //TODO: move measurements to service 9 | $scope.measurements = ['testTime','gcTime','garbageCount','retainedCount']; 10 | }], 11 | templateUrl: 'components/table-report-directive/table-report-directive.html' 12 | }; 13 | }]); 14 | -------------------------------------------------------------------------------- /dashboard/home.html: -------------------------------------------------------------------------------- 1 |

Available Benchmarks

2 |
    3 |
  • 4 | 5 |
  • 6 |
7 |
8 | Could not load benchmarks:
9 | Status Code: {{responseError.code}}
10 | Response Body: {{responseError.body}} 11 |
12 | -------------------------------------------------------------------------------- /dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Benchpress Dashboard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /dashboard/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.plugins.push('karma-ng-html2js-preprocessor'); 3 | config.set({ 4 | preprocessors: { 5 | 'components/**/*.html': 'ng-html2js' 6 | }, 7 | frameworks: ['jasmine'], 8 | files: [ 9 | 'bower_components/angular/angular.js', 10 | 'bower_components/angular-route/angular-route.js', 11 | 'bower_components/angular-mocks/angular-mocks.js', 12 | 'test/mock-api.js', 13 | 'app.js', 14 | 'components/iframe-runner-directive/iframe-runner.js', 15 | 'components/**/*', 16 | 'benchmark/**.js', 17 | '*.js' 18 | ], 19 | exclude: ['karma.conf.js'], 20 | browsers: ['Chrome'] 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /dashboard/test/mock-api.js: -------------------------------------------------------------------------------- 1 | angular.module('bpdMockAPI', []). 2 | value('mockAPI', { 3 | '/api/benchmarks': { 4 | benchmarks:[{ 5 | name: 'largetable', 6 | description: 'a benchmark to draw a large table in many ways' 7 | },{ 8 | name:'render-table', 9 | description: 'a normal sized table is destroyed and created' 10 | }]}, 11 | '/benchmarks/largetable/scripts.json': { 12 | "scripts":[{ 13 | "id":"jquery", 14 | "src":"jquery-noop.js" 15 | },{ 16 | "id":"angular","src":"angular.js" 17 | },{ 18 | "src":"app.js" 19 | }]} 20 | }); 21 | -------------------------------------------------------------------------------- /dist.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | 3 | var browserify = require('browserify'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | 7 | ['auto-bp.js','bp.js'].forEach(function(dest) { 8 | var bpOut = ''; 9 | browserify(path.resolve('./lib/', dest), {baseDir: './lib/'}).bundle().on('data', function(chunk) { 10 | bpOut += chunk.toString(); 11 | }).on('end', function() { 12 | bpOut = bpOut.replace(/\$traceurRuntime/g, '$diTraceurRuntime'); 13 | bpOut = bpOut.replace(/System/g, 'DITraceurSystem'); 14 | fs.writeFileSync(path.resolve('dist/', dest), bpOut); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /grunt-benchmarks/sample-folder/bp.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | 3 | } -------------------------------------------------------------------------------- /grunt-benchmarks/sample-folder/main.html: -------------------------------------------------------------------------------- 1 | sample! -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Jun 10 2014 08:41:10 GMT-0700 (PDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine', 'browserify'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'lib/*.js', 18 | 'test/*.js', 19 | 'node_modules/underscore/underscore.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | 'lib/cli.js' 26 | ], 27 | 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 31 | reporters: ['progress'], 32 | 33 | preprocessors: { 34 | 'lib/*.js': ['browserify'], 35 | 'test/*.js': ['browserify'] 36 | }, 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | 43 | // enable / disable colors in the output (reporters and logs) 44 | colors: true, 45 | 46 | 47 | // level of logging 48 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 49 | logLevel: config.LOG_INFO, 50 | 51 | 52 | // enable / disable watching file and executing tests whenever any file changes 53 | autoWatch: true, 54 | 55 | 56 | // Start these browsers, currently available: 57 | // - Chrome 58 | // - ChromeCanary 59 | // - Firefox 60 | // - Opera 61 | // - Safari (only Mac) 62 | // - PhantomJS 63 | // - IE (only Windows) 64 | browsers: ['Chrome'], 65 | 66 | browserify: { 67 | debug: true 68 | }, 69 | 70 | 71 | // If browser does not capture in given timeout [ms], kill it 72 | captureTimeout: 60000, 73 | 74 | 75 | // Continuous Integration mode 76 | // if true, it capture browsers, run tests and exit 77 | singleRun: false 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /lib/Aggregator.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var Measure = require('./Measure'); 3 | var RunState = require('./RunState'); 4 | var Statistics = require('./Statistics'); 5 | var Steps = require('./Steps'); 6 | 7 | function Aggregator (measure, runState, statistics, steps) { 8 | this._measure = measure; 9 | this._runState = runState; 10 | this._statistics = statistics; 11 | this._steps = steps.all(); 12 | this.timesPerAction = {}; 13 | } 14 | 15 | Aggregator.prototype.trimSamples = function(times) { 16 | var delta = times.length - this._runState.numSamples; 17 | if (delta > 0) { 18 | return times.slice(delta); 19 | } 20 | return times; 21 | }; 22 | 23 | Aggregator.prototype.getTimesPerAction = function(name) { 24 | if (this.timesPerAction[name]) return this.timesPerAction[name]; 25 | var tpa = this.timesPerAction[name] = { 26 | name: name, 27 | nextEntry: 0 28 | }; 29 | 30 | _.each(this._measure.characteristics, function(c) { 31 | tpa[c] = { 32 | recent: undefined, 33 | history: [], 34 | avg: {}, 35 | min: Number.MAX_VALUE, 36 | max: Number.MIN_VALUE 37 | }; 38 | }); 39 | return tpa; 40 | }; 41 | 42 | Aggregator.prototype.updateTimes = function(tpa, index, reference, recentTime) { 43 | var curTpa = tpa[reference]; 44 | curTpa.recent = recentTime; 45 | curTpa.history[index] = recentTime; 46 | curTpa.history = this.trimSamples(curTpa.history); 47 | curTpa.min = Math.min(curTpa.min, recentTime); 48 | curTpa.max = Math.max(curTpa.max, recentTime); 49 | }; 50 | 51 | Aggregator.prototype.calcStats = function() { 52 | var stats = {}; 53 | var self = this; 54 | this._steps.forEach(function(bs) { 55 | var recentResult = self._runState.recentResult[bs.name], 56 | tpa = self.getTimesPerAction(bs.name); 57 | tpa.description = bs.description; 58 | _.each(self._measure.characteristics, function(c) { 59 | self.updateTimes(tpa, tpa.nextEntry, c, recentResult[c]); 60 | var mean = self._statistics.getMean(tpa[c].history); 61 | var stdDev = self._statistics.calculateStandardDeviation(tpa[c].history, mean); 62 | tpa[c].avg = { 63 | mean: mean, 64 | stdDev: stdDev, 65 | coefficientOfVariation: self._statistics.calculateCoefficientOfVariation(stdDev, mean) 66 | }; 67 | }); 68 | 69 | // Start over samples when hits the max set by numSamples 70 | tpa.nextEntry++; 71 | tpa.nextEntry %= self._runState.numSamples; 72 | stats[bs.name] = tpa; 73 | }); 74 | return stats; 75 | }; 76 | 77 | di.annotate(Aggregator, new di.Inject(Measure, RunState, Statistics, Steps)); 78 | module.exports = Aggregator; 79 | -------------------------------------------------------------------------------- /lib/AutoRunner.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var Aggregator = require('./Aggregator'); 3 | var Globals = require('./Globals'); 4 | var Logger = require('./Logger'); 5 | var Runner = require('./Runner'); 6 | var RunState = require('./RunState'); 7 | var Rx = require('rx'); 8 | var Steps = require('./Steps'); 9 | var Utils = require('./Utils'); 10 | 11 | function AutoRunner(aggregator,globals,logger,runner,runState,steps,utils){ 12 | this._aggregator = aggregator; 13 | this._globals = globals; 14 | this._logger = logger; 15 | this._runner = runner; 16 | this._runState = runState; 17 | this._utils = utils; 18 | this._steps = steps; 19 | } 20 | 21 | AutoRunner.prototype.bootstrap = function() { 22 | var parsed = this._utils.parseSearch(this._globals._window.location.search) 23 | this._runState.setDefault(parsed); 24 | if (parsed['__bpAutoClose__'] === 'true') { 25 | this._runState.headless = true; 26 | } 27 | }; 28 | 29 | AutoRunner.prototype.ready = function () { 30 | this._globals._window.setTimeout(function() { 31 | this.runBenchmark(this._runState); 32 | }.bind(this)); 33 | }; 34 | 35 | AutoRunner.prototype.runBenchmark = function(config) { 36 | this.runAllTests().subscribe(function(update) { 37 | this._logger.write(update); 38 | var benchpressProgress = new Event('benchpressProgress'); 39 | benchpressProgress.result = update.result; 40 | this._globals._window.dispatchEvent(benchpressProgress); 41 | }.bind(this), null, function() { 42 | if (this._runState.headless === true) { 43 | var benchpressComplete = new Event('benchpressComplete'); 44 | benchpressComplete.result = this.stats; 45 | this._globals._window.dispatchEvent(benchpressComplete); 46 | this._globals._window.close(); 47 | } 48 | }.bind(this)); 49 | }; 50 | 51 | AutoRunner.prototype.runAllTests = function (iterations) { 52 | this.subject = this.subject || new Rx.Subject(); 53 | iterations = typeof iterations === 'number' ? iterations : this._runState.iterations; 54 | if (iterations--) { 55 | this._steps.all().forEach(function(bs) { 56 | var testResults = this._runner.runTimedTest(bs); 57 | this._runState.recentResult[bs.name] = testResults; 58 | 59 | }.bind(this)); 60 | this.stats = this._aggregator.calcStats(); 61 | this.subject.onNext({result: this.stats}); 62 | 63 | window.requestAnimationFrame(function() { 64 | this.runAllTests(iterations); 65 | }.bind(this)); 66 | } 67 | else { 68 | this.stats = this._aggregator.calcStats(); 69 | this.subject.onCompleted(); 70 | this.subject.dispose(); 71 | delete this.subject; 72 | } 73 | return this.subject; 74 | }; 75 | 76 | di.annotate(AutoRunner, new di.Inject(Aggregator,Globals,Logger,Runner,RunState,Steps,Utils)); 77 | module.exports = AutoRunner; 78 | -------------------------------------------------------------------------------- /lib/ClientScripts.js: -------------------------------------------------------------------------------- 1 | function ClientScripts () { 2 | this._scripts = []; 3 | } 4 | 5 | ClientScripts.prototype.all = function() { 6 | return this._scripts; 7 | } 8 | 9 | ClientScripts.prototype.shiftOne = function() { 10 | return this._scripts.shift(); 11 | }; 12 | 13 | ClientScripts.prototype.add = function(script) { 14 | this._scripts.push(script); 15 | }; 16 | 17 | ClientScripts.prototype.addMany = function(scripts) { 18 | this._scripts.push.apply(this._scripts, scripts); 19 | } 20 | 21 | module.exports = ClientScripts; 22 | -------------------------------------------------------------------------------- /lib/Document.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var ClientScripts = require('./ClientScripts'); 3 | var Globals = require('./Globals'); 4 | var RunState = require('./RunState'); 5 | var Rx = require('rx'); 6 | var Utils = require('./Utils'); 7 | 8 | function Document(globals, runState, scripts, utils) { 9 | this._utils = utils; 10 | this._scripts = scripts; 11 | this._globals = globals; 12 | this._runState = runState; 13 | this.loadNextScript = this.loadNextScript.bind(this); 14 | } 15 | 16 | Document.prototype.setAutoRun = function(val) { 17 | this.autoRunner = val; 18 | }; 19 | 20 | Document.prototype.addSampleRange = function() { 21 | this.sampleRange = this.container().querySelector('#sampleRange'); 22 | if (this.sampleRange) { 23 | this.sampleRange.value = Math.max(this._runState.numSamples, 1); 24 | this.sampleRange.addEventListener('input', this.onSampleInputChanged.bind(this)); 25 | } 26 | }; 27 | 28 | Document.prototype.onSampleInputChanged = function (evt) { 29 | var value = evt.target.value; 30 | this._runState.numSamples = parseInt(value, 10); 31 | }; 32 | 33 | Document.prototype.container = function() { 34 | if (!this._container) { 35 | this._container = document.querySelector('#benchmarkContainer'); 36 | } 37 | return this._container; 38 | }; 39 | 40 | Document.prototype.addInfo = function() { 41 | this.infoDiv = this.container().querySelector('div.info'); 42 | if (this.infoDiv) { 43 | this.infoTemplate = _.template(this.container().querySelector('#infoTemplate').innerHTML); 44 | } 45 | }; 46 | 47 | Document.prototype.loadNextScript = function() { 48 | var params = this._utils.parseSearch(this._globals._window.location.search); 49 | var config = this._scripts.shiftOne(); 50 | if (!config) return; 51 | if (config.id) { 52 | if (params[config.id]) { 53 | config.src = params[config.id]; 54 | } 55 | this.addScriptToUI(config); 56 | } 57 | var tag = document.createElement('script'); 58 | tag.setAttribute('src', config.src); 59 | tag.onload = this.loadNextScript; 60 | document.body.appendChild(tag); 61 | }; 62 | 63 | Document.prototype.openTab = function (id) { 64 | var divs = this._container.querySelectorAll('.tab-pane'); 65 | for (var i=0;i 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | %%PLACEHOLDER%% 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/bp.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var ClientScripts = require('./ClientScripts'); 3 | var Document = require('./Document'); 4 | var Globals = require('./Globals'); 5 | var Measure = require('./Measure'); 6 | var Report = require('./HtmlReport'); 7 | var Runner = require('./Runner'); 8 | var Statistics = require('./Statistics'); 9 | var Steps = require('./Steps'); 10 | var Variables = require('./Variables'); 11 | 12 | //Benchpress Facade 13 | function BenchPress (doc, globals, measure, report, runner, scripts, statistics, steps, variables) { 14 | // Left benchmarkSteps on global for backwards-compatibility 15 | //Deprecated 16 | window.benchmarkSteps = this.steps = steps.all(); 17 | this._scripts = scripts; 18 | this.runner = runner; 19 | 20 | //Deprecated 21 | this.scripts = scripts.all(); 22 | this.doc = doc; 23 | this.variables = variables; 24 | 25 | 26 | //Legacy Support 27 | this.Document = doc; 28 | this.Measure = measure; 29 | this.Runner = runner; 30 | this.Report = report; 31 | this.Statistics = statistics; 32 | if (globals._window) globals._window.addEventListener('DOMContentLoaded', function(e) { 33 | doc.onDOMContentLoaded.call(doc); 34 | runner.bootstrap(); 35 | }.bind(this)); 36 | } 37 | 38 | di.annotate(BenchPress, new di.Inject(Document, Globals, Measure, Report, Runner, ClientScripts, Statistics, Steps, Variables)) 39 | 40 | var injector = new di.Injector([]); 41 | window.bp = injector.get(BenchPress); 42 | 43 | module.exports = BenchPress; 44 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | http = require('http'), 3 | path = require('path'), 4 | benchpressBase = path.resolve(module.filename, '../../'), 5 | rimraf = require('rimraf'), 6 | mkdirp = require('mkdirp'), 7 | defaultBuildPath = path.resolve('benchpress-build'), 8 | defaultBenchmarksPath = path.resolve('benchmarks'), 9 | underscoreMapPath = path.resolve(benchpressBase, 'node_modules/underscore/underscore-min.map'), 10 | underscorePath = path.resolve(benchpressBase, 'node_modules/underscore/underscore-min.js'), 11 | bootstrapPath = path.resolve(benchpressBase, 'node_modules/bootstrap/dist/css/bootstrap.min.css'), 12 | bpPath = path.resolve(benchpressBase, 'lib/bp.js'), 13 | libPath = path.resolve(benchpressBase, 'lib'), 14 | minimist = require('minimist'), 15 | server = require('./node/server'), 16 | browserify = require('browserify'); 17 | 18 | 19 | exports.launchChrome = function launchChrome(done) { 20 | if (process.platform == 'linux') { 21 | require('child_process').exec('google-chrome -incognito --js-flags="--expose-gc"', function(err) { 22 | if (err) console.error('An error occurred trying to launch Chrome: ', err); 23 | done && done(); 24 | }); 25 | } 26 | else if (process.platform === 'darwin') { 27 | require('child_process').exec('/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary ' + [ 28 | '--no-default-browser-check', 29 | '--no-first-run', 30 | '--disable-default-apps', 31 | '--disable-popup-blocking', 32 | '--disable-translate', 33 | '--enable-memory-info', 34 | '--enable-precise-memory-info', 35 | '--enable-memory-benchmarking', 36 | '--js-flags="--expose-gc --nocrankshaft --noopt"', 37 | '-incognito'].join(' '), function(err) { 38 | if (err) console.error('An error occurred trying to launch Chrome: ', err); 39 | done && done(); 40 | }); 41 | } 42 | else { 43 | console.log('Cannot launch chrome for your platform: ', process.platform); 44 | done && done(); 45 | } 46 | }; 47 | 48 | exports.run = function run(options) { 49 | options = options || {}; 50 | options.buildPath = options.buildPath || path.relative('./', defaultBuildPath) 51 | return server.run(options); 52 | }; 53 | 54 | exports.build = function build(options, done) { 55 | options = options || {}; 56 | var buildPath = options.buildPath || defaultBuildPath, 57 | benchmarksPath = options.benchmarksPath || defaultBenchmarksPath; 58 | 59 | //Start fresh 60 | rimraf(buildPath, buildIt); 61 | 62 | function buildIt (err) { 63 | var template, autoTemplate, benchmarks; 64 | if (err) throw err; 65 | mkdirp.sync(buildPath); 66 | autoTemplate = fs.readFileSync(path.resolve(libPath, 'auto-template.html')); 67 | //Get benchmark html template 68 | fs.readFile(path.resolve(libPath, 'template.html'), function(err, template) { 69 | if (err) { 70 | switch (err.errno) { 71 | case 34: 72 | throw new Error('Could not find template.html in module'); 73 | default: 74 | throw err; 75 | } 76 | } 77 | benchmarks = fs.readdirSync(benchmarksPath); 78 | benchmarks.forEach(function(benchmark) { 79 | var dependencies, main, config, autoMain, 80 | benchmarkPath = path.resolve(benchmarksPath, benchmark), 81 | scriptTags = '', 82 | writtenFiles = 0; 83 | 84 | 85 | //Ignore any non-directories in the benchmark folder 86 | if (!fs.lstatSync(benchmarkPath).isDirectory()) return; 87 | 88 | fs.mkdirSync(path.resolve(buildPath, benchmark)); 89 | 90 | config = new Config(); 91 | require(path.resolve(benchmarkPath, 'bp.conf.js'))(config); 92 | 93 | dependencies = fs.readdirSync(benchmarkPath); 94 | 95 | dependencies.forEach(function(dependency) { 96 | var dependencyPath = path.resolve(benchmarkPath, dependency); 97 | if (dependency === 'main.html') { 98 | //This is the main benchmark template 99 | main = fs.readFileSync(dependencyPath).toString(); 100 | autoMain = autoTemplate.toString().replace('%%PLACEHOLDER%%', main); 101 | main = template.toString().replace('%%PLACEHOLDER%%', main); 102 | 103 | } 104 | else { 105 | fs.createReadStream(dependencyPath).pipe(fs.createWriteStream(path.resolve(buildPath, benchmark, dependency))); 106 | } 107 | }); 108 | 109 | main = main.replace('%%SCRIPTS%%', JSON.stringify(config.scripts)); 110 | main = main.replace('%%MODULES%%', JSON.stringify(config.modules) || 'undefined'); 111 | autoMain = autoMain.replace('%%SCRIPTS%%', JSON.stringify(config.scripts)); 112 | autoMain = autoMain.replace('%%MODULES%%', JSON.stringify(config.modules) || 'undefined'); 113 | fs.writeFileSync(path.resolve(buildPath, benchmark, 'index.html'), main); 114 | fs.writeFileSync(path.resolve(buildPath, benchmark, 'index-auto.html'), autoMain); 115 | createBenchmarkListingPage(buildPath, benchmarks); 116 | 117 | ['auto-bp','bp'].forEach(function(dest) { 118 | var bpOut = ''; 119 | browserify(path.resolve(benchpressBase , './lib/', dest), {baseDir: './lib/'}).bundle().on('data', function(chunk) { 120 | bpOut += chunk.toString(); 121 | }).on('end', function() { 122 | bpOut = bpOut.replace(/\$traceurRuntime/g, '$diTraceurRuntime'); 123 | bpOut = bpOut.replace(/System/g, 'DITraceurSystem'); 124 | fs.writeFileSync(path.resolve(buildPath, benchmark, dest+'.js'), bpOut); 125 | isDone(); 126 | }); 127 | }); 128 | 129 | fs.createReadStream(underscorePath). 130 | pipe(fs.createWriteStream(path.resolve(buildPath, benchmark, 'underscore-min.js'))). 131 | on('close', isDone); 132 | fs.createReadStream(bootstrapPath). 133 | pipe(fs.createWriteStream(path.resolve(buildPath, benchmark, 'bootstrap.min.css'))). 134 | on('close', isDone); 135 | 136 | function isDone() { 137 | writtenFiles++; 138 | if (writtenFiles === 4) done && done(); 139 | } 140 | }); 141 | }); 142 | 143 | } 144 | 145 | function Config() {} 146 | 147 | Config.prototype.set = function (obj) { 148 | for (var k in obj) { 149 | if (obj.hasOwnProperty(k)) { 150 | this[k] = obj[k]; 151 | } 152 | } 153 | } 154 | }; 155 | 156 | exports.exec = function() { 157 | var args = minimist(process.argv.slice(2)); 158 | switch (process.argv[2]) { 159 | case 'build': 160 | exports.build({ 161 | buildPath: args['build-path'], 162 | benchmarksPath: args['benchmark-path'] 163 | }); 164 | break; 165 | case 'run': 166 | exports.run({ 167 | buildPath: args['build-path'] 168 | }); 169 | break; 170 | case 'launch_chrome': 171 | exports.launchChrome(); 172 | break; 173 | default: 174 | console.log('Unknown command: ', process.argv[2]); 175 | console.log('Acceptable commands are "build", "run", "launch_chrome"'); 176 | console.log('benchpress version ', require('../package.json').version); 177 | } 178 | }; 179 | 180 | function createBenchmarkListingPage (buildPath, directoryList) { 181 | var directoryMarkup = [ 182 | '', 183 | '', 184 | 'Available Benchmarks', 185 | '', 186 | '', 187 | '

Available Benchmarks

', 188 | '
    ', 189 | '%%CONTENT%%', 190 | '
', 191 | '', 192 | ''].join('\n'), 193 | liMarkup = '
  • %%BENCHMARK%%
  • ' 194 | content = ''; 195 | directoryList.forEach(function(benchmark) { 196 | content += liMarkup.replace(/%%BENCHMARK%%/g, benchmark); 197 | }); 198 | directoryMarkup = directoryMarkup.replace('%%CONTENT%%', content); 199 | fs.writeFileSync(path.resolve(buildPath, 'index.html'), directoryMarkup); 200 | } 201 | -------------------------------------------------------------------------------- /lib/node/api.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var fs = require('fs'); 3 | var router = express.Router(); 4 | var minimist = require('minimist'); 5 | 6 | router.get('/', function(req, res, next) { 7 | res.status(404); 8 | next(); 9 | }); 10 | 11 | router.get('/benchmarks', function(req, res, next) { 12 | var args = minimist(process.argv.slice(2)); 13 | var path = args['benchmarks-path'] || 'benchmarks'; 14 | 15 | fs.readdir(path, function(err, folders){ 16 | if (err) next(err); 17 | res.json({ 18 | benchmarks: folders.map(function(item) { 19 | return {name: item}; 20 | }) 21 | }); 22 | next(); 23 | }); 24 | }); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /lib/node/expressMock.js: -------------------------------------------------------------------------------- 1 | var expressMock = module.exports = function () { 2 | function App () { 3 | } 4 | 5 | App.get = function() { 6 | expressMock.get.apply(App, arguments); 7 | return App; 8 | }; 9 | 10 | App.use = function() { 11 | expressMock.use.apply(App, arguments); 12 | return App; 13 | }; 14 | 15 | return App; 16 | } 17 | 18 | expressMock.static = function(path) { 19 | return function(staticFn) { 20 | }; 21 | }; 22 | 23 | expressMock.get = function (path, handler) { 24 | expressMock.handlers['get'][path] = handler; 25 | return expressMock; 26 | }; 27 | 28 | expressMock.use = function (fn) { 29 | 30 | }; 31 | 32 | expressMock.handlers = { 33 | get: {} 34 | }; 35 | 36 | expressMock._handle = function (method, path, args) { 37 | var handler; 38 | if (handler = expressMock.handlers[method][path]) { 39 | handler.apply(null, args); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/node/httpMock.js: -------------------------------------------------------------------------------- 1 | exports.createServer = function (callback) { 2 | return exports; 3 | }; 4 | 5 | exports.listen = function(port) { 6 | console.log('I am listening'); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/node/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | http = require('http'), 5 | api = require('./api'); 6 | 7 | exports.run = function run(options) { 8 | options = options || {}; 9 | var port = options.port || 3339; 10 | 11 | var app = express(). 12 | get('/auto-bp.js', function(req, res, next) { 13 | fs.createReadStream(path.resolve('dist','auto-bp.js')).pipe(res); 14 | }). 15 | get('/bp.js', function(req, res, next) { 16 | fs.createReadStream(path.resolve('dist','bp.js')).pipe(res); 17 | }). 18 | use('/api', api). 19 | use(express.static(path.resolve('./dashboard'))). 20 | //TODO: make configurable 21 | use('/benchmarks/',express.static(path.resolve('./benchmarks'))); 22 | 23 | console.log('listening at ', port); 24 | return http.createServer(app).listen(port); 25 | }; 26 | -------------------------------------------------------------------------------- /lib/node/server.spec.js: -------------------------------------------------------------------------------- 1 | var rewire = require('rewire'); 2 | var expressMock = require('./expressMock'); 3 | var httpMock = require('./httpMock'); 4 | 5 | describe('server', function() { 6 | var server, listenSpy, getSpy, useSpy, staticSpy; 7 | 8 | beforeEach(function() { 9 | listenSpy = spyOn(httpMock, 'listen'); 10 | getSpy = spyOn(expressMock, 'get'); 11 | useSpy = spyOn(expressMock, 'use'); 12 | staticSpy = spyOn(expressMock, 'static'); 13 | server = rewire('./server'); 14 | server.__set__('express', expressMock); 15 | server.__set__('http', httpMock); 16 | }); 17 | 18 | 19 | it('should have a run method', function() { 20 | expect(typeof server.run).toBe('function'); 21 | }); 22 | 23 | 24 | it('should open at port 3339 by default', function() { 25 | server.run(); 26 | expect(listenSpy).toHaveBeenCalledWith(3339); 27 | }); 28 | 29 | 30 | it('should open at specified port if added to options', function() { 31 | server.run({port: 9000}); 32 | expect(listenSpy).toHaveBeenCalledWith(9000); 33 | }); 34 | 35 | 36 | it('should redirect GET / to specified buildPath', function() { 37 | var redirectSpy = jasmine.createSpy('redirect'); 38 | getSpy.andCallThrough(); 39 | server.run({buildPath: 'foo/bar'}); 40 | expect(getSpy.calls[0].args[0]).toBe('/'); 41 | 42 | expressMock._handle('get', '/', [null, {redirect: redirectSpy}]) 43 | expect(redirectSpy).toHaveBeenCalledWith('foo/bar'); 44 | }); 45 | 46 | 47 | it('should call use with expressMock.static(./)', function() { 48 | var cwd = process.cwd(); 49 | staticSpy.andCallThrough(); 50 | server.run(); 51 | expect(staticSpy).toHaveBeenCalledWith(cwd); 52 | expect(useSpy.calls[0].args[0].toString()).toContain('(staticFn)'); 53 | }); 54 | 55 | 56 | it('should open a websocket connection', function() { 57 | 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /lib/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Big Table Benchmark 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 20 | 21 |
    22 |
    23 |
    24 |
    25 | « all benchmarks 26 |

    Benchpress

    27 |
    28 |
    29 |
    30 |
    31 | 32 | 45 | 46 | 47 |
    48 |
    49 |
    50 |
    51 | 55 | 56 |
    57 |
    58 |
    59 | 60 | 61 | 62 | 63 |
    64 |
    65 |
    66 |
    67 |

    68 | The following scripts can be overridden by adding query params in the current window 69 | where key is the script's id, and value is the path to the new script to test with. 70 |

    71 | 72 | 73 | 74 | 77 | 80 | 81 | 82 | 83 | 84 |
    75 | id to override 76 | 78 | current path 79 |
    85 |
    86 |
    87 |

    Incognito Recommended

    88 |

    89 | It's recommended to open a new Incognito Window before each test run/loop. 90 | Running in Incognito prevents user-added extensions from running, and provides more 91 | accurate data since optimizations and artifacts that the JavaScript engine may have 92 | collected from previous test runs will not be present. 93 |

    94 | 95 |

    Don't Touch Anything During Runs

    96 |

    97 | Any user activity, such as scrolling or backgrounding the browser window, could affect the performance of the code under test. Most consistent samples are collected by kicking off a test and keeping hands away from the keyboard and mouse until the test run is finished. 98 |

    99 |
    100 |
    101 |
    102 |
    103 |
    104 |
    105 |
    106 |

    Results:

    107 |
    108 |
    109 |
    110 |
    111 | test time (ms) 112 |
    113 |
    114 | gc time (ms) 115 |
    116 |
    117 | garbage (KB) 118 |
    119 |
    120 | retained memory (KB) 121 |
    122 |
    123 |
    124 |
    125 |
    126 |
    127 | 128 | 190 | 191 | 197 |
    198 |
    199 |
    200 | %%PLACEHOLDER%% 201 |
    202 | 205 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-benchpress", 3 | "version": "0.2.1", 4 | "description": "A macro benchmark runner for JavaScript Web apps", 5 | "main": "lib/bp.js", 6 | "bin": { 7 | "benchpress": "bin/benchpress" 8 | }, 9 | "scripts": { 10 | "test": "karma start" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/angular/benchpress" 15 | }, 16 | "keywords": [ 17 | "angular", 18 | "benchmark", 19 | "javascript" 20 | ], 21 | "author": "jeffbcross, jbdeboer", 22 | "license": "Apache 2", 23 | "bugs": { 24 | "url": "https://github.com/angular/benchpress/issues" 25 | }, 26 | "homepage": "https://github.com/angular/benchpress", 27 | "dependencies": { 28 | "bootstrap": "^3.2.0", 29 | "express": "^4.8.6", 30 | "minimist": "^1.1.0", 31 | "mkdirp": "^0.5.0", 32 | "rimraf": "^2.2.8", 33 | "underscore": "^1.6.0", 34 | "di": "~2.0.0-pre-9", 35 | "rx": "~2.3.20", 36 | "browserify": "~7.0.0" 37 | }, 38 | "devDependencies": { 39 | "grunt": "^0.4.5", 40 | "jasmine-node": "~1.14.5", 41 | "karma": "^0.12.21", 42 | "karma-bro": "~0.10.0", 43 | "karma-chrome-launcher": "^0.1.4", 44 | "karma-cli": "0.0.4", 45 | "karma-jasmine": "^0.1.5", 46 | "karma-ng-html2js-preprocessor": "^0.1.2", 47 | "rewire": "~2.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tasks/bp_build.js: -------------------------------------------------------------------------------- 1 | var cli = require('../lib/cli'); 2 | 3 | module.exports = function (grunt) { 4 | grunt.registerTask('bp_build', 'build benchmarks for project', function() { 5 | cli.build(this.options(), this.async()); 6 | }); 7 | }; -------------------------------------------------------------------------------- /tasks/bp_launch_chrome.js: -------------------------------------------------------------------------------- 1 | var cli = require('../lib/cli'); 2 | 3 | module.exports = function (grunt) { 4 | grunt.registerTask('bp_launch_chrome', 'launch chrome canary in incognito with flags', 5 | function() { 6 | cli.launchChrome(this.async()); 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /tasks/bp_run.js: -------------------------------------------------------------------------------- 1 | var cli = require('../lib/cli'); 2 | 3 | module.exports = function (grunt) { 4 | grunt.registerTask('bp_run', 'run a server from cwd', function() { 5 | cli.run(this.async()); 6 | }); 7 | }; -------------------------------------------------------------------------------- /test/Aggregator.spec.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var testing = require('../node_modules/di/dist/cjs/testing'); 3 | var Aggregator = require('../lib/Aggregator'); 4 | var Globals = require('../lib/Globals'); 5 | var MockGlobals = require('./MockGlobals'); 6 | var RunState = require('../lib/RunState'); 7 | var Steps = require('../lib/Steps'); 8 | 9 | describe('Aggregator', function() { 10 | var runState, aggregator; 11 | 12 | beforeEach(testing.inject(Aggregator, RunState, function(a,rs){ 13 | aggregator = a; 14 | runState = rs; 15 | })); 16 | 17 | describe('.trimSamples()', function() { 18 | it('should remove the left side of the input if longer than numSamples', function() { 19 | runState.numSamples = 3; 20 | expect(aggregator.trimSamples([0,1,2,3,4,5,6])).toEqual([4,5,6]); 21 | }); 22 | 23 | 24 | it('should return the whole list if shorter than or equal to numSamples', function() { 25 | runState.numSamples = 7; 26 | expect(aggregator.trimSamples([0,1,2,3,4,5,6])).toEqual([0,1,2,3,4,5,6]); 27 | expect(aggregator.trimSamples([0,1,2,3,4,5])).toEqual([0,1,2,3,4,5]); 28 | }); 29 | }); 30 | 31 | 32 | describe('.getTimesPerAction()', function() { 33 | it('should return the time for the action if already set', function() { 34 | aggregator.timesPerAction['foo'] = {name: 'foo'}; 35 | expect(aggregator.getTimesPerAction('foo').name).toBe('foo'); 36 | }); 37 | }); 38 | 39 | 40 | describe('.calcStats()', function() { 41 | var runState, steps; 42 | beforeEach(testing.inject(RunState, Steps, function(rs,s) { 43 | steps = s; 44 | runState = rs; 45 | steps.add({ 46 | fn: function() {}, 47 | name: 'fakeStep' 48 | }); 49 | 50 | runState.numSamples = 5; 51 | runState.iterations = 5; 52 | runState.recentResult = { 53 | fakeStep: { 54 | testTime: 5, 55 | gcTime: 2, 56 | recentGarbagePerStep: 200, 57 | recentRetainedMemoryPerStep: 100 58 | } 59 | }; 60 | 61 | aggregator.timesPerAction = { 62 | fakeStep: { 63 | testTime: { 64 | history: [3,7] 65 | }, 66 | garbageCount: { 67 | history: [50,50] 68 | }, 69 | retainedCount: { 70 | history: [25,25] 71 | }, 72 | gcTime: { 73 | recent: 3, 74 | history: [1,3] 75 | }, 76 | nextEntry: 2 77 | }, 78 | }; 79 | })); 80 | 81 | 82 | it('should return a stats object', function() { 83 | expect(typeof aggregator.calcStats().fakeStep).toBe('object'); 84 | expect(Object.keys(aggregator.calcStats())).toEqual(['fakeStep']); 85 | }); 86 | 87 | it('should set the most recent time for each step to the next entry', function() { 88 | aggregator.calcStats(); 89 | expect(aggregator.timesPerAction.fakeStep.testTime.history[2]).toBe(5); 90 | runState.recentResult.fakeStep.testTime = 25; 91 | aggregator.calcStats(); 92 | expect(aggregator.timesPerAction.fakeStep.testTime.history[3]).toBe(25); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/AutoRunner.spec.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var testing = require('../node_modules/di/dist/cjs/testing'); 3 | var AutoRunner = require('../lib/AutoRunner'); 4 | var Globals = require('../lib/Globals'); 5 | var Logger = require('../lib/Logger'); 6 | var MockGlobals = require('./MockGlobals'); 7 | var RunState = require('../lib/RunState'); 8 | var Rx = require('rx'); 9 | var Utils = require('../lib/Utils'); 10 | 11 | describe('AutoRunner', function() { 12 | var globals, autoRunner, logger, utils; 13 | beforeEach(function() { 14 | testing.use(MockGlobals).as(Globals); 15 | testing.inject(AutoRunner, Globals, Logger, RunState, Utils, function(a,g,l,rs,u){ 16 | globals = g; 17 | autoRunner = a; 18 | logger = l; 19 | runState = rs; 20 | utils = u; 21 | }); 22 | }); 23 | 24 | describe('.bootstrap', function() { 25 | it('should call runState.setDefault with query params', function() { 26 | spyOn(runState, 'setDefault'); 27 | globals._window.location.search = '?numSamples=50&iterations=100'; 28 | autoRunner.bootstrap(); 29 | expect(runState.setDefault).toHaveBeenCalledWith({ 30 | numSamples: '50', 31 | iterations: '100' 32 | }); 33 | }); 34 | 35 | 36 | it('should set autoClose on runState if __bpAutoClose__ is true in search params', function() { 37 | expect(runState.headless).toBeUndefined(); 38 | globals._window.location.search = '?__bpAutoClose__=true'; 39 | autoRunner.bootstrap(); 40 | expect(runState.headless).toBe(true); 41 | }); 42 | }); 43 | 44 | 45 | describe('.ready()', function() { 46 | it('should call runBenchmark with the current value of runState', function() { 47 | spyOn(autoRunner, 'runBenchmark'); 48 | globals._window.setTimeout = function (fn) { 49 | fn(); 50 | } 51 | autoRunner.ready(); 52 | expect(autoRunner.runBenchmark.calls[0].args[0].iterations).toBe(runState.iterations); 53 | expect(autoRunner.runBenchmark.calls[0].args[0].numSamples).toBe(runState.numSamples); 54 | }); 55 | }); 56 | 57 | 58 | describe('.runBenchmark()', function() { 59 | var subscribeSpy; 60 | 61 | beforeEach(function(){ 62 | subscribeSpy = jasmine.createSpy('subscribe'); 63 | spyOn(autoRunner, 'runAllTests').andReturn({ 64 | subscribe: subscribeSpy 65 | }); 66 | }); 67 | 68 | it('should call runAllTests()', function() { 69 | autoRunner.runBenchmark(this._runState); 70 | expect(autoRunner.runAllTests).toHaveBeenCalled(); 71 | }); 72 | 73 | 74 | it('should subscribe to the observable', function() { 75 | autoRunner.runBenchmark(this._runState); 76 | expect(subscribeSpy).toHaveBeenCalled(); 77 | }); 78 | 79 | 80 | it('should log reports to Logger when subscription sends updates', function() { 81 | spyOn(logger, 'write'); 82 | autoRunner.runBenchmark(this._runState); 83 | 84 | //Send subscription message 85 | subscribeSpy.calls[0].args[0]('new report'); 86 | expect(logger.write).toHaveBeenCalledWith('new report'); 87 | }); 88 | }); 89 | 90 | 91 | describe('.runAllTests()', function() { 92 | it('should run for the specified number of iterations', function() { 93 | var complete; 94 | runs(function(){ 95 | spyOn(autoRunner, 'runAllTests').andCallThrough(); 96 | autoRunner.runAllTests(10).subscribe(function() { 97 | }, null, function() { 98 | complete = true; 99 | }); 100 | }); 101 | 102 | waitsFor(function() { 103 | return complete; 104 | }, 'complete to be true', 1000); 105 | 106 | runs(function() { 107 | expect(autoRunner.runAllTests.callCount).toBe(11); 108 | }); 109 | }); 110 | 111 | 112 | it('should call onComplete on the subject when iterations reaches 0', function() { 113 | var onCompletedSpy = jasmine.createSpy('onCompleted'); 114 | var disposeSpy = jasmine.createSpy('dispose'); 115 | autoRunner.subject = {onCompleted: onCompletedSpy, dispose: disposeSpy}; 116 | autoRunner.runAllTests(0); 117 | expect(onCompletedSpy).toHaveBeenCalled(); 118 | expect(disposeSpy).toHaveBeenCalled(); 119 | expect(autoRunner.subject).toBeUndefined(); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/MockGlobals.js: -------------------------------------------------------------------------------- 1 | function MockGlobals () { 2 | this._window = { 3 | addEventListener: jasmine.createSpy('addEventListener'), 4 | dispatchEvent: jasmine.createSpy('dispatchEvent'), 5 | location: { 6 | search: '?variable=bindOnce&angular=foo&bar=baz' 7 | } 8 | } 9 | } 10 | 11 | module.exports = MockGlobals; -------------------------------------------------------------------------------- /test/RunState.spec.js: -------------------------------------------------------------------------------- 1 | var di = require('di'); 2 | var testing = require('../node_modules/di/dist/cjs/testing'); 3 | var Globals = require('../lib/Globals'); 4 | var MockGlobals = require('./MockGlobals'); 5 | var RunState = require('../lib/RunState'); 6 | var Utils = require('../lib/Utils'); 7 | 8 | describe('RunState', function() { 9 | var runState, report, globals, utils; 10 | 11 | beforeEach(function() { 12 | testing.use(MockGlobals).as(Globals); 13 | testing.inject(RunState, Globals, Utils, function(r,g,u) { 14 | runState = r; 15 | globals = g; 16 | utils = u; 17 | }); 18 | }); 19 | 20 | 21 | it('should set default properties', function() { 22 | expect(runState.iterations).toBe(25); 23 | expect(runState.numSamples).toBe(20); 24 | expect(runState.recentResult).toEqual({}); 25 | }); 26 | 27 | 28 | describe('.setIterations()', function() { 29 | it('should set provided arguments to runState object', function() { 30 | runState.setIterations(15); 31 | expect(runState.iterations).toBe(15); 32 | }); 33 | }); 34 | 35 | 36 | describe('.resetIterations()', function() { 37 | it('should set runState object to defaults', function() { 38 | runState.iterations = 99; 39 | runState.resetIterations(); 40 | expect(runState.iterations).toBe(25); 41 | }); 42 | }); 43 | 44 | 45 | describe('.setDefaults()', function() { 46 | beforeEach(function() { 47 | globals._window.location.search = '?iterations=10&numSamples=5'; 48 | }); 49 | 50 | 51 | it('should override defaults with defaults specified in params', function() { 52 | var queryParams = utils.parseSearch(globals._window.location.search); 53 | runState.setDefault(queryParams); 54 | expect(runState.numSamples).toBe(5); 55 | expect(runState.defaults.numSamples).toBe(5); 56 | expect(runState.iterations).toBe(10); 57 | expect(runState.defaults.iterations).toBe(10); 58 | }); 59 | 60 | 61 | it('should throw if provided non number values', function() { 62 | var queryParams = utils.parseSearch('iterations=foo'); 63 | expect(function() { 64 | runState.setDefault(queryParams) 65 | }).toThrow('iterations must be of type number, got: string'); 66 | 67 | queryParams = utils.parseSearch('numSamples=bar'); 68 | expect(function() { 69 | runState.setDefault(queryParams) 70 | }).toThrow('numSamples must be of type number, got: string'); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/bp.spec.js: -------------------------------------------------------------------------------- 1 | describe('bp', function() { 2 | var di = require('di'); 3 | var testing = require('../node_modules/di/dist/cjs/testing'); 4 | var mockStep = { 5 | fn: function() {}, 6 | name: 'fakeStep' 7 | }; 8 | 9 | var Aggregator = require('../lib/Aggregator'); 10 | var Document = require('../lib/Document'); 11 | var Globals = require('../lib/Globals'); 12 | var MockGlobals = require('./MockGlobals'); 13 | var Report = require('../lib/HtmlReport'); 14 | var Runner = require('../lib/Runner'); 15 | var ClientScripts = require('../lib/ClientScripts'); 16 | var RunState = require('../lib/RunState'); 17 | var Statistics = require('../lib/Statistics'); 18 | var Steps = require('../lib/Steps'); 19 | var Utils = require('../lib/Utils'); 20 | var Variables = require('../lib/Variables.js'); 21 | 22 | describe('.variables', function() { 23 | var var1, var2, variables; 24 | 25 | beforeEach(function() { 26 | testing.use(MockGlobals).as(Globals); 27 | testing.inject(Variables, function(vars) { 28 | variables = vars; 29 | }); 30 | 31 | var1 = {value: 'bindOnce', label: 'bind once'}; 32 | var2 = {value: 'baseline', label: 'baseline'}; 33 | }); 34 | 35 | 36 | it('should set pending selected variable from the query params', function() { 37 | expect(variables.selected).toBeUndefined(); 38 | variables.addMany([var1, var2]); 39 | expect(variables.selected).toBe(var1); 40 | }); 41 | 42 | 43 | it('should delete pending selected after selected has been set', function() { 44 | variables.variables = [{value: 'bindOnce'}]; 45 | variables.pending = 'bindOnce'; 46 | variables.select('bindOnce'); 47 | expect(variables._pending).toBe(null); 48 | }); 49 | 50 | describe('.add()', function() { 51 | it('should add the variable to the variables array', function() { 52 | variables.add(var1); 53 | expect(variables.variables[0]).toBe(var1); 54 | }); 55 | }); 56 | 57 | 58 | describe('.addMany()', function() { 59 | it('should add all variables to the variables array', function() { 60 | expect(variables.variables.length).toBe(0); 61 | 62 | var arr = [var1, var2]; 63 | 64 | variables.addMany(arr); 65 | 66 | expect(variables.variables.length).toBe(2); 67 | expect(variables.variables[0]).toBe(var1); 68 | expect(variables.variables[1]).toBe(var2); 69 | }); 70 | }); 71 | 72 | 73 | describe('.select()', function() { 74 | it('should set the variable by provided value', function() { 75 | expect(variables.selected).toBe(undefined); 76 | variables.addMany([var1, var2]); 77 | variables.select('bindOnce'); 78 | expect(variables.selected).toBe(var1); 79 | }); 80 | 81 | 82 | it('should set to undefined if value does not match a variable', function() { 83 | variables._globals._window.location.search = ''; 84 | variables.addMany([var1, var2]); 85 | expect(variables.selected).toBe(undefined); 86 | variables.select('fakeVar'); 87 | expect(variables.selected).toBe(undefined); 88 | }); 89 | }); 90 | }); 91 | 92 | 93 | describe('.statistics', function() { 94 | var statistics; 95 | beforeEach(function() { 96 | testing.use(MockGlobals).as(Globals); 97 | testing.inject(Statistics, function(stats) { 98 | statistics = stats; 99 | }); 100 | }); 101 | 102 | 103 | describe('.calculateConfidenceInterval()', function() { 104 | it('should provide the correct confidence interval', function() { 105 | expect(statistics.calculateConfidenceInterval(30, 1000)).toBe(1.859419264179007); 106 | }); 107 | }); 108 | 109 | 110 | describe('.calculateRelativeMarginOfError()', function() { 111 | it('should provide the correct margin of error', function() { 112 | expect(statistics.calculateRelativeMarginOfError(1.85, 5)).toBe(0.37); 113 | }); 114 | }); 115 | 116 | 117 | describe('.getMean()', function() { 118 | it('should return the mean for a given sample', function() { 119 | expect(statistics.getMean([1,2,5,4])).toBe(3); 120 | }); 121 | }); 122 | 123 | 124 | describe('.calculateStandardDeviation()', function() { 125 | it('should provide the correct standardDeviation for the provided sample and mean', function() { 126 | expect(statistics.calculateStandardDeviation([ 127 | 2,4,4,4,5,5,7,9 128 | ], 5)).toBe(2.138089935299395); 129 | }); 130 | 131 | 132 | it('should provide the correct standardDeviation for the provided sample and mean', function() { 133 | expect(statistics.calculateStandardDeviation([ 134 | 674.64,701.78,668.33,662.15,663.34,677.32,664.25,1233.00,1100.80,716.15,681.52,671.23,702.70,686.89,939.39,830.28,695.46,695.66,675.15,667.48], 750.38)).toBe(158.57877026559186); 135 | }); 136 | }); 137 | 138 | 139 | describe('.calculateCoefficientOfVariation()', function() { 140 | it('should calculate the correct coefficient of variation', function() { 141 | expect(statistics.calculateCoefficientOfVariation(0.5, 5)).toBe(0.1); 142 | }); 143 | }); 144 | }); 145 | 146 | 147 | describe('.utils', function() { 148 | var utils; 149 | beforeEach(function() { 150 | testing.use(MockGlobals).as(Globals); 151 | testing.inject(Utils, function(u) { 152 | utils = u; 153 | }); 154 | }); 155 | 156 | describe('.parseSearch()', function() { 157 | it('should return serialized query params', function() { 158 | expect(utils.parseSearch('?variable=bindOnce&angular=angular.js')).toEqual({ 159 | variable: 'bindOnce', 160 | angular: 'angular.js' 161 | }); 162 | }); 163 | 164 | 165 | it('should only remove leading character if "?" present', function() { 166 | expect(utils.parseSearch('?foo=bar&b=a')).toEqual({ 167 | foo:'bar', 168 | b: 'a' 169 | }); 170 | expect(utils.parseSearch('foo=bar&b=a')).toEqual({ 171 | foo:'bar', 172 | b: 'a' 173 | }); 174 | }); 175 | }); 176 | }); 177 | 178 | 179 | describe('.document', function() { 180 | var doc, scripts, utils, runState; 181 | 182 | beforeEach(function() { 183 | testing.use(MockGlobals).as(Globals); 184 | testing.inject(Document, ClientScripts, Utils, RunState, function(d, s, u, rs) { 185 | doc = d; 186 | doc._container = document.createElement('div'); 187 | var scriptsTemplate = document.createElement('div') 188 | scriptsTemplate.setAttribute('id', 'scriptTemplate'); 189 | doc._container.appendChild(scriptsTemplate); 190 | scripts = s; 191 | utils = u; 192 | runState = rs; 193 | }); 194 | }); 195 | 196 | 197 | describe('.observeBtns()', function() { 198 | it('should return an observable', function() { 199 | expect(typeof doc.observeBtns().filter).toBe('function'); 200 | }); 201 | }); 202 | 203 | 204 | describe('.onSampleRangeChanged()', function() { 205 | beforeEach(testing.inject(Runner, function(runner) { 206 | runState.resetIterations(); 207 | })); 208 | 209 | 210 | it('should change the numSamples property', function() { 211 | expect(runState.numSamples).toBe(20); 212 | doc.onSampleInputChanged({target: {value: '80'}}); 213 | expect(runState.numSamples).toBe(80); 214 | }); 215 | }); 216 | 217 | 218 | describe('.writeReport()', function() { 219 | it('should write the report to the infoDiv', function() { 220 | doc.infoDiv = document.createElement('div'); 221 | doc.writeReport('report!'); 222 | expect(doc.infoDiv.innerHTML).toBe('report!') 223 | }); 224 | }); 225 | 226 | 227 | describe('.onDOMContentLoaded()', function() { 228 | it('should call methods to write to the dom', function() { 229 | var rangeSpy = spyOn(doc, 'addSampleRange'); 230 | var infoSpy = spyOn(doc, 'addInfo'); 231 | 232 | doc.onDOMContentLoaded(); 233 | expect(rangeSpy).toHaveBeenCalled(); 234 | expect(infoSpy).toHaveBeenCalled(); 235 | }); 236 | }); 237 | 238 | 239 | describe('.loadNextScript()', function() { 240 | beforeEach(function() { 241 | scripts.addMany([{src: 'angular.js', id: 'angular'}, {src: 'bar'}]); 242 | }); 243 | 244 | it('should shift the first config from window.scripts', function() { 245 | doc.loadNextScript(); 246 | expect(scripts.all()).toEqual([{src: 'bar'}]); 247 | }); 248 | 249 | 250 | it('should override script with provided source from query params', function() { 251 | var bodySpy = spyOn(document.body, 'appendChild'); 252 | doc.loadNextScript(); 253 | expect(bodySpy.calls[0].args[0].getAttribute('src')).toBe('foo'); 254 | }); 255 | 256 | 257 | it('should call addScriptToUI with config with correct src', function() { 258 | var spy = spyOn(doc, 'addScriptToUI'); 259 | doc.loadNextScript(); 260 | expect(spy).toHaveBeenCalled(); 261 | expect(spy.calls[0].args[0]).toEqual({id: 'angular', src: 'foo'}); 262 | }) 263 | }); 264 | 265 | 266 | xdescribe('.addScriptToUI()', function() { 267 | beforeEach(function() { 268 | doc._scriptsContainer = document.createElement('div'); 269 | doc._scriptsContainer.classList.add('scripts'); 270 | var scriptTemplate = document.createElement('script'); 271 | scriptTemplate.setAttribute('id', 'scriptTemplate'); 272 | doc._container.appendChild(scriptTemplate); 273 | }); 274 | 275 | 276 | it('should add a script to the info container', function() { 277 | var appendSpy = spyOn(doc._scriptsContainer, 'appendChild'); 278 | doc.addScriptToUI({src: '/foo.js', id: 'foo'}); 279 | expect(appendSpy).toHaveBeenCalled(); 280 | }); 281 | }); 282 | 283 | 284 | xdescribe('.addScriptsContainer()', function() { 285 | it('should set the container to doc._scriptsContainer'); 286 | it('should add script template to doc'); 287 | }); 288 | 289 | 290 | describe('.getParams()', function() { 291 | it('should call utils.parseSearch()', function() { 292 | var spy = spyOn(utils, 'parseSearch'); 293 | doc.getParams(); 294 | expect(spy).toHaveBeenCalled(); 295 | }); 296 | 297 | it('should parse query params into an object', function() { 298 | expect(doc.getParams()).toEqual({ 299 | variable: 'bindOnce', 300 | angular: 'foo', 301 | bar: 'baz' 302 | }); 303 | }) 304 | }); 305 | }); 306 | 307 | 308 | describe('.runner', function() { 309 | var runner, report, doc, globals, runState; 310 | 311 | beforeEach(function() { 312 | testing.use(MockGlobals).as(Globals); 313 | testing.inject(Document, Globals, Report, Runner, RunState, function(d,g,r,run,rs) { 314 | doc = d; 315 | globals = g; 316 | report = r; 317 | runner = run; 318 | runState = rs; 319 | 320 | runState.numSamples = 99; 321 | runState.iterations = 100; 322 | runState.recentResult = { 323 | fakeStep: { 324 | testTime: 2 325 | } 326 | }; 327 | }); 328 | 329 | doc.infoDiv = document.createElement('div'); 330 | }); 331 | 332 | 333 | describe('.runTimedTest()', function() { 334 | it('should call gc if available', function() { 335 | globals._window.gc = function() {}; 336 | var spy = spyOn(globals._window, 'gc'); 337 | runner.runTimedTest(mockStep, {}); 338 | expect(spy).toHaveBeenCalled(); 339 | }); 340 | 341 | 342 | it('should return the time required to run the test', function() { 343 | var times = {}; 344 | expect(typeof runner.runTimedTest(mockStep, times).testTime).toBe('number'); 345 | }); 346 | }); 347 | 348 | 349 | describe('.bootstrap()', function() { 350 | it('should subscribe to button clicks from document', function() { 351 | var docBtnSpy = spyOn(doc, 'observeBtns').andCallThrough(); 352 | runner.bootstrap(); 353 | expect(docBtnSpy).toHaveBeenCalled(); 354 | expect(typeof runner._btnSubscription).toBe('object'); 355 | }); 356 | }); 357 | 358 | 359 | describe('.onBtnClick()', function() { 360 | var sampleEvent; 361 | beforeEach(function() { 362 | sampleEvent = { 363 | target: { 364 | textContent: 'Loop' 365 | } 366 | } 367 | }); 368 | 369 | it('throw an error if the button text does not match anything', function() { 370 | expect(runner.onBtnClick).toThrow('Could not find handler'); 371 | }); 372 | 373 | 374 | it('should call the correct benchmark runner', function() { 375 | var loopSpy = spyOn(runner, 'loopBenchmark'); 376 | var onceSpy = spyOn(runner, 'onceBenchmark'); 377 | var twentyFiveSpy = spyOn(runner, 'twentyFiveBenchmark'); 378 | var profileSpy = spyOn(runner, 'profile'); 379 | expect(loopSpy.callCount).toBe(0); 380 | expect(onceSpy.callCount).toBe(0); 381 | expect(twentyFiveSpy.callCount).toBe(0); 382 | expect(profileSpy.callCount).toBe(0); 383 | 384 | runner.onBtnClick(sampleEvent); 385 | expect(loopSpy.callCount).toBe(1); 386 | 387 | sampleEvent.target.textContent = 'Once'; 388 | runner.onBtnClick(sampleEvent); 389 | expect(onceSpy.callCount).toBe(1); 390 | 391 | sampleEvent.target.textContent = 'Loop 25x'; 392 | runner.onBtnClick(sampleEvent); 393 | expect(twentyFiveSpy.callCount).toBe(1); 394 | 395 | sampleEvent.target.textContent = 'Profile'; 396 | runner.onBtnClick(sampleEvent); 397 | expect(profileSpy.callCount).toBe(1); 398 | }) 399 | }); 400 | 401 | 402 | describe('.runAllTests()', function() { 403 | beforeEach(testing.inject(Steps, Document, function(steps, doc) { 404 | steps.add(mockStep); 405 | doc.infoTemplate = jasmine.createSpy('infoTemplate'); 406 | })); 407 | 408 | it('should call resetIterations before calling done', function() { 409 | var spy = spyOn(runState, 'resetIterations'); 410 | runState.iterations = 0; 411 | runner.runAllTests(); 412 | expect(spy).toHaveBeenCalled(); 413 | }); 414 | 415 | 416 | it('should call done after running for the appropriate number of iterations', function() { 417 | var spy = spyOn(mockStep, 'fn'); 418 | var doneSpy = jasmine.createSpy('done'); 419 | 420 | runs(function() { 421 | runState.setIterations(5, 5); 422 | runner.runAllTests(doneSpy); 423 | }); 424 | 425 | waitsFor(function() { 426 | return doneSpy.callCount; 427 | }, 'done to be called', 500); 428 | 429 | runs(function() { 430 | expect(spy.callCount).toBe(5); 431 | }); 432 | }); 433 | }); 434 | 435 | 436 | describe('.loopBenchmark()', function() { 437 | var runAllTestsSpy, btn; 438 | beforeEach(function() { 439 | runAllTestsSpy = spyOn(runner, 'runAllTests'); 440 | doc.loopBtn = document.createElement('button'); 441 | }); 442 | 443 | it('should call runAllTests if iterations does not start at greater than -1', function() { 444 | runState.iterations = 0; 445 | runner.loopBenchmark(); 446 | expect(runAllTestsSpy).toHaveBeenCalled(); 447 | expect(runAllTestsSpy.callCount).toBe(1); 448 | }); 449 | 450 | 451 | it('should set the button text to "Pause" while iterating', function() { 452 | runState.iterations = 0; 453 | runner.loopBenchmark(); 454 | expect(doc.loopBtn.innerText).toBe('Pause'); 455 | }); 456 | 457 | 458 | it('should set the runState -1 iterations', function() { 459 | var spy = spyOn(runState, 'setIterations'); 460 | runState.iterations = 0; 461 | runner.loopBenchmark(); 462 | expect(spy).toHaveBeenCalledWith(-1); 463 | }); 464 | }); 465 | 466 | 467 | describe('.onceBenchmark()', function() { 468 | var runAllTestsSpy; 469 | beforeEach(function() { 470 | doc.onceBtn = document.createElement('button'); 471 | runAllTestsSpy = spyOn(runner, 'runAllTests'); 472 | }); 473 | 474 | it('should call runAllTests', function() { 475 | expect(runAllTestsSpy.callCount).toBe(0); 476 | runner.onceBenchmark(); 477 | expect(runAllTestsSpy).toHaveBeenCalled(); 478 | }); 479 | 480 | 481 | it('should set the button text to "..."', function() { 482 | expect(runAllTestsSpy.callCount).toBe(0); 483 | runner.onceBenchmark(); 484 | }); 485 | }); 486 | 487 | 488 | describe('.twentyFiveBenchmark()', function() { 489 | var runAllTestsSpy; 490 | beforeEach(function() { 491 | doc.twentyFiveBtn = document.createElement('button'); 492 | runAllTestsSpy = spyOn(runner, 'runAllTests'); 493 | }); 494 | 495 | 496 | it('should set the runState to 25 iterations', function() { 497 | var spy = spyOn(runState, 'setIterations'); 498 | runner.twentyFiveBenchmark(); 499 | expect(spy).toHaveBeenCalledWith(25); 500 | }); 501 | 502 | 503 | it('should call runAllTests', function() { 504 | runner.twentyFiveBenchmark(); 505 | expect(runAllTestsSpy).toHaveBeenCalled(); 506 | }); 507 | 508 | 509 | it('should pass runAllTests a third argument specifying times to ignore', function() { 510 | runner.twentyFiveBenchmark(); 511 | expect(runAllTestsSpy.calls[0].args[1]).toBe(5); 512 | }); 513 | }); 514 | }); 515 | }); -------------------------------------------------------------------------------- /test_node.sh: -------------------------------------------------------------------------------- 1 | ./node_modules/.bin/jasmine-node lib/node/*.js --autotest --color 2 | --------------------------------------------------------------------------------