| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 'parent' | 8 * IFrame, so the configuration consists of two parts - a 'parent' |
| 9 * config that manages all the tests, and a 'child' 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 * Note: this unit test configuration will not work with the debugger (the tests | 12 * Note: this unit test configuration will not work with the debugger (the tests |
| 13 * are executed in a separate IFrame). | 13 * are executed in a separate IFrame). |
| 14 */ | 14 */ |
| 15 library unittest_interactive_html_config; | 15 library unittest_interactive_html_config; |
| 16 | 16 |
| 17 // TODO(gram) - add options for: remove IFrame on done/keep | 17 // TODO(gram) - add options for: remove IFrame on done/keep |
| 18 // IFrame for failed tests/keep IFrame for all tests. | 18 // IFrame for failed tests/keep IFrame for all tests. |
| 19 | 19 |
| 20 import 'dart:html'; | 20 import 'dart:html'; |
| 21 import 'dart:async'; | 21 import 'dart:async'; |
| 22 import 'dart:math'; | 22 import 'dart:math'; |
| 23 import 'unittest.dart'; | 23 import 'unittest.dart'; |
| 24 | 24 |
| 25 /** The messages exchanged between parent and child. */ | 25 /** The messages exchanged between parent and child. */ |
| 26 | |
| 27 class _Message { | 26 class _Message { |
| 28 static const START = 'start'; | 27 static const START = 'start'; |
| 29 static const LOG = 'log'; | 28 static const LOG = 'log'; |
| 30 static const STACK = 'stack'; | 29 static const STACK = 'stack'; |
| 31 static const PASS = 'pass'; | 30 static const PASS = 'pass'; |
| 32 static const FAIL = 'fail'; | 31 static const FAIL = 'fail'; |
| 33 static const ERROR = 'error'; | 32 static const ERROR = 'error'; |
| 33 static const _PREFIX = 'TestMsg:'; |
| 34 | 34 |
| 35 String messageType; | 35 final String messageType; |
| 36 int elapsed; | 36 final int elapsed; |
| 37 String body; | 37 final String body; |
| 38 | 38 |
| 39 static String text(String messageType, | 39 static String text(String messageType, |
| 40 [int elapsed = 0, String body = '']) => | 40 [int elapsed = 0, String body = '']) => |
| 41 '$messageType $elapsed $body'; | 41 '$_PREFIX$messageType $elapsed $body'; |
| 42 | 42 |
| 43 _Message(this.messageType, [this.elapsed = 0, this.body = '']); | 43 _Message(this.messageType, [this.elapsed = 0, this.body = '']); |
| 44 | 44 |
| 45 _Message.fromString(String msg) { | 45 factory _Message.fromString(String msg) { |
| 46 int idx = msg.indexOf(' '); | 46 if(!msg.startsWith(_PREFIX)) { |
| 47 messageType = msg.substring(0, idx); | 47 return null; |
| 48 } |
| 49 int idx = msg.indexOf(' ', _PREFIX.length); |
| 50 var messageType = msg.substring(_PREFIX.length, idx); |
| 48 ++idx; | 51 ++idx; |
| 49 int idx2 = msg.indexOf(' ', idx); | 52 int idx2 = msg.indexOf(' ', idx); |
| 50 elapsed = int.parse(msg.substring(idx, idx2)); | 53 var elapsed = int.parse(msg.substring(idx, idx2)); |
| 51 ++idx2; | 54 ++idx2; |
| 52 body = msg.substring(idx2); | 55 var body = msg.substring(idx2); |
| 56 |
| 57 return new _Message(messageType, elapsed, body); |
| 53 } | 58 } |
| 54 | 59 |
| 55 String toString() => text(messageType, elapsed, body); | 60 String toString() => text(messageType, elapsed, body); |
| 56 } | 61 } |
| 57 | 62 |
| 58 | 63 |
| 59 class HtmlConfiguration extends Configuration { | 64 class HtmlConfiguration extends Configuration { |
| 60 StreamSubscription _errorSubscription; | 65 StreamSubscription _errorSubscription; |
| 61 | 66 |
| 62 void _installErrorHandler() { | 67 void _installErrorHandler() { |
| (...skipping 14 matching lines...) Expand all Loading... |
| 77 | 82 |
| 78 /** | 83 /** |
| 79 * The child configuration that is used to run individual tests in | 84 * The child configuration that is used to run individual tests in |
| 80 * an IFrame and post the results back to the parent. In principle | 85 * an IFrame and post the results back to the parent. In principle |
| 81 * this can run more than one test in the IFrame but currently only | 86 * this can run more than one test in the IFrame but currently only |
| 82 * one is used. | 87 * one is used. |
| 83 */ | 88 */ |
| 84 class ChildInteractiveHtmlConfiguration extends HtmlConfiguration { | 89 class ChildInteractiveHtmlConfiguration extends HtmlConfiguration { |
| 85 | 90 |
| 86 /** The window to which results must be posted. */ | 91 /** The window to which results must be posted. */ |
| 87 WindowBase parentWindow; | 92 WindowBase _parentWindow; |
| 88 | 93 |
| 89 /** The time at which tests start. */ | 94 /** The time at which tests start. */ |
| 90 Map<int,DateTime> _testStarts; | 95 final Map<int,DateTime> _testStarts; |
| 91 | 96 |
| 92 ChildInteractiveHtmlConfiguration() : | 97 ChildInteractiveHtmlConfiguration() : |
| 93 _testStarts = new Map<int,DateTime>(); | 98 _testStarts = new Map<int,DateTime>(); |
| 94 | 99 |
| 95 /** Don't start running tests automatically. */ | 100 /** Don't start running tests automatically. */ |
| 96 get autoStart => false; | 101 get autoStart => false; |
| 97 | 102 |
| 98 void onInit() { | 103 void onInit() { |
| 99 _installErrorHandler(); | 104 _installErrorHandler(); |
| 100 | 105 |
| 101 /** | 106 /** |
| 102 * The parent posts a 'start' message to kick things off, | 107 * The parent posts a 'start' message to kick things off, |
| 103 * which is handled by this handler. It saves the parent | 108 * which is handled by this handler. It saves the parent |
| 104 * window, gets the test ID from the query parameter in the | 109 * window, gets the test ID from the query parameter in the |
| 105 * IFrame URL, sets that as a solo test and starts test execution. | 110 * IFrame URL, sets that as a solo test and starts test execution. |
| 106 */ | 111 */ |
| 107 window.onMessage.listen((MessageEvent e) { | 112 window.onMessage.listen((MessageEvent e) { |
| 108 // Get the result, do any logging, then do a pass/fail. | 113 // Get the result, do any logging, then do a pass/fail. |
| 109 var m = new _Message.fromString(e.data); | 114 var m = new _Message.fromString(e.data); |
| 110 if (m.messageType == _Message.START) { | 115 if (m != null && m.messageType == _Message.START) { |
| 111 parentWindow = e.source; | 116 _parentWindow = e.source; |
| 112 String search = window.location.search; | 117 String search = window.location.search; |
| 113 int pos = search.indexOf('t='); | 118 int pos = search.indexOf('t='); |
| 114 String ids = search.substring(pos+2); | 119 String ids = search.substring(pos+2); |
| 115 int id = int.parse(ids); | 120 int id = int.parse(ids); |
| 116 setSoloTest(id); | 121 setSoloTest(id); |
| 117 runTests(); | 122 runTests(); |
| 118 } | 123 } |
| 119 }); | 124 }); |
| 120 } | 125 } |
| 121 | 126 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 135 * a timestamp and posts them back to the parent window. | 140 * a timestamp and posts them back to the parent window. |
| 136 */ | 141 */ |
| 137 void onLogMessage(TestCase testCase, String message) { | 142 void onLogMessage(TestCase testCase, String message) { |
| 138 int elapsed; | 143 int elapsed; |
| 139 if (testCase == null) { | 144 if (testCase == null) { |
| 140 elapsed = -1; | 145 elapsed = -1; |
| 141 } else { | 146 } else { |
| 142 DateTime end = new DateTime.now(); | 147 DateTime end = new DateTime.now(); |
| 143 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 148 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
| 144 } | 149 } |
| 145 parentWindow.postMessage( | 150 _parentWindow.postMessage( |
| 146 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); | 151 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); |
| 147 } | 152 } |
| 148 | 153 |
| 149 /** | 154 /** |
| 150 * Get the elapsed time for the test, anbd post the test result | 155 * Get the elapsed time for the test, anbd post the test result |
| 151 * back to the parent window. If the test failed due to an exception | 156 * back to the parent window. If the test failed due to an exception |
| 152 * the stack is posted back too (before the test result). | 157 * the stack is posted back too (before the test result). |
| 153 */ | 158 */ |
| 154 void onTestResult(TestCase testCase) { | 159 void onTestResult(TestCase testCase) { |
| 155 super.onTestResult(testCase); | 160 super.onTestResult(testCase); |
| 156 DateTime end = new DateTime.now(); | 161 DateTime end = new DateTime.now(); |
| 157 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 162 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
| 158 if (testCase.stackTrace != null) { | 163 if (testCase.stackTrace != null) { |
| 159 parentWindow.postMessage( | 164 _parentWindow.postMessage( |
| 160 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); | 165 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); |
| 161 } | 166 } |
| 162 parentWindow.postMessage( | 167 _parentWindow.postMessage( |
| 163 _Message.text(testCase.result, elapsed, testCase.message), '*'); | 168 _Message.text(testCase.result, elapsed, testCase.message), '*'); |
| 164 } | 169 } |
| 165 void onSummary(int passed, int failed, int errors, List<TestCase> results, | 170 void onSummary(int passed, int failed, int errors, List<TestCase> results, |
| 166 String uncaughtError) { | 171 String uncaughtError) { |
| 167 } | 172 } |
| 168 | 173 |
| 169 void onDone(bool success) { | 174 void onDone(bool success) { |
| 170 _uninstallErrorHandler(); | 175 _uninstallErrorHandler(); |
| 171 } | 176 } |
| 172 } | 177 } |
| 173 | 178 |
| 174 /** | 179 /** |
| 175 * The parent configuration runs in the top-level window; it wraps the tests | 180 * The parent configuration runs in the top-level window; it wraps the tests |
| 176 * in new functions that create child IFrames and run the real tests. | 181 * in new functions that create child IFrames and run the real tests. |
| 177 */ | 182 */ |
| 178 class ParentInteractiveHtmlConfiguration extends HtmlConfiguration { | 183 class ParentInteractiveHtmlConfiguration extends HtmlConfiguration { |
| 179 Map<int,DateTime> _testStarts; | 184 final Map<int,DateTime> _testStarts; |
| 180 | 185 |
| 181 | 186 |
| 182 /** The stack that was posted back from the child, if any. */ | 187 /** The stack that was posted back from the child, if any. */ |
| 183 String _stack; | 188 String _stack; |
| 184 | 189 |
| 185 int _testTime; | 190 int _testTime; |
| 186 /** | 191 /** |
| 187 * Whether or not we have already wrapped the TestCase test functions | 192 * Whether or not we have already wrapped the TestCase test functions |
| 188 * in new closures that instead create an IFrame and get it to run the | 193 * in new closures that instead create an IFrame and get it to run the |
| 189 * test. | 194 * test. |
| 190 */ | 195 */ |
| 191 bool _doneWrap = false; | 196 bool _doneWrap = false; |
| 192 | 197 |
| 193 StreamSubscription _messageSubscription; | 198 StreamSubscription _messageSubscription; |
| 194 | 199 |
| 195 ParentInteractiveHtmlConfiguration() : | 200 ParentInteractiveHtmlConfiguration() : |
| 196 _testStarts = new Map<int,DateTime>(); | 201 _testStarts = new Map<int,DateTime>(); |
| 197 | 202 |
| 198 // We need to block until the test is done, so we make a | 203 // We need to block until the test is done, so we make a |
| 199 // dummy async callback that we will use to flag completion. | 204 // dummy async callback that we will use to flag completion. |
| 200 Function completeTest = null; | 205 Function _completeTest = null; |
| 201 | 206 |
| 202 wrapTest(TestCase testCase) { | 207 Function _wrapTest(TestCase testCase) { |
| 203 String baseUrl = window.location.toString(); | 208 String baseUrl = window.location.toString(); |
| 204 String url = '${baseUrl}?t=${testCase.id}'; | 209 String url = '${baseUrl}?t=${testCase.id}'; |
| 205 return () { | 210 return () { |
| 206 // Rebuild the child IFrame. | 211 // Rebuild the child IFrame. |
| 207 Element childDiv = document.query('#child'); | 212 Element childDiv = document.query('#child'); |
| 208 childDiv.nodes.clear(); | 213 childDiv.nodes.clear(); |
| 209 IFrameElement child = new Element.html(""" | 214 IFrameElement child = new Element.html(""" |
| 210 <iframe id='childFrame${testCase.id}' src='$url' style='display:none'> | 215 <iframe id='childFrame${testCase.id}' src='$url' style='display:none'> |
| 211 </iframe>"""); | 216 </iframe>"""); |
| 212 childDiv.nodes.add(child); | 217 childDiv.nodes.add(child); |
| 213 completeTest = expectAsync0((){ }); | 218 _completeTest = expectAsync0((){ }); |
| 214 // Kick off the test when the IFrame is loaded. | 219 // Kick off the test when the IFrame is loaded. |
| 215 child.onLoad.listen((e) { | 220 child.onLoad.listen((e) { |
| 216 child.contentWindow.postMessage(_Message.text(_Message.START), '*'); | 221 child.contentWindow.postMessage(_Message.text(_Message.START), '*'); |
| 217 }); | 222 }); |
| 218 }; | 223 }; |
| 219 } | 224 } |
| 220 | 225 |
| 221 void _handleMessage(MessageEvent e) { | 226 void _handleMessage(MessageEvent e) { |
| 222 // Get the result, do any logging, then do a pass/fail. | 227 // Get the result, do any logging, then do a pass/fail. |
| 223 var msg = new _Message.fromString(e.data); | 228 var msg = new _Message.fromString(e.data); |
| 229 |
| 230 if(msg == null) { |
| 231 return; |
| 232 } |
| 224 if (msg.messageType == _Message.LOG) { | 233 if (msg.messageType == _Message.LOG) { |
| 225 logMessage(e.data); | 234 logMessage(e.data); |
| 226 } else if (msg.messageType == _Message.STACK) { | 235 } else if (msg.messageType == _Message.STACK) { |
| 227 _stack = msg.body; | 236 _stack = msg.body; |
| 228 } else { | 237 } else { |
| 229 _testTime = msg.elapsed; | 238 _testTime = msg.elapsed; |
| 230 logMessage(_Message.text(_Message.LOG, _testTime, 'Complete')); | 239 logMessage(_Message.text(_Message.LOG, _testTime, 'Complete')); |
| 231 if (msg.messageType == _Message.PASS) { | 240 if (msg.messageType == _Message.PASS) { |
| 232 currentTestCase.pass(); | 241 currentTestCase.pass(); |
| 233 } else if (msg.messageType == _Message.FAIL) { | 242 } else if (msg.messageType == _Message.FAIL) { |
| 234 currentTestCase.fail(msg.body, _stack); | 243 currentTestCase.fail(msg.body, _stack); |
| 235 } else if (msg.messageType == _Message.ERROR) { | 244 } else if (msg.messageType == _Message.ERROR) { |
| 236 currentTestCase.error(msg.body, _stack); | 245 currentTestCase.error(msg.body, _stack); |
| 237 } | 246 } |
| 238 completeTest(); | 247 _completeTest(); |
| 239 } | 248 } |
| 240 } | 249 } |
| 241 | 250 |
| 242 void onInit() { | 251 void onInit() { |
| 243 _installErrorHandler(); | 252 _installErrorHandler(); |
| 244 document.query('#group-divs').innerHtml = ""; | 253 document.query('#group-divs').innerHtml = ""; |
| 245 } | 254 } |
| 246 | 255 |
| 247 void onStart() { | 256 void onStart() { |
| 248 _installErrorHandler(); | 257 _installErrorHandler(); |
| 249 if (!_doneWrap) { | 258 if (!_doneWrap) { |
| 250 _doneWrap = true; | 259 _doneWrap = true; |
| 251 for (int i = 0; i < testCases.length; i++) { | 260 for (int i = 0; i < testCases.length; i++) { |
| 252 testCases[i].testFunction = wrapTest(testCases[i]); | 261 testCases[i].testFunction = _wrapTest(testCases[i]); |
| 253 testCases[i].setUp = null; | 262 testCases[i].setUp = null; |
| 254 testCases[i].tearDown = null; | 263 testCases[i].tearDown = null; |
| 255 } | 264 } |
| 256 } | 265 } |
| 257 assert(_messageSubscription == null); | 266 assert(_messageSubscription == null); |
| 258 _messageSubscription = window.onMessage.listen(_handleMessage); | 267 _messageSubscription = window.onMessage.listen(_handleMessage); |
| 259 } | 268 } |
| 260 | 269 |
| 261 static final _notAlphaNumeric = new RegExp('[^a-z0-9A-Z]'); | 270 static final _notAlphaNumeric = new RegExp('[^a-z0-9A-Z]'); |
| 262 | 271 |
| (...skipping 197 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 460 "</img></div>")); | 469 "</img></div>")); |
| 461 } | 470 } |
| 462 if (document.query('#child') == null) { | 471 if (document.query('#child') == null) { |
| 463 document.body.nodes.add(new Element.html("<div id='child'></div>")); | 472 document.body.nodes.add(new Element.html("<div id='child'></div>")); |
| 464 } | 473 } |
| 465 } | 474 } |
| 466 | 475 |
| 467 /** | 476 /** |
| 468 * Allocate a Configuration. We allocate either a parent or child, depending on | 477 * Allocate a Configuration. We allocate either a parent or child, depending on |
| 469 * whether the URL has a search part. | 478 * whether the URL has a search part. |
| 470 * | 479 * |
| 471 * Note: this unit test configuration will not work with the debugger (the tests | 480 * Note: this unit test configuration will not work with the debugger (the tests |
| 472 * are executed in a separate IFrame). | 481 * are executed in a separate IFrame). |
| 473 */ | 482 */ |
| 474 void useInteractiveHtmlConfiguration() { | 483 void useInteractiveHtmlConfiguration() { |
| 475 if (window.location.search == '') { // This is the parent. | 484 if (window.location.search == '') { // This is the parent. |
| 476 _prepareDom(); | 485 _prepareDom(); |
| 477 unittestConfiguration = _singletonParent; | 486 unittestConfiguration = _singletonParent; |
| 478 } else { | 487 } else { |
| 479 unittestConfiguration = _singletonChild; | 488 unittestConfiguration = _singletonChild; |
| 480 } | 489 } |
| 481 } | 490 } |
| 482 | 491 |
| 483 final _singletonParent = new ParentInteractiveHtmlConfiguration(); | 492 final _singletonParent = new ParentInteractiveHtmlConfiguration(); |
| 484 final _singletonChild = new ChildInteractiveHtmlConfiguration(); | 493 final _singletonChild = new ChildInteractiveHtmlConfiguration(); |
| 485 | 494 |
| 486 String _CSS = """ | 495 const String _CSS = """ |
| 487 body { | 496 body { |
| 488 font-family: Arial, sans-serif; | 497 font-family: Arial, sans-serif; |
| 489 margin: 0; | 498 margin: 0; |
| 490 font-size: 14px; | 499 font-size: 14px; |
| 491 } | 500 } |
| 492 | 501 |
| 493 #application h2, | 502 #application h2, |
| 494 #specs h2 { | 503 #specs h2 { |
| 495 margin: 0; | 504 margin: 0; |
| 496 padding: 0.5em; | 505 padding: 0.5em; |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 673 | 682 |
| 674 ul, menu, dir { | 683 ul, menu, dir { |
| 675 display: block; | 684 display: block; |
| 676 list-style-type: disc; | 685 list-style-type: disc; |
| 677 -webkit-margin-before: 1em; | 686 -webkit-margin-before: 1em; |
| 678 -webkit-margin-after: 1em; | 687 -webkit-margin-after: 1em; |
| 679 -webkit-margin-start: 0px; | 688 -webkit-margin-start: 0px; |
| 680 -webkit-margin-end: 0px; | 689 -webkit-margin-end: 0px; |
| 681 -webkit-padding-start: 40px; | 690 -webkit-padding-start: 40px; |
| 682 } | 691 } |
| 683 | 692 """; |
| 684 """; | |
| OLD | NEW |