Skip to content

Commit 0ac1178

Browse files
bwilkersoncommit-bot@chromium.org
authored andcommitted
Initial stress test for code completion
Change-Id: I5ec7ff846da2924ef26a7e738a5e30191a3df9c5 Reviewed-on: https://dart-review.googlesource.com/68440 Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
1 parent e0f6fdf commit 0ac1178

File tree

2 files changed

+324
-0
lines changed

2 files changed

+324
-0
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) 2018, 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+
import 'dart:io';
6+
7+
import 'package:args/args.dart';
8+
9+
import 'completion_runner.dart';
10+
11+
/**
12+
* The main entry point for the code completion stress test.
13+
*/
14+
void main(List<String> args) async {
15+
ArgParser parser = createArgParser();
16+
ArgResults result = parser.parse(args);
17+
18+
if (validArguments(parser, result)) {
19+
String analysisRoot = result.rest[0];
20+
21+
CompletionRunner runner = new CompletionRunner(
22+
output: stdout,
23+
printMissing: result['missing'],
24+
timing: result['timing'],
25+
useCFE: result['use-cfe'],
26+
verbose: result['verbose']);
27+
await runner.runAll(analysisRoot);
28+
stdout.flush();
29+
}
30+
}
31+
32+
/**
33+
* Create a parser that can be used to parse the command-line arguments.
34+
*/
35+
ArgParser createArgParser() {
36+
ArgParser parser = new ArgParser();
37+
parser.addFlag(
38+
'help',
39+
abbr: 'h',
40+
help: 'Print this help message',
41+
negatable: false,
42+
);
43+
parser.addFlag(
44+
'missing',
45+
help: 'Report locations where the current identifier was not suggested',
46+
negatable: false,
47+
);
48+
parser.addFlag(
49+
'timing',
50+
help: 'Report timing information',
51+
negatable: false,
52+
);
53+
parser.addFlag(
54+
'use-cfe',
55+
help: 'Use the CFE to perform the analysis',
56+
negatable: false,
57+
);
58+
parser.addFlag(
59+
'verbose',
60+
abbr: 'v',
61+
help: 'Produce verbose output',
62+
negatable: false,
63+
);
64+
return parser;
65+
}
66+
67+
/**
68+
* Print usage information for this tool.
69+
*/
70+
void printUsage(ArgParser parser, {String error}) {
71+
if (error != null) {
72+
print(error);
73+
print('');
74+
}
75+
print('usage: dart completion path');
76+
print('');
77+
print('Test the completion engine by requesting completion at the offset of');
78+
print('each identifier in the files contained in the given path. The path');
79+
print('can be either a single Dart file or a directory.');
80+
print('');
81+
print(parser.usage);
82+
}
83+
84+
/**
85+
* Return `true` if the command-line arguments (represented by the [result] and
86+
* parsed by the [parser]) are valid.
87+
*/
88+
bool validArguments(ArgParser parser, ArgResults result) {
89+
if (result.wasParsed('help')) {
90+
printUsage(parser);
91+
return false;
92+
} else if (result.rest.length < 1) {
93+
printUsage(parser, error: 'Missing path to files');
94+
return false;
95+
} else if (result.rest.length > 1) {
96+
printUsage(parser, error: 'Only one file can be analyzed');
97+
return false;
98+
}
99+
return true;
100+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// Copyright (c) 2018, 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+
import 'dart:async';
6+
7+
import 'package:analysis_server/src/services/completion/completion_core.dart';
8+
import 'package:analysis_server/src/services/completion/completion_performance.dart';
9+
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
10+
import 'package:analysis_server/src/utilities/null_string_sink.dart';
11+
import 'package:analyzer/dart/analysis/analysis_context.dart';
12+
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
13+
import 'package:analyzer/dart/analysis/results.dart';
14+
import 'package:analyzer/dart/ast/ast.dart';
15+
import 'package:analyzer/dart/ast/visitor.dart';
16+
import 'package:analyzer/file_system/overlay_file_system.dart';
17+
import 'package:analyzer/file_system/physical_file_system.dart';
18+
import 'package:analyzer/source/line_info.dart';
19+
import 'package:analyzer/src/generated/source.dart';
20+
import 'package:analyzer_plugin/protocol/protocol_common.dart';
21+
22+
/**
23+
* A runner that can request code completion at the location of each identifier
24+
* in a Dart file.
25+
*/
26+
class CompletionRunner {
27+
/**
28+
* The sink to which output is to be written.
29+
*/
30+
final StringSink output;
31+
32+
/**
33+
* A flag indicating whether to produce output about missing suggestions.
34+
*/
35+
final bool printMissing;
36+
37+
/**
38+
* A flag indicating whether to produce timing information.
39+
*/
40+
final bool timing;
41+
42+
/**
43+
* A flag indicating whether to use the CFE when running the tests.
44+
*/
45+
final bool useCFE;
46+
47+
/**
48+
* A flag indicating whether to produce verbose output.
49+
*/
50+
final bool verbose;
51+
52+
/**
53+
* A flag indicating whether we should delete each identifier before
54+
* attempting to complete at that offset.
55+
*/
56+
bool deleteBeforeCompletion = false;
57+
58+
/**
59+
* Initialize a newly created completion runner.
60+
*/
61+
CompletionRunner(
62+
{StringSink output,
63+
bool printMissing,
64+
bool timing,
65+
bool useCFE,
66+
bool verbose})
67+
: this.output = output ?? new NullStringSink(),
68+
this.printMissing = printMissing ?? false,
69+
this.timing = timing ?? false,
70+
this.useCFE = useCFE ?? false,
71+
this.verbose = verbose ?? false;
72+
73+
/**
74+
* Test the completion engine at the locations of each of the identifiers in
75+
* each of the files in the given [analysisRoot].
76+
*/
77+
Future<void> runAll(String analysisRoot) async {
78+
OverlayResourceProvider resourceProvider =
79+
new OverlayResourceProvider(PhysicalResourceProvider.INSTANCE);
80+
AnalysisContextCollection collection = new AnalysisContextCollection(
81+
includedPaths: <String>[analysisRoot],
82+
resourceProvider: resourceProvider,
83+
useCFE: useCFE); // ignore: deprecated_member_use
84+
DartCompletionManager contributor = new DartCompletionManager();
85+
CompletionPerformance performance = new CompletionPerformance();
86+
int stamp = 1;
87+
88+
int fileCount = 0;
89+
int identifierCount = 0;
90+
int missingCount = 0;
91+
92+
// Consider getting individual timings so that we can also report the
93+
// longest and shortest times, or even a distribution.
94+
Stopwatch timer = new Stopwatch();
95+
96+
for (AnalysisContext context in collection.contexts) {
97+
for (String path in context.contextRoot.analyzedFiles()) {
98+
fileCount++;
99+
output.write('.');
100+
ResolveResult result =
101+
await context.currentSession.getResolvedAst(path);
102+
String content = result.content;
103+
LineInfo lineInfo = result.lineInfo;
104+
List<SimpleIdentifier> identifiers = _identifiersIn(result.unit);
105+
106+
for (SimpleIdentifier identifier in identifiers) {
107+
identifierCount++;
108+
int offset = identifier.offset;
109+
if (deleteBeforeCompletion) {
110+
String modifiedContent = content.substring(0, offset) +
111+
content.substring(identifier.end);
112+
resourceProvider.setOverlay(path,
113+
content: modifiedContent, modificationStamp: stamp++);
114+
result = await context.currentSession.getResolvedAst(path);
115+
}
116+
117+
timer.start();
118+
CompletionRequestImpl request =
119+
new CompletionRequestImpl(result, offset, performance);
120+
List<CompletionSuggestion> suggestions =
121+
await contributor.computeSuggestions(request);
122+
timer.stop();
123+
124+
if (!identifier.inDeclarationContext() &&
125+
!_isNamedExpressionName(identifier)) {
126+
if (!_hasSuggestion(suggestions, identifier.name)) {
127+
missingCount++;
128+
if (printMissing) {
129+
CharacterLocation location = lineInfo.getLocation(offset);
130+
output.writeln('Missing suggestion of "${identifier.name}" at '
131+
'$path:${location.lineNumber}:${location.columnNumber}');
132+
if (verbose) {
133+
_printSuggestions(suggestions);
134+
}
135+
}
136+
}
137+
}
138+
}
139+
if (deleteBeforeCompletion) {
140+
resourceProvider.removeOverlay(path);
141+
}
142+
}
143+
}
144+
output.writeln();
145+
if (printMissing) {
146+
output.writeln();
147+
}
148+
if (identifierCount == 0) {
149+
output.writeln('No identifiers found in $fileCount files');
150+
} else {
151+
int percent = (missingCount * 100 / identifierCount).round();
152+
output.writeln('$percent% missing suggestions '
153+
'($missingCount of $identifierCount in $fileCount files)');
154+
}
155+
if (timing && identifierCount > 0) {
156+
int time = timer.elapsedMilliseconds;
157+
int averageTime = (time / identifierCount).round();
158+
output.writeln('completion took $time ms, '
159+
'which is an average of $averageTime ms per completion');
160+
}
161+
}
162+
163+
/**
164+
* Return `true` if the given list of [suggestions] includes a suggestion for
165+
* the given [identifier].
166+
*/
167+
bool _hasSuggestion(
168+
List<CompletionSuggestion> suggestions, String identifier) {
169+
for (CompletionSuggestion suggestion in suggestions) {
170+
if (suggestion.completion == identifier) {
171+
return true;
172+
}
173+
}
174+
return false;
175+
}
176+
177+
/**
178+
* Return a list containing information about the identifiers in the given
179+
* compilation [unit].
180+
*/
181+
List<SimpleIdentifier> _identifiersIn(CompilationUnit unit) {
182+
IdentifierCollector visitor = new IdentifierCollector();
183+
unit.accept(visitor);
184+
return visitor.identifiers;
185+
}
186+
187+
/**
188+
* Return `true` if the given [identifier] is being used as the name of a
189+
* named expression.
190+
*/
191+
bool _isNamedExpressionName(SimpleIdentifier identifier) {
192+
AstNode parent = identifier.parent;
193+
return parent is NamedExpression && parent.name == identifier;
194+
}
195+
196+
/**
197+
* Print information about the given [suggestions].
198+
*/
199+
void _printSuggestions(List<CompletionSuggestion> suggestions) {
200+
if (suggestions.length == 0) {
201+
output.writeln(' No suggestions');
202+
return;
203+
}
204+
output.writeln(' Suggestions:');
205+
for (CompletionSuggestion suggestion in suggestions) {
206+
output.writeln(' ${suggestion.completion}');
207+
}
208+
}
209+
}
210+
211+
/**
212+
* A visitor that will collect simple identifiers in the AST being visited.
213+
*/
214+
class IdentifierCollector extends RecursiveAstVisitor<void> {
215+
/**
216+
* The simple identifiers that were collected.
217+
*/
218+
final List<SimpleIdentifier> identifiers = <SimpleIdentifier>[];
219+
220+
@override
221+
visitSimpleIdentifier(SimpleIdentifier node) {
222+
identifiers.add(node);
223+
}
224+
}

0 commit comments

Comments
 (0)