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 |