da15746f4c
Change-Id: Ib9634c5e5bdd8c0097f55a9cf71054e2a2b6a5fe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98287 Reviewed-by: Ari Aye <ariaye@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
211 lines
7.3 KiB
Dart
211 lines
7.3 KiB
Dart
// 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 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'domain_completion_test.dart';
|
|
|
|
/**
|
|
* A base class for classes containing completion tests.
|
|
*/
|
|
class CompletionTestCase extends CompletionDomainHandlerListTokenDetailsTest {
|
|
static const String CURSOR_MARKER = '!';
|
|
|
|
List get suggestedCompletions => suggestions
|
|
.map((CompletionSuggestion suggestion) => suggestion.completion)
|
|
.toList();
|
|
|
|
void assertHasCompletion(String completion) {
|
|
int expectedOffset = completion.indexOf(CURSOR_MARKER);
|
|
if (expectedOffset >= 0) {
|
|
if (completion.indexOf(CURSOR_MARKER, expectedOffset + 1) >= 0) {
|
|
fail(
|
|
"Invalid completion, contains multiple cursor positions: '$completion'");
|
|
}
|
|
completion = completion.replaceFirst(CURSOR_MARKER, '');
|
|
} else {
|
|
expectedOffset = completion.length;
|
|
}
|
|
CompletionSuggestion matchingSuggestion;
|
|
suggestions.forEach((CompletionSuggestion suggestion) {
|
|
if (suggestion.completion == completion) {
|
|
if (matchingSuggestion == null) {
|
|
matchingSuggestion = suggestion;
|
|
} else {
|
|
// It is OK to have a class and its default constructor suggestions.
|
|
if (matchingSuggestion.element?.kind == ElementKind.CLASS &&
|
|
suggestion.element?.kind == ElementKind.CONSTRUCTOR ||
|
|
matchingSuggestion.element?.kind == ElementKind.CONSTRUCTOR &&
|
|
suggestion.element?.kind == ElementKind.CLASS) {
|
|
return;
|
|
}
|
|
fail(
|
|
"Expected exactly one '$completion' but found multiple:\n $suggestedCompletions");
|
|
}
|
|
}
|
|
});
|
|
if (matchingSuggestion == null) {
|
|
fail("Expected '$completion' but found none:\n $suggestedCompletions");
|
|
}
|
|
expect(matchingSuggestion.selectionOffset, equals(expectedOffset));
|
|
expect(matchingSuggestion.selectionLength, equals(0));
|
|
}
|
|
|
|
void assertHasNoCompletion(String completion) {
|
|
if (suggestions.any((CompletionSuggestion suggestion) =>
|
|
suggestion.completion == completion)) {
|
|
fail(
|
|
"Did not expect completion '$completion' but found:\n $suggestedCompletions");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Discard any results that do not start with the characters the user has
|
|
* "already typed".
|
|
*/
|
|
void filterResults(String content) {
|
|
String charsAlreadyTyped =
|
|
content.substring(replacementOffset, completionOffset).toLowerCase();
|
|
suggestions = suggestions
|
|
.where((CompletionSuggestion suggestion) =>
|
|
suggestion.completion.toLowerCase().startsWith(charsAlreadyTyped))
|
|
.toList();
|
|
}
|
|
|
|
runTest(LocationSpec spec, [Map<String, String> extraFiles]) {
|
|
super.setUp();
|
|
return new Future(() {
|
|
String content = spec.source;
|
|
newFile(testFile, content: content);
|
|
this.testCode = content;
|
|
completionOffset = spec.testLocation;
|
|
if (extraFiles != null) {
|
|
extraFiles.forEach((String fileName, String content) {
|
|
newFile(fileName, content: content);
|
|
});
|
|
}
|
|
}).then((_) => getSuggestions()).then((_) {
|
|
filterResults(spec.source);
|
|
for (String result in spec.positiveResults) {
|
|
assertHasCompletion(result);
|
|
}
|
|
for (String result in spec.negativeResults) {
|
|
assertHasNoCompletion(result);
|
|
}
|
|
}).whenComplete(() {
|
|
super.tearDown();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A specification of the completion results expected at a given location.
|
|
*/
|
|
class LocationSpec {
|
|
String id;
|
|
int testLocation = -1;
|
|
List<String> positiveResults = <String>[];
|
|
List<String> negativeResults = <String>[];
|
|
String source;
|
|
|
|
LocationSpec(this.id);
|
|
|
|
/**
|
|
* Parse a set of tests from the given `originalSource`. Return a list of the
|
|
* specifications that were parsed.
|
|
*
|
|
* The source string has test locations embedded in it, which are identified
|
|
* by '!X' where X is a single character. Each X is matched to positive or
|
|
* negative results in the array of [validationStrings]. Validation strings
|
|
* contain the name of a prediction with a two character prefix. The first
|
|
* character of the prefix corresponds to an X in the [originalSource]. The
|
|
* second character is either a '+' or a '-' indicating whether the string is
|
|
* a positive or negative result. If logical not is needed in the source it
|
|
* can be represented by '!!'.
|
|
*
|
|
* The [originalSource] is the source for a test that contains test locations.
|
|
* The [validationStrings] are the positive and negative predictions.
|
|
*/
|
|
static List<LocationSpec> from(
|
|
String originalSource, List<String> validationStrings) {
|
|
Map<String, LocationSpec> tests = new HashMap<String, LocationSpec>();
|
|
String modifiedSource = originalSource;
|
|
int modifiedPosition = 0;
|
|
while (true) {
|
|
int index = modifiedSource.indexOf('!', modifiedPosition);
|
|
if (index < 0) {
|
|
break;
|
|
}
|
|
int n = 1; // only delete one char for double-bangs
|
|
String id = modifiedSource.substring(index + 1, index + 2);
|
|
if (id != '!') {
|
|
n = 2;
|
|
LocationSpec test = new LocationSpec(id);
|
|
tests[id] = test;
|
|
test.testLocation = index;
|
|
} else {
|
|
modifiedPosition = index + 1;
|
|
}
|
|
modifiedSource = modifiedSource.substring(0, index) +
|
|
modifiedSource.substring(index + n);
|
|
}
|
|
if (modifiedSource == originalSource) {
|
|
throw new StateError("No tests in source: " + originalSource);
|
|
}
|
|
for (String result in validationStrings) {
|
|
if (result.length < 3) {
|
|
throw new StateError("Invalid location result: " + result);
|
|
}
|
|
String id = result.substring(0, 1);
|
|
String sign = result.substring(1, 2);
|
|
String value = result.substring(2);
|
|
LocationSpec test = tests[id];
|
|
if (test == null) {
|
|
throw new StateError("Invalid location result id: $id for: $result");
|
|
}
|
|
test.source = modifiedSource;
|
|
if (sign == '+') {
|
|
test.positiveResults.add(value);
|
|
} else if (sign == '-') {
|
|
test.negativeResults.add(value);
|
|
} else {
|
|
String err = "Invalid location result sign: $sign for: $result";
|
|
throw new StateError(err);
|
|
}
|
|
}
|
|
List<String> badPoints = <String>[];
|
|
List<String> badResults = <String>[];
|
|
for (LocationSpec test in tests.values) {
|
|
if (test.testLocation == -1) {
|
|
badPoints.add(test.id);
|
|
}
|
|
if (test.positiveResults.isEmpty && test.negativeResults.isEmpty) {
|
|
badResults.add(test.id);
|
|
}
|
|
}
|
|
if (!(badPoints.isEmpty && badResults.isEmpty)) {
|
|
StringBuffer err = new StringBuffer();
|
|
if (!badPoints.isEmpty) {
|
|
err.write("No test location for tests:");
|
|
for (String ch in badPoints) {
|
|
err..write(' ')..write(ch);
|
|
}
|
|
err.write(' ');
|
|
}
|
|
if (!badResults.isEmpty) {
|
|
err.write("No results for tests:");
|
|
for (String ch in badResults) {
|
|
err..write(' ')..write(ch);
|
|
}
|
|
}
|
|
throw new StateError(err.toString());
|
|
}
|
|
return tests.values.toList();
|
|
}
|
|
}
|