OLD | NEW |
(Empty) | |
| 1 /* |
| 2 QuickCheck tests for WebGL: |
| 3 |
| 4 1. Write a valid arg generator for each function |
| 5 1.1. Write valid arg predicates to use with random generator: |
| 6 if value passes generator, accept it as valid. |
| 7 1.2. Often needs initializing and cleanup: |
| 8 setup - generate - cleanup |
| 9 gl.createBuffer - test(bindBufferGenerator) - gl.deleteBuffer |
| 10 |
| 11 2. Write an invalid arg generator |
| 12 2.1. Take valid args, modify an arg until the args no longer pass |
| 13 checkArgValidity. |
| 14 2.2. Repeat for all args. |
| 15 |
| 16 3. Test functions using the generators |
| 17 3.1. Args generated with the valid arg generator should pass |
| 18 assertOk(f(args)) |
| 19 3.2. Args generated with the invalid arg generator should pass |
| 20 assertFail(f(args)) |
| 21 */ |
| 22 var GLcanvas = document.createElement('canvas'); |
| 23 var canvas2D = document.createElement('canvas'); |
| 24 GLcanvas.width = GLcanvas.height = 256; |
| 25 GL = GLcanvas.getContext(GL_CONTEXT_ID); |
| 26 Array.from = function(o) { |
| 27 var a = []; |
| 28 for (var i=0; i<o.length; i++) |
| 29 a.push(o[i]); |
| 30 return a; |
| 31 } |
| 32 Array.prototype.has = function(v) { return this.indexOf(v) != -1; } |
| 33 Array.prototype.random = function() { return this[randomInt(this.length)]; } |
| 34 |
| 35 castToInt = function(o) { |
| 36 if (typeof o == 'number') |
| 37 return isNaN(o) ? 0 : Math.floor(o); |
| 38 if (o == true) return 1; |
| 39 return 0; |
| 40 }; |
| 41 |
| 42 // Creates a constant checker / generator from its arguments. |
| 43 // |
| 44 // E.g. if you want a constant checker for the constants 1, 2, and 3, you |
| 45 // would do the following: |
| 46 // |
| 47 // var cc = constCheck(1,2,3); |
| 48 // var randomConst = cc.random(); |
| 49 // if (cc.has(randomConst)) |
| 50 // console.log("randomConst is included in cc's constants"); |
| 51 // |
| 52 constCheck = function() { |
| 53 var a = Array.from(arguments); |
| 54 a.has = function(v) { return this.indexOf(castToInt(v)) != -1; }; |
| 55 return a; |
| 56 } |
| 57 |
| 58 bindTextureTarget = constCheck(GL.TEXTURE_2D, GL.TEXTURE_CUBE_MAP); |
| 59 blendEquationMode = constCheck(GL.FUNC_ADD, GL.FUNC_SUBTRACT, GL.FUNC_REVERSE_SU
BTRACT); |
| 60 blendFuncSfactor = constCheck( |
| 61 GL.ZERO, GL.ONE, GL.SRC_COLOR, GL.ONE_MINUS_SRC_COLOR, GL.DST_COLOR, |
| 62 GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.DST_ALPHA, |
| 63 GL.ONE_MINUS_DST_ALPHA, GL.CONSTANT_COLOR, GL.ONE_MINUS_CONSTANT_COLOR, |
| 64 GL.CONSTANT_ALPHA, GL.ONE_MINUS_CONSTANT_ALPHA, GL.SRC_ALPHA_SATURATE |
| 65 ); |
| 66 blendFuncDfactor = constCheck( |
| 67 GL.ZERO, GL.ONE, GL.SRC_COLOR, GL.ONE_MINUS_SRC_COLOR, GL.DST_COLOR, |
| 68 GL.ONE_MINUS_DST_COLOR, GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.DST_ALPHA, |
| 69 GL.ONE_MINUS_DST_ALPHA, GL.CONSTANT_COLOR, GL.ONE_MINUS_CONSTANT_COLOR, |
| 70 GL.CONSTANT_ALPHA, GL.ONE_MINUS_CONSTANT_ALPHA |
| 71 ); |
| 72 bufferTarget = constCheck(GL.ARRAY_BUFFER, GL.ELEMENT_ARRAY_BUFFER); |
| 73 bufferMode = constCheck(GL.STREAM_DRAW, GL.STATIC_DRAW, GL.DYNAMIC_DRAW); |
| 74 clearMask = constCheck( |
| 75 GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, |
| 76 GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT, |
| 77 GL.COLOR_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, |
| 78 GL.DEPTH_BUFFER_BIT | GL.STENCIL_BUFFER_BIT, |
| 79 GL.COLOR_BUFFER_BIT, GL.DEPTH_BUFFER_BIT, GL.STENCIL_BUFFER_BIT, 0 |
| 80 ); |
| 81 cullFace = constCheck(GL.FRONT, GL.BACK, GL.FRONT_AND_BACK); |
| 82 depthFuncFunc = constCheck( |
| 83 GL.NEVER, GL.LESS, GL.EQUAL, GL.LEQUAL, GL.GREATER, GL.NOTEQUAL, |
| 84 GL.GEQUAL, GL.ALWAYS |
| 85 ); |
| 86 stencilFuncFunc = depthFuncFunc; |
| 87 enableCap = constCheck( |
| 88 GL.BLEND, GL.CULL_FACE, GL.DEPTH_TEST, GL.DITHER, GL.POLYGON_OFFSET_FILL, |
| 89 GL.SAMPLE_ALPHA_TO_COVERAGE, GL.SAMPLE_COVERAGE, GL.SCISSOR_TEST, |
| 90 GL.STENCIL_TEST |
| 91 ); |
| 92 frontFaceMode = constCheck(GL.CCW, GL.CW); |
| 93 getParameterPname = constCheck( |
| 94 GL.ACTIVE_TEXTURE || "GL.ACTIVE_TEXTURE", |
| 95 GL.ALIASED_LINE_WIDTH_RANGE || "GL.ALIASED_LINE_WIDTH_RANGE", |
| 96 GL.ALIASED_POINT_SIZE_RANGE || "GL.ALIASED_POINT_SIZE_RANGE", |
| 97 GL.ALPHA_BITS || "GL.ALPHA_BITS", |
| 98 GL.ARRAY_BUFFER_BINDING || "GL.ARRAY_BUFFER_BINDING", |
| 99 GL.BLEND || "GL.BLEND", |
| 100 GL.BLEND_COLOR || "GL.BLEND_COLOR", |
| 101 GL.BLEND_DST_ALPHA || "GL.BLEND_DST_ALPHA", |
| 102 GL.BLEND_DST_RGB || "GL.BLEND_DST_RGB", |
| 103 GL.BLEND_EQUATION_ALPHA || "GL.BLEND_EQUATION_ALPHA", |
| 104 GL.BLEND_EQUATION_RGB || "GL.BLEND_EQUATION_RGB", |
| 105 GL.BLEND_SRC_ALPHA || "GL.BLEND_SRC_ALPHA", |
| 106 GL.BLEND_SRC_RGB || "GL.BLEND_SRC_RGB", |
| 107 GL.BLUE_BITS || "GL.BLUE_BITS", |
| 108 GL.COLOR_CLEAR_VALUE || "GL.COLOR_CLEAR_VALUE", |
| 109 GL.COLOR_WRITEMASK || "GL.COLOR_WRITEMASK", |
| 110 GL.COMPRESSED_TEXTURE_FORMATS || "GL.COMPRESSED_TEXTURE_FORMATS", |
| 111 GL.CULL_FACE || "GL.CULL_FACE", |
| 112 GL.CULL_FACE_MODE || "GL.CULL_FACE_MODE", |
| 113 GL.CURRENT_PROGRAM || "GL.CURRENT_PROGRAM", |
| 114 GL.DEPTH_BITS || "GL.DEPTH_BITS", |
| 115 GL.DEPTH_CLEAR_VALUE || "GL.DEPTH_CLEAR_VALUE", |
| 116 GL.DEPTH_FUNC || "GL.DEPTH_FUNC", |
| 117 GL.DEPTH_RANGE || "GL.DEPTH_RANGE", |
| 118 GL.DEPTH_TEST || "GL.DEPTH_TEST", |
| 119 GL.DEPTH_WRITEMASK || "GL.DEPTH_WRITEMASK", |
| 120 GL.DITHER || "GL.DITHER", |
| 121 GL.ELEMENT_ARRAY_BUFFER_BINDING || "GL.ELEMENT_ARRAY_BUFFER_BINDING", |
| 122 GL.FRAMEBUFFER_BINDING || "GL.FRAMEBUFFER_BINDING", |
| 123 GL.FRONT_FACE || "GL.FRONT_FACE", |
| 124 GL.GENERATE_MIPMAP_HINT || "GL.GENERATE_MIPMAP_HINT", |
| 125 GL.GREEN_BITS || "GL.GREEN_BITS", |
| 126 GL.LINE_WIDTH || "GL.LINE_WIDTH", |
| 127 GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS || "GL.MAX_COMBINED_TEXTURE_IMAGE_UNITS", |
| 128 GL.MAX_CUBE_MAP_TEXTURE_SIZE || "GL.MAX_CUBE_MAP_TEXTURE_SIZE", |
| 129 GL.MAX_FRAGMENT_UNIFORM_VECTORS || "GL.MAX_FRAGMENT_UNIFORM_VECTORS", |
| 130 GL.MAX_RENDERBUFFER_SIZE || "GL.MAX_RENDERBUFFER_SIZE", |
| 131 GL.MAX_TEXTURE_IMAGE_UNITS || "GL.MAX_TEXTURE_IMAGE_UNITS", |
| 132 GL.MAX_TEXTURE_SIZE || "GL.MAX_TEXTURE_SIZE", |
| 133 GL.MAX_VARYING_VECTORS || "GL.MAX_VARYING_VECTORS", |
| 134 GL.MAX_VERTEX_ATTRIBS || "GL.MAX_VERTEX_ATTRIBS", |
| 135 GL.MAX_VERTEX_TEXTURE_IMAGE_UNITS || "GL.MAX_VERTEX_TEXTURE_IMAGE_UNITS", |
| 136 GL.MAX_VERTEX_UNIFORM_VECTORS || "GL.MAX_VERTEX_UNIFORM_VECTORS", |
| 137 GL.MAX_VIEWPORT_DIMS || "GL.MAX_VIEWPORT_DIMS", |
| 138 GL.NUM_COMPRESSED_TEXTURE_FORMATS || "GL.NUM_COMPRESSED_TEXTURE_FORMATS", |
| 139 GL.PACK_ALIGNMENT || "GL.PACK_ALIGNMENT", |
| 140 GL.POLYGON_OFFSET_FACTOR || "GL.POLYGON_OFFSET_FACTOR", |
| 141 GL.POLYGON_OFFSET_FILL || "GL.POLYGON_OFFSET_FILL", |
| 142 GL.POLYGON_OFFSET_UNITS || "GL.POLYGON_OFFSET_UNITS", |
| 143 GL.RED_BITS || "GL.RED_BITS", |
| 144 GL.RENDERBUFFER_BINDING || "GL.RENDERBUFFER_BINDING", |
| 145 GL.SAMPLE_BUFFERS || "GL.SAMPLE_BUFFERS", |
| 146 GL.SAMPLE_COVERAGE_INVERT || "GL.SAMPLE_COVERAGE_INVERT", |
| 147 GL.SAMPLE_COVERAGE_VALUE || "GL.SAMPLE_COVERAGE_VALUE", |
| 148 GL.SAMPLES || "GL.SAMPLES", |
| 149 GL.SCISSOR_BOX || "GL.SCISSOR_BOX", |
| 150 GL.SCISSOR_TEST || "GL.SCISSOR_TEST", |
| 151 GL.STENCIL_BACK_FAIL || "GL.STENCIL_BACK_FAIL", |
| 152 GL.STENCIL_BACK_FUNC || "GL.STENCIL_BACK_FUNC", |
| 153 GL.STENCIL_BACK_PASS_DEPTH_FAIL || "GL.STENCIL_BACK_PASS_DEPTH_FAIL", |
| 154 GL.STENCIL_BACK_PASS_DEPTH_PASS || "GL.STENCIL_BACK_PASS_DEPTH_PASS", |
| 155 GL.STENCIL_BACK_REF || "GL.STENCIL_BACK_REF", |
| 156 GL.STENCIL_BACK_VALUE_MASK || "GL.STENCIL_BACK_VALUE_MASK", |
| 157 GL.STENCIL_BACK_WRITEMASK || "GL.STENCIL_BACK_WRITEMASK", |
| 158 GL.STENCIL_BITS || "GL.STENCIL_BITS", |
| 159 GL.STENCIL_CLEAR_VALUE || "GL.STENCIL_CLEAR_VALUE", |
| 160 GL.STENCIL_FAIL || "GL.STENCIL_FAIL", |
| 161 GL.STENCIL_FUNC || "GL.STENCIL_FUNC", |
| 162 GL.STENCIL_PASS_DEPTH_FAIL || "GL.STENCIL_PASS_DEPTH_FAIL", |
| 163 GL.STENCIL_PASS_DEPTH_PASS || "GL.STENCIL_PASS_DEPTH_PASS", |
| 164 GL.STENCIL_REF || "GL.STENCIL_REF", |
| 165 GL.STENCIL_TEST || "GL.STENCIL_TEST", |
| 166 GL.STENCIL_VALUE_MASK || "GL.STENCIL_VALUE_MASK", |
| 167 GL.STENCIL_WRITEMASK || "GL.STENCIL_WRITEMASK", |
| 168 GL.SUBPIXEL_BITS || "GL.SUBPIXEL_BITS", |
| 169 GL.TEXTURE_BINDING_2D || "GL.TEXTURE_BINDING_2D", |
| 170 GL.TEXTURE_BINDING_CUBE_MAP || "GL.TEXTURE_BINDING_CUBE_MAP", |
| 171 GL.UNPACK_ALIGNMENT || "GL.UNPACK_ALIGNMENT", |
| 172 GL.VIEWPORT || "GL.VIEWPORT" |
| 173 ); |
| 174 mipmapHint = constCheck(GL.FASTEST, GL.NICEST, GL.DONT_CARE); |
| 175 pixelStoreiPname = constCheck(GL.PACK_ALIGNMENT, GL.UNPACK_ALIGNMENT); |
| 176 pixelStoreiParam = constCheck(1,2,4,8); |
| 177 shaderType = constCheck(GL.VERTEX_SHADER, GL.FRAGMENT_SHADER); |
| 178 stencilOp = constCheck(GL.KEEP, GL.ZERO, GL.REPLACE, GL.INCR, GL.INCR_WRAP, |
| 179 GL.DECR, GL.DECR_WRAP, GL.INVERT); |
| 180 texImageTarget = constCheck( |
| 181 GL.TEXTURE_2D, |
| 182 GL.TEXTURE_CUBE_MAP_POSITIVE_X, |
| 183 GL.TEXTURE_CUBE_MAP_NEGATIVE_X, |
| 184 GL.TEXTURE_CUBE_MAP_POSITIVE_Y, |
| 185 GL.TEXTURE_CUBE_MAP_NEGATIVE_Y, |
| 186 GL.TEXTURE_CUBE_MAP_POSITIVE_Z, |
| 187 GL.TEXTURE_CUBE_MAP_NEGATIVE_Z |
| 188 ); |
| 189 texImageInternalFormat = constCheck( |
| 190 GL.ALPHA, |
| 191 GL.LUMINANCE, |
| 192 GL.LUMINANCE_ALPHA, |
| 193 GL.RGB, |
| 194 GL.RGBA |
| 195 ); |
| 196 texImageFormat = constCheck( |
| 197 GL.ALPHA, |
| 198 GL.LUMINANCE, |
| 199 GL.LUMINANCE_ALPHA, |
| 200 GL.RGB, |
| 201 GL.RGBA |
| 202 ); |
| 203 texImageType = constCheck(GL.UNSIGNED_BYTE); |
| 204 texParameterPname = constCheck( |
| 205 GL.TEXTURE_MIN_FILTER, GL.TEXTURE_MAG_FILTER, |
| 206 GL.TEXTURE_WRAP_S, GL.TEXTURE_WRAP_T); |
| 207 texParameterParam = {}; |
| 208 texParameterParam[GL.TEXTURE_MIN_FILTER] = constCheck( |
| 209 GL.NEAREST, GL.LINEAR, GL.NEAREST_MIPMAP_NEAREST, GL.LINEAR_MIPMAP_NEAREST, |
| 210 GL.NEAREST_MIPMAP_LINEAR, GL.LINEAR_MIPMAP_LINEAR); |
| 211 texParameterParam[GL.TEXTURE_MAG_FILTER] = constCheck(GL.NEAREST, GL.LINEAR); |
| 212 texParameterParam[GL.TEXTURE_WRAP_S] = constCheck( |
| 213 GL.CLAMP_TO_EDGE, GL.MIRRORED_REPEAT, GL.REPEAT); |
| 214 texParameterParam[GL.TEXTURE_WRAP_T] = texParameterParam[GL.TEXTURE_WRAP_S]; |
| 215 textureUnit = constCheck.apply(this, (function(){ |
| 216 var textureUnits = []; |
| 217 var texUnits = GL.getParameter(GL.MAX_TEXTURE_IMAGE_UNITS); |
| 218 for (var i=0; i<texUnits; i++) textureUnits.push(GL['TEXTURE'+i]); |
| 219 return textureUnits; |
| 220 })()); |
| 221 |
| 222 var StencilBits = GL.getParameter(GL.STENCIL_BITS); |
| 223 var MaxStencilValue = 1 << StencilBits; |
| 224 |
| 225 var MaxVertexAttribs = GL.getParameter(GL.MAX_VERTEX_ATTRIBS); |
| 226 var LineWidthRange = GL.getParameter(GL.ALIASED_LINE_WIDTH_RANGE); |
| 227 |
| 228 // Returns true if bufData can be passed to GL.bufferData |
| 229 isBufferData = function(bufData) { |
| 230 if (typeof bufData == 'number') |
| 231 return bufData >= 0; |
| 232 if (bufData instanceof ArrayBuffer) |
| 233 return true; |
| 234 return WebGLArrayTypes.some(function(t) { |
| 235 return bufData instanceof t; |
| 236 }); |
| 237 }; |
| 238 |
| 239 isVertexAttribute = function(idx) { |
| 240 if (typeof idx != 'number') return false; |
| 241 return idx >= 0 && idx < MaxVertexAttribs; |
| 242 }; |
| 243 |
| 244 isValidName = function(name) { |
| 245 if (typeof name != 'string') return false; |
| 246 for (var i=0; i<name.length; i++) { |
| 247 var c = name.charCodeAt(i); |
| 248 if (c & 0x00FF == 0 || c & 0xFF00 == 0) { |
| 249 return false; |
| 250 } |
| 251 } |
| 252 return true; |
| 253 }; |
| 254 |
| 255 WebGLArrayTypes = [ |
| 256 Float32Array, |
| 257 Int32Array, |
| 258 Int16Array, |
| 259 Int8Array, |
| 260 Uint32Array, |
| 261 Uint16Array, |
| 262 Uint8Array |
| 263 ]; |
| 264 webGLArrayContentGenerators = [randomLength, randomSmallIntArray]; |
| 265 randomWebGLArray = function() { |
| 266 var t = WebGLArrayTypes.random(); |
| 267 return new t(webGLArrayContentGenerators.random()()); |
| 268 }; |
| 269 |
| 270 randomArrayBuffer = function(buflen) { |
| 271 if (buflen == null) buflen = 256; |
| 272 var len = randomInt(buflen)+1; |
| 273 var rv; |
| 274 try { |
| 275 rv = new ArrayBuffer(len); |
| 276 } catch(e) { |
| 277 log("Error creating ArrayBuffer with length " + len); |
| 278 throw(e); |
| 279 } |
| 280 return rv; |
| 281 }; |
| 282 |
| 283 bufferDataGenerators = [randomLength, randomWebGLArray, randomArrayBuffer]; |
| 284 randomBufferData = function() { |
| 285 return bufferDataGenerators.random()(); |
| 286 }; |
| 287 |
| 288 randomSmallWebGLArray = function(buflen) { |
| 289 var t = WebGLArrayTypes.random(); |
| 290 return new t(randomInt(buflen/4)+1); |
| 291 }; |
| 292 |
| 293 bufferSubDataGenerators = [randomSmallWebGLArray, randomArrayBuffer]; |
| 294 randomBufferSubData = function(buflen) { |
| 295 var data = bufferSubDataGenerators.random()(buflen); |
| 296 var offset = randomInt(buflen - data.byteLength); |
| 297 return {data:data, offset:offset}; |
| 298 }; |
| 299 |
| 300 randomColor = function() { |
| 301 return [Math.random(), Math.random(), Math.random(), Math.random()]; |
| 302 }; |
| 303 |
| 304 randomName = function() { |
| 305 var arr = []; |
| 306 var len = randomLength()+1; |
| 307 for (var i=0; i<len; i++) { |
| 308 var l = randomInt(255)+1; |
| 309 var h = randomInt(255)+1; |
| 310 var c = (h << 8) | l; |
| 311 arr.push(String.fromCharCode(c)); |
| 312 } |
| 313 return arr.join(''); |
| 314 }; |
| 315 randomVertexAttribute = function() { |
| 316 return randomInt(MaxVertexAttribs); |
| 317 }; |
| 318 |
| 319 randomBool = function() { return Math.random() > 0.5; }; |
| 320 |
| 321 randomStencil = function() { |
| 322 return randomInt(MaxStencilValue); |
| 323 }; |
| 324 |
| 325 randomLineWidth = function() { |
| 326 var lo = LineWidthRange[0], |
| 327 hi = LineWidthRange[1]; |
| 328 return randomFloatFromRange(lo, hi); |
| 329 }; |
| 330 |
| 331 randomImage = function(w,h) { |
| 332 var img; |
| 333 var r = Math.random(); |
| 334 if (r < 0.5) { |
| 335 img = document.createElement('canvas'); |
| 336 img.width = w; img.height = h; |
| 337 img.getContext('2d').fillRect(0,0,w,h); |
| 338 } else if (r < 0.5) { |
| 339 img = document.createElement('video'); |
| 340 img.width = w; img.height = h; |
| 341 } else if (r < 0.75) { |
| 342 img = document.createElement('img'); |
| 343 img.width = w; img.height = h; |
| 344 } else { |
| 345 img = canvas2D.getContext('2d').createImageData(w,h); |
| 346 } |
| 347 return img |
| 348 }; |
| 349 |
| 350 mutateArgs = function(args) { |
| 351 var mutateCount = randomIntFromRange(1, args.length); |
| 352 var newArgs = Array.from(args); |
| 353 for (var i=0; i<mutateCount; i++) { |
| 354 var idx = randomInt(args.length); |
| 355 newArgs[idx] = generateRandomArg(idx, args.length); |
| 356 } |
| 357 return newArgs; |
| 358 }; |
| 359 |
| 360 // Calls testFunction numberOfTests times with arguments generated by |
| 361 // argGen.generate() (or empty arguments if no generate present). |
| 362 // |
| 363 // The arguments testFunction is called with are the generated args, |
| 364 // the argGen, and what argGen.setup() returned or [] if argGen has not setup |
| 365 // method. I.e. testFunction(generatedArgs, argGen, setupVars). |
| 366 // |
| 367 argGeneratorTestRunner = function(argGen, testFunction, numberOfTests) { |
| 368 // do argument generator setup if needed |
| 369 var setupVars = argGen.setup ? argGen.setup() : []; |
| 370 var error; |
| 371 for (var i=0; i<numberOfTests; i++) { |
| 372 var failed = false; |
| 373 // generate arguments if argGen has a generate method |
| 374 var generatedArgs = argGen.generate ? argGen.generate.apply(argGen, setupVar
s) : []; |
| 375 try { |
| 376 // call testFunction with the generated args |
| 377 testFunction.call(this, generatedArgs, argGen, setupVars); |
| 378 } catch (e) { |
| 379 failed = true; |
| 380 error = e; |
| 381 } |
| 382 // if argGen needs cleanup for generated args, do it here |
| 383 if (argGen.cleanup) |
| 384 argGen.cleanup.apply(argGen, generatedArgs); |
| 385 if (failed) break; |
| 386 } |
| 387 // if argGen needs to do a final cleanup for setupVars, do it here |
| 388 if (argGen.teardown) |
| 389 argGen.teardown.apply(argGen, setupVars); |
| 390 if (error) throw(error); |
| 391 } |
OLD | NEW |