Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / src / base / unittest.js
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 base.requireStylesheet('ui.trace_viewer');
8 base.requireStylesheet('base.unittest');
9
10 base.require('base.key_event_manager');
11 base.require('base.promise');
12 base.require('base.settings');
13 base.require('base.unittest.test_error');
14 base.require('base.unittest.assertions');
15
16 base.exportTo('base.unittest', function() {
17   var TestStatus = {
18     FAILED: 0,
19     PASSED: 1,
20     PENDING: 2
21   };
22
23   var TestTypes = {
24     UNITTEST: 0,
25     PERFTEST: 1
26   };
27
28   var showCondensed_ = false;
29   var testType_ = TestTypes.UNITTEST;
30
31   function showCondensed(val) {
32     showCondensed_ = val;
33   }
34
35   function testType(val) {
36     if (val === 'perf')
37       testType_ = TestTypes.PERFTEST;
38     else
39       testType_ = TestTypes.UNITTEST;
40   }
41
42   function logWarningMessage(message) {
43     var messagesEl = document.querySelector('#messages');
44     messagesEl.setAttribute('hasMessages', true);
45
46     var li = document.createElement('li');
47     li.innerText = message;
48
49     var list = document.querySelector('#message-list');
50     list.appendChild(li);
51   }
52
53   function TestRunner(tests) {
54     this.suites_ = [];
55     this.suiteNames_ = {};
56     this.tests_ = tests || [];
57     this.moduleCount_ = 0;
58
59     this.stats_ = {
60       tests: 0,
61       failures: 0,
62       exceptions: [],
63       duration: 0.0
64     };
65   }
66
67   TestRunner.prototype = {
68     __proto__: Object.prototype,
69
70     run: function() {
71       this.clear_(document.querySelector('#test-results'));
72       this.clear_(document.querySelector('#exception-list'));
73
74       this.updateStats_();
75       this.runSuites_();
76     },
77
78     addSuite: function(suite) {
79       if (this.suiteNames_[suite.name] === true)
80         logWarningMessage('Duplicate test suite name detected: ' + suite.name);
81
82       this.suites_.push(suite);
83       this.suiteNames_[suite.name] = true;
84     },
85
86     get suiteCount() {
87       return this.suites_.length;
88     },
89
90     clear_: function(el) {
91       while (el.firstChild)
92         el.removeChild(el.firstChild);
93     },
94
95     runSuites_: function(opt_idx) {
96       var idx = opt_idx || 0;
97
98       var suiteCount = this.suites_.length;
99       if (idx >= suiteCount) {
100         var harness = document.querySelector('#test-results');
101         harness.appendChild(document.createElement('br'));
102         harness.appendChild(document.createTextNode('Test Run Complete'));
103         return;
104       }
105
106       var suite = this.suites_[idx];
107       suite.showLongResults = (suiteCount === 1);
108       suite.displayInfo();
109       return suite.runTests(this.tests_).then(function(ignored) {
110         this.stats_.duration += suite.duration;
111         this.stats_.tests += suite.testCount;
112         this.stats_.failures += suite.failureCount;
113
114         this.updateStats_();
115         return this.runSuites_(idx + 1);
116       }.bind(this));
117     },
118
119     onAnimationFrameError: function(e, opt_stack) {
120       if (e.message)
121         console.error(e.message, e.stack);
122       else
123         console.error(e);
124
125       var exception = {e: e, stack: opt_stack};
126       this.stats_.exceptions.push(exception);
127       this.appendException(exception);
128       this.updateStats_();
129     },
130
131     updateStats_: function() {
132       var statEl = document.querySelector('#stats');
133       statEl.innerHTML =
134           this.suites_.length + ' suites, ' +
135           '<span class="passed">' + this.stats_.tests + '</span> tests, ' +
136           '<span class="failed">' + this.stats_.failures +
137           '</span> failures, ' +
138           '<span class="exception">' + this.stats_.exceptions.length +
139           '</span> exceptions,' +
140           ' in ' + this.stats_.duration.toFixed(2) + 'ms.';
141     },
142
143     appendException: function(exc) {
144       var exceptionsEl = document.querySelector('#exceptions');
145       exceptionsEl.setAttribute('hasExceptions', this.stats_.exceptions.length);
146
147       var excEl = document.createElement('li');
148       excEl.innerHTML = exc.e + '<pre>' + exc.stack + '</pre>';
149
150       var exceptionsEl = document.querySelector('#exception-list');
151       exceptionsEl.appendChild(excEl);
152     }
153   };
154
155   function TestSuite(name, suite) {
156     this.name_ = name;
157     this.tests_ = [];
158     this.testNames_ = {};
159     this.failures_ = [];
160     this.showLongResults = false;
161     this.duration_ = 0.0;
162     this.resultsEl_ = undefined;
163
164     global.setupOnce = function(fn) { this.setupOnceFn_ = fn; }.bind(this);
165     global.setup = function(fn) { this.setupFn_ = fn; }.bind(this);
166     global.teardown = function(fn) { this.teardownFn_ = fn; }.bind(this);
167
168     global.test = function(name, test, options) {
169       options = options || {};
170
171       if (this.testNames_[name] === true)
172         logWarningMessage('Duplicate test name detected: ' + name);
173
174       var testName = name;
175       // If the test cares about DPI settings then we first push a test
176       // that fakes the DPI as the low or hi Dpi version, depending on what
177       // we're current using.
178       if (options.dpiAware) {
179         var defaultDevicePixelRatio = window.devicePixelRatio;
180         var dpi = defaultDevicePixelRatio > 1 ? 1 : 2;
181
182         var testWrapper = function() {
183           window.devicePixelRatio = dpi;
184           test.bind(this).call();
185           window.devicePixelRatio = defaultDevicePixelRatio;
186         };
187
188         var newName = name;
189         if (dpi === 1) {
190           newName += '_loDPI';
191           testName += '_hiDPI';
192         } else {
193           newName += '_hiDPI';
194           testName += '_loDPI';
195         }
196
197         this.tests_.push(new Test(newName, testWrapper, options || {}));
198       }
199
200       this.tests_.push(new Test(testName, test, options || {}));
201       this.testNames_[name] = true;
202     }.bind(this);
203
204     global.perfTest = function(name, test, options) {
205       if (this.testNames_[name] === true)
206         logWarningMessage('Duplicate test name detected: ' + name);
207
208       this.tests_.push(new PerfTest(name, test, options || {}));
209       this.testNames_[name] = true;
210     }.bind(this);
211
212     global.timedPerfTest = function(name, test, options) {
213       if (options === undefined || options.iterations === undefined)
214         throw new Error('timedPerfTest must have iteration option provided.');
215
216       name += '_' + options.iterations;
217       if (this.testNames_[name] === true)
218         logWarningMessage('Duplicate test name detected: ' + name);
219
220       options.results = options.results || TimingTestResult;
221       var testWrapper = function(results) {
222         results.testCount = options.iterations;
223         for (var i = 0; i < options.iterations; ++i) {
224           var start = window.performance.now();
225           test.bind(this).call();
226           results.add(window.performance.now() - start);
227         }
228       };
229
230       this.tests_.push(new PerfTest(name, testWrapper, options));
231       this.testNames_[name] = true;
232     }.bind(this);
233
234     suite.call();
235
236     global.setupOnce = undefined;
237     global.setup = undefined;
238     global.teardown = undefined;
239     global.test = undefined;
240     global.perfTest = undefined;
241     global.timedPerfTest = undefined;
242   }
243
244   TestSuite.prototype = {
245     __proto__: Object.prototype,
246
247     get name() {
248       return this.name_;
249     },
250
251     get results() {
252       return (this.failureCount > 0) ? TestStatus.FAILED : TestStatus.PASSED;
253     },
254
255     get testCount() {
256       return this.tests_.length;
257     },
258
259     get failureCount() {
260       return this.failures.length;
261     },
262
263     get failures() {
264       return this.failures_;
265     },
266
267     get duration() {
268       return this.duration_;
269     },
270
271     displayInfo: function() {
272       this.resultsEl_ = document.createElement('div');
273       this.resultsEl_.className = 'test-result';
274
275       var resultsPanel = document.querySelector('#test-results');
276       resultsPanel.appendChild(this.resultsEl_);
277
278       if (this.showLongResults) {
279         this.resultsEl_.innerText = this.name;
280       } else {
281         var link = '/src/tests.html?suite=';
282         link += this.name.replace(/\./g, '/');
283         link += '&type=' + (testType_ === TestTypes.PERFTEST ? 'perf' : 'unit');
284
285         var suiteInfo = document.createElement('a');
286         suiteInfo.href = link;
287         suiteInfo.innerText = this.name;
288         this.resultsEl_.appendChild(suiteInfo);
289       }
290
291       var statusEl = document.createElement('span');
292       statusEl.classList.add('results');
293       statusEl.classList.add('pending');
294       statusEl.innerText = 'pending';
295       this.resultsEl_.appendChild(statusEl);
296     },
297
298     runTests: function(testsToRun) {
299       this.testsToRun_ = testsToRun;
300
301       if (this.setupOnceFn_ !== undefined)
302         this.setupOnceFn_.bind(this).call();
303
304       var remainingTests;
305       if (testsToRun.length) {
306         remainingTests = this.tests_.reduce(function(remainingTests, test) {
307           if (this.testsToRun_.indexOf(test.name) !== -1)
308             remainingTests.push(test);
309           return remainingTests;
310         }.bind(this), []);
311       } else {
312         remainingTests = this.tests_.slice(0);
313       }
314
315       return this.runRemainingTests_(remainingTests).then(
316           function resolve(ignored) {
317             this.duration_ = this.tests_.reduce(function(total, test) {
318               return total += test.duration;
319             }, 0);
320             this.outputResults();
321           }.bind(this),
322           function reject(e) {
323             console.error(e);
324             this.outputResults();
325           }.bind(this)
326       );
327     },
328
329     runRemainingTests_: function(remainingTests) {
330       if (!remainingTests.length)
331         return base.Promise.resolve();
332       var test = remainingTests.pop();
333
334       // Clear settings storage before each test.
335       global.sessionStorage.clear();
336       base.Settings.setAlternativeStorageInstance(global.sessionStorage);
337       base.onAnimationFrameError =
338           testRunners[testType_].onAnimationFrameError.bind(
339               testRunners[testType_]);
340       base.KeyEventManager.resetInstanceForUnitTesting();
341
342       var testWorkAreaEl_ = document.createElement('div');
343       this.resultsEl_.appendChild(testWorkAreaEl_);
344
345       test.workArea = testWorkAreaEl_;
346       if (this.setupFn_ !== undefined)
347         this.setupFn_.bind(test).call();
348
349       var suite = this;
350       return test.run.then(function(ignore) {
351         test.status = TestStatus.PASSED;
352         suite.testTearDown_(test);
353         return suite.runRemainingTests_(remainingTests);
354       }, function(error) {
355         var stack = error && error.stack ? error.stack : '';
356         console.error("Rejected, cause: \'" + error + "\'", stack);
357         test.status = TestStatus.FAILED;
358         test.failure = error;
359         suite.failures_.push({
360           error: test.failure,
361           test: test.name
362         });
363         suite.testTearDown_(test);
364         return suite.runRemainingTests_(remainingTests);
365       });
366     },
367
368     testTearDown_: function(test) {
369       this.resultsEl_.removeChild(test.workArea);
370
371       if (this.teardownFn_ !== undefined)
372         this.teardownFn_.bind(test).call();
373     },
374
375     outputResults: function() {
376       if ((this.results === TestStatus.PASSED) && showCondensed_ &&
377           !this.showLongResults) {
378         var parent = this.resultsEl_.parentNode;
379         parent.removeChild(this.resultsEl_);
380         this.resultsEl_ = undefined;
381
382         parent.appendChild(document.createTextNode('.'));
383         return;
384       }
385
386       var status = this.resultsEl_.querySelector('.results');
387       status.classList.remove('pending');
388       if (this.results === TestStatus.PASSED) {
389         status.innerText = 'passed';
390         status.classList.add('passed');
391       } else {
392         status.innerText = 'FAILED';
393         status.classList.add('failed');
394       }
395
396       status.innerText += ' (' + this.duration_.toFixed(2) + 'ms)';
397
398       var child = this.showLongResults ? this.outputLongResults() :
399                                          this.outputShortResults();
400       if (child !== undefined)
401         this.resultsEl_.appendChild(child);
402     },
403
404     outputShortResults: function() {
405       if (this.results === TestStatus.PASSED)
406         return undefined;
407
408       var parent = document.createElement('div');
409
410       var failureList = this.failures;
411       for (var i = 0; i < failureList.length; ++i) {
412         var fail = failureList[i];
413
414         var preEl = document.createElement('pre');
415         preEl.className = 'failure';
416         preEl.innerText = 'Test: ' + fail.test + '\n' + fail.error.stack;
417         parent.appendChild(preEl);
418       }
419
420       return parent;
421     },
422
423     outputLongResults: function() {
424       var parent = document.createElement('div');
425
426       this.tests_.forEach(function(test) {
427         if (this.testsToRun_.length !== 0 &&
428             this.testsToRun_.indexOf(test.name) === -1)
429           return;
430
431         // Construct an individual result div.
432         var testEl = document.createElement('div');
433         testEl.className = 'individual-result';
434
435         var link = '/src/tests.html?suite=';
436         link += this.name.replace(/\./g, '/');
437         link += '&test=' + test.name.replace(/\./g, '/');
438         link += '&type=' +
439             (testType_ === TestTypes.PERFTEST ? 'perf' : 'unit');
440
441         var suiteInfo = document.createElement('a');
442         suiteInfo.href = link;
443         suiteInfo.innerText = test.name;
444         testEl.appendChild(suiteInfo);
445
446         parent.appendChild(testEl);
447
448         var resultEl = document.createElement('span');
449         resultEl.classList.add('results');
450         testEl.appendChild(resultEl);
451         if (test.status === TestStatus.PASSED) {
452           resultEl.classList.add('passed');
453           resultEl.innerText =
454               'passed (' + test.output() + ')';
455         } else if (test.status === TestStatus.PENDING) {
456           resultEl.classList.add('failed');
457           resultEl.innerText = 'PENDING...TIMEOUT';
458         } else {
459           resultEl.classList.add('failed');
460           resultEl.innerText = 'FAILED';
461
462           var preEl = document.createElement('pre');
463           preEl.className = 'failure';
464           preEl.innerText = test.failure.stack || test.failure;
465           testEl.appendChild(preEl);
466         }
467
468         if (test.hasAppendedContent)
469           testEl.appendChild(test.appendedContent);
470       }.bind(this));
471
472       return parent;
473     },
474
475     toString: function() {
476       return this.name_;
477     }
478   };
479
480   function Test(name, test, options) {
481     this.name_ = name;
482     this.test_ = test;
483     this.isPerfTest_ = false;
484     this.options_ = options;
485     this.failure_ = undefined;
486     this.duration_ = 0;
487     this.status_ = TestStatus.PENDING;
488
489     this.appendedContent_ = undefined;
490   }
491
492   Test.prototype = {
493     __proto__: Object.prototype,
494
495     get run() {
496       var test = this;
497       return new base.Promise(function(r) {
498         var startTime = window.performance.now();
499         try {
500           var maybePromise = test.test_();
501           if (maybePromise) {
502             // An async test may not have completed.
503             maybePromise.then(function(ignored) {
504               test.duration = window.performance.now() - startTime;
505               r.resolve();
506             }, r.reject);
507           } else {
508             test.duration = window.performance.now() - startTime;
509             r.resolve();
510           }
511         } catch (e) {
512           test.duration = window.performance.now() - startTime;
513           r.reject(e);
514         }
515       });
516     },
517
518     get failure() {
519       return this.failure_;
520     },
521
522     set failure(val) {
523       this.failure_ = val;
524     },
525
526     get name() {
527       return this.name_;
528     },
529
530     get isPerfTest() {
531       return this.isPerfTest_;
532     },
533
534     get testRuns() {
535       return this.testRuns_;
536     },
537
538     get status() {
539       return this.status_;
540     },
541
542     set status(val) {
543       this.status_ = val;
544     },
545
546     get duration() {
547       return this.duration_;
548     },
549
550     get options() {
551       return this.options_;
552     },
553
554     set duration(duration) {
555       this.duration_ = duration;
556     },
557
558     get hasAppendedContent() {
559       return (this.appendedContent_ !== undefined);
560     },
561
562     get appendedContent() {
563       return this.appendedContent_;
564     },
565
566     get workArea() {
567       return this.testWorkArea_;
568     },
569
570     set workArea(workArea) {
571       this.testWorkArea_ = workArea;
572     },
573
574     addHTMLOutput: function(element) {
575       this.testWorkArea_.appendChild(element);
576       this.appendedContent_ = element;
577     },
578
579     toString: function() {
580       return this.name_;
581     },
582
583     output: function() {
584       return this.duration_.toFixed(2) + 'ms';
585     }
586   };
587
588   function PerfTest(name, test, options) {
589     Test.apply(this, arguments);
590     this.isPerfTest_ = true;
591
592     var resultObject = options.results || TestResult;
593     this.results_ = new resultObject();
594   }
595
596   PerfTest.prototype = {
597     __proto__: Test.prototype,
598
599     run: function() {
600       try {
601         this.test_.call(this, this.results_);
602         this.status_ = TestStatus.PASSED;
603       } catch (e) {
604         console.error(e, e.stack);
605         this.failure_ = e;
606       }
607     },
608
609     output: function() {
610       return this.results_.output();
611     }
612   };
613
614   var testRunners = {};
615   var totalSuiteCount_ = 0;
616
617   function allSuitesLoaded_() {
618     return (testRunners[TestTypes.UNITTEST].suiteCount +
619         testRunners[TestTypes.PERFTEST].suiteCount) >= totalSuiteCount_;
620   }
621
622   function testSuite(name, suite) {
623     testRunners[TestTypes.UNITTEST].addSuite(new TestSuite(name, suite));
624     if (allSuitesLoaded_())
625       runSuites();
626   }
627
628   function perfTestSuite(name, suite) {
629     testRunners[TestTypes.PERFTEST].addSuite(new TestSuite(name, suite));
630     if (allSuitesLoaded_())
631       runSuites();
632   }
633
634   function Suites(suitePaths, tests) {
635     // Assume one suite per file.
636     totalSuiteCount_ = suitePaths.length;
637
638     testRunners[TestTypes.UNITTEST] = new TestRunner(tests);
639     testRunners[TestTypes.PERFTEST] = new TestRunner(tests);
640
641     var modules = [];
642     suitePaths.forEach(function(path) {
643       var moduleName = path.slice(5, path.length - 3);
644       moduleName = moduleName.replace(/\//g, '.');
645       modules.push(moduleName);
646     });
647     base.require(modules);
648   }
649
650   function runSuites() {
651     testRunners[testType_].run();
652   }
653
654   function TestResult() {
655     this.results_ = [];
656   }
657   TestResult.prototype = {
658     add: function(result) {
659       this.results_.push(result);
660     },
661
662     output: function() {
663       return this.results_.join(', ');
664     }
665   };
666
667   function TimingTestResult() {
668     TestResult.apply(this, arguments);
669     this.runCount_ = 0;
670   }
671   TimingTestResult.prototype = {
672     __proto__: TestResult.prototype,
673
674     set testCount(runs) {
675       this.runCount_ = runs;
676     },
677
678     output: function() {
679       var totalTime = 0.0;
680       this.results_.forEach(function(t) { totalTime += t; });
681       return totalTime.toFixed(2) + 'ms) ' + this.runCount_ + ' runs, ' +
682           'avg ' + (totalTime / this.runCount_).toFixed(2) + 'ms/run';
683     }
684   };
685
686   return {
687     showCondensed: showCondensed,
688     testType: testType,
689     testSuite: testSuite,
690     perfTestSuite: perfTestSuite,
691     runSuites: runSuites,
692     Suites: Suites,
693
694     TestSuite_: TestSuite
695   };
696 });