feat: improve error messages for recognized issues (#2875)

This commit is contained in:
Bryan Oltman
2025-02-11 11:24:49 -05:00
committed by GitHub
parent ee7b1d94a1
commit 0391b31c38
37 changed files with 474 additions and 125 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/android_sdk.dart';
import 'package:shorebird_cli/src/android_studio.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/cache.dart';
@@ -0,0 +1,110 @@
import 'dart:convert';
import 'package:shorebird_cli/src/shorebird_process.dart';
const String _missingKeystoreFixSuggestion =
'This error is likely due to a missing keystore file. You can read about Android app signing here: https://developer.android.com/studio/publish/app-signing.';
/// {@template artifact_build_exception}
/// Thrown when a build fails.
/// {@endtemplate}
class ArtifactBuildException implements Exception {
/// {@macro artifact_build_exception}
ArtifactBuildException(
this.message, {
List<String>? stdout,
List<String>? stderr,
String? fixRecommendation,
}) : stdout = stdout ?? [],
stderr = stderr ?? [] {
flutterError =
_errorMessageFromOutput(this.stdout + this.stderr).join('\n');
this.fixRecommendation = fixRecommendation ??
_recommendationFromOutput(this.stdout + this.stderr);
}
/// {@macro artifact_build_exception}
factory ArtifactBuildException.fromProcessResult(
String message, {
required ShorebirdProcessResult buildProcessResult,
String? fixRecommendation,
}) {
return ArtifactBuildException(
message,
stdout: const LineSplitter().convert('${buildProcessResult.stdout}'),
stderr: const LineSplitter().convert('${buildProcessResult.stderr}'),
fixRecommendation: fixRecommendation,
);
}
/// Information about the build failure.
late final String message;
/// The stdout output from the build process, split into lines.
final List<String> stdout;
/// The stderr output from the build process, split into lines.
final List<String> stderr;
/// The relevant error message (if we can find one) from the Flutter build
/// output.
late final String? flutterError;
/// An optional tip to help the user fix the build failure.
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:.*');
// This precedes recommendations that are not applicable to us (e.g., "Get
// more help at https://help.gradle.org.")
final suggestionsHeader = RegExp(r'.*\* Try:.*');
String trimLine(String line) {
return line.trim().replaceAll(RegExp(r'^\[.*\]'), '');
}
var inErrorOutput = false;
final ret = <String>[];
for (final line in output) {
if (failureHeader.hasMatch(line)) {
inErrorOutput = true;
} else if (stackTraceHeader.hasMatch(line) ||
suggestionsHeader.hasMatch(line)) {
inErrorOutput = false;
}
if (inErrorOutput) {
ret.add(trimLine(line));
}
}
return ret;
}
/// Maps lists of regular expressions to a recommendation. We use a list of
/// regular expressions instead of a single regular expression to allow for
/// multiple possible error messages that have the same root cause.
final _regexpToRecommendations = {
(
[RegExp("Execution failed for task ':app:signReleaseBundle'")],
_missingKeystoreFixSuggestion,
),
};
String? _recommendationFromOutput(List<String> output) {
for (final entry in _regexpToRecommendations) {
final regexes = entry.$1;
for (final regexp in regexes) {
if (output.any(regexp.hasMatch)) {
return entry.$2;
}
}
}
return null;
}
}
@@ -8,6 +8,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/os/operating_system_interface.dart';
@@ -18,6 +19,8 @@ import 'package:shorebird_cli/src/shorebird_documentation.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
export 'artifact_build_exception.dart';
/// Used to wrap code that invokes `flutter build` with Shorebird's fork of
/// Flutter.
typedef ShorebirdBuildCommand = Future<void> Function();
@@ -62,20 +65,6 @@ class MacosBuildResult {
final File kernelFile;
}
/// {@template artifact_build_exception}
/// Thrown when a build fails.
/// {@endtemplate}
class ArtifactBuildException implements Exception {
/// {@macro artifact_build_exception}
ArtifactBuildException(this.message);
/// Information about the build failure.
final String message;
@override
String toString() => message;
}
/// A reference to a [ArtifactBuilder] instance.
final artifactBuilderRef = create(ArtifactBuilder.new);
@@ -132,10 +121,12 @@ class ArtifactBuilder {
// this format. We can use the 'Task :' line to get the current task
// being run.
final gradleTaskRegex = RegExp(r'^\[.*\] \> (Task :.*)$');
final stdoutLines = <String>[];
buildProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
stdoutLines.add(line);
if (buildProgress == null) {
return;
}
@@ -149,10 +140,13 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stdErr = stderrLines.join('\n');
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: $stdErr');
throw ArtifactBuildException(
'Failed to build',
stderr: stderrLines,
stdout: stdoutLines,
);
}
});
@@ -217,8 +211,9 @@ class ArtifactBuilder {
);
if (result.exitCode != ExitCode.success.code) {
throw ArtifactBuildException(
'Failed to build: ${result.stderr}',
throw ArtifactBuildException.fromProcessResult(
'Failed to build',
buildProcessResult: result,
);
}
});
@@ -269,7 +264,10 @@ class ArtifactBuilder {
);
if (result.exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: ${result.stderr}');
throw ArtifactBuildException.fromProcessResult(
'Failed to build',
buildProcessResult: result,
);
}
});
}
@@ -298,21 +296,26 @@ class ArtifactBuilder {
environment: base64PublicKey?.toPublicKeyEnv(),
);
final stdoutLines = <String>[];
buildProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
logger.detail(line);
stdoutLines.add(line);
});
final stderrLines = await buildProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stdErr = stderrLines.join('\n');
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: $stdErr');
throw ArtifactBuildException(
'Failed to build',
stdout: stdoutLines,
stderr: stderrLines,
);
}
});
}
@@ -377,24 +380,25 @@ class ArtifactBuilder {
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stderr = stderrLines.join('\n');
final stdout = stdoutLines.join('\n');
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
Failed to build
stdout: $stdout
stderr: $stderr''');
throw ArtifactBuildException(
'Failed to build',
stdout: stdoutLines,
stderr: stderrLines,
);
}
appDillPath = findAppDill(stdout: stdout);
});
if (appDillPath == null) {
throw ArtifactBuildException('''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''');
throw ArtifactBuildException(
'Unable to find app.dill file.',
fixRecommendation:
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
);
}
return MacosBuildResult(kernelFile: File(appDillPath!));
@@ -459,23 +463,32 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
buildProgress?.updateDetailMessage(null);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: $stderr');
throw ArtifactBuildException(
'Failed to build',
stdout: stdoutLines,
stderr: stderrLines,
);
}
// TODO(https://github.com/shorebirdtech/shorebird/issues/2855): this is
// not treated as an error by Flutter, we should not throw here.
if (stderr.contains('Encountered error while creating the IPA')) {
throw ArtifactBuildException('''
Failed to build:
$stderr''');
throw ArtifactBuildException(
'Failed to build',
stdout: stdoutLines,
stderr: stderrLines,
);
}
appDillPath = findAppDill(stdout: stdout);
});
if (appDillPath == null) {
throw ArtifactBuildException('''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''');
throw ArtifactBuildException(
'Unable to find app.dill file.',
fixRecommendation:
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
);
}
return IpaBuildResult(kernelFile: File(appDillPath!));
@@ -510,10 +523,11 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
});
if (appDillPath == null) {
throw ArtifactBuildException('''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''');
throw ArtifactBuildException(
'Unable to find app.dill file.',
fixRecommendation:
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
);
}
return IosFrameworkBuildResult(kernelFile: File(appDillPath!));
@@ -586,8 +600,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
);
if (result.exitCode != ExitCode.success.code) {
throw ArtifactBuildException(
'Failed to create snapshot: ${result.stderr}',
throw ArtifactBuildException.fromProcessResult(
'Failed to create snapshot',
buildProcessResult: result,
);
}
@@ -618,11 +633,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
environment: base64PublicKey?.toPublicKeyEnv(),
);
final stdoutLines = <String>[];
buildProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((line) {
logger.detail(line);
stdoutLines.add(line);
// TODO(bryanoltman): update build progress
});
@@ -630,10 +647,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
final stdErr = stderrLines.join('\n');
final exitCode = await buildProcess.exitCode;
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('Failed to build: $stdErr');
throw ArtifactBuildException(
'Failed to build',
stderr: stderrLines,
stdout: stdoutLines,
);
}
});
@@ -6,7 +6,7 @@ import 'package:io/io.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
@@ -6,7 +6,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive/directory_archive.dart';
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive/archive.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/archive/archive.dart';
import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -6,7 +6,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive/archive.dart';
import 'package:shorebird_cli/src/archive_analysis/windows_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -4,7 +4,7 @@ import 'package:archive/archive_io.dart';
import 'package:io/io.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
@@ -4,7 +4,7 @@ import 'package:io/io.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
@@ -2,7 +2,7 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive_analysis/plist.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
@@ -4,12 +4,14 @@ import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:meta/meta.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
import 'package:shorebird_cli/src/common_arguments.dart';
import 'package:shorebird_cli/src/config/config.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/extensions/string.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/metadata/metadata.dart';
import 'package:shorebird_cli/src/platform.dart';
@@ -278,8 +280,29 @@ of the iOS app that is using this module. (aar and ios-framework only)''',
);
final FileSystemEntity releaseArtifact;
try {
releaseArtifact = await releaser.buildReleaseArtifacts();
releaseArtifact = await releaser.buildReleaseArtifacts(
progress: buildProgress,
);
buildProgress.complete();
} on ArtifactBuildException catch (e) {
buildProgress.fail(e.message);
logger
..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}')
..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}');
if (!e.flutterError.isNullOrEmpty) {
logger.err(e.flutterError);
}
if (!e.fixRecommendation.isNullOrEmpty) {
logger.info(e.fixRecommendation);
}
if (e.fixRecommendation.isNullOrEmpty &&
e.flutterError.isNullOrEmpty) {
// If we have no fix recommendation or were unable to parse a
// flutter error, fall back to printing the raw stderr.
logger.info(e.stderr.join(Platform.lineTerminator));
}
throw ProcessExit(ExitCode.software.code);
} on Exception catch (e) {
buildProgress.fail('Failed to build release artifacts: $e');
throw ProcessExit(ExitCode.software.code);
@@ -4,7 +4,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive/archive.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/releaser.dart';
@@ -0,0 +1,117 @@
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:test/test.dart';
void main() {
group(ArtifactBuildException, () {
group('fromProcessResult', () {
test('translates stdout and stderr to lists of strings', () {
const buildProcessResult = ShorebirdProcessResult(
exitCode: 1,
stdout: 'stdout',
stderr: 'stderr',
);
final exception = ArtifactBuildException.fromProcessResult(
'message',
buildProcessResult: buildProcessResult,
);
expect(exception.stdout, ['stdout']);
expect(exception.stderr, ['stderr']);
});
});
group('when no errors are recognized', () {
test('returns a message with no fix recommendation or Flutter error', () {
final exception = ArtifactBuildException(
'message',
stderr: ['some stderr output'],
stdout: ['some stdout output'],
);
expect(exception.flutterError, isEmpty);
expect(exception.fixRecommendation, isNull);
});
});
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'''
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'),
);
});
});
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'));
});
});
});
}
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/os/operating_system_interface.dart';
@@ -17,19 +17,10 @@ import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:test/test.dart';
import 'fakes.dart';
import 'mocks.dart';
import '../fakes.dart';
import '../mocks.dart';
void main() {
group(ArtifactBuildException, () {
test('toString is message', () {
expect(
ArtifactBuildException('my message').toString(),
equals('my message'),
);
});
});
group(ArtifactBuilder, () {
final projectRoot = Directory.systemTemp.createTempSync();
late Apple apple;
@@ -760,7 +751,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
equals('Failed to build: stderr contents'),
equals('Failed to build'),
),
),
);
@@ -1002,13 +993,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
expect(
() => runWithOverrides(() => builder.buildMacos(codesign: false)),
throwsA(
isA<ArtifactBuildException>().having(
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
'''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''',
'Unable to find app.dill file.',
)
.having(
(e) => e.fixRecommendation,
'fixRecommendation',
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
),
),
);
@@ -1267,11 +1261,13 @@ error: exportArchive: No signing certificate "iOS Distribution" found''',
expect(
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
'''
Failed to build:
isA<ArtifactBuildException>()
.having((e) => e.message, 'message', 'Failed to build')
.having(
(e) => e.stderr,
'stderr',
const LineSplitter().convert(
'''
Encountered error while creating the IPA:
error: exportArchive: Communication with Apple failed
error: exportArchive: No signing certificate "iOS Distribution" found
@@ -1285,7 +1281,8 @@ error: exportArchive: Communication with Apple failed
error: exportArchive: No signing certificate "iOS Distribution" found
error: exportArchive: Communication with Apple failed
error: exportArchive: No signing certificate "iOS Distribution" found''',
),
),
),
),
);
});
@@ -1321,11 +1318,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''',
expect(
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
'''
Failed to build:
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
'Failed to build',
)
.having(
(e) => e.stderr,
'stderr',
const LineSplitter().convert('''
Encountered error while creating the IPA:
error: exportArchive Communication with Apple failed
error: exportArchive No signing certificate "iOS Distribution" found
@@ -1338,8 +1340,8 @@ error: exportArchive No signing certificate "iOS Distribution" found
error: exportArchive Communication with Apple failed
error: exportArchive No signing certificate "iOS Distribution" found
error: exportArchive Communication with Apple failed
error: exportArchive No signing certificate "iOS Distribution" found''',
),
error: exportArchive No signing certificate "iOS Distribution" found'''),
),
),
);
});
@@ -1361,13 +1363,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''',
expect(
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
throwsA(
isA<ArtifactBuildException>().having(
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
'''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''',
'Unable to find app.dill file.',
)
.having(
(e) => e.fixRecommendation,
'fixRecommendation',
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
),
),
);
@@ -1479,13 +1484,16 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
expect(
() => runWithOverrides(builder.buildIosFramework),
throwsA(
isA<ArtifactBuildException>().having(
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
'''
Unable to find app.dill file.
Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.
''',
'Unable to find app.dill file.',
)
.having(
(e) => e.fixRecommendation,
'fixRecommendation',
'''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''',
),
),
);
@@ -1630,7 +1638,7 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
equals('Failed to build: stderr contents'),
equals('Failed to build'),
),
),
);
@@ -6,7 +6,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/patch/patch.dart';
@@ -10,7 +10,7 @@ import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -8,7 +8,7 @@ import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -10,7 +10,7 @@ import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -8,7 +8,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
@@ -9,7 +9,7 @@ import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/aar_releaser.dart';
import 'package:shorebird_cli/src/engine_config.dart';
@@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
import 'package:shorebird_cli/src/commands/release/android_releaser.dart';
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/ios_framework_releaser.dart';
@@ -9,7 +9,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
@@ -9,7 +9,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
@@ -5,6 +5,7 @@ import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/commands/release/release.dart';
@@ -139,7 +140,7 @@ void main() {
when(() => releaser.assertPreconditions()).thenAnswer((_) async => {});
when(() => releaser.assertArgsAreValid()).thenAnswer((_) async => {});
when(
() => releaser.buildReleaseArtifacts(),
() => releaser.buildReleaseArtifacts(progress: any(named: 'progress')),
).thenAnswer((_) async => File(''));
when(
() => releaser.getReleaseVersion(
@@ -266,7 +267,7 @@ void main() {
() => logger.progress(
'Building $artifactDisplayName with Flutter $flutterRevision',
),
releaser.buildReleaseArtifacts,
() => releaser.buildReleaseArtifacts(progress: any(named: 'progress')),
() => progress.complete(
'Building $artifactDisplayName with Flutter $flutterRevision',
),
@@ -296,7 +297,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
group('when build fails', () {
setUp(() {
when(
() => releaser.buildReleaseArtifacts(),
() => releaser.buildReleaseArtifacts(
progress: any(named: 'progress'),
),
).thenThrow(Exception('oops'));
});
@@ -311,6 +314,65 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
),
).called(1);
});
group('when failure is an ArtifactBuildException', () {
late ArtifactBuildException exception;
setUp(() {
exception = MockArtifactBuildException();
when(() => exception.message).thenReturn('oops');
when(() => exception.stderr).thenReturn(['stderr']);
when(() => exception.stdout).thenReturn(['stdout']);
when(
() => releaser.buildReleaseArtifacts(
progress: any(named: 'progress'),
),
).thenThrow(exception);
});
group('when a Flutter error was detected', () {
setUp(() {
when(() => exception.flutterError).thenReturn('flutter error');
});
test('logs Flutter error at the err level', () async {
await expectLater(
() => runWithOverrides(command.run),
exitsWithCode(ExitCode.software),
);
verify(() => logger.err('flutter error')).called(1);
});
});
group('when a fix recommendation is provided', () {
setUp(() {
when(() => exception.fixRecommendation).thenReturn('fix it');
});
test('logs fix recommendation at the info level', () async {
await expectLater(
() => runWithOverrides(command.run),
exitsWithCode(ExitCode.software),
);
verify(() => logger.info('fix it')).called(1);
});
});
group('when neither flutter error nor fix suggestion are provided', () {
setUp(() {
when(() => exception.flutterError).thenReturn(null);
when(() => exception.fixRecommendation).thenReturn(null);
});
test('logs stderr', () async {
await expectLater(
() => runWithOverrides(command.run),
exitsWithCode(ExitCode.software),
);
verify(() => logger.info('stderr')).called(1);
});
});
});
});
group('when dry-run is specified', () {
@@ -392,7 +454,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel
() => logger.progress(
'Building $artifactDisplayName with Flutter $flutterRevision',
),
releaser.buildReleaseArtifacts,
() => releaser.buildReleaseArtifacts(
progress: any(named: 'progress'),
),
() => progress.complete(
'Building $artifactDisplayName with Flutter $flutterRevision',
),
@@ -566,7 +630,11 @@ $exception''',
test(
'uses specified flutter version to build '
'and reverts to original flutter version', () async {
when(releaser.buildReleaseArtifacts).thenAnswer((_) async {
when(
() => releaser.buildReleaseArtifacts(
progress: any(named: 'progress'),
),
).thenAnswer((_) async {
// Ensure we're using the correct flutter version.
expect(shorebirdEnv.flutterRevision, equals(revision));
return File('');
@@ -7,7 +7,7 @@ import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
import 'package:shorebird_cli/src/code_signer.dart';
+4 -1
View File
@@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/android_sdk.dart';
import 'package:shorebird_cli/src/android_studio.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/auth/auth.dart';
import 'package:shorebird_cli/src/cache.dart' show Cache;
@@ -63,6 +63,9 @@ class MockArgParser extends Mock implements ArgParser {}
class MockArgResults extends Mock implements ArgResults {}
class MockArtifactBuildException extends Mock
implements ArtifactBuildException {}
class MockArtifactBuilder extends Mock implements ArtifactBuilder {}
class MockArtifactManager extends Mock implements ArtifactManager {}