blob: ee52fdd6551ecd7369699b19ed6de443218225d6 [file] [log] [blame]
// Copyright (c) 2015, 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.
/**
* Support for interacting with an analysis server running in a separate
* process.
*/
library analysis_server.test.stress.utilities.server;
import 'dart:async';
import 'dart:collection';
import 'package:analysis_server/plugin/protocol/protocol.dart';
import '../../integration/integration_test_methods.dart';
import '../../integration/integration_tests.dart' as base;
/**
* ???
*/
class ErrorMap {
/**
* A table mapping file paths to the errors associated with that file.
*/
final Map<String, List<AnalysisError>> pathMap =
new HashMap<String, List<AnalysisError>>();
/**
* Initialize a newly created error map.
*/
ErrorMap();
/**
* Initialize a newly created error map to contain the same mapping as the
* given [errorMap].
*/
ErrorMap.from(ErrorMap errorMap) {
pathMap.addAll(errorMap.pathMap);
}
void operator []=(String filePath, List<AnalysisError> errors) {
pathMap[filePath] = errors;
}
/**
* Compare the this error map with the state captured in the given [errorMap].
* Throw an exception if the two maps do not agree.
*/
String expectErrorMap(ErrorMap errorMap) {
StringBuffer buffer = new StringBuffer();
_ErrorComparator comparator = new _ErrorComparator(buffer);
comparator.compare(pathMap, errorMap.pathMap);
if (buffer.length > 0) {
return buffer.toString();
}
return null;
}
}
/**
* An interface for starting and communicating with an analysis server running
* in a separate process.
*/
class Server extends base.Server with IntegrationTestMixin {
/**
* A list containing the paths of files for which an overlay has been created.
*/
List<String> filesWithOverlays = <String>[];
/**
* A mapping from the absolute paths of files to the most recent set of errors
* received for that file.
*/
ErrorMap _errorMap = new ErrorMap();
/**
* Initialize a new analysis server. The analysis server is not running and
* must be started using [start].
*/
Server() {
initializeInttestMixin();
onAnalysisErrors.listen(_recordErrors);
}
/**
* Return a list of the paths of files that are currently being analyzed.
*/
List<String> get analyzedDartFiles {
// TODO(brianwilkerson) Implement this.
return <String>[];
}
/**
* Return a table mapping the absolute paths of files to the most recent set
* of errors received for that file. The content of the map will not change
* when new sets of errors are received.
*/
ErrorMap get errorMap => new ErrorMap.from(_errorMap);
@override
base.Server get server => this;
/**
* Compute a mapping from each of the file paths in the given list of
* [filePaths] to the list of errors in the file at that path.
*/
Future<ErrorMap> computeErrorMap(List<String> filePaths) async {
ErrorMap errorMap = new ErrorMap();
List<Future> futures = <Future>[];
for (String filePath in filePaths) {
futures.add(sendAnalysisGetErrors(filePath)
.then((AnalysisGetErrorsResult result) {
errorMap[filePath] = result.errors;
}));
}
await Future.wait(futures);
return errorMap;
}
/**
* Remove any existing overlays.
*/
Future<AnalysisUpdateContentResult> removeAllOverlays() {
Map<String, dynamic> files = new HashMap<String, dynamic>();
for (String path in filesWithOverlays) {
files[path] = new RemoveContentOverlay();
}
return sendAnalysisUpdateContent(files);
}
@override
Future<AnalysisUpdateContentResult> sendAnalysisUpdateContent(
Map<String, dynamic> files) {
files.forEach((String path, dynamic overlay) {
if (overlay is AddContentOverlay) {
filesWithOverlays.add(path);
} else if (overlay is RemoveContentOverlay) {
filesWithOverlays.remove(path);
}
});
return super.sendAnalysisUpdateContent(files);
}
/**
* Record the errors in the given [params].
*/
void _recordErrors(AnalysisErrorsParams params) {
_errorMap[params.file] = params.errors;
}
}
/**
* A utility class used to compare two sets of errors.
*/
class _ErrorComparator {
/**
* An empty list of analysis errors.
*/
static final List<AnalysisError> NO_ERRORS = <AnalysisError>[];
/**
* The buffer to which an error description will be written if any of the
* files have different errors than are expected.
*/
final StringBuffer buffer;
/**
* Initialize a newly created comparator to write to the given [buffer].
*/
_ErrorComparator(this.buffer);
/**
* Compare the [actualErrorMap] and the [expectedErrorMap], writing a
* description to the [buffer] if they are not the same. The error maps are
* expected to be maps from absolute file paths to the list of actual or
* expected errors.
*/
void compare(Map<String, List<AnalysisError>> actualErrorMap,
Map<String, List<AnalysisError>> expectedErrorMap) {
Set<String> allFiles = new HashSet();
allFiles.addAll(actualErrorMap.keys);
allFiles.addAll(expectedErrorMap.keys);
List<String> sortedFiles = allFiles.toList()..sort();
for (String filePath in sortedFiles) {
List<AnalysisError> actualErrors = actualErrorMap[filePath];
List<AnalysisError> expectedErrors = expectedErrorMap[filePath];
_compareLists(
filePath, actualErrors ?? NO_ERRORS, expectedErrors ?? NO_ERRORS);
}
}
/**
* Compare the [actualErrors] and [expectedErrors], writing a description to
* the [buffer] if they are not the same.
*/
void _compareLists(String filePath, List<AnalysisError> actualErrors,
List<AnalysisError> expectedErrors) {
List<AnalysisError> remainingExpected =
new List<AnalysisError>.from(expectedErrors);
for (AnalysisError actualError in actualErrors) {
AnalysisError expectedError = _findError(remainingExpected, actualError);
if (expectedError == null) {
_writeReport(filePath, actualErrors, expectedErrors);
return;
}
remainingExpected.remove(expectedError);
}
if (remainingExpected.isNotEmpty) {
_writeReport(filePath, actualErrors, expectedErrors);
}
}
/**
* Return `true` if the [firstError] and the [secondError] are equivalent.
*/
bool _equalErrors(AnalysisError firstError, AnalysisError secondError) =>
firstError.severity == secondError.severity &&
firstError.type == secondError.type &&
_equalLocations(firstError.location, secondError.location) &&
firstError.message == secondError.message;
/**
* Return `true` if the [firstLocation] and the [secondLocation] are
* equivalent.
*/
bool _equalLocations(Location firstLocation, Location secondLocation) =>
firstLocation.file == secondLocation.file &&
firstLocation.offset == secondLocation.offset &&
firstLocation.length == secondLocation.length;
/**
* Search through the given list of [errors] for an error that is equal to the
* [targetError]. If one is found, return it, otherwise return `null`.
*/
AnalysisError _findError(
List<AnalysisError> errors, AnalysisError targetError) {
for (AnalysisError error in errors) {
if (_equalErrors(error, targetError)) {
return error;
}
}
return null;
}
/**
* Write the given list of [errors], preceded by a header beginning with the
* given [prefix].
*/
void _writeErrors(String prefix, List<AnalysisError> errors) {
buffer.write(prefix);
buffer.write(errors.length);
buffer.write(' errors:');
for (AnalysisError error in errors) {
buffer.writeln();
Location location = error.location;
int offset = location.offset;
buffer.write(' ');
buffer.write(location.file);
buffer.write(' (');
buffer.write(offset);
buffer.write('..');
buffer.write(offset + location.length);
buffer.write(') ');
buffer.write(error.severity);
buffer.write(', ');
buffer.write(error.type);
buffer.write(' : ');
buffer.write(error.message);
}
}
/**
* Write a report of the differences between the [actualErrors] and the
* [expectedErrors]. The errors are reported as being from the file at the
* given [filePath].
*/
void _writeReport(String filePath, List<AnalysisError> actualErrors,
List<AnalysisError> expectedErrors) {
if (buffer.length > 0) {
buffer.writeln();
buffer.writeln();
}
buffer.writeln(filePath);
_writeErrors(' Expected ', expectedErrors);
buffer.writeln();
_writeErrors(' Found ', expectedErrors);
}
}