feat: better handle archiving failures (#2880)

This commit is contained in:
Bryan Oltman
2025-02-11 15:36:13 -05:00
committed by GitHub
parent cf80f7893e
commit 081a6e2a5f
2 changed files with 122 additions and 77 deletions
@@ -54,14 +54,22 @@ class ArtifactBuildException implements Exception {
late final String? fixRecommendation;
List<String> _errorMessageFromOutput(List<String> output) {
final failureHeader =
RegExp(r'.*FAILURE: Build failed with an exception\..*');
// This precedes a stack trace
final stackTraceHeader = RegExp(r'.*\* Exception is:.*');
final failureHeaders = [
RegExp(r'.*FAILURE: Build failed with an exception\..*'),
RegExp(r'.*Error \(Xcode\).*'),
];
// This precedes recommendations that are not applicable to us (e.g., "Get
// more help at https://help.gradle.org.")
final suggestionsHeader = RegExp(r'.*\* Try:.*');
final failureFooters = [
// This precedes a stack trace
RegExp(r'.*\* Exception is:.*'),
// This precedes recommendations that are not applicable to us (e.g., "Get
// more help at https://help.gradle.org.")
RegExp(r'.*\* Try:.*'),
// This precedes a stacktrace in the case of an Xcode error
RegExp('Encountered error while archiving for device'),
];
String trimLine(String line) {
return line.trim().replaceAll(RegExp(r'^\[.*\]'), '');
@@ -70,10 +78,9 @@ class ArtifactBuildException implements Exception {
var inErrorOutput = false;
final ret = <String>[];
for (final line in output) {
if (failureHeader.hasMatch(line)) {
if (failureHeaders.any((r) => r.hasMatch(line))) {
inErrorOutput = true;
} else if (stackTraceHeader.hasMatch(line) ||
suggestionsHeader.hasMatch(line)) {
} else if (failureFooters.any((r) => r.hasMatch(line))) {
inErrorOutput = false;
}
@@ -93,6 +100,8 @@ class ArtifactBuildException implements Exception {
[RegExp("Execution failed for task ':app:signReleaseBundle'")],
_missingKeystoreFixSuggestion,
),
// Note: Xcode archive failures include suggestions from the flutter tool,
// so we don't need to duplicate them here.
};
String? _recommendationFromOutput(List<String> output) {
@@ -34,83 +34,119 @@ void main() {
});
});
group('when an error is recognized but no fix recommendation is found', () {
test('has a Flutter error and no fix recommendation', () {
final exception = ArtifactBuildException(
'message',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
equals('FAILURE: Build failed with an exception.'),
);
expect(exception.fixRecommendation, isNull);
group('gradle', () {
group('when an error is recognized but no fix recommendation is found',
() {
test('has a Flutter error and no fix recommendation', () {
final exception = ArtifactBuildException(
'message',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
equals('FAILURE: Build failed with an exception.'),
);
expect(exception.fixRecommendation, isNull);
});
});
});
group('when a known error is recognized and a fix recommendation is found',
() {
test('has a Flutter error and a fix recommendation', () {
final exception = ArtifactBuildException(
'message',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* What went wrong:',
"Execution failed for task ':app:signReleaseBundle'.",
r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''',
'> java.lang.NullPointerException (no error message)',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
equals(r'''
group(
'when a known error is recognized and a fix recommendation is found',
() {
test('has a Flutter error and a fix recommendation', () {
final exception = ArtifactBuildException(
'message',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* What went wrong:',
"Execution failed for task ':app:signReleaseBundle'.",
r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''',
'> java.lang.NullPointerException (no error message)',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
equals(r'''
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:signReleaseBundle'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable
> java.lang.NullPointerException (no error message)'''),
);
expect(
exception.fixRecommendation,
contains('This error is likely due to a missing keystore file'),
);
);
expect(
exception.fixRecommendation,
contains('This error is likely due to a missing keystore file'),
);
});
});
group('when a fix recommendation is provided', () {
test('does not read output to find fix recommendation', () {
final exception = ArtifactBuildException(
'message',
fixRecommendation: 'some recommendation',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
'FAILURE: Build failed with an exception.',
);
expect(exception.fixRecommendation, equals('some recommendation'));
});
});
});
group('when a fix recommendation is provided', () {
test('does not read output to find fix recommendation', () {
final exception = ArtifactBuildException(
'message',
fixRecommendation: 'some recommendation',
stderr: [
'some stderr output',
'FAILURE: Build failed with an exception.',
'* Exception is:',
'some stack trace',
'* Try:',
'some recommendation',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
'FAILURE: Build failed with an exception.',
);
expect(exception.fixRecommendation, equals('some recommendation'));
group('xcode archiving', () {
group(
'when a known error is recognized and a fix recommendation is found',
() {
test('has a Flutter error and a fix recommendation', () {
final exception = ArtifactBuildException(
'message',
stderr: [
'some stderr output',
'Error (Xcode):',
'some error message',
'some stack trace',
'some recommendation',
'Encountered error while archiving for device',
'not part of the reported error',
],
stdout: ['some stdout output'],
);
expect(
exception.flutterError,
equals(
'''
Error (Xcode):
some error message
some stack trace
some recommendation''',
),
);
});
});
});
});