| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// Given the beginning and ending file names in a batch, does as much automated | 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. | 6 /// migration and possible and prints out the remaining manual steps required. |
| 7 /// | 7 /// |
| 8 /// This should be safe to run, and safe to re-run on an in-progress chunk. | 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. | 9 /// However, it has not been thoroughly tested, so run at your own risk. |
| 10 | 10 |
| 11 import 'dart:io'; | 11 import 'dart:io'; |
| 12 | 12 |
| 13 import 'package:path/path.dart' as p; | 13 import 'package:path/path.dart' as p; |
| 14 | 14 |
| 15 import 'package:migration/src/log.dart'; | 15 import 'package:migration/src/log.dart'; |
| 16 import 'package:migration/src/validate.dart'; |
| 16 import 'package:status_file/status_file.dart'; | 17 import 'package:status_file/status_file.dart'; |
| 17 | 18 |
| 18 const simpleDirs = const ["corelib", "language", "lib"]; | 19 const simpleDirs = const ["corelib", "language", "lib"]; |
| 19 | 20 |
| 20 final String sdkRoot = | 21 final String sdkRoot = |
| 21 p.normalize(p.join(p.dirname(p.fromUri(Platform.script)), '../../../')); | 22 p.normalize(p.join(p.dirname(p.fromUri(Platform.script)), '../../../')); |
| 22 | 23 |
| 23 final String testRoot = p.join(sdkRoot, "tests"); | 24 final String testRoot = p.join(sdkRoot, "tests"); |
| 24 | 25 |
| 25 bool dryRun = false; | 26 bool dryRun = false; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 57 startIndex = i; | 58 startIndex = i; |
| 58 } | 59 } |
| 59 | 60 |
| 60 if (tests[i].twoPath.compareTo(last) > 0) { | 61 if (tests[i].twoPath.compareTo(last) > 0) { |
| 61 endIndex = i; | 62 endIndex = i; |
| 62 break; | 63 break; |
| 63 } | 64 } |
| 64 } | 65 } |
| 65 | 66 |
| 66 if ((endIndex - startIndex) == 0) { | 67 if ((endIndex - startIndex) == 0) { |
| 67 print(bold("No tests to migrate.")); | 68 print(bold("No tests in range.")); |
| 68 return; | 69 return; |
| 69 } | 70 } |
| 70 | 71 |
| 71 print("Migrating ${bold(endIndex - startIndex)} tests from ${bold(first)} " | 72 print("Migrating ${bold(endIndex - startIndex)} tests from ${bold(first)} " |
| 72 "to ${bold(last)}..."); | 73 "to ${bold(last)}..."); |
| 73 print(""); | 74 print(""); |
| 74 | 75 |
| 75 tests = tests.getRange(startIndex, endIndex); | 76 var allTodos = <String, List<String>>{}; |
| 76 var todos = <String>[]; | 77 tests = tests.sublist(startIndex, endIndex); |
| 77 var migratedTests = 0; | 78 var migratedTests = 0; |
| 78 var unmigratedTests = 0; | 79 var unmigratedTests = 0; |
| 79 for (var test in tests) { | 80 for (var test in tests) { |
| 80 if (test.migrate(todos)) { | 81 var todos = test.migrate(); |
| 82 if (todos.isEmpty) { |
| 81 migratedTests++; | 83 migratedTests++; |
| 82 } else { | 84 } else { |
| 83 unmigratedTests++; | 85 unmigratedTests++; |
| 86 allTodos[test.twoPath] = todos; |
| 84 } | 87 } |
| 85 } | 88 } |
| 86 | 89 |
| 90 // Print status file entries. |
| 91 var statusFileEntries = new StringBuffer(); |
| 92 var statusFiles = loadStatusFiles(); |
| 93 for (var statusFile in statusFiles) { |
| 94 printStatusFileEntries(statusFileEntries, tests, statusFile); |
| 95 } |
| 96 |
| 97 new File("statuses.migration") |
| 98 .writeAsStringSync(statusFileEntries.toString()); |
| 99 print("Wrote relevant test status file entries to 'statuses.migration'."); |
| 100 |
| 101 // Tell the user what's left TODO. |
| 87 print(""); | 102 print(""); |
| 88 | |
| 89 var summary = ""; | 103 var summary = ""; |
| 90 | 104 |
| 91 if (migratedTests > 0) { | 105 if (migratedTests > 0) { |
| 92 var s = migratedTests == 1 ? "" : "s"; | 106 var s = migratedTests == 1 ? "" : "s"; |
| 93 summary += "Successfully migrated ${green(migratedTests)} test$s. "; | 107 summary += "Successfully migrated ${green(migratedTests)} test$s. "; |
| 94 } | 108 } |
| 95 | 109 |
| 96 if (unmigratedTests > 0) { | 110 if (unmigratedTests > 0) { |
| 97 var s = migratedTests == 1 ? "" : "s"; | 111 var s = unmigratedTests == 1 ? "" : "s"; |
| 98 summary += "Need manual work on ${red(unmigratedTests)} test$s:"; | 112 summary += "Need manual work on ${red(unmigratedTests)} test$s:"; |
| 99 } | 113 } |
| 100 | 114 |
| 101 print(summary); | 115 print(summary); |
| 102 todos.forEach(todo); | 116 var todoTests = allTodos.keys.toList(); |
| 103 | 117 todoTests.sort(); |
| 104 // Print status file entries. | 118 for (var todoTest in todoTests) { |
| 105 var statusFileEntries = new StringBuffer(); | 119 print("- ${bold(todoTest)}:"); |
| 106 var statusFiles = loadStatusFiles(); | 120 allTodos[todoTest].forEach(todo); |
| 107 for (var statusFile in statusFiles) { | |
| 108 printStatusFileEntries(statusFileEntries, tests, statusFile); | |
| 109 } | 121 } |
| 110 | |
| 111 new File("statuses.migration") | |
| 112 .writeAsStringSync(statusFileEntries.toString()); | |
| 113 print( | |
| 114 bold("Wrote relevant test status file entries to 'statuses.migration'")); | |
| 115 } | 122 } |
| 116 | 123 |
| 117 /// Returns a [String] of the relevant status file entries associated with the | 124 /// Returns a [String] of the relevant status file entries associated with the |
| 118 /// tests in [tests] found in [statusFile]. | 125 /// tests in [tests] found in [statusFile]. |
| 119 void printStatusFileEntries( | 126 void printStatusFileEntries( |
| 120 StringBuffer statusFileEntries, List<Fork> tests, StatusFile statusFile) { | 127 StringBuffer statusFileEntries, List<Fork> tests, StatusFile statusFile) { |
| 121 var filteredStatusFile = new StatusFile(statusFile.path); | 128 var filteredStatusFile = new StatusFile(statusFile.path); |
| 122 var testNames = <String>[]; | 129 var testNames = <String>[]; |
| 123 for (var test in tests) { | 130 for (var test in tests) { |
| 124 testNames.add(test.twoPath.split("/").last.split(".")[0]); | 131 testNames.add(test.twoPath.split("/").last.split(".")[0]); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 141 } | 148 } |
| 142 if (!filteredStatusFile.isEmpty) { | 149 if (!filteredStatusFile.isEmpty) { |
| 143 statusFileEntries.writeln("Entries for status file ${statusFile.path}:"); | 150 statusFileEntries.writeln("Entries for status file ${statusFile.path}:"); |
| 144 statusFileEntries.writeln(filteredStatusFile); | 151 statusFileEntries.writeln(filteredStatusFile); |
| 145 } | 152 } |
| 146 } | 153 } |
| 147 | 154 |
| 148 String toTwoPath(String path) { | 155 String toTwoPath(String path) { |
| 149 // Allow eliding "_test" and/or ".dart" to make things more command-line | 156 // Allow eliding "_test" and/or ".dart" to make things more command-line |
| 150 // friendly. | 157 // friendly. |
| 151 if (!path.endsWith("_test.dart")) path += "_test.dart"; | 158 if (!path.endsWith(".dart") && !path.endsWith("_test.dart")) { |
| 159 path += "_test.dart"; |
| 160 } |
| 152 if (!path.endsWith(".dart")) path += ".dart"; | 161 if (!path.endsWith(".dart")) path += ".dart"; |
| 153 | 162 |
| 154 for (var dir in simpleDirs) { | 163 for (var dir in simpleDirs) { |
| 155 if (p.isWithin(dir, path)) { | 164 if (p.isWithin(dir, path)) { |
| 156 return p.join("${dir}_2", p.relative(path, from: dir)); | 165 return p.join("${dir}_2", p.relative(path, from: dir)); |
| 157 } | 166 } |
| 158 | 167 |
| 159 if (p.isWithin("${dir}_strong", path)) { | 168 if (p.isWithin("${dir}_strong", path)) { |
| 160 return p.join("${dir}_2", p.relative(path, from: dir)); | 169 return p.join("${dir}_2", p.relative(path, from: dir)); |
| 161 } | 170 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 176 /// Loads all of the unforked test files. | 185 /// Loads all of the unforked test files. |
| 177 /// | 186 /// |
| 178 /// Creates an list of [Fork]s, ordered by their destination paths. Handles | 187 /// Creates an list of [Fork]s, ordered by their destination paths. Handles |
| 179 /// tests that only appear in one fork or the other, or both. | 188 /// tests that only appear in one fork or the other, or both. |
| 180 List<Fork> scanTests() { | 189 List<Fork> scanTests() { |
| 181 var tests = <String, Fork>{}; | 190 var tests = <String, Fork>{}; |
| 182 | 191 |
| 183 addTestDirectory(String fromDir, String twoDir) { | 192 addTestDirectory(String fromDir, String twoDir) { |
| 184 for (var entry | 193 for (var entry |
| 185 in new Directory(p.join(testRoot, fromDir)).listSync(recursive: true)) { | 194 in new Directory(p.join(testRoot, fromDir)).listSync(recursive: true)) { |
| 186 if (!entry.path.endsWith("_test.dart")) continue; | 195 if (!entry.path.endsWith(".dart")) continue; |
| 187 | 196 |
| 188 var fromPath = p.relative(entry.path, from: testRoot); | 197 var fromPath = p.relative(entry.path, from: testRoot); |
| 189 var twoPath = p.join(twoDir, p.relative(fromPath, from: fromDir)); | 198 var twoPath = p.join(twoDir, p.relative(fromPath, from: fromDir)); |
| 199 |
| 190 var fork = tests.putIfAbsent(twoPath, () => new Fork(twoPath)); | 200 var fork = tests.putIfAbsent(twoPath, () => new Fork(twoPath)); |
| 191 if (fromDir.contains("_strong")) { | 201 if (fromDir.contains("_strong")) { |
| 192 fork.strongPath = fromPath; | 202 fork.strongPath = fromPath; |
| 193 } else { | 203 } else { |
| 194 fork.onePath = fromPath; | 204 fork.onePath = fromPath; |
| 195 } | 205 } |
| 196 } | 206 } |
| 197 } | 207 } |
| 198 | 208 |
| 199 addTestDirectory("corelib", "corelib_2"); | 209 addTestDirectory("corelib", "corelib_2"); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 234 } | 244 } |
| 235 | 245 |
| 236 /// Moves the file from [from] to [to], which are both assumed to be relative | 246 /// Moves the file from [from] to [to], which are both assumed to be relative |
| 237 /// paths inside "tests". | 247 /// paths inside "tests". |
| 238 void moveFile(String from, String to) { | 248 void moveFile(String from, String to) { |
| 239 if (dryRun) { | 249 if (dryRun) { |
| 240 print(" Dry run: move $from to $to"); | 250 print(" Dry run: move $from to $to"); |
| 241 return; | 251 return; |
| 242 } | 252 } |
| 243 | 253 |
| 254 // Create the directory if needed. |
| 255 new Directory(p.dirname(p.join(testRoot, to))).createSync(recursive: true); |
| 256 |
| 244 new File(p.join(testRoot, from)).renameSync(p.join(testRoot, to)); | 257 new File(p.join(testRoot, from)).renameSync(p.join(testRoot, to)); |
| 245 } | 258 } |
| 246 | 259 |
| 247 /// Reads the contents of the file at [path], which is assumed to be relative | 260 /// Reads the contents of the file at [path], which is assumed to be relative |
| 248 /// within "tests". | 261 /// within "tests". |
| 249 String readFile(String path) { | 262 String readFile(String path) { |
| 250 return new File(p.join(testRoot, path)).readAsStringSync(); | 263 return new File(p.join(testRoot, path)).readAsStringSync(); |
| 251 } | 264 } |
| 252 | 265 |
| 253 /// Deletes the file at [path], which is assumed to be relative within "tests". | 266 /// Deletes the file at [path], which is assumed to be relative within "tests". |
| 254 void deleteFile(String path) { | 267 void deleteFile(String path) { |
| 255 if (dryRun) { | 268 if (dryRun) { |
| 256 print(" Dry run: delete $path"); | 269 print(" Dry run: delete $path"); |
| 257 return; | 270 return; |
| 258 } | 271 } |
| 259 | 272 |
| 260 new File(p.join(testRoot, path)).deleteSync(); | 273 new File(p.join(testRoot, path)).deleteSync(); |
| 261 } | 274 } |
| 262 | 275 |
| 263 bool checkForUnitTest(String path, String source) { | |
| 264 if (!source.contains("package:unittest")) return false; | |
| 265 | |
| 266 note("${bold(path)} uses unittest package."); | |
| 267 return true; | |
| 268 } | |
| 269 | |
| 270 class Fork { | 276 class Fork { |
| 271 final String twoPath; | 277 final String twoPath; |
| 272 String onePath; | 278 String onePath; |
| 273 String strongPath; | 279 String strongPath; |
| 274 | 280 |
| 275 String get twoSource { | 281 String get twoSource { |
| 276 if (twoPath == null) return null; | 282 if (twoPath == null) return null; |
| 277 if (_twoSource == null) _twoSource = readFile(twoPath); | 283 if (_twoSource == null) _twoSource = readFile(twoPath); |
| 278 return _twoSource; | 284 return _twoSource; |
| 279 } | 285 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 291 String get strongSource { | 297 String get strongSource { |
| 292 if (strongPath == null) return null; | 298 if (strongPath == null) return null; |
| 293 if (_strongSource == null) _strongSource = readFile(strongPath); | 299 if (_strongSource == null) _strongSource = readFile(strongPath); |
| 294 return _strongSource; | 300 return _strongSource; |
| 295 } | 301 } |
| 296 | 302 |
| 297 String _strongSource; | 303 String _strongSource; |
| 298 | 304 |
| 299 Fork(this.twoPath); | 305 Fork(this.twoPath); |
| 300 | 306 |
| 301 bool migrate(List<String> todos) { | 307 List<String> migrate() { |
| 302 print("- ${bold(twoPath)}:"); | 308 print("- ${bold(twoPath)}:"); |
| 303 | 309 |
| 304 var todosBefore = todos.length; | 310 var todos = <String>[]; |
| 305 var isMigrated = new File(p.join(testRoot, twoPath)).existsSync(); | 311 var isMigrated = new File(p.join(testRoot, twoPath)).existsSync(); |
| 306 | 312 |
| 307 // If there is a migrated version and it's the same as an unmigrated one, | 313 // If there is a migrated version and it's the same as an unmigrated one, |
| 308 // delete the unmigrated one. | 314 // delete the unmigrated one. |
| 309 if (isMigrated) { | 315 if (isMigrated) { |
| 310 if (onePath != null) { | 316 if (onePath != null) { |
| 311 if (oneSource == twoSource) { | 317 if (oneSource == twoSource) { |
| 312 deleteFile(onePath); | 318 deleteFile(onePath); |
| 313 done("Deleted already-migrated $onePath."); | 319 done("Deleted already-migrated $onePath."); |
| 314 } else { | 320 } else { |
| 315 note("${bold(onePath)} does not match already-migrated " | 321 note("${bold(onePath)} does not match already-migrated " |
| 316 "${bold(twoPath)}."); | 322 "${bold(twoPath)}."); |
| 317 todos.add("Merge ${bold(onePath)} into ${bold(twoPath)}."); | 323 todos.add("Merge from ${bold(onePath)} into this file."); |
| 318 checkForUnitTest(onePath, oneSource); | 324 validateFile(onePath, oneSource); |
| 319 } | 325 } |
| 320 } | 326 } |
| 321 | 327 |
| 322 if (strongPath != null) { | 328 if (strongPath != null) { |
| 323 if (strongSource == twoSource) { | 329 if (strongSource == twoSource) { |
| 324 deleteFile(strongPath); | 330 deleteFile(strongPath); |
| 325 done("Deleted already-migrated ${bold(strongPath)}."); | 331 done("Deleted already-migrated ${bold(strongPath)}."); |
| 326 } else { | 332 } else { |
| 327 note("${bold(strongPath)} does not match already-migrated " | 333 note("${bold(strongPath)} does not match already-migrated " |
| 328 "${bold(twoPath)}."); | 334 "${bold(twoPath)}."); |
| 329 todos.add("Merge ${bold(strongPath)} into ${bold(twoPath)}."); | 335 todos.add("Merge from ${bold(strongPath)} into this file."); |
| 330 checkForUnitTest(strongPath, strongSource); | 336 validateFile(strongPath, strongSource); |
| 331 } | 337 } |
| 332 } | 338 } |
| 333 } else { | 339 } else { |
| 334 // If it only exists in one place, just move it. | 340 // If it only exists in one place, just move it. |
| 335 if (strongPath == null) { | 341 if (strongPath == null) { |
| 336 moveFile(onePath, twoPath); | 342 moveFile(onePath, twoPath); |
| 337 done("Moved from ${bold(onePath)} (no strong mode fork)."); | 343 done("Moved from ${bold(onePath)} (no strong mode fork)."); |
| 338 } else if (onePath == null) { | 344 } else if (onePath == null) { |
| 339 moveFile(strongPath, twoPath); | 345 moveFile(strongPath, twoPath); |
| 340 done("Moved from ${bold(strongPath)} (no 1.0 mode fork)."); | 346 done("Moved from ${bold(strongPath)} (no 1.0 mode fork)."); |
| 341 } else if (oneSource == strongSource) { | 347 } else if (oneSource == strongSource) { |
| 342 // The forks are identical, pick one. | 348 // The forks are identical, pick one. |
| 343 moveFile(onePath, twoPath); | 349 moveFile(onePath, twoPath); |
| 344 deleteFile(strongPath); | 350 deleteFile(strongPath); |
| 345 done("Merged identical forks."); | 351 done("Merged identical forks."); |
| 346 checkForUnitTest(twoPath, oneSource); | 352 validateFile(twoPath, oneSource); |
| 347 } else { | 353 } else { |
| 348 // Otherwise, a manual merge is required. Start with the strong one. | 354 // Otherwise, a manual merge is required. Start with the strong one. |
| 355 print(new File(strongPath).existsSync()); |
| 349 moveFile(strongPath, twoPath); | 356 moveFile(strongPath, twoPath); |
| 350 done("Moved strong fork, kept 1.0 fork, manual merge required."); | 357 done("Moved strong fork, kept 1.0 fork, manual merge required."); |
| 351 todos.add("Merge ${bold(onePath)} into ${bold(twoPath)}."); | 358 todos.add("Merge from ${bold(onePath)} into this file."); |
| 352 checkForUnitTest(onePath, oneSource); | 359 validateFile(onePath, oneSource); |
| 353 } | 360 } |
| 354 } | 361 } |
| 355 | 362 |
| 356 if (checkForUnitTest(twoPath, twoSource)) { | 363 validateFile(twoPath, twoSource, todos); |
| 357 todos.add("Migrate ${bold(twoPath)} off unittest."); | |
| 358 } | |
| 359 | 364 |
| 360 return todos.length == todosBefore; | 365 return todos; |
| 361 } | 366 } |
| 362 } | 367 } |
| OLD | NEW |