OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * This configuration can be used to rerun selected tests, as well | 6 * This configuration can be used to rerun selected tests, as well |
7 * as see diagnostic output from tests. It runs each test in its own | 7 * as see diagnostic output from tests. It runs each test in its own |
8 * IFrame, so the configuration consists of two parts - a 'master' | 8 * IFrame, so the configuration consists of two parts - a 'parent' |
9 * config that manages all the tests, and a 'slave' config for the | 9 * config that manages all the tests, and a 'child' config for the |
10 * IFrame that runs the individual tests. | 10 * IFrame that runs the individual tests. |
11 */ | 11 */ |
12 #library('interactive_config'); | 12 #library('interactive_config'); |
13 | 13 |
14 // TODO(gram) - add options for: remove IFrame on done/keep | 14 // TODO(gram) - add options for: remove IFrame on done/keep |
15 // IFrame for failed tests/keep IFrame for all tests. | 15 // IFrame for failed tests/keep IFrame for all tests. |
16 | 16 |
17 #import('dart:html'); | 17 #import('dart:html'); |
18 #import('dart:math'); | 18 #import('dart:math'); |
19 #import('unittest.dart'); | 19 #import('unittest.dart'); |
20 | 20 |
21 /** The messages exchanged between master and slave. */ | 21 /** The messages exchanged between parent and child. */ |
22 | 22 |
23 class _Message { | 23 class _Message { |
24 static final START = 'start'; | 24 static final START = 'start'; |
25 static final LOG = 'log'; | 25 static final LOG = 'log'; |
26 static final STACK = 'stack'; | 26 static final STACK = 'stack'; |
27 static final PASS = 'pass'; | 27 static final PASS = 'pass'; |
28 static final FAIL = 'fail'; | 28 static final FAIL = 'fail'; |
29 static final ERROR = 'error'; | 29 static final ERROR = 'error'; |
30 | 30 |
31 String messageType; | 31 String messageType; |
(...skipping 13 matching lines...) Expand all Loading... |
45 int idx2 = msg.indexOf(' ', idx); | 45 int idx2 = msg.indexOf(' ', idx); |
46 elapsed = parseInt(msg.substring(idx, idx2)); | 46 elapsed = parseInt(msg.substring(idx, idx2)); |
47 ++idx2; | 47 ++idx2; |
48 body = msg.substring(idx2); | 48 body = msg.substring(idx2); |
49 } | 49 } |
50 | 50 |
51 String toString() => text(messageType, elapsed, body); | 51 String toString() => text(messageType, elapsed, body); |
52 } | 52 } |
53 | 53 |
54 /** | 54 /** |
55 * The slave configuration that is used to run individual tests in | 55 * The child configuration that is used to run individual tests in |
56 * an IFrame and post the results back to the master. In principle | 56 * an IFrame and post the results back to the parent. In principle |
57 * this can run more than one test in the IFrame but currently only | 57 * this can run more than one test in the IFrame but currently only |
58 * one is used. | 58 * one is used. |
59 */ | 59 */ |
60 class SlaveInteractiveHtmlConfiguration extends Configuration { | 60 class ChildInteractiveHtmlConfiguration extends Configuration { |
61 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | 61 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |
62 EventListener _onErrorClosure; | 62 EventListener _onErrorClosure; |
63 | 63 |
64 /** The window to which results must be posted. */ | 64 /** The window to which results must be posted. */ |
65 Window masterWindow; | 65 Window parentWindow; |
66 | 66 |
67 /** The time at which tests start. */ | 67 /** The time at which tests start. */ |
68 Map<int,Date> _testStarts; | 68 Map<int,Date> _testStarts; |
69 | 69 |
70 SlaveInteractiveHtmlConfiguration() : | 70 ChildInteractiveHtmlConfiguration() : |
71 _testStarts = new Map<int,Date>(); | 71 _testStarts = new Map<int,Date>(); |
72 | 72 |
73 /** Don't start running tests automatically. */ | 73 /** Don't start running tests automatically. */ |
74 get autoStart() => false; | 74 get autoStart() => false; |
75 | 75 |
76 void onInit() { | 76 void onInit() { |
77 _onErrorClosure = | 77 _onErrorClosure = |
78 (e) => handleExternalError(e, '(DOM callback has errors)'); | 78 (e) => handleExternalError(e, '(DOM callback has errors)'); |
79 | 79 |
80 /** | 80 /** |
81 * The master posts a 'start' message to kick things off, | 81 * The parent posts a 'start' message to kick things off, |
82 * which is handled by this handler. It saves the master | 82 * which is handled by this handler. It saves the parent |
83 * window, gets the test ID from the query parameter in the | 83 * window, gets the test ID from the query parameter in the |
84 * IFrame URL, sets that as a solo test and starts test execution. | 84 * IFrame URL, sets that as a solo test and starts test execution. |
85 */ | 85 */ |
86 window.on.message.add((MessageEvent e) { | 86 window.on.message.add((MessageEvent e) { |
87 // Get the result, do any logging, then do a pass/fail. | 87 // Get the result, do any logging, then do a pass/fail. |
88 var m = new _Message.fromString(e.data); | 88 var m = new _Message.fromString(e.data); |
89 if (m.messageType == _Message.START) { | 89 if (m.messageType == _Message.START) { |
90 masterWindow = e.source; | 90 parentWindow = e.source; |
91 String search = window.location.search; | 91 String search = window.location.search; |
92 int pos = search.indexOf('t='); | 92 int pos = search.indexOf('t='); |
93 String ids = search.substring(pos+2); | 93 String ids = search.substring(pos+2); |
94 int id = parseInt(ids); | 94 int id = parseInt(ids); |
95 setSoloTest(id); | 95 setSoloTest(id); |
96 runTests(); | 96 runTests(); |
97 } | 97 } |
98 }); | 98 }); |
99 } | 99 } |
100 | 100 |
101 void onStart() { | 101 void onStart() { |
102 // Listen for uncaught errors. | 102 // Listen for uncaught errors. |
103 window.on.error.add(_onErrorClosure); | 103 window.on.error.add(_onErrorClosure); |
104 } | 104 } |
105 | 105 |
106 /** Record the start time of the test. */ | 106 /** Record the start time of the test. */ |
107 void onTestStart(TestCase testCase) { | 107 void onTestStart(TestCase testCase) { |
108 super.onTestStart(testCase); | 108 super.onTestStart(testCase); |
109 _testStarts[testCase.id]= new Date.now(); | 109 _testStarts[testCase.id]= new Date.now(); |
110 } | 110 } |
111 | 111 |
112 /** | 112 /** |
113 * Tests can call [log] for diagnostic output. These log | 113 * Tests can call [log] for diagnostic output. These log |
114 * messages in turn get passed to this method, which adds | 114 * messages in turn get passed to this method, which adds |
115 * a timestamp and posts them back to the master window. | 115 * a timestamp and posts them back to the parent window. |
116 */ | 116 */ |
117 void logMessage(TestCase testCase, String message) { | 117 void logMessage(TestCase testCase, String message) { |
118 int elapsed; | 118 int elapsed; |
119 if (testCase == null) { | 119 if (testCase == null) { |
120 elapsed = -1; | 120 elapsed = -1; |
121 } else { | 121 } else { |
122 Date end = new Date.now(); | 122 Date end = new Date.now(); |
123 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 123 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
124 } | 124 } |
125 masterWindow.postMessage( | 125 parentWindow.postMessage( |
126 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); | 126 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); |
127 } | 127 } |
128 | 128 |
129 /** | 129 /** |
130 * Get the elapsed time for the test, anbd post the test result | 130 * Get the elapsed time for the test, anbd post the test result |
131 * back to the master window. If the test failed due to an exception | 131 * back to the parent window. If the test failed due to an exception |
132 * the stack is posted back too (before the test result). | 132 * the stack is posted back too (before the test result). |
133 */ | 133 */ |
134 void onTestResult(TestCase testCase) { | 134 void onTestResult(TestCase testCase) { |
135 super.onTestResult(testCase); | 135 super.onTestResult(testCase); |
136 Date end = new Date.now(); | 136 Date end = new Date.now(); |
137 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 137 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
138 if (testCase.stackTrace != null) { | 138 if (testCase.stackTrace != null) { |
139 masterWindow.postMessage( | 139 parentWindow.postMessage( |
140 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); | 140 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); |
141 } | 141 } |
142 masterWindow.postMessage( | 142 parentWindow.postMessage( |
143 _Message.text(testCase.result, elapsed, testCase.message), '*'); | 143 _Message.text(testCase.result, elapsed, testCase.message), '*'); |
144 } | 144 } |
145 | 145 |
146 void onDone(int passed, int failed, int errors, List<TestCase> results, | 146 void onDone(int passed, int failed, int errors, List<TestCase> results, |
147 String uncaughtError) { | 147 String uncaughtError) { |
148 window.on.error.remove(_onErrorClosure); | 148 window.on.error.remove(_onErrorClosure); |
149 } | 149 } |
150 } | 150 } |
151 | 151 |
152 /** | 152 /** |
153 * The master configuration runs in the top-level window; it wraps the tests | 153 * The parent configuration runs in the top-level window; it wraps the tests |
154 * in new functions that create slave IFrames and run the real tests. | 154 * in new functions that create child IFrames and run the real tests. |
155 */ | 155 */ |
156 class MasterInteractiveHtmlConfiguration extends Configuration { | 156 class ParentInteractiveHtmlConfiguration extends Configuration { |
157 Map<int,Date> _testStarts; | 157 Map<int,Date> _testStarts; |
158 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | 158 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |
159 EventListener _onErrorClosure; | 159 EventListener _onErrorClosure; |
160 | 160 |
161 /** The stack that was posted back from the slave, if any. */ | 161 /** The stack that was posted back from the child, if any. */ |
162 String _stack; | 162 String _stack; |
163 | 163 |
164 int _testTime; | 164 int _testTime; |
165 /** | 165 /** |
166 * Whether or not we have already wrapped the TestCase test functions | 166 * Whether or not we have already wrapped the TestCase test functions |
167 * in new closures that instead create an IFrame and get it to run the | 167 * in new closures that instead create an IFrame and get it to run the |
168 * test. | 168 * test. |
169 */ | 169 */ |
170 bool _doneWrap = false; | 170 bool _doneWrap = false; |
171 | 171 |
172 /** | 172 /** |
173 * We use this to make a single closure from _handleMessage so we | 173 * We use this to make a single closure from _handleMessage so we |
174 * can remove the handler later. | 174 * can remove the handler later. |
175 */ | 175 */ |
176 Function _messageHandler; | 176 Function _messageHandler; |
177 | 177 |
178 MasterInteractiveHtmlConfiguration() : | 178 ParentInteractiveHtmlConfiguration() : |
179 _testStarts = new Map<int,Date>(); | 179 _testStarts = new Map<int,Date>(); |
180 | 180 |
181 // We need to block until the test is done, so we make a | 181 // We need to block until the test is done, so we make a |
182 // dummy async callback that we will use to flag completion. | 182 // dummy async callback that we will use to flag completion. |
183 Function completeTest = null; | 183 Function completeTest = null; |
184 | 184 |
185 wrapTest(TestCase testCase) { | 185 wrapTest(TestCase testCase) { |
186 String baseUrl = window.location.toString(); | 186 String baseUrl = window.location.toString(); |
187 String url = '${baseUrl}?t=${testCase.id}'; | 187 String url = '${baseUrl}?t=${testCase.id}'; |
188 return () { | 188 return () { |
189 // Rebuild the slave IFrame. | 189 // Rebuild the child IFrame. |
190 Element slaveDiv = document.query('#slave'); | 190 Element childDiv = document.query('#child'); |
191 slaveDiv.nodes.clear(); | 191 childDiv.nodes.clear(); |
192 IFrameElement slave = new Element.html(""" | 192 IFrameElement child = new Element.html(""" |
193 <iframe id='slaveFrame${testCase.id}' src='$url' style='display:none'> | 193 <iframe id='childFrame${testCase.id}' src='$url' style='display:none'> |
194 </iframe>"""); | 194 </iframe>"""); |
195 slaveDiv.nodes.add(slave); | 195 childDiv.nodes.add(child); |
196 completeTest = expectAsync0((){ }); | 196 completeTest = expectAsync0((){ }); |
197 // Kick off the test when the IFrame is loaded. | 197 // Kick off the test when the IFrame is loaded. |
198 slave.on.load.add((e) { | 198 child.on.load.add((e) { |
199 slave.contentWindow.postMessage(_Message.text(_Message.START), '*'); | 199 child.contentWindow.postMessage(_Message.text(_Message.START), '*'); |
200 }); | 200 }); |
201 }; | 201 }; |
202 } | 202 } |
203 | 203 |
204 void _handleMessage(MessageEvent e) { | 204 void _handleMessage(MessageEvent e) { |
205 // Get the result, do any logging, then do a pass/fail. | 205 // Get the result, do any logging, then do a pass/fail. |
206 var msg = new _Message.fromString(e.data); | 206 var msg = new _Message.fromString(e.data); |
207 if (msg.messageType == _Message.LOG) { | 207 if (msg.messageType == _Message.LOG) { |
208 log(e.data); | 208 trace(e.data); |
209 } else if (msg.messageType == _Message.STACK) { | 209 } else if (msg.messageType == _Message.STACK) { |
210 _stack = msg.body; | 210 _stack = msg.body; |
211 } else { | 211 } else { |
212 _testTime = msg.elapsed; | 212 _testTime = msg.elapsed; |
213 log(_Message.text(_Message.LOG, _testTime, 'Complete')); | 213 trace(_Message.text(_Message.LOG, _testTime, 'Complete')); |
214 if (msg.messageType == _Message.PASS) { | 214 if (msg.messageType == _Message.PASS) { |
215 currentTestCase.pass(); | 215 currentTestCase.pass(); |
216 } else if (msg.messageType == _Message.FAIL) { | 216 } else if (msg.messageType == _Message.FAIL) { |
217 currentTestCase.fail(msg.body, _stack); | 217 currentTestCase.fail(msg.body, _stack); |
218 } else if (msg.messageType == _Message.ERROR) { | 218 } else if (msg.messageType == _Message.ERROR) { |
219 currentTestCase.error(msg.body, _stack); | 219 currentTestCase.error(msg.body, _stack); |
220 } | 220 } |
221 completeTest(); | 221 completeTest(); |
222 } | 222 } |
223 } | 223 } |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
337 } | 337 } |
338 }); | 338 }); |
339 } else { // Reset the test element. | 339 } else { // Reset the test element. |
340 testItem.classes.clear(); | 340 testItem.classes.clear(); |
341 testItem.classes.add('test-it'); | 341 testItem.classes.add('test-it'); |
342 testItem.classes.add('status-pending'); | 342 testItem.classes.add('status-pending'); |
343 testItem.query('#$_actionIdPrefix$id').innerHTML = ''; | 343 testItem.query('#$_actionIdPrefix$id').innerHTML = ''; |
344 } | 344 } |
345 } | 345 } |
346 | 346 |
347 // Actually test logging is handled by the slave, then posted | 347 // Actually test logging is handled by the child, then posted |
348 // back to the master. So here we know that the [message] argument | 348 // back to the parent. So here we know that the [message] argument |
349 // is in the format used by [_Message]. | 349 // is in the format used by [_Message]. |
350 void logMessage(TestCase testCase, String message) { | 350 void logMessage(TestCase testCase, String message) { |
351 var msg = new _Message.fromString(message); | 351 var msg = new _Message.fromString(message); |
352 if (msg.elapsed < 0) { // No associated test case. | 352 if (msg.elapsed < 0) { // No associated test case. |
353 document.query('#otherlogs').nodes.add( | 353 document.query('#otherlogs').nodes.add( |
354 new Element.html('<p>${msg.body}</p>')); | 354 new Element.html('<p>${msg.body}</p>')); |
355 } else { | 355 } else { |
356 var actions = document.query('#$_testIdPrefix${testCase.id}'). | 356 var actions = document.query('#$_testIdPrefix${testCase.id}'). |
357 query('.test-actions'); | 357 query('.test-actions'); |
358 String elapsedText = msg.elapsed >= 0 ? "${msg.elapsed}ms" : ""; | 358 String elapsedText = msg.elapsed >= 0 ? "${msg.elapsed}ms" : ""; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 window.on.error.remove(_onErrorClosure); | 397 window.on.error.remove(_onErrorClosure); |
398 document.query('#busy').style.display = 'none'; | 398 document.query('#busy').style.display = 'none'; |
399 InputElement startButton = document.query('#start'); | 399 InputElement startButton = document.query('#start'); |
400 startButton.disabled = false; | 400 startButton.disabled = false; |
401 } | 401 } |
402 } | 402 } |
403 | 403 |
404 /** | 404 /** |
405 * Add the divs to the DOM if they are not present. We have a 'controls' | 405 * Add the divs to the DOM if they are not present. We have a 'controls' |
406 * div for control, 'specs' div with test results, a 'busy' div for the | 406 * div for control, 'specs' div with test results, a 'busy' div for the |
407 * animated GIF used to indicate tests are running, and a 'slave' div to | 407 * animated GIF used to indicate tests are running, and a 'child' div to |
408 * hold the iframe for the test. | 408 * hold the iframe for the test. |
409 */ | 409 */ |
410 void _prepareDom() { | 410 void _prepareDom() { |
411 if (document.query('#control') == null) { | 411 if (document.query('#control') == null) { |
412 // Use this as an opportunity for adding the CSS too. | 412 // Use this as an opportunity for adding the CSS too. |
413 // I wanted to avoid having to include a css element explicitly | 413 // I wanted to avoid having to include a css element explicitly |
414 // in the main html file. I considered moving all the styles | 414 // in the main html file. I considered moving all the styles |
415 // inline as attributes but that started getting very messy, | 415 // inline as attributes but that started getting very messy, |
416 // so we do it this way. | 416 // so we do it this way. |
417 document.body.nodes.add(new Element.html("<style>$_CSS</style>")); | 417 document.body.nodes.add(new Element.html("<style>$_CSS</style>")); |
(...skipping 13 matching lines...) Expand all Loading... |
431 } | 431 } |
432 if (document.query('#specs') == null) { | 432 if (document.query('#specs') == null) { |
433 document.body.nodes.add(new Element.html( | 433 document.body.nodes.add(new Element.html( |
434 "<div id='specs'><div id='group-divs'></div></div>")); | 434 "<div id='specs'><div id='group-divs'></div></div>")); |
435 } | 435 } |
436 if (document.query('#busy') == null) { | 436 if (document.query('#busy') == null) { |
437 document.body.nodes.add(new Element.html( | 437 document.body.nodes.add(new Element.html( |
438 "<div id='busy' style='display:none'><img src='googleballs.gif'>" | 438 "<div id='busy' style='display:none'><img src='googleballs.gif'>" |
439 "</img></div>")); | 439 "</img></div>")); |
440 } | 440 } |
441 if (document.query('#slave') == null) { | 441 if (document.query('#child') == null) { |
442 document.body.nodes.add(new Element.html("<div id='slave'></div>")); | 442 document.body.nodes.add(new Element.html("<div id='child'></div>")); |
443 } | 443 } |
444 } | 444 } |
445 | 445 |
446 /** | 446 /** |
447 * Allocate a Configuration. We allocate either a master or | 447 * Allocate a Configuration. We allocate either a parent or |
448 * slave, depedning on whether the URL has a search part. | 448 * child, depedning on whether the URL has a search part. |
449 */ | 449 */ |
450 void useInteractiveHtmlConfiguration() { | 450 void useInteractiveHtmlConfiguration() { |
451 if (window.location.search == '') { // This is the master. | 451 if (window.location.search == '') { // This is the parent. |
452 _prepareDom(); | 452 _prepareDom(); |
453 configure(new MasterInteractiveHtmlConfiguration()); | 453 configure(new ParentInteractiveHtmlConfiguration()); |
454 } else { | 454 } else { |
455 configure(new SlaveInteractiveHtmlConfiguration()); | 455 configure(new ChildInteractiveHtmlConfiguration()); |
456 } | 456 } |
457 } | 457 } |
458 | 458 |
459 String _CSS = """ | 459 String _CSS = """ |
460 body { | 460 body { |
461 font-family: Arial, sans-serif; | 461 font-family: Arial, sans-serif; |
462 margin: 0; | 462 margin: 0; |
463 font-size: 14px; | 463 font-size: 14px; |
464 } | 464 } |
465 | 465 |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
648 display: block; | 648 display: block; |
649 list-style-type: disc; | 649 list-style-type: disc; |
650 -webkit-margin-before: 1em; | 650 -webkit-margin-before: 1em; |
651 -webkit-margin-after: 1em; | 651 -webkit-margin-after: 1em; |
652 -webkit-margin-start: 0px; | 652 -webkit-margin-start: 0px; |
653 -webkit-margin-end: 0px; | 653 -webkit-margin-end: 0px; |
654 -webkit-padding-start: 40px; | 654 -webkit-padding-start: 40px; |
655 } | 655 } |
656 | 656 |
657 """; | 657 """; |
OLD | NEW |