OLD | NEW |
| (Empty) |
1 /* global self */ | |
2 | |
3 // testharness.js has the higher priority. | |
4 var TESTHARNESS = true; | |
5 var JSTEST = false; | |
6 | |
7 (function () { | |
8 // Selected properies from testharness.js | |
9 var testharnessProperties = [ | |
10 'test', 'async_test', 'promise_test', 'promise_rejects', | |
11 'generate_tests', 'setup', 'done', 'assert_true', 'assert_false' | |
12 ]; | |
13 | |
14 // Selected properties from js-test.js | |
15 var jsTestProperties = [ | |
16 'isJsTest', 'testPassed', 'testFailed', 'gc', 'finishJSTest' | |
17 ]; | |
18 | |
19 // Check if testharness.js is properly loaded and set up a flag for it. | |
20 for (var name in testharnessProperties) { | |
21 if (!self.hasOwnProperty(testharnessProperties[name])) { | |
22 TESTHARNESS = false; | |
23 break; | |
24 } | |
25 } | |
26 | |
27 // Immediately return here because testharness.js is ready. | |
28 if (TESTHARNESS) | |
29 return; | |
30 | |
31 // Because testharness.js is not loaded, let us assume that js-test.js might | |
32 // be in use. Check if js-test.js is properly loaded and set up a flag for | |
33 // it. | |
34 JSTEST = true; | |
35 for (var name in jsTestProperties) { | |
36 if (!self.hasOwnProperty(jsTestProperties[name])) { | |
37 JSTEST = false; | |
38 break; | |
39 } | |
40 } | |
41 | |
42 // If both are not loaded at all, throw here. | |
43 if (!JSTEST) | |
44 throw new Error('Cannot proceed. No test infrastructure is loaded.'); | |
45 })(); | |
46 | |
47 | |
48 // |Audit| is a task runner for web audio test. It makes asynchronous web audio | |
49 // testing simple and manageable. | |
50 // | |
51 // EXAMPLE: | |
52 // | |
53 // var audit = Audit.createTaskRunner(); | |
54 // // Define test routine. Make sure to call done() when reached at the end. | |
55 // audit.defineTask('foo', function (done) { | |
56 // var context = new AudioContext(); | |
57 // // do things | |
58 // context.oncomplete = function () { | |
59 // // verification here | |
60 // done(); | |
61 // }; | |
62 // }); | |
63 // | |
64 // audit.defineTask('bar', function (done) { | |
65 // // your code here | |
66 // done(); | |
67 // }); | |
68 // | |
69 // // Queue tasks by readable task names. | |
70 // audit.runTasks('foo', 'bar'); | |
71 // | |
72 // // Alternatively, if you want to run all of the defined tasks in the order
in which they were | |
73 // // defined, use no arguments: | |
74 // audit.runTasks(); | |
75 var Audit = (function () { | |
76 | |
77 'use strict'; | |
78 | |
79 function Tasks() { | |
80 this.tasks = {}; | |
81 this.queue = []; | |
82 this.currentTask = 0; | |
83 } | |
84 | |
85 // This is to prime the task runner for the testharness.js async operation. | |
86 Tasks.prototype._initialize = function () { | |
87 if (TESTHARNESS) { | |
88 setup(new Function(), { | |
89 explicit_done: true | |
90 }); | |
91 } | |
92 }; | |
93 | |
94 // Finalize the task runner by notifying testharness and testRunner that | |
95 // all the task is completed. | |
96 Tasks.prototype._finalize = function () { | |
97 if (TESTHARNESS) { | |
98 // From testharness.js | |
99 done(); | |
100 } | |
101 }; | |
102 | |
103 Tasks.prototype.defineTask = function (taskName, taskFunc) { | |
104 // Check if there is a task defined with the same name. If found, do | |
105 // not add the task to the roster. | |
106 if (this.tasks.hasOwnProperty(taskName)) { | |
107 debug('>> Audit.defineTask:: Duplicate task definition. ("' + taskNa
me + '")'); | |
108 return; | |
109 } | |
110 | |
111 this.tasks[taskName] = taskFunc; | |
112 | |
113 // Push the task name in the order of definition. | |
114 this.queue.push(taskName); | |
115 }; | |
116 | |
117 // If arguments are given, only run the requested tasks. Check for any | |
118 // undefined or duplicate task in the requested task arguments. If there | |
119 // is no argument, run all the defined tasks. | |
120 Tasks.prototype.runTasks = function () { | |
121 | |
122 this._initialize(); | |
123 | |
124 if (arguments.length > 0) { | |
125 | |
126 // Reset task queue and refill it with the with the given arguments, | |
127 // in argument order. | |
128 this.queue = []; | |
129 | |
130 for (var i = 0; i < arguments.length; i++) { | |
131 if (!this.tasks.hasOwnProperty(arguments[i])) | |
132 debug('>> Audit.runTasks:: Ignoring undefined task. ("' + ar
guments[i] + '")'); | |
133 else if (this.queue.indexOf(arguments[i]) > -1) | |
134 debug('>> Audit.runTasks:: Ignoring duplicate task request.
("' + arguments[i] + '")'); | |
135 else | |
136 this.queue.push(arguments[i]); | |
137 } | |
138 } | |
139 | |
140 if (this.queue.length === 0) { | |
141 debug('>> Audit.runTasks:: No task to run.'); | |
142 return; | |
143 } | |
144 | |
145 // taskDone() callback from each task. Increase the task index and call | |
146 // the next task. Note that explicit signaling by taskDone() in each | |
147 // task is needed because some of tests run asynchronously. | |
148 var taskDone = function () { | |
149 if (this.currentTask !== this.queue.length - 1) { | |
150 ++this.currentTask; | |
151 // debug('>> Audit.runTasks: ' + this.queue[this.currentTask]); | |
152 this.tasks[this.queue[this.currentTask]](taskDone); | |
153 } else { | |
154 this._finalize(); | |
155 } | |
156 return; | |
157 }.bind(this); | |
158 | |
159 // Start the first task. | |
160 // debug('>> Audit.runTasks: ' + this.queue[this.currentTask]); | |
161 this.tasks[this.queue[this.currentTask]](taskDone); | |
162 }; | |
163 | |
164 return { | |
165 createTaskRunner: function () { | |
166 return new Tasks(); | |
167 } | |
168 }; | |
169 | |
170 })(); | |
171 | |
172 | |
173 // |Should| JS layout test utility. | |
174 // Dependency: ../resources/js-test.js | |
175 var Should = (function () { | |
176 | |
177 'use strict'; | |
178 | |
179 // ShouldModel internal class. For the exposed (factory) method, it is the | |
180 // return value of this closure. | |
181 function ShouldModel(desc, target, opts) { | |
182 this.desc = desc; | |
183 this.target = target; | |
184 | |
185 // Check if the target contains any NaN value. | |
186 this._checkNaN(this.target, 'ACTUAL'); | |
187 | |
188 // |_testPassed| and |_testFailed| set this appropriately. | |
189 this._success = false; | |
190 | |
191 // If the number of errors is greater than this, the rest of error | |
192 // messages are suppressed. the value is fairly arbitrary, but shouldn't | |
193 // be too small or too large. | |
194 this.NUM_ERRORS_LOG = opts.numberOfErrorLog; | |
195 | |
196 // If the number of array elements is greater than this, the rest of | |
197 // elements will be omitted. | |
198 this.NUM_ARRAY_LOG = opts.numberOfArrayLog; | |
199 | |
200 // If true, verbose output for the failure case is printed, for methods
where this makes | |
201 // sense. | |
202 this.verbose = !opts.brief; | |
203 | |
204 // If set, this is the precision with which numbers will be printed. | |
205 this.PRINT_PRECISION = opts.precision; | |
206 } | |
207 | |
208 // Internal methods starting with a underscore. | |
209 ShouldModel.prototype._testPassed = function (msg, addNewline) { | |
210 this._success = true; | |
211 var newLine = addNewline ? '\n' : ''; | |
212 if (TESTHARNESS) { | |
213 // Using testharness.js | |
214 test(function () { | |
215 assert_true(true); | |
216 }, this.desc + ' ' + msg + '.' + newLine); | |
217 } else { | |
218 // Using js-test.js | |
219 testPassed(this.desc + ' ' + msg + '.' + newLine); | |
220 } | |
221 }; | |
222 | |
223 ShouldModel.prototype._testFailed = function (msg, addNewline) { | |
224 this._success = false; | |
225 var that = this; | |
226 var newLine = addNewline ? '\n' : ''; | |
227 if (TESTHARNESS) { | |
228 test(function () { | |
229 assert_true(false, that.desc + ' ' + msg + '.' + newLine); | |
230 }, this.desc); | |
231 } else { | |
232 testFailed(this.desc + ' ' + msg + '.' + newLine); | |
233 } | |
234 }; | |
235 | |
236 ShouldModel.prototype._isArray = function (arg) { | |
237 return arg instanceof Array || arg instanceof Float32Array || arg instance
of Uint8Array || | |
238 arg instanceof Uint16Array || arg instanceof Uint32Array || arg instance
of Int8Array || | |
239 arg instanceof Int16Array || arg instanceof Int32Array || arg instanceof
Uint8ClampedArray || | |
240 arg instanceof Float64Array; | |
241 }; | |
242 | |
243 ShouldModel.prototype._assert = function (expression, reason, value) { | |
244 if (expression) | |
245 return; | |
246 | |
247 var failureMessage = 'Assertion failed: ' + reason + ' ' + this.desc +'.
'; | |
248 if (arguments.length >= 3) | |
249 failureMessage += ": " + value; | |
250 | |
251 if (TESTHARNESS) { | |
252 test(function () { | |
253 assert_true(false, reason + ' (' + value + ')'); | |
254 }, this.desc) | |
255 } else { | |
256 testFailed(failureMessage); | |
257 } | |
258 | |
259 throw failureMessage; | |
260 }; | |
261 | |
262 // Check the expected value if it is a NaN (Number) or has NaN(s) in | |
263 // its content (Array or Float32Array). Returns a string depends on the | |
264 // result of check. | |
265 ShouldModel.prototype._checkNaN = function (value, label) { | |
266 var failureMessage = 'NaN found in ' + label + ' while testing "' + | |
267 this.desc + '"'; | |
268 | |
269 // Checking a single variable first. | |
270 if (Number.isNaN(value)) { | |
271 if (TESTHARNESS) { | |
272 test(function () { | |
273 assert_true(false, failureMessage); | |
274 }, this.desc) | |
275 } else { | |
276 testFailed(failureMessage); | |
277 } | |
278 | |
279 throw failureMessage; | |
280 } | |
281 | |
282 // If the value is not a NaN nor array, we can assume it is safe. | |
283 if (!this._isArray(value)) | |
284 return; | |
285 | |
286 // Otherwise, check the array array. | |
287 var indices = []; | |
288 for (var i = 0; i < value.length; i++) { | |
289 if (Number.isNaN(value[i])) | |
290 indices.push(i); | |
291 } | |
292 | |
293 if (indices.length === 0) | |
294 return; | |
295 | |
296 var failureDetail = ' (' + indices.length + ' instances total)\n'; | |
297 for (var n = 0; n < indices.length; n++) { | |
298 failureDetail += ' >> [' + indices[n] + '] = NaN\n'; | |
299 if (n >= this.NUM_ERRORS_LOG) { | |
300 failureDetail += ' and ' + (indices.length - n) + | |
301 ' more NaNs...'; | |
302 break; | |
303 } | |
304 } | |
305 | |
306 if (TESTHARNESS) { | |
307 test(function () { | |
308 assert_true(false, failureMessage + failureDetail); | |
309 }, this.desc) | |
310 } else { | |
311 testFailed(failureMessage + failureDetail); | |
312 } | |
313 | |
314 throw failureMessage; | |
315 }; | |
316 | |
317 // Check if |target| exists. | |
318 // | |
319 // Example: | |
320 // Should('Object', {}).exist(); | |
321 // Result: | |
322 // "PASS Object exists." | |
323 ShouldModel.prototype.exist = function () { | |
324 if (this.target !== null && this.target !== undefined) { | |
325 this._testPassed('exists'); | |
326 } else { | |
327 this._testFailed('does not exist'); | |
328 } | |
329 | |
330 return this._success; | |
331 }; | |
332 | |
333 // Check if |target| is equal to |value|. | |
334 // | |
335 // Example: | |
336 // Should('Zero', 0).beEqualTo(0); | |
337 // Result: | |
338 // "PASS Zero is equal to 0." | |
339 ShouldModel.prototype.beEqualTo = function (value) { | |
340 if (value != null) { | |
341 var type = typeof value; | |
342 this._assert(type === 'number' || type === 'string' || type === 'boo
lean', | |
343 'value should be number, string, or boolean for', value
); | |
344 } | |
345 | |
346 this._checkNaN(value, 'EXPECTED'); | |
347 | |
348 var outputValue = value; | |
349 if (type === 'string') | |
350 outputValue = '"' + outputValue + '"'; | |
351 if (this.target === value) { | |
352 var outputValue = (type === 'string') ? '"' + value + '"' : value; | |
353 this._testPassed('is equal to ' + outputValue); | |
354 } else { | |
355 var targetValue = this.target; | |
356 if (typeof this.target === 'string') | |
357 targetValue = '"' + targetValue + '"'; | |
358 this._testFailed('was ' + targetValue + ' instead of ' + outputValue
); | |
359 } | |
360 return this._success; | |
361 }; | |
362 | |
363 // Check if |target| is not equal to |value|. | |
364 // | |
365 // Example: | |
366 // Should('One', one).notBeEqualTo(0); | |
367 // Result: | |
368 // "PASS One is not equal to 0." | |
369 ShouldModel.prototype.notBeEqualTo = function (value) { | |
370 var type = typeof value; | |
371 this._assert(type === 'number' || type === 'string' || type === "boolean
", | |
372 'value should be number, string, or boolean for', value); | |
373 | |
374 this._checkNaN(value, 'EXPECTED'); | |
375 | |
376 if (this.target === value) | |
377 this._testFailed('should not be equal to ' + value); | |
378 else | |
379 this._testPassed('is not equal to ' + value); | |
380 return this._success; | |
381 }; | |
382 | |
383 // Check if |target| is greater than or equal to |value|. | |
384 // | |
385 // Example: | |
386 // Should("SNR", snr).beGreaterThanOrEqualTo(100); | |
387 // Result: | |
388 // "PASS SNR exceeds 100" | |
389 // "FAIL SNR (n) is not greater than or equal to 100" | |
390 ShouldModel.prototype.beGreaterThanOrEqualTo = function (value) { | |
391 var type = typeof value; | |
392 this._assert(type === 'number' || type === 'string', | |
393 'value should be number or string for', value); | |
394 | |
395 this._checkNaN(value, 'EXPECTED'); | |
396 | |
397 var prefix = '(' + this.target + ') '; | |
398 | |
399 if (this.target >= value) { | |
400 if (!this.verbose) | |
401 prefix = ''; | |
402 this._testPassed(prefix + "is greater than or equal to " + value); | |
403 } else { | |
404 this._testFailed(prefix + "is not greater than or equal to " + value
); | |
405 } | |
406 return this._success; | |
407 } | |
408 | |
409 // Check if |target| is greater than |value|. | |
410 // | |
411 // Example: | |
412 // Should("SNR", snr).beGreaterThan(100); | |
413 // Result: | |
414 // "PASS SNR is greater than 100" | |
415 // "FAIL SNR (n) is not greater than 100" | |
416 ShouldModel.prototype.beGreaterThan = function (value) { | |
417 var type = typeof value; | |
418 this._assert(type === 'number' || type === 'string', | |
419 'value should be number or string for', value); | |
420 | |
421 this._checkNaN(value, 'EXPECTED'); | |
422 | |
423 if (this.target > value) | |
424 this._testPassed("is greater than " + value); | |
425 else | |
426 this._testFailed("(" + this.target + ") is not greater than " + valu
e); | |
427 return this._success; | |
428 } | |
429 | |
430 // Check if |target| is lest than or equal to |value|. | |
431 // | |
432 // Example: | |
433 // maxError = 1e-6; | |
434 // Should("max error", maxError).beLessThanOrEqualTo(1e-5); | |
435 // Should("max error", maxError).beLessThanOrEqualTo(-1); | |
436 // Result: | |
437 // "PASS max error is less than or equal to 1e-5" | |
438 // "FAIL max error (1e-6) is not less than or equal to -1" | |
439 ShouldModel.prototype.beLessThanOrEqualTo = function (value) { | |
440 var type = typeof value; | |
441 this._assert(type === 'number', 'value should be number or string for',
value); | |
442 | |
443 this._checkNaN(value, 'EXPECTED'); | |
444 | |
445 var prefix = '(' + this.target + ') '; | |
446 | |
447 if (this.target <= value) { | |
448 if (!this.verbose) | |
449 prefix = ''; | |
450 this._testPassed(prefix + "is less than or equal to " + value); | |
451 } else { | |
452 this._testFailed(prefix + "is not less than or equal to " + value); | |
453 } | |
454 return this._success; | |
455 } | |
456 | |
457 // Check if |target| is close to |value| using the given relative error |thr
eshold|. |value| | |
458 // should not be zero, but no check is made for that. The |target| value is
printed to | |
459 // precision |precision|, with |precision| defaulting to 7. | |
460 // | |
461 // If |value| is 0, however, |threshold| is treated as an absolute threshold
. | |
462 // | |
463 // Example: | |
464 // Should("One", 1.001).beCloseTo(1, .1); | |
465 // Should("One", 2).beCloseTo(1, .1); | |
466 // Result: | |
467 // "PASS One is 1 within a relative error of 0.1." | |
468 // "FAIL One is not 1 within a relative error of 0.1: 2" | |
469 ShouldModel.prototype.beCloseTo = function (value, errorThreshold) { | |
470 var type = typeof value; | |
471 this._assert(type === 'number', 'value should be number for', value); | |
472 | |
473 this._checkNaN(value, 'EXPECTED'); | |
474 | |
475 if (value) { | |
476 var relativeError = Math.abs(this.target - value) / Math.abs(value); | |
477 if (relativeError <= errorThreshold) { | |
478 this._testPassed("is " + value.toPrecision(this.PRINT_PRECISION)
+ | |
479 " within a relative error of " + errorThreshold); | |
480 } else { | |
481 // Include actual relative error so the failed test case can be
updated with the actual | |
482 // relative error, if appropriate. | |
483 this._testFailed("is not " + value.toPrecision(this.PRINT_PRECIS
ION) + | |
484 " within a relative error of " + errorThreshold + | |
485 ": " + this.target + " with relative error " + relativeError | |
486 ); | |
487 } | |
488 } else { | |
489 var absoluteError = Math.abs(this.target - value); | |
490 if (absoluteError <= errorThreshold) { | |
491 this._testPassed("is " + value.toPrecision(this.PRINT_PRECISION)
+ | |
492 " within an absolute error of " + errorThreshold); | |
493 } else { | |
494 // Include actual absolute error so the failed test case can be
updated with the | |
495 // actual error, if appropriate. | |
496 this._testFailed("is not " + value.toPrecision(this.PRINT_PRECIS
ION) + | |
497 " within an absolute error of " + errorThreshold + | |
498 ": " + this.target + " with absolute error " + absoluteError | |
499 ); | |
500 } | |
501 } | |
502 return this._success; | |
503 } | |
504 | |
505 // Check if |func| throws an exception with a certain |errorType| correctly. | |
506 // |errorType| is optional. | |
507 // | |
508 // Example: | |
509 // Should('A bad code', function () { var a = b; }).throw(); | |
510 // Result: | |
511 // "PASS A bad code threw an exception." | |
512 // Example: | |
513 // Should('var c = d', function () { var c = d; }).throw('ReferenceError'); | |
514 // "PASS var c = d threw ReferenceError." | |
515 ShouldModel.prototype.throw = function (errorType) { | |
516 if (typeof this.target !== 'function') { | |
517 console.log('target is not a function. test halted.'); | |
518 return; | |
519 } | |
520 | |
521 try { | |
522 this.target(); | |
523 this._testFailed('did not throw an exception'); | |
524 } catch (error) { | |
525 if (errorType === undefined) | |
526 this._testPassed('threw an exception of type ' + error.name); | |
527 else if (error.name === errorType) | |
528 this._testPassed('threw ' + errorType + ': ' + error.message); | |
529 else if (self.hasOwnProperty(errorType) && error instanceof self[err
orType]) | |
530 this._testPassed('threw ' + errorType + ': ' + error.message); | |
531 else | |
532 this._testFailed('threw ' + error.name + ' instead of ' + errorT
ype); | |
533 } | |
534 return this._success; | |
535 }; | |
536 | |
537 // Check if |func| does not throw an exception. | |
538 // | |
539 // Example: | |
540 // Should('var foo = "bar"', function () { var foo = 'bar'; }).notThrow(); | |
541 // Result: | |
542 // "PASS var foo = "bar" did not throw an exception." | |
543 ShouldModel.prototype.notThrow = function () { | |
544 try { | |
545 this.target(); | |
546 this._testPassed('did not throw an exception'); | |
547 } catch (error) { | |
548 this._testFailed('threw ' + error.name + ': ' + error.message); | |
549 } | |
550 return this._success; | |
551 }; | |
552 | |
553 // Check if |target| array is filled with constant values. | |
554 // | |
555 // Example: | |
556 // Should('[2, 2, 2]', [2, 2, 2]).beConstantValueOf(2); | |
557 // Result: | |
558 // "PASS [2, 2, 2] has constant values of 2." | |
559 ShouldModel.prototype.beConstantValueOf = function (value) { | |
560 this._checkNaN(value, 'EXPECTED'); | |
561 | |
562 var mismatches = {}; | |
563 for (var i = 0; i < this.target.length; i++) { | |
564 if (this.target[i] !== value) | |
565 mismatches[i] = this.target[i]; | |
566 } | |
567 | |
568 var numberOfmismatches = Object.keys(mismatches).length; | |
569 | |
570 if (numberOfmismatches === 0) { | |
571 this._testPassed('contains only the constant ' + value); | |
572 } else { | |
573 var counter = 0; | |
574 var failureMessage = 'contains ' + numberOfmismatches + | |
575 ' values that are NOT equal to ' + value + ':'; | |
576 for (var index in mismatches) { | |
577 failureMessage += '\n[' + index + '] : ' + mismatches[index]; | |
578 if (++counter >= this.NUM_ERRORS_LOG) { | |
579 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
580 ' more differences...'; | |
581 break; | |
582 } | |
583 } | |
584 this._testFailed(failureMessage); | |
585 } | |
586 return this._success; | |
587 }; | |
588 | |
589 // Check if |target| array is identical to |expected| array element-wise. | |
590 // | |
591 // Example: | |
592 // Should('[1, 2, 3]', [1, 2, 3]).beEqualToArray([1, 2, 3]); | |
593 // Result: | |
594 // "PASS [1, 2, 3] is identical to the array [1,2,3]." | |
595 ShouldModel.prototype.beEqualToArray = function (array) { | |
596 this._assert(this._isArray(array) && this.target.length === array.length
, | |
597 'Invalid array or the length does not match.', array); | |
598 | |
599 this._checkNaN(array, 'EXPECTED'); | |
600 | |
601 var mismatches = {}; | |
602 for (var i = 0; i < this.target.length; i++) { | |
603 if (this.target[i] !== array[i]) | |
604 mismatches[i] = this.target[i]; | |
605 } | |
606 | |
607 var numberOfmismatches = Object.keys(mismatches).length; | |
608 var arrSlice = array.slice(0, this.NUM_ARRAY_LOG); | |
609 var arrStr = arrSlice[0].toPrecision(this.PRINT_PRECISION); | |
610 for (var k = 1; k < arrSlice.length; ++k) | |
611 arrStr += ',' + arrSlice[k].toPrecision(this.PRINT_PRECISION); | |
612 if (array.length > this.NUM_ARRAY_LOG) | |
613 arrStr += ',...'; | |
614 | |
615 if (numberOfmismatches === 0) { | |
616 this._testPassed('is identical to the array [' + arrStr + ']'); | |
617 } else { | |
618 var counter = 0; | |
619 var failureMessage = 'is not equal to the array [' + arrStr + ']'; | |
620 if (this.verbose) | |
621 failureMessage += '\nindex\tActual\t\tExpected'; | |
622 for (var index in mismatches) { | |
623 failureMessage += '\n[' + index + '] : ' + mismatches[index]; | |
624 if (this.verbose) | |
625 failureMessage += '\t' + array[index]; | |
626 if (++counter >= this.NUM_ERRORS_LOG) { | |
627 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
628 ' more differences...'; | |
629 break; | |
630 } | |
631 } | |
632 | |
633 this._testFailed(failureMessage); | |
634 } | |
635 return this._success; | |
636 }; | |
637 | |
638 // Check if |target| array is close to |expected| array element-wise within | |
639 // an certain error bound given by |absoluteThresholdOrOptions|. | |
640 // | |
641 // The error criterion is: | |
642 // | |
643 // Math.abs(target[k] - expected[k]) < Math.max(abserr, relerr * Math.abs(
expected)) | |
644 // | |
645 // If |absoluteThresholdOrOptions| is a number, t, then abserr = t and reler
r = 0. That is the | |
646 // max difference is bounded by t. | |
647 // | |
648 // If |absoluteThresholdOrOptions| is a property bag, then abserr is the val
ue of the | |
649 // absoluteThreshold property and relerr is the value of the relativeThresho
ld property. If | |
650 // nothing is given, then abserr = relerr = 0. If abserr = 0, then the erro
r criterion is a | |
651 // relative error. A non-zero abserr value produces a mix intended to handl
e the case where the | |
652 // expected value is 0, allowing the target value to differ by abserr from t
he expected. | |
653 // | |
654 // Example: | |
655 // Should('My array', [0.11, 0.19]).beCloseToArray([0.1, 0.2], 0.02); | |
656 // Result: | |
657 // "PASS My array equals [0.1,0.2] within an element-wise tolerance of 0.02.
" | |
658 ShouldModel.prototype.beCloseToArray = function (expected, absoluteThreshold
OrOptions) { | |
659 // For the comparison, the target length must be bigger than the expecte
d. | |
660 this._assert(this.target.length >= expected.length, | |
661 'The target array length must be longer than ' + expected.length + | |
662 ' but got ' + this.target.length + '.'); | |
663 | |
664 this._checkNaN(expected, 'EXPECTED'); | |
665 | |
666 var absoluteErrorThreshold = 0; | |
667 var relativeErrorThreshold = 0; | |
668 | |
669 // A collection of all of the values that satisfy the error criterion.
This holds the | |
670 // absolute difference between the target element and the expected eleme
nt. | |
671 var mismatches = {}; | |
672 | |
673 // Keep track of the max absolute error found | |
674 var maxAbsError = -Infinity; | |
675 var maxAbsErrorIndex = -1; | |
676 // Keep trac of the max relative error found, ignoring cases where the r
elative error is | |
677 // Infinity because the expected value is 0. | |
678 var maxRelError = -Infinity; | |
679 var maxRelErrorIndex = -1; | |
680 | |
681 // A number or string for printing out the actual thresholds used for th
e error criterion. | |
682 var maxAllowedError; | |
683 | |
684 // Set up the thresholds based on |absoluteThresholdOrOptions|. | |
685 if (typeof(absoluteThresholdOrOptions) === 'number') { | |
686 absoluteErrorThreshold = absoluteThresholdOrOptions; | |
687 maxAllowedError = absoluteErrorThreshold; | |
688 } else { | |
689 var opts = absoluteThresholdOrOptions; | |
690 if (opts.hasOwnProperty('absoluteThreshold')) | |
691 absoluteErrorThreshold = opts.absoluteThreshold; | |
692 if (opts.hasOwnProperty('relativeThreshold')) | |
693 relativeErrorThreshold = opts.relativeThreshold; | |
694 maxAllowedError = '{absoluteThreshold: ' + absoluteErrorThreshold | |
695 + ', relativeThreshold: ' + relativeErrorThreshold | |
696 + '}'; | |
697 } | |
698 | |
699 for (var i = 0; i < expected.length; i++) { | |
700 var diff = Math.abs(this.target[i] - expected[i]); | |
701 if (diff > Math.max(absoluteErrorThreshold, relativeErrorThreshold *
Math.abs(expected[i]))) { | |
702 mismatches[i] = diff; | |
703 // Keep track of the location of the absolute max difference. | |
704 if (diff > maxAbsError) { | |
705 maxAbsErrorIndex = i; | |
706 maxAbsError = diff; | |
707 } | |
708 // Keep track of the location of the max relative error, ignorin
g cases where the | |
709 // relative error is NaN. | |
710 var relError = diff / Math.abs(expected[i]); | |
711 if (!isNaN(relError) && relError > maxRelError) { | |
712 maxRelErrorIndex = i; | |
713 maxRelError = relError; | |
714 } | |
715 } | |
716 } | |
717 | |
718 var numberOfmismatches = Object.keys(mismatches).length; | |
719 var arrSlice = expected.slice(0, Math.min(expected.length, this.NUM_ARRA
Y_LOG)); | |
720 var arrStr; | |
721 | |
722 arrStr = arrSlice[0].toPrecision(this.PRINT_PRECISION); | |
723 for (var k = 1; k < arrSlice.length; ++k) | |
724 arrStr += ',' + arrSlice[k].toPrecision(this.PRINT_PRECISION); | |
725 | |
726 if (expected.length > this.NUM_ARRAY_LOG) | |
727 arrStr += ',...'; | |
728 if (numberOfmismatches === 0) { | |
729 this._testPassed('equals [' + arrStr + | |
730 '] with an element-wise tolerance of ' + maxAllowedError); | |
731 } else { | |
732 var counter = 0; | |
733 var failureMessage = 'does not equal [' + arrStr + | |
734 '] with an element-wise tolerance of ' + maxAllowedError; | |
735 | |
736 // Print a nice header for the table to follow. | |
737 if (this.verbose) | |
738 failureMessage += "\nIndex Actual Expected
Diff Relative"; | |
739 else | |
740 failureMessage += "\nDifference between expected and actual:"; | |
741 | |
742 for (var index in mismatches) { | |
743 failureMessage += '\n[' + index + ']: '; | |
744 if (this.verbose) { | |
745 // When verbose, print out actual, expected, absolute error,
and relative error. | |
746 // TODO: print these out in nice columns to make it easier t
o read. | |
747 var relError = Math.abs(this.target[index] - expected[index]
) / Math.abs(expected[index]); | |
748 failureMessage += this.target[index].toExponential(16) + '
' | |
749 + expected[index].toExponential(16) + ' ' | |
750 + mismatches[index].toExponential(16) + ' ' | |
751 + relError.toExponential(16) + ' ' | |
752 + Math.max(absoluteErrorThreshold, | |
753 relativeErrorThreshold * Math.abs(expecte
d[index])); | |
754 } else { | |
755 // Otherwise, just the print the absolute error. | |
756 failureMessage += mismatches[index]; | |
757 } | |
758 if (++counter >= this.NUM_ERRORS_LOG) { | |
759 failureMessage += '\nand ' + (numberOfmismatches - counter)
+ | |
760 ' more differences, with max absolute error'; | |
761 if (this.verbose) { | |
762 // When verbose, print out the location of both the max
absolute error and | |
763 // the max relative error so we can adjust thresholds ap
propriately in the | |
764 // test. | |
765 var relError = Math.abs(this.target[maxAbsErrorIndex] -
expected[maxAbsErrorIndex]) | |
766 / Math.abs(expected[maxAbsErrorIndex]); | |
767 failureMessage += ' at index ' + maxAbsErrorIndex + ':'; | |
768 failureMessage += '\n[' + maxAbsErrorIndex + ']: '; | |
769 failureMessage += this.target[maxAbsErrorIndex].toExpone
ntial(16) + ' ' | |
770 + expected[maxAbsErrorIndex].toExponential(16) +
' ' | |
771 + mismatches[maxAbsErrorIndex].toExponential(16)
+ ' ' | |
772 + relError.toExponential(16) + ' ' | |
773 + Math.max(absoluteErrorThreshold, | |
774 relativeErrorThreshold * Math.abs(expected[m
axAbsErrorIndex])); | |
775 failureMessage += '\nand max relative error'; | |
776 failureMessage += ' at index ' + maxRelErrorIndex + ':'; | |
777 failureMessage += '\n[' + maxRelErrorIndex + ']: '; | |
778 failureMessage += this.target[maxRelErrorIndex].toExpone
ntial(16) + ' ' | |
779 + expected[maxRelErrorIndex].toExponential(16) +
' ' | |
780 + mismatches[maxRelErrorIndex].toExponential(16)
+ ' ' | |
781 + maxRelError.toExponential(16) + ' ' | |
782 + Math.max(absoluteErrorThreshold, | |
783 relativeErrorThreshold * Math.abs(expected[m
axRelErrorIndex])); | |
784 } else { | |
785 // Not verbose, so just print out the max absolute error | |
786 failureMessage += ' of ' + maxAbsError + ' at index ' +
maxAbsErrorIndex; | |
787 } | |
788 break; | |
789 } | |
790 } | |
791 | |
792 this._testFailed(failureMessage); | |
793 } | |
794 return this._success; | |
795 }; | |
796 | |
797 // Check if |target| array contains a set of values in a certain order. | |
798 // | |
799 // Example: | |
800 // Should('My random array', [1, 1, 3, 3, 2]).containValues([1, 3, 2]); | |
801 // Result: | |
802 // "PASS My random array contains all the expected values in the correct | |
803 // order: [1,3,2]." | |
804 ShouldModel.prototype.containValues = function (expected) { | |
805 this._checkNaN(expected, 'EXPECTED'); | |
806 | |
807 var indexExpected = 0, indexActual = 0; | |
808 while (indexExpected < expected.length && indexActual < this.target.leng
th) { | |
809 if (expected[indexExpected] === this.target[indexActual]) | |
810 indexActual++; | |
811 else | |
812 indexExpected++; | |
813 } | |
814 | |
815 if (indexExpected < expected.length-1 || indexActual < this.target.lengt
h-1) { | |
816 this._testFailed('contains an unexpected value ' + this.target[index
Actual] + | |
817 ' at index ' + indexActual); | |
818 } else { | |
819 this._testPassed('contains all the expected values in the correct or
der: [' + | |
820 expected + ']'); | |
821 } | |
822 return this._success; | |
823 }; | |
824 | |
825 // Check if |target| array does not have any glitches. Note that |threshold| | |
826 // is not optional and is to define the desired threshold value. | |
827 // | |
828 // Example: | |
829 // Should('Channel #0', chanL).notGlitch(0.0005); | |
830 // Result: | |
831 // "PASS Channel #0 has no glitch above the threshold of 0.0005." | |
832 ShouldModel.prototype.notGlitch = function (threshold) { | |
833 this._checkNaN(threshold, 'EXPECTED'); | |
834 | |
835 for (var i = 1; i < this.target.length; i++) { | |
836 var diff = Math.abs(this.target[i-1] - this.target[i]); | |
837 if (diff >= threshold) { | |
838 this._testFailed('has a glitch at index ' + i + ' of size ' + di
ff); | |
839 return this._success; | |
840 } | |
841 } | |
842 this._testPassed('has no glitch above the threshold of ' + threshold); | |
843 return this._success; | |
844 }; | |
845 | |
846 // Check if the target promise is resolved correctly. | |
847 // | |
848 // Example: | |
849 // Should('My promise', promise).beResolved().then(nextStuff); | |
850 // Result: | |
851 // "PASS My promise resolved correctly." | |
852 // "FAIL My promise rejected incorrectly (with _ERROR_)." | |
853 ShouldModel.prototype.beResolved = function () { | |
854 return this.target.then(function () { | |
855 this._testPassed('resolved correctly'); | |
856 }.bind(this), function (err) { | |
857 this._testFailed('rejected incorrectly (with ' + err + ')'); | |
858 }.bind(this)); | |
859 }; | |
860 | |
861 // Check if the target promise is rejected correctly. | |
862 // | |
863 // Example: | |
864 // Should('My promise', promise).beRejected().then(nextStuff); | |
865 // Result: | |
866 // "PASS My promise rejected correctly (with _ERROR_)." | |
867 // "FAIL My promise resolved incorrectly." | |
868 ShouldModel.prototype.beRejected = function () { | |
869 return this.target.then(function () { | |
870 this._testFailed('resolved incorrectly'); | |
871 }.bind(this), function (err) { | |
872 this._testPassed('rejected correctly (with ' + err + ')'); | |
873 }.bind(this)); | |
874 }; | |
875 | |
876 // A summary message | |
877 // | |
878 // Example: | |
879 // Should("Summary1", true).summarize("passed1", "failed1"); | |
880 // Should("Summary2", false).summarize("passed2", "failed2"); | |
881 // Result: | |
882 // "PASS Summary1: passed1." | |
883 // "FAIL Summary2: failed2." | |
884 ShouldModel.prototype.summarize = function (pass, fail) { | |
885 // It's really nice to have blank lines after the summary, but | |
886 // testharness thinks the whole testsuite fails if we do that. | |
887 if (this.target) | |
888 this._testPassed(pass, false); | |
889 else | |
890 this._testFailed(fail, false); | |
891 return this._success; | |
892 } | |
893 | |
894 // Should() method. | |
895 // | |
896 // |desc| is the description of the task or check and |target| is a value | |
897 // needs to be checked or a task to be performed. |opt| contains options for | |
898 // printing out log messages: options are |opt.numberOfErrorLog| and | |
899 // |opts.numberOfArrayLog|. | |
900 return function (desc, target, opts) { | |
901 var _opts = { | |
902 numberOfErrorLog: 8, | |
903 numberOfArrayLog: 16, | |
904 verbose: true | |
905 }; | |
906 | |
907 if (opts instanceof Object) { | |
908 if (opts.hasOwnProperty('numberOfErrorLog')) | |
909 _opts.numberOfErrorLog = opts.numberOfErrorLog; | |
910 if (opts.hasOwnProperty('numberOfArrayLog')) | |
911 _opts.numberOfArrayLog = opts.numberOfArrayLog; | |
912 if (opts.hasOwnProperty('brief')) | |
913 _opts.brief = opts.brief; | |
914 if (opts.hasOwnProperty('precision')) | |
915 _opts.precision = opts.precision; | |
916 } | |
917 | |
918 return new ShouldModel(desc, target, _opts); | |
919 }; | |
920 | |
921 })(); | |
OLD | NEW |