1 | 1 | var inBrowser = typeof window !== 'undefined' && this === window; |
2 | 1 | var parseAndModify = (inBrowser ? window.falafel : require("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 | | __blanket, |
31 | | copynumber = Math.floor(Math.random()*1000), |
32 | | coverageInfo = {},options = { |
33 | | reporter: null, |
34 | | adapter:null, |
35 | | filter: null, |
36 | | customVariable: null, |
37 | | loader: null, |
38 | | ignoreScriptError: false, |
39 | | existingRequireJS:false, |
40 | | autoStart: false, |
41 | | timeout: 180, |
42 | | ignoreCors: false, |
43 | | branchTracking: false, |
44 | | sourceURL: false, |
45 | | debug:false, |
46 | | engineOnly:false, |
47 | | testReadyCallback:null, |
48 | | commonJS:false, |
49 | | instrumentCache:false, |
50 | | modulePattern: null |
51 | | }; |
52 | | |
53 | 1 | if (inBrowser && typeof window.blanket !== 'undefined'){ |
54 | 0 | __blanket = window.blanket.noConflict(); |
55 | | } |
56 | | |
57 | 1 | _blanket = { |
58 | | noConflict: function(){ |
59 | 0 | if (__blanket){ |
60 | 0 | return __blanket; |
61 | | } |
62 | 0 | return _blanket; |
63 | | }, |
64 | | _getCopyNumber: function(){ |
65 | | //internal method |
66 | | //for differentiating between instances |
67 | 0 | return copynumber; |
68 | | }, |
69 | | extend: function(obj) { |
70 | | //borrowed from underscore |
71 | 0 | _blanket._extend(_blanket,obj); |
72 | | }, |
73 | | _extend: function(dest,source){ |
74 | 0 | if (source) { |
75 | 0 | for (var prop in source) { |
76 | 0 | if ( dest[prop] instanceof Object && typeof dest[prop] !== "function"){ |
77 | 0 | _blanket._extend(dest[prop],source[prop]); |
78 | | }else{ |
79 | 0 | dest[prop] = source[prop]; |
80 | | } |
81 | | } |
82 | | } |
83 | | }, |
84 | | getCovVar: function(){ |
85 | 28 | var opt = _blanket.options("customVariable"); |
86 | 28 | if (opt){ |
87 | 0 | if (_blanket.options("debug")) {console.log("BLANKET-Using custom tracking variable:",opt);} |
88 | 0 | return inBrowser ? "window."+opt : opt; |
89 | | } |
90 | 28 | return inBrowser ? "window._$blanket" : "_$jscoverage"; |
91 | | }, |
92 | | options: function(key,value){ |
93 | 327 | if (typeof key !== "string"){ |
94 | 0 | _blanket._extend(options,key); |
95 | 327 | }else if (typeof value === 'undefined'){ |
96 | 316 | return options[key]; |
97 | | }else{ |
98 | 11 | options[key]=value; |
99 | | } |
100 | | }, |
101 | | instrument: function(config, next){ |
102 | | //check instrumented hash table, |
103 | | //return instrumented code if available. |
104 | 10 | var inFile = config.inputFile, |
105 | | inFileName = config.inputFileName; |
106 | | //check instrument cache |
107 | 10 | if (_blanket.options("instrumentCache") && sessionStorage && sessionStorage.getItem("blanket_instrument_store-"+inFileName)){ |
108 | 0 | if (_blanket.options("debug")) {console.log("BLANKET-Reading instrumentation from cache: ",inFileName);} |
109 | 0 | next(sessionStorage.getItem("blanket_instrument_store-"+inFileName)); |
110 | | }else{ |
111 | 10 | var sourceArray = _blanket._prepareSource(inFile); |
112 | 10 | _blanket._trackingArraySetup=[]; |
113 | 10 | var instrumented = parseAndModify(inFile,{loc:true,comment:true}, _blanket._addTracking(inFileName)); |
114 | 9 | instrumented = _blanket._trackingSetup(inFileName,sourceArray)+instrumented; |
115 | 9 | if (_blanket.options("sourceURL")){ |
116 | 0 | instrumented += "\n//@ sourceURL="+inFileName.replace("http://",""); |
117 | | } |
118 | 9 | if (_blanket.options("debug")) {console.log("BLANKET-Instrumented file: ",inFileName);} |
119 | 9 | if (_blanket.options("instrumentCache") && sessionStorage){ |
120 | 0 | if (_blanket.options("debug")) {console.log("BLANKET-Saving instrumentation to cache: ",inFileName);} |
121 | 0 | sessionStorage.setItem("blanket_instrument_store-"+inFileName,instrumented); |
122 | | } |
123 | 9 | next(instrumented); |
124 | | } |
125 | | }, |
126 | | _trackingArraySetup: [], |
127 | | _branchingArraySetup: [], |
128 | | _prepareSource: function(source){ |
129 | 11 | return source.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/gm,"\n").split('\n'); |
130 | | }, |
131 | | _trackingSetup: function(filename,sourceArray){ |
132 | 10 | var branches = _blanket.options("branchTracking"); |
133 | 10 | var sourceString = sourceArray.join("',\n'"); |
134 | 10 | var intro = ""; |
135 | 10 | var covVar = _blanket.getCovVar(); |
136 | | |
137 | 10 | intro += "if (typeof "+covVar+" === 'undefined') "+covVar+" = {};\n"; |
138 | 10 | if (branches){ |
139 | 5 | intro += "var _$branchFcn=function(f,l,c,r){ "; |
140 | 5 | intro += "if (!!r) { "; |
141 | 5 | intro += covVar+"[f].branchData[l][c][0] = "+covVar+"[f].branchData[l][c][0] || [];"; |
142 | 5 | intro += covVar+"[f].branchData[l][c][0].push(r); }"; |
143 | 5 | intro += "else { "; |
144 | 5 | intro += covVar+"[f].branchData[l][c][1] = "+covVar+"[f].branchData[l][c][1] || [];"; |
145 | 5 | intro += covVar+"[f].branchData[l][c][1].push(r); }"; |
146 | 5 | intro += "return r;};\n"; |
147 | | } |
148 | 10 | intro += "if (typeof "+covVar+"['"+filename+"'] === 'undefined'){"; |
149 | | |
150 | 10 | intro += covVar+"['"+filename+"']=[];\n"; |
151 | 10 | if (branches){ |
152 | 5 | intro += covVar+"['"+filename+"'].branchData=[];\n"; |
153 | | } |
154 | 10 | intro += covVar+"['"+filename+"'].source=['"+sourceString+"'];\n"; |
155 | | //initialize array values |
156 | 10 | _blanket._trackingArraySetup.sort(function(a,b){ |
157 | 28 | return parseInt(a,10) > parseInt(b,10); |
158 | | }).forEach(function(item){ |
159 | 31 | intro += covVar+"['"+filename+"']["+item+"]=0;\n"; |
160 | | }); |
161 | 10 | if (branches){ |
162 | 5 | _blanket._branchingArraySetup.sort(function(a,b){ |
163 | 20 | return a.line > b.line; |
164 | | }).sort(function(a,b){ |
165 | 33 | return a.column > b.column; |
166 | | }).forEach(function(item){ |
167 | 25 | if (item.file === filename){ |
168 | 9 | intro += "if (typeof "+ covVar+"['"+filename+"'].branchData["+item.line+"] === 'undefined'){\n"; |
169 | 9 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]=[];\n"; |
170 | 9 | intro += "}"; |
171 | 9 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"] = [];\n"; |
172 | 9 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"].consequent = "+JSON.stringify(item.consequent)+";\n"; |
173 | 9 | intro += covVar+"['"+filename+"'].branchData["+item.line+"]["+item.column+"].alternate = "+JSON.stringify(item.alternate)+";\n"; |
174 | | } |
175 | | }); |
176 | | } |
177 | 10 | intro += "}"; |
178 | | |
179 | 10 | return intro; |
180 | | }, |
181 | | _blockifyIf: function(node){ |
182 | 246 | if (linesToAddBrackets.indexOf(node.type) > -1){ |
183 | 9 | var bracketsExistObject = node.consequent || node.body; |
184 | 9 | var bracketsExistAlt = node.alternate; |
185 | 9 | if( bracketsExistAlt && bracketsExistAlt.type !== "BlockStatement") { |
186 | 2 | bracketsExistAlt.update("{\n"+bracketsExistAlt.source()+"}\n"); |
187 | | } |
188 | 9 | if( bracketsExistObject && bracketsExistObject.type !== "BlockStatement") { |
189 | 6 | bracketsExistObject.update("{\n"+bracketsExistObject.source()+"}\n"); |
190 | | } |
191 | | } |
192 | | }, |
193 | | _trackBranch: function(node,filename){ |
194 | | //recursive on consequent and alternative |
195 | 9 | var line = node.loc.start.line; |
196 | 9 | var col = node.loc.start.column; |
197 | | |
198 | 9 | _blanket._branchingArraySetup.push({ |
199 | | line: line, |
200 | | column: col, |
201 | | file:filename, |
202 | | consequent: node.consequent.loc, |
203 | | alternate: node.alternate.loc |
204 | | }); |
205 | | |
206 | 9 | var source = node.source(); |
207 | 9 | var updated = "_$branchFcn"+ |
208 | | "('"+filename+"',"+line+","+col+","+source.slice(0,source.indexOf("?"))+ |
209 | | ")"+source.slice(source.indexOf("?")); |
210 | 9 | node.update(updated); |
211 | | }, |
212 | | _addTracking: function (filename) { |
213 | | //falafel doesn't take a file name |
214 | | //so we include the filename in a closure |
215 | | //and return the function to falafel |
216 | 12 | var covVar = _blanket.getCovVar(); |
217 | | |
218 | 12 | return function(node){ |
219 | 246 | _blanket._blockifyIf(node); |
220 | | |
221 | 246 | if (linesToAddTracking.indexOf(node.type) > -1 && node.parent.type !== "LabeledStatement") { |
222 | 42 | _blanket._checkDefs(node,filename); |
223 | 41 | if (node.type === "VariableDeclaration" && |
224 | | (node.parent.type === "ForStatement" || node.parent.type === "ForInStatement")){ |
225 | 1 | return; |
226 | | } |
227 | 40 | if (node.loc && node.loc.start){ |
228 | 40 | node.update(covVar+"['"+filename+"']["+node.loc.start.line+"]++;\n"+node.source()); |
229 | 40 | _blanket._trackingArraySetup.push(node.loc.start.line); |
230 | | }else{ |
231 | | //I don't think we can handle a node with no location |
232 | 0 | throw new Error("The instrumenter encountered a node with no location: "+Object.keys(node)); |
233 | | } |
234 | 204 | }else if (_blanket.options("branchTracking") && node.type === "ConditionalExpression"){ |
235 | 9 | _blanket._trackBranch(node,filename); |
236 | | } |
237 | | }; |
238 | | }, |
239 | | _checkDefs: function(node,filename){ |
240 | | // Make sure developers don't redefine window. if they do, inform them it is wrong. |
241 | 42 | if (inBrowser){ |
242 | 0 | if (node.type === "VariableDeclaration" && node.declarations) { |
243 | 0 | node.declarations.forEach(function(declaration) { |
244 | 0 | if (declaration.id.name === "window") { |
245 | 0 | throw new Error("Instrumentation error, you cannot redefine the 'window' variable in " + filename + ":" + node.loc.start.line); |
246 | | } |
247 | | }); |
248 | | } |
249 | 0 | if (node.type === "FunctionDeclaration" && node.params) { |
250 | 0 | node.params.forEach(function(param) { |
251 | 0 | if (param.name === "window") { |
252 | 0 | throw new Error("Instrumentation error, you cannot redefine the 'window' variable in " + filename + ":" + node.loc.start.line); |
253 | | } |
254 | | }); |
255 | | } |
256 | | //Make sure developers don't redefine the coverage variable |
257 | 0 | if (node.type === "ExpressionStatement" && |
258 | | node.expression && node.expression.left && |
259 | | node.expression.left.object && node.expression.left.property && |
260 | | node.expression.left.object.name + |
261 | | "." + node.expression.left.property.name === _blanket.getCovVar()) { |
262 | 0 | throw new Error("Instrumentation error, you cannot redefine the coverage variable in " + filename + ":" + node.loc.start.line); |
263 | | } |
264 | | }else{ |
265 | | //Make sure developers don't redefine the coverage variable in node |
266 | 42 | if (node.type === "ExpressionStatement" && |
267 | | node.expression && node.expression.left && |
268 | | !node.expression.left.object && !node.expression.left.property && |
269 | | node.expression.left.name === _blanket.getCovVar()) { |
270 | 1 | throw new Error("Instrumentation error, you cannot redefine the coverage variable in " + filename + ":" + node.loc.start.line); |
271 | | } |
272 | | } |
273 | | }, |
274 | | setupCoverage: function(){ |
275 | 1 | coverageInfo.instrumentation = "blanket"; |
276 | 1 | coverageInfo.stats = { |
277 | | "suites": 0, |
278 | | "tests": 0, |
279 | | "passes": 0, |
280 | | "pending": 0, |
281 | | "failures": 0, |
282 | | "start": new Date() |
283 | | }; |
284 | | }, |
285 | | _checkIfSetup: function(){ |
286 | 4 | if (!coverageInfo.stats){ |
287 | 0 | throw new Error("You must call blanket.setupCoverage() first."); |
288 | | } |
289 | | }, |
290 | | onTestStart: function(){ |
291 | 1 | if (_blanket.options("debug")) {console.log("BLANKET-Test event started");} |
292 | 1 | this._checkIfSetup(); |
293 | 1 | coverageInfo.stats.tests++; |
294 | 1 | coverageInfo.stats.pending++; |
295 | | }, |
296 | | onTestDone: function(total,passed){ |
297 | 1 | this._checkIfSetup(); |
298 | 1 | if(passed === total){ |
299 | 1 | coverageInfo.stats.passes++; |
300 | | }else{ |
301 | 0 | coverageInfo.stats.failures++; |
302 | | } |
303 | 1 | coverageInfo.stats.pending--; |
304 | | }, |
305 | | onModuleStart: function(){ |
306 | 1 | this._checkIfSetup(); |
307 | 1 | coverageInfo.stats.suites++; |
308 | | }, |
309 | | onTestsDone: function(){ |
310 | 1 | if (_blanket.options("debug")) {console.log("BLANKET-Test event done");} |
311 | 1 | this._checkIfSetup(); |
312 | 1 | coverageInfo.stats.end = new Date(); |
313 | | |
314 | 1 | if (inBrowser){ |
315 | 0 | this.report(coverageInfo); |
316 | | }else{ |
317 | 1 | if (!_blanket.options("branchTracking")){ |
318 | 1 | delete (inBrowser ? window : global)[_blanket.getCovVar()].branchFcn; |
319 | | } |
320 | 1 | this.options("reporter").call(this,coverageInfo); |
321 | | } |
322 | | } |
323 | | }; |
324 | 1 | return _blanket; |
325 | | })(); |
326 | | |