@@ -27,7 +27,12 @@ class InvalidationTester {
27
27
/// The [OutputStrategy] for generated assets.
28
28
///
29
29
/// [OutputStrategy.inputDigest] is the default if there is no entry.
30
- final Map <AssetId , OutputStrategy > _outputs = {};
30
+ final Map <AssetId , OutputStrategy > _outputStrategies = {};
31
+
32
+ /// The [FailureStrategy] for generated assets.
33
+ ///
34
+ /// [FailureStrategy.succeed] is the default if there is no entry.
35
+ final Map <AssetId , FailureStrategy > _failureStrategies = {};
31
36
32
37
/// Assets written by generators.
33
38
final Set <AssetId > _generatedOutputsWritten = {};
@@ -73,14 +78,32 @@ class InvalidationTester {
73
78
/// By default, output will be a digest of all read files. This changes it to
74
79
/// fixed: it won't change when inputs change.
75
80
void fixOutput (String name) {
76
- _outputs[name.assetId] = OutputStrategy .fixed;
81
+ _outputStrategies[name.assetId] = OutputStrategy .fixed;
82
+ }
83
+
84
+ /// Sets the output strategy for [name] back to the default,
85
+ /// [OutputStrategy.inputDigest] .
86
+ void digestOutput (String name) {
87
+ _outputStrategies[name.assetId] = OutputStrategy .inputDigest;
77
88
}
78
89
79
90
/// Sets the output strategy for [name] to [OutputStrategy.none] .
80
91
///
81
92
/// The generator will not output the file.
82
93
void skipOutput (String name) {
83
- _outputs[name.assetId] = OutputStrategy .none;
94
+ _outputStrategies[name.assetId] = OutputStrategy .none;
95
+ }
96
+
97
+ /// Sets the failure strategy for [name] to [FailureStrategy.fail] .
98
+ ///
99
+ /// The generator will write any outputs it is configured to write, then fail.
100
+ void fail (String name) {
101
+ _failureStrategies[name.assetId] = FailureStrategy .fail;
102
+ }
103
+
104
+ /// Sets the failure strategy for [name] to [FailureStrategy.succeed] .
105
+ void succeed (String name) {
106
+ _failureStrategies[name.assetId] = FailureStrategy .succeed;
84
107
}
85
108
86
109
/// Does a build.
@@ -140,9 +163,10 @@ class InvalidationTester {
140
163
optionalBuilders: _builders.where ((b) => b.isOptional).toSet (),
141
164
testingBuilderConfig: false ,
142
165
);
166
+ final logString = log.toString ();
143
167
printOnFailure (
144
168
'=== build log #${++_buildNumber } ===\n\n '
145
- '${log . toString () .trimAndIndent }' ,
169
+ '${logString .trimAndIndent }' ,
146
170
);
147
171
_readerWriter = testBuildResult.readerWriter;
148
172
@@ -152,7 +176,9 @@ class InvalidationTester {
152
176
);
153
177
final deleted = deletedAssets.map (_assetIdToName);
154
178
155
- return Result (written: written, deleted: deleted);
179
+ return logString.contains ('Succeeded after' )
180
+ ? Result (written: written, deleted: deleted)
181
+ : Result .failure (written: written, deleted: deleted);
156
182
}
157
183
}
158
184
@@ -168,6 +194,12 @@ enum OutputStrategy {
168
194
inputDigest,
169
195
}
170
196
197
+ /// Whether a generator succeeds or fails.
198
+ ///
199
+ /// Writing files is independent from success or failure: if a generator is
200
+ /// configured to write files, it does so before failing.
201
+ enum FailureStrategy { fail, succeed }
202
+
171
203
/// The changes on disk caused by the build.
172
204
class Result {
173
205
/// The "names" of the assets that were written.
@@ -176,22 +208,34 @@ class Result {
176
208
/// The "names" of the assets that were deleted.
177
209
BuiltSet <String > deleted;
178
210
211
+ /// Whether the build succeeded.
212
+ bool succeeded;
213
+
179
214
Result ({Iterable <String >? written, Iterable <String >? deleted})
180
215
: written = (written ?? {}).toBuiltSet (),
181
- deleted = (deleted ?? {}).toBuiltSet ();
216
+ deleted = (deleted ?? {}).toBuiltSet (),
217
+ succeeded = true ;
218
+
219
+ Result .failure ({Iterable <String >? written, Iterable <String >? deleted})
220
+ : written = (written ?? {}).toBuiltSet (),
221
+ deleted = (deleted ?? {}).toBuiltSet (),
222
+ succeeded = false ;
182
223
183
224
@override
184
225
bool operator == (Object other) {
185
226
if (other is ! Result ) return false ;
186
- return written == other.written && deleted == other.deleted;
227
+ return succeeded == other.succeeded &&
228
+ written == other.written &&
229
+ deleted == other.deleted;
187
230
}
188
231
189
232
@override
190
- int get hashCode => Object .hash (written, deleted);
233
+ int get hashCode => Object .hash (succeeded, written, deleted);
191
234
192
235
@override
193
236
String toString () => [
194
237
'Result(' ,
238
+ if (! succeeded) 'failed' ,
195
239
if (written.isNotEmpty) 'written: ${written .join (', ' )}' ,
196
240
if (deleted.isNotEmpty) 'deleted: ${deleted .join (', ' )}' ,
197
241
')' ,
@@ -249,7 +293,7 @@ class TestBuilder implements Builder {
249
293
for (final write in writes) {
250
294
final writeId = buildStep.inputId.replaceAllPathExtensions (write);
251
295
final outputStrategy =
252
- _tester._outputs [writeId] ?? OutputStrategy .inputDigest;
296
+ _tester._outputStrategies [writeId] ?? OutputStrategy .inputDigest;
253
297
final output = switch (outputStrategy) {
254
298
OutputStrategy .fixed => '' ,
255
299
OutputStrategy .inputDigest =>
@@ -261,14 +305,20 @@ class TestBuilder implements Builder {
261
305
_tester._generatedOutputsWritten.add (writeId);
262
306
}
263
307
}
308
+ for (final write in writes) {
309
+ final writeId = buildStep.inputId.replaceAllPathExtensions (write);
310
+ if (_tester._failureStrategies[writeId] == FailureStrategy .fail) {
311
+ throw StateError ('Failing as requested by test setup.' );
312
+ }
313
+ }
264
314
}
265
315
}
266
316
267
317
extension LogRecordExtension on LogRecord {
268
- /// Displays [message] with error and stack trace if present.
318
+ /// Displays [toString] plus error and stack trace if present.
269
319
String get display {
270
320
if (error == null && stackTrace == null ) return message;
271
- return '$message \n $error \n $stackTrace ' ;
321
+ return '${ toString ()} \n $error \n $stackTrace ' ;
272
322
}
273
323
}
274
324
0 commit comments