Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 /** | 5 /** |
| 6 * A stress test for the analysis server. | 6 * A stress test for the analysis server. |
| 7 */ | 7 */ |
| 8 library analysis_server.test.stress.replay.replay; | 8 library analysis_server.test.stress.replay.replay; |
| 9 | 9 |
| 10 import 'dart:async'; | 10 import 'dart:async'; |
| 11 import 'dart:io'; | 11 import 'dart:io'; |
| 12 | 12 |
| 13 import 'package:analysis_server/plugin/protocol/protocol.dart'; | 13 import 'package:analysis_server/plugin/protocol/protocol.dart'; |
| 14 import 'package:analyzer/src/generated/error.dart' as error; | |
| 14 import 'package:analyzer/src/generated/java_engine.dart'; | 15 import 'package:analyzer/src/generated/java_engine.dart'; |
| 16 import 'package:analyzer/src/generated/scanner.dart'; | |
| 15 import 'package:analyzer/src/generated/source.dart'; | 17 import 'package:analyzer/src/generated/source.dart'; |
| 16 import 'package:analyzer/src/util/glob.dart'; | 18 import 'package:analyzer/src/util/glob.dart'; |
| 17 import 'package:args/args.dart'; | 19 import 'package:args/args.dart'; |
| 18 import 'package:path/path.dart' as path; | 20 import 'package:path/path.dart' as path; |
| 19 | 21 |
| 20 import '../utilities/git.dart'; | 22 import '../utilities/git.dart'; |
| 21 import '../utilities/server.dart'; | 23 import '../utilities/server.dart'; |
| 22 import 'operation.dart'; | 24 import 'operation.dart'; |
| 23 | 25 |
| 24 /** | 26 /** |
| 25 * Run the simulation based on the given command-line [arguments]. | 27 * Run the simulation based on the given command-line [arguments]. |
| 26 */ | 28 */ |
| 27 Future main(List<String> arguments) async { | 29 Future main(List<String> arguments) async { |
| 28 Driver driver = new Driver(); | 30 Driver driver = new Driver(); |
| 29 await driver.run(arguments); | 31 await driver.run(arguments); |
| 30 } | 32 } |
| 31 | 33 |
| 32 /** | 34 /** |
| 33 * The driver class that runs the simulation. | 35 * The driver class that runs the simulation. |
| 34 */ | 36 */ |
| 35 class Driver { | 37 class Driver { |
| 36 /** | 38 /** |
|
skybrian
2015/12/12 02:01:20
Any plans to switch to '///' in the analyzer?
Brian Wilkerson
2015/12/12 15:28:23
Nope. At least two of us really dislike that form
| |
| 39 * The value of the [OVERLAY_STYLE_OPTION_NAME] indicating that modifications | |
| 40 * to a file should be represented by an add overlay, followed by zero or more | |
| 41 * change overlays, followed by a remove overlay. | |
| 42 */ | |
| 43 static String CHANGE_OVERLAY_STYLE = 'change'; | |
| 44 | |
| 45 /** | |
| 37 * The name of the command-line flag that will print help text. | 46 * The name of the command-line flag that will print help text. |
| 38 */ | 47 */ |
| 39 static String HELP_FLAG_NAME = 'help'; | 48 static String HELP_FLAG_NAME = 'help'; |
| 40 | 49 |
| 41 /** | 50 /** |
| 51 * The value of the [OVERLAY_STYLE_OPTION_NAME] indicating that modifications | |
| 52 * to a file should be represented by an add overlay, followed by zero or more | |
| 53 * additional add overlays, followed by a remove overlay. | |
| 54 */ | |
| 55 static String MULTIPLE_ADD_OVERLAY_STYLE = 'multipleAdd'; | |
| 56 | |
| 57 /** | |
| 58 * The name of the command-line option used to specify the style of | |
| 59 * interaction to use when making `analysis.updateContent` requests. | |
| 60 */ | |
| 61 static String OVERLAY_STYLE_OPTION_NAME = 'overlay-style'; | |
| 62 | |
| 63 /** | |
| 42 * The name of the pubspec file. | 64 * The name of the pubspec file. |
| 43 */ | 65 */ |
| 44 static const String PUBSPEC_FILE_NAME = 'pubspec.yaml'; | 66 static const String PUBSPEC_FILE_NAME = 'pubspec.yaml'; |
| 45 | 67 |
| 46 /** | 68 /** |
| 47 * The name of the branch used to clean-up after making temporary changes. | 69 * The name of the branch used to clean-up after making temporary changes. |
| 48 */ | 70 */ |
| 49 static const String TEMP_BRANCH_NAME = 'temp'; | 71 static const String TEMP_BRANCH_NAME = 'temp'; |
| 50 | 72 |
| 51 /** | 73 /** |
| 74 * The style of interaction to use for analysis.updateContent requests. | |
| 75 */ | |
| 76 OverlayStyle overlayStyle; | |
| 77 | |
| 78 /** | |
| 52 * The absolute path of the repository. | 79 * The absolute path of the repository. |
| 53 */ | 80 */ |
| 54 String repositoryPath; | 81 String repositoryPath; |
| 55 | 82 |
| 56 /** | 83 /** |
| 57 * The absolute paths to the analysis roots. | 84 * The absolute paths to the analysis roots. |
| 58 */ | 85 */ |
| 59 List<String> analysisRoots; | 86 List<String> analysisRoots; |
| 60 | 87 |
| 61 /** | 88 /** |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 80 Statistics statistics; | 107 Statistics statistics; |
| 81 | 108 |
| 82 /** | 109 /** |
| 83 * Initialize a newly created driver. | 110 * Initialize a newly created driver. |
| 84 */ | 111 */ |
| 85 Driver() { | 112 Driver() { |
| 86 statistics = new Statistics(this); | 113 statistics = new Statistics(this); |
| 87 } | 114 } |
| 88 | 115 |
| 89 /** | 116 /** |
| 90 * Run the test based on the given command-line arguments ([args]). | 117 * Run the simulation based on the given command-line arguments ([args]). |
| 91 */ | 118 */ |
| 92 Future run(List<String> args) async { | 119 Future run(List<String> args) async { |
| 93 // | 120 // |
| 94 // Process the command-line arguments. | 121 // Process the command-line arguments. |
| 95 // | 122 // |
| 96 ArgParser parser = _createArgParser(); | 123 if (!_processCommandLine(args)) { |
| 97 ArgResults results; | |
| 98 try { | |
| 99 results = parser.parse(args); | |
| 100 } catch (exception) { | |
| 101 _showUsage(parser); | |
| 102 return null; | 124 return null; |
| 103 } | 125 } |
| 104 | |
| 105 if (results[HELP_FLAG_NAME]) { | |
| 106 _showUsage(parser); | |
| 107 return null; | |
| 108 } | |
| 109 | |
| 110 List<String> arguments = results.arguments; | |
| 111 if (arguments.length < 2) { | |
| 112 _showUsage(parser); | |
| 113 return null; | |
| 114 } | |
| 115 repositoryPath = path.normalize(arguments[0]); | |
| 116 repository = new GitRepository(repositoryPath); | |
| 117 | |
| 118 analysisRoots = arguments | |
| 119 .sublist(1) | |
| 120 .map((String analysisRoot) => path.normalize(analysisRoot)) | |
| 121 .toList(); | |
| 122 for (String analysisRoot in analysisRoots) { | |
| 123 if (repositoryPath != analysisRoot && | |
| 124 !path.isWithin(repositoryPath, analysisRoot)) { | |
| 125 _showUsage(parser, | |
| 126 'Analysis roots must be contained within the repository: $analysisRo ot'); | |
| 127 return null; | |
| 128 } | |
| 129 } | |
| 130 // | 126 // |
| 131 // Replay the commit history. | 127 // Simulate interactions with the server. |
| 132 // | 128 // |
| 133 Stopwatch stopwatch = new Stopwatch(); | 129 await _runSimulation(); |
| 134 statistics.stopwatch = stopwatch; | |
| 135 stopwatch.start(); | |
| 136 await server.start(); | |
| 137 server.sendServerSetSubscriptions([ServerService.STATUS]); | |
| 138 server.sendAnalysisSetGeneralSubscriptions( | |
| 139 [GeneralAnalysisService.ANALYZED_FILES]); | |
| 140 // TODO(brianwilkerson) Get the list of glob patterns from the server after | |
| 141 // an API for getting them has been implemented. | |
| 142 fileGlobs = <Glob>[ | |
| 143 new Glob(path.context.separator, '**.dart'), | |
| 144 new Glob(path.context.separator, '**.html'), | |
| 145 new Glob(path.context.separator, '**.htm'), | |
| 146 new Glob(path.context.separator, '**/.analysisOptions') | |
| 147 ]; | |
| 148 try { | |
| 149 _replayChanges(); | |
| 150 } finally { | |
| 151 server.sendServerShutdown(); | |
| 152 repository.checkout('master'); | |
| 153 } | |
| 154 stopwatch.stop(); | |
| 155 // | 130 // |
| 156 // Print out statistics gathered while performing the simulation. | 131 // Print out statistics gathered while performing the simulation. |
| 157 // | 132 // |
| 158 statistics.print(); | 133 statistics.print(); |
| 134 exit(0); | |
| 159 return null; | 135 return null; |
| 160 } | 136 } |
| 161 | 137 |
| 162 /** | 138 /** |
| 163 * Create and return a parser that can be used to parse the command-line | 139 * Create and return a parser that can be used to parse the command-line |
| 164 * arguments. | 140 * arguments. |
| 165 */ | 141 */ |
| 166 ArgParser _createArgParser() { | 142 ArgParser _createArgParser() { |
| 167 ArgParser parser = new ArgParser(); | 143 ArgParser parser = new ArgParser(); |
| 168 parser.addFlag(HELP_FLAG_NAME, | 144 parser.addFlag(HELP_FLAG_NAME, |
| 169 abbr: 'h', | 145 abbr: 'h', |
| 170 help: 'Print usage information', | 146 help: 'Print usage information', |
| 171 defaultsTo: false, | 147 defaultsTo: false, |
| 172 negatable: false); | 148 negatable: false); |
| 149 | |
| 150 parser.addOption(OVERLAY_STYLE_OPTION_NAME, | |
| 151 help: | |
| 152 'The style of interaction to use for analysis.updateContent requests ', | |
| 153 allowed: [CHANGE_OVERLAY_STYLE, MULTIPLE_ADD_OVERLAY_STYLE], | |
| 154 allowedHelp: { | |
| 155 CHANGE_OVERLAY_STYLE: '<add> <change>* <remove>', | |
| 156 MULTIPLE_ADD_OVERLAY_STYLE: '<add>+ <remove>' | |
| 157 }, | |
| 158 defaultsTo: 'change'); | |
| 173 return parser; | 159 return parser; |
| 174 } | 160 } |
| 175 | 161 |
| 162 /** | |
| 163 * Add source edits to the given [fileEdit] based on the given [blobDiff]. | |
| 164 */ | |
| 176 void _createSourceEdits(FileEdit fileEdit, BlobDiff blobDiff) { | 165 void _createSourceEdits(FileEdit fileEdit, BlobDiff blobDiff) { |
| 177 LineInfo info = fileEdit.lineInfo; | 166 LineInfo info = fileEdit.lineInfo; |
| 178 for (DiffHunk hunk in blobDiff.hunks) { | 167 for (DiffHunk hunk in blobDiff.hunks) { |
| 179 List<SourceEdit> sourceEdits = <SourceEdit>[]; | |
| 180 int srcStart = info.getOffsetOfLine(hunk.srcLine); | 168 int srcStart = info.getOffsetOfLine(hunk.srcLine); |
| 181 int srcEnd = info.getOffsetOfLine(hunk.srcLine + hunk.removeLines.length); | 169 int srcEnd = info.getOffsetOfLine(hunk.srcLine + hunk.removeLines.length); |
| 182 // TODO(brianwilkerson) Create multiple edits instead of a single edit. | 170 String addedText = _join(hunk.addLines); |
| 183 sourceEdits.add(new SourceEdit( | 171 // |
| 184 srcStart, srcEnd - srcStart + 1, _join(hunk.addLines))); | 172 // Create the source edits. |
| 173 // | |
| 174 List<int> breakOffsets = _getBreakOffsets(addedText); | |
| 175 int breakCount = breakOffsets.length; | |
| 176 List<SourceEdit> sourceEdits = <SourceEdit>[]; | |
| 177 if (breakCount == 0) { | |
| 178 sourceEdits | |
| 179 .add(new SourceEdit(srcStart, srcEnd - srcStart + 1, addedText)); | |
| 180 } else { | |
| 181 int previousOffset = breakOffsets[0]; | |
| 182 String string = addedText.substring(0, previousOffset); | |
| 183 sourceEdits | |
| 184 .add(new SourceEdit(srcStart, srcEnd - srcStart + 1, string)); | |
| 185 String reconstruction = string; | |
| 186 for (int i = 1; i < breakCount; i++) { | |
| 187 int offset = breakOffsets[i]; | |
| 188 string = addedText.substring(previousOffset, offset); | |
| 189 reconstruction += string; | |
| 190 sourceEdits.add(new SourceEdit(srcStart + previousOffset, 0, string)); | |
| 191 previousOffset = offset; | |
| 192 } | |
| 193 string = addedText.substring(previousOffset); | |
| 194 reconstruction += string; | |
| 195 sourceEdits.add(new SourceEdit(srcStart + previousOffset, 0, string)); | |
| 196 if (reconstruction != addedText) { | |
| 197 throw new AssertionError(); | |
| 198 } | |
| 199 } | |
| 185 fileEdit.addSourceEdits(sourceEdits); | 200 fileEdit.addSourceEdits(sourceEdits); |
| 186 } | 201 } |
| 187 } | 202 } |
| 188 | 203 |
| 189 /** | 204 /** |
| 190 * Return athe absolute paths of all of the pubspec files in all of the | 205 * Return the absolute paths of all of the pubspec files in all of the |
| 191 * analysis roots. | 206 * analysis roots. |
| 192 */ | 207 */ |
| 193 Iterable<String> _findPubspecsInAnalysisRoots() { | 208 Iterable<String> _findPubspecsInAnalysisRoots() { |
| 194 List<String> pubspecFiles = <String>[]; | 209 List<String> pubspecFiles = <String>[]; |
| 195 for (String directoryPath in analysisRoots) { | 210 for (String directoryPath in analysisRoots) { |
| 196 Directory directory = new Directory(directoryPath); | 211 Directory directory = new Directory(directoryPath); |
| 197 List<FileSystemEntity> children = | 212 List<FileSystemEntity> children = |
| 198 directory.listSync(recursive: true, followLinks: false); | 213 directory.listSync(recursive: true, followLinks: false); |
| 199 for (FileSystemEntity child in children) { | 214 for (FileSystemEntity child in children) { |
| 200 String filePath = child.path; | 215 String filePath = child.path; |
| 201 if (path.basename(filePath) == PUBSPEC_FILE_NAME) { | 216 if (path.basename(filePath) == PUBSPEC_FILE_NAME) { |
| 202 pubspecFiles.add(filePath); | 217 pubspecFiles.add(filePath); |
| 203 } | 218 } |
| 204 } | 219 } |
| 205 } | 220 } |
| 206 return pubspecFiles; | 221 return pubspecFiles; |
| 207 } | 222 } |
| 208 | 223 |
| 224 /** | |
| 225 * Return a list of offsets into the given [text] that represent good places | |
| 226 * to break the text when building edits. | |
| 227 */ | |
| 228 List<int> _getBreakOffsets(String text) { | |
|
skybrian
2015/12/12 02:01:20
It seems like we could simulate typing one charact
Brian Wilkerson
2015/12/12 15:28:23
We're trying to simulate how the client (editor) w
| |
| 229 List<int> breakOffsets = <int>[]; | |
| 230 Scanner scanner = new Scanner(null, new CharSequenceReader(text), | |
| 231 error.AnalysisErrorListener.NULL_LISTENER); | |
| 232 Token token = scanner.tokenize(); | |
| 233 // TODO(brianwilkerson) Randomize. Sometimes add zero (0) as a break point. | |
| 234 while (token.type != TokenType.EOF) { | |
| 235 // TODO(brianwilkerson) Break inside comments? | |
| 236 // Token comment = token.precedingComments; | |
| 237 int offset = token.offset; | |
| 238 int length = token.length; | |
| 239 breakOffsets.add(offset); | |
| 240 if (token.type == TokenType.IDENTIFIER && length > 3) { | |
| 241 breakOffsets.add(offset + (length ~/ 2)); | |
| 242 } | |
| 243 token = token.next; | |
| 244 } | |
| 245 return breakOffsets; | |
| 246 } | |
| 247 | |
| 248 /** | |
| 249 * Join the given [lines] into a single string. | |
| 250 */ | |
| 209 String _join(List<String> lines) { | 251 String _join(List<String> lines) { |
| 210 StringBuffer buffer = new StringBuffer(); | 252 StringBuffer buffer = new StringBuffer(); |
| 211 for (int i = 0; i < lines.length; i++) { | 253 for (int i = 0; i < lines.length; i++) { |
| 212 buffer.writeln(lines[i]); | 254 buffer.writeln(lines[i]); |
| 213 } | 255 } |
| 214 return buffer.toString(); | 256 return buffer.toString(); |
| 215 } | 257 } |
| 216 | 258 |
| 217 /** | 259 /** |
| 260 * Process the command-line [arguments]. Return `true` if the simulation | |
| 261 * should be run. | |
| 262 */ | |
| 263 bool _processCommandLine(List<String> args) { | |
| 264 ArgParser parser = _createArgParser(); | |
| 265 ArgResults results; | |
| 266 try { | |
| 267 results = parser.parse(args); | |
| 268 } catch (exception) { | |
| 269 _showUsage(parser); | |
| 270 return false; | |
| 271 } | |
| 272 | |
| 273 if (results[HELP_FLAG_NAME]) { | |
| 274 _showUsage(parser); | |
| 275 return false; | |
| 276 } | |
| 277 | |
| 278 String overlayStyleValue = results[OVERLAY_STYLE_OPTION_NAME]; | |
| 279 if (overlayStyleValue == CHANGE_OVERLAY_STYLE) { | |
| 280 overlayStyle = OverlayStyle.change; | |
| 281 } else if (overlayStyleValue == MULTIPLE_ADD_OVERLAY_STYLE) { | |
| 282 overlayStyle = OverlayStyle.multipleAdd; | |
| 283 } | |
| 284 | |
| 285 List<String> arguments = results.arguments; | |
| 286 if (arguments.length < 2) { | |
| 287 _showUsage(parser); | |
| 288 return false; | |
| 289 } | |
| 290 repositoryPath = path.normalize(arguments[0]); | |
| 291 repository = new GitRepository(repositoryPath); | |
| 292 | |
| 293 analysisRoots = arguments | |
| 294 .sublist(1) | |
| 295 .map((String analysisRoot) => path.normalize(analysisRoot)) | |
| 296 .toList(); | |
| 297 for (String analysisRoot in analysisRoots) { | |
| 298 if (repositoryPath != analysisRoot && | |
| 299 !path.isWithin(repositoryPath, analysisRoot)) { | |
| 300 _showUsage(parser, | |
| 301 'Analysis roots must be contained within the repository: $analysisRo ot'); | |
| 302 return false; | |
| 303 } | |
| 304 } | |
| 305 return true; | |
| 306 } | |
| 307 | |
| 308 /** | |
| 218 * Replay the changes in each commit. | 309 * Replay the changes in each commit. |
| 219 */ | 310 */ |
| 220 void _replayChanges() { | 311 Future _replayChanges() async { |
| 221 // | 312 // |
| 222 // Get the revision history of the repo. | 313 // Get the revision history of the repo. |
| 223 // | 314 // |
| 224 LinearCommitHistory history = repository.getCommitHistory(); | 315 LinearCommitHistory history = repository.getCommitHistory(); |
| 225 statistics.commitCount = history.commitIds.length; | 316 statistics.commitCount = history.commitIds.length; |
| 226 LinearCommitHistoryIterator iterator = history.iterator(); | 317 LinearCommitHistoryIterator iterator = history.iterator(); |
| 227 // | 318 // |
| 228 // Iterate over the history, applying changes. | 319 // Iterate over the history, applying changes. |
| 229 // | 320 // |
| 230 bool firstCheckout = true; | 321 bool firstCheckout = true; |
| 231 // Map<String, List<AnalysisError>> expectedErrors = null; | 322 ErrorMap expectedErrors = null; |
| 232 Iterable<String> changedPubspecs; | 323 Iterable<String> changedPubspecs; |
| 233 while (iterator.moveNext()) { | 324 while (iterator.moveNext()) { |
| 234 // | 325 // |
| 235 // Checkout the commit on which the changes are based. | 326 // Checkout the commit on which the changes are based. |
| 236 // | 327 // |
| 237 repository.checkout(iterator.srcCommit); | 328 String commit = iterator.srcCommit; |
| 238 // if (expectedErrors != null) { | 329 repository.checkout(commit); |
| 239 // await server.analysisFinished; | 330 if (expectedErrors != null) { |
| 240 // server.expectErrorState(expectedErrors); | 331 ErrorMap actualErrors = |
| 241 // } | 332 await server.computeErrorMap(server.analyzedDartFiles); |
| 333 String difference = expectedErrors.expectErrorMap(actualErrors); | |
| 334 if (difference != null) { | |
| 335 stdout.write('Mismatched errors after commit '); | |
| 336 stdout.writeln(commit); | |
| 337 stdout.writeln(); | |
| 338 stdout.writeln(difference); | |
| 339 return; | |
| 340 } | |
| 341 } | |
| 242 if (firstCheckout) { | 342 if (firstCheckout) { |
| 243 changedPubspecs = _findPubspecsInAnalysisRoots(); | 343 changedPubspecs = _findPubspecsInAnalysisRoots(); |
| 244 server.sendAnalysisSetAnalysisRoots(analysisRoots, []); | 344 server.sendAnalysisSetAnalysisRoots(analysisRoots, []); |
| 245 firstCheckout = false; | 345 firstCheckout = false; |
| 246 } else { | 346 } else { |
| 247 server.removeAllOverlays(); | 347 server.removeAllOverlays(); |
| 248 } | 348 } |
| 249 // await server.analysisFinished; | 349 expectedErrors = await server.computeErrorMap(server.analyzedDartFiles); |
| 250 // expectedErrors = server.errorMap; | |
| 251 for (String filePath in changedPubspecs) { | 350 for (String filePath in changedPubspecs) { |
| 252 _runPub(filePath); | 351 _runPub(filePath); |
| 253 } | 352 } |
| 254 // | 353 // |
| 255 // Apply the changes. | 354 // Apply the changes. |
| 256 // | 355 // |
| 257 CommitDelta commitDelta = iterator.next(); | 356 CommitDelta commitDelta = iterator.next(); |
| 258 commitDelta.filterDiffs(analysisRoots, fileGlobs); | 357 commitDelta.filterDiffs(analysisRoots, fileGlobs); |
| 259 if (commitDelta.hasDiffs) { | 358 if (commitDelta.hasDiffs) { |
| 260 statistics.commitsWithChangeInRootCount++; | 359 statistics.commitsWithChangeInRootCount++; |
| 261 _replayDiff(commitDelta); | 360 _replayDiff(commitDelta); |
| 262 } | 361 } |
| 263 changedPubspecs = commitDelta.filesMatching(PUBSPEC_FILE_NAME); | 362 changedPubspecs = commitDelta.filesMatching(PUBSPEC_FILE_NAME); |
| 363 stdout.write('.'); | |
| 264 } | 364 } |
| 265 server.removeAllOverlays(); | 365 server.removeAllOverlays(); |
| 366 stdout.writeln(); | |
| 266 } | 367 } |
| 267 | 368 |
| 369 /** | |
| 370 * Replay the changes between two commits, as represented by the given | |
| 371 * [commitDelta]. | |
| 372 */ | |
| 268 void _replayDiff(CommitDelta commitDelta) { | 373 void _replayDiff(CommitDelta commitDelta) { |
| 269 List<FileEdit> editList = <FileEdit>[]; | 374 List<FileEdit> editList = <FileEdit>[]; |
| 270 for (DiffRecord record in commitDelta.diffRecords) { | 375 for (DiffRecord record in commitDelta.diffRecords) { |
| 271 FileEdit edit = new FileEdit(record); | 376 FileEdit edit = new FileEdit(overlayStyle, record); |
| 272 _createSourceEdits(edit, record.getBlobDiff()); | 377 _createSourceEdits(edit, record.getBlobDiff()); |
| 273 editList.add(edit); | 378 editList.add(edit); |
| 274 } | 379 } |
| 380 // | |
| 275 // TODO(brianwilkerson) Randomize. | 381 // TODO(brianwilkerson) Randomize. |
| 276 // Randomly select operations from different files to simulate a user | 382 // Randomly select operations from different files to simulate a user |
| 277 // editing multiple files simultaneously. | 383 // editing multiple files simultaneously. |
| 384 // | |
| 278 for (FileEdit edit in editList) { | 385 for (FileEdit edit in editList) { |
| 279 List<String> currentFile = <String>[edit.filePath]; | 386 List<String> currentFile = <String>[edit.filePath]; |
| 280 server.sendAnalysisSetPriorityFiles(currentFile); | 387 server.sendAnalysisSetPriorityFiles(currentFile); |
| 281 server.sendAnalysisSetSubscriptions({ | 388 server.sendAnalysisSetSubscriptions({ |
| 282 AnalysisService.FOLDING: currentFile, | 389 AnalysisService.FOLDING: currentFile, |
| 283 AnalysisService.HIGHLIGHTS: currentFile, | 390 AnalysisService.HIGHLIGHTS: currentFile, |
| 284 AnalysisService.IMPLEMENTED: currentFile, | 391 AnalysisService.IMPLEMENTED: currentFile, |
| 285 AnalysisService.NAVIGATION: currentFile, | 392 AnalysisService.NAVIGATION: currentFile, |
| 286 AnalysisService.OCCURRENCES: currentFile, | 393 AnalysisService.OCCURRENCES: currentFile, |
| 287 AnalysisService.OUTLINE: currentFile, | 394 AnalysisService.OUTLINE: currentFile, |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 299 void _runPub(String filePath) { | 406 void _runPub(String filePath) { |
| 300 String directoryPath = path.dirname(filePath); | 407 String directoryPath = path.dirname(filePath); |
| 301 if (new Directory(directoryPath).existsSync()) { | 408 if (new Directory(directoryPath).existsSync()) { |
| 302 Process.runSync( | 409 Process.runSync( |
| 303 '/Users/brianwilkerson/Dev/dart/dart-sdk/bin/pub', ['get'], | 410 '/Users/brianwilkerson/Dev/dart/dart-sdk/bin/pub', ['get'], |
| 304 workingDirectory: directoryPath); | 411 workingDirectory: directoryPath); |
| 305 } | 412 } |
| 306 } | 413 } |
| 307 | 414 |
| 308 /** | 415 /** |
| 416 * Run the simulation by starting up a server and sending it requests. | |
| 417 */ | |
| 418 Future _runSimulation() async { | |
| 419 Stopwatch stopwatch = new Stopwatch(); | |
| 420 statistics.stopwatch = stopwatch; | |
| 421 stopwatch.start(); | |
| 422 await server.start(); | |
| 423 server.sendServerSetSubscriptions([ServerService.STATUS]); | |
| 424 server.sendAnalysisSetGeneralSubscriptions( | |
| 425 [GeneralAnalysisService.ANALYZED_FILES]); | |
| 426 // TODO(brianwilkerson) Get the list of glob patterns from the server after | |
| 427 // an API for getting them has been implemented. | |
| 428 fileGlobs = <Glob>[ | |
| 429 new Glob(path.context.separator, '**.dart'), | |
| 430 new Glob(path.context.separator, '**.html'), | |
| 431 new Glob(path.context.separator, '**.htm'), | |
| 432 new Glob(path.context.separator, '**/.analysisOptions') | |
| 433 ]; | |
| 434 try { | |
| 435 await _replayChanges(); | |
| 436 } finally { | |
| 437 server.sendServerShutdown(); | |
| 438 repository.checkout('master'); | |
| 439 } | |
| 440 stopwatch.stop(); | |
| 441 } | |
| 442 | |
| 443 /** | |
| 309 * Display usage information, preceeded by the [errorMessage] if one is given. | 444 * Display usage information, preceeded by the [errorMessage] if one is given. |
| 310 */ | 445 */ |
| 311 void _showUsage(ArgParser parser, [String errorMessage = null]) { | 446 void _showUsage(ArgParser parser, [String errorMessage = null]) { |
| 312 if (errorMessage != null) { | 447 if (errorMessage != null) { |
| 313 stderr.writeln(errorMessage); | 448 stderr.writeln(errorMessage); |
| 314 stderr.writeln(); | 449 stderr.writeln(); |
| 315 } | 450 } |
| 316 stderr.writeln(''' | 451 stderr.writeln(''' |
| 317 Usage: replay [options...] repositoryPath analysisRoot... | 452 Usage: replay [options...] repositoryPath analysisRoot... |
| 318 | 453 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 331 OPTIONS:'''); | 466 OPTIONS:'''); |
| 332 stderr.writeln(parser.usage); | 467 stderr.writeln(parser.usage); |
| 333 } | 468 } |
| 334 } | 469 } |
| 335 | 470 |
| 336 /** | 471 /** |
| 337 * A representation of the edits to be applied to a single file. | 472 * A representation of the edits to be applied to a single file. |
| 338 */ | 473 */ |
| 339 class FileEdit { | 474 class FileEdit { |
| 340 /** | 475 /** |
| 476 * The style of interaction to use for analysis.updateContent requests. | |
| 477 */ | |
| 478 OverlayStyle overlayStyle; | |
| 479 | |
| 480 /** | |
| 341 * The absolute path of the file to be edited. | 481 * The absolute path of the file to be edited. |
| 342 */ | 482 */ |
| 343 String filePath; | 483 String filePath; |
| 344 | 484 |
| 345 /** | 485 /** |
| 346 * The content of the file before any edits have been applied. | 486 * The content of the file before any edits have been applied. |
| 347 */ | 487 */ |
| 348 String content; | 488 String content; |
| 349 | 489 |
| 350 /** | 490 /** |
| 351 * The line info for the file before any edits have been applied. | 491 * The line info for the file before any edits have been applied. |
| 352 */ | 492 */ |
| 353 LineInfo lineInfo; | 493 LineInfo lineInfo; |
| 354 | 494 |
| 355 /** | 495 /** |
| 356 * The lists of source edits, one list for each hunk being edited. | 496 * The lists of source edits, one list for each hunk being edited. |
| 357 */ | 497 */ |
| 358 List<List<SourceEdit>> editLists = <List<SourceEdit>>[]; | 498 List<List<SourceEdit>> editLists = <List<SourceEdit>>[]; |
| 359 | 499 |
| 360 /** | 500 /** |
| 501 * The current content of the file. This field is only used if the overlay | |
| 502 * style is [OverlayStyle.multipleAdd]. | |
| 503 */ | |
| 504 String currentContent; | |
| 505 | |
| 506 /** | |
| 361 * Initialize a collection of edits to be associated with the file at the | 507 * Initialize a collection of edits to be associated with the file at the |
| 362 * given [filePath]. | 508 * given [filePath]. |
| 363 */ | 509 */ |
| 364 FileEdit(DiffRecord record) { | 510 FileEdit(this.overlayStyle, DiffRecord record) { |
| 365 filePath = record.srcPath; | 511 filePath = record.srcPath; |
| 366 if (record.isAddition) { | 512 if (record.isAddition) { |
| 367 content = ''; | 513 content = ''; |
| 368 lineInfo = new LineInfo(<int>[0]); | 514 lineInfo = new LineInfo(<int>[0]); |
| 369 } else if (record.isCopy || record.isRename || record.isTypeChange) { | 515 } else if (record.isCopy || record.isRename || record.isTypeChange) { |
| 370 throw new ArgumentError('Unhandled change of type ${record.status}'); | 516 throw new ArgumentError('Unhandled change of type ${record.status}'); |
| 371 } else { | 517 } else { |
| 372 content = new File(filePath).readAsStringSync(); | 518 content = new File(filePath).readAsStringSync(); |
| 373 lineInfo = new LineInfo(StringUtilities.computeLineStarts(content)); | 519 lineInfo = new LineInfo(StringUtilities.computeLineStarts(content)); |
| 374 } | 520 } |
| 521 currentContent = content; | |
| 375 } | 522 } |
| 376 | 523 |
| 377 /** | 524 /** |
| 378 * Add a list of source edits that, taken together, transform a single hunk in | 525 * Add a list of source edits that, taken together, transform a single hunk in |
| 379 * the file. | 526 * the file. |
| 380 */ | 527 */ |
| 381 void addSourceEdits(List<SourceEdit> sourceEdits) { | 528 void addSourceEdits(List<SourceEdit> sourceEdits) { |
| 382 editLists.add(sourceEdits); | 529 editLists.add(sourceEdits); |
| 383 } | 530 } |
| 384 | 531 |
| 385 /** | 532 /** |
| 386 * Return a list of operations to be sent to the server. | 533 * Return a list of operations to be sent to the server. |
| 387 */ | 534 */ |
| 388 List<ServerOperation> getOperations() { | 535 List<ServerOperation> getOperations() { |
| 536 List<ServerOperation> operations = <ServerOperation>[]; | |
| 537 void addUpdateContent(var overlay) { | |
| 538 operations.add(new Analysis_UpdateContent(filePath, overlay)); | |
| 539 } | |
| 540 | |
| 389 // TODO(brianwilkerson) Randomize. | 541 // TODO(brianwilkerson) Randomize. |
| 390 // Make the order of edits random. Doing so will require updating the | 542 // Make the order of edits random. Doing so will require updating the |
| 391 // offsets of edits after the selected edit point. | 543 // offsets of edits after the selected edit point. |
| 392 List<ServerOperation> operations = <ServerOperation>[]; | 544 addUpdateContent(new AddContentOverlay(content)); |
| 393 operations.add( | |
| 394 new AnalysisUpdateContent(filePath, new AddContentOverlay(content))); | |
| 395 for (List<SourceEdit> editList in editLists.reversed) { | 545 for (List<SourceEdit> editList in editLists.reversed) { |
| 396 for (SourceEdit edit in editList.reversed) { | 546 for (SourceEdit edit in editList.reversed) { |
| 397 operations.add(new AnalysisUpdateContent( | 547 var overlay = null; |
| 398 filePath, new ChangeContentOverlay([edit]))); | 548 if (overlayStyle == OverlayStyle.change) { |
| 549 overlay = new ChangeContentOverlay([edit]); | |
| 550 } else if (overlayStyle == OverlayStyle.multipleAdd) { | |
| 551 currentContent = edit.apply(currentContent); | |
| 552 overlay = new AddContentOverlay(currentContent); | |
| 553 } else { | |
| 554 throw new StateError( | |
| 555 'Failed to handle overlay style = $overlayStyle'); | |
| 556 } | |
| 557 if (overlay != null) { | |
| 558 addUpdateContent(overlay); | |
| 559 } | |
| 399 } | 560 } |
| 400 } | 561 } |
| 401 operations | 562 addUpdateContent(new RemoveContentOverlay()); |
| 402 .add(new AnalysisUpdateContent(filePath, new RemoveContentOverlay())); | |
| 403 return operations; | 563 return operations; |
| 404 } | 564 } |
| 405 } | 565 } |
| 406 | 566 |
| 407 /** | 567 /** |
| 568 * The possible styles of interaction to use for analysis.updateContent requests . | |
| 569 */ | |
| 570 enum OverlayStyle { change, multipleAdd } | |
| 571 | |
| 572 /** | |
| 408 * A set of statistics related to the execution of the simulation. | 573 * A set of statistics related to the execution of the simulation. |
| 409 */ | 574 */ |
| 410 class Statistics { | 575 class Statistics { |
| 411 /** | 576 /** |
| 412 * The driver driving the simulation. | 577 * The driver driving the simulation. |
| 413 */ | 578 */ |
| 414 final Driver driver; | 579 final Driver driver; |
| 415 | 580 |
| 416 /** | 581 /** |
| 417 * The stopwatch being used to time the simulation. | 582 * The stopwatch being used to time the simulation. |
| 418 */ | 583 */ |
| 419 Stopwatch stopwatch; | 584 Stopwatch stopwatch; |
| 420 | 585 |
| 421 /** | 586 /** |
| 422 * The total number of commits in the repository. | 587 * The total number of commits in the repository. |
| 423 */ | 588 */ |
| 424 int commitCount; | 589 int commitCount; |
| 425 | 590 |
| 426 /** | 591 /** |
| 427 * The number of commits in the repository that touched one of the files in | 592 * The number of commits in the repository that touched one of the files in |
| 428 * one of the analysis roots. | 593 * one of the analysis roots. |
| 429 */ | 594 */ |
| 430 int commitsWithChangeInRootCount = 0; | 595 int commitsWithChangeInRootCount = 0; |
| 431 | 596 |
| 432 /** | 597 /** |
| 433 * Initialize a newly created set of statistics. | 598 * Initialize a newly created set of statistics. |
| 434 */ | 599 */ |
| 435 Statistics(this.driver); | 600 Statistics(this.driver); |
| 436 | 601 |
| 602 /** | |
| 603 * Print the statistics to [stdout]. | |
| 604 */ | |
| 437 void print() { | 605 void print() { |
| 438 stdout.write('Replay commits in '); | 606 stdout.write('Replay commits in '); |
| 439 stdout.writeln(driver.repositoryPath); | 607 stdout.writeln(driver.repositoryPath); |
| 440 stdout.write(' replay took '); | 608 stdout.write(' replay took '); |
| 441 stdout.writeln(_printTime(stopwatch.elapsedMilliseconds)); | 609 stdout.writeln(_printTime(stopwatch.elapsedMilliseconds)); |
| 442 stdout.write(' analysis roots = '); | 610 stdout.write(' analysis roots = '); |
| 443 stdout.writeln(driver.analysisRoots); | 611 stdout.writeln(driver.analysisRoots); |
| 444 stdout.write(' number of commits = '); | 612 stdout.write(' number of commits = '); |
| 445 stdout.writeln(commitCount); | 613 stdout.writeln(commitCount); |
| 446 stdout.write(' number of commits with a change in an analysis root = '); | 614 stdout.write(' number of commits with a change in an analysis root = '); |
| 447 stdout.writeln(commitsWithChangeInRootCount); | 615 stdout.writeln(commitsWithChangeInRootCount); |
| 448 } | 616 } |
| 449 | 617 |
| 618 /** | |
| 619 * Return a textual representation of the given duration, represented in | |
| 620 * [milliseconds]. | |
| 621 */ | |
| 450 String _printTime(int milliseconds) { | 622 String _printTime(int milliseconds) { |
| 451 int seconds = milliseconds ~/ 1000; | 623 int seconds = milliseconds ~/ 1000; |
| 452 milliseconds -= seconds * 1000; | 624 milliseconds -= seconds * 1000; |
| 453 int minutes = seconds ~/ 60; | 625 int minutes = seconds ~/ 60; |
| 454 seconds -= minutes * 60; | 626 seconds -= minutes * 60; |
| 455 int hours = minutes ~/ 60; | 627 int hours = minutes ~/ 60; |
| 456 minutes -= hours * 60; | 628 minutes -= hours * 60; |
| 457 | 629 |
| 458 if (hours > 0) { | 630 if (hours > 0) { |
| 459 return '$hours:$minutes:$seconds.$milliseconds'; | 631 return '$hours:$minutes:$seconds.$milliseconds'; |
| 460 } else if (minutes > 0) { | 632 } else if (minutes > 0) { |
| 461 return '$minutes:$seconds.$milliseconds m'; | 633 return '$minutes:$seconds.$milliseconds m'; |
| 462 } else if (seconds > 0) { | 634 } else if (seconds > 0) { |
| 463 return '$seconds.$milliseconds s'; | 635 return '$seconds.$milliseconds s'; |
| 464 } | 636 } |
| 465 return '$milliseconds ms'; | 637 return '$milliseconds ms'; |
| 466 } | 638 } |
| 467 } | 639 } |
| OLD | NEW |