@@ -27,7 +27,12 @@ class InvalidationTester {
2727 /// The [OutputStrategy] for generated assets.
2828 ///
2929 /// [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 = {};
3136
3237 /// Assets written by generators.
3338 final Set <AssetId > _generatedOutputsWritten = {};
@@ -73,14 +78,32 @@ class InvalidationTester {
7378 /// By default, output will be a digest of all read files. This changes it to
7479 /// fixed: it won't change when inputs change.
7580 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;
7788 }
7889
7990 /// Sets the output strategy for [name] to [OutputStrategy.none] .
8091 ///
8192 /// The generator will not output the file.
8293 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;
84107 }
85108
86109 /// Does a build.
@@ -140,9 +163,10 @@ class InvalidationTester {
140163 optionalBuilders: _builders.where ((b) => b.isOptional).toSet (),
141164 testingBuilderConfig: false ,
142165 );
166+ final logString = log.toString ();
143167 printOnFailure (
144168 '=== build log #${++_buildNumber } ===\n\n '
145- '${log . toString () .trimAndIndent }' ,
169+ '${logString .trimAndIndent }' ,
146170 );
147171 _readerWriter = testBuildResult.readerWriter;
148172
@@ -152,7 +176,9 @@ class InvalidationTester {
152176 );
153177 final deleted = deletedAssets.map (_assetIdToName);
154178
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);
156182 }
157183}
158184
@@ -168,6 +194,12 @@ enum OutputStrategy {
168194 inputDigest,
169195}
170196
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+
171203/// The changes on disk caused by the build.
172204class Result {
173205 /// The "names" of the assets that were written.
@@ -176,22 +208,34 @@ class Result {
176208 /// The "names" of the assets that were deleted.
177209 BuiltSet <String > deleted;
178210
211+ /// Whether the build succeeded.
212+ bool succeeded;
213+
179214 Result ({Iterable <String >? written, Iterable <String >? deleted})
180215 : 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 ;
182223
183224 @override
184225 bool operator == (Object other) {
185226 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;
187230 }
188231
189232 @override
190- int get hashCode => Object .hash (written, deleted);
233+ int get hashCode => Object .hash (succeeded, written, deleted);
191234
192235 @override
193236 String toString () => [
194237 'Result(' ,
238+ if (! succeeded) 'failed' ,
195239 if (written.isNotEmpty) 'written: ${written .join (', ' )}' ,
196240 if (deleted.isNotEmpty) 'deleted: ${deleted .join (', ' )}' ,
197241 ')' ,
@@ -249,7 +293,7 @@ class TestBuilder implements Builder {
249293 for (final write in writes) {
250294 final writeId = buildStep.inputId.replaceAllPathExtensions (write);
251295 final outputStrategy =
252- _tester._outputs [writeId] ?? OutputStrategy .inputDigest;
296+ _tester._outputStrategies [writeId] ?? OutputStrategy .inputDigest;
253297 final output = switch (outputStrategy) {
254298 OutputStrategy .fixed => '' ,
255299 OutputStrategy .inputDigest =>
@@ -261,14 +305,20 @@ class TestBuilder implements Builder {
261305 _tester._generatedOutputsWritten.add (writeId);
262306 }
263307 }
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+ }
264314 }
265315}
266316
267317extension LogRecordExtension on LogRecord {
268- /// Displays [message] with error and stack trace if present.
318+ /// Displays [toString] plus error and stack trace if present.
269319 String get display {
270320 if (error == null && stackTrace == null ) return message;
271- return '$message \n $error \n $stackTrace ' ;
321+ return '${ toString ()} \n $error \n $stackTrace ' ;
272322 }
273323}
274324
0 commit comments