| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2016, 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 library analyzer.test.stress.limited_invalidation; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:io'; | |
| 9 | |
| 10 import 'package:analyzer/dart/ast/ast.dart'; | |
| 11 import 'package:analyzer/dart/element/element.dart'; | |
| 12 import 'package:analyzer/dart/element/type.dart'; | |
| 13 import 'package:analyzer/error/error.dart'; | |
| 14 import 'package:analyzer/file_system/file_system.dart' as fs; | |
| 15 import 'package:analyzer/file_system/physical_file_system.dart'; | |
| 16 import 'package:analyzer/src/context/builder.dart'; | |
| 17 import 'package:analyzer/src/context/cache.dart'; | |
| 18 import 'package:analyzer/src/context/context.dart'; | |
| 19 import 'package:analyzer/src/dart/ast/utilities.dart'; | |
| 20 import 'package:analyzer/src/dart/element/member.dart'; | |
| 21 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| 22 import 'package:analyzer/src/generated/engine.dart'; | |
| 23 import 'package:analyzer/src/generated/sdk.dart'; | |
| 24 import 'package:analyzer/src/generated/source.dart'; | |
| 25 import 'package:analyzer/src/generated/utilities_collection.dart'; | |
| 26 import 'package:analyzer/src/task/dart.dart'; | |
| 27 import 'package:analyzer/task/general.dart'; | |
| 28 import 'package:analyzer/task/model.dart'; | |
| 29 import 'package:path/path.dart' as path; | |
| 30 import 'package:test/test.dart'; | |
| 31 | |
| 32 main() { | |
| 33 new StressTest().run(); | |
| 34 } | |
| 35 | |
| 36 void _failTypeMismatch(Object actual, Object expected, {String reason}) { | |
| 37 String message = 'Actual $actual is ${actual.runtimeType}, ' | |
| 38 'but expected $expected is ${expected.runtimeType}'; | |
| 39 if (reason != null) { | |
| 40 message += ' $reason'; | |
| 41 } | |
| 42 fail(message); | |
| 43 } | |
| 44 | |
| 45 void _logPrint(String message) { | |
| 46 DateTime time = new DateTime.now(); | |
| 47 print('$time: $message'); | |
| 48 } | |
| 49 | |
| 50 class FileInfo { | |
| 51 final String path; | |
| 52 final int modification; | |
| 53 | |
| 54 FileInfo(this.path, this.modification); | |
| 55 } | |
| 56 | |
| 57 class FolderDiff { | |
| 58 final List<String> added; | |
| 59 final List<String> changed; | |
| 60 final List<String> removed; | |
| 61 | |
| 62 FolderDiff(this.added, this.changed, this.removed); | |
| 63 | |
| 64 bool get isEmpty => added.isEmpty && changed.isEmpty && removed.isEmpty; | |
| 65 bool get isNotEmpty => !isEmpty; | |
| 66 | |
| 67 @override | |
| 68 String toString() { | |
| 69 return '[added=$added, changed=$changed, removed=$removed]'; | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 class FolderInfo { | |
| 74 final String path; | |
| 75 final List<FileInfo> files = <FileInfo>[]; | |
| 76 | |
| 77 FolderInfo(this.path) { | |
| 78 List<FileSystemEntity> entities = | |
| 79 new Directory(path).listSync(recursive: true); | |
| 80 for (FileSystemEntity entity in entities) { | |
| 81 if (entity is File) { | |
| 82 String path = entity.path; | |
| 83 if (path.contains('packages') || path.contains('.pub')) { | |
| 84 continue; | |
| 85 } | |
| 86 if (path.endsWith('.dart')) { | |
| 87 files.add(new FileInfo( | |
| 88 path, entity.lastModifiedSync().millisecondsSinceEpoch)); | |
| 89 } | |
| 90 } | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 FolderDiff diff(FolderInfo oldFolder) { | |
| 95 Map<String, FileInfo> toMap(FolderInfo folder) { | |
| 96 Map<String, FileInfo> map = <String, FileInfo>{}; | |
| 97 folder.files.forEach((file) { | |
| 98 map[file.path] = file; | |
| 99 }); | |
| 100 return map; | |
| 101 } | |
| 102 | |
| 103 Map<String, FileInfo> newFiles = toMap(this); | |
| 104 Map<String, FileInfo> oldFiles = toMap(oldFolder); | |
| 105 Set<String> addedPaths = newFiles.keys.toSet()..removeAll(oldFiles.keys); | |
| 106 Set<String> removedPaths = oldFiles.keys.toSet()..removeAll(newFiles.keys); | |
| 107 List<String> changedPaths = <String>[]; | |
| 108 newFiles.forEach((path, newFile) { | |
| 109 FileInfo oldFile = oldFiles[path]; | |
| 110 if (oldFile != null && oldFile.modification != newFile.modification) { | |
| 111 changedPaths.add(path); | |
| 112 } | |
| 113 }); | |
| 114 return new FolderDiff( | |
| 115 addedPaths.toList(), changedPaths, removedPaths.toList()); | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 class GitException { | |
| 120 final String message; | |
| 121 final String stdout; | |
| 122 final String stderr; | |
| 123 | |
| 124 GitException(this.message) | |
| 125 : stdout = null, | |
| 126 stderr = null; | |
| 127 | |
| 128 GitException.forProcessResult(this.message, ProcessResult processResult) | |
| 129 : stdout = processResult.stdout, | |
| 130 stderr = processResult.stderr; | |
| 131 | |
| 132 @override | |
| 133 String toString() => '$message\n$stdout\n$stderr\n'; | |
| 134 } | |
| 135 | |
| 136 class GitRepository { | |
| 137 final String path; | |
| 138 | |
| 139 GitRepository(this.path); | |
| 140 | |
| 141 Future checkout(String hash) async { | |
| 142 // TODO(scheglov) use for updating only some files | |
| 143 if (hash.endsWith('hash')) { | |
| 144 List<String> filePaths = <String>[ | |
| 145 '/Users/user/full/path/one.dart', | |
| 146 '/Users/user/full/path/two.dart', | |
| 147 ]; | |
| 148 for (var filePath in filePaths) { | |
| 149 await Process.run('git', <String>['checkout', '-f', hash, filePath], | |
| 150 workingDirectory: path); | |
| 151 } | |
| 152 return; | |
| 153 } | |
| 154 ProcessResult processResult = await Process | |
| 155 .run('git', <String>['checkout', '-f', hash], workingDirectory: path); | |
| 156 _throwIfNotSuccess(processResult); | |
| 157 } | |
| 158 | |
| 159 Future<List<GitRevision>> getRevisions({String after}) async { | |
| 160 List<String> args = <String>['log', '--format=%ct %H %s']; | |
| 161 if (after != null) { | |
| 162 args.add('--after=$after'); | |
| 163 } | |
| 164 ProcessResult processResult = | |
| 165 await Process.run('git', args, workingDirectory: path); | |
| 166 _throwIfNotSuccess(processResult); | |
| 167 String output = processResult.stdout; | |
| 168 List<String> logLines = output.split('\n'); | |
| 169 List<GitRevision> revisions = <GitRevision>[]; | |
| 170 for (String logLine in logLines) { | |
| 171 int index1 = logLine.indexOf(' '); | |
| 172 if (index1 != -1) { | |
| 173 int index2 = logLine.indexOf(' ', index1 + 1); | |
| 174 if (index2 != -1) { | |
| 175 int timestamp = int.parse(logLine.substring(0, index1)); | |
| 176 String hash = logLine.substring(index1 + 1, index2); | |
| 177 String message = logLine.substring(index2).trim(); | |
| 178 revisions.add(new GitRevision(timestamp, hash, message)); | |
| 179 } | |
| 180 } | |
| 181 } | |
| 182 return revisions; | |
| 183 } | |
| 184 | |
| 185 void removeIndexLock() { | |
| 186 File file = new File('$path/.git/index.lock'); | |
| 187 if (file.existsSync()) { | |
| 188 file.deleteSync(); | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 Future resetHard() async { | |
| 193 ProcessResult processResult = await Process | |
| 194 .run('git', <String>['reset', '--hard'], workingDirectory: path); | |
| 195 _throwIfNotSuccess(processResult); | |
| 196 } | |
| 197 | |
| 198 void _throwIfNotSuccess(ProcessResult processResult) { | |
| 199 if (processResult.exitCode != 0) { | |
| 200 throw new GitException.forProcessResult( | |
| 201 'Unable to run "git log".', processResult); | |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 | |
| 206 class GitRevision { | |
| 207 final int timestamp; | |
| 208 final String hash; | |
| 209 final String message; | |
| 210 | |
| 211 GitRevision(this.timestamp, this.hash, this.message); | |
| 212 | |
| 213 @override | |
| 214 String toString() { | |
| 215 DateTime dateTime = | |
| 216 new DateTime.fromMillisecondsSinceEpoch(timestamp * 1000, isUtc: true) | |
| 217 .toLocal(); | |
| 218 return '$dateTime|$hash|$message|'; | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 class StressTest { | |
| 223 String repoPath = '/Users/scheglov/tmp/limited-invalidation/path'; | |
| 224 String folderPath = '/Users/scheglov/tmp/limited-invalidation/path'; | |
| 225 // String repoPath = '/Users/scheglov/tmp/limited-invalidation/async'; | |
| 226 // String folderPath = '/Users/scheglov/tmp/limited-invalidation/async'; | |
| 227 // String repoPath = '/Users/scheglov/tmp/limited-invalidation/sdk'; | |
| 228 // String folderPath = '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyz
er'; | |
| 229 | |
| 230 fs.ResourceProvider resourceProvider; | |
| 231 path.Context pathContext; | |
| 232 DartSdkManager sdkManager; | |
| 233 ContentCache contentCache; | |
| 234 | |
| 235 AnalysisContextImpl expectedContext; | |
| 236 AnalysisContextImpl actualContext; | |
| 237 | |
| 238 Set<Element> currentRevisionValidatedElements = new Set<Element>(); | |
| 239 | |
| 240 void createContexts() { | |
| 241 assert(expectedContext == null); | |
| 242 assert(actualContext == null); | |
| 243 resourceProvider = PhysicalResourceProvider.INSTANCE; | |
| 244 pathContext = resourceProvider.pathContext; | |
| 245 fs.Folder sdkDirectory = | |
| 246 FolderBasedDartSdk.defaultSdkDirectory(resourceProvider); | |
| 247 sdkManager = new DartSdkManager(sdkDirectory.path, false); | |
| 248 contentCache = new ContentCache(); | |
| 249 ContextBuilderOptions builderOptions = new ContextBuilderOptions(); | |
| 250 builderOptions.defaultOptions = new AnalysisOptionsImpl(); | |
| 251 ContextBuilder builder = new ContextBuilder( | |
| 252 resourceProvider, sdkManager, contentCache, | |
| 253 options: builderOptions); | |
| 254 expectedContext = builder.buildContext(folderPath); | |
| 255 actualContext = builder.buildContext(folderPath); | |
| 256 expectedContext.analysisOptions = | |
| 257 new AnalysisOptionsImpl.from(expectedContext.analysisOptions) | |
| 258 ..incremental = true; | |
| 259 actualContext.analysisOptions = | |
| 260 new AnalysisOptionsImpl.from(actualContext.analysisOptions) | |
| 261 ..incremental = true | |
| 262 ..finerGrainedInvalidation = true; | |
| 263 print('Created contexts'); | |
| 264 } | |
| 265 | |
| 266 run() async { | |
| 267 GitRepository repository = new GitRepository(repoPath); | |
| 268 | |
| 269 // Recover. | |
| 270 repository.removeIndexLock(); | |
| 271 await repository.resetHard(); | |
| 272 | |
| 273 await repository.checkout('master'); | |
| 274 List<GitRevision> revisions = | |
| 275 await repository.getRevisions(after: '2016-01-01'); | |
| 276 revisions = revisions.reversed.toList(); | |
| 277 // TODO(scheglov) Use to compare two revisions. | |
| 278 // List<GitRevision> revisions = [ | |
| 279 // new GitRevision(0, '99517a162cbabf3d3afbdb566df3fe2b18cd4877', 'aaa'), | |
| 280 // new GitRevision(0, '2ef00b0c3d0182b5e4ea5ca55fd00b9d038ae40d', 'bbb'), | |
| 281 // ]; | |
| 282 FolderInfo oldFolder = null; | |
| 283 for (GitRevision revision in revisions) { | |
| 284 print(revision); | |
| 285 await repository.checkout(revision.hash); | |
| 286 | |
| 287 // Run "pub get". | |
| 288 if (!new File('$folderPath/pubspec.yaml').existsSync()) { | |
| 289 continue; | |
| 290 } | |
| 291 { | |
| 292 ProcessResult processResult = await Process.run( | |
| 293 '/Users/scheglov/Applications/dart-sdk/bin/pub', <String>['get'], | |
| 294 workingDirectory: folderPath); | |
| 295 if (processResult.exitCode != 0) { | |
| 296 _logPrint('Pub get failed.'); | |
| 297 _logPrint(processResult.stdout); | |
| 298 _logPrint(processResult.stderr); | |
| 299 continue; | |
| 300 } | |
| 301 _logPrint('\tpub get OK'); | |
| 302 } | |
| 303 FolderInfo newFolder = new FolderInfo(folderPath); | |
| 304 | |
| 305 if (expectedContext == null) { | |
| 306 createContexts(); | |
| 307 _applyChanges( | |
| 308 newFolder.files.map((file) => file.path).toList(), [], []); | |
| 309 _analyzeContexts(); | |
| 310 } | |
| 311 | |
| 312 if (oldFolder != null) { | |
| 313 FolderDiff diff = newFolder.diff(oldFolder); | |
| 314 print(' $diff'); | |
| 315 if (diff.isNotEmpty) { | |
| 316 _applyChanges(diff.added, diff.changed, diff.removed); | |
| 317 _analyzeContexts(); | |
| 318 } | |
| 319 } | |
| 320 oldFolder = newFolder; | |
| 321 print('\n'); | |
| 322 print('\n'); | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 /** | |
| 327 * Perform analysis tasks up to 512 times and assert that it was enough. | |
| 328 */ | |
| 329 void _analyzeAll_assertFinished(AnalysisContext context, | |
| 330 [int maxIterations = 1000000]) { | |
| 331 for (int i = 0; i < maxIterations; i++) { | |
| 332 List<ChangeNotice> notice = context.performAnalysisTask().changeNotices; | |
| 333 if (notice == null) { | |
| 334 return; | |
| 335 } | |
| 336 } | |
| 337 throw new StateError( | |
| 338 "performAnalysisTask failed to terminate after analyzing all sources"); | |
| 339 } | |
| 340 | |
| 341 void _analyzeContexts() { | |
| 342 { | |
| 343 Stopwatch sw = new Stopwatch()..start(); | |
| 344 _analyzeAll_assertFinished(expectedContext); | |
| 345 print(' analyze(expected): ${sw.elapsedMilliseconds}'); | |
| 346 } | |
| 347 { | |
| 348 Stopwatch sw = new Stopwatch()..start(); | |
| 349 _analyzeAll_assertFinished(actualContext); | |
| 350 print(' analyze(actual): ${sw.elapsedMilliseconds}'); | |
| 351 } | |
| 352 _validateContexts(); | |
| 353 } | |
| 354 | |
| 355 void _applyChanges( | |
| 356 List<String> added, List<String> changed, List<String> removed) { | |
| 357 ChangeSet changeSet = new ChangeSet(); | |
| 358 added.map(_pathToSource).forEach(changeSet.addedSource); | |
| 359 removed.map(_pathToSource).forEach(changeSet.removedSource); | |
| 360 changed.map(_pathToSource).forEach(changeSet.changedSource); | |
| 361 changed.forEach((path) => new File(path).readAsStringSync()); | |
| 362 { | |
| 363 Stopwatch sw = new Stopwatch()..start(); | |
| 364 expectedContext.applyChanges(changeSet); | |
| 365 print(' apply(expected): ${sw.elapsedMilliseconds}'); | |
| 366 } | |
| 367 { | |
| 368 Stopwatch sw = new Stopwatch()..start(); | |
| 369 actualContext.applyChanges(changeSet); | |
| 370 print(' apply(actual): ${sw.elapsedMilliseconds}'); | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 Source _pathToSource(String path) { | |
| 375 fs.File file = resourceProvider.getFile(path); | |
| 376 return _createSourceInContext(expectedContext, file); | |
| 377 } | |
| 378 | |
| 379 void _validateContexts() { | |
| 380 currentRevisionValidatedElements.clear(); | |
| 381 MapIterator<AnalysisTarget, CacheEntry> iterator = | |
| 382 expectedContext.privateAnalysisCachePartition.iterator(); | |
| 383 while (iterator.moveNext()) { | |
| 384 AnalysisTarget target = iterator.key; | |
| 385 CacheEntry entry = iterator.value; | |
| 386 if (target is NonExistingSource) { | |
| 387 continue; | |
| 388 } | |
| 389 _validateEntry(target, entry); | |
| 390 } | |
| 391 } | |
| 392 | |
| 393 void _validateElements( | |
| 394 Element actualValue, Element expectedValue, Set visited) { | |
| 395 if (actualValue == null && expectedValue == null) { | |
| 396 return; | |
| 397 } | |
| 398 if (!currentRevisionValidatedElements.add(expectedValue)) { | |
| 399 return; | |
| 400 } | |
| 401 if (!visited.add(expectedValue)) { | |
| 402 return; | |
| 403 } | |
| 404 List<Element> sortElements(List<Element> elements) { | |
| 405 elements = elements.toList(); | |
| 406 elements.sort((a, b) { | |
| 407 if (a.nameOffset != b.nameOffset) { | |
| 408 return a.nameOffset - b.nameOffset; | |
| 409 } | |
| 410 return a.name.compareTo(b.name); | |
| 411 }); | |
| 412 return elements; | |
| 413 } | |
| 414 | |
| 415 void validateSortedElements( | |
| 416 List<Element> actualElements, List<Element> expectedElements) { | |
| 417 expect(actualElements, hasLength(expectedElements.length)); | |
| 418 actualElements = sortElements(actualElements); | |
| 419 expectedElements = sortElements(expectedElements); | |
| 420 for (int i = 0; i < expectedElements.length; i++) { | |
| 421 _validateElements(actualElements[i], expectedElements[i], visited); | |
| 422 } | |
| 423 } | |
| 424 | |
| 425 expect(actualValue?.runtimeType, expectedValue?.runtimeType); | |
| 426 expect(actualValue.nameOffset, expectedValue.nameOffset); | |
| 427 expect(actualValue.name, expectedValue.name); | |
| 428 if (expectedValue is ClassElement) { | |
| 429 var actualElement = actualValue as ClassElement; | |
| 430 validateSortedElements(actualElement.accessors, expectedValue.accessors); | |
| 431 validateSortedElements( | |
| 432 actualElement.constructors, expectedValue.constructors); | |
| 433 validateSortedElements(actualElement.fields, expectedValue.fields); | |
| 434 validateSortedElements(actualElement.methods, expectedValue.methods); | |
| 435 } | |
| 436 if (expectedValue is CompilationUnitElement) { | |
| 437 var actualElement = actualValue as CompilationUnitElement; | |
| 438 validateSortedElements(actualElement.accessors, expectedValue.accessors); | |
| 439 validateSortedElements(actualElement.functions, expectedValue.functions); | |
| 440 validateSortedElements(actualElement.types, expectedValue.types); | |
| 441 validateSortedElements( | |
| 442 actualElement.functionTypeAliases, expectedValue.functionTypeAliases); | |
| 443 validateSortedElements( | |
| 444 actualElement.topLevelVariables, expectedValue.topLevelVariables); | |
| 445 } | |
| 446 if (expectedValue is ExecutableElement) { | |
| 447 var actualElement = actualValue as ExecutableElement; | |
| 448 validateSortedElements( | |
| 449 actualElement.parameters, expectedValue.parameters); | |
| 450 _validateTypes( | |
| 451 actualElement.returnType, expectedValue.returnType, visited); | |
| 452 } | |
| 453 } | |
| 454 | |
| 455 void _validateEntry(AnalysisTarget target, CacheEntry expectedEntry) { | |
| 456 CacheEntry actualEntry = | |
| 457 actualContext.privateAnalysisCachePartition.get(target); | |
| 458 if (actualEntry == null) { | |
| 459 return; | |
| 460 } | |
| 461 print(' (${target.runtimeType}) $target'); | |
| 462 for (ResultDescriptor result in expectedEntry.nonInvalidResults) { | |
| 463 var expectedData = expectedEntry.getResultDataOrNull(result); | |
| 464 var actualData = actualEntry.getResultDataOrNull(result); | |
| 465 if (expectedData?.state == CacheState.INVALID) { | |
| 466 expectedData = null; | |
| 467 } | |
| 468 if (actualData?.state == CacheState.INVALID) { | |
| 469 actualData = null; | |
| 470 } | |
| 471 if (actualData == null) { | |
| 472 if (result != CONTENT && | |
| 473 result != LIBRARY_ELEMENT4 && | |
| 474 result != LIBRARY_ELEMENT5 && | |
| 475 result != READY_LIBRARY_ELEMENT6 && | |
| 476 result != READY_LIBRARY_ELEMENT7) { | |
| 477 Source targetSource = target.source; | |
| 478 if (targetSource != null && | |
| 479 targetSource.fullName.startsWith(folderPath)) { | |
| 480 fail('No ResultData $result for $target'); | |
| 481 } | |
| 482 } | |
| 483 continue; | |
| 484 } | |
| 485 Object expectedValue = expectedData.value; | |
| 486 Object actualValue = actualData.value; | |
| 487 print(' $result ${expectedValue?.runtimeType}'); | |
| 488 _validateResult(target, result, actualValue, expectedValue); | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 void _validatePairs(AnalysisTarget target, ResultDescriptor result, | |
| 493 List actualList, List expectedList) { | |
| 494 if (expectedList == null) { | |
| 495 expect(actualList, isNull); | |
| 496 return; | |
| 497 } | |
| 498 expect(actualList, isNotNull); | |
| 499 expect(actualList, hasLength(expectedList.length)); | |
| 500 for (int i = 0; i < expectedList.length; i++) { | |
| 501 Object expected = expectedList[i]; | |
| 502 Object actual = actualList[i]; | |
| 503 _validateResult(target, result, actual, expected); | |
| 504 } | |
| 505 } | |
| 506 | |
| 507 void _validateResult(AnalysisTarget target, ResultDescriptor result, | |
| 508 Object actualValue, Object expectedValue) { | |
| 509 if (expectedValue is bool) { | |
| 510 expect(actualValue, expectedValue, reason: '$result of $target'); | |
| 511 } | |
| 512 if (expectedValue is CompilationUnit) { | |
| 513 expect(actualValue, new isInstanceOf<CompilationUnit>()); | |
| 514 new _AstValidator().isEqualNodes(expectedValue, actualValue); | |
| 515 } | |
| 516 if (expectedValue is Element) { | |
| 517 expect(actualValue, new isInstanceOf<Element>()); | |
| 518 _validateElements(actualValue, expectedValue, new Set.identity()); | |
| 519 } | |
| 520 if (expectedValue is List) { | |
| 521 if (actualValue is List) { | |
| 522 _validatePairs(target, result, actualValue, expectedValue); | |
| 523 } else { | |
| 524 _failTypeMismatch(actualValue, expectedValue); | |
| 525 } | |
| 526 } | |
| 527 if (expectedValue is AnalysisError) { | |
| 528 if (actualValue is AnalysisError) { | |
| 529 expect(actualValue.source, expectedValue.source); | |
| 530 expect(actualValue.offset, expectedValue.offset); | |
| 531 expect(actualValue.message, expectedValue.message); | |
| 532 } else { | |
| 533 _failTypeMismatch(actualValue, expectedValue); | |
| 534 } | |
| 535 } | |
| 536 } | |
| 537 | |
| 538 void _validateTypes(DartType actualType, DartType expectedType, Set visited) { | |
| 539 if (!visited.add(expectedType)) { | |
| 540 return; | |
| 541 } | |
| 542 expect(actualType?.runtimeType, expectedType?.runtimeType); | |
| 543 _validateElements(actualType.element, expectedType.element, visited); | |
| 544 } | |
| 545 | |
| 546 /** | |
| 547 * Create and return a source representing the given [file] within the given | |
| 548 * [context]. | |
| 549 */ | |
| 550 static Source _createSourceInContext(AnalysisContext context, fs.File file) { | |
| 551 Source source = file.createSource(); | |
| 552 if (context == null) { | |
| 553 return source; | |
| 554 } | |
| 555 Uri uri = context.sourceFactory.restoreUri(source); | |
| 556 return file.createSource(uri); | |
| 557 } | |
| 558 } | |
| 559 | |
| 560 /** | |
| 561 * Compares tokens and ASTs, and built elements of declared identifiers. | |
| 562 */ | |
| 563 class _AstValidator extends AstComparator { | |
| 564 @override | |
| 565 bool isEqualNodes(AstNode expected, AstNode actual) { | |
| 566 // TODO(scheglov) skip comments for now | |
| 567 // [ElementBuilder.visitFunctionExpression] in resolver_test.dart | |
| 568 // Going from c4493869ca19ef9ba6bd35d3d42e1209eb3b7e63 | |
| 569 // to 3977c9f2274df35df6332a65af9973fd6517bc12 | |
| 570 // With files: | |
| 571 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/gener
ated/resolver.dart', | |
| 572 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/dart/
element/builder.dart', | |
| 573 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/test/generate
d/resolver_test.dart', | |
| 574 if (expected is CommentReference) { | |
| 575 return true; | |
| 576 } | |
| 577 // Compare nodes. | |
| 578 bool result = super.isEqualNodes(expected, actual); | |
| 579 if (!result) { | |
| 580 fail('|$actual| != expected |$expected|'); | |
| 581 } | |
| 582 // Verify that identifiers have equal elements and types. | |
| 583 if (expected is SimpleIdentifier && actual is SimpleIdentifier) { | |
| 584 _verifyElements(actual.staticElement, expected.staticElement, | |
| 585 '$expected staticElement'); | |
| 586 _verifyElements(actual.propagatedElement, expected.propagatedElement, | |
| 587 '$expected staticElement'); | |
| 588 _verifyTypes( | |
| 589 actual.staticType, expected.staticType, '$expected staticType'); | |
| 590 _verifyTypes(actual.propagatedType, expected.propagatedType, | |
| 591 '$expected propagatedType'); | |
| 592 _verifyElements(actual.staticParameterElement, | |
| 593 expected.staticParameterElement, '$expected staticParameterElement'); | |
| 594 _verifyElements( | |
| 595 actual.propagatedParameterElement, | |
| 596 expected.propagatedParameterElement, | |
| 597 '$expected propagatedParameterElement'); | |
| 598 } | |
| 599 return true; | |
| 600 } | |
| 601 | |
| 602 void _verifyElements(Element actual, Element expected, String desc) { | |
| 603 if (expected == null && actual == null) { | |
| 604 return; | |
| 605 } | |
| 606 if (expected is MultiplyDefinedElement && | |
| 607 actual is MultiplyDefinedElement) { | |
| 608 return; | |
| 609 } | |
| 610 while (expected is Member) { | |
| 611 if (actual is Member) { | |
| 612 actual = (actual as Member).baseElement; | |
| 613 expected = (expected as Member).baseElement; | |
| 614 } else { | |
| 615 _failTypeMismatch(actual, expected, reason: desc); | |
| 616 } | |
| 617 } | |
| 618 expect(actual, equals(expected), reason: desc); | |
| 619 } | |
| 620 | |
| 621 void _verifyTypes(DartType actual, DartType expected, String desc) { | |
| 622 _verifyElements(actual?.element, expected?.element, '$desc element'); | |
| 623 if (expected is InterfaceType) { | |
| 624 if (actual is InterfaceType) { | |
| 625 List<DartType> actualArguments = actual.typeArguments; | |
| 626 List<DartType> expectedArguments = expected.typeArguments; | |
| 627 expect( | |
| 628 actualArguments, | |
| 629 pairwiseCompare(expectedArguments, (a, b) { | |
| 630 _verifyTypes(a, b, '$desc typeArguments'); | |
| 631 return true; | |
| 632 }, 'elements')); | |
| 633 } else { | |
| 634 _failTypeMismatch(actual, expected); | |
| 635 } | |
| 636 } | |
| 637 } | |
| 638 } | |
| OLD | NEW |