| OLD | NEW |
| (Empty) | |
| 1 library test_utils; |
| 2 |
| 3 import 'dart:async'; |
| 4 import 'dart:io'; |
| 5 |
| 6 import 'path.dart'; |
| 7 |
| 8 class TestUtils { |
| 9 /** |
| 10 * Any script using TestUtils must set dartDirUri to a file:// URI |
| 11 * pointing to the root of the Dart checkout. |
| 12 */ |
| 13 static setDartDirUri(uri) { |
| 14 dartDirUri = uri; |
| 15 dartDir = new Path(uri.toFilePath()); |
| 16 } |
| 17 static Uri dartDirUri; |
| 18 static Path dartDir; |
| 19 static LastModifiedCache lastModifiedCache = new LastModifiedCache(); |
| 20 static ExistsCache existsCache = new ExistsCache(); |
| 21 static Path currentWorkingDirectory = |
| 22 new Path(Directory.current.path); |
| 23 |
| 24 /** |
| 25 * Creates a directory using a [relativePath] to an existing |
| 26 * [base] directory if that [relativePath] does not already exist. |
| 27 */ |
| 28 static Directory mkdirRecursive(Path base, Path relativePath) { |
| 29 if (relativePath.isAbsolute) { |
| 30 base = new Path('/'); |
| 31 } |
| 32 Directory dir = new Directory(base.toNativePath()); |
| 33 assert(dir.existsSync()); |
| 34 var segments = relativePath.segments(); |
| 35 for (String segment in segments) { |
| 36 base = base.append(segment); |
| 37 if (base.toString() == "/$segment" && |
| 38 segment.length == 2 && |
| 39 segment.endsWith(':')) { |
| 40 // Skip the directory creation for a path like "/E:". |
| 41 continue; |
| 42 } |
| 43 dir = new Directory(base.toNativePath()); |
| 44 if (!dir.existsSync()) { |
| 45 dir.createSync(); |
| 46 } |
| 47 assert(dir.existsSync()); |
| 48 } |
| 49 return dir; |
| 50 } |
| 51 |
| 52 /** |
| 53 * Copy a [source] file to a new place. |
| 54 * Assumes that the directory for [dest] already exists. |
| 55 */ |
| 56 static Future copyFile(Path source, Path dest) { |
| 57 return new File(source.toNativePath()).openRead() |
| 58 .pipe(new File(dest.toNativePath()).openWrite()); |
| 59 } |
| 60 |
| 61 static Future copyDirectory(String source, String dest) { |
| 62 source = new Path(source).toNativePath(); |
| 63 dest = new Path(dest).toNativePath(); |
| 64 |
| 65 var executable = 'cp'; |
| 66 var args = ['-Rp', source, dest]; |
| 67 if (Platform.operatingSystem == 'windows') { |
| 68 executable = 'xcopy'; |
| 69 args = [source, dest, '/e', '/i']; |
| 70 } |
| 71 return Process.run(executable, args).then((ProcessResult result) { |
| 72 if (result.exitCode != 0) { |
| 73 throw new Exception("Failed to execute '$executable " |
| 74 "${args.join(' ')}'."); |
| 75 } |
| 76 }); |
| 77 } |
| 78 |
| 79 static Future deleteDirectory(String path) { |
| 80 // We are seeing issues with long path names on windows when |
| 81 // deleting them. Use the system tools to delete our long paths. |
| 82 // See issue 16264. |
| 83 if (Platform.operatingSystem == 'windows') { |
| 84 var native_path = new Path(path).toNativePath(); |
| 85 // Running this in a shell sucks, but rmdir is not part of the standard |
| 86 // path. |
| 87 return Process.run('rmdir', ['/s', '/q', native_path], runInShell: true) |
| 88 .then((ProcessResult result) { |
| 89 if (result.exitCode != 0) { |
| 90 throw new Exception('Can\'t delete path $native_path. ' |
| 91 'This path might be too long'); |
| 92 } |
| 93 }); |
| 94 } else { |
| 95 var dir = new Directory(path); |
| 96 return dir.delete(recursive: true); |
| 97 } |
| 98 } |
| 99 |
| 100 static Path debugLogfile() { |
| 101 return new Path(".debug.log"); |
| 102 } |
| 103 |
| 104 static String flakyFileName() { |
| 105 // If a flaky test did fail, infos about it (i.e. test name, stdin, stdout) |
| 106 // will be written to this file. This is useful for the debugging of |
| 107 // flaky tests. |
| 108 // When running on a built bot, the file can be made visible in the |
| 109 // waterfall UI. |
| 110 return ".flaky.log"; |
| 111 } |
| 112 |
| 113 static String testOutcomeFileName() { |
| 114 // If test.py was invoked with '--write-test-outcome-log it will write |
| 115 // test outcomes to this file. |
| 116 return ".test-outcome.log"; |
| 117 } |
| 118 |
| 119 static void ensureExists(String filename, Map configuration) { |
| 120 if (!configuration['list'] && !existsCache.doesFileExist(filename)) { |
| 121 throw "'$filename' does not exist"; |
| 122 } |
| 123 } |
| 124 |
| 125 static Path absolutePath(Path path) { |
| 126 if (!path.isAbsolute) { |
| 127 return currentWorkingDirectory.join(path); |
| 128 } |
| 129 return path; |
| 130 } |
| 131 |
| 132 static String outputDir(Map configuration) { |
| 133 var result = ''; |
| 134 var system = configuration['system']; |
| 135 if (system == 'linux') { |
| 136 result = 'out/'; |
| 137 } else if (system == 'macos') { |
| 138 result = 'xcodebuild/'; |
| 139 } else if (system == 'windows') { |
| 140 result = 'build/'; |
| 141 } |
| 142 return result; |
| 143 } |
| 144 |
| 145 static List<String> standardOptions(Map configuration) { |
| 146 List args = ["--ignore-unrecognized-flags"]; |
| 147 if (configuration["checked"]) { |
| 148 args.add('--enable_asserts'); |
| 149 args.add("--enable_type_checks"); |
| 150 } |
| 151 String compiler = configuration["compiler"]; |
| 152 if (compiler == "dart2js" || compiler == "dart2dart") { |
| 153 args = []; |
| 154 if (configuration["checked"]) { |
| 155 args.add('--enable-checked-mode'); |
| 156 } |
| 157 // args.add("--verbose"); |
| 158 if (!isBrowserRuntime(configuration['runtime'])) { |
| 159 args.add("--allow-mock-compilation"); |
| 160 args.add("--categories=all"); |
| 161 } |
| 162 } |
| 163 if ((compiler == "dart2js" || compiler == "dart2dart") && |
| 164 configuration["minified"]) { |
| 165 args.add("--minify"); |
| 166 } |
| 167 if (compiler == "dartanalyzer" || compiler == "dart2analyzer") { |
| 168 args.add("--show-package-warnings"); |
| 169 args.add("--enable-async"); |
| 170 } |
| 171 return args; |
| 172 } |
| 173 |
| 174 static bool isBrowserRuntime(String runtime) { |
| 175 const BROWSERS = const [ |
| 176 'drt', |
| 177 'dartium', |
| 178 'ie9', |
| 179 'ie10', |
| 180 'ie11', |
| 181 'safari', |
| 182 'opera', |
| 183 'chrome', |
| 184 'ff', |
| 185 'chromeOnAndroid', |
| 186 'safarimobilesim', |
| 187 'ContentShellOnAndroid', |
| 188 'DartiumOnAndroid' |
| 189 ]; |
| 190 return BROWSERS.contains(runtime); |
| 191 } |
| 192 |
| 193 static bool isJsCommandLineRuntime(String runtime) => |
| 194 const ['d8', 'jsshell'].contains(runtime); |
| 195 |
| 196 static bool isCommandLineAnalyzer(String compiler) => |
| 197 compiler == 'dartanalyzer' || compiler == 'dart2analyzer'; |
| 198 |
| 199 static String buildDir(Map configuration) { |
| 200 // FIXME(kustermann,ricow): Our code assumes that the returned 'buildDir' |
| 201 // is relative to the current working directory. |
| 202 // Thus, if we pass in an absolute path (e.g. '--build-directory=/tmp/out') |
| 203 // we get into trouble. |
| 204 if (configuration['build_directory'] != '') { |
| 205 return configuration['build_directory']; |
| 206 } |
| 207 |
| 208 return "${outputDir(configuration)}${configurationDir(configuration)}"; |
| 209 } |
| 210 |
| 211 static getValidOutputDir(Map configuration, String mode, String arch) { |
| 212 // We allow our code to have been cross compiled, i.e., that there |
| 213 // is an X in front of the arch. We don't allow both a cross compiled |
| 214 // and a normal version to be present (except if you specifically pass |
| 215 // in the build_directory). |
| 216 var normal = '$mode$arch'; |
| 217 var cross = '${mode}X$arch'; |
| 218 var outDir = outputDir(configuration); |
| 219 var normalDir = new Directory(new Path('$outDir$normal').toNativePath()); |
| 220 var crossDir = new Directory(new Path('$outDir$cross').toNativePath()); |
| 221 if (normalDir.existsSync() && crossDir.existsSync()) { |
| 222 throw "You can't have both $normalDir and $crossDir, we don't know which" |
| 223 " binary to use"; |
| 224 } |
| 225 if (crossDir.existsSync()) { |
| 226 return cross; |
| 227 } |
| 228 return normal; |
| 229 } |
| 230 |
| 231 static String configurationDir(Map configuration) { |
| 232 // For regular dart checkouts, the configDir by default is mode+arch. |
| 233 // For Dartium, the configDir by default is mode (as defined by the Chrome |
| 234 // build setup). We can detect this because in the dartium checkout, the |
| 235 // "output" directory is a sibling of the dart directory instead of a child. |
| 236 var mode = (configuration['mode'] == 'debug') ? 'Debug' : 'Release'; |
| 237 var arch = configuration['arch'].toUpperCase(); |
| 238 if (currentWorkingDirectory != dartDir) { |
| 239 return getValidOutputDir(configuration, mode, arch); |
| 240 } else { |
| 241 return mode; |
| 242 } |
| 243 } |
| 244 |
| 245 /** |
| 246 * Returns the path to the dart binary checked into the repo, used for |
| 247 * bootstrapping test.dart. |
| 248 */ |
| 249 static Path get dartTestExecutable { |
| 250 var path = '$dartDir/tools/testing/bin/' |
| 251 '${Platform.operatingSystem}/dart'; |
| 252 if (Platform.operatingSystem == 'windows') { |
| 253 path = '$path.exe'; |
| 254 } |
| 255 return new Path(path); |
| 256 } |
| 257 |
| 258 /** |
| 259 * Gets extra options under [key] passed to the testing script. |
| 260 */ |
| 261 static List<String> getExtraOptions(Map configuration, String key) { |
| 262 if (configuration[key] == null) return <String>[]; |
| 263 return configuration[key] |
| 264 .split(" ") |
| 265 .map((s) => s.trim()) |
| 266 .where((s) => s.isNotEmpty) |
| 267 .toList(); |
| 268 } |
| 269 |
| 270 /** |
| 271 * Gets extra vm options passed to the testing script. |
| 272 */ |
| 273 static List<String> getExtraVmOptions(Map configuration) => |
| 274 getExtraOptions(configuration, 'vm_options'); |
| 275 |
| 276 static String getShortName(String path) { |
| 277 final PATH_REPLACEMENTS = const { |
| 278 "pkg_polymer_e2e_test_bad_import_test": "polymer_bi", |
| 279 "pkg_polymer_e2e_test_canonicalization_test": "polymer_c16n", |
| 280 "pkg_polymer_e2e_test_experimental_boot_test": "polymer_boot", |
| 281 "pkg_polymer_e2e_test_good_import_test": "polymer_gi", |
| 282 "tests_co19_src_Language_12_Expressions_14_Function_Invocation_": |
| 283 "co19_fn_invoke_", |
| 284 "tests_co19_src_LayoutTests_fast_css_getComputedStyle_getComputedStyle-": |
| 285 "co19_css_getComputedStyle_", |
| 286 "tests_co19_src_LayoutTests_fast_dom_Document_CaretRangeFromPoint_" |
| 287 "caretRangeFromPoint-": "co19_caretrangefrompoint_", |
| 288 "tests_co19_src_LayoutTests_fast_dom_Document_CaretRangeFromPoint_" |
| 289 "hittest-relative-to-viewport_": "co19_caretrange_hittest_", |
| 290 "tests_co19_src_LayoutTests_fast_dom_HTMLLinkElement_link-onerror-" |
| 291 "stylesheet-with-": "co19_dom_link-", |
| 292 "tests_co19_src_LayoutTests_fast_dom_": "co19_dom", |
| 293 "tests_co19_src_LayoutTests_fast_canvas_webgl": "co19_canvas_webgl", |
| 294 "tests_co19_src_LibTest_core_AbstractClassInstantiationError_" |
| 295 "AbstractClassInstantiationError_": "co19_abstract_class_", |
| 296 "tests_co19_src_LibTest_core_IntegerDivisionByZeroException_" |
| 297 "IntegerDivisionByZeroException_": "co19_division_by_zero", |
| 298 "tests_co19_src_WebPlatformTest_html_dom_documents_dom-tree-accessors_": |
| 299 "co19_dom_accessors_", |
| 300 "tests_co19_src_WebPlatformTest_html_semantics_embedded-content_" |
| 301 "media-elements_": "co19_media_elements", |
| 302 "tests_co19_src_WebPlatformTest_html_semantics_": "co19_semantics_", |
| 303 |
| 304 "tests_co19_src_WebPlatformTest_html-templates_additions-to-" |
| 305 "the-steps-to-clone-a-node_": "co19_htmltemplates_clone_", |
| 306 "tests_co19_src_WebPlatformTest_html-templates_definitions_" |
| 307 "template-contents-owner": "co19_htmltemplates_contents", |
| 308 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 309 "templates_additions-to-": "co19_htmltemplates_add_", |
| 310 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 311 "templates_appending-to-a-template_": "co19_htmltemplates_append_", |
| 312 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 313 "templates_clearing-the-stack-back-to-a-given-context_": |
| 314 "co19_htmltemplates_clearstack_", |
| 315 "tests_co19_src_WebPlatformTest_html-templates_parsing-html-" |
| 316 "templates_creating-an-element-for-the-token_": |
| 317 "co19_htmltemplates_create_", |
| 318 "tests_co19_src_WebPlatformTest_html-templates_template-element" |
| 319 "_template-": "co19_htmltemplates_element-", |
| 320 "tests_co19_src_WebPlatformTest_html-templates_": "co19_htmltemplate_", |
| 321 |
| 322 "tests_co19_src_WebPlatformTest_shadow-dom_shadow-trees_": |
| 323 "co19_shadow-trees_", |
| 324 "tests_co19_src_WebPlatformTest_shadow-dom_elements-and-dom-objects_": |
| 325 "co19_shadowdom_", |
| 326 "tests_co19_src_WebPlatformTest_shadow-dom_html-elements-in-" |
| 327 "shadow-trees_": "co19_shadow_html_", |
| 328 "tests_co19_src_WebPlatformTest_html_webappapis_system-state-and-" |
| 329 "capabilities_the-navigator-object": "co19_webappapis_navigator_", |
| 330 "tests_co19_src_WebPlatformTest_DOMEvents_approved_": "co19_dom_approved_" |
| 331 }; |
| 332 |
| 333 // Some tests are already in [build_dir]/generated_tests. |
| 334 String GEN_TESTS = 'generated_tests/'; |
| 335 if (path.contains(GEN_TESTS)) { |
| 336 int index = path.indexOf(GEN_TESTS) + GEN_TESTS.length; |
| 337 path = 'multitest/${path.substring(index)}'; |
| 338 } |
| 339 path = path.replaceAll('/', '_'); |
| 340 final int WINDOWS_SHORTEN_PATH_LIMIT = 58; |
| 341 if (Platform.operatingSystem == 'windows' && |
| 342 path.length > WINDOWS_SHORTEN_PATH_LIMIT) { |
| 343 for (var key in PATH_REPLACEMENTS.keys) { |
| 344 if (path.startsWith(key)) { |
| 345 path = path.replaceFirst(key, PATH_REPLACEMENTS[key]); |
| 346 break; |
| 347 } |
| 348 } |
| 349 } |
| 350 return path; |
| 351 } |
| 352 } |
| 353 |
| 354 |
| 355 class LastModifiedCache { |
| 356 final Map<String, DateTime> _cache = <String, DateTime>{}; |
| 357 |
| 358 /** |
| 359 * Returns the last modified date of the given [uri]. |
| 360 * |
| 361 * The return value will be cached for future queries. If [uri] is a local |
| 362 * file, it's last modified [Date] will be returned. If the file does not |
| 363 * exist, null will be returned instead. |
| 364 * In case [uri] is not a local file, this method will always return |
| 365 * the current date. |
| 366 */ |
| 367 DateTime getLastModified(Uri uri) { |
| 368 if (uri.scheme == "file") { |
| 369 if (_cache.containsKey(uri.path)) { |
| 370 return _cache[uri.path]; |
| 371 } |
| 372 var file = new File(new Path(uri.path).toNativePath()); |
| 373 _cache[uri.path] = file.existsSync() ? file.lastModifiedSync() : null; |
| 374 return _cache[uri.path]; |
| 375 } |
| 376 return new DateTime.now(); |
| 377 } |
| 378 } |
| 379 |
| 380 |
| 381 class ExistsCache { |
| 382 final Map<String, bool> _cache = <String, bool>{}; |
| 383 |
| 384 /** |
| 385 * Returns true if the file in [path] exists, false otherwise. |
| 386 * |
| 387 * The information will be cached. |
| 388 */ |
| 389 bool doesFileExist(String path) { |
| 390 if (!_cache.containsKey(path)) { |
| 391 _cache[path] = new File(path).existsSync(); |
| 392 } |
| 393 return _cache[path]; |
| 394 } |
| 395 } |
| OLD | NEW |