| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analyzer/dart/ast/ast.dart' show AstNode, SimpleIdentifier; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/error/listener.dart'; |
| import 'package:analyzer/exception/exception.dart'; |
| import 'package:analyzer/src/dart/ast/utilities.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/java_engine.dart'; |
| import 'package:analyzer/src/generated/parser.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:plugin/manager.dart'; |
| import 'package:plugin/plugin.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'analysis_context_factory.dart'; |
| |
| /** |
| * The class `EngineTestCase` defines utility methods for making assertions. |
| */ |
| class EngineTestCase { |
| /** |
| * Flag indicating whether the fasta parser is being used. |
| */ |
| bool get usingFastaParser => Parser.useFasta; |
| |
| /** |
| * Assert that the given collection has the same number of elements as the number of specified |
| * names, and that for each specified name, a corresponding element can be found in the given |
| * collection with that name. |
| * |
| * @param elements the elements |
| * @param names the names |
| */ |
| void assertNamedElements(List<Element> elements, List<String> names) { |
| for (String elemName in names) { |
| bool found = false; |
| for (Element elem in elements) { |
| if (elem.name == elemName) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.write("Expected element named: "); |
| buffer.write(elemName); |
| buffer.write("\n but found: "); |
| for (Element elem in elements) { |
| buffer.write(elem.name); |
| buffer.write(", "); |
| } |
| fail(buffer.toString()); |
| } |
| } |
| expect(elements, hasLength(names.length)); |
| } |
| |
| AnalysisContext createAnalysisContext() { |
| return AnalysisContextFactory.contextWithCore(); |
| } |
| |
| /** |
| * Return the getter in the given type with the given name. Inherited getters are ignored. |
| * |
| * @param type the type in which the getter is declared |
| * @param getterName the name of the getter to be returned |
| * @return the property accessor element representing the getter with the given name |
| */ |
| PropertyAccessorElement getGetter(InterfaceType type, String getterName) { |
| for (PropertyAccessorElement accessor in type.element.accessors) { |
| if (accessor.isGetter && accessor.name == getterName) { |
| return accessor; |
| } |
| } |
| fail("Could not find getter named $getterName in ${type.displayName}"); |
| } |
| |
| /** |
| * Return the method in the given type with the given name. Inherited methods are ignored. |
| * |
| * @param type the type in which the method is declared |
| * @param methodName the name of the method to be returned |
| * @return the method element representing the method with the given name |
| */ |
| MethodElement getMethod(InterfaceType type, String methodName) { |
| for (MethodElement method in type.element.methods) { |
| if (method.name == methodName) { |
| return method; |
| } |
| } |
| fail("Could not find method named $methodName in ${type.displayName}"); |
| } |
| |
| void setUp() { |
| List<Plugin> plugins = <Plugin>[]; |
| plugins.addAll(AnalysisEngine.instance.requiredPlugins); |
| new ExtensionManager().processPlugins(plugins); |
| } |
| |
| void tearDown() {} |
| |
| /** |
| * Assert that the given object is an instance of the expected class. |
| * |
| * @param expectedClass the class that the object is expected to be an instance of |
| * @param object the object being tested |
| * @return the object that was being tested |
| * @throws Exception if the object is not an instance of the expected class |
| */ |
| static Object assertInstanceOf( |
| Predicate<Object> predicate, Type expectedClass, Object object) { |
| if (!predicate(object)) { |
| fail( |
| "Expected instance of $expectedClass, found ${object == null ? "null" : object.runtimeType}"); |
| } |
| return object; |
| } |
| |
| /** |
| * @return the [AstNode] with requested type at offset of the "prefix". |
| */ |
| static AstNode findNode( |
| AstNode root, String code, String prefix, Predicate<AstNode> predicate) { |
| int offset = code.indexOf(prefix); |
| if (offset == -1) { |
| throw new ArgumentError("Not found '$prefix'."); |
| } |
| AstNode node = new NodeLocator(offset).searchWithin(root); |
| return node.getAncestor(predicate); |
| } |
| |
| /** |
| * Find the [SimpleIdentifier] with at offset of the "prefix". |
| */ |
| static SimpleIdentifier findSimpleIdentifier( |
| AstNode root, String code, String prefix) { |
| int offset = code.indexOf(prefix); |
| if (offset == -1) { |
| throw new ArgumentError("Not found '$prefix'."); |
| } |
| return new NodeLocator(offset).searchWithin(root); |
| } |
| } |
| |
| /** |
| * A description of an error that is expected to be reported. |
| */ |
| class ExpectedError { |
| /** |
| * An empty array of error descriptors used when no errors are expected. |
| */ |
| static List<ExpectedError> NO_ERRORS = <ExpectedError>[]; |
| |
| /** |
| * The error code associated with the error. |
| */ |
| final ErrorCode code; |
| |
| /** |
| * The offset of the beginning of the error's region. |
| */ |
| final int offset; |
| |
| /** |
| * The offset of the beginning of the error's region. |
| */ |
| final int length; |
| |
| /** |
| * Initialize a newly created error description. |
| */ |
| ExpectedError(this.code, this.offset, this.length); |
| } |
| |
| /** |
| * An error listener that collects all of the errors passed to it for later |
| * examination. |
| */ |
| class GatheringErrorListener implements AnalysisErrorListener { |
| /** |
| * A flag indicating whether error ranges are to be compared when comparing |
| * expected and actual errors. |
| */ |
| final bool checkRanges; |
| |
| /** |
| * A list containing the errors that were collected. |
| */ |
| List<AnalysisError> _errors = <AnalysisError>[]; |
| |
| /** |
| * A table mapping sources to the line information for the source. |
| */ |
| Map<Source, LineInfo> _lineInfoMap = <Source, LineInfo>{}; |
| |
| /** |
| * Initialize a newly created error listener to collect errors. |
| */ |
| GatheringErrorListener({this.checkRanges = Parser.useFasta}); |
| |
| /** |
| * Return the errors that were collected. |
| */ |
| List<AnalysisError> get errors => _errors; |
| |
| /** |
| * Return `true` if at least one error has been gathered. |
| */ |
| bool get hasErrors => _errors.length > 0; |
| |
| /** |
| * Add the given [errors] to this listener. |
| */ |
| void addAll(List<AnalysisError> errors) { |
| for (AnalysisError error in errors) { |
| onError(error); |
| } |
| } |
| |
| /** |
| * Add all of the errors recorded by the given [listener] to this listener. |
| */ |
| void addAll2(RecordingErrorListener listener) { |
| addAll(listener.errors); |
| } |
| |
| /** |
| * Assert that the number of errors that have been gathered matches the number |
| * of [expectedErrors] and that they have the expected error codes and |
| * locations. The order in which the errors were gathered is ignored. |
| */ |
| void assertErrors(List<ExpectedError> expectedErrors) { |
| if (_errors.length != expectedErrors.length) { |
| _fail(expectedErrors); |
| } |
| List<ExpectedError> remainingErrors = expectedErrors.toList(); |
| for (AnalysisError error in _errors) { |
| if (!_foundAndRemoved(remainingErrors, error)) { |
| _fail(expectedErrors); |
| } |
| } |
| } |
| |
| /** |
| * Assert that the number of errors that have been gathered matches the number |
| * of [expectedErrorCodes] and that they have the expected error codes. The |
| * order in which the errors were gathered is ignored. |
| */ |
| void assertErrorsWithCodes( |
| [List<ErrorCode> expectedErrorCodes = const <ErrorCode>[]]) { |
| StringBuffer buffer = new StringBuffer(); |
| // |
| // Compute the expected number of each type of error. |
| // |
| Map<ErrorCode, int> expectedCounts = <ErrorCode, int>{}; |
| for (ErrorCode code in expectedErrorCodes) { |
| int count = expectedCounts[code]; |
| if (count == null) { |
| count = 1; |
| } else { |
| count = count + 1; |
| } |
| expectedCounts[code] = count; |
| } |
| // |
| // Compute the actual number of each type of error. |
| // |
| Map<ErrorCode, List<AnalysisError>> errorsByCode = |
| <ErrorCode, List<AnalysisError>>{}; |
| for (AnalysisError error in _errors) { |
| errorsByCode |
| .putIfAbsent(error.errorCode, () => <AnalysisError>[]) |
| .add(error); |
| } |
| // |
| // Compare the expected and actual number of each type of error. |
| // |
| expectedCounts.forEach((ErrorCode code, int expectedCount) { |
| int actualCount; |
| List<AnalysisError> list = errorsByCode.remove(code); |
| if (list == null) { |
| actualCount = 0; |
| } else { |
| actualCount = list.length; |
| } |
| if (actualCount != expectedCount) { |
| if (buffer.length == 0) { |
| buffer.write("Expected "); |
| } else { |
| buffer.write("; "); |
| } |
| buffer.write(expectedCount); |
| buffer.write(" errors of type "); |
| buffer.write(code.uniqueName); |
| buffer.write(", found "); |
| buffer.write(actualCount); |
| } |
| }); |
| // |
| // Check that there are no more errors in the actual-errors map, |
| // otherwise record message. |
| // |
| errorsByCode.forEach((ErrorCode code, List<AnalysisError> actualErrors) { |
| int actualCount = actualErrors.length; |
| if (buffer.length == 0) { |
| buffer.write("Expected "); |
| } else { |
| buffer.write("; "); |
| } |
| buffer.write("0 errors of type "); |
| buffer.write(code.uniqueName); |
| buffer.write(", found "); |
| buffer.write(actualCount); |
| buffer.write(" ("); |
| for (int i = 0; i < actualErrors.length; i++) { |
| AnalysisError error = actualErrors[i]; |
| if (i > 0) { |
| buffer.write(", "); |
| } |
| buffer.write(error.offset); |
| } |
| buffer.write(")"); |
| }); |
| if (buffer.length > 0) { |
| fail(buffer.toString()); |
| } |
| } |
| |
| /** |
| * Assert that the number of errors that have been gathered matches the number |
| * of [expectedSeverities] and that there are the same number of errors and |
| * warnings as specified by the argument. The order in which the errors were |
| * gathered is ignored. |
| */ |
| void assertErrorsWithSeverities(List<ErrorSeverity> expectedSeverities) { |
| int expectedErrorCount = 0; |
| int expectedWarningCount = 0; |
| for (ErrorSeverity severity in expectedSeverities) { |
| if (severity == ErrorSeverity.ERROR) { |
| expectedErrorCount++; |
| } else { |
| expectedWarningCount++; |
| } |
| } |
| int actualErrorCount = 0; |
| int actualWarningCount = 0; |
| for (AnalysisError error in _errors) { |
| if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) { |
| actualErrorCount++; |
| } else { |
| actualWarningCount++; |
| } |
| } |
| if (expectedErrorCount != actualErrorCount || |
| expectedWarningCount != actualWarningCount) { |
| fail( |
| "Expected $expectedErrorCount errors and $expectedWarningCount warnings, found $actualErrorCount errors and $actualWarningCount warnings"); |
| } |
| } |
| |
| /** |
| * Assert that no errors have been gathered. |
| */ |
| void assertNoErrors() { |
| assertErrors(ExpectedError.NO_ERRORS); |
| } |
| |
| /** |
| * Return the line information associated with the given [source], or `null` |
| * if no line information has been associated with the source. |
| */ |
| LineInfo getLineInfo(Source source) => _lineInfoMap[source]; |
| |
| /** |
| * Return `true` if an error with the given [errorCode] has been gathered. |
| */ |
| bool hasError(ErrorCode errorCode) { |
| for (AnalysisError error in _errors) { |
| if (identical(error.errorCode, errorCode)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @override |
| void onError(AnalysisError error) { |
| _errors.add(error); |
| } |
| |
| /** |
| * Set the line information associated with the given [source] to the given |
| * list of [lineStarts]. |
| */ |
| void setLineInfo(Source source, List<int> lineStarts) { |
| _lineInfoMap[source] = new LineInfo(lineStarts); |
| } |
| |
| /** |
| * Return `true` if the [actualError] matches the [expectedError]. |
| */ |
| bool _equalErrors(ExpectedError expectedError, AnalysisError actualError) { |
| if (!identical(expectedError.code, actualError.errorCode)) { |
| return false; |
| } else if (!checkRanges) { |
| return true; |
| } |
| return expectedError.offset == actualError.offset && |
| expectedError.length == actualError.length; |
| } |
| |
| /** |
| * Assert that the number of errors that have been gathered matches the number |
| * of [expectedErrors] and that they have the expected error codes. The order |
| * in which the errors were gathered is ignored. |
| */ |
| void _fail(List<ExpectedError> expectedErrors) { |
| StringBuffer buffer = new StringBuffer(); |
| buffer.write("Expected "); |
| buffer.write(expectedErrors.length); |
| buffer.write(" errors:"); |
| for (ExpectedError error in expectedErrors) { |
| buffer.writeln(); |
| int offset = error.offset; |
| buffer.write(' ${error.code} ($offset..${offset + error.length})'); |
| } |
| buffer.writeln(); |
| buffer.write("found "); |
| buffer.write(_errors.length); |
| buffer.write(" errors:"); |
| for (AnalysisError error in _errors) { |
| buffer.writeln(); |
| int offset = error.offset; |
| buffer.write(' ${error.errorCode} ' |
| '($offset..${offset + error.length}): ${error.message}'); |
| } |
| fail(buffer.toString()); |
| } |
| |
| /** |
| * Search through the given list of [errors] for an error that is equal to the |
| * [targetError]. If one is found, remove it from the list and return `true`, |
| * otherwise return `false` without modifying the list. |
| */ |
| bool _foundAndRemoved(List<ExpectedError> errors, AnalysisError targetError) { |
| for (ExpectedError error in errors) { |
| if (_equalErrors(error, targetError)) { |
| errors.remove(error); |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Instances of the class [TestLogger] implement a logger that can be used by |
| * tests. |
| */ |
| class TestLogger implements Logger { |
| /** |
| * All logged messages. |
| */ |
| List<String> log = []; |
| |
| @override |
| void logError(String message, [CaughtException exception]) { |
| log.add("error: $message"); |
| } |
| |
| @override |
| void logInformation(String message, [CaughtException exception]) { |
| log.add("info: $message"); |
| } |
| } |
| |
| class TestSource extends Source { |
| String _name; |
| String _contents; |
| int _modificationStamp = 0; |
| bool exists2 = true; |
| |
| /** |
| * A flag indicating whether an exception should be generated when an attempt |
| * is made to access the contents of this source. |
| */ |
| bool generateExceptionOnRead = false; |
| |
| /** |
| * The number of times that the contents of this source have been requested. |
| */ |
| int readCount = 0; |
| |
| TestSource([this._name = '/test.dart', this._contents]); |
| |
| TimestampedData<String> get contents { |
| readCount++; |
| if (generateExceptionOnRead) { |
| String msg = "I/O Exception while getting the contents of " + _name; |
| throw new Exception(msg); |
| } |
| return new TimestampedData<String>(0, _contents); |
| } |
| |
| String get encoding => _name; |
| |
| String get fullName { |
| return _name; |
| } |
| |
| int get hashCode => 0; |
| |
| bool get isInSystemLibrary { |
| return false; |
| } |
| |
| @override |
| int get modificationStamp => |
| generateExceptionOnRead ? -1 : _modificationStamp; |
| |
| String get shortName { |
| return _name; |
| } |
| |
| Uri get uri => new Uri.file(_name); |
| |
| UriKind get uriKind { |
| throw new UnsupportedError('uriKind'); |
| } |
| |
| bool operator ==(Object other) { |
| if (other is TestSource) { |
| return other._name == _name; |
| } |
| return false; |
| } |
| |
| bool exists() => exists2; |
| void getContentsToReceiver(Source_ContentReceiver receiver) { |
| throw new UnsupportedError('getContentsToReceiver'); |
| } |
| |
| Source resolve(String uri) { |
| throw new UnsupportedError('resolve'); |
| } |
| |
| void setContents(String value) { |
| generateExceptionOnRead = false; |
| _modificationStamp = new DateTime.now().millisecondsSinceEpoch; |
| _contents = value; |
| } |
| |
| @override |
| String toString() => '$_name'; |
| } |
| |
| class TestSourceWithUri extends TestSource { |
| final Uri uri; |
| |
| TestSourceWithUri(String path, this.uri, [String content]) |
| : super(path, content); |
| |
| String get encoding => uri.toString(); |
| |
| UriKind get uriKind { |
| if (uri == null) { |
| return UriKind.FILE_URI; |
| } else if (uri.scheme == 'dart') { |
| return UriKind.DART_URI; |
| } else if (uri.scheme == 'package') { |
| return UriKind.PACKAGE_URI; |
| } |
| return UriKind.FILE_URI; |
| } |
| |
| bool operator ==(Object other) { |
| if (other is TestSource) { |
| return other.uri == uri; |
| } |
| return false; |
| } |
| } |