Chromium Code Reviews| Index: lib/src/testing.dart |
| diff --git a/lib/src/testing.dart b/lib/src/testing.dart |
| index 590298ee75645270505619afe6b31ddf63a7b638..a6033f6044205e784e40e0e5b98d64a0a1376ac8 100644 |
| --- a/lib/src/testing.dart |
| +++ b/lib/src/testing.dart |
| @@ -4,22 +4,23 @@ |
| library dev_compiler.src.testing; |
| +import 'dart:collection' show Queue; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/memory_file_system.dart'; |
| import 'package:analyzer/src/generated/ast.dart'; |
| -import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
| +import 'package:analyzer/src/generated/engine.dart' |
| + show AnalysisContext, AnalysisEngine; |
| +import 'package:analyzer/src/generated/error.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:source_span/source_span.dart'; |
| import 'package:test/test.dart'; |
| -import 'package:dev_compiler/config.dart'; |
| -import 'package:dev_compiler/devc.dart' show Compiler; |
| +import 'package:dev_compiler/strong_mode.dart'; |
| import 'analysis_context.dart'; |
| import 'dependency_graph.dart' show runtimeFilesForServerMode; |
| import 'info.dart'; |
| import 'options.dart'; |
| -import 'report.dart'; |
| import 'utils.dart'; |
| /// Run the checker on a program with files contents as indicated in |
| @@ -46,97 +47,47 @@ import 'utils.dart'; |
| /// ''' |
| /// }); |
| /// |
| -CheckerResults testChecker(Map<String, String> testFiles, {String sdkDir, |
| - customUrlMappings: const {}, |
| - CheckerReporter createReporter(AnalysisContext context), relaxedCasts: true, |
| - inferDownwards: RulesOptions.inferDownwardsDefault, |
| - inferFromOverrides: ResolverOptions.inferFromOverridesDefault, |
| - inferTransitively: ResolverOptions.inferTransitivelyDefault, |
| - nonnullableTypes: TypeOptions.NONNULLABLE_TYPES}) { |
| +void testChecker(Map<String, String> testFiles, {String sdkDir, |
|
Jennifer Messerly
2015/06/10 22:34:17
big refactor here to switch it to test StrongCheck
|
| + customUrlMappings: const {}, relaxedCasts: true, |
| + inferDownwards: StrongModeOptions.inferDownwardsDefault, |
| + inferFromOverrides: StrongModeOptions.inferFromOverridesDefault, |
| + inferTransitively: StrongModeOptions.inferTransitivelyDefault, |
| + nonnullableTypes: StrongModeOptions.NONNULLABLE_TYPES}) { |
| expect(testFiles.containsKey('/main.dart'), isTrue, |
| reason: '`/main.dart` is missing in testFiles'); |
| var provider = createTestResourceProvider(testFiles); |
| var uriResolver = new TestUriResolver(provider); |
| + var context = AnalysisEngine.instance.createAnalysisContext(); |
| + context.sourceFactory = createSourceFactory(new SourceResolverOptions( |
| + customUrlMappings: customUrlMappings, |
| + useMockSdk: sdkDir == null, |
| + dartSdkPath: sdkDir, |
| + entryPointFile: '/main.dart'), fileResolvers: [uriResolver]); |
| - var options = new CompilerOptions( |
| + var checker = new StrongChecker(context, new StrongModeOptions( |
| relaxedCasts: relaxedCasts, |
| inferDownwards: inferDownwards, |
| inferFromOverrides: inferFromOverrides, |
| inferTransitively: inferTransitively, |
| nonnullableTypes: nonnullableTypes, |
| - useMockSdk: sdkDir == null, |
| - dartSdkPath: sdkDir, |
| - runtimeDir: '/dev_compiler_runtime/', |
| - entryPointFile: '/main.dart', |
| - customUrlMappings: customUrlMappings); |
| - |
| - var context = createAnalysisContext(options, fileResolvers: [uriResolver]); |
| + hints: true)); |
| // Run the checker on /main.dart. |
| - var mainFile = new Uri.file('/main.dart'); |
| - TestReporter testReporter; |
| - CheckerReporter reporter; |
| - if (createReporter == null) { |
| - reporter = testReporter = new TestReporter(context); |
| - } else { |
| - reporter = createReporter(context); |
| - } |
| - var results = |
| - new Compiler(options, context: context, reporter: reporter).run(); |
| + var mainSource = uriResolver.resolveAbsolute(new Uri.file('/main.dart')); |
| + var initialLibrary = context.resolveCompilationUnit2(mainSource, mainSource); |
| - // Extract expectations from the comments in the test files. |
| - var expectedErrors = <AstNode, List<_ErrorExpectation>>{}; |
| - var visitor = new _ErrorMarkerVisitor(expectedErrors); |
| - var initialLibrary = |
| - context.getLibraryElement(uriResolver.resolveAbsolute(mainFile)); |
| - for (var lib in reachableLibraries(initialLibrary)) { |
| + // Extract expectations from the comments in the test files, and |
| + // check that all errors we emit are included in the expected map. |
| + var allLibraries = reachableLibraries(initialLibrary.element.library); |
| + for (var lib in allLibraries) { |
| for (var unit in lib.units) { |
| - unit.unit.accept(visitor); |
| - } |
| - } |
| - |
| - if (testReporter == null) return results; |
| - |
| - var total = expectedErrors.values.fold(0, (p, l) => p + l.length); |
| - // Check that all errors we emit are included in the expected map. |
| - for (var lib in results.libraries) { |
| - var uri = lib.library.source.uri; |
| - testReporter.infoMap[uri].forEach((node, actual) { |
| - var expected = expectedErrors[node]; |
| - var expectedTotal = expected == null ? 0 : expected.length; |
| - if (actual.length != expectedTotal) { |
| - expect(actual.length, expectedTotal, |
| - reason: 'The checker found ${actual.length} errors on the ' |
| - 'expression `$node`, but we expected $expectedTotal. These are the ' |
| - 'errors the checker found:\n\n ${_unexpectedErrors(node, actual)}'); |
| - } |
| + if (unit.source.uri.scheme == 'dart') continue; |
| - for (int i = 0; i < expected.length; i++) { |
| - expect(actual[i].level, expected[i].level, |
| - reason: 'expected different logging level at:\n\n' |
| - '${_messageWithSpan(actual[i])}'); |
| - expect(actual[i].runtimeType, expected[i].type, |
| - reason: 'expected different error type at:\n\n' |
| - '${_messageWithSpan(actual[i])}'); |
| - } |
| - expectedErrors.remove(node); |
| - }); |
| - } |
| - |
| - // Check that all expected errors are accounted for. |
| - if (!expectedErrors.isEmpty) { |
| - var newTotal = expectedErrors.values.fold(0, (p, l) => p + l.length); |
| - // Non empty error expectation lists remaining |
| - if (newTotal > 0) { |
| - fail('Not all expected errors were reported by the checker. Only' |
| - ' ${total - newTotal} out of $total expected errors were reported.\n' |
| - 'The following errors were not reported:\n' |
| - '${_unreportedErrors(expectedErrors)}'); |
| + var errorInfo = checker.computeErrors(unit.source); |
| + new _ExpectedErrorVisitor(errorInfo.errors).validate(unit.unit); |
| } |
| } |
| - |
| - return results; |
| } |
| /// Creates a [MemoryResourceProvider] with test data |
| @@ -171,66 +122,27 @@ class TestUriResolver extends ResourceUriResolver { |
| } |
| } |
| -class TestReporter extends SummaryReporter { |
| - Map<Uri, Map<AstNode, List<StaticInfo>>> infoMap = {}; |
| - Map<AstNode, List<StaticInfo>> _current; |
| +class _ExpectedErrorVisitor extends UnifyingAstVisitor { |
| + final Set<AnalysisError> _actualErrors; |
| + CompilationUnit _unit; |
| + String _unitSourceCode; |
| - TestReporter(AnalysisContext context) : super(context); |
| + _ExpectedErrorVisitor(List<AnalysisError> actualErrors) |
| + : _actualErrors = new Set.from(actualErrors); |
| - void enterLibrary(Uri uri) { |
| - super.enterLibrary(uri); |
| - infoMap[uri] = _current = {}; |
| - } |
| + validate(CompilationUnit unit) { |
| + _unit = unit; |
| + // This reads the file. Only safe because tests use MemoryFileSystem. |
| + _unitSourceCode = unit.element.source.contents.data; |
| + |
| + // Visit the compilation unit. |
| + unit.accept(this); |
| - void log(Message info) { |
| - super.log(info); |
| - if (info is StaticInfo) { |
| - _current.putIfAbsent(info.node, () => []).add(info); |
| + if (_actualErrors.isNotEmpty) { |
| + var actualMsgs = _actualErrors.map(_formatActualError).join('\n'); |
| + fail('Unexpected errors reported by checker:\n\n$actualMsgs'); |
| } |
| } |
| -} |
| - |
| -/// Create an error explanation for errors that were not expected, but that the |
| -/// checker produced. |
| -String _unexpectedErrors(AstNode node, List errors) { |
| - final span = _spanFor(node); |
| - return errors.map((e) { |
| - var level = e.level.name.toLowerCase(); |
| - return '$level: ${span.message(e.message, color: colorOf(level))}'; |
| - }).join('\n'); |
| -} |
| - |
| -String _unreportedErrors(Map<AstNode, List<_ErrorExpectation>> expected) { |
| - var sb = new StringBuffer(); |
| - for (var node in expected.keys) { |
| - var span = _spanFor(node); |
| - expected[node].forEach((e) { |
| - var level = e.level.name.toLowerCase(); |
| - sb.write('$level: ${span.message("${e.type}", color: colorOf(level))}\n'); |
| - }); |
| - } |
| - return sb.toString(); |
| -} |
| - |
| -String _messageWithSpan(StaticInfo info) { |
| - var span = _spanFor(info.node); |
| - var level = info.level.name.toLowerCase(); |
| - return '$level: ${span.message(info.message, color: colorOf(level))}'; |
| -} |
| - |
| -SourceSpan _spanFor(AstNode node) { |
| - var unit = node.root as CompilationUnit; |
| - var source = unit.element.source; |
| - // This reads the file. Only safe in tests, because they use MemoryFileSystem. |
| - var content = source.contents.data; |
| - return createSpanHelper(unit, node.offset, node.end, source, content); |
| -} |
| - |
| -/// Visitor that extracts expected errors from comments. |
| -class _ErrorMarkerVisitor extends UnifyingAstVisitor { |
| - Map<AstNode, List<_ErrorExpectation>> expectedErrors; |
| - |
| - _ErrorMarkerVisitor(this.expectedErrors); |
| visitNode(AstNode node) { |
| var token = node.beginToken; |
| @@ -248,36 +160,79 @@ class _ErrorMarkerVisitor extends UnifyingAstVisitor { |
| if (start != -1 && end != -1) { |
| expect(start, lessThan(end)); |
| var errors = commentText.substring(start + 2, end).split(','); |
| - var expectations = errors.map(_ErrorExpectation.parse); |
| - expectedErrors[node] = expectations.where((x) => x != null).toList(); |
| + var expectations = |
| + errors.map(_ErrorExpectation.parse).where((x) => x != null); |
| + |
| + for (var e in expectations) _expectError(node, e); |
| } |
| } |
| } |
| return super.visitNode(node); |
| } |
| + |
| + void _expectError(AstNode node, _ErrorExpectation expected) { |
| + |
| + // See if we can find the expected error in our actual errors |
| + for (var actual in _actualErrors) { |
| + if (actual.offset == node.offset && actual.length == node.length) { |
| + var actualMsg = _formatActualError(actual); |
| + expect(_actualErrorLevel(actual), expected.level, |
| + reason: 'expected different error code at:\n\n$actualMsg'); |
| + expect(actual.errorCode.name, expected.typeName, |
| + reason: 'expected different error type at:\n\n$actualMsg'); |
| + |
| + // We found it. Stop the search. |
| + _actualErrors.remove(actual); |
| + return; |
| + } |
| + } |
| + |
| + var span = _createSpan(node.offset, node.length); |
| + var levelName = expected.level.name.toLowerCase(); |
| + var location = |
| + '$levelName: ${span.message(expected.typeName, color: colorOf(levelName))}'; |
|
Jennifer Messerly
2015/06/10 22:34:17
oops, forgot dart_style can't handle this. will fi
|
| + fail('expected error was not reported at:\n\n$location'); |
| + } |
| + |
| + Level _actualErrorLevel(AnalysisError actual) { |
| + return const <ErrorSeverity, Level>{ |
| + ErrorSeverity.ERROR: Level.SEVERE, |
| + ErrorSeverity.WARNING: Level.WARNING, |
| + ErrorSeverity.INFO: Level.INFO |
| + }[actual.errorCode.errorSeverity]; |
| + } |
| + |
| + String _formatActualError(AnalysisError actual) { |
| + var span = _createSpan(actual.offset, actual.length); |
| + var levelName = _actualErrorLevel(actual).name.toLowerCase(); |
| + return '$levelName: ${span.message(actual.message, color: colorOf(levelName))}'; |
|
Jennifer Messerly
2015/06/10 22:34:17
this too
|
| + } |
| + |
| + SourceSpan _createSpan(int offset, int len) { |
| + return createSpanHelper( |
| + _unit, offset, offset + len, _unit.element.source, _unitSourceCode); |
| + } |
| } |
| /// Describes an expected message that should be produced by the checker. |
| class _ErrorExpectation { |
| final Level level; |
| - final Type type; |
| - _ErrorExpectation(this.level, this.type); |
| + final String typeName; |
| + _ErrorExpectation(this.level, this.typeName); |
| static _ErrorExpectation _parse(String descriptor) { |
| var tokens = descriptor.split(':'); |
| expect(tokens.length, 2, reason: 'invalid error descriptor'); |
| var name = tokens[0].toUpperCase(); |
| - var typeName = tokens[1].toLowerCase(); |
| + var typeName = tokens[1]; |
| var level = |
| Level.LEVELS.firstWhere((l) => l.name == name, orElse: () => null); |
| expect(level, isNotNull, |
| reason: 'invalid level in error descriptor: `${tokens[0]}`'); |
| - var type = infoTypes.firstWhere((t) => '$t'.toLowerCase() == typeName, |
| - orElse: () => null); |
| - expect(type, isNotNull, |
| + expect(typeName, isNotNull, |
| reason: 'invalid type in error descriptor: ${tokens[1]}'); |
| - return new _ErrorExpectation(level, type); |
| + return new _ErrorExpectation(level, typeName); |
| } |
| static _ErrorExpectation parse(String descriptor) { |
| @@ -293,5 +248,5 @@ class _ErrorExpectation { |
| return _parse(tokens[0]); |
| } |
| - String toString() => '$level $type'; |
| + String toString() => '$level $typeName'; |
| } |