Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(409)

Side by Side Diff: pkg/front_end/lib/src/fasta/testing/validating_instrumentation.dart

Issue 2824393002: Introduce a testing framework for use with fasta type inference. (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 import 'dart:async';
ahe 2017/04/19 11:38:29 Missing copyright.
Paul Berry 2017/04/19 13:20:10 Done.
2 import 'dart:convert';
3 import 'dart:io';
4
5 import 'package:front_end/src/base/instrumentation.dart';
6 import 'package:front_end/src/fasta/scanner.dart';
7 import 'package:front_end/src/fasta/scanner/io.dart';
8
9 /// Implementation of [Instrumentation] which checks property/value pairs
10 /// against expectations encoded in source files using "/*@...*/" comments.
11 class ValidatingInstrumentation implements Instrumentation {
12 static final _ESCAPE_SEQUENCE = new RegExp(r'\\(.)');
13
14 /// Map from category names to the property names they are short for.
ahe 2017/04/19 11:38:29 Here and several other places below: consider usin
Paul Berry 2017/04/19 13:20:09 Done.
15 static const _CATEGORIES = const {
16 'inference': const [
17 'topType',
18 'typeArg',
19 'promotedType',
20 'type',
21 'returnType'
22 ],
23 };
24
25 /// Map from file URI to the as-yet unsatisfied expectations from that file,
26 /// organized by file offset.
27 final _expectations = <Uri, Map<int, List<_Expectation>>>{};
ahe 2017/04/19 11:38:29 Consider renaming to unsatisfiedExpecations.
Paul Berry 2017/04/19 13:20:10 Done.
28
29 /// Information about "testedFeatures" annotations, organized by file URI and
30 /// file offset. The inner map is guaranteed to be in ascending order of
31 /// file offset.
32 final _testedFeaturesState = <Uri, Map<int, Set<String>>>{};
33
34 /// String descriptions of the expectation mismatches found so far.
35 final _problems = <String>[];
36
37 /// Fixes that would need to be performed on source files in order for all
38 /// expectations to be met, organized by file URI. The inner map is not
39 /// guaranteed to be in ascending order of file offset.
40 final _fixes = <Uri, List<_Fix>>{};
41
42 /// Indicates whether any expectation mismatches were found.
43 ///
44 /// Should be called after [finish].
45 bool get hasProblems => _problems.isNotEmpty;
46
47 /// Gets a description of all expectation mismatches that were found, in a
48 /// form suitable for printing to the console.
49 ///
50 /// Should be called after [finish].
51 get problemsAsString => _problems.join('\n');
52
53 /// Checks whether the property/value pairs passed to [record] match the
54 /// expectations loaded by [loadExpectations].
55 void finish() {
56 _expectations.forEach((uri, expectationsForUri) {
57 expectationsForUri.forEach((offset, expectationsAtOffset) {
58 for (var expectation in expectationsAtOffset) {
59 _problem(
60 uri,
61 offset,
62 'expected ${expectation.property}=${expectation.value}, '
63 'got nothing',
64 new _Fix(
65 expectation.commentOffset, expectation.commentLength, ''));
66 }
67 });
68 });
69 }
70
71 /// Updates the source file at [uri] based on the actual property/value
72 /// pairs that were observed.
73 Future<Null> fixSource(Uri uri) async {
74 var fixes = _fixes[uri];
75 if (fixes == null) return;
76 var bytes = (await readBytesFromFile(uri)).toList();
77 // Remove the trailing \0 that's added by readBytesFromFile.
ahe 2017/04/19 11:38:29 We probably need to add boolean argument to readBy
ahe 2017/04/19 13:10:59 I've done that in CL 2827543006.
Paul Berry 2017/04/19 13:20:09 Thanks! I'll wait until you land that and then cl
Paul Berry 2017/04/19 13:53:55 Done.
78 bytes.removeLast();
79 fixes.sort((a, b) => b.offset.compareTo(a.offset));
ahe 2017/04/19 11:38:29 I assume you sort them in reverse order to avoid h
Paul Berry 2017/04/19 13:20:10 Done.
80 for (var fix in fixes) {
81 bytes.replaceRange(
82 fix.offset, fix.offset + fix.length, UTF8.encode(fix.replacement));
83 }
84 await new File.fromUri(uri).writeAsBytes(bytes);
85 }
86
87 /// Loads expectations from the source file located at [uri].
88 ///
89 /// Should be called before [finish].
90 Future<Null> loadExpectations(Uri uri) async {
91 var bytes = await readBytesFromFile(uri);
92 var expectations = _expectations.putIfAbsent(uri, () => {});
93 var testedFeaturesState = _testedFeaturesState.putIfAbsent(uri, () => {});
94 ScannerResult result = scan(bytes, includeComments: true);
95 for (Token token = result.tokens; !token.isEof; token = token.next) {
96 for (Token commentToken = token.precedingCommentTokens;
97 commentToken != null;
98 commentToken = commentToken.next) {
ahe 2017/04/19 11:38:29 Perhaps we should add a forEachComment to ScannerR
Paul Berry 2017/04/19 13:20:10 Fair enough. I will do that as a follow-up CL.
Paul Berry 2017/04/19 15:31:20 I looked into this and it's uglier than I expected
99 String lexeme = commentToken.lexeme;
100 if (lexeme.startsWith('/*@') && lexeme.endsWith('*/')) {
101 var expectation = lexeme.substring(3, lexeme.length - 2);
102 var equals = expectation.indexOf('=');
103 String property;
104 String value;
105 if (equals == -1) {
106 property = expectation;
107 value = '';
108 } else {
109 property = expectation.substring(0, equals);
110 value = expectation
111 .substring(equals + 1)
112 .replaceAllMapped(_ESCAPE_SEQUENCE, (m) => m.group(1));
113 }
ahe 2017/04/19 11:38:29 I suggest adding: property = property.trim(); val
Paul Berry 2017/04/19 13:20:10 Done.
114 if (property == 'testedFeatures') {
115 Set<String> state = new Set<String>();
116 for (String category in value.split(',')) {
ahe 2017/04/19 11:38:29 And here: category = category.trim();
Paul Berry 2017/04/19 13:20:10 Done.
117 // If an unrecognized category name is found, it is assumed to be
118 // just a property name.
119 state.addAll(_CATEGORIES[category] ?? [category]);
120 }
121 testedFeaturesState[commentToken.offset] = state;
122 } else {
123 var offset = token.charOffset;
124 var expectationsAtOffset =
125 expectations.putIfAbsent(offset, () => []);
126 expectationsAtOffset.add(new _Expectation(
127 property, value, commentToken.offset, commentToken.length));
128 }
129 }
130 }
131 }
132 }
133
134 @override
135 void record(
136 String property, Uri uri, int offset, InstrumentationValue value) {
137 var expectationsForUri = _expectations[uri];
138 if (expectationsForUri == null) return;
139 var expectationsAtOffset = expectationsForUri[offset];
140 if (expectationsAtOffset != null) {
141 for (int i = 0; i < expectationsAtOffset.length; i++) {
142 var expectation = expectationsAtOffset[i];
143 if (expectation.property == property) {
144 if (!value.matches(expectation.value)) {
145 _problemWithStack(
146 uri,
147 offset,
148 'expected $property=${expectation.value}, got '
149 '$property=${value.canonicalize()}',
150 new _Fix(expectation.commentOffset, expectation.commentLength,
151 _makeExpectationComment(property, value)));
152 }
153 expectationsAtOffset.removeAt(i);
154 return;
155 }
156 }
157 }
158 // Unexpected property/value pair. See if we should report.
159 if (_shouldCheck(property, uri, offset)) {
160 _problemWithStack(
161 uri,
162 offset,
163 'expected nothing, got $property=${value.canonicalize()}',
164 new _Fix(offset, 0, _makeExpectationComment(property, value)));
165 }
166 }
167
168 String _escape(String s) {
169 return s.replaceAll(r'\', r'\\').replaceAll('*/', r'*\/');
170 }
171
172 String _formatProblem(
173 Uri uri, int offset, String desc, StackTrace stackTrace) {
174 return '$uri:$offset: $desc${stackTrace == null ? '' : '\n$stackTrace'}';
ahe 2017/04/19 11:38:29 Perhaps you can use format from ../messages.dart h
Paul Berry 2017/04/19 13:20:10 Done.
175 }
176
177 String _makeExpectationComment(String property, InstrumentationValue value) {
178 return '/*@$property=${_escape(value.canonicalize())}*/';
179 }
180
181 void _problem(Uri uri, int offset, String desc, _Fix fix) {
182 _problems.add(_formatProblem(uri, offset, desc, null));
183 _fixes.putIfAbsent(uri, () => []).add(fix);
184 }
185
186 void _problemWithStack(Uri uri, int offset, String desc, _Fix fix) {
187 try {
188 throw null;
ahe 2017/04/19 11:38:29 We now have StackTrace.current.
Paul Berry 2017/04/19 13:20:10 Done.
189 } catch (_, stackTrace) {
190 _problems.add(_formatProblem(uri, offset, desc, stackTrace));
191 _fixes.putIfAbsent(uri, () => []).add(fix);
192 }
193 }
194
195 bool _shouldCheck(String property, Uri uri, int offset) {
196 var state = false;
197 var testedFeaturesStateForUri = _testedFeaturesState[uri];
198 if (testedFeaturesStateForUri == null) return false;
199 for (int i in testedFeaturesStateForUri.keys) {
200 if (i > offset) break;
201 var testedFeaturesStateAtOffset = testedFeaturesStateForUri[i];
202 state = testedFeaturesStateAtOffset.contains(property);
203 }
204 return state;
205 }
206 }
207
208 class _Expectation {
209 final String property;
210 final String value;
211 final int commentOffset;
212 final int commentLength;
213
214 _Expectation(
215 this.property, this.value, this.commentOffset, this.commentLength);
216 }
217
218 class _Fix {
219 final int offset;
220 final int length;
221 final String replacement;
222
223 _Fix(this.offset, this.length, this.replacement);
224 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698