Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // TODO(jmesserly): this file needs to be refactored, it's a port from | 5 // TODO(jmesserly): this file needs to be refactored, it's a port from |
| 6 // package:dev_compiler's tests | 6 // package:dev_compiler's tests |
| 7 library analyzer.test.src.task.strong.strong_test_helper; | 7 library analyzer.test.src.task.strong.strong_test_helper; |
| 8 | 8 |
| 9 import 'package:analyzer/dart/ast/ast.dart'; | 9 import 'package:analyzer/dart/ast/ast.dart'; |
| 10 import 'package:analyzer/dart/ast/visitor.dart'; | 10 import 'package:analyzer/dart/ast/visitor.dart'; |
| 11 import 'package:analyzer/dart/element/element.dart'; | 11 import 'package:analyzer/dart/element/element.dart'; |
| 12 import 'package:analyzer/file_system/file_system.dart'; | 12 import 'package:analyzer/file_system/file_system.dart'; |
| 13 import 'package:analyzer/file_system/memory_file_system.dart'; | 13 import 'package:analyzer/file_system/memory_file_system.dart'; |
| 14 import 'package:analyzer/src/context/context.dart' show SdkAnalysisContext; | 14 import 'package:analyzer/src/context/context.dart' show SdkAnalysisContext; |
| 15 import 'package:analyzer/src/generated/engine.dart'; | 15 import 'package:analyzer/src/generated/engine.dart'; |
| 16 import 'package:analyzer/src/generated/error.dart'; | 16 import 'package:analyzer/src/generated/error.dart'; |
| 17 import 'package:analyzer/src/generated/sdk.dart'; | 17 import 'package:analyzer/src/generated/sdk.dart'; |
| 18 import 'package:analyzer/src/generated/source.dart'; | 18 import 'package:analyzer/src/generated/source.dart'; |
| 19 import 'package:analyzer/src/generated/type_system.dart'; | 19 import 'package:analyzer/src/generated/type_system.dart'; |
| 20 import 'package:analyzer/src/task/strong/checker.dart'; | 20 import 'package:analyzer/src/task/strong/checker.dart'; |
| 21 import 'package:logging/logging.dart'; | 21 import 'package:logging/logging.dart'; |
| 22 import 'package:source_span/source_span.dart'; | 22 import 'package:source_span/source_span.dart'; |
| 23 import 'package:unittest/unittest.dart'; | 23 import 'package:unittest/unittest.dart'; |
| 24 | 24 |
| 25 const String GREEN_COLOR = '\u001b[32m'; | |
| 26 | 25 |
| 27 const String NO_COLOR = '\u001b[0m'; | 26 MemoryResourceProvider files; |
| 27 bool _checkCalled; | |
| 28 | 28 |
| 29 const String _CYAN_COLOR = '\u001b[36m'; | 29 initStrongModeTests() { |
| 30 setUp(() { | |
| 31 AnalysisEngine.instance.processRequiredPlugins(); | |
| 32 files = new MemoryResourceProvider(); | |
| 33 _checkCalled = false; | |
| 34 }); | |
| 30 | 35 |
| 31 const String _MAGENTA_COLOR = '\u001b[35m'; | 36 tearDown(() { |
| 37 // This is a sanity check, in case only addFile is called. | |
| 38 expect(_checkCalled, true, reason: 'must call check() method in test case'); | |
| 39 files = null; | |
| 40 }); | |
| 41 } | |
| 32 | 42 |
| 33 const String _RED_COLOR = '\u001b[31m'; | 43 /// Adds a file using [addFile] and calls [check]. |
| 44 void checkFile(String content) { | |
| 45 addFile(content); | |
| 46 check(); | |
| 47 } | |
| 48 | |
| 49 /// Adds [files] using [addFiles] and calls [check]. | |
| 50 void checkFiles(Map<String, String> files) { | |
|
Bob Nystrom
2016/02/05 22:54:39
Does this have any uses? If not, may as well leave
Jennifer Messerly
2016/02/05 23:22:19
Good catch. Removed!
| |
| 51 addFiles(files); | |
| 52 check(); | |
| 53 } | |
| 54 | |
| 55 /// Adds a file to check. The file should contain: | |
| 56 /// | |
| 57 /// * all expected failures are listed in the source code using comments | |
| 58 /// immediately in front of the AST node that should contain the error. | |
| 59 /// | |
| 60 /// * errors are formatted as a token `level:Type`, where `level` is the | |
| 61 /// logging level were the error would be reported at, and `Type` is the | |
| 62 /// concrete subclass of [StaticInfo] that denotes the error. | |
| 63 /// | |
| 64 /// For example to check that an assignment produces a type error, you can | |
| 65 /// create a file like: | |
| 66 /// | |
| 67 /// addFile(''' | |
| 68 /// String x = /*severe:STATIC_TYPE_ERROR*/3; | |
| 69 /// '''); | |
| 70 /// check(); | |
| 71 /// | |
| 72 /// For a single file, you may also use [checkFile]. | |
| 73 void addFile(String content, {String name: '/main.dart'}) { | |
| 74 name = name.replaceFirst('^package:', '/packages/'); | |
| 75 files.newFile(name, content); | |
| 76 } | |
| 77 | |
| 78 /// Calls [addFile] for each name, content pair. | |
| 79 void addFiles(Map<String, String> files) { | |
| 80 files.forEach((name, content) { | |
| 81 addFile(content, name: name); | |
| 82 }); | |
| 83 } | |
| 84 | |
| 85 /// Run the checker on a program, staring from '/main.dart', and verifies that | |
| 86 /// errors/warnings/hints match the expected value. | |
| 87 /// | |
| 88 /// See [addFile] for more information about how to encode expectations in | |
| 89 /// the file text. | |
| 90 void check() { | |
| 91 _checkCalled = true; | |
| 92 | |
| 93 expect(files.getFile('/main.dart').exists, true, | |
| 94 reason: '`/main.dart` is missing'); | |
| 95 | |
| 96 var uriResolver = new TestUriResolver(files); | |
| 97 // Enable task model strong mode | |
| 98 var context = AnalysisEngine.instance.createAnalysisContext(); | |
| 99 context.analysisOptions.strongMode = true; | |
| 100 context.analysisOptions.strongModeHints = true; | |
| 101 context.sourceFactory = new SourceFactory([ | |
| 102 new MockDartSdk(_mockSdkSources, reportMissing: true).resolver, | |
| 103 uriResolver | |
| 104 ]); | |
| 105 | |
| 106 // Run the checker on /main.dart. | |
| 107 Source mainSource = uriResolver.resolveAbsolute(new Uri.file('/main.dart')); | |
| 108 var initialLibrary = | |
| 109 context.resolveCompilationUnit2(mainSource, mainSource); | |
| 110 | |
| 111 var collector = new _ErrorCollector(); | |
| 112 var checker = new CodeChecker( | |
| 113 context.typeProvider, new StrongTypeSystemImpl(), collector, | |
| 114 hints: true); | |
| 115 | |
| 116 // Extract expectations from the comments in the test files, and | |
| 117 // check that all errors we emit are included in the expected map. | |
| 118 var allLibraries = reachableLibraries(initialLibrary.element.library); | |
| 119 for (var lib in allLibraries) { | |
| 120 for (var unit in lib.units) { | |
| 121 var errors = <AnalysisError>[]; | |
| 122 collector.errors = errors; | |
| 123 | |
| 124 var source = unit.source; | |
| 125 if (source.uri.scheme == 'dart') continue; | |
| 126 | |
| 127 var librarySource = context.getLibrariesContaining(source).single; | |
| 128 var resolved = context.resolveCompilationUnit2(source, librarySource); | |
| 129 var analyzerErrors = context | |
| 130 .getErrors(source) | |
| 131 .errors | |
| 132 .where((error) => | |
| 133 error.errorCode.name.startsWith('STRONG_MODE_INFERRED_TYPE')) | |
| 134 .toList(); | |
| 135 errors.addAll(analyzerErrors); | |
| 136 checker.visitCompilationUnit(resolved); | |
| 137 | |
| 138 new _ExpectedErrorVisitor(errors).validate(resolved); | |
| 139 } | |
| 140 } | |
| 141 } | |
| 34 | 142 |
| 35 /// Sample mock SDK sources. | 143 /// Sample mock SDK sources. |
| 36 final Map<String, String> mockSdkSources = { | 144 final Map<String, String> _mockSdkSources = { |
| 37 // The list of types below is derived from: | 145 // The list of types below is derived from: |
| 38 // * types we use via our smoke queries, including HtmlElement and | 146 // * types we use via our smoke queries, including HtmlElement and |
| 39 // types from `_typeHandlers` (deserialize.dart) | 147 // types from `_typeHandlers` (deserialize.dart) |
| 40 // * types that are used internally by the resolver (see | 148 // * types that are used internally by the resolver (see |
| 41 // _initializeFrom in resolver.dart). | 149 // _initializeFrom in resolver.dart). |
| 42 'dart:core': ''' | 150 'dart:core': ''' |
| 43 library dart.core; | 151 library dart.core; |
| 44 | 152 |
| 45 void print(Object o) {} | 153 void print(Object o) {} |
| 46 | 154 |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 121 | 229 |
| 122 'dart:_foreign_helper': ''' | 230 'dart:_foreign_helper': ''' |
| 123 library dart._foreign_helper; | 231 library dart._foreign_helper; |
| 124 | 232 |
| 125 JS(String typeDescription, String codeTemplate, | 233 JS(String typeDescription, String codeTemplate, |
| 126 [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) | 234 [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11]) |
| 127 {} | 235 {} |
| 128 ''' | 236 ''' |
| 129 }; | 237 }; |
| 130 | 238 |
| 131 /// Returns an ANSII color escape sequence corresponding to [levelName]. Colors | |
| 132 /// are defined for: severe, error, warning, or info. Returns null if the level | |
| 133 /// name is not recognized. | |
| 134 String colorOf(String levelName) { | |
| 135 levelName = levelName.toLowerCase(); | |
| 136 if (levelName == 'shout' || levelName == 'severe' || levelName == 'error') { | |
| 137 return _RED_COLOR; | |
| 138 } | |
| 139 if (levelName == 'warning') return _MAGENTA_COLOR; | |
| 140 if (levelName == 'info') return _CYAN_COLOR; | |
| 141 return null; | |
| 142 } | |
| 143 | |
| 144 SourceSpanWithContext createSpanHelper( | 239 SourceSpanWithContext createSpanHelper( |
| 145 LineInfo lineInfo, int start, int end, Source source, String content) { | 240 LineInfo lineInfo, int start, int end, Source source, String content) { |
| 146 var startLoc = locationForOffset(lineInfo, source.uri, start); | 241 var startLoc = locationForOffset(lineInfo, source.uri, start); |
| 147 var endLoc = locationForOffset(lineInfo, source.uri, end); | 242 var endLoc = locationForOffset(lineInfo, source.uri, end); |
| 148 | 243 |
| 149 var lineStart = startLoc.offset - startLoc.column; | 244 var lineStart = startLoc.offset - startLoc.column; |
| 150 // Find the end of the line. This is not exposed directly on LineInfo, but | 245 // Find the end of the line. This is not exposed directly on LineInfo, but |
| 151 // we can find it pretty easily. | 246 // we can find it pretty easily. |
| 152 // TODO(jmesserly): for now we do the simple linear scan. Ideally we can get | 247 // TODO(jmesserly): for now we do the simple linear scan. Ideally we can get |
| 153 // some help from the LineInfo API. | 248 // some help from the LineInfo API. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 188 if (seen.contains(lib)) return; | 283 if (seen.contains(lib)) return; |
| 189 seen.add(lib); | 284 seen.add(lib); |
| 190 results.add(lib); | 285 results.add(lib); |
| 191 lib.importedLibraries.forEach(find); | 286 lib.importedLibraries.forEach(find); |
| 192 lib.exportedLibraries.forEach(find); | 287 lib.exportedLibraries.forEach(find); |
| 193 } | 288 } |
| 194 find(start); | 289 find(start); |
| 195 return results; | 290 return results; |
| 196 } | 291 } |
| 197 | 292 |
| 198 /// Run the checker on a program with files contents as indicated in | |
| 199 /// [testFiles]. | |
| 200 /// | |
| 201 /// This function makes several assumptions to make it easier to describe error | |
| 202 /// expectations: | |
| 203 /// | |
| 204 /// * a file named `/main.dart` exists in [testFiles]. | |
| 205 /// * all expected failures are listed in the source code using comments | |
| 206 /// immediately in front of the AST node that should contain the error. | |
| 207 /// * errors are formatted as a token `level:Type`, where `level` is the | |
| 208 /// logging level were the error would be reported at, and `Type` is the | |
| 209 /// concrete subclass of [StaticInfo] that denotes the error. | |
| 210 /// | |
| 211 /// For example, to check that an assignment produces a warning about a boxing | |
| 212 /// conversion, you can describe the test as follows: | |
| 213 /// | |
| 214 /// testChecker({ | |
| 215 /// '/main.dart': ''' | |
| 216 /// testMethod() { | |
| 217 /// dynamic x = /*warning:Box*/3; | |
| 218 /// } | |
| 219 /// ''' | |
| 220 /// }); | |
| 221 /// | |
| 222 void testChecker(String name, Map<String, String> testFiles) { | |
| 223 test(name, () { | |
| 224 AnalysisEngine.instance.processRequiredPlugins(); | |
| 225 expect(testFiles.containsKey('/main.dart'), isTrue, | |
| 226 reason: '`/main.dart` is missing in testFiles'); | |
| 227 | |
| 228 var provider = new MemoryResourceProvider(); | |
| 229 testFiles.forEach((key, value) { | |
| 230 var scheme = 'package:'; | |
| 231 if (key.startsWith(scheme)) { | |
| 232 key = '/packages/${key.substring(scheme.length)}'; | |
| 233 } | |
| 234 provider.newFile(key, value); | |
| 235 }); | |
| 236 var uriResolver = new TestUriResolver(provider); | |
| 237 // Enable task model strong mode | |
| 238 var context = AnalysisEngine.instance.createAnalysisContext(); | |
| 239 context.analysisOptions.strongMode = true; | |
| 240 context.analysisOptions.strongModeHints = true; | |
| 241 | |
| 242 context.sourceFactory = new SourceFactory([ | |
| 243 new MockDartSdk(mockSdkSources, reportMissing: true).resolver, | |
| 244 uriResolver | |
| 245 ]); | |
| 246 | |
| 247 // Run the checker on /main.dart. | |
| 248 Source mainSource = uriResolver.resolveAbsolute(new Uri.file('/main.dart')); | |
| 249 var initialLibrary = | |
| 250 context.resolveCompilationUnit2(mainSource, mainSource); | |
| 251 | |
| 252 var collector = new _ErrorCollector(); | |
| 253 var checker = new CodeChecker( | |
| 254 context.typeProvider, new StrongTypeSystemImpl(), collector, | |
| 255 hints: true); | |
| 256 | |
| 257 // Extract expectations from the comments in the test files, and | |
| 258 // check that all errors we emit are included in the expected map. | |
| 259 var allLibraries = reachableLibraries(initialLibrary.element.library); | |
| 260 for (var lib in allLibraries) { | |
| 261 for (var unit in lib.units) { | |
| 262 var errors = <AnalysisError>[]; | |
| 263 collector.errors = errors; | |
| 264 | |
| 265 var source = unit.source; | |
| 266 if (source.uri.scheme == 'dart') continue; | |
| 267 | |
| 268 var librarySource = context.getLibrariesContaining(source).single; | |
| 269 var resolved = context.resolveCompilationUnit2(source, librarySource); | |
| 270 var analyzerErrors = context | |
| 271 .getErrors(source) | |
| 272 .errors | |
| 273 .where((error) => | |
| 274 error.errorCode.name.startsWith('STRONG_MODE_INFERRED_TYPE')) | |
| 275 .toList(); | |
| 276 errors.addAll(analyzerErrors); | |
| 277 checker.visitCompilationUnit(resolved); | |
| 278 | |
| 279 new _ExpectedErrorVisitor(errors).validate(resolved); | |
| 280 } | |
| 281 } | |
| 282 }); | |
| 283 } | |
| 284 | |
| 285 /// Dart SDK which contains a mock implementation of the SDK libraries. May be | 293 /// Dart SDK which contains a mock implementation of the SDK libraries. May be |
| 286 /// used to speed up execution when most of the core libraries is not needed. | 294 /// used to speed up execution when most of the core libraries is not needed. |
| 287 class MockDartSdk implements DartSdk { | 295 class MockDartSdk implements DartSdk { |
| 288 final Map<Uri, _MockSdkSource> _sources = {}; | 296 final Map<Uri, _MockSdkSource> _sources = {}; |
| 289 final bool reportMissing; | 297 final bool reportMissing; |
| 290 final Map<String, SdkLibrary> _libs = {}; | 298 final Map<String, SdkLibrary> _libs = {}; |
| 291 final String sdkVersion = '0'; | 299 final String sdkVersion = '0'; |
| 292 final AnalysisContext context = new SdkAnalysisContext(); | 300 final AnalysisContext context = new SdkAnalysisContext(); |
| 293 DartUriResolver _resolver; | 301 DartUriResolver _resolver; |
| 294 MockDartSdk(Map<String, String> sources, {this.reportMissing}) { | 302 MockDartSdk(Map<String, String> sources, {this.reportMissing}) { |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 478 reason: 'expected different error type at:\n\n$actualMsg'); | 486 reason: 'expected different error type at:\n\n$actualMsg'); |
| 479 | 487 |
| 480 // We found it. Stop the search. | 488 // We found it. Stop the search. |
| 481 _actualErrors.remove(actual); | 489 _actualErrors.remove(actual); |
| 482 return; | 490 return; |
| 483 } | 491 } |
| 484 } | 492 } |
| 485 | 493 |
| 486 var span = _createSpan(node.offset, node.length); | 494 var span = _createSpan(node.offset, node.length); |
| 487 var levelName = expected.level.name.toLowerCase(); | 495 var levelName = expected.level.name.toLowerCase(); |
| 488 var msg = span.message(expected.typeName, color: colorOf(levelName)); | 496 var msg = span.message(expected.typeName, color: _colorOf(levelName)); |
| 489 fail('expected error was not reported at:\n\n$levelName: $msg'); | 497 fail('expected error was not reported at:\n\n$levelName: $msg'); |
| 490 } | 498 } |
| 491 | 499 |
| 492 String _formatActualError(AnalysisError actual) { | 500 String _formatActualError(AnalysisError actual) { |
| 493 var span = _createSpan(actual.offset, actual.length); | 501 var span = _createSpan(actual.offset, actual.length); |
| 494 var levelName = _actualErrorLevel(actual).name.toLowerCase(); | 502 var levelName = _actualErrorLevel(actual).name.toLowerCase(); |
| 495 var msg = span.message(actual.message, color: colorOf(levelName)); | 503 var msg = span.message(actual.message, color: _colorOf(levelName)); |
| 496 return '$levelName: [${errorCodeName(actual.errorCode)}] $msg'; | 504 return '$levelName: [${errorCodeName(actual.errorCode)}] $msg'; |
| 497 } | 505 } |
| 506 | |
| 507 /// Returns an ANSII color escape sequence corresponding to [levelName]. Color s | |
| 508 /// are defined for: severe, error, warning, or info. Returns null if the leve l | |
|
Bob Nystrom
2016/02/05 22:54:39
Long lines.
Jennifer Messerly
2016/02/05 23:22:19
Fixed.
| |
| 509 /// name is not recognized. | |
| 510 String _colorOf(String levelName) { | |
| 511 const String CYAN_COLOR = '\u001b[36m'; | |
| 512 const String MAGENTA_COLOR = '\u001b[35m'; | |
| 513 const String RED_COLOR = '\u001b[31m'; | |
| 514 | |
| 515 levelName = levelName.toLowerCase(); | |
| 516 if (levelName == 'shout' || levelName == 'severe' || levelName == 'error') { | |
| 517 return RED_COLOR; | |
| 518 } | |
| 519 if (levelName == 'warning') return MAGENTA_COLOR; | |
| 520 if (levelName == 'info') return CYAN_COLOR; | |
| 521 return null; | |
| 522 } | |
| 498 } | 523 } |
| 499 | 524 |
| 500 class _MockSdkSource implements Source { | 525 class _MockSdkSource implements Source { |
| 501 /// Absolute URI which this source can be imported from. | 526 /// Absolute URI which this source can be imported from. |
| 502 final Uri uri; | 527 final Uri uri; |
| 503 final String _contents; | 528 final String _contents; |
| 504 | 529 |
| 505 final int modificationStamp = 1; | 530 final int modificationStamp = 1; |
| 506 | 531 |
| 507 _MockSdkSource(this.uri, this._contents); | 532 _MockSdkSource(this.uri, this._contents); |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 524 UriKind get uriKind => UriKind.DART_URI; | 549 UriKind get uriKind => UriKind.DART_URI; |
| 525 | 550 |
| 526 bool exists() => true; | 551 bool exists() => true; |
| 527 | 552 |
| 528 Source resolveRelative(Uri relativeUri) => | 553 Source resolveRelative(Uri relativeUri) => |
| 529 throw new UnsupportedError('not expecting relative urls in dart: mocks'); | 554 throw new UnsupportedError('not expecting relative urls in dart: mocks'); |
| 530 | 555 |
| 531 Uri resolveRelativeUri(Uri relativeUri) => | 556 Uri resolveRelativeUri(Uri relativeUri) => |
| 532 throw new UnsupportedError('not expecting relative urls in dart: mocks'); | 557 throw new UnsupportedError('not expecting relative urls in dart: mocks'); |
| 533 } | 558 } |
| OLD | NEW |