| 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 * Classes and methods for executing tests. | 6 * Classes and methods for executing tests. |
| 7 * | 7 * |
| 8 * This module includes: | 8 * This module includes: |
| 9 * - Managing parallel execution of tests, including timeout checks. | 9 * - Managing parallel execution of tests, including timeout checks. |
| 10 * - Evaluating the output of each test as pass/fail/crash/timeout. | 10 * - Evaluating the output of each test as pass/fail/crash/timeout. |
| 11 */ | 11 */ |
| 12 library test_runner; | 12 library test_runner; |
| 13 | 13 |
| 14 import "dart:async"; | 14 import 'dart:async'; |
| 15 import "dart:collection" show Queue; | 15 import 'dart:collection' show Queue; |
| 16 import "dart:convert" show LineSplitter, UTF8, JSON; | 16 import 'dart:convert' show LineSplitter, UTF8, JSON; |
| 17 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow | 17 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow |
| 18 // CommandOutput.exitCode in subclasses of CommandOutput. | 18 // CommandOutput.exitCode in subclasses of CommandOutput. |
| 19 import "dart:io" as io; | 19 import 'dart:io' as io; |
| 20 import "dart:math" as math; | 20 import 'dart:math' as math; |
| 21 import 'dependency_graph.dart' as dgraph; | |
| 22 import "browser_controller.dart"; | |
| 23 import "path.dart"; | |
| 24 import "status_file_parser.dart"; | |
| 25 import "test_progress.dart"; | |
| 26 import "test_suite.dart"; | |
| 27 import "utils.dart"; | |
| 28 import 'record_and_replay.dart'; | |
| 29 | 21 |
| 30 const int CRASHING_BROWSER_EXITCODE = -10; | 22 import 'lib/browser_controller.dart'; |
| 31 const int SLOW_TIMEOUT_MULTIPLIER = 4; | 23 import 'lib/command.dart'; |
| 24 import 'lib/dependency_graph.dart' as dgraph; |
| 25 import 'lib/path.dart'; |
| 26 import 'lib/record_and_replay.dart'; |
| 27 import 'lib/test_case.dart'; |
| 28 import 'lib/test_information.dart'; |
| 29 import 'lib/test_progress.dart'; |
| 30 import 'lib/utils.dart'; |
| 31 import 'test_suite.dart'; |
| 32 | 32 |
| 33 const MESSAGE_CANNOT_OPEN_DISPLAY = 'Gtk-WARNING **: cannot open display'; | |
| 34 const MESSAGE_FAILED_TO_RUN_COMMAND = 'Failed to run command. return code=1'; | |
| 35 | |
| 36 typedef void TestCaseEvent(TestCase testCase); | |
| 37 typedef void ExitCodeEvent(int exitCode); | 33 typedef void ExitCodeEvent(int exitCode); |
| 38 typedef void EnqueueMoreWork(ProcessQueue queue); | 34 typedef void EnqueueMoreWork(ProcessQueue queue); |
| 39 | 35 |
| 40 // Some IO tests use these variables and get confused if the host environment | 36 // Some IO tests use these variables and get confused if the host environment |
| 41 // variables are inherited so they are excluded. | 37 // variables are inherited so they are excluded. |
| 42 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES = | 38 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES = |
| 43 const ['http_proxy', 'https_proxy', 'no_proxy', | 39 const ['http_proxy', 'https_proxy', 'no_proxy', |
| 44 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; | 40 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; |
| 45 | 41 |
| 46 | |
| 47 /** A command executed as a step in a test case. */ | |
| 48 class Command { | |
| 49 /** A descriptive name for this command. */ | |
| 50 String displayName; | |
| 51 | |
| 52 /** Number of times this command *can* be retried */ | |
| 53 int get maxNumRetries => 2; | |
| 54 | |
| 55 /** Reproduction command */ | |
| 56 String get reproductionCommand => null; | |
| 57 | |
| 58 // We compute the Command.hashCode lazily and cache it here, since it might | |
| 59 // be expensive to compute (and hashCode is called often). | |
| 60 int _cachedHashCode; | |
| 61 | |
| 62 Command._(this.displayName); | |
| 63 | |
| 64 int get hashCode { | |
| 65 if (_cachedHashCode == null) { | |
| 66 var builder = new HashCodeBuilder(); | |
| 67 _buildHashCode(builder); | |
| 68 _cachedHashCode = builder.value; | |
| 69 } | |
| 70 return _cachedHashCode; | |
| 71 } | |
| 72 | |
| 73 operator ==(other) => identical(this, other) || | |
| 74 (runtimeType == other.runtimeType && _equal(other)); | |
| 75 | |
| 76 void _buildHashCode(HashCodeBuilder builder) { | |
| 77 builder.addJson(displayName); | |
| 78 } | |
| 79 | |
| 80 bool _equal(Command other) => | |
| 81 hashCode == other.hashCode && | |
| 82 displayName == other.displayName; | |
| 83 | |
| 84 String toString() => reproductionCommand; | |
| 85 | |
| 86 Future<bool> get outputIsUpToDate => new Future.value(false); | |
| 87 } | |
| 88 | |
| 89 class ProcessCommand extends Command { | |
| 90 /** Path to the executable of this command. */ | |
| 91 String executable; | |
| 92 | |
| 93 /** Command line arguments to the executable. */ | |
| 94 List<String> arguments; | |
| 95 | |
| 96 /** Environment for the command */ | |
| 97 Map<String, String> environmentOverrides; | |
| 98 | |
| 99 /** Working directory for the command */ | |
| 100 final String workingDirectory; | |
| 101 | |
| 102 ProcessCommand._(String displayName, this.executable, | |
| 103 this.arguments, | |
| 104 [this.environmentOverrides = null, | |
| 105 this.workingDirectory = null]) | |
| 106 : super._(displayName) { | |
| 107 if (io.Platform.operatingSystem == 'windows') { | |
| 108 // Windows can't handle the first command if it is a .bat file or the like | |
| 109 // with the slashes going the other direction. | |
| 110 // NOTE: Issue 1306 | |
| 111 executable = executable.replaceAll('/', '\\'); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 void _buildHashCode(HashCodeBuilder builder) { | |
| 116 super._buildHashCode(builder); | |
| 117 builder.addJson(executable); | |
| 118 builder.addJson(workingDirectory); | |
| 119 builder.addJson(arguments); | |
| 120 builder.addJson(environmentOverrides); | |
| 121 } | |
| 122 | |
| 123 bool _equal(ProcessCommand other) => | |
| 124 super._equal(other) && | |
| 125 executable == other.executable && | |
| 126 deepJsonCompare(arguments, other.arguments) && | |
| 127 workingDirectory == other.workingDirectory && | |
| 128 deepJsonCompare(environmentOverrides, other.environmentOverrides); | |
| 129 | |
| 130 String get reproductionCommand { | |
| 131 var command = ([executable]..addAll(arguments)) | |
| 132 .map(escapeCommandLineArgument).join(' '); | |
| 133 if (workingDirectory != null) { | |
| 134 command = "$command (working directory: $workingDirectory)"; | |
| 135 } | |
| 136 return command; | |
| 137 } | |
| 138 | |
| 139 Future<bool> get outputIsUpToDate => new Future.value(false); | |
| 140 } | |
| 141 | |
| 142 class CompilationCommand extends ProcessCommand { | |
| 143 final String _outputFile; | |
| 144 final bool _neverSkipCompilation; | |
| 145 final List<Uri> _bootstrapDependencies; | |
| 146 | |
| 147 CompilationCommand._(String displayName, | |
| 148 this._outputFile, | |
| 149 this._neverSkipCompilation, | |
| 150 this._bootstrapDependencies, | |
| 151 String executable, | |
| 152 List<String> arguments, | |
| 153 Map<String, String> environmentOverrides) | |
| 154 : super._(displayName, executable, arguments, environmentOverrides); | |
| 155 | |
| 156 Future<bool> get outputIsUpToDate { | |
| 157 if (_neverSkipCompilation) return new Future.value(false); | |
| 158 | |
| 159 Future<List<Uri>> readDepsFile(String path) { | |
| 160 var file = new io.File(new Path(path).toNativePath()); | |
| 161 if (!file.existsSync()) { | |
| 162 return new Future.value(null); | |
| 163 } | |
| 164 return file.readAsLines().then((List<String> lines) { | |
| 165 var dependencies = new List<Uri>(); | |
| 166 for (var line in lines) { | |
| 167 line = line.trim(); | |
| 168 if (line.length > 0) { | |
| 169 dependencies.add(Uri.parse(line)); | |
| 170 } | |
| 171 } | |
| 172 return dependencies; | |
| 173 }); | |
| 174 } | |
| 175 | |
| 176 return readDepsFile("$_outputFile.deps").then((dependencies) { | |
| 177 if (dependencies != null) { | |
| 178 dependencies.addAll(_bootstrapDependencies); | |
| 179 var jsOutputLastModified = TestUtils.lastModifiedCache.getLastModified( | |
| 180 new Uri(scheme: 'file', path: _outputFile)); | |
| 181 if (jsOutputLastModified != null) { | |
| 182 for (var dependency in dependencies) { | |
| 183 var dependencyLastModified = | |
| 184 TestUtils.lastModifiedCache.getLastModified(dependency); | |
| 185 if (dependencyLastModified == null || | |
| 186 dependencyLastModified.isAfter(jsOutputLastModified)) { | |
| 187 return false; | |
| 188 } | |
| 189 } | |
| 190 return true; | |
| 191 } | |
| 192 } | |
| 193 return false; | |
| 194 }); | |
| 195 } | |
| 196 | |
| 197 void _buildHashCode(HashCodeBuilder builder) { | |
| 198 super._buildHashCode(builder); | |
| 199 builder.addJson(_outputFile); | |
| 200 builder.addJson(_neverSkipCompilation); | |
| 201 builder.addJson(_bootstrapDependencies); | |
| 202 } | |
| 203 | |
| 204 bool _equal(CompilationCommand other) => | |
| 205 super._equal(other) && | |
| 206 _outputFile == other._outputFile && | |
| 207 _neverSkipCompilation == other._neverSkipCompilation && | |
| 208 deepJsonCompare(_bootstrapDependencies, other._bootstrapDependencies); | |
| 209 } | |
| 210 | |
| 211 /// This is just a Pair(String, Map) class with hashCode and operator == | |
| 212 class AddFlagsKey { | |
| 213 final String flags; | |
| 214 final Map env; | |
| 215 AddFlagsKey(this.flags, this.env); | |
| 216 // Just use object identity for environment map | |
| 217 bool operator ==(other) => | |
| 218 other is AddFlagsKey && flags == other.flags && env == other.env; | |
| 219 int get hashCode => flags.hashCode ^ env.hashCode; | |
| 220 } | |
| 221 | |
| 222 class ContentShellCommand extends ProcessCommand { | |
| 223 ContentShellCommand._(String executable, | |
| 224 String htmlFile, | |
| 225 List<String> options, | |
| 226 List<String> dartFlags, | |
| 227 Map<String, String> environmentOverrides) | |
| 228 : super._("content_shell", | |
| 229 executable, | |
| 230 _getArguments(options, htmlFile), | |
| 231 _getEnvironment(environmentOverrides, dartFlags)); | |
| 232 | |
| 233 // Cache the modified environments in a map from the old environment and | |
| 234 // the string of Dart flags to the new environment. Avoid creating new | |
| 235 // environment object for each command object. | |
| 236 static Map<AddFlagsKey, Map> environments = | |
| 237 new Map<AddFlagsKey, Map>(); | |
| 238 | |
| 239 static Map _getEnvironment(Map env, List<String> dartFlags) { | |
| 240 var needDartFlags = dartFlags != null && dartFlags.length > 0; | |
| 241 if (needDartFlags) { | |
| 242 if (env == null) { | |
| 243 env = const { }; | |
| 244 } | |
| 245 var flags = dartFlags.join(' '); | |
| 246 return environments.putIfAbsent(new AddFlagsKey(flags, env), | |
| 247 () => new Map.from(env) | |
| 248 ..addAll({'DART_FLAGS': flags, 'DART_FORWARDING_PRINT': '1'})); | |
| 249 } | |
| 250 return env; | |
| 251 } | |
| 252 | |
| 253 static List<String> _getArguments(List<String> options, String htmlFile) { | |
| 254 var arguments = new List.from(options); | |
| 255 arguments.add(htmlFile); | |
| 256 return arguments; | |
| 257 } | |
| 258 | |
| 259 int get maxNumRetries => 3; | |
| 260 } | |
| 261 | |
| 262 class BrowserTestCommand extends Command { | |
| 263 final String browser; | |
| 264 final String url; | |
| 265 final Map configuration; | |
| 266 final bool retry; | |
| 267 | |
| 268 BrowserTestCommand._(String _browser, | |
| 269 this.url, | |
| 270 this.configuration, | |
| 271 this.retry) | |
| 272 : super._(_browser), browser = _browser; | |
| 273 | |
| 274 void _buildHashCode(HashCodeBuilder builder) { | |
| 275 super._buildHashCode(builder); | |
| 276 builder.addJson(browser); | |
| 277 builder.addJson(url); | |
| 278 builder.add(configuration); | |
| 279 builder.add(retry); | |
| 280 } | |
| 281 | |
| 282 bool _equal(BrowserTestCommand other) => | |
| 283 super._equal(other) && | |
| 284 browser == other.browser && | |
| 285 url == other.url && | |
| 286 identical(configuration, other.configuration) && | |
| 287 retry == other.retry; | |
| 288 | |
| 289 String get reproductionCommand { | |
| 290 var parts = [TestUtils.dartTestExecutable.toString(), | |
| 291 'tools/testing/dart/launch_browser.dart', | |
| 292 browser, | |
| 293 url]; | |
| 294 return parts.map(escapeCommandLineArgument).join(' '); | |
| 295 } | |
| 296 | |
| 297 int get maxNumRetries => 4; | |
| 298 } | |
| 299 | |
| 300 class BrowserHtmlTestCommand extends BrowserTestCommand { | |
| 301 List<String> expectedMessages; | |
| 302 BrowserHtmlTestCommand._(String browser, | |
| 303 String url, | |
| 304 Map configuration, | |
| 305 this.expectedMessages, | |
| 306 bool retry) | |
| 307 : super._(browser, url, configuration, retry); | |
| 308 | |
| 309 void _buildHashCode(HashCodeBuilder builder) { | |
| 310 super._buildHashCode(builder); | |
| 311 builder.addJson(expectedMessages); | |
| 312 } | |
| 313 | |
| 314 bool _equal(BrowserHtmlTestCommand other) => | |
| 315 super._equal(other) && | |
| 316 identical(expectedMessages, other.expectedMessages); | |
| 317 } | |
| 318 | |
| 319 class AnalysisCommand extends ProcessCommand { | |
| 320 final String flavor; | |
| 321 | |
| 322 AnalysisCommand._(this.flavor, | |
| 323 String displayName, | |
| 324 String executable, | |
| 325 List<String> arguments, | |
| 326 Map<String, String> environmentOverrides) | |
| 327 : super._(displayName, executable, arguments, environmentOverrides); | |
| 328 | |
| 329 void _buildHashCode(HashCodeBuilder builder) { | |
| 330 super._buildHashCode(builder); | |
| 331 builder.addJson(flavor); | |
| 332 } | |
| 333 | |
| 334 bool _equal(AnalysisCommand other) => | |
| 335 super._equal(other) && | |
| 336 flavor == other.flavor; | |
| 337 } | |
| 338 | |
| 339 class VmCommand extends ProcessCommand { | |
| 340 VmCommand._(String executable, | |
| 341 List<String> arguments, | |
| 342 Map<String,String> environmentOverrides) | |
| 343 : super._("vm", executable, arguments, environmentOverrides); | |
| 344 } | |
| 345 | |
| 346 class JSCommandlineCommand extends ProcessCommand { | |
| 347 JSCommandlineCommand._(String displayName, | |
| 348 String executable, | |
| 349 List<String> arguments, | |
| 350 [Map<String, String> environmentOverrides = null]) | |
| 351 : super._(displayName, | |
| 352 executable, | |
| 353 arguments, | |
| 354 environmentOverrides); | |
| 355 } | |
| 356 | |
| 357 class PubCommand extends ProcessCommand { | |
| 358 final String command; | |
| 359 | |
| 360 PubCommand._(String pubCommand, | |
| 361 String pubExecutable, | |
| 362 String pubspecYamlDirectory, | |
| 363 String pubCacheDirectory) | |
| 364 : super._('pub_$pubCommand', | |
| 365 new io.File(pubExecutable).absolute.path, | |
| 366 [pubCommand], | |
| 367 {'PUB_CACHE' : pubCacheDirectory}, | |
| 368 pubspecYamlDirectory), command = pubCommand; | |
| 369 | |
| 370 void _buildHashCode(HashCodeBuilder builder) { | |
| 371 super._buildHashCode(builder); | |
| 372 builder.addJson(command); | |
| 373 } | |
| 374 | |
| 375 bool _equal(PubCommand other) => | |
| 376 super._equal(other) && | |
| 377 command == other.command; | |
| 378 } | |
| 379 | |
| 380 /* [ScriptCommand]s are executed by dart code. */ | |
| 381 abstract class ScriptCommand extends Command { | |
| 382 ScriptCommand._(String displayName) : super._(displayName); | |
| 383 | |
| 384 Future<ScriptCommandOutputImpl> run(); | |
| 385 } | |
| 386 | |
| 387 class CleanDirectoryCopyCommand extends ScriptCommand { | |
| 388 final String _sourceDirectory; | |
| 389 final String _destinationDirectory; | |
| 390 | |
| 391 CleanDirectoryCopyCommand._(this._sourceDirectory, this._destinationDirectory) | |
| 392 : super._('dir_copy'); | |
| 393 | |
| 394 String get reproductionCommand => | |
| 395 "Copying '$_sourceDirectory' to '$_destinationDirectory'."; | |
| 396 | |
| 397 Future<ScriptCommandOutputImpl> run() { | |
| 398 var watch = new Stopwatch()..start(); | |
| 399 | |
| 400 var destination = new io.Directory(_destinationDirectory); | |
| 401 | |
| 402 return destination.exists().then((bool exists) { | |
| 403 var cleanDirectoryFuture; | |
| 404 if (exists) { | |
| 405 cleanDirectoryFuture = TestUtils.deleteDirectory(_destinationDirectory); | |
| 406 } else { | |
| 407 cleanDirectoryFuture = new Future.value(null); | |
| 408 } | |
| 409 return cleanDirectoryFuture.then((_) { | |
| 410 return TestUtils.copyDirectory(_sourceDirectory, _destinationDirectory); | |
| 411 }); | |
| 412 }).then((_) { | |
| 413 return new ScriptCommandOutputImpl( | |
| 414 this, Expectation.PASS, "", watch.elapsed); | |
| 415 }).catchError((error) { | |
| 416 return new ScriptCommandOutputImpl( | |
| 417 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed); | |
| 418 }); | |
| 419 } | |
| 420 | |
| 421 void _buildHashCode(HashCodeBuilder builder) { | |
| 422 super._buildHashCode(builder); | |
| 423 builder.addJson(_sourceDirectory); | |
| 424 builder.addJson(_destinationDirectory); | |
| 425 } | |
| 426 | |
| 427 bool _equal(CleanDirectoryCopyCommand other) => | |
| 428 super._equal(other) && | |
| 429 _sourceDirectory == other._sourceDirectory && | |
| 430 _destinationDirectory == other._destinationDirectory; | |
| 431 } | |
| 432 | |
| 433 class ModifyPubspecYamlCommand extends ScriptCommand { | |
| 434 String _pubspecYamlFile; | |
| 435 String _destinationFile; | |
| 436 Map<String, Map> _dependencyOverrides; | |
| 437 | |
| 438 ModifyPubspecYamlCommand._(this._pubspecYamlFile, | |
| 439 this._destinationFile, | |
| 440 this._dependencyOverrides) | |
| 441 : super._("modify_pubspec") { | |
| 442 assert(_pubspecYamlFile.endsWith("pubspec.yaml")); | |
| 443 assert(_destinationFile.endsWith("pubspec.yaml")); | |
| 444 } | |
| 445 | |
| 446 String get reproductionCommand => | |
| 447 "Adding necessary dependency overrides to '$_pubspecYamlFile' " | |
| 448 "(destination = $_destinationFile)."; | |
| 449 | |
| 450 Future<ScriptCommandOutputImpl> run() { | |
| 451 var watch = new Stopwatch()..start(); | |
| 452 | |
| 453 var pubspecLockFile = | |
| 454 _destinationFile.substring(0, _destinationFile.length - ".yaml".length) | |
| 455 + ".lock"; | |
| 456 | |
| 457 var file = new io.File(_pubspecYamlFile); | |
| 458 var destinationFile = new io.File(_destinationFile); | |
| 459 var lockfile = new io.File(pubspecLockFile); | |
| 460 return file.readAsString().then((String yamlString) { | |
| 461 var dependencyOverrideSection = new StringBuffer(); | |
| 462 if (_dependencyOverrides.isNotEmpty) { | |
| 463 dependencyOverrideSection.write( | |
| 464 "\n" | |
| 465 "# This section was autogenerated by test.py!\n" | |
| 466 "dependency_overrides:\n"); | |
| 467 _dependencyOverrides.forEach((String packageName, Map override) { | |
| 468 dependencyOverrideSection.write(" $packageName:\n"); | |
| 469 override.forEach((overrideKey, overrideValue) { | |
| 470 dependencyOverrideSection.write( | |
| 471 " $overrideKey: $overrideValue\n"); | |
| 472 }); | |
| 473 }); | |
| 474 } | |
| 475 var modifiedYamlString = "$yamlString\n$dependencyOverrideSection"; | |
| 476 return destinationFile.writeAsString(modifiedYamlString).then((_) { | |
| 477 lockfile.exists().then((bool lockfileExists) { | |
| 478 if (lockfileExists) { | |
| 479 return lockfile.delete(); | |
| 480 } | |
| 481 }); | |
| 482 }); | |
| 483 }).then((_) { | |
| 484 return new ScriptCommandOutputImpl( | |
| 485 this, Expectation.PASS, "", watch.elapsed); | |
| 486 }).catchError((error) { | |
| 487 return new ScriptCommandOutputImpl( | |
| 488 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed); | |
| 489 }); | |
| 490 } | |
| 491 | |
| 492 void _buildHashCode(HashCodeBuilder builder) { | |
| 493 super._buildHashCode(builder); | |
| 494 builder.addJson(_pubspecYamlFile); | |
| 495 builder.addJson(_destinationFile); | |
| 496 builder.addJson(_dependencyOverrides); | |
| 497 } | |
| 498 | |
| 499 bool _equal(ModifyPubspecYamlCommand other) => | |
| 500 super._equal(other) && | |
| 501 _pubspecYamlFile == other._pubspecYamlFile && | |
| 502 _destinationFile == other._destinationFile && | |
| 503 deepJsonCompare(_dependencyOverrides, other._dependencyOverrides); | |
| 504 } | |
| 505 | |
| 506 /* | |
| 507 * [MakeSymlinkCommand] makes a symbolic link to another directory. | |
| 508 */ | |
| 509 class MakeSymlinkCommand extends ScriptCommand { | |
| 510 String _link; | |
| 511 String _target; | |
| 512 | |
| 513 MakeSymlinkCommand._(this._link, this._target) : super._('make_symlink'); | |
| 514 | |
| 515 String get reproductionCommand => | |
| 516 "Make symbolic link '$_link' (target: $_target)'."; | |
| 517 | |
| 518 Future<ScriptCommandOutputImpl> run() { | |
| 519 var watch = new Stopwatch()..start(); | |
| 520 var targetFile = new io.Directory(_target); | |
| 521 return targetFile.exists().then((bool targetExists) { | |
| 522 if (!targetExists) { | |
| 523 throw new Exception("Target '$_target' does not exist"); | |
| 524 } | |
| 525 var link = new io.Link(_link); | |
| 526 | |
| 527 return link.exists() | |
| 528 .then((bool exists) { if (exists) return link.delete(); }) | |
| 529 .then((_) => link.create(_target)); | |
| 530 }).then((_) { | |
| 531 return new ScriptCommandOutputImpl( | |
| 532 this, Expectation.PASS, "", watch.elapsed); | |
| 533 }).catchError((error) { | |
| 534 return new ScriptCommandOutputImpl( | |
| 535 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed); | |
| 536 }); | |
| 537 } | |
| 538 | |
| 539 void _buildHashCode(HashCodeBuilder builder) { | |
| 540 super._buildHashCode(builder); | |
| 541 builder.addJson(_link); | |
| 542 builder.addJson(_target); | |
| 543 } | |
| 544 | |
| 545 bool _equal(MakeSymlinkCommand other) => | |
| 546 super._equal(other) && | |
| 547 _link == other._link && | |
| 548 _target == other._target; | |
| 549 } | |
| 550 | |
| 551 class CommandBuilder { | |
| 552 static final CommandBuilder instance = new CommandBuilder._(); | |
| 553 | |
| 554 bool _cleared = false; | |
| 555 final _cachedCommands = new Map<Command, Command>(); | |
| 556 | |
| 557 CommandBuilder._(); | |
| 558 | |
| 559 void clearCommandCache() { | |
| 560 _cachedCommands.clear(); | |
| 561 _cleared = true; | |
| 562 } | |
| 563 | |
| 564 ContentShellCommand getContentShellCommand(String executable, | |
| 565 String htmlFile, | |
| 566 List<String> options, | |
| 567 List<String> dartFlags, | |
| 568 Map<String, String> environment) { | |
| 569 ContentShellCommand command = new ContentShellCommand._( | |
| 570 executable, htmlFile, options, dartFlags, environment); | |
| 571 return _getUniqueCommand(command); | |
| 572 } | |
| 573 | |
| 574 BrowserTestCommand getBrowserTestCommand(String browser, | |
| 575 String url, | |
| 576 Map configuration, | |
| 577 bool retry) { | |
| 578 var command = new BrowserTestCommand._(browser, url, configuration, retry); | |
| 579 return _getUniqueCommand(command); | |
| 580 } | |
| 581 | |
| 582 BrowserHtmlTestCommand getBrowserHtmlTestCommand(String browser, | |
| 583 String url, | |
| 584 Map configuration, | |
| 585 List<String> expectedMessages, | |
| 586 bool retry) { | |
| 587 var command = new BrowserHtmlTestCommand._( | |
| 588 browser, url, configuration, expectedMessages, retry); | |
| 589 return _getUniqueCommand(command); | |
| 590 } | |
| 591 | |
| 592 CompilationCommand getCompilationCommand(String displayName, | |
| 593 outputFile, | |
| 594 neverSkipCompilation, | |
| 595 List<Uri> bootstrapDependencies, | |
| 596 String executable, | |
| 597 List<String> arguments, | |
| 598 Map<String, String> environment) { | |
| 599 var command = | |
| 600 new CompilationCommand._( | |
| 601 displayName, outputFile, neverSkipCompilation, | |
| 602 bootstrapDependencies, executable, arguments, environment); | |
| 603 return _getUniqueCommand(command); | |
| 604 } | |
| 605 | |
| 606 AnalysisCommand getAnalysisCommand( | |
| 607 String displayName, executable, arguments, environmentOverrides, | |
| 608 {String flavor: 'dartanalyzer'}) { | |
| 609 var command = new AnalysisCommand._( | |
| 610 flavor, displayName, executable, arguments, environmentOverrides); | |
| 611 return _getUniqueCommand(command); | |
| 612 } | |
| 613 | |
| 614 VmCommand getVmCommand(String executable, | |
| 615 List<String> arguments, | |
| 616 Map<String, String> environmentOverrides) { | |
| 617 var command = new VmCommand._(executable, arguments, environmentOverrides); | |
| 618 return _getUniqueCommand(command); | |
| 619 } | |
| 620 | |
| 621 Command getJSCommandlineCommand(String displayName, executable, arguments, | |
| 622 [environment = null]) { | |
| 623 var command = new JSCommandlineCommand._(displayName, executable, arguments, | |
| 624 environment); | |
| 625 return _getUniqueCommand(command); | |
| 626 } | |
| 627 | |
| 628 Command getProcessCommand(String displayName, executable, arguments, | |
| 629 [environment = null, workingDirectory = null]) { | |
| 630 var command = new ProcessCommand._(displayName, executable, arguments, | |
| 631 environment, workingDirectory); | |
| 632 return _getUniqueCommand(command); | |
| 633 } | |
| 634 | |
| 635 Command getCopyCommand(String sourceDirectory, String destinationDirectory) { | |
| 636 var command = new CleanDirectoryCopyCommand._(sourceDirectory, | |
| 637 destinationDirectory); | |
| 638 return _getUniqueCommand(command); | |
| 639 } | |
| 640 | |
| 641 Command getPubCommand(String pubCommand, | |
| 642 String pubExecutable, | |
| 643 String pubspecYamlDirectory, | |
| 644 String pubCacheDirectory) { | |
| 645 var command = new PubCommand._(pubCommand, | |
| 646 pubExecutable, | |
| 647 pubspecYamlDirectory, | |
| 648 pubCacheDirectory); | |
| 649 return _getUniqueCommand(command); | |
| 650 } | |
| 651 | |
| 652 Command getMakeSymlinkCommand(String link, String target) { | |
| 653 return _getUniqueCommand(new MakeSymlinkCommand._(link, target)); | |
| 654 } | |
| 655 | |
| 656 Command getModifyPubspecCommand(String pubspecYamlFile, Map depsOverrides, | |
| 657 {String destinationFile: null}) { | |
| 658 if (destinationFile == null) destinationFile = pubspecYamlFile; | |
| 659 return _getUniqueCommand(new ModifyPubspecYamlCommand._( | |
| 660 pubspecYamlFile, destinationFile, depsOverrides)); | |
| 661 } | |
| 662 | |
| 663 Command _getUniqueCommand(Command command) { | |
| 664 // All Command classes implement hashCode and operator==. | |
| 665 // We check if this command has already been built. | |
| 666 // If so, we return the cached one. Otherwise we | |
| 667 // store the one given as [command] argument. | |
| 668 if (_cleared) { | |
| 669 throw new Exception( | |
| 670 "CommandBuilder.get[type]Command called after cache cleared"); | |
| 671 } | |
| 672 var cachedCommand = _cachedCommands[command]; | |
| 673 if (cachedCommand != null) { | |
| 674 return cachedCommand; | |
| 675 } | |
| 676 _cachedCommands[command] = command; | |
| 677 return command; | |
| 678 } | |
| 679 } | |
| 680 | |
| 681 /** | |
| 682 * TestCase contains all the information needed to run a test and evaluate | |
| 683 * its output. Running a test involves starting a separate process, with | |
| 684 * the executable and arguments given by the TestCase, and recording its | |
| 685 * stdout and stderr output streams, and its exit code. TestCase only | |
| 686 * contains static information about the test; actually running the test is | |
| 687 * performed by [ProcessQueue] using a [RunningProcess] object. | |
| 688 * | |
| 689 * The output information is stored in a [CommandOutput] instance contained | |
| 690 * in TestCase.commandOutputs. The last CommandOutput instance is responsible | |
| 691 * for evaluating if the test has passed, failed, crashed, or timed out, and the | |
| 692 * TestCase has information about what the expected result of the test should | |
| 693 * be. | |
| 694 * | |
| 695 * The TestCase has a callback function, [completedHandler], that is run when | |
| 696 * the test is completed. | |
| 697 */ | |
| 698 class TestCase extends UniqueObject { | |
| 699 // Flags set in _expectations from the optional argument info. | |
| 700 static final int IS_NEGATIVE = 1 << 0; | |
| 701 static final int HAS_RUNTIME_ERROR = 1 << 1; | |
| 702 static final int HAS_STATIC_WARNING = 1 << 2; | |
| 703 static final int IS_NEGATIVE_IF_CHECKED = 1 << 3; | |
| 704 static final int HAS_COMPILE_ERROR = 1 << 4; | |
| 705 static final int HAS_COMPILE_ERROR_IF_CHECKED = 1 << 5; | |
| 706 static final int EXPECT_COMPILE_ERROR = 1 << 6; | |
| 707 /** | |
| 708 * A list of commands to execute. Most test cases have a single command. | |
| 709 * Dart2js tests have two commands, one to compile the source and another | |
| 710 * to execute it. Some isolate tests might even have three, if they require | |
| 711 * compiling multiple sources that are run in isolation. | |
| 712 */ | |
| 713 List<Command> commands; | |
| 714 Map<Command, CommandOutput> commandOutputs = new Map<Command,CommandOutput>(); | |
| 715 | |
| 716 Map configuration; | |
| 717 String displayName; | |
| 718 int _expectations = 0; | |
| 719 int hash = 0; | |
| 720 Set<Expectation> expectedOutcomes; | |
| 721 | |
| 722 TestCase(this.displayName, | |
| 723 this.commands, | |
| 724 this.configuration, | |
| 725 this.expectedOutcomes, | |
| 726 {isNegative: false, | |
| 727 TestInformation info: null}) { | |
| 728 if (isNegative || displayName.contains("negative_test")) { | |
| 729 _expectations |= IS_NEGATIVE; | |
| 730 } | |
| 731 if (info != null) { | |
| 732 _setExpectations(info); | |
| 733 hash = info.originTestPath.relativeTo(TestUtils.dartDir) | |
| 734 .toString().hashCode; | |
| 735 } | |
| 736 } | |
| 737 | |
| 738 void _setExpectations(TestInformation info) { | |
| 739 // We don't want to keep the entire (large) TestInformation structure, | |
| 740 // so we copy the needed bools into flags set in a single integer. | |
| 741 if (info.hasRuntimeError) _expectations |= HAS_RUNTIME_ERROR; | |
| 742 if (info.hasStaticWarning) _expectations |= HAS_STATIC_WARNING; | |
| 743 if (info.isNegativeIfChecked) _expectations |= IS_NEGATIVE_IF_CHECKED; | |
| 744 if (info.hasCompileError) _expectations |= HAS_COMPILE_ERROR; | |
| 745 if (info.hasCompileErrorIfChecked) { | |
| 746 _expectations |= HAS_COMPILE_ERROR_IF_CHECKED; | |
| 747 } | |
| 748 if (info.hasCompileError || | |
| 749 (configuration['checked'] && info.hasCompileErrorIfChecked)) { | |
| 750 _expectations |= EXPECT_COMPILE_ERROR; | |
| 751 } | |
| 752 } | |
| 753 | |
| 754 bool get isNegative => _expectations & IS_NEGATIVE != 0; | |
| 755 bool get hasRuntimeError => _expectations & HAS_RUNTIME_ERROR != 0; | |
| 756 bool get hasStaticWarning => _expectations & HAS_STATIC_WARNING != 0; | |
| 757 bool get isNegativeIfChecked => _expectations & IS_NEGATIVE_IF_CHECKED != 0; | |
| 758 bool get hasCompileError => _expectations & HAS_COMPILE_ERROR != 0; | |
| 759 bool get hasCompileErrorIfChecked => | |
| 760 _expectations & HAS_COMPILE_ERROR_IF_CHECKED != 0; | |
| 761 bool get expectCompileError => _expectations & EXPECT_COMPILE_ERROR != 0; | |
| 762 | |
| 763 bool get unexpectedOutput { | |
| 764 var outcome = lastCommandOutput.result(this); | |
| 765 return !expectedOutcomes.any((expectation) { | |
| 766 return outcome.canBeOutcomeOf(expectation); | |
| 767 }); | |
| 768 } | |
| 769 | |
| 770 Expectation get result => lastCommandOutput.result(this); | |
| 771 | |
| 772 CommandOutput get lastCommandOutput { | |
| 773 if (commandOutputs.length == 0) { | |
| 774 throw new Exception("CommandOutputs is empty, maybe no command was run? (" | |
| 775 "displayName: '$displayName', " | |
| 776 "configurationString: '$configurationString')"); | |
| 777 } | |
| 778 return commandOutputs[commands[commandOutputs.length - 1]]; | |
| 779 } | |
| 780 | |
| 781 Command get lastCommandExecuted { | |
| 782 if (commandOutputs.length == 0) { | |
| 783 throw new Exception("CommandOutputs is empty, maybe no command was run? (" | |
| 784 "displayName: '$displayName', " | |
| 785 "configurationString: '$configurationString')"); | |
| 786 } | |
| 787 return commands[commandOutputs.length - 1]; | |
| 788 } | |
| 789 | |
| 790 int get timeout { | |
| 791 if (expectedOutcomes.contains(Expectation.SLOW)) { | |
| 792 return configuration['timeout'] * SLOW_TIMEOUT_MULTIPLIER; | |
| 793 } else { | |
| 794 return configuration['timeout']; | |
| 795 } | |
| 796 } | |
| 797 | |
| 798 String get configurationString { | |
| 799 final compiler = configuration['compiler']; | |
| 800 final runtime = configuration['runtime']; | |
| 801 final mode = configuration['mode']; | |
| 802 final arch = configuration['arch']; | |
| 803 final checked = configuration['checked'] ? '-checked' : ''; | |
| 804 return "$compiler-$runtime$checked ${mode}_$arch"; | |
| 805 } | |
| 806 | |
| 807 List<String> get batchTestArguments { | |
| 808 assert(commands.last is ProcessCommand); | |
| 809 return (commands.last as ProcessCommand).arguments; | |
| 810 } | |
| 811 | |
| 812 bool get isFlaky { | |
| 813 if (expectedOutcomes.contains(Expectation.SKIP) || | |
| 814 expectedOutcomes.contains(Expectation.SKIP_BY_DESIGN)) { | |
| 815 return false; | |
| 816 } | |
| 817 | |
| 818 return expectedOutcomes | |
| 819 .where((expectation) => !expectation.isMetaExpectation).length > 1; | |
| 820 } | |
| 821 | |
| 822 bool get isFinished { | |
| 823 return commandOutputs.length > 0 && | |
| 824 (!lastCommandOutput.successful || | |
| 825 commands.length == commandOutputs.length); | |
| 826 } | |
| 827 } | |
| 828 | |
| 829 | |
| 830 /** | |
| 831 * BrowserTestCase has an extra compilation command that is run in a separate | |
| 832 * process, before the regular test is run as in the base class [TestCase]. | |
| 833 * If the compilation command fails, then the rest of the test is not run. | |
| 834 */ | |
| 835 class BrowserTestCase extends TestCase { | |
| 836 | |
| 837 BrowserTestCase(displayName, commands, configuration, | |
| 838 expectedOutcomes, info, isNegative, this._testingUrl) | |
| 839 : super(displayName, commands, configuration, | |
| 840 expectedOutcomes, isNegative: isNegative, info: info); | |
| 841 | |
| 842 String _testingUrl; | |
| 843 | |
| 844 String get testingUrl => _testingUrl; | |
| 845 } | |
| 846 | |
| 847 class UnittestSuiteMessagesMixin { | |
| 848 bool _isAsyncTest(String testOutput) { | |
| 849 return testOutput.contains("unittest-suite-wait-for-done"); | |
| 850 } | |
| 851 | |
| 852 bool _isAsyncTestSuccessful(String testOutput) { | |
| 853 return testOutput.contains("unittest-suite-success"); | |
| 854 } | |
| 855 | |
| 856 Expectation _negateOutcomeIfIncompleteAsyncTest(Expectation outcome, | |
| 857 String testOutput) { | |
| 858 // If this is an asynchronous test and the asynchronous operation didn't | |
| 859 // complete successfully, it's outcome is Expectation.FAIL. | |
| 860 // TODO: maybe we should introduce a AsyncIncomplete marker or so | |
| 861 if (outcome == Expectation.PASS) { | |
| 862 if (_isAsyncTest(testOutput) && | |
| 863 !_isAsyncTestSuccessful(testOutput)) { | |
| 864 return Expectation.FAIL; | |
| 865 } | |
| 866 } | |
| 867 return outcome; | |
| 868 } | |
| 869 } | |
| 870 | |
| 871 /** | |
| 872 * CommandOutput records the output of a completed command: the process's exit | |
| 873 * code, the standard output and standard error, whether the process timed out, | |
| 874 * and the time the process took to run. It also contains a pointer to the | |
| 875 * [TestCase] this is the output of. | |
| 876 */ | |
| 877 abstract class CommandOutput { | |
| 878 Command get command; | |
| 879 | |
| 880 Expectation result(TestCase testCase); | |
| 881 | |
| 882 bool get hasCrashed; | |
| 883 | |
| 884 bool get hasTimedOut; | |
| 885 | |
| 886 bool didFail(testcase); | |
| 887 | |
| 888 bool hasFailed(TestCase testCase); | |
| 889 | |
| 890 bool get canRunDependendCommands; | |
| 891 | |
| 892 bool get successful; // otherwise we might to retry running | |
| 893 | |
| 894 Duration get time; | |
| 895 | |
| 896 int get exitCode; | |
| 897 | |
| 898 int get pid; | |
| 899 | |
| 900 List<int> get stdout; | |
| 901 | |
| 902 List<int> get stderr; | |
| 903 | |
| 904 List<String> get diagnostics; | |
| 905 | |
| 906 bool get compilationSkipped; | |
| 907 } | |
| 908 | |
| 909 class CommandOutputImpl extends UniqueObject implements CommandOutput { | |
| 910 Command command; | |
| 911 int exitCode; | |
| 912 | |
| 913 bool timedOut; | |
| 914 List<int> stdout; | |
| 915 List<int> stderr; | |
| 916 Duration time; | |
| 917 List<String> diagnostics; | |
| 918 bool compilationSkipped; | |
| 919 int pid; | |
| 920 | |
| 921 /** | |
| 922 * A flag to indicate we have already printed a warning about ignoring the VM | |
| 923 * crash, to limit the amount of output produced per test. | |
| 924 */ | |
| 925 bool alreadyPrintedWarning = false; | |
| 926 | |
| 927 // TODO(kustermann): Remove testCase from this class. | |
| 928 CommandOutputImpl(Command this.command, | |
| 929 int this.exitCode, | |
| 930 bool this.timedOut, | |
| 931 List<int> this.stdout, | |
| 932 List<int> this.stderr, | |
| 933 Duration this.time, | |
| 934 bool this.compilationSkipped, | |
| 935 int this.pid) { | |
| 936 diagnostics = []; | |
| 937 } | |
| 938 | |
| 939 Expectation result(TestCase testCase) { | |
| 940 if (hasCrashed) return Expectation.CRASH; | |
| 941 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 942 return hasFailed(testCase) ? Expectation.FAIL : Expectation.PASS; | |
| 943 } | |
| 944 | |
| 945 bool get hasCrashed { | |
| 946 // The Java dartc runner and dart2js exits with code 253 in case | |
| 947 // of unhandled exceptions. | |
| 948 if (exitCode == 253) return true; | |
| 949 if (io.Platform.operatingSystem == 'windows') { | |
| 950 // The VM uses std::abort to terminate on asserts. | |
| 951 // std::abort terminates with exit code 3 on Windows. | |
| 952 if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) { | |
| 953 return !timedOut; | |
| 954 } | |
| 955 // If a program receives an uncaught system exception, the program | |
| 956 // terminates with the exception code as exit code. | |
| 957 // The 0x3FFFFF00 mask here tries to determine if an exception indicates | |
| 958 // a crash of the program. | |
| 959 // System exception codes can be found in 'winnt.h', for example | |
| 960 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)" | |
| 961 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); | |
| 962 } | |
| 963 return !timedOut && ((exitCode < 0)); | |
| 964 } | |
| 965 | |
| 966 bool get hasTimedOut => timedOut; | |
| 967 | |
| 968 bool didFail(TestCase testCase) { | |
| 969 return (exitCode != 0 && !hasCrashed); | |
| 970 } | |
| 971 | |
| 972 bool get canRunDependendCommands { | |
| 973 // FIXME(kustermann): We may need to change this | |
| 974 return !hasTimedOut && exitCode == 0; | |
| 975 } | |
| 976 | |
| 977 bool get successful { | |
| 978 // FIXME(kustermann): We may need to change this | |
| 979 return !hasTimedOut && exitCode == 0; | |
| 980 } | |
| 981 | |
| 982 // Reverse result of a negative test. | |
| 983 bool hasFailed(TestCase testCase) { | |
| 984 return testCase.isNegative ? !didFail(testCase) : didFail(testCase); | |
| 985 } | |
| 986 | |
| 987 Expectation _negateOutcomeIfNegativeTest(Expectation outcome, | |
| 988 bool isNegative) { | |
| 989 if (!isNegative) return outcome; | |
| 990 | |
| 991 if (outcome.canBeOutcomeOf(Expectation.FAIL)) { | |
| 992 return Expectation.PASS; | |
| 993 } | |
| 994 return Expectation.FAIL; | |
| 995 } | |
| 996 } | |
| 997 | |
| 998 class BrowserCommandOutputImpl extends CommandOutputImpl { | |
| 999 // Although tests are reported as passing, content shell sometimes exits with | |
| 1000 // a nonzero exitcode which makes our dartium builders extremely falky. | |
| 1001 // See: http://dartbug.com/15139. | |
| 1002 static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022; | |
| 1003 static bool isWindows = io.Platform.operatingSystem == 'windows'; | |
| 1004 | |
| 1005 bool _failedBecauseOfMissingXDisplay; | |
| 1006 | |
| 1007 BrowserCommandOutputImpl( | |
| 1008 command, | |
| 1009 exitCode, | |
| 1010 timedOut, | |
| 1011 stdout, | |
| 1012 stderr, | |
| 1013 time, | |
| 1014 compilationSkipped) : | |
| 1015 super(command, | |
| 1016 exitCode, | |
| 1017 timedOut, | |
| 1018 stdout, | |
| 1019 stderr, | |
| 1020 time, | |
| 1021 compilationSkipped, | |
| 1022 0) { | |
| 1023 _failedBecauseOfMissingXDisplay = _didFailBecauseOfMissingXDisplay(); | |
| 1024 if (_failedBecauseOfMissingXDisplay) { | |
| 1025 DebugLogger.warning("Warning: Test failure because of missing XDisplay"); | |
| 1026 // If we get the X server error, or DRT crashes with a core dump, retry | |
| 1027 // the test. | |
| 1028 } | |
| 1029 } | |
| 1030 | |
| 1031 Expectation result(TestCase testCase) { | |
| 1032 // Handle crashes and timeouts first | |
| 1033 if (hasCrashed) return Expectation.CRASH; | |
| 1034 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1035 | |
| 1036 var outcome = _getOutcome(); | |
| 1037 | |
| 1038 if (testCase.hasRuntimeError) { | |
| 1039 if (!outcome.canBeOutcomeOf(Expectation.RUNTIME_ERROR)) { | |
| 1040 return Expectation.MISSING_RUNTIME_ERROR; | |
| 1041 } | |
| 1042 } | |
| 1043 if (testCase.isNegative) { | |
| 1044 if (outcome.canBeOutcomeOf(Expectation.FAIL)) return Expectation.PASS; | |
| 1045 return Expectation.FAIL; | |
| 1046 } | |
| 1047 return outcome; | |
| 1048 } | |
| 1049 | |
| 1050 bool get successful => canRunDependendCommands; | |
| 1051 | |
| 1052 bool get canRunDependendCommands { | |
| 1053 // We cannot rely on the exit code of content_shell as a method to determine | |
| 1054 // if we were successful or not. | |
| 1055 return super.canRunDependendCommands && !didFail(null); | |
| 1056 } | |
| 1057 | |
| 1058 bool get hasCrashed { | |
| 1059 return super.hasCrashed || _rendererCrashed; | |
| 1060 } | |
| 1061 | |
| 1062 Expectation _getOutcome() { | |
| 1063 if (_failedBecauseOfMissingXDisplay) { | |
| 1064 return Expectation.FAIL; | |
| 1065 } | |
| 1066 | |
| 1067 if (_browserTestFailure) { | |
| 1068 return Expectation.RUNTIME_ERROR; | |
| 1069 } | |
| 1070 return Expectation.PASS; | |
| 1071 } | |
| 1072 | |
| 1073 bool _didFailBecauseOfMissingXDisplay() { | |
| 1074 // Browser case: | |
| 1075 // If the browser test failed, it may have been because content shell | |
| 1076 // and the virtual framebuffer X server didn't hook up, or it crashed with | |
| 1077 // a core dump. Sometimes content shell crashes after it has set the stdout | |
| 1078 // to PASS, so we have to do this check first. | |
| 1079 var stderrLines = decodeUtf8(super.stderr).split("\n"); | |
| 1080 for (String line in stderrLines) { | |
| 1081 // TODO(kustermann,ricow): Issue: 7564 | |
| 1082 // This seems to happen quite frequently, we need to figure out why. | |
| 1083 if (line.contains(MESSAGE_CANNOT_OPEN_DISPLAY) || | |
| 1084 line.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) { | |
| 1085 return true; | |
| 1086 } | |
| 1087 } | |
| 1088 return false; | |
| 1089 } | |
| 1090 | |
| 1091 bool get _rendererCrashed => | |
| 1092 decodeUtf8(super.stdout).contains("#CRASHED - rendere"); | |
| 1093 | |
| 1094 bool get _browserTestFailure { | |
| 1095 // Browser tests fail unless stdout contains | |
| 1096 // 'Content-Type: text/plain' followed by 'PASS'. | |
| 1097 bool hasContentType = false; | |
| 1098 var stdoutLines = decodeUtf8(super.stdout).split("\n"); | |
| 1099 var containsFail = false; | |
| 1100 var containsPass = false; | |
| 1101 for (String line in stdoutLines) { | |
| 1102 switch (line) { | |
| 1103 case 'Content-Type: text/plain': | |
| 1104 hasContentType = true; | |
| 1105 break; | |
| 1106 case 'FAIL': | |
| 1107 if (hasContentType) { | |
| 1108 containsFail = true; | |
| 1109 } | |
| 1110 break; | |
| 1111 case 'PASS': | |
| 1112 if (hasContentType) { | |
| 1113 containsPass = true; | |
| 1114 } | |
| 1115 break; | |
| 1116 } | |
| 1117 } | |
| 1118 if (hasContentType) { | |
| 1119 if (containsFail && containsPass) { | |
| 1120 DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)"); | |
| 1121 } | |
| 1122 if (!containsFail && !containsPass) { | |
| 1123 DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. " | |
| 1124 "($command)"); | |
| 1125 return true; | |
| 1126 } | |
| 1127 if (containsFail) { | |
| 1128 return true; | |
| 1129 } | |
| 1130 assert(containsPass); | |
| 1131 if (exitCode != 0) { | |
| 1132 var message = "All tests passed, but exitCode != 0. " | |
| 1133 "Actual exitcode: $exitCode. " | |
| 1134 "($command)"; | |
| 1135 DebugLogger.warning(message); | |
| 1136 diagnostics.add(message); | |
| 1137 } | |
| 1138 return (!hasCrashed && | |
| 1139 exitCode != 0 && | |
| 1140 (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE)); | |
| 1141 } | |
| 1142 DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. " | |
| 1143 "($command)."); | |
| 1144 return true; | |
| 1145 } | |
| 1146 } | |
| 1147 | |
| 1148 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl { | |
| 1149 HTMLBrowserCommandOutputImpl( | |
| 1150 command, | |
| 1151 exitCode, | |
| 1152 timedOut, | |
| 1153 stdout, | |
| 1154 stderr, | |
| 1155 time, | |
| 1156 compilationSkipped) : | |
| 1157 super(command, | |
| 1158 exitCode, | |
| 1159 timedOut, | |
| 1160 stdout, | |
| 1161 stderr, | |
| 1162 time, | |
| 1163 compilationSkipped); | |
| 1164 | |
| 1165 bool didFail(TestCase testCase) { | |
| 1166 return _getOutcome() != Expectation.PASS; | |
| 1167 } | |
| 1168 | |
| 1169 | |
| 1170 bool get _browserTestFailure { | |
| 1171 // We should not need to convert back and forward. | |
| 1172 var output = decodeUtf8(super.stdout); | |
| 1173 if (output.contains("FAIL")) return true; | |
| 1174 return !output.contains("PASS"); | |
| 1175 } | |
| 1176 } | |
| 1177 | |
| 1178 class BrowserTestJsonResult { | |
| 1179 static const ALLOWED_TYPES = | |
| 1180 const ['sync_exception', 'window_onerror', 'script_onerror', | |
| 1181 'window_compilationerror', 'print', 'message_received', 'dom', | |
| 1182 'debug']; | |
| 1183 | |
| 1184 final Expectation outcome; | |
| 1185 final String htmlDom; | |
| 1186 final List events; | |
| 1187 | |
| 1188 BrowserTestJsonResult(this.outcome, this.htmlDom, this.events); | |
| 1189 | |
| 1190 static BrowserTestJsonResult parseFromString(String content) { | |
| 1191 void validate(String assertion, bool value) { | |
| 1192 if (!value) { | |
| 1193 throw "InvalidFormat sent from browser driving page: $assertion:\n\n" | |
| 1194 "$content"; | |
| 1195 } | |
| 1196 } | |
| 1197 | |
| 1198 var events; | |
| 1199 try { | |
| 1200 events = JSON.decode(content); | |
| 1201 if (events != null) { | |
| 1202 validate("Message must be a List", events is List); | |
| 1203 | |
| 1204 Map<String, List<String>> messagesByType = {}; | |
| 1205 ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]); | |
| 1206 | |
| 1207 for (var entry in events) { | |
| 1208 validate("An entry must be a Map", entry is Map); | |
| 1209 | |
| 1210 var type = entry['type']; | |
| 1211 var value = entry['value']; | |
| 1212 var timestamp = entry['timestamp']; | |
| 1213 | |
| 1214 validate("'type' of an entry must be a String", | |
| 1215 type is String); | |
| 1216 validate("'type' has to be in $ALLOWED_TYPES.", | |
| 1217 ALLOWED_TYPES.contains(type)); | |
| 1218 validate("'timestamp' of an entry must be a number", | |
| 1219 timestamp is num); | |
| 1220 | |
| 1221 messagesByType[type].add(value); | |
| 1222 } | |
| 1223 validate("The message must have exactly one 'dom' entry.", | |
| 1224 messagesByType['dom'].length == 1); | |
| 1225 | |
| 1226 var dom = messagesByType['dom'][0]; | |
| 1227 if (dom.endsWith('\n')) { | |
| 1228 dom = '$dom\n'; | |
| 1229 } | |
| 1230 | |
| 1231 return new BrowserTestJsonResult( | |
| 1232 _getOutcome(messagesByType), dom, events); | |
| 1233 } | |
| 1234 } catch(error) { | |
| 1235 // If something goes wrong, we know the content was not in the correct | |
| 1236 // JSON format. So we can't parse it. | |
| 1237 // The caller is responsible for falling back to the old way of | |
| 1238 // determining if a test failed. | |
| 1239 } | |
| 1240 | |
| 1241 return null; | |
| 1242 } | |
| 1243 | |
| 1244 static Expectation _getOutcome(Map<String, List<String>> messagesByType) { | |
| 1245 occured(type) => messagesByType[type].length > 0; | |
| 1246 searchForMsg(types, message) { | |
| 1247 return types.any((type) => messagesByType[type].contains(message)); | |
| 1248 } | |
| 1249 | |
| 1250 // FIXME(kustermann,ricow): I think this functionality doesn't work in | |
| 1251 // test_controller.js: So far I haven't seen anything being reported on | |
| 1252 // "window.compilationerror" | |
| 1253 if (occured('window_compilationerror')) { | |
| 1254 return Expectation.COMPILETIME_ERROR; | |
| 1255 } | |
| 1256 | |
| 1257 if (occured('sync_exception') || | |
| 1258 occured('window_onerror') || | |
| 1259 occured('script_onerror')) { | |
| 1260 return Expectation.RUNTIME_ERROR; | |
| 1261 } | |
| 1262 | |
| 1263 if (messagesByType['dom'][0].contains('FAIL')) { | |
| 1264 return Expectation.RUNTIME_ERROR; | |
| 1265 } | |
| 1266 | |
| 1267 // We search for these messages in 'print' and 'message_received' because | |
| 1268 // the unittest implementation posts these messages using | |
| 1269 // "window.postMessage()" instead of the normal "print()" them. | |
| 1270 | |
| 1271 var isAsyncTest = searchForMsg(['print', 'message_received'], | |
| 1272 'unittest-suite-wait-for-done'); | |
| 1273 var isAsyncSuccess = | |
| 1274 searchForMsg(['print', 'message_received'], 'unittest-suite-success') || | |
| 1275 searchForMsg(['print', 'message_received'], 'unittest-suite-done'); | |
| 1276 | |
| 1277 if (isAsyncTest) { | |
| 1278 if (isAsyncSuccess) { | |
| 1279 return Expectation.PASS; | |
| 1280 } | |
| 1281 return Expectation.RUNTIME_ERROR; | |
| 1282 } | |
| 1283 | |
| 1284 var mainStarted = | |
| 1285 searchForMsg(['print', 'message_received'], 'dart-calling-main'); | |
| 1286 var mainDone = | |
| 1287 searchForMsg(['print', 'message_received'], 'dart-main-done'); | |
| 1288 | |
| 1289 if (mainStarted && mainDone) { | |
| 1290 return Expectation.PASS; | |
| 1291 } | |
| 1292 return Expectation.FAIL; | |
| 1293 } | |
| 1294 } | |
| 1295 | |
| 1296 class BrowserControllerTestOutcome extends CommandOutputImpl | |
| 1297 with UnittestSuiteMessagesMixin { | |
| 1298 BrowserTestOutput _result; | |
| 1299 Expectation _rawOutcome; | |
| 1300 | |
| 1301 factory BrowserControllerTestOutcome(Command command, | |
| 1302 BrowserTestOutput result) { | |
| 1303 void validate(String assertion, bool value) { | |
| 1304 if (!value) { | |
| 1305 throw "InvalidFormat sent from browser driving page: $assertion:\n\n" | |
| 1306 "${result.lastKnownMessage}"; | |
| 1307 } | |
| 1308 } | |
| 1309 | |
| 1310 String indent(String string, int numSpaces) { | |
| 1311 var spaces = new List.filled(numSpaces, ' ').join(''); | |
| 1312 return string.replaceAll('\r\n', '\n') | |
| 1313 .split('\n') | |
| 1314 .map((line) => "$spaces$line") | |
| 1315 .join('\n'); | |
| 1316 } | |
| 1317 | |
| 1318 String stdout = ""; | |
| 1319 String stderr = ""; | |
| 1320 Expectation outcome; | |
| 1321 | |
| 1322 var parsedResult = | |
| 1323 BrowserTestJsonResult.parseFromString(result.lastKnownMessage); | |
| 1324 if (parsedResult != null) { | |
| 1325 outcome = parsedResult.outcome; | |
| 1326 } else { | |
| 1327 // Old way of determining whether a test failed or passed. | |
| 1328 if (result.lastKnownMessage.contains("FAIL")) { | |
| 1329 outcome = Expectation.RUNTIME_ERROR; | |
| 1330 } else if (result.lastKnownMessage.contains("PASS")) { | |
| 1331 outcome = Expectation.PASS; | |
| 1332 } else { | |
| 1333 outcome = Expectation.RUNTIME_ERROR; | |
| 1334 } | |
| 1335 } | |
| 1336 | |
| 1337 if (result.didTimeout) { | |
| 1338 if (result.delayUntilTestStarted != null) { | |
| 1339 stderr = "This test timed out. The delay until the test actually " | |
| 1340 "started was: ${result.delayUntilTestStarted}."; | |
| 1341 } else { | |
| 1342 // TODO(ricow/kustermann) as soon as we record the state periodically, | |
| 1343 // we will have more information and can remove this warning. | |
| 1344 stderr = "This test has not notified test.py that it started running. " | |
| 1345 "This could be a bug in test.py! " | |
| 1346 "Please contact ricow/kustermann"; | |
| 1347 } | |
| 1348 } | |
| 1349 | |
| 1350 if (parsedResult != null) { | |
| 1351 stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n"; | |
| 1352 } else { | |
| 1353 stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n"; | |
| 1354 } | |
| 1355 | |
| 1356 stderr = | |
| 1357 '$stderr\n\n' | |
| 1358 'BrowserOutput while running the test (* EXPERIMENTAL *):\n' | |
| 1359 'BrowserOutput.stdout:\n' | |
| 1360 '${indent(result.browserOutput.stdout.toString(), 2)}\n' | |
| 1361 'BrowserOutput.stderr:\n' | |
| 1362 '${indent(result.browserOutput.stderr.toString(), 2)}\n' | |
| 1363 '\n'; | |
| 1364 return new BrowserControllerTestOutcome._internal( | |
| 1365 command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr)); | |
| 1366 } | |
| 1367 | |
| 1368 BrowserControllerTestOutcome._internal( | |
| 1369 Command command, BrowserTestOutput result, this._rawOutcome, | |
| 1370 List<int> stdout, List<int> stderr) | |
| 1371 : super(command, 0, result.didTimeout, stdout, stderr, result.duration, | |
| 1372 false, 0) { | |
| 1373 _result = result; | |
| 1374 } | |
| 1375 | |
| 1376 Expectation result(TestCase testCase) { | |
| 1377 // Handle timeouts first | |
| 1378 if (_result.didTimeout) return Expectation.TIMEOUT; | |
| 1379 | |
| 1380 // Multitests are handled specially | |
| 1381 if (testCase.hasRuntimeError) { | |
| 1382 if (_rawOutcome == Expectation.RUNTIME_ERROR) return Expectation.PASS; | |
| 1383 return Expectation.MISSING_RUNTIME_ERROR; | |
| 1384 } | |
| 1385 | |
| 1386 return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative); | |
| 1387 } | |
| 1388 } | |
| 1389 | |
| 1390 | |
| 1391 class AnalysisCommandOutputImpl extends CommandOutputImpl { | |
| 1392 // An error line has 8 fields that look like: | |
| 1393 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source. | |
| 1394 final int ERROR_LEVEL = 0; | |
| 1395 final int ERROR_TYPE = 1; | |
| 1396 final int FILENAME = 3; | |
| 1397 final int FORMATTED_ERROR = 7; | |
| 1398 | |
| 1399 AnalysisCommandOutputImpl(command, | |
| 1400 exitCode, | |
| 1401 timedOut, | |
| 1402 stdout, | |
| 1403 stderr, | |
| 1404 time, | |
| 1405 compilationSkipped) : | |
| 1406 super(command, | |
| 1407 exitCode, | |
| 1408 timedOut, | |
| 1409 stdout, | |
| 1410 stderr, | |
| 1411 time, | |
| 1412 compilationSkipped, | |
| 1413 0); | |
| 1414 | |
| 1415 Expectation result(TestCase testCase) { | |
| 1416 // TODO(kustermann): If we run the analyzer not in batch mode, make sure | |
| 1417 // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings, | |
| 1418 // no errors) | |
| 1419 | |
| 1420 // Handle crashes and timeouts first | |
| 1421 if (hasCrashed) return Expectation.CRASH; | |
| 1422 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1423 | |
| 1424 // Get the errors/warnings from the analyzer | |
| 1425 List<String> errors = []; | |
| 1426 List<String> warnings = []; | |
| 1427 parseAnalyzerOutput(errors, warnings); | |
| 1428 | |
| 1429 // Handle errors / missing errors | |
| 1430 if (testCase.expectCompileError) { | |
| 1431 if (errors.length > 0) { | |
| 1432 return Expectation.PASS; | |
| 1433 } | |
| 1434 return Expectation.MISSING_COMPILETIME_ERROR; | |
| 1435 } | |
| 1436 if (errors.length > 0) { | |
| 1437 return Expectation.COMPILETIME_ERROR; | |
| 1438 } | |
| 1439 | |
| 1440 // Handle static warnings / missing static warnings | |
| 1441 if (testCase.hasStaticWarning) { | |
| 1442 if (warnings.length > 0) { | |
| 1443 return Expectation.PASS; | |
| 1444 } | |
| 1445 return Expectation.MISSING_STATIC_WARNING; | |
| 1446 } | |
| 1447 if (warnings.length > 0) { | |
| 1448 return Expectation.STATIC_WARNING; | |
| 1449 } | |
| 1450 | |
| 1451 assert (errors.length == 0 && warnings.length == 0); | |
| 1452 assert (!testCase.hasCompileError && | |
| 1453 !testCase.hasStaticWarning); | |
| 1454 return Expectation.PASS; | |
| 1455 } | |
| 1456 | |
| 1457 void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) { | |
| 1458 // Parse a line delimited by the | character using \ as an escape charager | |
| 1459 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ | |
| 1460 List<String> splitMachineError(String line) { | |
| 1461 StringBuffer field = new StringBuffer(); | |
| 1462 List<String> result = []; | |
| 1463 bool escaped = false; | |
| 1464 for (var i = 0 ; i < line.length; i++) { | |
| 1465 var c = line[i]; | |
| 1466 if (!escaped && c == '\\') { | |
| 1467 escaped = true; | |
| 1468 continue; | |
| 1469 } | |
| 1470 escaped = false; | |
| 1471 if (c == '|') { | |
| 1472 result.add(field.toString()); | |
| 1473 field = new StringBuffer(); | |
| 1474 continue; | |
| 1475 } | |
| 1476 field.write(c); | |
| 1477 } | |
| 1478 result.add(field.toString()); | |
| 1479 return result; | |
| 1480 } | |
| 1481 | |
| 1482 for (String line in decodeUtf8(super.stderr).split("\n")) { | |
| 1483 if (line.length == 0) continue; | |
| 1484 List<String> fields = splitMachineError(line); | |
| 1485 // We only consider errors/warnings for files of interest. | |
| 1486 if (fields.length > FORMATTED_ERROR) { | |
| 1487 if (fields[ERROR_LEVEL] == 'ERROR') { | |
| 1488 outErrors.add(fields[FORMATTED_ERROR]); | |
| 1489 } else if (fields[ERROR_LEVEL] == 'WARNING') { | |
| 1490 outWarnings.add(fields[FORMATTED_ERROR]); | |
| 1491 } | |
| 1492 // OK to Skip error output that doesn't match the machine format | |
| 1493 } | |
| 1494 } | |
| 1495 } | |
| 1496 } | |
| 1497 | |
| 1498 class VmCommandOutputImpl extends CommandOutputImpl | |
| 1499 with UnittestSuiteMessagesMixin { | |
| 1500 static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254; | |
| 1501 static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255; | |
| 1502 | |
| 1503 VmCommandOutputImpl(Command command, int exitCode, bool timedOut, | |
| 1504 List<int> stdout, List<int> stderr, Duration time, | |
| 1505 int pid) | |
| 1506 : super(command, exitCode, timedOut, stdout, stderr, time, false, pid); | |
| 1507 | |
| 1508 Expectation result(TestCase testCase) { | |
| 1509 // Handle crashes and timeouts first | |
| 1510 if (hasCrashed) return Expectation.CRASH; | |
| 1511 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1512 | |
| 1513 // Multitests are handled specially | |
| 1514 if (testCase.expectCompileError) { | |
| 1515 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) { | |
| 1516 return Expectation.PASS; | |
| 1517 } | |
| 1518 return Expectation.MISSING_COMPILETIME_ERROR; | |
| 1519 } | |
| 1520 if (testCase.hasRuntimeError) { | |
| 1521 // TODO(kustermann): Do we consider a "runtimeError" only an uncaught | |
| 1522 // exception or does any nonzero exit code fullfil this requirement? | |
| 1523 if (exitCode != 0) { | |
| 1524 return Expectation.PASS; | |
| 1525 } | |
| 1526 return Expectation.MISSING_RUNTIME_ERROR; | |
| 1527 } | |
| 1528 | |
| 1529 // The actual outcome depends on the exitCode | |
| 1530 Expectation outcome; | |
| 1531 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) { | |
| 1532 outcome = Expectation.COMPILETIME_ERROR; | |
| 1533 } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) { | |
| 1534 outcome = Expectation.RUNTIME_ERROR; | |
| 1535 } else if (exitCode != 0) { | |
| 1536 // This is a general fail, in case we get an unknown nonzero exitcode. | |
| 1537 outcome = Expectation.FAIL; | |
| 1538 } else { | |
| 1539 outcome = Expectation.PASS; | |
| 1540 } | |
| 1541 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout)); | |
| 1542 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
| 1543 } | |
| 1544 } | |
| 1545 | |
| 1546 class CompilationCommandOutputImpl extends CommandOutputImpl { | |
| 1547 static const DART2JS_EXITCODE_CRASH = 253; | |
| 1548 | |
| 1549 CompilationCommandOutputImpl(Command command, int exitCode, bool timedOut, | |
| 1550 List<int> stdout, List<int> stderr, Duration time, | |
| 1551 bool compilationSkipped) | |
| 1552 : super(command, exitCode, timedOut, stdout, stderr, time, | |
| 1553 compilationSkipped, 0); | |
| 1554 | |
| 1555 Expectation result(TestCase testCase) { | |
| 1556 // Handle general crash/timeout detection. | |
| 1557 if (hasCrashed) return Expectation.CRASH; | |
| 1558 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1559 | |
| 1560 // Handle dart2js/dart2dart specific crash detection | |
| 1561 if (exitCode == DART2JS_EXITCODE_CRASH || | |
| 1562 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR || | |
| 1563 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) { | |
| 1564 return Expectation.CRASH; | |
| 1565 } | |
| 1566 | |
| 1567 // Multitests are handled specially | |
| 1568 if (testCase.expectCompileError) { | |
| 1569 // Nonzero exit code of the compiler means compilation failed | |
| 1570 // TODO(kustermann): Do we have a special exit code in that case??? | |
| 1571 if (exitCode != 0) { | |
| 1572 return Expectation.PASS; | |
| 1573 } | |
| 1574 return Expectation.MISSING_COMPILETIME_ERROR; | |
| 1575 } | |
| 1576 | |
| 1577 // TODO(kustermann): This is a hack, remove it | |
| 1578 if (testCase.hasRuntimeError && testCase.commands.length > 1) { | |
| 1579 // We expected to run the test, but we got an compile time error. | |
| 1580 // If the compilation succeeded, we wouldn't be in here! | |
| 1581 assert(exitCode != 0); | |
| 1582 return Expectation.COMPILETIME_ERROR; | |
| 1583 } | |
| 1584 | |
| 1585 Expectation outcome = | |
| 1586 exitCode == 0 ? Expectation.PASS : Expectation.COMPILETIME_ERROR; | |
| 1587 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
| 1588 } | |
| 1589 } | |
| 1590 | |
| 1591 class JsCommandlineOutputImpl extends CommandOutputImpl | |
| 1592 with UnittestSuiteMessagesMixin { | |
| 1593 JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut, | |
| 1594 List<int> stdout, List<int> stderr, Duration time) | |
| 1595 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0); | |
| 1596 | |
| 1597 Expectation result(TestCase testCase) { | |
| 1598 // Handle crashes and timeouts first | |
| 1599 if (hasCrashed) return Expectation.CRASH; | |
| 1600 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1601 | |
| 1602 if (testCase.hasRuntimeError) { | |
| 1603 if (exitCode != 0) return Expectation.PASS; | |
| 1604 return Expectation.MISSING_RUNTIME_ERROR; | |
| 1605 } | |
| 1606 | |
| 1607 var outcome = exitCode == 0 ? Expectation.PASS : Expectation.RUNTIME_ERROR; | |
| 1608 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout)); | |
| 1609 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
| 1610 } | |
| 1611 } | |
| 1612 | |
| 1613 class PubCommandOutputImpl extends CommandOutputImpl { | |
| 1614 PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut, | |
| 1615 List<int> stdout, List<int> stderr, Duration time) | |
| 1616 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0); | |
| 1617 | |
| 1618 Expectation result(TestCase testCase) { | |
| 1619 // Handle crashes and timeouts first | |
| 1620 if (hasCrashed) return Expectation.CRASH; | |
| 1621 if (hasTimedOut) return Expectation.TIMEOUT; | |
| 1622 | |
| 1623 if (exitCode == 0) { | |
| 1624 return Expectation.PASS; | |
| 1625 } else if ((command as PubCommand).command == 'get') { | |
| 1626 return Expectation.PUB_GET_ERROR; | |
| 1627 } else { | |
| 1628 return Expectation.FAIL; | |
| 1629 } | |
| 1630 } | |
| 1631 } | |
| 1632 | |
| 1633 class ScriptCommandOutputImpl extends CommandOutputImpl { | |
| 1634 final Expectation _result; | |
| 1635 | |
| 1636 ScriptCommandOutputImpl(ScriptCommand command, this._result, | |
| 1637 String scriptExecutionInformation, Duration time) | |
| 1638 : super(command, 0, false, [], [], time, false, 0) { | |
| 1639 var lines = scriptExecutionInformation.split("\n"); | |
| 1640 diagnostics.addAll(lines); | |
| 1641 } | |
| 1642 | |
| 1643 Expectation result(TestCase testCase) => _result; | |
| 1644 | |
| 1645 bool get canRunDependendCommands => _result == Expectation.PASS; | |
| 1646 | |
| 1647 bool get successful => _result == Expectation.PASS; | |
| 1648 | |
| 1649 } | |
| 1650 | |
| 1651 CommandOutput createCommandOutput(Command command, | |
| 1652 int exitCode, | |
| 1653 bool timedOut, | |
| 1654 List<int> stdout, | |
| 1655 List<int> stderr, | |
| 1656 Duration time, | |
| 1657 bool compilationSkipped, | |
| 1658 [int pid = 0]) { | |
| 1659 if (command is ContentShellCommand) { | |
| 1660 return new BrowserCommandOutputImpl( | |
| 1661 command, exitCode, timedOut, stdout, stderr, | |
| 1662 time, compilationSkipped); | |
| 1663 } else if (command is BrowserTestCommand) { | |
| 1664 return new HTMLBrowserCommandOutputImpl( | |
| 1665 command, exitCode, timedOut, stdout, stderr, | |
| 1666 time, compilationSkipped); | |
| 1667 } else if (command is AnalysisCommand) { | |
| 1668 return new AnalysisCommandOutputImpl( | |
| 1669 command, exitCode, timedOut, stdout, stderr, | |
| 1670 time, compilationSkipped); | |
| 1671 } else if (command is VmCommand) { | |
| 1672 return new VmCommandOutputImpl( | |
| 1673 command, exitCode, timedOut, stdout, stderr, time, pid); | |
| 1674 } else if (command is CompilationCommand) { | |
| 1675 return new CompilationCommandOutputImpl( | |
| 1676 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
| 1677 } else if (command is JSCommandlineCommand) { | |
| 1678 return new JsCommandlineOutputImpl( | |
| 1679 command, exitCode, timedOut, stdout, stderr, time); | |
| 1680 } else if (command is PubCommand) { | |
| 1681 return new PubCommandOutputImpl( | |
| 1682 command, exitCode, timedOut, stdout, stderr, time); | |
| 1683 } | |
| 1684 | |
| 1685 return new CommandOutputImpl( | |
| 1686 command, exitCode, timedOut, stdout, stderr, | |
| 1687 time, compilationSkipped, pid); | |
| 1688 } | |
| 1689 | |
| 1690 | |
| 1691 /** | 42 /** |
| 1692 * An OutputLog records the output from a test, but truncates it if | 43 * An OutputLog records the output from a test, but truncates it if |
| 1693 * it is longer than MAX_HEAD characters, and just keeps the head and | 44 * it is longer than MAX_HEAD characters, and just keeps the head and |
| 1694 * the last TAIL_LENGTH characters of the output. | 45 * the last TAIL_LENGTH characters of the output. |
| 1695 */ | 46 */ |
| 1696 class OutputLog { | 47 class OutputLog { |
| 1697 static const int MAX_HEAD = 100 * 1024; | 48 static const int MAX_HEAD = 100 * 1024; |
| 1698 static const int TAIL_LENGTH = 10 * 1024; | 49 static const int TAIL_LENGTH = 10 * 1024; |
| 1699 List<int> head = <int>[]; | 50 List<int> head = <int>[]; |
| 1700 List<int> tail; | 51 List<int> tail; |
| (...skipping 912 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2613 } | 964 } |
| 2614 | 965 |
| 2615 Future cleanup() => new Future.value(); | 966 Future cleanup() => new Future.value(); |
| 2616 | 967 |
| 2617 Future<CommandOutput> runCommand(node, ProcessCommand command, int timeout) { | 968 Future<CommandOutput> runCommand(node, ProcessCommand command, int timeout) { |
| 2618 assert(node.dependencies.length == 0); | 969 assert(node.dependencies.length == 0); |
| 2619 return new Future.value(_archive.outputOf(command)); | 970 return new Future.value(_archive.outputOf(command)); |
| 2620 } | 971 } |
| 2621 } | 972 } |
| 2622 | 973 |
| 2623 bool shouldRetryCommand(CommandOutput output) { | |
| 2624 var command = output.command; | |
| 2625 // We rerun tests on Safari because 6.2 and 7.1 are flaky. Issue 21434. | |
| 2626 if (command is BrowserTestCommand && | |
| 2627 command.retry && | |
| 2628 command.browser == 'safari' && | |
| 2629 output is BrowserControllerTestOutcome && | |
| 2630 output._rawOutcome != Expectation.PASS) { | |
| 2631 return true; | |
| 2632 } | |
| 2633 | |
| 2634 if (!output.successful) { | |
| 2635 List<String> stdout, stderr; | |
| 2636 | |
| 2637 decodeOutput() { | |
| 2638 if (stdout == null && stderr == null) { | |
| 2639 stdout = decodeUtf8(output.stderr).split("\n"); | |
| 2640 stderr = decodeUtf8(output.stderr).split("\n"); | |
| 2641 } | |
| 2642 } | |
| 2643 | |
| 2644 if (io.Platform.operatingSystem == 'linux') { | |
| 2645 decodeOutput(); | |
| 2646 // No matter which command we ran: If we get failures due to the | |
| 2647 // "xvfb-run" issue 7564, try re-running the test. | |
| 2648 bool containsFailureMsg(String line) { | |
| 2649 return line.contains(MESSAGE_CANNOT_OPEN_DISPLAY) || | |
| 2650 line.contains(MESSAGE_FAILED_TO_RUN_COMMAND); | |
| 2651 } | |
| 2652 if (stdout.any(containsFailureMsg) || stderr.any(containsFailureMsg)) { | |
| 2653 return true; | |
| 2654 } | |
| 2655 } | |
| 2656 | |
| 2657 // We currently rerun dartium tests, see issue 14074. | |
| 2658 if (command is BrowserTestCommand && | |
| 2659 command.retry && | |
| 2660 command.browser == 'dartium') { | |
| 2661 return true; | |
| 2662 } | |
| 2663 } | |
| 2664 return false; | |
| 2665 } | |
| 2666 | |
| 2667 /* | 974 /* |
| 2668 * [TestCaseCompleter] will listen for | 975 * [TestCaseCompleter] will listen for |
| 2669 * NodeState.Processing -> NodeState.{Successful,Failed} state changes and | 976 * NodeState.Processing -> NodeState.{Successful,Failed} state changes and |
| 2670 * will complete a TestCase if it is finished. | 977 * will complete a TestCase if it is finished. |
| 2671 * | 978 * |
| 2672 * It provides a stream [finishedTestCases], which will stream all TestCases | 979 * It provides a stream [finishedTestCases], which will stream all TestCases |
| 2673 * once they're finished. After all TestCases are done, the stream will be | 980 * once they're finished. After all TestCases are done, the stream will be |
| 2674 * closed. | 981 * closed. |
| 2675 */ | 982 */ |
| 2676 class TestCaseCompleter { | 983 class TestCaseCompleter { |
| (...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2953 } | 1260 } |
| 2954 } | 1261 } |
| 2955 | 1262 |
| 2956 void eventAllTestsDone() { | 1263 void eventAllTestsDone() { |
| 2957 for (var listener in _eventListener) { | 1264 for (var listener in _eventListener) { |
| 2958 listener.allDone(); | 1265 listener.allDone(); |
| 2959 } | 1266 } |
| 2960 _allDone(); | 1267 _allDone(); |
| 2961 } | 1268 } |
| 2962 } | 1269 } |
| OLD | NEW |