| 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 /// Test suite for running tests in a shared Dart VM. Take a look at | |
| 6 /// ../../../tests/fletch_tests/all_tests.dart for more information. | |
| 7 library test.fletch_test_suite; | |
| 8 | |
| 9 import 'dart:io' as io; | |
| 10 | |
| 11 import 'dart:convert' show | |
| 12 JSON, | |
| 13 LineSplitter, | |
| 14 UTF8, | |
| 15 Utf8Decoder; | |
| 16 | |
| 17 import 'dart:async' show | |
| 18 Completer, | |
| 19 Future, | |
| 20 Stream, | |
| 21 StreamIterator, | |
| 22 Timer; | |
| 23 | |
| 24 import 'test_suite.dart' show | |
| 25 TestSuite, | |
| 26 TestUtils; | |
| 27 | |
| 28 import 'test_runner.dart' show | |
| 29 Command, | |
| 30 CommandBuilder, | |
| 31 CommandOutput, | |
| 32 TestCase; | |
| 33 | |
| 34 import 'runtime_configuration.dart' show | |
| 35 RuntimeConfiguration; | |
| 36 | |
| 37 import 'status_file_parser.dart' show | |
| 38 Expectation, | |
| 39 ReadTestExpectationsInto, | |
| 40 TestExpectations; | |
| 41 | |
| 42 import '../../../tests/fletch_tests/messages.dart' show | |
| 43 ErrorMessage, | |
| 44 Info, | |
| 45 ListTests, | |
| 46 ListTestsReply, | |
| 47 Message, | |
| 48 NamedMessage, | |
| 49 RunTest, | |
| 50 TestFailed, | |
| 51 TestStdoutLine, | |
| 52 TimedOut, | |
| 53 messageTransformer; | |
| 54 | |
| 55 class FletchTestRuntimeConfiguration extends RuntimeConfiguration { | |
| 56 final String system; | |
| 57 final String dartBinary; | |
| 58 | |
| 59 FletchTestRuntimeConfiguration(Map configuration) | |
| 60 : system = configuration['system'], | |
| 61 dartBinary = '${TestUtils.buildDir(configuration)}' | |
| 62 '${io.Platform.pathSeparator}dart', | |
| 63 super.subclass(); | |
| 64 } | |
| 65 | |
| 66 class FletchTestSuite extends TestSuite { | |
| 67 final String testSuiteDir; | |
| 68 | |
| 69 TestCompleter completer; | |
| 70 | |
| 71 FletchTestSuite(Map configuration, this.testSuiteDir) | |
| 72 : super(configuration, "fletch_tests"); | |
| 73 | |
| 74 void forEachTest( | |
| 75 void onTest(TestCase testCase), | |
| 76 Map testCache, | |
| 77 [void onDone()]) { | |
| 78 this.doTest = onTest; | |
| 79 if (configuration['runtime'] != 'fletch_tests') { | |
| 80 onDone(); | |
| 81 return; | |
| 82 } | |
| 83 | |
| 84 FletchTestRuntimeConfiguration runtimeConfiguration = | |
| 85 new RuntimeConfiguration(configuration); | |
| 86 | |
| 87 TestExpectations expectations = new TestExpectations(); | |
| 88 String buildDir = TestUtils.buildDir(configuration); | |
| 89 String version; | |
| 90 | |
| 91 // Define a path for temporary output generated during tests. | |
| 92 String tempDirPath = '$buildDir/temporary_test_output'; | |
| 93 io.Directory tempDir = new io.Directory(tempDirPath); | |
| 94 try { | |
| 95 tempDir.deleteSync(recursive: true); | |
| 96 } on io.FileSystemException catch (e) { | |
| 97 // Ignored, we assume the file did not exist. | |
| 98 } | |
| 99 | |
| 100 String javaHome = _guessJavaHome(configuration["arch"]); | |
| 101 if (javaHome == null) { | |
| 102 String arch = configuration["arch"]; | |
| 103 print("Notice: Java tests are disabled"); | |
| 104 print("Unable to find a JDK installation for architecture $arch"); | |
| 105 print("Install a JDK or set JAVA_PATH to an existing installation."); | |
| 106 // TODO(zerny): Throw an error if no-java is not supplied. | |
| 107 } else { | |
| 108 print("Notice: Enabled Java tests using JDK at $javaHome"); | |
| 109 } | |
| 110 | |
| 111 bool helperProgramExited = false; | |
| 112 io.Process vmProcess; | |
| 113 ReadTestExpectationsInto( | |
| 114 expectations, '$testSuiteDir/fletch_tests.status', | |
| 115 configuration).then((_) { | |
| 116 return new io.File('$buildDir/gen/version.cc').readAsLines(); | |
| 117 }).then((List<String> versionFileLines) { | |
| 118 // Search for the 'return "version_string";' line. | |
| 119 for (String line in versionFileLines) { | |
| 120 if (line.contains('return')) { | |
| 121 version = line.substring( | |
| 122 line.indexOf('"') + 1, line.lastIndexOf('"')); | |
| 123 } | |
| 124 } | |
| 125 assert(version != null); | |
| 126 }).then((_) { | |
| 127 return io.ServerSocket.bind(io.InternetAddress.LOOPBACK_IP_V4, 0); | |
| 128 }).then((io.ServerSocket server) { | |
| 129 return io.Process.start( | |
| 130 runtimeConfiguration.dartBinary, | |
| 131 ['-Dfletch-vm=$buildDir/fletch-vm', | |
| 132 '-Dfletch.version=$version', | |
| 133 '-Ddart-sdk=third_party/dart/sdk/', | |
| 134 '-Dtests-dir=tests/', | |
| 135 '-Djava-home=$javaHome', | |
| 136 '-Dtest.dart.build-dir=$buildDir', | |
| 137 '-Dtest.dart.build-arch=${configuration["arch"]}', | |
| 138 '-Dtest.dart.build-system=${configuration["system"]}', | |
| 139 '-Dtest.dart.build-clang=${configuration["clang"]}', | |
| 140 '-Dtest.dart.build-asan=${configuration["asan"]}', | |
| 141 '-Dtest.dart.temp-dir=$tempDirPath', | |
| 142 '-Dtest.dart.servicec-dir=tools/servicec/', | |
| 143 '-c', | |
| 144 '--packages=.packages', | |
| 145 '-Dtest.fletch_test_suite.port=${server.port}', | |
| 146 '$testSuiteDir/fletch_test_suite.dart']).then((io.Process process) { | |
| 147 process.exitCode.then((_) { | |
| 148 helperProgramExited = true; | |
| 149 server.close(); | |
| 150 }); | |
| 151 vmProcess = process; | |
| 152 return process.stdin.close(); | |
| 153 }).then((_) { | |
| 154 return server.first.catchError((error) { | |
| 155 // The VM died before we got a connection. | |
| 156 assert(helperProgramExited); | |
| 157 return null; | |
| 158 }); | |
| 159 }).then((io.Socket socket) { | |
| 160 server.close(); | |
| 161 return socket; | |
| 162 }); | |
| 163 }).then((io.Socket socket) { | |
| 164 assert(socket != null || helperProgramExited); | |
| 165 completer = new TestCompleter(vmProcess, socket); | |
| 166 completer.initialize(); | |
| 167 return completer.requestTestNames(); | |
| 168 }).then((List<String> testNames) { | |
| 169 for (String name in testNames) { | |
| 170 Set<Expectation> expectedOutcomes = expectations.expectations(name); | |
| 171 TestCase testCase = new TestCase( | |
| 172 'fletch_tests/$name', <Command>[], configuration, expectedOutcomes); | |
| 173 var command = new FletchTestCommand(name, completer); | |
| 174 testCase.commands.add(command); | |
| 175 if (!expectedOutcomes.contains(Expectation.SKIP)) { | |
| 176 completer.expect(command); | |
| 177 } | |
| 178 enqueueNewTestCase(testCase); | |
| 179 } | |
| 180 }).then((_) { | |
| 181 onDone(); | |
| 182 }); | |
| 183 } | |
| 184 | |
| 185 void cleanup() { | |
| 186 completer.allDone(); | |
| 187 } | |
| 188 | |
| 189 String _guessJavaHome(String buildArchitecture) { | |
| 190 String arch = buildArchitecture == 'ia32' ? '32' : '64'; | |
| 191 | |
| 192 // Try to locate a valid installation based on JAVA_HOME. | |
| 193 String javaHome = | |
| 194 _guessJavaHomeArch(io.Platform.environment['JAVA_HOME'], arch); | |
| 195 if (javaHome != null) return javaHome; | |
| 196 | |
| 197 // Try to locate a valid installation using the java_home utility. | |
| 198 String javaHomeUtil = '/usr/libexec/java_home'; | |
| 199 if (new io.File(javaHomeUtil).existsSync()) { | |
| 200 List<String> args = <String>['-v', '1.6+', '-d', arch]; | |
| 201 io.ProcessResult result = | |
| 202 io.Process.runSync(javaHomeUtil, args); | |
| 203 if (result.exitCode == 0) { | |
| 204 String javaHome = result.stdout.trim(); | |
| 205 if (_isValidJDK(javaHome)) return javaHome; | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 // Try to locate a valid installation using the path to javac. | |
| 210 io.ProcessResult result = | |
| 211 io.Process.runSync('command', ['-v', 'javac'], runInShell: true); | |
| 212 if (result.exitCode == 0) { | |
| 213 String javac = result.stdout.trim(); | |
| 214 while (io.FileSystemEntity.isLinkSync(javac)) { | |
| 215 javac = new io.Link(javac).resolveSymbolicLinksSync(); | |
| 216 } | |
| 217 // TODO(zerny): Take into account Mac javac paths can be of the form: | |
| 218 // .../Versions/X/Commands/javac | |
| 219 String javaHome = | |
| 220 _guessJavaHomeArch(javac.replaceAll('/bin/javac', ''), arch); | |
| 221 if (javaHome != null) return javaHome; | |
| 222 } | |
| 223 | |
| 224 return null; | |
| 225 } | |
| 226 | |
| 227 String _guessJavaHomeArch(String javaHome, String arch) { | |
| 228 if (javaHome == null) return null; | |
| 229 | |
| 230 // Check if the java installation supports the requested architecture. | |
| 231 if (new io.File('$javaHome/bin/java').existsSync()) { | |
| 232 int supportsVersion = io.Process.runSync( | |
| 233 '$javaHome/bin/java', ['-d$arch', '-version']).exitCode; | |
| 234 if (supportsVersion == 0 && _isValidJDK(javaHome)) return javaHome; | |
| 235 } | |
| 236 | |
| 237 // Check for architecture specific installation by post-fixing arch. | |
| 238 String archPostfix = '${javaHome}-$arch'; | |
| 239 if (_isValidJDK(archPostfix)) return archPostfix; | |
| 240 | |
| 241 // Check for architecture specific installation by replacing amd64 and i386. | |
| 242 String archReplace; | |
| 243 if (arch == '32' && javaHome.contains('amd64')) { | |
| 244 archReplace = javaHome.replaceAll('amd64', 'i386'); | |
| 245 } else if (arch == '64' && javaHome.contains('i386')) { | |
| 246 archReplace = javaHome.replaceAll('i386', 'amd64'); | |
| 247 } | |
| 248 if (_isValidJDK(archReplace)) return archReplace; | |
| 249 | |
| 250 return null; | |
| 251 } | |
| 252 | |
| 253 bool _isValidJDK(String javaHome) { | |
| 254 if (javaHome == null) return false; | |
| 255 return new io.File('$javaHome/include/jni.h').existsSync(); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 /// Pattern that matches warnings (from dart2js) that contain a comment saying | |
| 260 /// "NO_LINT". | |
| 261 final RegExp noLintFilter = | |
| 262 new RegExp(r"[^\n]*\n[^\n]*\n[^\n]* // NO_LINT\n *\^+\n"); | |
| 263 | |
| 264 class FletchTestOutputCommand implements CommandOutput { | |
| 265 final Command command; | |
| 266 final Duration time; | |
| 267 final Message message; | |
| 268 final List<String> stdoutLines; | |
| 269 | |
| 270 FletchTestOutputCommand( | |
| 271 this.command, | |
| 272 this.message, | |
| 273 this.time, | |
| 274 this.stdoutLines); | |
| 275 | |
| 276 Expectation result(TestCase testCase) { | |
| 277 switch (message.type) { | |
| 278 case 'TestPassed': | |
| 279 return Expectation.PASS; | |
| 280 | |
| 281 case 'TestFailed': | |
| 282 return Expectation.FAIL; | |
| 283 | |
| 284 case 'TimedOut': | |
| 285 return Expectation.TIMEOUT; | |
| 286 | |
| 287 default: | |
| 288 return Expectation.CRASH; | |
| 289 } | |
| 290 } | |
| 291 | |
| 292 bool get hasCrashed => false; | |
| 293 | |
| 294 bool get hasTimedOut => false; | |
| 295 | |
| 296 bool didFail(testCase) => message.type != 'TestPassed'; | |
| 297 | |
| 298 bool hasFailed(TestCase testCase) { | |
| 299 return testCase.isNegative ? !didFail(testCase) : didFail(testCase); | |
| 300 } | |
| 301 | |
| 302 bool get canRunDependendCommands => false; | |
| 303 | |
| 304 bool get successful => true; | |
| 305 | |
| 306 int get exitCode => 0; | |
| 307 | |
| 308 int get pid => 0; | |
| 309 | |
| 310 List<int> get stdout { | |
| 311 if (stdoutLines != null) { | |
| 312 return UTF8.encode(stdoutLines.join("\n")); | |
| 313 } else { | |
| 314 return <int>[]; | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 List<int> get stderr { | |
| 319 String result; | |
| 320 | |
| 321 switch (message.type) { | |
| 322 case 'TestPassed': | |
| 323 case 'TimedOut': | |
| 324 return <int>[]; | |
| 325 | |
| 326 case 'TestFailed': | |
| 327 TestFailed failed = message; | |
| 328 result = '${failed.error}\n${failed.stackTrace}'; | |
| 329 break; | |
| 330 | |
| 331 default: | |
| 332 result = '$message'; | |
| 333 break; | |
| 334 } | |
| 335 return UTF8.encode(result); | |
| 336 } | |
| 337 | |
| 338 List<String> get diagnostics => <String>[]; | |
| 339 | |
| 340 bool get compilationSkipped => false; | |
| 341 } | |
| 342 | |
| 343 class FletchTestCommand implements Command { | |
| 344 final String _name; | |
| 345 | |
| 346 final TestCompleter _completer; | |
| 347 | |
| 348 FletchTestCommand(this._name, this._completer); | |
| 349 | |
| 350 String get displayName => "fletch_test"; | |
| 351 | |
| 352 int get maxNumRetries => 0; | |
| 353 | |
| 354 Future<FletchTestOutputCommand> run(int timeout) { | |
| 355 Stopwatch sw = new Stopwatch()..start(); | |
| 356 return _completer.run(this, timeout).then((NamedMessage message) { | |
| 357 FletchTestOutputCommand output = | |
| 358 new FletchTestOutputCommand( | |
| 359 this, message, sw.elapsed, _completer.testOutput[message.name]); | |
| 360 _completer.done(this); | |
| 361 return output; | |
| 362 }); | |
| 363 } | |
| 364 | |
| 365 String toString() => 'FletchTestCommand($_name)'; | |
| 366 | |
| 367 set displayName(_) => throw "not supported"; | |
| 368 | |
| 369 get commandLine => throw "not supported"; | |
| 370 set commandLine(_) => throw "not supported"; | |
| 371 | |
| 372 String get reproductionCommand => throw "not supported"; | |
| 373 | |
| 374 get outputIsUpToDate => throw "not supported"; | |
| 375 } | |
| 376 | |
| 377 class TestCompleter { | |
| 378 final Map<String, FletchTestCommand> expected = | |
| 379 new Map<String, FletchTestCommand>(); | |
| 380 final Map<String, Completer> completers = new Map<String, Completer>(); | |
| 381 final Completer<List<String>> testNamesCompleter = | |
| 382 new Completer<List<String>>(); | |
| 383 final Map<String, List<String>> testOutput = new Map<String, List<String>>(); | |
| 384 final io.Process vmProcess; | |
| 385 final io.Socket socket; | |
| 386 | |
| 387 int exitCode; | |
| 388 String stderr = ""; | |
| 389 | |
| 390 TestCompleter(this.vmProcess, this.socket); | |
| 391 | |
| 392 void initialize() { | |
| 393 List<String> stderrLines = <String>[]; | |
| 394 Future stdoutFuture = | |
| 395 vmProcess.stdout.transform(UTF8.decoder).transform(new LineSplitter()) | |
| 396 .listen(io.stdout.writeln).asFuture(); | |
| 397 bool inDartVmUncaughtMessage = false; | |
| 398 Future stderrFuture = | |
| 399 vmProcess.stderr.transform(UTF8.decoder).transform(new LineSplitter()) | |
| 400 .listen((String line) { | |
| 401 io.stderr.writeln(line); | |
| 402 stderrLines.add(line); | |
| 403 }).asFuture(); | |
| 404 vmProcess.exitCode.then((value) { | |
| 405 exitCode = value; | |
| 406 stderr = stderrLines.join("\n"); | |
| 407 for (String name in completers.keys) { | |
| 408 Completer completer = completers[name]; | |
| 409 completer.complete( | |
| 410 new TestFailed( | |
| 411 name, | |
| 412 "Helper program exited prematurely with exit code $exitCode.", | |
| 413 stderr)); | |
| 414 } | |
| 415 if (exitCode != 0) { | |
| 416 stdoutFuture.then((_) => stderrFuture).then((_) { | |
| 417 throw "Helper program exited with exit code $exitCode.\n$stderr"; | |
| 418 }); | |
| 419 } | |
| 420 }); | |
| 421 // TODO(ahe): Don't use StreamIterator here, just use listen and | |
| 422 // processMessage. | |
| 423 StreamIterator<Message> messages; | |
| 424 if (socket == null) { | |
| 425 messages = new StreamIterator<Message>( | |
| 426 new Stream<Message>.fromIterable(<Message>[])); | |
| 427 } else { | |
| 428 messages = new StreamIterator<Message>( | |
| 429 socket | |
| 430 .transform(UTF8.decoder).transform(new LineSplitter()) | |
| 431 .transform(messageTransformer)); | |
| 432 } | |
| 433 process(messages); | |
| 434 } | |
| 435 | |
| 436 Future<List<String>> requestTestNames() { | |
| 437 if (socket == null) return new Future.value(<String>[]); | |
| 438 socket.writeln(JSON.encode(const ListTests().toJson())); | |
| 439 return testNamesCompleter.future; | |
| 440 } | |
| 441 | |
| 442 void expect(FletchTestCommand command) { | |
| 443 expected[command._name] = command; | |
| 444 } | |
| 445 | |
| 446 void done(FletchTestCommand command) { | |
| 447 expected.remove(command._name); | |
| 448 if (expected.isEmpty) { | |
| 449 allDone(); | |
| 450 } | |
| 451 } | |
| 452 | |
| 453 Future run(FletchTestCommand command, int timeout) { | |
| 454 if (command._name == "self/testNeverCompletes") { | |
| 455 // Ensure timeout test times out quickly. | |
| 456 timeout = 1; | |
| 457 } | |
| 458 socket.writeln( | |
| 459 JSON.encode(new RunTest(command._name).toJson())); | |
| 460 Timer timer = new Timer(new Duration(seconds: timeout), () { | |
| 461 socket.writeln( | |
| 462 JSON.encode(new TimedOut(command._name).toJson())); | |
| 463 }); | |
| 464 | |
| 465 Completer completer = new Completer(); | |
| 466 completers[command._name] = completer; | |
| 467 if (exitCode != null) { | |
| 468 completer.complete( | |
| 469 new TestFailed( | |
| 470 command._name, | |
| 471 "Helper program exited prematurely with exit code $exitCode.", | |
| 472 stderr)); | |
| 473 } | |
| 474 return completer.future.then((value) { | |
| 475 timer.cancel(); | |
| 476 return value; | |
| 477 }); | |
| 478 } | |
| 479 | |
| 480 void processMessage(Message message) { | |
| 481 switch (message.type) { | |
| 482 case 'Info': | |
| 483 Info info = message; | |
| 484 // For debugging, shouldn't normally be called. | |
| 485 print(info.data); | |
| 486 break; | |
| 487 case 'TestPassed': | |
| 488 case 'TestFailed': | |
| 489 case 'TimedOut': | |
| 490 NamedMessage namedMessage = message; | |
| 491 Completer completer = completers.remove(namedMessage.name); | |
| 492 completer.complete(message); | |
| 493 break; | |
| 494 case 'ListTestsReply': | |
| 495 ListTestsReply reply = message; | |
| 496 testNamesCompleter.complete(reply.tests); | |
| 497 break; | |
| 498 case 'InternalErrorMessage': | |
| 499 ErrorMessage error = message; | |
| 500 print(error.error); | |
| 501 print(error.stackTrace); | |
| 502 throw "Internal error in helper process: ${error.error}"; | |
| 503 case 'TestStdoutLine': | |
| 504 TestStdoutLine line = message; | |
| 505 testOutput.putIfAbsent(line.name, () => <String>[]).add(line.line); | |
| 506 break; | |
| 507 default: | |
| 508 throw "Unhandled message from helper process: $message"; | |
| 509 } | |
| 510 } | |
| 511 | |
| 512 void process(StreamIterator<Message> messages) { | |
| 513 messages.moveNext().then((bool hasNext) { | |
| 514 if (hasNext) { | |
| 515 processMessage(messages.current); | |
| 516 process(messages); | |
| 517 } | |
| 518 }); | |
| 519 } | |
| 520 | |
| 521 void allDone() { | |
| 522 // This should cause the vmProcess to exit. | |
| 523 socket.close(); | |
| 524 } | |
| 525 } | |
| OLD | NEW |