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 |