| 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:unittest/unittest.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 (_) => new FolderBasedDartSdk(resourceProvider, sdkDirectory)); |
| 249 contentCache = new ContentCache(); |
| 250 ContextBuilder builder = |
| 251 new ContextBuilder(resourceProvider, sdkManager, contentCache); |
| 252 builder.defaultOptions = new AnalysisOptionsImpl(); |
| 253 expectedContext = builder.buildContext(folderPath); |
| 254 actualContext = builder.buildContext(folderPath); |
| 255 expectedContext.analysisOptions = |
| 256 new AnalysisOptionsImpl.from(expectedContext.analysisOptions) |
| 257 ..incremental = true; |
| 258 actualContext.analysisOptions = |
| 259 new AnalysisOptionsImpl.from(actualContext.analysisOptions) |
| 260 ..incremental = true |
| 261 ..finerGrainedInvalidation = true; |
| 262 print('Created contexts'); |
| 263 } |
| 264 |
| 265 run() async { |
| 266 GitRepository repository = new GitRepository(repoPath); |
| 267 |
| 268 // Recover. |
| 269 repository.removeIndexLock(); |
| 270 await repository.resetHard(); |
| 271 |
| 272 await repository.checkout('master'); |
| 273 List<GitRevision> revisions = |
| 274 await repository.getRevisions(after: '2016-01-01'); |
| 275 revisions = revisions.reversed.toList(); |
| 276 // TODO(scheglov) Use to compare two revisions. |
| 277 // List<GitRevision> revisions = [ |
| 278 // new GitRevision(0, '99517a162cbabf3d3afbdb566df3fe2b18cd4877', 'aaa'), |
| 279 // new GitRevision(0, '2ef00b0c3d0182b5e4ea5ca55fd00b9d038ae40d', 'bbb'), |
| 280 // ]; |
| 281 FolderInfo oldFolder = null; |
| 282 for (GitRevision revision in revisions) { |
| 283 print(revision); |
| 284 await repository.checkout(revision.hash); |
| 285 |
| 286 // Run "pub get". |
| 287 if (!new File('$folderPath/pubspec.yaml').existsSync()) { |
| 288 continue; |
| 289 } |
| 290 { |
| 291 ProcessResult processResult = await Process.run( |
| 292 '/Users/scheglov/Applications/dart-sdk/bin/pub', <String>['get'], |
| 293 workingDirectory: folderPath); |
| 294 if (processResult.exitCode != 0) { |
| 295 _logPrint('Pub get failed.'); |
| 296 _logPrint(processResult.stdout); |
| 297 _logPrint(processResult.stderr); |
| 298 continue; |
| 299 } |
| 300 _logPrint('\tpub get OK'); |
| 301 } |
| 302 FolderInfo newFolder = new FolderInfo(folderPath); |
| 303 |
| 304 if (expectedContext == null) { |
| 305 createContexts(); |
| 306 _applyChanges( |
| 307 newFolder.files.map((file) => file.path).toList(), [], []); |
| 308 _analyzeContexts(); |
| 309 } |
| 310 |
| 311 if (oldFolder != null) { |
| 312 FolderDiff diff = newFolder.diff(oldFolder); |
| 313 print(' $diff'); |
| 314 if (diff.isNotEmpty) { |
| 315 _applyChanges(diff.added, diff.changed, diff.removed); |
| 316 _analyzeContexts(); |
| 317 } |
| 318 } |
| 319 oldFolder = newFolder; |
| 320 print('\n'); |
| 321 print('\n'); |
| 322 } |
| 323 } |
| 324 |
| 325 /** |
| 326 * Perform analysis tasks up to 512 times and assert that it was enough. |
| 327 */ |
| 328 void _analyzeAll_assertFinished(AnalysisContext context, |
| 329 [int maxIterations = 1000000]) { |
| 330 for (int i = 0; i < maxIterations; i++) { |
| 331 List<ChangeNotice> notice = context.performAnalysisTask().changeNotices; |
| 332 if (notice == null) { |
| 333 return; |
| 334 } |
| 335 } |
| 336 throw new StateError( |
| 337 "performAnalysisTask failed to terminate after analyzing all sources"); |
| 338 } |
| 339 |
| 340 void _analyzeContexts() { |
| 341 { |
| 342 Stopwatch sw = new Stopwatch()..start(); |
| 343 _analyzeAll_assertFinished(expectedContext); |
| 344 print(' analyze(expected): ${sw.elapsedMilliseconds}'); |
| 345 } |
| 346 { |
| 347 Stopwatch sw = new Stopwatch()..start(); |
| 348 _analyzeAll_assertFinished(actualContext); |
| 349 print(' analyze(actual): ${sw.elapsedMilliseconds}'); |
| 350 } |
| 351 _validateContexts(); |
| 352 } |
| 353 |
| 354 void _applyChanges( |
| 355 List<String> added, List<String> changed, List<String> removed) { |
| 356 ChangeSet changeSet = new ChangeSet(); |
| 357 added.map(_pathToSource).forEach(changeSet.addedSource); |
| 358 removed.map(_pathToSource).forEach(changeSet.removedSource); |
| 359 changed.map(_pathToSource).forEach(changeSet.changedSource); |
| 360 changed.forEach((path) => new File(path).readAsStringSync()); |
| 361 { |
| 362 Stopwatch sw = new Stopwatch()..start(); |
| 363 expectedContext.applyChanges(changeSet); |
| 364 print(' apply(expected): ${sw.elapsedMilliseconds}'); |
| 365 } |
| 366 { |
| 367 Stopwatch sw = new Stopwatch()..start(); |
| 368 actualContext.applyChanges(changeSet); |
| 369 print(' apply(actual): ${sw.elapsedMilliseconds}'); |
| 370 } |
| 371 } |
| 372 |
| 373 Source _pathToSource(String path) { |
| 374 fs.File file = resourceProvider.getFile(path); |
| 375 return _createSourceInContext(expectedContext, file); |
| 376 } |
| 377 |
| 378 void _validateContexts() { |
| 379 currentRevisionValidatedElements.clear(); |
| 380 MapIterator<AnalysisTarget, CacheEntry> iterator = |
| 381 expectedContext.privateAnalysisCachePartition.iterator(); |
| 382 while (iterator.moveNext()) { |
| 383 AnalysisTarget target = iterator.key; |
| 384 CacheEntry entry = iterator.value; |
| 385 if (target is NonExistingSource) { |
| 386 continue; |
| 387 } |
| 388 _validateEntry(target, entry); |
| 389 } |
| 390 } |
| 391 |
| 392 void _validateElements( |
| 393 Element actualValue, Element expectedValue, Set visited) { |
| 394 if (actualValue == null && expectedValue == null) { |
| 395 return; |
| 396 } |
| 397 if (!currentRevisionValidatedElements.add(expectedValue)) { |
| 398 return; |
| 399 } |
| 400 if (!visited.add(expectedValue)) { |
| 401 return; |
| 402 } |
| 403 List<Element> sortElements(List<Element> elements) { |
| 404 elements = elements.toList(); |
| 405 elements.sort((a, b) { |
| 406 if (a.nameOffset != b.nameOffset) { |
| 407 return a.nameOffset - b.nameOffset; |
| 408 } |
| 409 return a.name.compareTo(b.name); |
| 410 }); |
| 411 return elements; |
| 412 } |
| 413 |
| 414 void validateSortedElements( |
| 415 List<Element> actualElements, List<Element> expectedElements) { |
| 416 expect(actualElements, hasLength(expectedElements.length)); |
| 417 actualElements = sortElements(actualElements); |
| 418 expectedElements = sortElements(expectedElements); |
| 419 for (int i = 0; i < expectedElements.length; i++) { |
| 420 _validateElements(actualElements[i], expectedElements[i], visited); |
| 421 } |
| 422 } |
| 423 |
| 424 expect(actualValue?.runtimeType, expectedValue?.runtimeType); |
| 425 expect(actualValue.nameOffset, expectedValue.nameOffset); |
| 426 expect(actualValue.name, expectedValue.name); |
| 427 if (expectedValue is ClassElement) { |
| 428 var actualElement = actualValue as ClassElement; |
| 429 validateSortedElements(actualElement.accessors, expectedValue.accessors); |
| 430 validateSortedElements( |
| 431 actualElement.constructors, expectedValue.constructors); |
| 432 validateSortedElements(actualElement.fields, expectedValue.fields); |
| 433 validateSortedElements(actualElement.methods, expectedValue.methods); |
| 434 } |
| 435 if (expectedValue is CompilationUnitElement) { |
| 436 var actualElement = actualValue as CompilationUnitElement; |
| 437 validateSortedElements(actualElement.accessors, expectedValue.accessors); |
| 438 validateSortedElements(actualElement.functions, expectedValue.functions); |
| 439 validateSortedElements(actualElement.types, expectedValue.types); |
| 440 validateSortedElements( |
| 441 actualElement.functionTypeAliases, expectedValue.functionTypeAliases); |
| 442 validateSortedElements( |
| 443 actualElement.topLevelVariables, expectedValue.topLevelVariables); |
| 444 } |
| 445 if (expectedValue is ExecutableElement) { |
| 446 var actualElement = actualValue as ExecutableElement; |
| 447 validateSortedElements( |
| 448 actualElement.parameters, expectedValue.parameters); |
| 449 _validateTypes( |
| 450 actualElement.returnType, expectedValue.returnType, visited); |
| 451 } |
| 452 } |
| 453 |
| 454 void _validateEntry(AnalysisTarget target, CacheEntry expectedEntry) { |
| 455 CacheEntry actualEntry = |
| 456 actualContext.privateAnalysisCachePartition.get(target); |
| 457 if (actualEntry == null) { |
| 458 return; |
| 459 } |
| 460 print(' (${target.runtimeType}) $target'); |
| 461 for (ResultDescriptor result in expectedEntry.nonInvalidResults) { |
| 462 var expectedData = expectedEntry.getResultDataOrNull(result); |
| 463 var actualData = actualEntry.getResultDataOrNull(result); |
| 464 if (expectedData?.state == CacheState.INVALID) { |
| 465 expectedData = null; |
| 466 } |
| 467 if (actualData?.state == CacheState.INVALID) { |
| 468 actualData = null; |
| 469 } |
| 470 if (actualData == null) { |
| 471 if (result != CONTENT && |
| 472 result != LIBRARY_ELEMENT4 && |
| 473 result != LIBRARY_ELEMENT5 && |
| 474 result != READY_LIBRARY_ELEMENT6 && |
| 475 result != READY_LIBRARY_ELEMENT7) { |
| 476 Source targetSource = target.source; |
| 477 if (targetSource != null && |
| 478 targetSource.fullName.startsWith(folderPath)) { |
| 479 fail('No ResultData $result for $target'); |
| 480 } |
| 481 } |
| 482 continue; |
| 483 } |
| 484 Object expectedValue = expectedData.value; |
| 485 Object actualValue = actualData.value; |
| 486 print(' $result ${expectedValue?.runtimeType}'); |
| 487 _validateResult(target, result, actualValue, expectedValue); |
| 488 } |
| 489 } |
| 490 |
| 491 void _validatePairs(AnalysisTarget target, ResultDescriptor result, |
| 492 List actualList, List expectedList) { |
| 493 if (expectedList == null) { |
| 494 expect(actualList, isNull); |
| 495 return; |
| 496 } |
| 497 expect(actualList, isNotNull); |
| 498 expect(actualList, hasLength(expectedList.length)); |
| 499 for (int i = 0; i < expectedList.length; i++) { |
| 500 Object expected = expectedList[i]; |
| 501 Object actual = actualList[i]; |
| 502 _validateResult(target, result, actual, expected); |
| 503 } |
| 504 } |
| 505 |
| 506 void _validateResult(AnalysisTarget target, ResultDescriptor result, |
| 507 Object actualValue, Object expectedValue) { |
| 508 if (expectedValue is bool) { |
| 509 expect(actualValue, expectedValue, reason: '$result of $target'); |
| 510 } |
| 511 if (expectedValue is CompilationUnit) { |
| 512 expect(actualValue, new isInstanceOf<CompilationUnit>()); |
| 513 new _AstValidator().isEqualNodes(expectedValue, actualValue); |
| 514 } |
| 515 if (expectedValue is Element) { |
| 516 expect(actualValue, new isInstanceOf<Element>()); |
| 517 _validateElements(actualValue, expectedValue, new Set.identity()); |
| 518 } |
| 519 if (expectedValue is List) { |
| 520 if (actualValue is List) { |
| 521 _validatePairs(target, result, actualValue, expectedValue); |
| 522 } else { |
| 523 _failTypeMismatch(actualValue, expectedValue); |
| 524 } |
| 525 } |
| 526 if (expectedValue is AnalysisError) { |
| 527 if (actualValue is AnalysisError) { |
| 528 expect(actualValue.source, expectedValue.source); |
| 529 expect(actualValue.offset, expectedValue.offset); |
| 530 expect(actualValue.message, expectedValue.message); |
| 531 } else { |
| 532 _failTypeMismatch(actualValue, expectedValue); |
| 533 } |
| 534 } |
| 535 } |
| 536 |
| 537 void _validateTypes(DartType actualType, DartType expectedType, Set visited) { |
| 538 if (!visited.add(expectedType)) { |
| 539 return; |
| 540 } |
| 541 expect(actualType?.runtimeType, expectedType?.runtimeType); |
| 542 _validateElements(actualType.element, expectedType.element, visited); |
| 543 } |
| 544 |
| 545 /** |
| 546 * Create and return a source representing the given [file] within the given |
| 547 * [context]. |
| 548 */ |
| 549 static Source _createSourceInContext(AnalysisContext context, fs.File file) { |
| 550 Source source = file.createSource(); |
| 551 if (context == null) { |
| 552 return source; |
| 553 } |
| 554 Uri uri = context.sourceFactory.restoreUri(source); |
| 555 return file.createSource(uri); |
| 556 } |
| 557 } |
| 558 |
| 559 /** |
| 560 * Compares tokens and ASTs, and built elements of declared identifiers. |
| 561 */ |
| 562 class _AstValidator extends AstComparator { |
| 563 @override |
| 564 bool isEqualNodes(AstNode expected, AstNode actual) { |
| 565 // TODO(scheglov) skip comments for now |
| 566 // [ElementBuilder.visitFunctionExpression] in resolver_test.dart |
| 567 // Going from c4493869ca19ef9ba6bd35d3d42e1209eb3b7e63 |
| 568 // to 3977c9f2274df35df6332a65af9973fd6517bc12 |
| 569 // With files: |
| 570 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/gener
ated/resolver.dart', |
| 571 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/dart/
element/builder.dart', |
| 572 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/test/generate
d/resolver_test.dart', |
| 573 if (expected is CommentReference) { |
| 574 return true; |
| 575 } |
| 576 // Compare nodes. |
| 577 bool result = super.isEqualNodes(expected, actual); |
| 578 if (!result) { |
| 579 fail('|$actual| != expected |$expected|'); |
| 580 } |
| 581 // Verify that identifiers have equal elements and types. |
| 582 if (expected is SimpleIdentifier && actual is SimpleIdentifier) { |
| 583 _verifyElements(actual.staticElement, expected.staticElement, |
| 584 '$expected staticElement'); |
| 585 _verifyElements(actual.propagatedElement, expected.propagatedElement, |
| 586 '$expected staticElement'); |
| 587 _verifyTypes( |
| 588 actual.staticType, expected.staticType, '$expected staticType'); |
| 589 _verifyTypes(actual.propagatedType, expected.propagatedType, |
| 590 '$expected propagatedType'); |
| 591 _verifyElements(actual.staticParameterElement, |
| 592 expected.staticParameterElement, '$expected staticParameterElement'); |
| 593 _verifyElements( |
| 594 actual.propagatedParameterElement, |
| 595 expected.propagatedParameterElement, |
| 596 '$expected propagatedParameterElement'); |
| 597 } |
| 598 return true; |
| 599 } |
| 600 |
| 601 void _verifyElements(Element actual, Element expected, String desc) { |
| 602 if (expected == null && actual == null) { |
| 603 return; |
| 604 } |
| 605 if (expected is MultiplyDefinedElement && |
| 606 actual is MultiplyDefinedElement) { |
| 607 return; |
| 608 } |
| 609 while (expected is Member) { |
| 610 if (actual is Member) { |
| 611 actual = (actual as Member).baseElement; |
| 612 expected = (expected as Member).baseElement; |
| 613 } else { |
| 614 _failTypeMismatch(actual, expected, reason: desc); |
| 615 } |
| 616 } |
| 617 expect(actual, equals(expected), reason: desc); |
| 618 } |
| 619 |
| 620 void _verifyTypes(DartType actual, DartType expected, String desc) { |
| 621 _verifyElements(actual?.element, expected?.element, '$desc element'); |
| 622 if (expected is InterfaceType) { |
| 623 if (actual is InterfaceType) { |
| 624 List<DartType> actualArguments = actual.typeArguments; |
| 625 List<DartType> expectedArguments = expected.typeArguments; |
| 626 expect( |
| 627 actualArguments, |
| 628 pairwiseCompare(expectedArguments, (a, b) { |
| 629 _verifyTypes(a, b, '$desc typeArguments'); |
| 630 return true; |
| 631 }, 'elements')); |
| 632 } else { |
| 633 _failTypeMismatch(actual, expected); |
| 634 } |
| 635 } |
| 636 } |
| 637 } |
| OLD | NEW |