1 | 1 | var inBrowser = typeof exports === 'undefined'; |
2 | 1 | var parseAndModify = (inBrowser ? window.falafel : require("/Users/alexsouthern/Dropbox/blanket/src/./lib/falafel").falafel); |
3 | | |
4 | 1 | (inBrowser ? window : exports).blanket = (function(){ |
5 | 1 | var linesToAddTracking = [ |
6 | | "ExpressionStatement", |
7 | | "BreakStatement" , |
8 | | "ContinueStatement" , |
9 | | "VariableDeclaration", |
10 | | "ReturnStatement" , |
11 | | "ThrowStatement" , |
12 | | "TryStatement" , |
13 | | "FunctionDeclaration" , |
14 | | "IfStatement" , |
15 | | "WhileStatement" , |
16 | | "DoWhileStatement" , |
17 | | "ForStatement" , |
18 | | "ForInStatement" , |
19 | | "SwitchStatement" , |
20 | | "WithStatement" |
21 | | ], |
22 | | linesToAddBrackets = [ |
23 | | "IfStatement" , |
24 | | "WhileStatement" , |
25 | | "DoWhileStatement" , |
26 | | "ForStatement" , |
27 | | "ForInStatement" , |
28 | | "WithStatement" |
29 | | ], |
30 | | covVar = (inBrowser ? "window._$blanket" : "_$jscoverage" ), |
31 | | __blanket, |
32 | | copynumber = Math.floor(Math.random()*1000), |
33 | | coverageInfo = {},options = { |
34 | | reporter: null, |
35 | | adapter:null, |
36 | | filter: null, |
37 | | orderedLoading: true, |
38 | | loader: null, |
39 | | ignoreScriptError: false, |
40 | | existingRequireJS:false, |
41 | | autoStart: false, |
42 | | timeout: 180, |
43 | | branchTracking: false, |
44 | | ignoreCors: false |
45 | | }; |
46 | | |
47 | 1 | if (inBrowser && typeof window.blanket !== 'undefined'){ |
48 | 0 | __blanket = window.blanket.noConflict(); |
49 | | } |
50 | | |
51 | 1 | _blanket = { |
52 | | noConflict: function(){ |
53 | 0 | if (__blanket){ |
54 | 0 | return __blanket; |
55 | | } |
56 | 0 | return _blanket; |
57 | | }, |
58 | | _getCopyNumber: function(){ |
59 | | //internal method |
60 | | //for differentiating between instances |
61 | 0 | return copynumber; |
62 | | }, |
63 | | extend: function(obj) { |
64 | | //borrowed from underscore |
65 | 0 | _blanket._extend(_blanket,obj); |
66 | | }, |
67 | | _extend: function(dest,source){ |
68 | 0 | if (source) { |
69 | 0 | for (var prop in source) { |
70 | 0 | if ( dest[prop] instanceof Object && typeof dest[prop] !== "function"){ |
71 | 0 | _blanket._extend(dest[prop],source[prop]); |
72 | | }else{ |
73 | 0 | dest[prop] = source[prop]; |
74 | | } |
75 | | } |
76 | | } |
77 | | }, |
78 | | options: function(key,value){ |
79 | 101 | if (typeof key !== "string"){ |
80 | 0 | _blanket._extend(options,key); |
81 | 101 | }else if (typeof value === 'undefined'){ |
82 | 100 | return options[key]; |
83 | | }else{ |
84 | 1 | options[key]=value; |
85 | | } |
86 | | }, |
87 | | instrument: function(config, next){ |
88 | 3 | var inFile = config.inputFile, |
89 | | inFileName = config.inputFileName; |
90 | 3 | var sourceArray = _blanket._prepareSource(inFile); |
91 | 3 | _blanket._trackingArraySetup=[]; |
92 | 3 | var instrumented = parseAndModify(inFile,{loc:true,comment:true}, _blanket._addTracking,inFileName); |
93 | 3 | instrumented = _blanket._trackingSetup(inFileName,sourceArray)+instrumented; |
94 | 3 | next(instrumented); |
95 | | }, |
96 | | _trackingArraySetup: [], |
97 | | _branchingArraySetup: [], |
98 | | _prepareSource: function(source){ |
99 | 4 | return source.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/gm,"\n").split('\n'); |
100 | | }, |
101 | | _trackingSetup: function(filename,sourceArray){ |
102 | 4 | var branches = _blanket.options("branchTracking"); |
103 | 4 | var sourceString = sourceArray.join("',\n'"); |
104 | 4 | var intro = ""; |
105 | | |
106 | 4 | intro += "if (typeof "+covVar+" === 'undefined') "+covVar+" = {};\n"; |
107 | 4 | if (branches){ |
108 | 0 | intro += "var _$branchFcn=function(f,l,c,r){ "; |
109 | 0 | intro += covVar+"[f].branchData[l][c].push(r);"; |
110 | 0 | intro += "return r;};\n"; |
111 | | } |
112 | 4 | intro += "if (typeof "+covVar+"['"+filename+"'] === 'undefined'){"; |
113 | | |
114 | 4 | intro += covVar+"['"+filename+"']=[];\n"; |
115 | 4 | if (branches){ |
116 | 0 | intro += covVar+"['"+filename+"'].branchData=[];\n"; |
117 | | } |
118 | 4 | intro += covVar+"['"+filename+"'].source=['"+sourceString+"'];\n"; |
119 | | //initialize array values |
120 | 4 | _blanket._trackingArraySetup.sort(function(a,b){ |
121 | 19 | return parseInt(a,10) > parseInt(b,10); |
122 | | }).forEach(function(item){ |
123 | 16 | intro += covVar+"['"+filename+"']["+item+"]=0;\n"; |
124 | | }); |
125 | 4 | if (branches){ |
126 | 0 | _blanket._branchingArraySetup.sort(function(a,b){ |
127 | 0 | return a.line > b.line; |
128 | | }).sort(function(a,b){ |
129 | 0 | return a.column > b.column; |
130 | | }).forEach(function(item){ |
131 | 0 | if (item.file === filename){ |
132 | 0 | intro += "if (typeof "+ covVar+"['"+filename+"'].branchData["+item.line+"] === 'undefined'){\n"; |
133 | 0 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]=[];\n"; |
134 | 0 | intro += "}"; |
135 | 0 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"] = [];\n"; |
136 | | } |
137 | | }); |
138 | | } |
139 | 4 | intro += "}"; |
140 | | |
141 | 4 | return intro; |
142 | | }, |
143 | | _blockifyIf: function(node){ |
144 | | |
145 | 121 | if (linesToAddBrackets.indexOf(node.type) > -1){ |
146 | 9 | var bracketsExistObject = node.consequent || node.body; |
147 | 9 | var bracketsExistAlt = node.alternate; |
148 | 9 | if( bracketsExistAlt && bracketsExistAlt.type !== "BlockStatement") { |
149 | 2 | bracketsExistAlt.update("{\n"+bracketsExistAlt.source()+"}\n"); |
150 | | } |
151 | 9 | if( bracketsExistObject && bracketsExistObject.type !== "BlockStatement") { |
152 | 6 | bracketsExistObject.update("{\n"+bracketsExistObject.source()+"}\n"); |
153 | | } |
154 | | } |
155 | | }, |
156 | | _trackBranch: function(node,filename){ |
157 | | //recursive on consequent and alternative |
158 | 0 | var line = node.loc.start.line; |
159 | 0 | var col = node.loc.start.column; |
160 | | |
161 | 0 | _blanket._branchingArraySetup.push({ |
162 | | line: line, |
163 | | column: col, |
164 | | file:filename |
165 | | }); |
166 | | |
167 | 0 | var source = node.source(); |
168 | 0 | var updated = "_$branchFcn"+ |
169 | | "('"+filename+"',"+line+","+col+","+source.slice(0,source.indexOf("?"))+ |
170 | | ")"+source.slice(source.indexOf("?")); |
171 | 0 | node.update(updated); |
172 | | }, |
173 | | _addTracking: function (node,filename) { |
174 | 121 | _blanket._blockifyIf(node); |
175 | 121 | if (linesToAddTracking.indexOf(node.type) > -1 && node.parent.type !== "LabeledStatement"){ |
176 | 26 | if (node.type === "VariableDeclaration" && |
177 | | (node.parent.type === "ForStatement" || node.parent.type === "ForInStatement")){ |
178 | 1 | return; |
179 | | } |
180 | 25 | if (node.loc && node.loc.start){ |
181 | 25 | node.update(covVar+"['"+filename+"']["+node.loc.start.line+"]++;\n"+node.source()); |
182 | 25 | _blanket._trackingArraySetup.push(node.loc.start.line); |
183 | | }else{ |
184 | | //I don't think we can handle a node with no location |
185 | 0 | throw new Error("The instrumenter encountered a node with no location: "+Object.keys(node)); |
186 | | } |
187 | 95 | }else if (_blanket.options("branchTracking") && node.type === "ConditionalExpression"){ |
188 | 0 | _blanket._trackBranch(node,filename); |
189 | | } |
190 | | }, |
191 | | setupCoverage: function(){ |
192 | 1 | coverageInfo.instrumentation = "blanket"; |
193 | 1 | coverageInfo.stats = { |
194 | | "suites": 0, |
195 | | "tests": 0, |
196 | | "passes": 0, |
197 | | "pending": 0, |
198 | | "failures": 0, |
199 | | "start": new Date() |
200 | | }; |
201 | | }, |
202 | | _checkIfSetup: function(){ |
203 | 4 | if (!coverageInfo.stats){ |
204 | 0 | throw new Error("You must call blanket.setupCoverage() first."); |
205 | | } |
206 | | }, |
207 | | onTestStart: function(){ |
208 | 1 | this._checkIfSetup(); |
209 | 1 | coverageInfo.stats.tests++; |
210 | 1 | coverageInfo.stats.pending++; |
211 | | }, |
212 | | onTestDone: function(total,passed){ |
213 | 1 | this._checkIfSetup(); |
214 | 1 | if(passed === total){ |
215 | 1 | coverageInfo.stats.passes++; |
216 | | }else{ |
217 | 0 | coverageInfo.stats.failures++; |
218 | | } |
219 | 1 | coverageInfo.stats.pending--; |
220 | | }, |
221 | | onModuleStart: function(){ |
222 | 1 | this._checkIfSetup(); |
223 | 1 | coverageInfo.stats.suites++; |
224 | | }, |
225 | | onTestsDone: function(){ |
226 | 1 | this._checkIfSetup(); |
227 | 1 | coverageInfo.stats.end = new Date(); |
228 | | |
229 | 1 | if (typeof exports === 'undefined'){ |
230 | 0 | this.report(coverageInfo); |
231 | | }else{ |
232 | 1 | delete _$jscoverage.branchFcn; |
233 | 1 | this.options("reporter").call(this,coverageInfo); |
234 | | } |
235 | | } |
236 | | }; |
237 | 1 | return _blanket; |
238 | | })(); |
239 | | |