OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
| 5 import 'dart:async'; |
5 import 'dart:io'; | 6 import 'dart:io'; |
6 import 'dart:convert'; | 7 import 'dart:convert'; |
| 8 import 'dart:math' as math; |
7 | 9 |
| 10 import 'configuration.dart'; |
8 import 'path.dart'; | 11 import 'path.dart'; |
9 | 12 |
10 // This is the maximum time we expect stdout/stderr of subprocesses to deliver | 13 // This is the maximum time we expect stdout/stderr of subprocesses to deliver |
11 // data after we've got the exitCode. | 14 // data after we've got the exitCode. |
12 const Duration MAX_STDIO_DELAY = const Duration(seconds: 30); | 15 const Duration MAX_STDIO_DELAY = const Duration(seconds: 30); |
13 | 16 |
14 String MAX_STDIO_DELAY_PASSED_MESSAGE = | 17 String MAX_STDIO_DELAY_PASSED_MESSAGE = |
15 """Not waiting for stdout/stderr from subprocess anymore | 18 """Not waiting for stdout/stderr from subprocess anymore |
16 ($MAX_STDIO_DELAY passed). Please note that this could be an indicator | 19 ($MAX_STDIO_DELAY passed). Please note that this could be an indicator |
17 that there is a hanging process which we were unable to kill."""; | 20 that there is a hanging process which we were unable to kill."""; |
(...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
248 class UniqueObject { | 251 class UniqueObject { |
249 static int _nextId = 1; | 252 static int _nextId = 1; |
250 final int _hashCode; | 253 final int _hashCode; |
251 | 254 |
252 int get hashCode => _hashCode; | 255 int get hashCode => _hashCode; |
253 operator ==(Object other) => | 256 operator ==(Object other) => |
254 other is UniqueObject && _hashCode == other._hashCode; | 257 other is UniqueObject && _hashCode == other._hashCode; |
255 | 258 |
256 UniqueObject() : _hashCode = ++_nextId; | 259 UniqueObject() : _hashCode = ++_nextId; |
257 } | 260 } |
| 261 |
| 262 class LastModifiedCache { |
| 263 Map<String, DateTime> _cache = <String, DateTime>{}; |
| 264 |
| 265 /** |
| 266 * Returns the last modified date of the given [uri]. |
| 267 * |
| 268 * The return value will be cached for future queries. If [uri] is a local |
| 269 * file, it's last modified [Date] will be returned. If the file does not |
| 270 * exist, null will be returned instead. |
| 271 * In case [uri] is not a local file, this method will always return |
| 272 * the current date. |
| 273 */ |
| 274 DateTime getLastModified(Uri uri) { |
| 275 if (uri.scheme == "file") { |
| 276 if (_cache.containsKey(uri.path)) { |
| 277 return _cache[uri.path]; |
| 278 } |
| 279 var file = new File(new Path(uri.path).toNativePath()); |
| 280 _cache[uri.path] = file.existsSync() ? file.lastModifiedSync() : null; |
| 281 return _cache[uri.path]; |
| 282 } |
| 283 return new DateTime.now(); |
| 284 } |
| 285 } |
| 286 |
| 287 class ExistsCache { |
| 288 Map<String, bool> _cache = <String, bool>{}; |
| 289 |
| 290 /** |
| 291 * Returns true if the file in [path] exists, false otherwise. |
| 292 * |
| 293 * The information will be cached. |
| 294 */ |
| 295 bool doesFileExist(String path) { |
| 296 if (!_cache.containsKey(path)) { |
| 297 _cache[path] = new File(path).existsSync(); |
| 298 } |
| 299 return _cache[path]; |
| 300 } |
| 301 } |
| 302 |
| 303 class TestUtils { |
| 304 /** |
| 305 * Any script using TestUtils must set dartDirUri to a file:// URI |
| 306 * pointing to the root of the Dart checkout. |
| 307 */ |
| 308 static void setDartDirUri(Uri uri) { |
| 309 dartDirUri = uri; |
| 310 dartDir = new Path(uri.toFilePath()); |
| 311 } |
| 312 |
| 313 static math.Random rand = new math.Random.secure(); |
| 314 static Uri dartDirUri; |
| 315 static Path dartDir; |
| 316 static LastModifiedCache lastModifiedCache = new LastModifiedCache(); |
| 317 static ExistsCache existsCache = new ExistsCache(); |
| 318 static Path currentWorkingDirectory = new Path(Directory.current.path); |
| 319 |
| 320 /** |
| 321 * Generates a random number. |
| 322 */ |
| 323 static int getRandomNumber() { |
| 324 return rand.nextInt(0xffffffff); |
| 325 } |
| 326 |
| 327 /** |
| 328 * Creates a directory using a [relativePath] to an existing |
| 329 * [base] directory if that [relativePath] does not already exist. |
| 330 */ |
| 331 static Directory mkdirRecursive(Path base, Path relativePath) { |
| 332 if (relativePath.isAbsolute) { |
| 333 base = new Path('/'); |
| 334 } |
| 335 Directory dir = new Directory(base.toNativePath()); |
| 336 assert(dir.existsSync()); |
| 337 var segments = relativePath.segments(); |
| 338 for (String segment in segments) { |
| 339 base = base.append(segment); |
| 340 if (base.toString() == "/$segment" && |
| 341 segment.length == 2 && |
| 342 segment.endsWith(':')) { |
| 343 // Skip the directory creation for a path like "/E:". |
| 344 continue; |
| 345 } |
| 346 dir = new Directory(base.toNativePath()); |
| 347 if (!dir.existsSync()) { |
| 348 dir.createSync(); |
| 349 } |
| 350 assert(dir.existsSync()); |
| 351 } |
| 352 return dir; |
| 353 } |
| 354 |
| 355 /** |
| 356 * Copy a [source] file to a new place. |
| 357 * Assumes that the directory for [dest] already exists. |
| 358 */ |
| 359 static Future copyFile(Path source, Path dest) { |
| 360 return new File(source.toNativePath()) |
| 361 .openRead() |
| 362 .pipe(new File(dest.toNativePath()).openWrite()); |
| 363 } |
| 364 |
| 365 static Future copyDirectory(String source, String dest) { |
| 366 source = new Path(source).toNativePath(); |
| 367 dest = new Path(dest).toNativePath(); |
| 368 |
| 369 var executable = 'cp'; |
| 370 var args = ['-Rp', source, dest]; |
| 371 if (Platform.operatingSystem == 'windows') { |
| 372 executable = 'xcopy'; |
| 373 args = [source, dest, '/e', '/i']; |
| 374 } |
| 375 return Process.run(executable, args).then((ProcessResult result) { |
| 376 if (result.exitCode != 0) { |
| 377 throw new Exception("Failed to execute '$executable " |
| 378 "${args.join(' ')}'."); |
| 379 } |
| 380 }); |
| 381 } |
| 382 |
| 383 static Future deleteDirectory(String path) { |
| 384 // We are seeing issues with long path names on windows when |
| 385 // deleting them. Use the system tools to delete our long paths. |
| 386 // See issue 16264. |
| 387 if (Platform.operatingSystem == 'windows') { |
| 388 var native_path = new Path(path).toNativePath(); |
| 389 // Running this in a shell sucks, but rmdir is not part of the standard |
| 390 // path. |
| 391 return Process |
| 392 .run('rmdir', ['/s', '/q', native_path], runInShell: true) |
| 393 .then((ProcessResult result) { |
| 394 if (result.exitCode != 0) { |
| 395 throw new Exception('Can\'t delete path $native_path. ' |
| 396 'This path might be too long'); |
| 397 } |
| 398 }); |
| 399 } else { |
| 400 var dir = new Directory(path); |
| 401 return dir.delete(recursive: true); |
| 402 } |
| 403 } |
| 404 |
| 405 static void deleteTempSnapshotDirectory(Configuration configuration) { |
| 406 if (configuration.compiler == Compiler.appJit || |
| 407 configuration.compiler == Compiler.precompiler) { |
| 408 var checked = configuration.isChecked ? '-checked' : ''; |
| 409 var strong = configuration.isStrong ? '-strong' : ''; |
| 410 var minified = configuration.isMinified ? '-minified' : ''; |
| 411 var csp = configuration.isCsp ? '-csp' : ''; |
| 412 var sdk = configuration.useSdk ? '-sdk' : ''; |
| 413 var dirName = "${configuration.compiler.name}" |
| 414 "$checked$strong$minified$csp$sdk"; |
| 415 var generatedPath = |
| 416 configuration.buildDirectory + "/generated_compilations/$dirName"; |
| 417 TestUtils.deleteDirectory(generatedPath); |
| 418 } |
| 419 } |
| 420 |
| 421 static final debugLogFilePath = new Path(".debug.log"); |
| 422 |
| 423 /// If a flaky test did fail, infos about it (i.e. test name, stdin, stdout) |
| 424 /// will be written to this file. |
| 425 /// |
| 426 /// This is useful for debugging flaky tests. When running on a buildbot, the |
| 427 /// file can be made visible in the waterfall UI. |
| 428 static const flakyFileName = ".flaky.log"; |
| 429 |
| 430 /// If test.py was invoked with '--write-test-outcome-log it will write |
| 431 /// test outcomes to this file. |
| 432 static const testOutcomeFileName = ".test-outcome.log"; |
| 433 |
| 434 static void ensureExists(String filename, Configuration configuration) { |
| 435 if (!configuration.listTests && !existsCache.doesFileExist(filename)) { |
| 436 throw "'$filename' does not exist"; |
| 437 } |
| 438 } |
| 439 |
| 440 static Path absolutePath(Path path) { |
| 441 if (!path.isAbsolute) { |
| 442 return currentWorkingDirectory.join(path); |
| 443 } |
| 444 return path; |
| 445 } |
| 446 |
| 447 static int shortNameCounter = 0; // Make unique short file names on Windows. |
| 448 |
| 449 static String getShortName(String path) { |
| 450 final PATH_REPLACEMENTS = const { |
| 451 "pkg_polymer_e2e_test_bad_import_test": "polymer_bi", |
| 452 "pkg_polymer_e2e_test_canonicalization_test": "polymer_c16n", |
| 453 "pkg_polymer_e2e_test_experimental_boot_test": "polymer_boot", |
| 454 "pkg_polymer_e2e_test_good_import_test": "polymer_gi", |
| 455 "tests_co19_src_Language_12_Expressions_14_Function_Invocation_": |
| 456 "co19_fn_invoke_", |
| 457 "tests_co19_src_LayoutTests_fast_css_getComputedStyle_getComputedStyle-": |
| 458 "co19_css_getComputedStyle_", |
| 459 "tests_co19_src_LayoutTests_fast_dom_Document_CaretRangeFromPoint_" |
| 460 "caretRangeFromPoint-": "co19_caretrangefrompoint_", |
| 461 "tests_co19_src_LayoutTests_fast_dom_Document_CaretRangeFromPoint_" |
| 462 "hittest-relative-to-viewport_": "co19_caretrange_hittest_", |
| 463 "tests_co19_src_LayoutTests_fast_dom_HTMLLinkElement_link-onerror-" |
| 464 "stylesheet-with-": "co19_dom_link-", |
| 465 "tests_co19_src_LayoutTests_fast_dom_": "co19_dom", |
| 466 "tests_co19_src_LayoutTests_fast_canvas_webgl": "co19_canvas_webgl", |
| 467 "tests_co19_src_LibTest_core_AbstractClassInstantiationError_" |
| 468 "AbstractClassInstantiationError_": "co19_abstract_class_", |
| 469 "tests_co19_src_LibTest_core_IntegerDivisionByZeroException_" |
| 470 "IntegerDivisionByZeroException_": "co19_division_by_zero", |
| 471 "tests_co19_src_WebPlatformTest_html_dom_documents_dom-tree-accessors_": |
| 472 "co19_dom_accessors_", |
| 473 "tests_co19_src_WebPlatformTest_html_semantics_embedded-content_" |
| 474 "media-elements_": "co19_media_elements", |
| 475 "tests_co19_src_WebPlatformTest_html_semantics_": "co19_semantics_", |
| 476 "tests_co19_src_WebPlatformTest_html-templates_additions-to-" |
| 477 "the-steps-to-clone-a-node_": "co19_htmltemplates_clone_", |
| 478 "tests_co19_src_WebPlatformTest_html-templates_definitions_" |
| 479 "template-contents-owner": "co19_htmltemplates_contents", |
| 480 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 481 "templates_additions-to-": "co19_htmltemplates_add_", |
| 482 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 483 "templates_appending-to-a-template_": "co19_htmltemplates_append_", |
| 484 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 485 "templates_clearing-the-stack-back-to-a-given-context_": |
| 486 "co19_htmltemplates_clearstack_", |
| 487 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 488 "templates_creating-an-element-for-the-token_": |
| 489 "co19_htmltemplates_create_", |
| 490 "tests_co19_src_WebPlatformTest_html-templates_template-element" |
| 491 "_template-": "co19_htmltemplates_element-", |
| 492 "tests_co19_src_WebPlatformTest_html-templates_": "co19_htmltemplate_", |
| 493 "tests_co19_src_WebPlatformTest_shadow-dom_shadow-trees_": |
| 494 "co19_shadow-trees_", |
| 495 "tests_co19_src_WebPlatformTest_shadow-dom_elements-and-dom-objects_": |
| 496 "co19_shadowdom_", |
| 497 "tests_co19_src_WebPlatformTest_shadow-dom_html-elements-in-" |
| 498 "shadow-trees_": "co19_shadow_html_", |
| 499 "tests_co19_src_WebPlatformTest_html_webappapis_system-state-and-" |
| 500 "capabilities_the-navigator-object": "co19_webappapis_navigator_", |
| 501 "tests_co19_src_WebPlatformTest_DOMEvents_approved_": "co19_dom_approved_" |
| 502 }; |
| 503 |
| 504 // Some tests are already in [build_dir]/generated_tests. |
| 505 String GEN_TESTS = 'generated_tests/'; |
| 506 if (path.contains(GEN_TESTS)) { |
| 507 int index = path.indexOf(GEN_TESTS) + GEN_TESTS.length; |
| 508 path = 'multitest/${path.substring(index)}'; |
| 509 } |
| 510 path = path.replaceAll('/', '_'); |
| 511 final int WINDOWS_SHORTEN_PATH_LIMIT = 58; |
| 512 final int WINDOWS_PATH_END_LENGTH = 30; |
| 513 if (Platform.operatingSystem == 'windows' && |
| 514 path.length > WINDOWS_SHORTEN_PATH_LIMIT) { |
| 515 for (var key in PATH_REPLACEMENTS.keys) { |
| 516 if (path.startsWith(key)) { |
| 517 path = path.replaceFirst(key, PATH_REPLACEMENTS[key]); |
| 518 break; |
| 519 } |
| 520 } |
| 521 if (path.length > WINDOWS_SHORTEN_PATH_LIMIT) { |
| 522 ++shortNameCounter; |
| 523 var pathEnd = path.substring(path.length - WINDOWS_PATH_END_LENGTH); |
| 524 path = "short${shortNameCounter}_$pathEnd"; |
| 525 } |
| 526 } |
| 527 return path; |
| 528 } |
| 529 } |
OLD | NEW |