| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library test.completion.support; |
| 6 |
| 7 import 'dart:collection'; |
| 8 |
| 9 import 'package:analysis_server/src/protocol.dart'; |
| 10 import 'package:analyzer/src/generated/java_core.dart'; |
| 11 import 'package:unittest/unittest.dart'; |
| 12 |
| 13 import 'domain_completion_test.dart'; |
| 14 |
| 15 /** |
| 16 * A base class for classes containing completion tests. |
| 17 */ |
| 18 class CompletionTestCase extends CompletionTest { |
| 19 static const String CURSOR_MARKER = '!'; |
| 20 |
| 21 List get suggestedCompletions => |
| 22 suggestions.map( |
| 23 (CompletionSuggestion suggestion) => suggestion.completion).toList(); |
| 24 |
| 25 void assertHasCompletion(String completion) { |
| 26 int expectedOffset = completion.indexOf(CURSOR_MARKER); |
| 27 if (expectedOffset >= 0) { |
| 28 if (completion.indexOf(CURSOR_MARKER, expectedOffset + 1) >= 0) { |
| 29 fail( |
| 30 "Invalid completion, contains multiple cursor positions: '$completio
n'"); |
| 31 } |
| 32 completion = completion.replaceFirst(CURSOR_MARKER, ''); |
| 33 } else { |
| 34 expectedOffset = completion.length; |
| 35 } |
| 36 CompletionSuggestion matchingSuggestion; |
| 37 suggestions.forEach((CompletionSuggestion suggestion) { |
| 38 if (suggestion.completion == completion) { |
| 39 if (matchingSuggestion == null) { |
| 40 matchingSuggestion = suggestion; |
| 41 } else { |
| 42 fail( |
| 43 "Expected exactly one '$completion' but found multiple:\n $sugges
tedCompletions"); |
| 44 } |
| 45 } |
| 46 }); |
| 47 if (matchingSuggestion == null) { |
| 48 fail("Expected '$completion' but found none:\n $suggestedCompletions"); |
| 49 } |
| 50 expect(matchingSuggestion.selectionOffset, equals(expectedOffset)); |
| 51 expect(matchingSuggestion.selectionLength, equals(0)); |
| 52 } |
| 53 |
| 54 void assertHasNoCompletion(String completion) { |
| 55 if (suggestions.any( |
| 56 (CompletionSuggestion suggestion) => suggestion.completion == completion
)) { |
| 57 fail( |
| 58 "Did not expect completion '$completion' but found:\n $suggestedCompl
etions"); |
| 59 } |
| 60 } |
| 61 |
| 62 runTest(LocationSpec spec, [Map<String, String> extraFiles]) { |
| 63 super.setUp(); |
| 64 String content = spec.source; |
| 65 addFile(testFile, content); |
| 66 this.testCode = content; |
| 67 completionOffset = spec.testLocation; |
| 68 if (extraFiles != null) { |
| 69 extraFiles.forEach((String fileName, String content) { |
| 70 addFile(fileName, content); |
| 71 }); |
| 72 } |
| 73 return getSuggestions().then((_) { |
| 74 try { |
| 75 //expect(replacementOffset, equals(completionOffset)); |
| 76 //expect(replacementLength, equals(0)); |
| 77 for (String result in spec.positiveResults) { |
| 78 assertHasCompletion(result); |
| 79 } |
| 80 for (String result in spec.negativeResults) { |
| 81 assertHasNoCompletion(result); |
| 82 } |
| 83 } finally { |
| 84 super.tearDown(); |
| 85 } |
| 86 }); |
| 87 } |
| 88 |
| 89 /** |
| 90 * Generate a set of completion tests based on the given [originalSource]. |
| 91 * |
| 92 * The source string has completion points embedded in it, which are |
| 93 * identified by '!X' where X is a single character. Each X is matched to |
| 94 * positive or negative results in the array of [validationStrings]. |
| 95 * Validation strings contain the name of a prediction with a two character |
| 96 * prefix. The first character of the prefix corresponds to an X in the |
| 97 * [originalSource]. The second character is either a '+' or a '-' indicating |
| 98 * whether the string is a positive or negative result. |
| 99 * |
| 100 * The [originalSource] is the source for a completion test that contains |
| 101 * completion points. The [validationStrings] are the positive and negative |
| 102 * predictions. |
| 103 */ |
| 104 static void buildTests(String baseName, String originalSource, |
| 105 List<String> results, [Map<String, String> extraFiles]) { |
| 106 List<LocationSpec> completionTests = |
| 107 LocationSpec.from(originalSource, results); |
| 108 completionTests.sort((LocationSpec first, LocationSpec second) { |
| 109 return first.id.compareTo(second.id); |
| 110 }); |
| 111 if (completionTests.isEmpty) { |
| 112 test(baseName, () { |
| 113 fail( |
| 114 "Expected exclamation point ('!') within the source denoting the" |
| 115 "position at which code completion should occur"); |
| 116 }); |
| 117 } |
| 118 for (LocationSpec spec in completionTests) { |
| 119 test("$baseName-${spec.id}", () { |
| 120 CompletionTestCase test = new CompletionTestCase(); |
| 121 return test.runTest(spec, extraFiles); |
| 122 }); |
| 123 } |
| 124 } |
| 125 } |
| 126 |
| 127 /** |
| 128 * A specification of the completion results expected at a given location. |
| 129 */ |
| 130 class LocationSpec { |
| 131 String id; |
| 132 int testLocation = -1; |
| 133 List<String> positiveResults = <String>[]; |
| 134 List<String> negativeResults = <String>[]; |
| 135 String source; |
| 136 |
| 137 LocationSpec(this.id); |
| 138 |
| 139 /** |
| 140 * Parse a set of tests from the given `originalSource`. Return a list of the |
| 141 * specifications that were parsed. |
| 142 * |
| 143 * The source string has test locations embedded in it, which are identified |
| 144 * by '!X' where X is a single character. Each X is matched to positive or |
| 145 * negative results in the array of [validationStrings]. Validation strings |
| 146 * contain the name of a prediction with a two character prefix. The first |
| 147 * character of the prefix corresponds to an X in the [originalSource]. The |
| 148 * second character is either a '+' or a '-' indicating whether the string is |
| 149 * a positive or negative result. If logical not is needed in the source it |
| 150 * can be represented by '!!'. |
| 151 * |
| 152 * The [originalSource] is the source for a test that contains test locations. |
| 153 * The [validationStrings] are the positive and negative predictions. |
| 154 */ |
| 155 static List<LocationSpec> from(String originalSource, |
| 156 List<String> validationStrings) { |
| 157 Map<String, LocationSpec> tests = new HashMap<String, LocationSpec>(); |
| 158 String modifiedSource = originalSource; |
| 159 int modifiedPosition = 0; |
| 160 while (true) { |
| 161 int index = modifiedSource.indexOf('!', modifiedPosition); |
| 162 if (index < 0) { |
| 163 break; |
| 164 } |
| 165 int n = 1; // only delete one char for double-bangs |
| 166 String id = modifiedSource.substring(index + 1, index + 2); |
| 167 if (id != '!') { |
| 168 n = 2; |
| 169 LocationSpec test = new LocationSpec(id); |
| 170 tests[id] = test; |
| 171 test.testLocation = index; |
| 172 } else { |
| 173 modifiedPosition = index + 1; |
| 174 } |
| 175 modifiedSource = |
| 176 modifiedSource.substring(0, index) + modifiedSource.substring(index +
n); |
| 177 } |
| 178 if (modifiedSource == originalSource) { |
| 179 throw new IllegalStateException("No tests in source: " + originalSource); |
| 180 } |
| 181 for (String result in validationStrings) { |
| 182 if (result.length < 3) { |
| 183 throw new IllegalStateException("Invalid location result: " + result); |
| 184 } |
| 185 String id = result.substring(0, 1); |
| 186 String sign = result.substring(1, 2); |
| 187 String value = result.substring(2); |
| 188 LocationSpec test = tests[id]; |
| 189 if (test == null) { |
| 190 throw new IllegalStateException( |
| 191 "Invalid location result id: $id for: $result"); |
| 192 } |
| 193 test.source = modifiedSource; |
| 194 if (sign == '+') { |
| 195 test.positiveResults.add(value); |
| 196 } else if (sign == '-') { |
| 197 test.negativeResults.add(value); |
| 198 } else { |
| 199 String err = "Invalid location result sign: $sign for: $result"; |
| 200 throw new IllegalStateException(err); |
| 201 } |
| 202 } |
| 203 List<String> badPoints = <String>[]; |
| 204 List<String> badResults = <String>[]; |
| 205 for (LocationSpec test in tests.values) { |
| 206 if (test.testLocation == -1) { |
| 207 badPoints.add(test.id); |
| 208 } |
| 209 if (test.positiveResults.isEmpty && test.negativeResults.isEmpty) { |
| 210 badResults.add(test.id); |
| 211 } |
| 212 } |
| 213 if (!(badPoints.isEmpty && badResults.isEmpty)) { |
| 214 StringBuffer err = new StringBuffer(); |
| 215 if (!badPoints.isEmpty) { |
| 216 err.write("No test location for tests:"); |
| 217 for (String ch in badPoints) { |
| 218 err |
| 219 ..write(' ') |
| 220 ..write(ch); |
| 221 } |
| 222 err.write(' '); |
| 223 } |
| 224 if (!badResults.isEmpty) { |
| 225 err.write("No results for tests:"); |
| 226 for (String ch in badResults) { |
| 227 err |
| 228 ..write(' ') |
| 229 ..write(ch); |
| 230 } |
| 231 } |
| 232 throw new IllegalStateException(err.toString()); |
| 233 } |
| 234 return tests.values.toList(); |
| 235 } |
| 236 } |
| OLD | NEW |