| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 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.md file. | |
| 4 | |
| 5 /// Helper program for running unit tests in warmed-up Dart VMs. | |
| 6 /// | |
| 7 /// This program listens for JSON encoded messages on stdin (one message per | |
| 8 /// line) and prints out messages in response (also in JSON, one per line). | |
| 9 /// | |
| 10 /// Messages are defined in 'message.dart'. | |
| 11 library fletch_tests.fletch_test_suite; | |
| 12 | |
| 13 import 'dart:core' hide print; | |
| 14 | |
| 15 import 'dart:io'; | |
| 16 | |
| 17 import 'dart:convert' show | |
| 18 JSON, | |
| 19 LineSplitter, | |
| 20 UTF8; | |
| 21 | |
| 22 import 'dart:async' show | |
| 23 Completer, | |
| 24 Future, | |
| 25 Stream, | |
| 26 StreamIterator, | |
| 27 Zone, | |
| 28 ZoneSpecification; | |
| 29 | |
| 30 import 'dart:isolate'; | |
| 31 | |
| 32 import 'package:fletchc/src/zone_helper.dart' show | |
| 33 runGuarded; | |
| 34 | |
| 35 import 'package:fletchc/src/hub/hub_main.dart' show | |
| 36 IsolatePool, | |
| 37 ManagedIsolate; | |
| 38 | |
| 39 import 'package:fletchc/src/worker/developer.dart' show | |
| 40 configFileUri; | |
| 41 | |
| 42 import 'package:fletchc/src/console_print.dart' show | |
| 43 printToConsole; | |
| 44 | |
| 45 import 'messages.dart' show | |
| 46 Info, | |
| 47 InternalErrorMessage, | |
| 48 ListTests, | |
| 49 ListTestsReply, | |
| 50 Message, | |
| 51 NamedMessage, | |
| 52 RunTest, | |
| 53 TestFailed, | |
| 54 TestPassed, | |
| 55 TestStdoutLine, | |
| 56 TimedOut, | |
| 57 messageTransformer; | |
| 58 | |
| 59 import 'all_tests.dart' show | |
| 60 NoArgFuture, | |
| 61 TESTS; | |
| 62 | |
| 63 // TODO(ahe): Should be: "@@@STEP_FAILURE@@@". | |
| 64 const String BUILDBOT_MARKER = "@@@STEP_WARNINGS@@@"; | |
| 65 | |
| 66 Map<String, NoArgFuture> expandedTests; | |
| 67 | |
| 68 Sink<Message> messageSink; | |
| 69 | |
| 70 main() async { | |
| 71 int port = const int.fromEnvironment("test.fletch_test_suite.port"); | |
| 72 Socket socket = await Socket.connect(InternetAddress.LOOPBACK_IP_V4, port); | |
| 73 messageSink = new SocketSink(socket); | |
| 74 IsolatePool pool = new IsolatePool(isolateMain); | |
| 75 Set<ManagedIsolate> isolates = new Set<ManagedIsolate>(); | |
| 76 Map<String, RunningTest> runningTests = <String, RunningTest>{}; | |
| 77 try { | |
| 78 Stream<Message> messages = utf8Lines(socket).transform(messageTransformer); | |
| 79 await for (Message message in messages) { | |
| 80 if (message is TimedOut) { | |
| 81 handleTimeout(message, runningTests); | |
| 82 continue; | |
| 83 } | |
| 84 ManagedIsolate isolate = await pool.getIsolate(); | |
| 85 isolates.add(isolate); | |
| 86 runInIsolate( | |
| 87 isolate.beginSession(), isolate, message, runningTests); | |
| 88 } | |
| 89 } catch (error, stackTrace) { | |
| 90 new InternalErrorMessage('$error', '$stackTrace').addTo(socket); | |
| 91 } | |
| 92 for (ManagedIsolate isolate in isolates) { | |
| 93 isolate.port.send(null); | |
| 94 } | |
| 95 await socket.close(); | |
| 96 } | |
| 97 | |
| 98 class RunningTest { | |
| 99 final cancelable; | |
| 100 final isolate; | |
| 101 RunningTest(this.cancelable, this.isolate); | |
| 102 | |
| 103 void kill() { | |
| 104 cancelable.cancel(); | |
| 105 isolate.kill(); | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 void handleTimeout(TimedOut message, Map<String, RunningTest> runningTests) { | |
| 110 RunningTest test = runningTests.remove(message.name); | |
| 111 if (test != null) { | |
| 112 test.kill(); | |
| 113 messageSink.add(message); | |
| 114 } else { | |
| 115 // This can happen for two reasons: | |
| 116 // 1. There's a bug, and test.dart will hang. | |
| 117 // 2. The test terminated just about the same time that test.dart decided | |
| 118 // it had timed out. | |
| 119 // Case 2 is unlikely, as tests aren't normally supposed to run for too | |
| 120 // long. Hopefully, case 1 is unlikely, but this message is helpful if | |
| 121 // test.dart hangs. | |
| 122 print("\nWarning: Unable to kill ${message.name}"); | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 void runInIsolate( | |
| 127 ReceivePort port, | |
| 128 ManagedIsolate isolate, | |
| 129 Message message, | |
| 130 Map<String, RunningTest> runningTests) { | |
| 131 StreamIterator iterator = new StreamIterator(port); | |
| 132 String name = message is NamedMessage ? message.name : null; | |
| 133 | |
| 134 if (name != null) { | |
| 135 runningTests[name] = new RunningTest(iterator, isolate); | |
| 136 } | |
| 137 | |
| 138 // The rest of this function is executed without "await" as we want tests to | |
| 139 // run in parallel on multiple isolates. | |
| 140 new Future<Null>(() async { | |
| 141 bool hasNext = await iterator.moveNext(); | |
| 142 if (!hasNext && name != null) { | |
| 143 // Timed out. | |
| 144 assert(runningTests[name] == null); | |
| 145 return null; | |
| 146 } | |
| 147 assert(hasNext); | |
| 148 SendPort sendPort = iterator.current; | |
| 149 sendPort.send(message); | |
| 150 | |
| 151 hasNext = await iterator.moveNext(); | |
| 152 if (!hasNext && name != null) { | |
| 153 // Timed out. | |
| 154 assert(runningTests[name] == null); | |
| 155 return null; | |
| 156 } | |
| 157 assert(hasNext); | |
| 158 do { | |
| 159 if (iterator.current == null) { | |
| 160 iterator.cancel(); | |
| 161 continue; | |
| 162 } | |
| 163 messageSink.add(iterator.current); | |
| 164 } while (await iterator.moveNext()); | |
| 165 runningTests.remove(name); | |
| 166 isolate.endSession(); | |
| 167 }).catchError((error, stackTrace) { | |
| 168 messageSink.add(new InternalErrorMessage('$error', '$stackTrace')); | |
| 169 }); | |
| 170 } | |
| 171 | |
| 172 /* void */ isolateMain(SendPort port) async { | |
| 173 expandedTests = await expandTests(TESTS); | |
| 174 ReceivePort receivePort = new ReceivePort(); | |
| 175 port.send(receivePort.sendPort); | |
| 176 port = null; | |
| 177 await for (SendPort port in receivePort) { | |
| 178 if (port == null) { | |
| 179 receivePort.close(); | |
| 180 break; | |
| 181 } | |
| 182 ReceivePort clientPort = new ReceivePort(); | |
| 183 port.send(clientPort.sendPort); | |
| 184 handleClient(port, clientPort); | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 Future<Null> handleClient(SendPort sendPort, ReceivePort receivePort) async { | |
| 189 messageSink = new PortSink(sendPort); | |
| 190 Message message = await receivePort.first; | |
| 191 Message reply; | |
| 192 if (message is RunTest) { | |
| 193 String name = message.name; | |
| 194 reply = await runTest(name, expandedTests[name]); | |
| 195 } else if (message is ListTests) { | |
| 196 reply = new ListTestsReply(expandedTests.keys.toList()); | |
| 197 } else { | |
| 198 reply = | |
| 199 new InternalErrorMessage("Unhandled message: ${message.type}", null); | |
| 200 } | |
| 201 sendPort.send(reply); | |
| 202 sendPort.send(null); // Ask the main isolate to stop listening. | |
| 203 receivePort.close(); | |
| 204 messageSink = null; | |
| 205 } | |
| 206 | |
| 207 Future<Message> runTest(String name, NoArgFuture test) async { | |
| 208 Directory tmpdir; | |
| 209 | |
| 210 printLineOnStdout(String line) { | |
| 211 if (messageSink != null) { | |
| 212 messageSink.add(new TestStdoutLine(name, line)); | |
| 213 } else { | |
| 214 stdout.writeln(line); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 Future setupGlobalStateForTesting() async { | |
| 219 tmpdir = await Directory.systemTemp.createTemp("fletch_test_home"); | |
| 220 configFileUri = tmpdir.uri.resolve('.fletch'); | |
| 221 printToConsole = printLineOnStdout; | |
| 222 } | |
| 223 | |
| 224 Future resetGlobalStateAfterTesting() async { | |
| 225 try { | |
| 226 await tmpdir.delete(recursive: true); | |
| 227 } on FileSystemException catch (e) { | |
| 228 printToConsole('Error when deleting $tmpdir: $e'); | |
| 229 } | |
| 230 printToConsole = Zone.ROOT.print; | |
| 231 } | |
| 232 | |
| 233 if (test == null) { | |
| 234 throw "No such test: $name"; | |
| 235 } | |
| 236 await setupGlobalStateForTesting(); | |
| 237 try { | |
| 238 await runGuarded( | |
| 239 test, | |
| 240 printLineOnStdout: printLineOnStdout, | |
| 241 handleLateError: (error, StackTrace stackTrace) { | |
| 242 if (name == 'zone_helper/testAlwaysFails') { | |
| 243 // This test always report a late error (to ensure the framework | |
| 244 // handles it). | |
| 245 return; | |
| 246 } | |
| 247 print( | |
| 248 // Print one string to avoid interleaved messages. | |
| 249 "\n$BUILDBOT_MARKER\nLate error in test '$name':\n" | |
| 250 "$error\n$stackTrace"); | |
| 251 }); | |
| 252 } catch (error, stackTrace) { | |
| 253 return new TestFailed(name, '$error', '$stackTrace'); | |
| 254 } finally { | |
| 255 await resetGlobalStateAfterTesting(); | |
| 256 } | |
| 257 return new TestPassed(name); | |
| 258 } | |
| 259 | |
| 260 Stream<String> utf8Lines(Stream<List<int>> stream) { | |
| 261 return stream.transform(UTF8.decoder).transform(new LineSplitter()); | |
| 262 } | |
| 263 | |
| 264 void print(object) { | |
| 265 if (messageSink != null) { | |
| 266 messageSink.add(new Info('$object')); | |
| 267 } else { | |
| 268 stdout.writeln('$object'); | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 Future<Map<String, NoArgFuture>> expandTests(Map<String, NoArgFuture> tests) { | |
| 273 Map<String, NoArgFuture> result = <String, NoArgFuture>{}; | |
| 274 var futures = []; | |
| 275 tests.forEach((String name, NoArgFuture f) { | |
| 276 if (name.endsWith("/*")) { | |
| 277 var future = f().then((Map<String, NoArgFuture> tests) { | |
| 278 tests.forEach((String name, NoArgFuture f) { | |
| 279 result[name] = f; | |
| 280 }); | |
| 281 }); | |
| 282 futures.add(future); | |
| 283 } else { | |
| 284 result[name] = f; | |
| 285 } | |
| 286 }); | |
| 287 return Future.wait(futures).then((_) => result); | |
| 288 } | |
| 289 | |
| 290 class SocketSink implements Sink<Message> { | |
| 291 final Socket socket; | |
| 292 | |
| 293 SocketSink(this.socket); | |
| 294 | |
| 295 void add(Message message) { | |
| 296 message.addTo(socket); | |
| 297 } | |
| 298 | |
| 299 void close() { | |
| 300 throw "not supported"; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 class PortSink implements Sink<Message> { | |
| 305 final SendPort port; | |
| 306 | |
| 307 PortSink(this.port); | |
| 308 | |
| 309 void add(Message message) { | |
| 310 port.send(message); | |
| 311 } | |
| 312 | |
| 313 void close() { | |
| 314 throw "not supported"; | |
| 315 } | |
| 316 } | |
| OLD | NEW |