Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(273)

Side by Side Diff: pkg/analyzer/test/stress/for_git_repository.dart

Issue 2206783002: Stress test for comparing analysis performed in two AnalysisContext instances with different option… (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « pkg/analyzer/lib/src/context/builder.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/context/builder.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698