Chromium Code Reviews| 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/file_system/file_system.dart' as fs; | |
| 14 import 'package:analyzer/file_system/physical_file_system.dart'; | |
| 15 import 'package:analyzer/src/context/builder.dart'; | |
| 16 import 'package:analyzer/src/context/cache.dart'; | |
| 17 import 'package:analyzer/src/context/context.dart'; | |
| 18 import 'package:analyzer/src/dart/ast/utilities.dart'; | |
| 19 import 'package:analyzer/src/dart/element/member.dart'; | |
| 20 import 'package:analyzer/src/generated/engine.dart'; | |
| 21 import 'package:analyzer/src/generated/error.dart'; | |
| 22 import 'package:analyzer/src/generated/sdk.dart'; | |
| 23 import 'package:analyzer/src/generated/sdk_io.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 Map<String, FileInfo> newFiles = toMap(this); | |
| 103 Map<String, FileInfo> oldFiles = toMap(oldFolder); | |
| 104 Set<String> addedPaths = newFiles.keys.toSet()..removeAll(oldFiles.keys); | |
| 105 Set<String> removedPaths = oldFiles.keys.toSet()..removeAll(newFiles.keys); | |
| 106 List<String> changedPaths = <String>[]; | |
| 107 newFiles.forEach((path, newFile) { | |
| 108 FileInfo oldFile = oldFiles[path]; | |
| 109 if (oldFile != null && oldFile.modification != newFile.modification) { | |
| 110 changedPaths.add(path); | |
| 111 } | |
| 112 }); | |
| 113 return new FolderDiff( | |
| 114 addedPaths.toList(), changedPaths, removedPaths.toList()); | |
| 115 } | |
| 116 } | |
| 117 | |
| 118 class GitException { | |
| 119 final String message; | |
| 120 final String stdout; | |
| 121 final String stderr; | |
| 122 | |
| 123 GitException(this.message) | |
| 124 : stdout = null, | |
| 125 stderr = null; | |
| 126 | |
| 127 GitException.forProcessResult(this.message, ProcessResult processResult) | |
| 128 : stdout = processResult.stdout, | |
| 129 stderr = processResult.stderr; | |
| 130 | |
| 131 @override | |
| 132 String toString() => '$message\n$stdout\n$stderr\n'; | |
| 133 } | |
| 134 | |
| 135 class GitRepository { | |
| 136 final String path; | |
| 137 | |
| 138 GitRepository(this.path); | |
| 139 | |
| 140 Future checkout(String hash) async { | |
| 141 // TODO(scheglov) use for updating only some files | |
| 142 if (hash.endsWith('hash')) { | |
| 143 List<String> filePaths = <String>[ | |
| 144 '/Users/user/full/path/one.dart', | |
| 145 '/Users/user/full/path/two.dart', | |
| 146 ]; | |
| 147 for (var filePath in filePaths) { | |
| 148 await Process.run('git', <String>['checkout', '-f', hash, filePath], | |
| 149 workingDirectory: path); | |
| 150 } | |
| 151 return; | |
| 152 } | |
| 153 ProcessResult processResult = await Process | |
| 154 .run('git', <String>['checkout', '-f', hash], workingDirectory: path); | |
| 155 _throwIfNotSuccess(processResult); | |
| 156 } | |
| 157 | |
| 158 Future<List<GitRevision>> getRevisions({String after}) async { | |
| 159 List<String> args = <String>['log', '--format=%ct %H %s']; | |
| 160 if (after != null) { | |
| 161 args.add('--after=$after'); | |
| 162 } | |
| 163 ProcessResult processResult = | |
| 164 await Process.run('git', args, workingDirectory: path); | |
| 165 _throwIfNotSuccess(processResult); | |
| 166 String output = processResult.stdout; | |
| 167 List<String> logLines = output.split('\n'); | |
| 168 List<GitRevision> revisions = <GitRevision>[]; | |
| 169 for (String logLine in logLines) { | |
| 170 int index1 = logLine.indexOf(' '); | |
| 171 if (index1 != -1) { | |
| 172 int index2 = logLine.indexOf(' ', index1 + 1); | |
| 173 if (index2 != -1) { | |
| 174 int timestamp = int.parse(logLine.substring(0, index1)); | |
| 175 String hash = logLine.substring(index1 + 1, index2); | |
| 176 String message = logLine.substring(index2).trim(); | |
| 177 revisions.add(new GitRevision(timestamp, hash, message)); | |
| 178 } | |
| 179 } | |
| 180 } | |
| 181 return revisions; | |
| 182 } | |
| 183 | |
| 184 void removeIndexLock() { | |
| 185 File file = new File('$path/.git/index.lock'); | |
| 186 if (file.existsSync()) { | |
| 187 file.deleteSync(); | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 Future resetHard() async { | |
| 192 ProcessResult processResult = await Process | |
| 193 .run('git', <String>['reset', '--hard'], workingDirectory: path); | |
| 194 _throwIfNotSuccess(processResult); | |
| 195 } | |
| 196 | |
| 197 void _throwIfNotSuccess(ProcessResult processResult) { | |
| 198 if (processResult.exitCode != 0) { | |
| 199 throw new GitException.forProcessResult( | |
| 200 'Unable to run "git log".', processResult); | |
| 201 } | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 class GitRevision { | |
| 206 final int timestamp; | |
| 207 final String hash; | |
| 208 final String message; | |
| 209 | |
| 210 GitRevision(this.timestamp, this.hash, this.message); | |
| 211 | |
| 212 @override | |
| 213 String toString() { | |
| 214 DateTime dateTime = | |
| 215 new DateTime.fromMillisecondsSinceEpoch(timestamp * 1000, isUtc: true) | |
| 216 .toLocal(); | |
| 217 return '$dateTime|$hash|$message|'; | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 class StressTest { | |
| 222 // String repoPath = '/Users/scheglov/tmp/limited-invalidation/path'; | |
| 223 // String folderPath = '/Users/scheglov/tmp/limited-invalidation/path'; | |
| 224 // String repoPath = '/Users/scheglov/tmp/limited-invalidation/async'; | |
| 225 // String folderPath = '/Users/scheglov/tmp/limited-invalidation/async'; | |
| 226 String repoPath = '/Users/scheglov/tmp/limited-invalidation/sdk'; | |
| 227 String folderPath = '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer '; | |
|
Brian Wilkerson
2016/08/03 13:49:11
Needs to not have machine specific paths. Can we m
| |
| 228 | |
| 229 fs.ResourceProvider resourceProvider; | |
| 230 path.Context pathContext; | |
| 231 DartSdkManager sdkManager; | |
| 232 ContentCache contentCache; | |
| 233 | |
| 234 AnalysisContextImpl expectedContext; | |
| 235 AnalysisContextImpl actualContext; | |
| 236 | |
| 237 Set<Element> currentRevisionValidatedElements = new Set<Element>(); | |
| 238 | |
| 239 void createContexts() { | |
| 240 assert(expectedContext == null); | |
| 241 assert(actualContext == null); | |
| 242 resourceProvider = PhysicalResourceProvider.INSTANCE; | |
| 243 pathContext = resourceProvider.pathContext; | |
| 244 sdkManager = new DartSdkManager( | |
| 245 DirectoryBasedDartSdk.defaultSdkDirectory.getAbsolutePath(), | |
| 246 false, | |
| 247 (_) => DirectoryBasedDartSdk.defaultSdk); | |
| 248 contentCache = new ContentCache(); | |
| 249 ContextBuilder builder = | |
| 250 new ContextBuilder(resourceProvider, sdkManager, contentCache); | |
| 251 builder.defaultOptions = new AnalysisOptionsImpl(); | |
| 252 expectedContext = builder.buildContext(folderPath); | |
| 253 actualContext = builder.buildContext(folderPath); | |
| 254 expectedContext.analysisOptions = | |
| 255 new AnalysisOptionsImpl.from(expectedContext.analysisOptions) | |
| 256 ..incremental = true; | |
| 257 actualContext.analysisOptions = | |
| 258 new AnalysisOptionsImpl.from(actualContext.analysisOptions) | |
| 259 ..incremental = true | |
| 260 ..finerGrainedInvalidation = true; | |
| 261 print(actualContext == expectedContext); | |
| 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-03-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 void validateSortedElements( | |
| 414 List<Element> actualElements, List<Element> expectedElements) { | |
| 415 expect(actualElements, hasLength(expectedElements.length)); | |
| 416 actualElements = sortElements(actualElements); | |
| 417 expectedElements = sortElements(expectedElements); | |
| 418 for (int i = 0; i < expectedElements.length; i++) { | |
| 419 _validateElements(actualElements[i], expectedElements[i], visited); | |
| 420 } | |
| 421 } | |
| 422 expect(actualValue?.runtimeType, expectedValue?.runtimeType); | |
| 423 expect(actualValue.nameOffset, expectedValue.nameOffset); | |
| 424 expect(actualValue.name, expectedValue.name); | |
| 425 if (expectedValue is ClassElement) { | |
| 426 var actualElement = actualValue as ClassElement; | |
| 427 validateSortedElements(actualElement.accessors, expectedValue.accessors); | |
| 428 validateSortedElements( | |
| 429 actualElement.constructors, expectedValue.constructors); | |
| 430 validateSortedElements(actualElement.fields, expectedValue.fields); | |
| 431 validateSortedElements(actualElement.methods, expectedValue.methods); | |
| 432 } | |
| 433 if (expectedValue is CompilationUnitElement) { | |
| 434 var actualElement = actualValue as CompilationUnitElement; | |
| 435 validateSortedElements(actualElement.accessors, expectedValue.accessors); | |
| 436 validateSortedElements(actualElement.functions, expectedValue.functions); | |
| 437 validateSortedElements(actualElement.types, expectedValue.types); | |
| 438 validateSortedElements( | |
| 439 actualElement.functionTypeAliases, expectedValue.functionTypeAliases); | |
| 440 validateSortedElements( | |
| 441 actualElement.topLevelVariables, expectedValue.topLevelVariables); | |
| 442 } | |
| 443 if (expectedValue is ExecutableElement) { | |
| 444 var actualElement = actualValue as ExecutableElement; | |
| 445 validateSortedElements( | |
| 446 actualElement.parameters, expectedValue.parameters); | |
| 447 _validateTypes( | |
| 448 actualElement.returnType, expectedValue.returnType, visited); | |
| 449 } | |
| 450 } | |
| 451 | |
| 452 void _validateEntry(AnalysisTarget target, CacheEntry expectedEntry) { | |
| 453 CacheEntry actualEntry = | |
| 454 actualContext.privateAnalysisCachePartition.get(target); | |
| 455 if (actualEntry == null) { | |
| 456 return; | |
| 457 } | |
| 458 print(' (${target.runtimeType}) $target'); | |
| 459 for (ResultDescriptor result in expectedEntry.nonInvalidResults) { | |
| 460 var expectedData = expectedEntry.getResultDataOrNull(result); | |
| 461 var actualData = actualEntry.getResultDataOrNull(result); | |
| 462 if (expectedData?.state == CacheState.INVALID) { | |
| 463 expectedData = null; | |
| 464 } | |
| 465 if (actualData?.state == CacheState.INVALID) { | |
| 466 actualData = null; | |
| 467 } | |
| 468 if (actualData == null) { | |
| 469 if (result != CONTENT && | |
| 470 result != LIBRARY_ELEMENT4 && | |
| 471 result != LIBRARY_ELEMENT5 && | |
| 472 result != READY_LIBRARY_ELEMENT6 && | |
| 473 result != READY_LIBRARY_ELEMENT7) { | |
| 474 Source targetSource = target.source; | |
| 475 if (targetSource != null && | |
| 476 targetSource.fullName.startsWith(folderPath)) { | |
| 477 fail('No ResultData $result for $target'); | |
| 478 } | |
| 479 } | |
| 480 continue; | |
| 481 } | |
| 482 Object expectedValue = expectedData.value; | |
| 483 Object actualValue = actualData.value; | |
| 484 print(' $result ${expectedValue?.runtimeType}'); | |
| 485 _validateResult(target, result, actualValue, expectedValue); | |
| 486 } | |
| 487 } | |
| 488 | |
| 489 void _validatePairs(AnalysisTarget target, ResultDescriptor result, | |
| 490 List actualList, List expectedList) { | |
| 491 if (expectedList == null) { | |
| 492 expect(actualList, isNull); | |
| 493 return; | |
| 494 } | |
| 495 expect(actualList, isNotNull); | |
| 496 expect(actualList, hasLength(expectedList.length)); | |
| 497 for (int i = 0; i < expectedList.length; i++) { | |
| 498 Object expected = expectedList[i]; | |
| 499 Object actual = actualList[i]; | |
| 500 _validateResult(target, result, actual, expected); | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 void _validateResult(AnalysisTarget target, ResultDescriptor result, | |
| 505 Object actualValue, Object expectedValue) { | |
| 506 if (expectedValue is bool) { | |
| 507 expect(actualValue, expectedValue, reason: '$result of $target'); | |
| 508 } | |
| 509 if (expectedValue is CompilationUnit) { | |
| 510 expect(actualValue, new isInstanceOf<CompilationUnit>()); | |
| 511 new _AstValidator().isEqualNodes(expectedValue, actualValue); | |
| 512 } | |
| 513 if (expectedValue is Element) { | |
| 514 expect(actualValue, new isInstanceOf<Element>()); | |
| 515 _validateElements(actualValue, expectedValue, new Set.identity()); | |
| 516 } | |
| 517 if (expectedValue is List) { | |
| 518 if (actualValue is List) { | |
| 519 _validatePairs(target, result, actualValue, expectedValue); | |
| 520 } else { | |
| 521 _failTypeMismatch(actualValue, expectedValue); | |
| 522 } | |
| 523 } | |
| 524 if (expectedValue is AnalysisError) { | |
| 525 if (actualValue is AnalysisError) { | |
| 526 expect(actualValue.source, expectedValue.source); | |
| 527 expect(actualValue.offset, expectedValue.offset); | |
| 528 expect(actualValue.message, expectedValue.message); | |
| 529 } else { | |
| 530 _failTypeMismatch(actualValue, expectedValue); | |
| 531 } | |
| 532 } | |
| 533 } | |
| 534 | |
| 535 void _validateTypes(DartType actualType, DartType expectedType, Set visited) { | |
| 536 if (!visited.add(expectedType)) { | |
| 537 return; | |
| 538 } | |
| 539 expect(actualType?.runtimeType, expectedType?.runtimeType); | |
| 540 _validateElements(actualType.element, expectedType.element, visited); | |
| 541 } | |
| 542 | |
| 543 /** | |
| 544 * Create and return a source representing the given [file] within the given | |
| 545 * [context]. | |
| 546 */ | |
| 547 static Source _createSourceInContext(AnalysisContext context, fs.File file) { | |
| 548 Source source = file.createSource(); | |
| 549 if (context == null) { | |
| 550 return source; | |
| 551 } | |
| 552 Uri uri = context.sourceFactory.restoreUri(source); | |
| 553 return file.createSource(uri); | |
| 554 } | |
| 555 } | |
| 556 | |
| 557 /** | |
| 558 * Compares tokens and ASTs, and built elements of declared identifiers. | |
| 559 */ | |
| 560 class _AstValidator extends AstComparator { | |
| 561 @override | |
| 562 bool isEqualNodes(AstNode expected, AstNode actual) { | |
| 563 // TODO(scheglov) skip comments for now | |
| 564 // [ElementBuilder.visitFunctionExpression] in resolver_test.dart | |
| 565 // Going from c4493869ca19ef9ba6bd35d3d42e1209eb3b7e63 | |
| 566 // to 3977c9f2274df35df6332a65af9973fd6517bc12 | |
| 567 // With files: | |
| 568 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/gener ated/resolver.dart', | |
| 569 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/lib/src/dart/ element/builder.dart', | |
| 570 // '/Users/scheglov/tmp/limited-invalidation/sdk/pkg/analyzer/test/generate d/resolver_test.dart', | |
| 571 if (expected is CommentReference) { | |
| 572 return true; | |
| 573 } | |
| 574 // Compare nodes. | |
| 575 bool result = super.isEqualNodes(expected, actual); | |
| 576 if (!result) { | |
| 577 fail('|$actual| != expected |$expected|'); | |
| 578 } | |
| 579 // Verify that identifiers have equal elements and types. | |
| 580 if (expected is SimpleIdentifier && actual is SimpleIdentifier) { | |
| 581 _verifyElements(actual.staticElement, expected.staticElement, | |
| 582 '$expected staticElement'); | |
| 583 _verifyElements(actual.propagatedElement, expected.propagatedElement, | |
| 584 '$expected staticElement'); | |
| 585 _verifyTypes( | |
| 586 actual.staticType, expected.staticType, '$expected staticType'); | |
| 587 _verifyTypes(actual.propagatedType, expected.propagatedType, | |
| 588 '$expected propagatedType'); | |
| 589 _verifyElements(actual.staticParameterElement, | |
| 590 expected.staticParameterElement, '$expected staticParameterElement'); | |
| 591 _verifyElements( | |
| 592 actual.propagatedParameterElement, | |
| 593 expected.propagatedParameterElement, | |
| 594 '$expected propagatedParameterElement'); | |
| 595 } | |
| 596 return true; | |
| 597 } | |
| 598 | |
| 599 void _verifyElements(Element actual, Element expected, String desc) { | |
| 600 if (expected == null && actual == null) { | |
| 601 return; | |
| 602 } | |
| 603 if (expected is MultiplyDefinedElement && | |
| 604 actual is MultiplyDefinedElement) { | |
| 605 return; | |
| 606 } | |
| 607 while (expected is Member) { | |
| 608 if (actual is Member) { | |
| 609 actual = (actual as Member).baseElement; | |
| 610 expected = (expected as Member).baseElement; | |
| 611 } else { | |
| 612 _failTypeMismatch(actual, expected, reason: desc); | |
| 613 } | |
| 614 } | |
| 615 expect(actual, equals(expected), reason: desc); | |
| 616 } | |
| 617 | |
| 618 void _verifyTypes(DartType actual, DartType expected, String desc) { | |
| 619 _verifyElements(actual?.element, expected?.element, '$desc element'); | |
| 620 if (expected is InterfaceType) { | |
| 621 if (actual is InterfaceType) { | |
| 622 List<DartType> actualArguments = actual.typeArguments; | |
| 623 List<DartType> expectedArguments = expected.typeArguments; | |
| 624 expect( | |
| 625 actualArguments, | |
| 626 pairwiseCompare(expectedArguments, (a, b) { | |
| 627 _verifyTypes(a, b, '$desc typeArguments'); | |
| 628 return true; | |
| 629 }, 'elements')); | |
| 630 } else { | |
| 631 _failTypeMismatch(actual, expected); | |
| 632 } | |
| 633 } | |
| 634 } | |
| 635 } | |
| OLD | NEW |