blob: 7b3677041c7f9c8574c4cc768b5152cbf1d8996a [file] [log] [blame]
// 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.
library analyzer.test.generated.test_support;
import 'dart:collection';
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/source.dart';
import 'package:plugin/manager.dart';
import 'package:plugin/plugin.dart';
import 'package:unittest/unittest.dart';
import 'analysis_context_factory.dart';
/**
* The class `EngineTestCase` defines utility methods for making assertions.
*/
class EngineTestCase {
/**
* 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 null;
}
/**
* 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}");
return null;
}
void setUp() {
List<Plugin> plugins = <Plugin>[];
plugins.addAll(AnalysisEngine.instance.requiredPlugins);
plugins.add(AnalysisEngine.instance.commandLinePlugin);
plugins.add(AnalysisEngine.instance.optionsPlugin);
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);
}
}
/**
* Instances of the class `GatheringErrorListener` implement an error listener that collects
* all of the errors passed to it for later examination.
*/
class GatheringErrorListener implements AnalysisErrorListener {
/**
* An empty array of errors used when no errors are expected.
*/
static List<AnalysisError> _NO_ERRORS = new List<AnalysisError>(0);
/**
* A list containing the errors that were collected.
*/
List<AnalysisError> _errors = new List<AnalysisError>();
/**
* A table mapping sources to the line information for the source.
*/
HashMap<Source, LineInfo> _lineInfoMap = new HashMap<Source, LineInfo>();
/**
* Initialize a newly created error listener to collect errors.
*/
GatheringErrorListener();
/**
* Return the errors that were collected.
*
* @return the errors that were collected
*/
List<AnalysisError> get errors => _errors;
/**
* Return `true` if at least one error has been gathered.
*
* @return `true` if at least one error has been gathered
*/
bool get hasErrors => _errors.length > 0;
/**
* Add all of the given errors to this listener.
*
* @param the errors to be added
*/
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.
*
* @param listener the listener that has recorded the errors to be added
*/
void addAll2(RecordingErrorListener listener) {
addAll(listener.errors);
}
/**
* Assert that the number of errors that have been gathered matches the number of errors that are
* given and that they have the expected error codes and locations. The order in which the errors
* were gathered is ignored.
*
* @param errorCodes the errors that should have been gathered
* @throws AssertionFailedError if a different number of errors have been gathered than were
* expected or if they do not have the same codes and locations
*/
void assertErrors(List<AnalysisError> expectedErrors) {
if (_errors.length != expectedErrors.length) {
_fail(expectedErrors);
}
List<AnalysisError> remainingErrors = new List<AnalysisError>();
for (AnalysisError error in expectedErrors) {
remainingErrors.add(error);
}
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 errors that are
* given and that they have the expected error codes. The order in which the errors were gathered
* is ignored.
*
* @param expectedErrorCodes the error codes of the errors that should have been gathered
* @throws AssertionFailedError if a different number of errors have been gathered than were
* expected
*/
void assertErrorsWithCodes(
[List<ErrorCode> expectedErrorCodes = ErrorCode.EMPTY_LIST]) {
StringBuffer buffer = new StringBuffer();
//
// Verify that the expected error codes have a non-empty message.
//
for (ErrorCode errorCode in expectedErrorCodes) {
expect(errorCode.message.isEmpty, isFalse,
reason: "Empty error code message");
}
//
// Compute the expected number of each type of error.
//
HashMap<ErrorCode, int> expectedCounts = new HashMap<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.
//
HashMap<ErrorCode, List<AnalysisError>> errorsByCode =
new HashMap<ErrorCode, List<AnalysisError>>();
for (AnalysisError error in _errors) {
ErrorCode code = error.errorCode;
List<AnalysisError> list = errorsByCode[code];
if (list == null) {
list = new List<AnalysisError>();
errorsByCode[code] = list;
}
list.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 severities that
* are given 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.
*
* @param expectedSeverities the severities of the errors that should have been gathered
* @throws AssertionFailedError if a different number of errors have been gathered than were
* expected
*/
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.
*
* @throws AssertionFailedError if any errors have been gathered
*/
void assertNoErrors() {
assertErrors(_NO_ERRORS);
}
/**
* Return the line information associated with the given source, or `null` if no line
* information has been associated with the source.
*
* @param source the source with which the line information is associated
* @return the line information associated with the source
*/
LineInfo getLineInfo(Source source) => _lineInfoMap[source];
/**
* Return `true` if an error with the given error code has been gathered.
*
* @param errorCode the error code being searched for
* @return `true` if an error with the given error code 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 information.
*
* @param source the source with which the line information is associated
* @param lineStarts the line start information to be associated with the source
*/
void setLineInfo(Source source, List<int> lineStarts) {
_lineInfoMap[source] = new LineInfo(lineStarts);
}
/**
* Return `true` if the [actualError] matches the [expectedError].
*/
bool _equalErrors(AnalysisError expectedError, AnalysisError actualError) {
Source expectedSource = expectedError.source;
return identical(expectedError.errorCode, actualError.errorCode) &&
expectedError.offset == actualError.offset &&
expectedError.length == actualError.length &&
(expectedSource == null ||
_equalSources(expectedSource, actualError.source));
}
/**
* Return `true` if the two sources are equivalent.
*
* @param firstSource the first source being compared
* @param secondSource the second source being compared
* @return `true` if the two sources are equivalent
*/
bool _equalSources(Source firstSource, Source secondSource) {
if (firstSource == null) {
return secondSource == null;
} else if (secondSource == null) {
return false;
}
return firstSource == secondSource;
}
/**
* Assert that the number of errors that have been gathered matches the number of errors that are
* given and that they have the expected error codes. The order in which the errors were gathered
* is ignored.
*
* @param errorCodes the errors that should have been gathered
* @throws AssertionFailedError with
*/
void _fail(List<AnalysisError> expectedErrors) {
StringBuffer buffer = new StringBuffer();
buffer.write("Expected ");
buffer.write(expectedErrors.length);
buffer.write(" errors:");
for (AnalysisError error in expectedErrors) {
Source source = error.source;
LineInfo lineInfo = _lineInfoMap[source];
buffer.writeln();
String sourceName = source == null ? '' : source.shortName;
if (lineInfo == null) {
int offset = error.offset;
buffer.write(' $sourceName ${error.errorCode} '
'($offset..${offset + error.length})');
} else {
LineInfo_Location location = lineInfo.getLocation(error.offset);
int lineNumber = location.lineNumber;
int columnNumber = location.columnNumber;
buffer.write(' $sourceName ${error.errorCode} '
'($lineNumber, $columnNumber/${error.length})');
}
}
buffer.writeln();
buffer.write("found ");
buffer.write(_errors.length);
buffer.write(" errors:");
for (AnalysisError error in _errors) {
Source source = error.source;
LineInfo lineInfo = _lineInfoMap[source];
buffer.writeln();
String sourceName = source == null ? '' : source.shortName;
if (lineInfo == null) {
int offset = error.offset;
buffer.write(' $sourceName ${error.errorCode} '
'($offset..${offset + error.length}): ${error.message}');
} else {
LineInfo_Location location = lineInfo.getLocation(error.offset);
int lineNumber = location.lineNumber;
int columnNumber = location.columnNumber;
buffer.write(' $sourceName ${error.errorCode} '
'($lineNumber, $columnNumber/${error.length}): ${error.message}');
}
}
fail(buffer.toString());
}
/**
* Search through the given list of errors for an error that is equal to the target error. If one
* is found, remove it from the list and return `true`, otherwise return `false`
* without modifying the list.
*
* @param errors the errors through which we are searching
* @param targetError the error being searched for
* @return `true` if the error is found and removed from the list
*/
bool _foundAndRemoved(List<AnalysisError> errors, AnalysisError targetError) {
for (AnalysisError 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;
}
}