OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// Given the beginning and ending file names in a batch, does as much automated |
| 6 /// migration and possible and prints out the remaining manual steps required. |
| 7 /// |
| 8 /// This should be safe to run, and safe to re-run on an in-progress chunk. |
| 9 /// However, it has not been thoroughly tested, so run at your own risk. |
| 10 |
| 11 import 'dart:io'; |
| 12 |
| 13 import 'package:path/path.dart' as p; |
| 14 |
| 15 import 'package:migration/src/log.dart'; |
| 16 |
| 17 const simpleDirs = const ["corelib", "language", "lib"]; |
| 18 |
| 19 final String sdkRoot = |
| 20 p.normalize(p.join(p.dirname(p.fromUri(Platform.script)), '../../../')); |
| 21 |
| 22 final String testRoot = p.join(sdkRoot, "tests"); |
| 23 |
| 24 bool dryRun = false; |
| 25 |
| 26 void main(List<String> arguments) { |
| 27 if (arguments.contains("--dry-run")) { |
| 28 dryRun = true; |
| 29 arguments = arguments.where((argument) => argument != "--dry-run").toList(); |
| 30 } |
| 31 |
| 32 if (arguments.length != 2) { |
| 33 stderr.writeln( |
| 34 "Usage: dart migrate_batch.dart [--dry-run] <first file> <last file>"); |
| 35 stderr.writeln(); |
| 36 stderr.writeln("Example:"); |
| 37 stderr.writeln(); |
| 38 stderr.writeln( |
| 39 " \$ dart migrate_batch.dart corelib/map_to_string corelib/queue"); |
| 40 exit(1); |
| 41 } |
| 42 |
| 43 var first = toTwoPath(arguments[0]); |
| 44 var last = toTwoPath(arguments[1]); |
| 45 |
| 46 var tests = scanTests(); |
| 47 |
| 48 // Find the range of files in the chunk. We use comparisons here instead of |
| 49 // equality to try to compensate for files that may only appear in one fork |
| 50 // and should be part of the chunk but aren't officially listed as the begin |
| 51 // or end point. |
| 52 var startIndex = -1; |
| 53 var endIndex = 0; |
| 54 for (var i = 0; i < tests.length; i++) { |
| 55 if (startIndex == -1 && tests[i].twoPath.compareTo(first) >= 0) { |
| 56 startIndex = i; |
| 57 } |
| 58 |
| 59 if (tests[i].twoPath.compareTo(last) > 0) { |
| 60 endIndex = i; |
| 61 break; |
| 62 } |
| 63 } |
| 64 |
| 65 print("Migrating ${bold(endIndex - startIndex)} tests from ${bold(first)} " |
| 66 "to ${bold(last)}..."); |
| 67 print(""); |
| 68 |
| 69 var todos = <String>[]; |
| 70 var migratedTests = 0; |
| 71 var unmigratedTests = 0; |
| 72 for (var i = startIndex; i < endIndex; i++) { |
| 73 if (tests[i].migrate(todos)) { |
| 74 migratedTests++; |
| 75 } else { |
| 76 unmigratedTests++; |
| 77 } |
| 78 } |
| 79 |
| 80 print(""); |
| 81 |
| 82 var summary = ""; |
| 83 |
| 84 if (migratedTests > 0) { |
| 85 var s = migratedTests == 1 ? "" : "s"; |
| 86 summary += "Successfully migrated ${green(migratedTests)} test$s. "; |
| 87 } |
| 88 |
| 89 if (unmigratedTests > 0) { |
| 90 var s = migratedTests == 1 ? "" : "s"; |
| 91 summary += "Need manual work on ${red(unmigratedTests)} test$s:"; |
| 92 } |
| 93 |
| 94 print(summary); |
| 95 todos.forEach(todo); |
| 96 } |
| 97 |
| 98 String toTwoPath(String path) { |
| 99 // Allow eliding "_test" and/or ".dart" to make things more command-line |
| 100 // friendly. |
| 101 if (!path.endsWith("_test.dart")) path += "_test.dart"; |
| 102 if (!path.endsWith(".dart")) path += ".dart"; |
| 103 |
| 104 for (var dir in simpleDirs) { |
| 105 if (p.isWithin(dir, path)) { |
| 106 return p.join("${dir}_2", p.relative(path, from: dir)); |
| 107 } |
| 108 |
| 109 if (p.isWithin("${dir}_strong", path)) { |
| 110 return p.join("${dir}_2", p.relative(path, from: dir)); |
| 111 } |
| 112 } |
| 113 |
| 114 if (p.isWithin("html", path)) { |
| 115 return p.join("lib_2/html", p.relative(path, from: "html")); |
| 116 } |
| 117 |
| 118 if (p.isWithin("isolate", path)) { |
| 119 return p.join("lib_2/isolate", p.relative(path, from: "isolate")); |
| 120 } |
| 121 |
| 122 // Guess it's already a two path. |
| 123 return path; |
| 124 } |
| 125 |
| 126 /// Loads all of the unforked test files. |
| 127 /// |
| 128 /// Creates an list of [Fork]s, ordered by their destination paths. Handles |
| 129 /// tests that only appear in one fork or the other, or both. |
| 130 List<Fork> scanTests() { |
| 131 var tests = <String, Fork>{}; |
| 132 |
| 133 addTestDirectory(String fromDir, String twoDir) { |
| 134 for (var entry |
| 135 in new Directory(p.join(testRoot, fromDir)).listSync(recursive: true)) { |
| 136 if (!entry.path.endsWith("_test.dart")) continue; |
| 137 |
| 138 var fromPath = p.relative(entry.path, from: testRoot); |
| 139 var twoPath = p.join(twoDir, p.relative(fromPath, from: fromDir)); |
| 140 |
| 141 var fork = tests.putIfAbsent(twoPath, () => new Fork(twoPath)); |
| 142 if (fromDir.contains("_strong")) { |
| 143 fork.strongPath = fromPath; |
| 144 } else { |
| 145 fork.onePath = fromPath; |
| 146 } |
| 147 } |
| 148 } |
| 149 |
| 150 addTestDirectory("corelib", "corelib_2"); |
| 151 addTestDirectory("corelib_strong", "corelib_2"); |
| 152 addTestDirectory("html", "lib_2/html"); |
| 153 addTestDirectory("isolate", "lib_2/isolate"); |
| 154 addTestDirectory("language", "language_2"); |
| 155 addTestDirectory("language_strong", "language_2"); |
| 156 addTestDirectory("lib", "lib_2"); |
| 157 addTestDirectory("lib_strong", "lib_2"); |
| 158 |
| 159 var sorted = tests.values.toList(); |
| 160 sorted.sort((a, b) => a.twoPath.compareTo(b.twoPath)); |
| 161 return sorted; |
| 162 } |
| 163 |
| 164 /// Moves the file from [from] to [to], which are both assumed to be relative |
| 165 /// paths inside "tests". |
| 166 void moveFile(String from, String to) { |
| 167 if (dryRun) { |
| 168 print(" Dry run: move $from to $to"); |
| 169 return; |
| 170 } |
| 171 |
| 172 new File(p.join(testRoot, from)).renameSync(p.join(testRoot, to)); |
| 173 } |
| 174 |
| 175 /// Reads the contents of the file at [path], which is assumed to be relative |
| 176 /// within "tests". |
| 177 String readFile(String path) { |
| 178 return new File(p.join(testRoot, path)).readAsStringSync(); |
| 179 } |
| 180 |
| 181 /// Deletes the file at [path], which is assumed to be relative within "tests". |
| 182 void deleteFile(String path) { |
| 183 if (dryRun) { |
| 184 print(" Dry run: delete $path"); |
| 185 return; |
| 186 } |
| 187 |
| 188 new File(p.join(testRoot, path)).deleteSync(); |
| 189 } |
| 190 |
| 191 bool checkForUnitTest(String path, String source) { |
| 192 if (!source.contains("package:unittest")) return false; |
| 193 |
| 194 note("${bold(path)} uses unittest package."); |
| 195 return true; |
| 196 } |
| 197 |
| 198 class Fork { |
| 199 final String twoPath; |
| 200 String onePath; |
| 201 String strongPath; |
| 202 |
| 203 String get twoSource { |
| 204 if (twoPath == null) return null; |
| 205 if (_twoSource == null) _twoSource = readFile(twoPath); |
| 206 return _twoSource; |
| 207 } |
| 208 |
| 209 String _twoSource; |
| 210 |
| 211 String get oneSource { |
| 212 if (onePath == null) return null; |
| 213 if (_oneSource == null) _oneSource = readFile(onePath); |
| 214 return _oneSource; |
| 215 } |
| 216 |
| 217 String _oneSource; |
| 218 |
| 219 String get strongSource { |
| 220 if (strongPath == null) return null; |
| 221 if (_strongSource == null) _strongSource = readFile(strongPath); |
| 222 return _strongSource; |
| 223 } |
| 224 |
| 225 String _strongSource; |
| 226 |
| 227 Fork(this.twoPath); |
| 228 |
| 229 bool migrate(List<String> todos) { |
| 230 print("- ${bold(twoPath)}:"); |
| 231 |
| 232 var todosBefore = todos.length; |
| 233 var isMigrated = new File(p.join(testRoot, twoPath)).existsSync(); |
| 234 |
| 235 // If there is a migrated version and it's the same as an unmigrated one, |
| 236 // delete the unmigrated one. |
| 237 if (isMigrated) { |
| 238 if (onePath != null) { |
| 239 if (oneSource == twoSource) { |
| 240 deleteFile(onePath); |
| 241 done("Deleted already-migrated $onePath."); |
| 242 } else { |
| 243 note("${bold(onePath)} does not match already-migrated " |
| 244 "${bold(twoPath)}."); |
| 245 todos.add("Merge ${bold(onePath)} into ${bold(twoPath)}."); |
| 246 checkForUnitTest(onePath, oneSource); |
| 247 } |
| 248 } |
| 249 |
| 250 if (strongPath != null) { |
| 251 if (strongSource == twoSource) { |
| 252 deleteFile(strongPath); |
| 253 done("Deleted already-migrated ${bold(strongPath)}."); |
| 254 } else { |
| 255 note("${bold(strongPath)} does not match already-migrated " |
| 256 "${bold(twoPath)}."); |
| 257 todos.add("Merge ${bold(strongPath)} into ${bold(twoPath)}."); |
| 258 checkForUnitTest(strongPath, strongSource); |
| 259 } |
| 260 } |
| 261 } else { |
| 262 // If it only exists in one place, just move it. |
| 263 if (strongPath == null) { |
| 264 moveFile(onePath, twoPath); |
| 265 done("Moved from ${bold(onePath)} (no strong mode fork)."); |
| 266 } else if (onePath == null) { |
| 267 moveFile(strongPath, twoPath); |
| 268 done("Moved from ${bold(strongPath)} (no 1.0 mode fork)."); |
| 269 } else if (oneSource == strongSource) { |
| 270 // The forks are identical, pick one. |
| 271 moveFile(onePath, twoPath); |
| 272 deleteFile(strongPath); |
| 273 done("Merged identical forks."); |
| 274 checkForUnitTest(twoPath, oneSource); |
| 275 } else { |
| 276 // Otherwise, a manual merge is required. Start with the strong one. |
| 277 moveFile(strongPath, twoPath); |
| 278 done("Moved strong fork, kept 1.0 fork, manual merge required."); |
| 279 todos.add("Merge ${bold(onePath)} into ${bold(twoPath)}."); |
| 280 checkForUnitTest(onePath, oneSource); |
| 281 } |
| 282 } |
| 283 |
| 284 if (checkForUnitTest(twoPath, twoSource)) { |
| 285 todos.add("Migrate ${bold(twoPath)} off unittest."); |
| 286 } |
| 287 |
| 288 return todos.length == todosBefore; |
| 289 } |
| 290 } |
OLD | NEW |