Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(760)

Side by Side Diff: ppapi/native_client/tools/browser_tester/browserdata/nacltest.js

Issue 10914053: Relocating files in the nacl repo that belong in chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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
6 function $(id) {
7 return document.getElementById(id);
8 }
9
10
11 function createNaClEmbed(args) {
12 var fallback = function(value, default_value) {
13 return value !== undefined ? value : default_value;
14 };
15 var embed = document.createElement('embed');
16 embed.id = args.id;
17 embed.src = args.src;
18 embed.type = fallback(args.type, 'application/x-nacl');
19 // JavaScript inconsistency: this is equivalent to class=... in HTML.
20 embed.className = fallback(args.className, 'naclModule');
21 embed.width = fallback(args.width, 0);
22 embed.height = fallback(args.height, 0);
23 return embed;
24 }
25
26
27 function decodeURIArgs(encoded) {
28 var args = {};
29 if (encoded.length > 0) {
30 var pairs = encoded.replace(/\+/g, ' ').split('&');
31 for (var p = 0; p < pairs.length; p++) {
32 var pair = pairs[p].split('=');
33 if (pair.length != 2) {
34 throw "Malformed argument key/value pair: '" + pairs[p] + "'";
35 }
36 args[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
37 }
38 }
39 return args;
40 }
41
42
43 function addDefaultsToArgs(defaults, args) {
44 for (var key in defaults) {
45 if (!(key in args)) {
46 args[key] = defaults[key];
47 }
48 }
49 }
50
51
52 // Return a dictionary of arguments for the test. These arguments are passed
53 // in the query string of the main page's URL. Any time this function is used,
54 // default values should be provided for every argument. In some cases a test
55 // may be run without an expected query string (manual testing, for example.)
56 // Careful: all the keys and values in the dictionary are strings. You will
57 // need to manually parse any non-string values you wish to use.
58 function getTestArguments(defaults) {
59 var encoded = window.location.search.substring(1);
60 var args = decodeURIArgs(encoded);
61 if (defaults !== undefined) {
62 addDefaultsToArgs(defaults, args);
63 }
64 return args;
65 }
66
67
68 function exceptionToLogText(e) {
69 if (typeof e == 'object' && 'message' in e && 'stack' in e) {
70 return e.message + '\n' + e.stack.toString();
71 } else if (typeof(e) == 'string') {
72 return e;
73 } else {
74 return toString(e)
75 }
76 }
77
78
79 // Logs test results to the server using URL-encoded RPC.
80 // Also logs the same test results locally into the DOM.
81 function RPCWrapper() {
82 // Work around how JS binds 'this'
83 var this_ = this;
84 // It is assumed RPC will work unless proven otherwise.
85 this.rpc_available = true;
86 // Set to true if any test fails.
87 this.ever_failed = false;
88 // Async calls can make it faster, but it can also change order of events.
89 this.async = false;
90
91 // Called if URL-encoded RPC gets a 404, can't find the server, etc.
92 function handleRPCFailure(name, message) {
93 // This isn't treated as a testing error - the test can be run without a
94 // web server that understands RPC.
95 this_.logLocal('RPC failure for ' + name + ': ' + message + ' - If you ' +
96 'are running this test manually, this is not a problem.',
97 'gray');
98 this_.disableRPC();
99 }
100
101 function handleRPCResponse(name, req) {
102 if (req.status == 200) {
103 if (req.responseText == 'Die, please') {
104 // TODO(eugenis): this does not end the browser process on Mac.
105 window.close();
106 } else if (req.responseText != 'OK') {
107 this_.logLocal('Unexpected RPC response to ' + name + ': \'' +
108 req.responseText + '\' - If you are running this test ' +
109 'manually, this is not a problem.', 'gray');
110 this_.disableRPC();
111 }
112 } else {
113 handleRPCFailure(name, req.status.toString());
114 }
115 }
116
117 // Performs a URL-encoded RPC call, given a function name and a dictionary
118 // (actually just an object - it's a JS idiom) of parameters.
119 function rpcCall(name, params) {
120 if (this_.rpc_available) {
121 // Construct the URL for the RPC request.
122 var args = [];
123 for (var pname in params) {
124 pvalue = params[pname];
125 args.push(encodeURIComponent(pname) + '=' + encodeURIComponent(pvalue));
126 }
127 var url = '/TESTER/' + name + '?' + args.join('&');
128 var req = new XMLHttpRequest();
129 // Async result handler
130 if (this_.async) {
131 req.onreadystatechange = function() {
132 if (req.readyState == XMLHttpRequest.DONE) {
133 handleRPCResponse(name, req);
134 }
135 }
136 }
137 try {
138 req.open('GET', url, this_.async);
139 req.send();
140 if (!this_.async) {
141 handleRPCResponse(name, req);
142 }
143 } catch (err) {
144 handleRPCFailure(name, err.toString());
145 }
146 }
147 }
148
149 // Pretty prints an error into the DOM.
150 this.logLocalError = function(message) {
151 this.logLocal(message, 'red');
152 this.visualError();
153 }
154
155 // If RPC isn't working, disable it to stop error message spam.
156 this.disableRPC = function() {
157 if (this.rpc_available) {
158 this.rpc_available = false;
159 this.logLocal('Disabling RPC', 'gray');
160 }
161 }
162
163 this.startup = function() {
164 // TODO(ncbray) move into test runner
165 this.num_passed = 0;
166 this.num_failed = 0;
167 this.num_errors = 0;
168 this._log('[STARTUP]');
169 }
170
171 this.shutdown = function() {
172 if (this.num_passed == 0 && this.num_failed == 0 && this.num_errors == 0) {
173 this.client_error('No tests were run. This may be a bug.');
174 }
175 var full_message = '[SHUTDOWN] ';
176 full_message += this.num_passed + ' passed';
177 full_message += ', ' + this.num_failed + ' failed';
178 full_message += ', ' + this.num_errors + ' errors';
179 this.logLocal(full_message);
180 rpcCall('Shutdown', {message: full_message, passed: !this.ever_failed});
181
182 if (this.ever_failed) {
183 this.localOutput.style.border = '2px solid #FF0000';
184 } else {
185 this.localOutput.style.border = '2px solid #00FF00';
186 }
187 }
188
189 this.ping = function() {
190 rpcCall('Ping', {});
191 }
192
193 this.heartbeat = function() {
194 rpcCall('JavaScriptIsAlive', {});
195 }
196
197 this.client_error = function(message) {
198 this.num_errors += 1;
199 this.visualError();
200 var full_message = '\n[CLIENT_ERROR] ' + exceptionToLogText(message)
201 // The client error could have been generated by logging - be careful.
202 try {
203 this._log(full_message, 'red');
204 } catch (err) {
205 // There's not much that can be done, at this point.
206 }
207 }
208
209 this.begin = function(test_name) {
210 var full_message = '[' + test_name + ' BEGIN]'
211 this._log(full_message, 'blue');
212 }
213
214 this._log = function(message, color, from_completed_test) {
215 if (typeof(message) != 'string') {
216 message = toString(message);
217 }
218
219 // For event-driven tests, output may come after the test has finished.
220 // Display this in a special way to assist debugging.
221 if (from_completed_test) {
222 color = 'orange';
223 message = 'completed test: ' + message;
224 }
225
226 this.logLocal(message, color);
227 rpcCall('TestLog', {message: message});
228 }
229
230 this.log = function(test_name, message, from_completed_test) {
231 if (message == undefined) {
232 // This is a log message that is not assosiated with a test.
233 // What we though was the test name is actually the message.
234 this._log(test_name);
235 } else {
236 if (typeof(message) != 'string') {
237 message = toString(message);
238 }
239 var full_message = '[' + test_name + ' LOG] ' + message;
240 this._log(full_message, 'black', from_completed_test);
241 }
242 }
243
244 this.fail = function(test_name, message, from_completed_test) {
245 this.num_failed += 1;
246 this.visualError();
247 var full_message = '[' + test_name + ' FAIL] ' + message
248 this._log(full_message, 'red', from_completed_test);
249 }
250
251 this.exception = function(test_name, err, from_completed_test) {
252 this.num_errors += 1;
253 this.visualError();
254 var message = exceptionToLogText(err);
255 var full_message = '[' + test_name + ' EXCEPTION] ' + message;
256 this._log(full_message, 'purple', from_completed_test);
257 }
258
259 this.pass = function(test_name, from_completed_test) {
260 this.num_passed += 1;
261 var full_message = '[' + test_name + ' PASS]';
262 this._log(full_message, 'green', from_completed_test);
263 }
264
265 this.blankLine = function() {
266 this._log('');
267 }
268
269 // Allows users to log time data that will be parsed and re-logged
270 // for chrome perf-bot graphs / performance regression testing.
271 // See: native_client/tools/process_perf_output.py
272 this.logTimeData = function(event, timeMS) {
273 this.log('NaClPerf [' + event + '] ' + timeMS + ' millisecs');
274 }
275
276 this.visualError = function() {
277 // Changing the color is defered until testing is done
278 this.ever_failed = true;
279 }
280
281 this.logLineLocal = function(text, color) {
282 text = text.replace(/\s+$/, '');
283 if (text == '') {
284 this.localOutput.appendChild(document.createElement('br'));
285 } else {
286 var mNode = document.createTextNode(text);
287 var div = document.createElement('div');
288 // Preserve whitespace formatting.
289 div.style['white-space'] = 'pre';
290 if (color != undefined) {
291 div.style.color = color;
292 }
293 div.appendChild(mNode);
294 this.localOutput.appendChild(div);
295 }
296 }
297
298 this.logLocal = function(message, color) {
299 var lines = message.split('\n');
300 for (var i = 0; i < lines.length; i++) {
301 this.logLineLocal(lines[i], color);
302 }
303 }
304
305 // Create a place in the page to output test results
306 this.localOutput = document.createElement('div');
307 this.localOutput.id = 'testresults';
308 this.localOutput.style.border = '2px solid #0000FF';
309 this.localOutput.style.padding = '10px';
310 document.body.appendChild(this.localOutput);
311 }
312
313
314 //
315 // BEGIN functions for testing
316 //
317
318
319 function fail(message, info, test_status) {
320 var parts = [];
321 if (message != undefined) {
322 parts.push(message);
323 }
324 if (info != undefined) {
325 parts.push('(' + info + ')');
326 }
327 var full_message = parts.join(' ');
328
329 if (test_status !== undefined) {
330 // New-style test
331 test_status.fail(full_message);
332 } else {
333 // Old-style test
334 throw {type: 'test_fail', message: full_message};
335 }
336 }
337
338
339 function assert(condition, message, test_status) {
340 if (!condition) {
341 fail(message, toString(condition), test_status);
342 }
343 }
344
345
346 // This is accepted best practice for checking if an object is an array.
347 function isArray(obj) {
348 return Object.prototype.toString.call(obj) === '[object Array]';
349 }
350
351
352 function toString(obj) {
353 if (typeof(obj) == 'string') {
354 return '\'' + obj + '\'';
355 }
356 try {
357 return obj.toString();
358 } catch (err) {
359 try {
360 // Arrays should do this automatically, but there is a known bug where
361 // NaCl gets array types wrong. .toString will fail on these objects.
362 return obj.join(',');
363 } catch (err) {
364 if (obj == undefined) {
365 return 'undefined';
366 } else {
367 // There is no way to create a textual representation of this object.
368 return '[UNPRINTABLE]';
369 }
370 }
371 }
372 }
373
374
375 // Old-style, but new-style tests use it indirectly.
376 // (The use of the "test" parameter indicates a new-style test. This is a
377 // temporary hack to avoid code duplication.)
378 function assertEqual(a, b, message, test_status) {
379 if (isArray(a) && isArray(b)) {
380 assertArraysEqual(a, b, message, test_status);
381 } else if (a !== b) {
382 fail(message, toString(a) + ' != ' + toString(b), test_status);
383 }
384 }
385
386
387 // Old-style, but new-style tests use it indirectly.
388 // (The use of the "test" parameter indicates a new-style test. This is a
389 // temporary hack to avoid code duplication.)
390 function assertArraysEqual(a, b, message, test_status) {
391 var dofail = function() {
392 fail(message, toString(a) + ' != ' + toString(b), test_status);
393 }
394 if (a.length != b.length) {
395 dofail();
396 }
397 for (var i = 0; i < a.length; i++) {
398 if (a[i] !== b[i]) {
399 dofail();
400 }
401 }
402 }
403
404
405 // Ideally there'd be some way to identify what exception was thrown, but JS
406 // exceptions are fairly ad-hoc.
407 // TODO(ncbray) allow manual validation of exception types?
408 function assertRaises(func, message, test_status) {
409 try {
410 func();
411 } catch (err) {
412 return;
413 }
414 fail(message, 'did not raise', test_status);
415 }
416
417
418 //
419 // END functions for testing
420 //
421
422
423 function haltAsyncTest() {
424 throw {type: 'test_halt'};
425 }
426
427
428 function begins_with(s, prefix) {
429 if (s.length >= prefix.length) {
430 return s.substr(0, prefix.length) == prefix;
431 } else {
432 return false;
433 }
434 }
435
436
437 function ends_with(s, suffix) {
438 if (s.length >= suffix.length) {
439 return s.substr(s.length - suffix.length, suffix.length) == suffix;
440 } else {
441 return false;
442 }
443 }
444
445
446 function embed_name(embed) {
447 if (embed.name != undefined) {
448 if (embed.id != undefined) {
449 return embed.name + ' / ' + embed.id;
450 } else {
451 return embed.name;
452 }
453 } else if (embed.id != undefined) {
454 return embed.id;
455 } else {
456 return '[no name]';
457 }
458 }
459
460
461 // Webkit Bug Workaround
462 // THIS SHOULD BE REMOVED WHEN Webkit IS FIXED
463 // http://code.google.com/p/nativeclient/issues/detail?id=2428
464 // http://code.google.com/p/chromium/issues/detail?id=103588
465
466 function ForcePluginLoadOnTimeout(elem, tester, timeout) {
467 tester.log('Registering ForcePluginLoadOnTimeout ' +
468 '(Bugs: NaCl 2428, Chrome 103588)');
469
470 var started_loading = elem.readyState !== undefined;
471
472 // Remember that the plugin started loading - it may be unloaded by the time
473 // the callback fires.
474 elem.addEventListener('load', function() {
475 started_loading = true;
476 }, true);
477
478 // Check that the plugin has at least started to load after "timeout" seconds,
479 // otherwise reload the page.
480 setTimeout(function() {
481 if (!started_loading) {
482 ForceNaClPluginReload(elem, tester);
483 }
484 }, timeout);
485 }
486
487 function ForceNaClPluginReload(elem, tester) {
488 if (elem.readyState === undefined) {
489 tester.log('WARNING: WebKit plugin-not-loading error detected; reloading.');
490 window.location.reload();
491 }
492 }
493
494 function NaClWaiter(body_element) {
495 // Work around how JS binds 'this'
496 var this_ = this;
497 var embedsToWaitFor = [];
498 // embedsLoaded contains list of embeds that have dispatched the
499 // 'loadend' progress event.
500 this.embedsLoaded = [];
501
502 this.is_loaded = function(embed) {
503 for (var i = 0; i < this_.embedsLoaded.length; ++i) {
504 if (this_.embedsLoaded[i] === embed) {
505 return true;
506 }
507 }
508 return (embed.readyState == 4) && !this_.has_errored(embed);
509 }
510
511 this.has_errored = function(embed) {
512 var msg = embed.lastError;
513 return embed.lastError != undefined && embed.lastError != '';
514 }
515
516 // If an argument was passed, it is the body element for registering
517 // event listeners for the 'loadend' event type.
518 if (body_element != undefined) {
519 var eventListener = function(e) {
520 if (e.type == 'loadend') {
521 this_.embedsLoaded.push(e.target);
522 }
523 }
524
525 body_element.addEventListener('loadend', eventListener, true);
526 }
527
528 // Takes an arbitrary number of arguments.
529 this.waitFor = function() {
530 for (var i = 0; i< arguments.length; i++) {
531 embedsToWaitFor.push(arguments[i]);
532 }
533 }
534
535 this.run = function(doneCallback, pingCallback) {
536 this.doneCallback = doneCallback;
537 this.pingCallback = pingCallback;
538
539 // Wait for up to forty seconds for the nexes to load.
540 // TODO(ncbray) use error handling mechanisms (when they are implemented)
541 // rather than a timeout.
542 this.totalWait = 0;
543 this.maxTotalWait = 40000;
544 this.retryWait = 10;
545 this.waitForPlugins();
546 }
547
548 this.waitForPlugins = function() {
549 var errored = [];
550 var loaded = [];
551 var waiting = [];
552
553 for (var i = 0; i < embedsToWaitFor.length; i++) {
554 try {
555 var e = embedsToWaitFor[i];
556 if (this.has_errored(e)) {
557 errored.push(e);
558 } else if (this.is_loaded(e)) {
559 loaded.push(e);
560 } else {
561 waiting.push(e);
562 }
563 } catch(err) {
564 // If the module is badly horked, touching lastError, etc, may except.
565 errored.push(err);
566 }
567 }
568
569 this.totalWait += this.retryWait;
570
571 if (waiting.length == 0) {
572 this.doneCallback(loaded, errored);
573 } else if (this.totalWait >= this.maxTotalWait) {
574 // Timeouts are considered errors.
575 this.doneCallback(loaded, errored.concat(waiting));
576 } else {
577 setTimeout(function() { this_.waitForPlugins(); }, this.retryWait);
578 // Capped exponential backoff
579 this.retryWait += this.retryWait/2;
580 // Paranoid: does setTimeout like floating point numbers?
581 this.retryWait = Math.round(this.retryWait);
582 if (this.retryWait > 100)
583 this.retryWait = 100;
584 // Prevent the server from thinking the test has died.
585 if (this.pingCallback)
586 this.pingCallback();
587 }
588 }
589 }
590
591
592 function logLoadStatus(rpc, load_errors_are_test_errors, loaded, waiting) {
593 for (var i = 0; i < loaded.length; i++) {
594 rpc.log(embed_name(loaded[i]) + ' loaded');
595 }
596 // Be careful when interacting with horked nexes.
597 var getCarefully = function (callback) {
598 try {
599 return callback();
600 } catch (err) {
601 return '<exception>';
602 }
603 }
604
605 var errored = false;
606 for (var j = 0; j < waiting.length; j++) {
607 // Workaround for WebKit layout bug that caused the NaCl plugin to not
608 // load. If we see that the plugin is not loaded after a timeout, we
609 // forcibly reload the page, thereby triggering layout. Re-running
610 // layout should make WebKit instantiate the plugin. NB: this could
611 // make the JavaScript-based code go into an infinite loop if the
612 // WebKit bug becomes deterministic or the NaCl plugin fails after
613 // loading, but the browser_tester.py code will timeout the test.
614 //
615 // http://code.google.com/p/nativeclient/issues/detail?id=2428
616 //
617 if (waiting[j].readyState == undefined) {
618 // alert('Woot'); // -- for manual debugging
619 rpc.log('WARNING: WebKit plugin-not-loading error detected; reloading.');
620 window.location.reload();
621 throw "reload NOW";
622 }
623 var name = getCarefully(function(){
624 return embed_name(waiting[j]);
625 });
626 var ready = getCarefully(function(){
627 var readyStateString =
628 ['UNSENT', 'OPENED', 'HEADERS_RECEIVED', 'LOADING', 'DONE'];
629 // An undefined index value will return and undefined result.
630 return readyStateString[waiting[j].readyState];
631 });
632 var last = getCarefully(function(){
633 return toString(waiting[j].lastError);
634 });
635 var msg = (name + ' did not load. Status: ' + ready + ' / ' + last);
636 if (load_errors_are_test_errors) {
637 rpc.client_error(msg);
638 errored = true;
639 } else {
640 rpc.log(msg);
641 }
642 }
643 return errored;
644 }
645
646
647 // Contains the state for a single test.
648 function TestStatus(tester, name, async) {
649 // Work around how JS binds 'this'
650 var this_ = this;
651 this.tester = tester;
652 this.name = name;
653 this.async = async;
654 this.running = true;
655
656 this.log = function(message) {
657 this.tester.rpc.log(this.name, toString(message), !this.running);
658 }
659
660 this.pass = function() {
661 // TODO raise if not running.
662 this.tester.rpc.pass(this.name, !this.running);
663 this._done();
664 haltAsyncTest();
665 }
666
667 this.fail = function(message) {
668 this.tester.rpc.fail(this.name, message, !this.running);
669 this._done();
670 haltAsyncTest();
671 }
672
673 this._done = function() {
674 if (this.running) {
675 this.running = false;
676 this.tester.testDone(this);
677 }
678 }
679
680 this.assert = function(condition, message) {
681 assert(condition, message, this);
682 }
683
684 this.assertEqual = function(a, b, message) {
685 assertEqual(a, b, message, this);
686 }
687
688 this.callbackWrapper = function(callback, args) {
689 // A stale callback?
690 if (!this.running)
691 return;
692
693 if (args === undefined)
694 args = [];
695
696 try {
697 callback.apply(undefined, args);
698 } catch (err) {
699 if (typeof err == 'object' && 'type' in err) {
700 if (err.type == 'test_halt') {
701 // New-style test
702 // If we get this exception, we can assume any callbacks or next
703 // tests have already been scheduled.
704 return;
705 } else if (err.type == 'test_fail') {
706 // Old-style test
707 // A special exception that terminates the test with a failure
708 this.tester.rpc.fail(this.name, err.message, !this.running);
709 this._done();
710 return;
711 }
712 }
713 // This is not a special type of exception, it is an error.
714 this.tester.rpc.exception(this.name, err, !this.running);
715 this._done();
716 return;
717 }
718
719 // A normal exit. Should we move on to the next test?
720 // Async tests do not move on without an explicit pass.
721 if (!this.async) {
722 this.tester.rpc.pass(this.name);
723 this._done();
724 }
725 }
726
727 // Async callbacks should be wrapped so the tester can catch unexpected
728 // exceptions.
729 this.wrap = function(callback) {
730 return function() {
731 this_.callbackWrapper(callback, arguments);
732 };
733 }
734
735 this.setTimeout = function(callback, time) {
736 setTimeout(this.wrap(callback), time);
737 }
738
739 this.waitForCallback = function(callbackName, expectedCalls) {
740 this.log('Waiting for ' + expectedCalls + ' invocations of callback: '
741 + callbackName);
742 var gotCallbacks = 0;
743
744 // Deliberately global - this is what the nexe expects.
745 // TODO(ncbray): consider returning this function, so the test has more
746 // flexibility. For example, in the test one could count to N
747 // using a different callback before calling _this_ callback, and
748 // continuing the test. Also, consider calling user-supplied callback
749 // when done waiting.
750 window[callbackName] = this.wrap(function() {
751 ++gotCallbacks;
752 this_.log('Received callback ' + gotCallbacks);
753 if (gotCallbacks == expectedCalls) {
754 this_.log("Done waiting");
755 this_.pass();
756 } else {
757 // HACK
758 haltAsyncTest();
759 }
760 });
761
762 // HACK if this function is used in a non-async test, make sure we don't
763 // spuriously pass. Throwing this exception forces us to behave like an
764 // async test.
765 haltAsyncTest();
766 }
767
768 // This function takes an array of messages and asserts that the nexe
769 // calls PostMessage with each of these messages, in order.
770 // Arguments:
771 // plugin - The DOM object for the NaCl plugin
772 // messages - An array of expected responses
773 // callback - An optional callback function that takes the current message
774 // string as an argument
775 this.expectMessageSequence = function(plugin, messages, callback) {
776 this.assert(messages.length > 0, 'Must provide at least one message');
777 var local_messages = messages.slice();
778 var listener = function(message) {
779 if (message.data.indexOf('@:') == 0) {
780 // skip debug messages
781 this_.log('DEBUG: ' + message.data.substr(2));
782 } else {
783 this_.assertEqual(message.data, local_messages.shift());
784 if (callback !== undefined) {
785 callback(message.data);
786 }
787 }
788 if (local_messages.length == 0) {
789 this_.pass();
790 } else {
791 this_.expectEvent(plugin, 'message', listener);
792 }
793 }
794 this.expectEvent(plugin, 'message', listener);
795 }
796
797 this.expectEvent = function(src, event_type, listener) {
798 var wrapper = this.wrap(function(e) {
799 src.removeEventListener(event_type, wrapper, false);
800 listener(e);
801 });
802 src.addEventListener(event_type, wrapper, false);
803 }
804 }
805
806
807 function Tester(body_element) {
808 // Work around how JS binds 'this'
809 var this_ = this;
810 // The tests being run.
811 var tests = [];
812 this.rpc = new RPCWrapper();
813 this.waiter = new NaClWaiter(body_element);
814
815 var load_errors_are_test_errors = true;
816
817 var parallel = false;
818
819 //
820 // BEGIN public interface
821 //
822
823 this.loadErrorsAreOK = function() {
824 load_errors_are_test_errors = false;
825 }
826
827 this.log = function(message) {
828 this.rpc.log(message);
829 }
830
831 // If this kind of test exits cleanly, it passes
832 this.addTest = function(name, testFunction) {
833 tests.push({name: name, callback: testFunction, async: false});
834 }
835
836 // This kind of test does not pass until "pass" is explicitly called.
837 this.addAsyncTest = function(name, testFunction) {
838 tests.push({name: name, callback: testFunction, async: true});
839 }
840
841 this.run = function() {
842 this.rpc.startup();
843 this.startHeartbeat();
844 this.waiter.run(
845 function(loaded, waiting) {
846 var errored = logLoadStatus(this_.rpc, load_errors_are_test_errors,
847 loaded, waiting);
848 if (errored) {
849 this_.rpc.blankLine();
850 this_.rpc.log('A nexe load error occured, aborting testing.');
851 this_._done();
852 } else {
853 this_.startTesting();
854 }
855 },
856 function() {
857 this_.rpc.ping();
858 }
859 );
860 }
861
862 this.runParallel = function() {
863 parallel = true;
864 this.run();
865 }
866
867 // Takes an arbitrary number of arguments.
868 this.waitFor = function() {
869 for (var i = 0; i< arguments.length; i++) {
870 this.waiter.waitFor(arguments[i]);
871 }
872 }
873
874 //
875 // END public interface
876 //
877
878 this.startHeartbeat = function() {
879 var rpc = this.rpc;
880 var heartbeat = function() {
881 rpc.heartbeat();
882 setTimeout(heartbeat, 500);
883 }
884 heartbeat();
885 }
886
887 this.launchTest = function(testIndex) {
888 var testDecl = tests[testIndex];
889 var currentTest = new TestStatus(this, testDecl.name, testDecl.async);
890 setTimeout(currentTest.wrap(function() {
891 this_.rpc.blankLine();
892 this_.rpc.begin(currentTest.name);
893 testDecl.callback(currentTest);
894 }), 0);
895 }
896
897 this._done = function() {
898 this.rpc.blankLine();
899 this.rpc.shutdown();
900 }
901
902 this.startTesting = function() {
903 if (tests.length == 0) {
904 // No tests specified.
905 this._done();
906 return;
907 }
908
909 this.testCount = 0;
910 if (parallel) {
911 // Launch all tests.
912 for (var i = 0; i < tests.length; i++) {
913 this.launchTest(i);
914 }
915 } else {
916 // Launch the first test.
917 this.launchTest(0);
918 }
919 }
920
921 this.testDone = function(test) {
922 this.testCount += 1;
923 if (this.testCount < tests.length) {
924 if (!parallel) {
925 // Move on to the next test if they're being run one at a time.
926 this.launchTest(this.testCount);
927 }
928 } else {
929 this._done();
930 }
931 }
932 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698