| 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 library dev_compiler.src.testing; | 5 library dev_compiler.src.testing; |
| 6 | 6 |
| 7 import 'package:analyzer/file_system/file_system.dart'; |
| 8 import 'package:analyzer/file_system/memory_file_system.dart'; |
| 7 import 'package:analyzer/src/generated/ast.dart'; | 9 import 'package:analyzer/src/generated/ast.dart'; |
| 8 import 'package:analyzer/src/generated/element.dart'; | 10 import 'package:analyzer/src/generated/element.dart'; |
| 9 import 'package:analyzer/src/generated/engine.dart' | 11 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| 10 show AnalysisContext, TimestampedData; | |
| 11 import 'package:analyzer/src/generated/source.dart'; | |
| 12 import 'package:logging/logging.dart'; | 12 import 'package:logging/logging.dart'; |
| 13 import 'package:path/path.dart' as path; | |
| 14 import 'package:source_span/source_span.dart'; | 13 import 'package:source_span/source_span.dart'; |
| 15 import 'package:unittest/unittest.dart'; | 14 import 'package:unittest/unittest.dart'; |
| 16 | 15 |
| 17 import 'package:dev_compiler/src/checker/dart_sdk.dart' | 16 import 'package:dev_compiler/src/checker/dart_sdk.dart' |
| 18 show mockSdkSources, dartSdkDirectory; | 17 show mockSdkSources, dartSdkDirectory; |
| 19 import 'package:dev_compiler/src/checker/resolver.dart' show TypeResolver; | 18 import 'package:dev_compiler/src/checker/resolver.dart' show TypeResolver; |
| 20 import 'package:dev_compiler/src/utils.dart'; | 19 import 'package:dev_compiler/src/utils.dart'; |
| 21 import 'package:dev_compiler/src/in_memory.dart'; | |
| 22 import 'package:dev_compiler/src/info.dart'; | 20 import 'package:dev_compiler/src/info.dart'; |
| 23 import 'package:dev_compiler/src/options.dart'; | 21 import 'package:dev_compiler/src/options.dart'; |
| 24 import 'package:dev_compiler/src/report.dart'; | 22 import 'package:dev_compiler/src/report.dart'; |
| 25 import 'package:dev_compiler/config.dart'; | 23 import 'package:dev_compiler/config.dart'; |
| 26 import 'package:dev_compiler/devc.dart' show Compiler; | 24 import 'package:dev_compiler/devc.dart' show Compiler; |
| 27 | 25 |
| 28 import 'dependency_graph.dart' show runtimeFilesForServerMode; | 26 import 'dependency_graph.dart' show runtimeFilesForServerMode; |
| 29 | 27 |
| 30 /// Run the checker on a program with files contents as indicated in | 28 /// Run the checker on a program with files contents as indicated in |
| 31 /// [testFiles]. | 29 /// [testFiles]. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 56 CheckerReporter createReporter(AnalysisContext context), | 54 CheckerReporter createReporter(AnalysisContext context), |
| 57 covariantGenerics: true, relaxedCasts: true, | 55 covariantGenerics: true, relaxedCasts: true, |
| 58 inferDownwards: RulesOptions.inferDownwardsDefault, | 56 inferDownwards: RulesOptions.inferDownwardsDefault, |
| 59 inferFromOverrides: ResolverOptions.inferFromOverridesDefault, | 57 inferFromOverrides: ResolverOptions.inferFromOverridesDefault, |
| 60 inferTransitively: ResolverOptions.inferTransitivelyDefault, | 58 inferTransitively: ResolverOptions.inferTransitivelyDefault, |
| 61 nonnullableTypes: TypeOptions.NONNULLABLE_TYPES, | 59 nonnullableTypes: TypeOptions.NONNULLABLE_TYPES, |
| 62 wrapClosures: RulesOptions.wrapClosuresDefault}) { | 60 wrapClosures: RulesOptions.wrapClosuresDefault}) { |
| 63 expect(testFiles.containsKey('/main.dart'), isTrue, | 61 expect(testFiles.containsKey('/main.dart'), isTrue, |
| 64 reason: '`/main.dart` is missing in testFiles'); | 62 reason: '`/main.dart` is missing in testFiles'); |
| 65 | 63 |
| 66 // Create a resolver that can load test files from memory. | 64 var provider = createTestResourceProvider(testFiles); |
| 67 var testUriResolver = new InMemoryUriResolver(testFiles); | 65 var uriResolver = new TestUriResolver(provider); |
| 66 |
| 68 var options = new CompilerOptions( | 67 var options = new CompilerOptions( |
| 69 allowConstCasts: allowConstCasts, | 68 allowConstCasts: allowConstCasts, |
| 70 covariantGenerics: covariantGenerics, | 69 covariantGenerics: covariantGenerics, |
| 71 relaxedCasts: relaxedCasts, | 70 relaxedCasts: relaxedCasts, |
| 72 inferDownwards: inferDownwards, | 71 inferDownwards: inferDownwards, |
| 73 inferFromOverrides: inferFromOverrides, | 72 inferFromOverrides: inferFromOverrides, |
| 74 inferTransitively: inferTransitively, | 73 inferTransitively: inferTransitively, |
| 75 nonnullableTypes: nonnullableTypes, | 74 nonnullableTypes: nonnullableTypes, |
| 76 wrapClosures: wrapClosures, | 75 wrapClosures: wrapClosures, |
| 77 useMockSdk: sdkDir == null, | 76 useMockSdk: sdkDir == null, |
| 78 dartSdkPath: sdkDir, | 77 dartSdkPath: sdkDir, |
| 79 runtimeDir: '/dev_compiler_runtime/', | 78 runtimeDir: '/dev_compiler_runtime/', |
| 80 entryPointFile: '/main.dart'); | 79 entryPointFile: '/main.dart'); |
| 81 var resolver = sdkDir == null | 80 var resolver = sdkDir == null |
| 82 ? new TypeResolver.fromMock(mockSdkSources, options, | 81 ? new TypeResolver.fromMock(mockSdkSources, options, |
| 83 otherResolvers: [testUriResolver]) | 82 otherResolvers: [uriResolver]) |
| 84 : new TypeResolver.fromDir(sdkDir, options, | 83 : new TypeResolver.fromDir(sdkDir, options, |
| 85 otherResolvers: [testUriResolver]); | 84 otherResolvers: [uriResolver]); |
| 86 var context = resolver.context; | 85 var context = resolver.context; |
| 87 | 86 |
| 88 // Run the checker on /main.dart. | 87 // Run the checker on /main.dart. |
| 89 var mainFile = new Uri.file('/main.dart'); | 88 var mainFile = new Uri.file('/main.dart'); |
| 90 var checkExpectations = createReporter == null; | 89 TestReporter testReporter; |
| 91 var reporter = (createReporter == null) | 90 CheckerReporter reporter; |
| 92 ? new TestReporter(context) | 91 if (createReporter == null) { |
| 93 : createReporter(context); | 92 reporter = testReporter = new TestReporter(context); |
| 93 } else { |
| 94 reporter = createReporter(context); |
| 95 } |
| 94 var results = | 96 var results = |
| 95 new Compiler(options, resolver: resolver, reporter: reporter).run(); | 97 new Compiler(options, resolver: resolver, reporter: reporter).run(); |
| 96 | 98 |
| 97 // Extract expectations from the comments in the test files. | 99 // Extract expectations from the comments in the test files. |
| 98 var expectedErrors = <AstNode, List<_ErrorExpectation>>{}; | 100 var expectedErrors = <AstNode, List<_ErrorExpectation>>{}; |
| 99 var visitor = new _ErrorMarkerVisitor(expectedErrors); | 101 var visitor = new _ErrorMarkerVisitor(expectedErrors); |
| 100 var initialLibrary = | 102 var initialLibrary = |
| 101 resolver.context.getLibraryElement(testUriResolver.files[mainFile]); | 103 resolver.context.getLibraryElement(uriResolver.resolveAbsolute(mainFile)); |
| 102 for (var lib in reachableLibraries(initialLibrary)) { | 104 for (var lib in reachableLibraries(initialLibrary)) { |
| 103 for (var unit in lib.units) { | 105 for (var unit in lib.units) { |
| 104 unit.unit.accept(visitor); | 106 unit.unit.accept(visitor); |
| 105 } | 107 } |
| 106 } | 108 } |
| 107 | 109 |
| 108 if (!checkExpectations) return results; | 110 if (testReporter == null) return results; |
| 109 | 111 |
| 110 var total = expectedErrors.values.fold(0, (p, l) => p + l.length); | 112 var total = expectedErrors.values.fold(0, (p, l) => p + l.length); |
| 111 // Check that all errors we emit are included in the expected map. | 113 // Check that all errors we emit are included in the expected map. |
| 112 for (var lib in results.libraries) { | 114 for (var lib in results.libraries) { |
| 113 var uri = lib.library.source.uri; | 115 var uri = lib.library.source.uri; |
| 114 (reporter as TestReporter).infoMap[uri].forEach((node, actual) { | 116 testReporter.infoMap[uri].forEach((node, actual) { |
| 115 var expected = expectedErrors[node]; | 117 var expected = expectedErrors[node]; |
| 116 var expectedTotal = expected == null ? 0 : expected.length; | 118 var expectedTotal = expected == null ? 0 : expected.length; |
| 117 if (actual.length != expectedTotal) { | 119 if (actual.length != expectedTotal) { |
| 118 expect(actual.length, expectedTotal, | 120 expect(actual.length, expectedTotal, |
| 119 reason: 'The checker found ${actual.length} errors on the ' | 121 reason: 'The checker found ${actual.length} errors on the ' |
| 120 'expression `$node`, but we expected $expectedTotal. These are the ' | 122 'expression `$node`, but we expected $expectedTotal. These are the ' |
| 121 'errors the checker found:\n\n ${_unexpectedErrors(node, actual)}'); | 123 'errors the checker found:\n\n ${_unexpectedErrors(node, actual)}'); |
| 122 } | 124 } |
| 123 | 125 |
| 124 for (int i = 0; i < expected.length; i++) { | 126 for (int i = 0; i < expected.length; i++) { |
| (...skipping 16 matching lines...) Expand all Loading... |
| 141 fail('Not all expected errors were reported by the checker. Only' | 143 fail('Not all expected errors were reported by the checker. Only' |
| 142 ' ${total - newTotal} out of $total expected errors were reported.\n' | 144 ' ${total - newTotal} out of $total expected errors were reported.\n' |
| 143 'The following errors were not reported:\n' | 145 'The following errors were not reported:\n' |
| 144 '${_unreportedErrors(expectedErrors)}'); | 146 '${_unreportedErrors(expectedErrors)}'); |
| 145 } | 147 } |
| 146 } | 148 } |
| 147 | 149 |
| 148 return results; | 150 return results; |
| 149 } | 151 } |
| 150 | 152 |
| 153 /// Creates a [MemoryResourceProvider] with test data |
| 154 MemoryResourceProvider createTestResourceProvider( |
| 155 Map<String, String> testFiles) { |
| 156 var provider = new MemoryResourceProvider(); |
| 157 runtimeFilesForServerMode.forEach((filepath) { |
| 158 testFiles['/dev_compiler_runtime/$filepath'] = |
| 159 '/* test contents of $filepath */'; |
| 160 }); |
| 161 testFiles.forEach((key, value) { |
| 162 var scheme = 'package:'; |
| 163 if (key.startsWith(scheme)) { |
| 164 key = '/packages/${key.substring(scheme.length)}'; |
| 165 } |
| 166 provider.newFile(key, value); |
| 167 }); |
| 168 return provider; |
| 169 } |
| 170 |
| 171 class TestUriResolver extends ResourceUriResolver { |
| 172 final MemoryResourceProvider provider; |
| 173 TestUriResolver(provider) |
| 174 : provider = provider, |
| 175 super(provider); |
| 176 resolveAbsolute(Uri uri) { |
| 177 if (uri.scheme == 'package') { |
| 178 return (provider.getResource('/packages/' + uri.path) as File) |
| 179 .createSource(uri); |
| 180 } |
| 181 return super.resolveAbsolute(uri); |
| 182 } |
| 183 } |
| 184 |
| 151 class TestReporter extends SummaryReporter { | 185 class TestReporter extends SummaryReporter { |
| 152 Map<Uri, Map<AstNode, List<StaticInfo>>> infoMap = {}; | 186 Map<Uri, Map<AstNode, List<StaticInfo>>> infoMap = {}; |
| 153 Uri _current; | 187 Map<AstNode, List<StaticInfo>> _current; |
| 154 | 188 |
| 155 TestReporter(AnalysisContext context) : super(context); | 189 TestReporter(AnalysisContext context) : super(context); |
| 156 | 190 |
| 157 void enterLibrary(Uri uri) { | 191 void enterLibrary(Uri uri) { |
| 158 super.enterLibrary(uri); | 192 super.enterLibrary(uri); |
| 159 infoMap[uri] = {}; | 193 infoMap[uri] = _current = {}; |
| 160 _current = uri; | |
| 161 } | 194 } |
| 162 | 195 |
| 163 void log(Message info) { | 196 void log(Message info) { |
| 164 super.log(info); | 197 super.log(info); |
| 165 if (info is StaticInfo) { | 198 if (info is StaticInfo) { |
| 166 infoMap[_current].putIfAbsent(info.node, () => []).add(info); | 199 _current.putIfAbsent(info.node, () => []).add(info); |
| 167 } | 200 } |
| 168 } | 201 } |
| 169 } | 202 } |
| 170 | 203 |
| 171 /// Create an error explanation for errors that were not expected, but that the | 204 /// Create an error explanation for errors that were not expected, but that the |
| 172 /// checker produced. | 205 /// checker produced. |
| 173 String _unexpectedErrors(AstNode node, List errors) { | 206 String _unexpectedErrors(AstNode node, List errors) { |
| 174 final span = _spanFor(node); | 207 final span = _spanFor(node); |
| 175 return errors.map((e) { | 208 return errors.map((e) { |
| 176 var level = e.level.name.toLowerCase(); | 209 var level = e.level.name.toLowerCase(); |
| (...skipping 13 matching lines...) Expand all Loading... |
| 190 return sb.toString(); | 223 return sb.toString(); |
| 191 } | 224 } |
| 192 | 225 |
| 193 String _messageWithSpan(StaticInfo info) { | 226 String _messageWithSpan(StaticInfo info) { |
| 194 var span = _spanFor(info.node); | 227 var span = _spanFor(info.node); |
| 195 var level = info.level.name.toLowerCase(); | 228 var level = info.level.name.toLowerCase(); |
| 196 return '$level: ${span.message(info.message, color: colorOf(level))}'; | 229 return '$level: ${span.message(info.message, color: colorOf(level))}'; |
| 197 } | 230 } |
| 198 | 231 |
| 199 SourceSpan _spanFor(AstNode node) { | 232 SourceSpan _spanFor(AstNode node) { |
| 200 var root = node.root as CompilationUnit; | 233 var unit = node.root as CompilationUnit; |
| 201 InMemorySource source = (root.element as CompilationUnitElementImpl).source; | 234 var source = unit.element.source; |
| 202 return source.spanFor(node); | 235 // This reads the file. Only safe in tests, because they use MemoryFileSystem. |
| 236 var content = source.contents.data; |
| 237 return createSpanHelper(unit, node.offset, node.end, source, content); |
| 203 } | 238 } |
| 204 | 239 |
| 205 /// Visitor that extracts expected errors from comments. | 240 /// Visitor that extracts expected errors from comments. |
| 206 class _ErrorMarkerVisitor extends UnifyingAstVisitor { | 241 class _ErrorMarkerVisitor extends UnifyingAstVisitor { |
| 207 Map<AstNode, List<_ErrorExpectation>> expectedErrors; | 242 Map<AstNode, List<_ErrorExpectation>> expectedErrors; |
| 208 | 243 |
| 209 _ErrorMarkerVisitor(this.expectedErrors); | 244 _ErrorMarkerVisitor(this.expectedErrors); |
| 210 | 245 |
| 211 visitNode(AstNode node) { | 246 visitNode(AstNode node) { |
| 212 var token = node.beginToken; | 247 var token = node.beginToken; |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 expect(tokens[1], "should", reason: 'invalid error descriptor'); | 307 expect(tokens[1], "should", reason: 'invalid error descriptor'); |
| 273 expect(tokens[2], "be", reason: 'invalid error descriptor'); | 308 expect(tokens[2], "be", reason: 'invalid error descriptor'); |
| 274 if (tokens[0] == "pass") return null; | 309 if (tokens[0] == "pass") return null; |
| 275 // TODO(leafp) For now, we just use whatever the current expectation is, | 310 // TODO(leafp) For now, we just use whatever the current expectation is, |
| 276 // eventually we could do more automated reporting here. | 311 // eventually we could do more automated reporting here. |
| 277 return _parse(tokens[0]); | 312 return _parse(tokens[0]); |
| 278 } | 313 } |
| 279 | 314 |
| 280 String toString() => '$level $type'; | 315 String toString() => '$level $type'; |
| 281 } | 316 } |
| OLD | NEW |