qunited 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
});
|