// 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 elements, List 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 plugins = []; 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 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 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 NO_ERRORS = []; /** * 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 _errors = []; /** * A table mapping sources to the line information for the source. */ Map _lineInfoMap = {}; /** * Initialize a newly created error listener to collect errors. */ GatheringErrorListener({this.checkRanges = Parser.useFasta}); /** * Return the errors that were collected. */ List 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 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 expectedErrors) { if (_errors.length != expectedErrors.length) { _fail(expectedErrors); } List 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 expectedErrorCodes = const []]) { StringBuffer buffer = new StringBuffer(); // // Compute the expected number of each type of error. // Map expectedCounts = {}; 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> errorsByCode = >{}; for (AnalysisError error in _errors) { errorsByCode .putIfAbsent(error.errorCode, () => []) .add(error); } // // Compare the expected and actual number of each type of error. // expectedCounts.forEach((ErrorCode code, int expectedCount) { int actualCount; List 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 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 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 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 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 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 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 get contents { readCount++; if (generateExceptionOnRead) { String msg = "I/O Exception while getting the contents of " + _name; throw new Exception(msg); } return new TimestampedData(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; } }