qunited 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +13 -0
  2. data/Gemfile +4 -0
  3. data/Rakefile +9 -0
  4. data/bin/qunited +23 -0
  5. data/lib/qunited/js_runner/base.rb +34 -0
  6. data/lib/qunited/js_runner/rhino/js/env.rhino.js +14006 -0
  7. data/lib/qunited/js_runner/rhino/js/js.jar +0 -0
  8. data/lib/qunited/js_runner/rhino/js/qunit-runner.js +168 -0
  9. data/lib/qunited/js_runner/rhino/js/qunit.js +1838 -0
  10. data/lib/qunited/js_runner/rhino/js/yaml.js +22 -0
  11. data/lib/qunited/js_runner/rhino.rb +65 -0
  12. data/lib/qunited/js_runner.rb +2 -0
  13. data/lib/qunited/rake_task.rb +87 -0
  14. data/lib/qunited/results.rb +164 -0
  15. data/lib/qunited/runner.rb +23 -0
  16. data/lib/qunited/version.rb +3 -0
  17. data/lib/qunited.rb +4 -0
  18. data/qunited.gemspec +20 -0
  19. data/test/fixtures/basic_project/app/assets/javascripts/application.js +6 -0
  20. data/test/fixtures/basic_project/test/javascripts/test_basics.js +6 -0
  21. data/test/fixtures/basic_project/test/javascripts/test_math.js +12 -0
  22. data/test/fixtures/dom_project/app/assets/javascripts/application.js +6 -0
  23. data/test/fixtures/dom_project/test/javascripts/test_misc.js +5 -0
  24. data/test/fixtures/errors_project/app/assets/javascripts/no_error.js +5 -0
  25. data/test/fixtures/errors_project/app/assets/javascripts/syntax_error.js +7 -0
  26. data/test/fixtures/errors_project/app/assets/javascripts/undefined_error.js +9 -0
  27. data/test/fixtures/errors_project/test/javascripts/this_test_has_no_errors_in_it.js +5 -0
  28. data/test/fixtures/errors_project/test/javascripts/this_test_has_no_tests.js +4 -0
  29. data/test/fixtures/errors_project/test/javascripts/this_test_has_syntax_error.js +10 -0
  30. data/test/fixtures/errors_project/test/javascripts/this_test_has_undefined_error.js +10 -0
  31. data/test/fixtures/failures_project/app/assets/javascripts/application.js +5 -0
  32. data/test/fixtures/failures_project/test/javascripts/test_basics.js +11 -0
  33. data/test/fixtures/failures_project/test/javascripts/test_math.js +12 -0
  34. data/test/test_helper.rb +5 -0
  35. data/test/unit/test_results.rb +338 -0
  36. data/test/unit/test_rhino_runner.rb +114 -0
  37. 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,2 @@
1
+ require 'qunited/js_runner/base'
2
+ require 'qunited/js_runner/rhino'
@@ -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
@@ -0,0 +1,3 @@
1
+ module QUnited
2
+ VERSION = "0.0.1"
3
+ end
data/lib/qunited.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'qunited/version'
2
+ require 'qunited/runner'
3
+ require 'qunited/results'
4
+ require 'qunited/js_runner'
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,6 @@
1
+
2
+ var APP = {};
3
+
4
+ APP.one = function() {
5
+ return 1;
6
+ };
@@ -0,0 +1,6 @@
1
+ // Leave in default module
2
+
3
+ test("The source code was loaded", function() {
4
+ expect(1);
5
+ equal(APP.one(), 1, "We have loaded it");
6
+ });
@@ -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,6 @@
1
+ // Put a div on it
2
+ var div = document.createElement('div');
3
+ var attr = document.createAttribute('id');
4
+ attr.nodeValue = 'the-only-div';
5
+ div.setAttributeNode(attr);
6
+ document.body.appendChild(div);
@@ -0,0 +1,5 @@
1
+ module("Miscellaneous")
2
+ test("Test that we have a body and that one element", function() {
3
+ equal(document.getElementsByTagName("body").length, 1, "We have a body");
4
+ ok(document.getElementById('the-only-div'), "Found our element");
5
+ });
@@ -0,0 +1,5 @@
1
+ var APP = {};
2
+
3
+ APP.one = function() {
4
+ return 1;
5
+ };
@@ -0,0 +1,7 @@
1
+
2
+ var APP = {
3
+ // Syntax error
4
+
5
+ APP.one = function() {
6
+ return 1;
7
+ };
@@ -0,0 +1,9 @@
1
+ var DEFINED = {
2
+ gotIt: true
3
+ };
4
+
5
+ bogusFunction(); // Undefined and will stop interpretation
6
+
7
+ var UNDEFINED = {
8
+ gotIt: false
9
+ };
@@ -0,0 +1,5 @@
1
+ module("Test has no error");
2
+
3
+ test("Test is fine", function() {
4
+ ok(true);
5
+ });
@@ -0,0 +1,4 @@
1
+ // No tests in here
2
+ (function() {
3
+ var two = 1 + 1;
4
+ })();
@@ -0,0 +1,10 @@
1
+ module("Math");
2
+
3
+ test("Addition is hard", function() {
4
+ var obj = {key = "value"}; // Doing it wrong
5
+ });
6
+
7
+ test("This expects the wrong number of assertions", function() {
8
+ expect(2);
9
+ equal(2 - 1, 1, "Two minus one equals one");
10
+ });
@@ -0,0 +1,10 @@
1
+ module("Basics");
2
+
3
+ test("This has one failure", function() {
4
+ bogusFunction(); // undefined error
5
+ });
6
+
7
+ test("This has no failures", function() {
8
+ expect(1);
9
+ equal(APP.one(), 1, "It is 1");
10
+ });
@@ -0,0 +1,5 @@
1
+ var APP = {};
2
+
3
+ APP.one = function() {
4
+ return 1;
5
+ };
@@ -0,0 +1,11 @@
1
+ module("Basics");
2
+
3
+ test("This has one failure", function() {
4
+ expect(1);
5
+ ok(false); // No message
6
+ });
7
+
8
+ test("This has no failures", function() {
9
+ expect(1);
10
+ equal(APP.one(), 1, "It is 1");
11
+ });
@@ -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
+ });
@@ -0,0 +1,5 @@
1
+ require 'minitest/autorun'
2
+
3
+ require File.join(File.dirname(__FILE__), *%w[.. lib qunited])
4
+
5
+ FIXTURES_DIR = File.expand_path('../fixtures', __FILE__)