qunited 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/Gemfile +4 -0
- data/Rakefile +9 -0
- data/bin/qunited +23 -0
- data/lib/qunited/js_runner/base.rb +34 -0
- data/lib/qunited/js_runner/rhino/js/env.rhino.js +14006 -0
- data/lib/qunited/js_runner/rhino/js/js.jar +0 -0
- data/lib/qunited/js_runner/rhino/js/qunit-runner.js +168 -0
- data/lib/qunited/js_runner/rhino/js/qunit.js +1838 -0
- data/lib/qunited/js_runner/rhino/js/yaml.js +22 -0
- data/lib/qunited/js_runner/rhino.rb +65 -0
- data/lib/qunited/js_runner.rb +2 -0
- data/lib/qunited/rake_task.rb +87 -0
- data/lib/qunited/results.rb +164 -0
- data/lib/qunited/runner.rb +23 -0
- data/lib/qunited/version.rb +3 -0
- data/lib/qunited.rb +4 -0
- data/qunited.gemspec +20 -0
- data/test/fixtures/basic_project/app/assets/javascripts/application.js +6 -0
- data/test/fixtures/basic_project/test/javascripts/test_basics.js +6 -0
- data/test/fixtures/basic_project/test/javascripts/test_math.js +12 -0
- data/test/fixtures/dom_project/app/assets/javascripts/application.js +6 -0
- data/test/fixtures/dom_project/test/javascripts/test_misc.js +5 -0
- data/test/fixtures/errors_project/app/assets/javascripts/no_error.js +5 -0
- data/test/fixtures/errors_project/app/assets/javascripts/syntax_error.js +7 -0
- data/test/fixtures/errors_project/app/assets/javascripts/undefined_error.js +9 -0
- data/test/fixtures/errors_project/test/javascripts/this_test_has_no_errors_in_it.js +5 -0
- data/test/fixtures/errors_project/test/javascripts/this_test_has_no_tests.js +4 -0
- data/test/fixtures/errors_project/test/javascripts/this_test_has_syntax_error.js +10 -0
- data/test/fixtures/errors_project/test/javascripts/this_test_has_undefined_error.js +10 -0
- data/test/fixtures/failures_project/app/assets/javascripts/application.js +5 -0
- data/test/fixtures/failures_project/test/javascripts/test_basics.js +11 -0
- data/test/fixtures/failures_project/test/javascripts/test_math.js +12 -0
- data/test/test_helper.rb +5 -0
- data/test/unit/test_results.rb +338 -0
- data/test/unit/test_rhino_runner.rb +114 -0
- metadata +100 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2010 Jeremy Faivre
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished
|
9
|
+
to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
21
|
+
*/
|
22
|
+
var Yaml=function(){};Yaml.prototype={spec:"1.2",setSpecVersion:function(a){if(a!="1.1"&&a!="1.2"){throw new InvalidArgumentException("Version "+a+" of the YAML specifications is not supported")}this.spec=a},getSpecVersion:function(){return this.spec},loadFile:function(a,b){if(b==undefined){input=this.getFileContents(a);return this.load(input)}this.getFileContents(a,function(c){b(new Yaml().load(c))})},load:function(a){var c=new YamlParser();var b=null;try{b=c.parse(a)}catch(d){if(d.name!=undefined&&d.name.toString=="TypeError"){throw d}throw"Syntax error: "+d.message}return b},dump:function(b,a){if(a==undefined){a=2}yaml=new YamlDumper();return yaml.dump(b,a)},getXHR:function(){if(window.XMLHttpRequest){return new XMLHttpRequest()}if(window.ActiveXObject){var c=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(var a=0;a<4;a++){try{return new ActiveXObject(c[a])}catch(b){}}}return null},getFileContents:function(a,c){var b=this.getXHR();if(c==undefined){b.open("GET",a,false);b.send(null);if(b.status==200||b.status==0){return b.responseText}return null}b.onreadystatechange=function(){if(b.readyState==4){if(b.status==200||b.status==0){c(b.responseText)}else{c(null)}}};b.open("GET",a,true);b.send(null)}};var YAML={encode:function(a){return new Yaml().dump(a)},decode:function(a){return new Yaml().load(a)},load:function(a,b){return new Yaml().loadFile(a,b)}};if(typeof(InvalidArgumentException)=="undefined"){InvalidArgumentException=function(a){this.name="InvalidArgumentException";this.message=a}};var YamlInline=function(){};YamlInline.prototype={i:null,load:function(b){var a=null;b=this.trim(b);if(0==b.length){return""}switch(b.charAt(0)){case"[":a=this.parseSequence(b);break;case"{":a=this.parseMapping(b);break;default:a=this.parseScalar(b)}return a},dump:function(d){var b;var a;var c=new Yaml();if("1.1"==c.getSpecVersion()){b=["true","on","+","yes","y"];a=["false","off","-","no","n"]}else{b=["true"];a=["false"]}if(typeof(d)=="object"&&null!=d){return this.dumpObject(d)}if(undefined==d||null==d){return"null"}if(typeof(d)=="boolean"){return d?"true":"false"}if(/^\d+/.test(d)){return typeof(d)=="string"?"'"+d+"'":parseInt(d)}if(this.isNumeric(d)){return typeof(d)=="string"?"'"+d+"'":parseFloat(d)}if(typeof(d)=="number"){return d==Infinity?".Inf":(d==-Infinity?"-.Inf":(isNaN(d)?".NAN":d))}if((d+"").indexOf("\n")!=-1||(d+"").indexOf("\r")!=-1){return'"'+d.split('"').join('\\"').split("\n").join("\\n").split("\r").join("\\r")+'"'}if((/[\s\'"\:\{\}\[\],&\*\#\?]/.test(d))||(/^[-?|<>=!%@`]/.test(d))){return"'"+d.split("'").join("''")+"'"}if(""==d){return"''"}if(this.getTimestampRegex().test(d)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),b)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),a)){return"'"+d+"'"}if(this.inArray(d.toLowerCase(),["null","~"])){return"'"+d+"'"}return d},dumpObject:function(e){var d=this.getKeys(e);var b=null;var c;var a=d.length;if(e instanceof Array){b=[];for(c=0;c<a;c++){b.push(this.dump(e[d[c]]))}return"["+b.join(", ")+"]"}b=[];for(c=0;c<a;c++){b.push(this.dump(d[c])+": "+this.dump(e[d[c]]))}return"{ "+b.join(", ")+" }"},parseScalar:function(b,g,e,d,f){if(g==undefined){g=null}if(e==undefined){e=['"',"'"]}if(d==undefined){d=0}if(f==undefined){f=true}var a=null;var h=null;var c=null;if(this.inArray(b[d],e)){a=this.parseQuotedScalar(b,d);d=this.i}else{if(!g){a=(b+"").substring(d);d+=a.length;h=a.indexOf(" #");if(h!=-1){a=a.substr(0,h).replace(/\s+$/g,"")}}else{if(c=new RegExp("^(.+?)("+g.join("|")+")").exec((b+"").substring(d))){a=c[1];d+=a.length}else{throw new InvalidArgumentException("Malformed inline YAML string ("+b+").")}}a=f?this.evaluateScalar(a):a}this.i=d;return a},parseQuotedScalar:function(b,d){var c=null;if(!(c=new RegExp("^"+YamlInline.REGEX_QUOTED_STRING).exec((b+"").substring(d)))){throw new InvalidArgumentException("Malformed inline YAML string ("+(b+"").substring(d)+").")}var a=c[0].substr(1,c[0].length-2);if('"'==(b+"").charAt(d)){a=a.split('\\"').join('"').split("\\n").join("\n").split("\\r").join("\r")}else{a=a.split("''").join("'")}d+=c[0].length;this.i=d;return a},parseSequence:function(g,c){if(c==undefined){c=0}var b=[];var a=g.length;c+=1;while(c<a){switch(g.charAt(c)){case"[":b.push(this.parseSequence(g,c));c=this.i;break;case"{":b.push(this.parseMapping(g,c));c=this.i;break;case"]":this.i=c;return b;case",":case" ":break;default:isQuoted=this.inArray(g.charAt(c),['"',"'"]);var d=this.parseScalar(g,[",","]"],['"',"'"],c);c=this.i;if(!isQuoted&&(d+"").indexOf(": ")!=-1){try{d=this.parseMapping("{"+d+"}")}catch(f){if(!(f instanceof InvalidArgumentException)){throw f}}}b.push(d);c--}c++}throw new InvalidArgumentException("Malformed inline YAML string "+g)},parseMapping:function(d,f){if(f==undefined){f=0}var c={};var a=d.length;f+=1;var b=false;var g=false;while(f<a){g=false;switch(d.charAt(f)){case" ":case",":f++;g=true;break;case"}":this.i=f;return c}if(g){continue}var e=this.parseScalar(d,[":"," "],['"',"'"],f,false);f=this.i;b=false;while(f<a){switch(d.charAt(f)){case"[":c[e]=this.parseSequence(d,f);f=this.i;b=true;break;case"{":c[e]=this.parseMapping(d,f);f=this.i;b=true;break;case":":case" ":break;default:c[e]=this.parseScalar(d,[",","}"],['"',"'"],f);f=this.i;b=true;f--}++f;if(b){g=true;break}}if(g){continue}}throw new InvalidArgumentException("Malformed inline YAML string "+d)},evaluateScalar:function(b){b=this.trim(b);var e;var d;var f=new Yaml();if("1.1"==f.getSpecVersion()){e=["true","on","+","yes","y"];d=["false","off","-","no","n"]}else{e=["true"];d=["false"]}var c=null;var a=null;if(("null"==b.toLowerCase())||(""==b)||("~"==b)){return null}if((b+"").indexOf("!str")!=-1){return(""+b).substring(5)}if((b+"").indexOf("! ")!=-1){return parseInt(this.parseScalar((b+"").substring(2)))}if(/^\d+/.test(b)){c=b;a=parseInt(b);return"0"==b.charAt(0)?this.octdec(b):((""+c==""+a)?a:c)}if(this.inArray(b.toLowerCase(),e)){return true}if(this.inArray(b.toLowerCase(),d)){return false}if(this.isNumeric(b)){return"0x"==(b+"").substr(0,2)?hexdec($scalar):floatval($scalar)}if(b.toLowerCase()==".inf"){return Infinity}if(b.toLowerCase()==".nan"){return NaN}if(b.toLowerCase()=="-.inf"){return -Infinity}if(/^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(b)){return parseFloat(b.split(",").join(""))}if(this.getTimestampRegex().test(b)){return this.strtodate(b)}return""+b},getTimestampRegex:function(){return new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:.([0-9]*))?(?:[ \t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?)?$","gi")},trim:function(a){return(a+"").replace(/^\s+/,"").replace(/\s+$/,"")},isNumeric:function(a){return(a-0)==a&&a.length>0&&a.replace(/\s+/g,"")!=""},inArray:function(c,d){var b;var a=d.length;for(b=0;b<a;b++){if(c==d[b]){return true}}return false},getKeys:function(c){var b=[];for(var a in c){if(c.hasOwnProperty(a)){b.push(a)}}return b},octdec:function(a){return parseInt((a+"").replace(/[^0-7]/gi,""),8)},hexdec:function(a){a=this.trim(a);if((a+"").substr(0,2)=="0x"){a=(a+"").substring(2)}return parseInt((a+"").replace(/[^a-f0-9]/gi,""),16)},strtodate:function(a){var b=new Date();b.setTime(this.strtotime(a,new Date().getTime()));return b},strtotime:function(o,t){var q,p,i,m="",s="";m=o;m=m.replace(/\s{2,}|^\s|\s$/g," ");m=m.replace(/[\t\r\n]/g,"");if(m=="now"){return(new Date()).getTime()/1000}else{if(!isNaN(s=Date.parse(m))){return(s/1000)}else{if(t){t=new Date(t*1000)}else{t=new Date()}}}m=m.toLowerCase();var r={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:{jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}};var v=this.strtotime;var u=function(a){var c=(a[2]&&a[2]=="ago");var d=(d=a[0]=="last"?-1:1)*(c?-1:1);switch(a[0]){case"last":case"next":switch(a[1].substring(0,3)){case"yea":t.setFullYear(t.getFullYear()+d);break;case"mon":t.setMonth(t.getMonth()+d);break;case"wee":t.setDate(t.getDate()+(d*7));break;case"day":t.setDate(t.getDate()+d);break;case"hou":t.setHours(t.getHours()+d);break;case"min":t.setMinutes(t.getMinutes()+d);break;case"sec":t.setSeconds(t.getSeconds()+d);break;default:var e;if(typeof(e=r.day[a[1].substring(0,3)])!="undefined"){var b=e-t.getDay();if(b==0){b=7*d}else{if(b>0){if(a[0]=="last"){b-=7}}else{if(a[0]=="next"){b+=7}}}t.setDate(t.getDate()+b)}}break;default:if(/\d+/.test(a[0])){d*=parseInt(a[0],10);switch(a[1].substring(0,3)){case"yea":t.setFullYear(t.getFullYear()+d);break;case"mon":t.setMonth(t.getMonth()+d);break;case"wee":t.setDate(t.getDate()+(d*7));break;case"day":t.setDate(t.getDate()+d);break;case"hou":t.setHours(t.getHours()+d);break;case"min":t.setMinutes(t.getMinutes()+d);break;case"sec":t.setSeconds(t.getSeconds()+d);break}}else{return false}break}return true};p=m.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(p!=null){if(!p[2]){p[2]="00:00:00"}else{if(!p[3]){p[2]+=":00"}}i=p[1].split(/-/g);for(q in r.mon){if(r.mon[q]==i[1]-1){i[1]=q}}i[0]=parseInt(i[0],10);i[0]=(i[0]>=0&&i[0]<=69)?"20"+(i[0]<10?"0"+i[0]:i[0]+""):(i[0]>=70&&i[0]<=99)?"19"+i[0]:i[0]+"";return parseInt(v(i[2]+" "+i[1]+" "+i[0]+" "+p[2])+(p[4]?p[4]/1000:""),10)}var n="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";p=m.match(new RegExp(n,"gi"));if(p==null){return false}for(q=0;q<p.length;q++){if(!u(p[q].split(" "))){return false}}return(t.getTime()/1000)}};YamlInline.REGEX_QUOTED_STRING="(?:\"(?:[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|'(?:[^']*(?:''[^']*)*)')";var YamlParser=function(a){this.offset=this.isDefined(a)?a:0};YamlParser.prototype={offset:0,lines:[],currentLineNb:-1,currentLine:"",refs:{},parse:function(m){this.currentLineNb=-1;this.currentLine="";this.lines=this.cleanup(m).split("\n");var u=null;while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){continue}if(/^\t+/.test(this.currentLine)){throw new InvalidArgumentException("A YAML file cannot contain tabs as indentation at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}var j=false;var r=false;var q=false;var b=null;var a=null;var t=null;var d=null;var e=null;var v=null;var h=null;var p=null;var f=null;if(b=/^\-((\s+)(.+?))?\s*$/.exec(this.currentLine)){if(!this.isDefined(u)){u=[]}if(!(u instanceof Array)){throw new InvalidArgumentException("Non array entry at line "+(this.getRealCurrentLineNb()+1)+".")}b={leadspaces:b[2],value:b[3]};if(this.isDefined(b.value)&&(a=/^&([^ ]+) *(.*)/.exec(b.value))){a={ref:a[1],value:a[2]};j=a.ref;b.value=a.value}if(!this.isDefined(b.value)||""==b.value.split(" ").join("")||this.trim(b.value).charAt(0)=="#"){t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;u.push(d.parse(this.getNextEmbedBlock()));this.refs=d.refs}else{if(this.isDefined(b.leadspaces)&&" "==b.leadspaces&&(a=new RegExp("^("+YamlInline.REGEX_QUOTED_STRING+"|[^ '\"{].*?) *:(\\s+(.+?))?\\s*$").exec(b.value))){a={key:a[1],value:a[3]};t=this.getRealCurrentLineNb();d=new YamlParser(t);d.refs=this.refs;e=b.value;if(!this.isNextLineIndented()){e+="\n"+this.getNextEmbedBlock(this.getCurrentLineIndentation()+2)}u.push(d.parse(e));this.refs=d.refs}else{u.push(this.parseValue(b.value))}}}else{if(b=new RegExp("^("+YamlInline.REGEX_QUOTED_STRING+"|[^ '\"].*?) *:(\\s+(.+?))?\\s*$").exec(this.currentLine)){if(!this.isDefined(u)){u={}}if(u instanceof Array){throw new InvalidArgumentException("Non mapped entry at line "+(this.getRealCurrentLineNb()+1)+".")}b={key:b[1],value:b[3]};v=(new YamlInline()).parseScalar(b.key);if("<<"==v){if(this.isDefined(b.value)&&"*"==(b.value+"").charAt(0)){r=b.value.substring(1)}else{if(this.isDefined(b.value)&&b.value!=""){m=b.value}else{m=this.getNextEmbedBlock()}t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;h=d.parse(m);this.refs=d.refs;var s=[];if(!this.isObject(h)){throw new InvalidArgumentException("YAML merge keys used with a scalar value instead of an array at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}else{if(this.isDefined(h[0])){f=this.reverseArray(h);p=f.length;for(var o=0;o<p;o++){var l=f[o];if(!this.isObject(f[o])){throw new InvalidArgumentException("Merge items must be arrays at line "+(this.getRealCurrentLineNb()+1)+" ("+f[o]+").")}s=this.mergeObject(f[o],s)}}else{s=this.mergeObject(s,h)}}q=s}}else{if(this.isDefined(b.value)&&(a=/^&([^ ]+) *(.*)/.exec(b.value))){a={ref:a[1],value:a[2]};j=a.ref;b.value=a.value}}if(q){u=q}else{if(!this.isDefined(b.value)||""==b.value.split(" ").join("")||this.trim(b.value).charAt(0)=="#"){if(this.isNextLineIndented()){u[v]=null}else{t=this.getRealCurrentLineNb()+1;d=new YamlParser(t);d.refs=this.refs;u[v]=d.parse(this.getNextEmbedBlock());this.refs=d.refs}}else{if(r){u=this.refs[r]}else{u[v]=this.parseValue(b.value)}}}}else{if(2==this.lines.length&&this.isEmpty(this.lines[1])){m=(new YamlInline()).load(this.lines[0]);if(this.isObject(m)){first=m[0];if("*"==(first+"").substr(0,1)){u=[];p=m.length;for(var o=0;o<p;o++){u.push(this.refs[m[o].substring(1)])}m=u}}return m}throw new InvalidArgumentException('"'+this.currentLine+'" at line '+(this.getRealCurrentLineNb()+1))}}if(j){if(u instanceof Array){this.refs[j]=u[u.length-1]}else{var g=null;for(var n in u){if(u.hasOwnProperty(n)){g=n}}this.refs[j]=u[n]}}}return this.isEmpty(u)?null:u},getRealCurrentLineNb:function(){return this.currentLineNb+this.offset},getCurrentLineIndentation:function(){return this.currentLine.length-this.currentLine.replace(/^ +/g,"").length},getNextEmbedBlock:function(d){this.moveToNextLine();var b=null;var a=null;if(!this.isDefined(d)){b=this.getCurrentLineIndentation();if(!this.isCurrentLineEmpty()&&0==b){throw new InvalidArgumentException("A Indentation problem at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}}else{b=d}var e=[this.currentLine.substring(b)];while(this.moveToNextLine()){if(this.isCurrentLineEmpty()){if(this.isCurrentLineBlank()){e.push(this.currentLine.substring(b))}continue}a=this.getCurrentLineIndentation();var c;if(c=/^( *)$/.exec(this.currentLine)){e.push(c[1])}else{if(a>=b){e.push(this.currentLine.substring(b))}else{if(0==a){this.moveToPreviousLine();break}else{throw new InvalidArgumentException("B Indentation problem at line "+(this.getRealCurrentLineNb()+1)+" ("+this.currentLine+")")}}}}return e.join("\n")},moveToNextLine:function(){if(this.currentLineNb>=this.lines.length-1){return false}this.currentLineNb++;this.currentLine=this.lines[this.currentLineNb];return true},moveToPreviousLine:function(){this.currentLineNb--;this.currentLine=this.lines[this.currentLineNb]},parseValue:function(c){if("*"==(c+"").charAt(0)){if(this.trim(c).charAt(0)=="#"){c=(c+"").substr(1,c.indexOf("#")-2)}else{c=(c+"").substring(1)}if(this.refs[c]==undefined){throw new InvalidArgumentException('Reference "'+c+'" does not exist ('+this.currentLine+").")}return this.refs[c]}var b=null;if(b=/^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(c)){b={separator:b[1],modifiers:b[2],comments:b[3]};var a=this.isDefined(b.modifiers)?b.modifiers:"";return this.parseFoldedScalar(b.separator,a.replace(/\d+/g,""),Math.abs(parseInt(a)))}else{return(new YamlInline()).load(c)}},parseFoldedScalar:function(c,h,f){if(h==undefined){h=""}if(f==undefined){f=0}c="|"==c?"\n":" ";var j="";var g=null;var b=this.moveToNextLine();while(b&&this.isCurrentLineBlank()){j+="\n";b=this.moveToNextLine()}if(!b){return""}var d=null;if(!(d=new RegExp("^("+(f?this.strRepeat(" ",f):" +")+")(.*)$").exec(this.currentLine))){this.moveToPreviousLine();return""}d={indent:d[1],text:d[2]};var a=d.indent;var e=0;j+=d.text+c;while(this.currentLineNb+1<this.lines.length){this.moveToNextLine();if(d=new RegExp("^( {"+a.length+",})(.+)$").exec(this.currentLine)){d={indent:d[1],text:d[2]};if(" "==c&&e!=d.indent){j=j.substr(0,j.length-1)+"\n"}e=d.indent;g=d.indent.length-a.length;j+=this.strRepeat(" ",g)+d.text+(g!=0?"\n":c)}else{if(d=/^( *)$/.exec(this.currentLine)){j+=d[1].replace(new RegExp("^ {1,"+a.length+"}","g"),"",d[1])+"\n"}else{this.moveToPreviousLine();break}}}if(" "==c){j=j.replace(/ (\n*)$/g,"\n$1")}switch(h){case"":j=j.replace(/\n+$/g,"\n");break;case"+":break;case"-":j=j.replace(/\n+$/g,"");break}return j},isNextLineIndented:function(){var b=this.getCurrentLineIndentation();var c=this.moveToNextLine();while(c&&this.isCurrentLineEmpty()){c=this.moveToNextLine()}if(false==c){return false}var a=false;if(this.getCurrentLineIndentation()<=b){a=true}this.moveToPreviousLine();return a},isCurrentLineEmpty:function(){return this.isCurrentLineBlank()||this.isCurrentLineComment()},isCurrentLineBlank:function(){return""==this.currentLine.split(" ").join("")},isCurrentLineComment:function(){var a=this.currentLine.replace(/^ +/g,"");return a.charAt(0)=="#"},cleanup:function(c){c=c.split("\r\n").join("\n").split("\r").join("\n");if(!/\n$/.test(c)){c+="\n"}var b=0;var a=/^\%YAML[: ][\d\.]+.*\n/;while(a.test(c)){c=c.replace(a,"");b++}this.offset+=b;a=/^(#.*?\n)+/;if(a.test(c)){trimmedValue=c.replace(a,"");this.offset+=this.subStrCount(c,"\n")-this.subStrCount(trimmedValue,"\n");c=trimmedValue}a=/^\-\-\-.*?\n/;if(a.test(c)){trimmedValue=c.replace(a,"");this.offset+=this.subStrCount(c,"\n")-this.subStrCount(trimmedValue,"\n");c=trimmedValue;c=c.replace(/\.\.\.\s*$/g,"")}return c},isObject:function(a){return typeof(a)=="object"&&this.isDefined(a)},isEmpty:function(a){return a==undefined||a==null||a==""||a==0||a=="0"||a==false},isDefined:function(a){return a!=undefined&&a!=null},reverseArray:function(c){var b=[];var a=c.length;for(var d=a-1;d>=0;d--){b.push(c[d])}return b},merge:function(e,d){var f={};for(i in e){if(/^\d+$/.test(i)){f.push(e)}else{f[i]=e[i]}}for(i in d){if(/^\d+$/.test(i)){f.push(d)}else{f[i]=d[i]}}return f},strRepeat:function(d,c){var b;var a="";for(b=0;b<c;b++){a+=d}return d},subStrCount:function(d,b,j,f){var h=0;d=""+d;b=""+b;if(j!=undefined){d=d.substr(j)}if(f!=undefined){d=d.substr(0,f)}var a=d.length;var g=b.length;for(var e=0;e<a;e++){if(b==d.substr(e,g)){h++}}return h},trim:function(a){return(a+"").replace(/^\s+/,"").replace(/\s+$/,"")}};YamlDumper=function(){};YamlDumper.prototype={dump:function(g,f,c){if(f==undefined){f=0}if(c==undefined){c=0}var b="";var e=c?this.strRepeat(" ",c):"";var i;if(f<=0||!this.isObject(g)||this.isEmpty(g)){i=new YamlInline();b+=e+i.dump(g)}else{var d=!this.arrayEquals(this.getKeys(g),this.range(0,g.length-1));var a;for(var h in g){if(g.hasOwnProperty(h)){a=f-1<=0||!this.isObject(g[h])||this.isEmpty(g[h]);if(d){i=new YamlInline()}b+=e+""+(d?i.dump(h)+":":"-")+""+(a?" ":"\n")+""+this.dump(g[h],f-1,(a?0:c+2))+""+(a?"\n":"")}}}return b},strRepeat:function(d,c){var b;var a="";for(b=0;b<c;b++){a+=d}return d},isObject:function(a){return typeof(a)=="object"&&this.isDefined(a)},isEmpty:function(a){return a==undefined||a==null||a==""||a==0||a=="0"||a==false},isDefined:function(a){return a!=undefined&&a!=null},getKeys:function(c){var b=[];for(var a in c){if(c.hasOwnProperty(a)){b.push(a)}}return b},range:function(d,a){if(d>a){return[]}var b=[];for(var c=d;c<=a;c++){b.push(c)}return b},arrayEquals:function(e,d){if(e.length!=d.length){return false}var c=e.length;for(var f=0;f<c;f++){if(e[f]!=d[f]){return false}}return true}};
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'yaml'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module QUnited
|
7
|
+
module JsRunner
|
8
|
+
class Rhino < Base
|
9
|
+
def can_run?
|
10
|
+
# TODO: test that you have Java
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
js_dir = File.expand_path('../rhino/js', __FILE__)
|
15
|
+
|
16
|
+
js_jar, runner = File.join(js_dir, 'js.jar'), File.join(js_dir, 'qunit-runner.js')
|
17
|
+
|
18
|
+
source_files_args = @source_files.map { |sf| %{"#{sf}"} }.join(' ')
|
19
|
+
test_files_args = @test_files.map { |tf| %{"#{tf}"} }.join(' ')
|
20
|
+
|
21
|
+
tmp_file = Tempfile.new('qunited_results')
|
22
|
+
tmp_file.close
|
23
|
+
|
24
|
+
cmd = %{java -jar "#{js_jar}" -opt -1 "#{runner}" "#{js_dir}" "#{tmp_file.path}"}
|
25
|
+
cmd << " #{source_files_args} -- #{test_files_args}"
|
26
|
+
|
27
|
+
# Swallow stdout but allow stderr to get blasted out to console - if there are uncaught
|
28
|
+
# exceptions or anything else that goes wrong with the JavaScript interpreter the user
|
29
|
+
# will probably want to know but we are not particularly interested in it.
|
30
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr|
|
31
|
+
stdout.each {||} # Ignore; this is just here to make sure we block
|
32
|
+
# while waiting for tests to finish
|
33
|
+
unless (err = stderr.read).strip.empty? then $stderr.puts(err) end
|
34
|
+
end
|
35
|
+
|
36
|
+
@raw_results = clean_up_results(YAML.load(IO.read(tmp_file)))
|
37
|
+
|
38
|
+
@results = ::QUnited::Results.new @raw_results
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def clean_up_results(results)
|
45
|
+
results.map! { |mod_results| symbolize_keys mod_results }
|
46
|
+
results.each do |mod_results|
|
47
|
+
mod_results[:tests].map! { |test| clean_up_test_results(symbolize_keys(test)) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def clean_up_test_results(test_results)
|
52
|
+
test_results[:start] = DateTime.parse(test_results[:start])
|
53
|
+
test_results[:duration] = Float(test_results[:duration])
|
54
|
+
test_results[:assertion_data].map! { |data| symbolize_keys data }
|
55
|
+
test_results
|
56
|
+
end
|
57
|
+
|
58
|
+
def symbolize_keys(hash)
|
59
|
+
new_hash = {}
|
60
|
+
hash.keys.each { |key| new_hash[key.to_sym] = hash[key] }
|
61
|
+
new_hash
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module QUnited
|
2
|
+
class RakeTask < ::Rake::TaskLib
|
3
|
+
include ::Rake::DSL if defined?(::Rake::DSL)
|
4
|
+
|
5
|
+
# Name of task.
|
6
|
+
#
|
7
|
+
# default:
|
8
|
+
# :qunited
|
9
|
+
attr_accessor :name
|
10
|
+
|
11
|
+
# Glob pattern to match JavaScript source files (and any dependencies). Note that the
|
12
|
+
# order will be indeterminate so if your JavaScript files must be included in a particular
|
13
|
+
# order you will have to use source_files=(files_array).
|
14
|
+
#
|
15
|
+
# If an array of files is set with source_files=(files_array) then this will be ignored.
|
16
|
+
#
|
17
|
+
# default (unless source_files array is set):
|
18
|
+
# 'app/assets/javascripts/**/*.js'
|
19
|
+
attr_accessor :source_files_pattern
|
20
|
+
|
21
|
+
# Array of JavaScript source files (and any dependencies). These will be loaded in order
|
22
|
+
# before loading the QUnit tests.
|
23
|
+
attr_accessor :source_files
|
24
|
+
|
25
|
+
# Glob pattern to match QUnit test files.
|
26
|
+
#
|
27
|
+
# If an array of test files is set with test_files=(files) then this will be ignored.
|
28
|
+
#
|
29
|
+
# default:
|
30
|
+
# 'test/javascripts/**/*.js'
|
31
|
+
attr_accessor :test_files_pattern
|
32
|
+
|
33
|
+
# Array of QUnit test files.
|
34
|
+
attr_accessor :test_files
|
35
|
+
|
36
|
+
# Use verbose output. If this is true, the task will print the QUnited command to stdout.
|
37
|
+
#
|
38
|
+
# default:
|
39
|
+
# true
|
40
|
+
attr_accessor :verbose
|
41
|
+
|
42
|
+
def initialize(*args)
|
43
|
+
@name = args.shift || :qunited
|
44
|
+
|
45
|
+
yield self if block_given?
|
46
|
+
|
47
|
+
desc('Run QUnit JavaScript tests') unless ::Rake.application.last_comment
|
48
|
+
|
49
|
+
task name do
|
50
|
+
RakeFileUtils.send(:verbose, verbose) do
|
51
|
+
if source_files_to_include.empty?
|
52
|
+
msg = "No JavaScript source files specified"
|
53
|
+
msg << " with pattern #{source_files_pattern}" if source_files_pattern
|
54
|
+
puts msg
|
55
|
+
elsif test_files_to_run.empty?
|
56
|
+
puts "No QUnit test files matching #{test_files_pattern} could be found"
|
57
|
+
else
|
58
|
+
begin
|
59
|
+
puts command if verbose
|
60
|
+
success = system(command)
|
61
|
+
rescue
|
62
|
+
end
|
63
|
+
raise "#{command} failed" unless success
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def command
|
72
|
+
"qunited #{source_files_to_include.join(' ')} -- #{test_files_to_run.join(' ')}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def source_files_to_include
|
76
|
+
source_files || pattern_to_filelist(source_files_pattern)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_files_to_run
|
80
|
+
test_files || pattern_to_filelist(test_files_pattern)
|
81
|
+
end
|
82
|
+
|
83
|
+
def pattern_to_filelist(pattern)
|
84
|
+
FileList[pattern].map { |f| f.gsub(/"/, '\"').gsub(/'/, "\\\\'") }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module QUnited
|
2
|
+
|
3
|
+
# Simple tests results compiler. Takes a raw results hash that was produced by a runner.
|
4
|
+
class Results
|
5
|
+
class ModuleResults
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def tests
|
11
|
+
@tests ||= @data[:tests].map { |test_data| TestResults.new test_data, @data[:name] }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestResults
|
16
|
+
def initialize(data, module_name)
|
17
|
+
@data, @module_name = data, module_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def assertions
|
21
|
+
@data[:assertion_data].map do |assertion_data|
|
22
|
+
AssertionResults.new assertion_data, @data[:name], @module_name, @data[:file]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def passed?; result == :passed end
|
27
|
+
def failed?; result == :failed end
|
28
|
+
def error?; result == :error end
|
29
|
+
|
30
|
+
def result
|
31
|
+
@result ||= if assertions.find { |a| a.error? }
|
32
|
+
:error
|
33
|
+
else
|
34
|
+
assertions.find { |a| a.failed? } ? :failed : :passed
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def duration; @data[:duration] end
|
39
|
+
|
40
|
+
def to_s; passed? ? '.' : (error? ? 'E' : 'F') end
|
41
|
+
end
|
42
|
+
|
43
|
+
class AssertionResults
|
44
|
+
def initialize(data, test_name, module_name, file)
|
45
|
+
@data, @test_name, @module_name, @file = data, test_name, module_name, file
|
46
|
+
end
|
47
|
+
|
48
|
+
def message
|
49
|
+
@data[:message]
|
50
|
+
end
|
51
|
+
|
52
|
+
def result
|
53
|
+
if @data[:result]
|
54
|
+
:passed
|
55
|
+
else
|
56
|
+
@data[:message] =~ /^Died on test/ ? :error : :failed
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def passed?; result == :passed end
|
61
|
+
def failed?; result == :failed end
|
62
|
+
def error?; result == :error end
|
63
|
+
|
64
|
+
def output(count)
|
65
|
+
return "" if passed?
|
66
|
+
msg = " " + (count ? "#{count.to_s}) " : "")
|
67
|
+
msg << "#{error? ? 'Error' : 'Failure'}:\n"
|
68
|
+
msg << "#{@test_name} (#{@module_name}) [#{@file}]\n"
|
69
|
+
msg << "#{@data[:message] || 'Failed assertion, no message given.'}\n"
|
70
|
+
|
71
|
+
# Results can be nil. Also, JavaScript nulls will be converted, by the YAML serializer, to
|
72
|
+
# Ruby nil. Convert that back to 'null' for the output.
|
73
|
+
if @data.key? :expected
|
74
|
+
expected, actual = @data[:expected], @data[:actual]
|
75
|
+
msg << "Expected: #{expected.nil? ? 'null' : expected.inspect}\n"
|
76
|
+
msg << " Actual: #{actual.nil? ? 'null' : actual.inspect}\n"
|
77
|
+
end
|
78
|
+
msg
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(modules_results_array)
|
83
|
+
@data = modules_results_array.freeze
|
84
|
+
@module_results = @data.map { |module_data| ModuleResults.new module_data }
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_s
|
88
|
+
return <<-OUTPUT
|
89
|
+
#{dots}
|
90
|
+
#{"\n\n#{failures_output}\n\n" unless failures_output.empty?}
|
91
|
+
#{times_line}
|
92
|
+
|
93
|
+
#{bottom_line}
|
94
|
+
OUTPUT
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_i
|
98
|
+
passed? ? 0 : 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def passed?
|
102
|
+
total_failures.zero? && total_errors.zero?
|
103
|
+
end
|
104
|
+
|
105
|
+
def failed?
|
106
|
+
!passed?
|
107
|
+
end
|
108
|
+
|
109
|
+
def dots
|
110
|
+
tests.map { |test| test.to_s }.join
|
111
|
+
end
|
112
|
+
|
113
|
+
def bottom_line
|
114
|
+
"#{total_tests} tests, #{total_assertions} assertions, " +
|
115
|
+
"#{total_failures} failures, #{total_errors} errors, 0 skips"
|
116
|
+
end
|
117
|
+
|
118
|
+
def times_line
|
119
|
+
"Finished in #{"%.6g" % total_time} seconds, #{"%.6g" % (total_tests/total_time)} tests/s, " +
|
120
|
+
"#{"%.6g" % (total_assertions/total_time)} assertions/s."
|
121
|
+
end
|
122
|
+
|
123
|
+
def failures_output
|
124
|
+
failures_output_array.join("\n")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Array of failure output block strings
|
128
|
+
def failures_output_array
|
129
|
+
return @failures_output_array if @failures_output_array
|
130
|
+
count = 0
|
131
|
+
@failures_output_array = (failures + errors).map { |failure| failure.output(count += 1) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def total_tests
|
135
|
+
@total_tests ||= @module_results.inject(0) { |count, mod| count += mod.tests.size }
|
136
|
+
end
|
137
|
+
|
138
|
+
def total_assertions; assertions.size end
|
139
|
+
def total_failures; failures.size end
|
140
|
+
def total_errors; errors.size end
|
141
|
+
|
142
|
+
def total_time
|
143
|
+
@total_time ||= tests.inject(0) { |total, test| total += test.duration }
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def tests
|
149
|
+
@tests ||= @module_results.inject([]) { |tests, mod| tests += mod.tests }
|
150
|
+
end
|
151
|
+
|
152
|
+
def assertions
|
153
|
+
@assertions ||= tests.inject([]) { |asserts, test| asserts += test.assertions }
|
154
|
+
end
|
155
|
+
|
156
|
+
def failures
|
157
|
+
@failures ||= assertions.select { |assert| assert.failed? }
|
158
|
+
end
|
159
|
+
|
160
|
+
def errors
|
161
|
+
@errors ||= assertions.select { |assert| assert.error? }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module QUnited
|
2
|
+
class Runner
|
3
|
+
def self.run(js_source_files, js_test_files)
|
4
|
+
js_runner_klass = self.js_runner
|
5
|
+
# TODO: test that this JsRunner can run with current environment
|
6
|
+
runner = js_runner_klass.new(js_source_files, js_test_files)
|
7
|
+
|
8
|
+
puts "\n# Running JavaScript tests with #{runner.name}:\n\n"
|
9
|
+
|
10
|
+
results = runner.run.results
|
11
|
+
puts results
|
12
|
+
results.to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the runner that we will be using to run the JavaScript tests.
|
16
|
+
#
|
17
|
+
# Right now we only have one JavaScript runner, but when we have multiple we will have to
|
18
|
+
# determine which one we will used unless explicitly configured.
|
19
|
+
def self.js_runner
|
20
|
+
::QUnited::JsRunner::Rhino
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/qunited.rb
ADDED
data/qunited.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "qunited/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "qunited"
|
7
|
+
s.version = QUnited::VERSION
|
8
|
+
s.authors = ["Aaron Royer"]
|
9
|
+
s.email = ["aaronroyer@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/aaronroyer/qunited"
|
11
|
+
s.summary = %q{QUnit tests in your build}
|
12
|
+
s.description = %q{QUnited runs headless QUnit tests as part of your normal build}
|
13
|
+
|
14
|
+
s.rubyforge_project = "qunited"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module("Math");
|
2
|
+
|
3
|
+
test("Addition works", function() {
|
4
|
+
expect(2);
|
5
|
+
equal(1 + 1, 2, "One plus one does equal two");
|
6
|
+
equal(2 + 2, 4, "Two plus two does equal four");
|
7
|
+
});
|
8
|
+
|
9
|
+
test("Subtraction works", function() {
|
10
|
+
expect(1);
|
11
|
+
equal(2 - 1, 1, "Two minus one equals one");
|
12
|
+
});
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module("Math");
|
2
|
+
|
3
|
+
test("Addition is hard", function() {
|
4
|
+
expect(2);
|
5
|
+
equal(1 + 1, 3, "This math is wrong");
|
6
|
+
equal(2 + 2, 5, "This math is also wrong");
|
7
|
+
});
|
8
|
+
|
9
|
+
test("This expects the wrong number of assertions", function() {
|
10
|
+
expect(2);
|
11
|
+
equal(2 - 1, 1, "Two minus one equals one");
|
12
|
+
});
|