flutter/packages/flutter_tools/test/commands.shard/permeable/build_appbundle_test.dart
Jonah Williams 8b6baae44c
[flutter_tools] move process manager into tool (#75350)
Our current top crasher is an unclear error when ProcessManager fails to resolve an executable path. To fix this, we'd like to being adjusting the process resolution logic and adding more instrumentation to track failures. In order to begin the process, the ProcessManager has been folded back into the flutter tool
2021-02-04 13:19:11 -08:00

352 lines
12 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/android/android_builder.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_appbundle.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import '../../src/android_common.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
void main() {
Cache.disableLocking();
group('Usage', () {
Directory tempDir;
TestUsage testUsage;
setUp(() {
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
testUsage = TestUsage();
});
tearDown(() {
tryToDelete(tempDir);
});
testUsingContext('indicate the default target platforms', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
final BuildAppBundleCommand command = await runBuildAppBundleCommand(projectPath);
expect(await command.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleTargetPlatform, 'android-arm,android-arm64,android-x64'));
}, overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(),
});
testUsingContext('build type', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
final BuildAppBundleCommand commandDefault = await runBuildAppBundleCommand(projectPath);
expect(await commandDefault.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
final BuildAppBundleCommand commandInRelease = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--release']);
expect(await commandInRelease.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
final BuildAppBundleCommand commandInDebug = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--debug']);
expect(await commandInDebug.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'debug'));
final BuildAppBundleCommand commandInProfile = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--profile']);
expect(await commandInProfile.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'profile'));
}, overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(),
});
testUsingContext('logs success', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
await runBuildAppBundleCommand(projectPath);
expect(testUsage.events, contains(
const TestUsageEvent('tool-command-result', 'appbundle', label: 'success'),
));
},
overrides: <Type, Generator>{
AndroidBuilder: () => FakeAndroidBuilder(),
Usage: () => testUsage,
});
});
group('Gradle', () {
Directory tempDir;
ProcessManager mockProcessManager;
MockAndroidSdk mockAndroidSdk;
String gradlew;
TestUsage testUsage;
setUp(() {
testUsage = TestUsage();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
gradlew = globals.fs.path.join(tempDir.path, 'flutter_project', 'android',
globals.platform.isWindows ? 'gradlew.bat' : 'gradlew');
mockProcessManager = MockProcessManager();
when(mockProcessManager.run(<String>[gradlew, '-v'],
environment: anyNamed('environment')))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, '', '')));
when(mockProcessManager.run(<String>[gradlew, 'app:properties'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'buildDir: irrelevant', '')));
when(mockProcessManager.run(<String>[gradlew, 'app:tasks', '--all', '--console=auto'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'assembleRelease', '')));
// Fallback with error.
final Process process = createMockProcess(exitCode: 1);
when(mockProcessManager.start(any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment')))
.thenAnswer((_) => Future<Process>.value(process));
when(mockProcessManager.canRun(any)).thenReturn(false);
when(mockProcessManager.runSync(
argThat(contains(contains('gen_snapshot'))),
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(0, 255, '', ''));
when(mockProcessManager.runSync(
<String>['/usr/bin/xcode-select', '--print-path'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenReturn(ProcessResult(0, 0, '', ''));
when(mockProcessManager.run(
<String>['which', 'pod'],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) {
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
});
mockAndroidSdk = MockAndroidSdk();
when(mockAndroidSdk.licensesAvailable).thenReturn(true);
when(mockAndroidSdk.platformToolsAvailable).thenReturn(true);
when(mockAndroidSdk.validateSdkWellFormed()).thenReturn(const <String>[]);
when(mockAndroidSdk.directory).thenReturn('irrelevant');
});
tearDown(() {
tryToDelete(tempDir);
});
group('AndroidSdk', () {
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
await expectLater(
runBuildAppBundleCommand(
projectPath,
arguments: <String>['--no-pub'],
),
throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'),
);
verifyNever(mockAndroidSdk.validateSdkWellFormed());
verify(mockAndroidSdk.reinitialize()).called(1);
},
overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
ProcessManager: () => mockProcessManager,
});
testUsingContext('throws throwsToolExit if AndroidSdk is null', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
await expectLater(() async {
await runBuildAppBundleCommand(
projectPath,
arguments: <String>['--no-pub'],
);
}, throwsToolExit(
message: 'No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable',
));
},
overrides: <Type, Generator>{
AndroidSdk: () => null,
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
ProcessManager: () => mockProcessManager,
});
});
testUsingContext("reports when the app isn't using AndroidX", () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
// Simulate a non-androidx project.
tempDir
.childDirectory('flutter_project')
.childDirectory('android')
.childFile('gradle.properties')
.writeAsStringSync('android.useAndroidX=false');
when(mockProcessManager.start(
<String>[
gradlew,
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=true',
'assembleRelease',
],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 0,
stdout: '',
),
);
});
// The command throws a [ToolExit] because it expects an AAB in the file system.
await expectLater(() async {
await runBuildAppBundleCommand(
projectPath,
);
}, throwsToolExit());
expect(
testLogger.statusText,
containsIgnoringWhitespace("Your app isn't using AndroidX"),
);
expect(
testLogger.statusText,
containsIgnoringWhitespace(
'To avoid potential build failures, you can quickly migrate your app by '
'following the steps on https://goo.gl/CP92wY'
),
);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'appbundle',
label: 'app-not-using-android-x',
parameters: <String, String>{},
),
));
},
overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
testUsingContext('reports when the app is using AndroidX', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']);
when(mockProcessManager.start(
<String>[
gradlew,
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
'-Ptrack-widget-creation=true',
'assembleRelease',
],
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) {
return Future<Process>.value(
createMockProcess(
exitCode: 0,
stdout: '',
),
);
});
// The command throws a [ToolExit] because it expects an AAB in the file system.
await expectLater(() async {
await runBuildAppBundleCommand(
projectPath,
);
}, throwsToolExit());
expect(
testLogger.statusText,
not(containsIgnoringWhitespace("Your app isn't using AndroidX")),
);
expect(
testLogger.statusText,
not(
containsIgnoringWhitespace(
'To avoid potential build failures, you can quickly migrate your app by '
'following the steps on https://goo.gl/CP92wY'),
)
);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'appbundle',
label: 'app-using-android-x',
parameters: <String, String>{},
),
));
},
overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
ProcessManager: () => mockProcessManager,
Usage: () => testUsage,
});
});
}
Future<BuildAppBundleCommand> runBuildAppBundleCommand(
String target, {
List<String> arguments,
}) async {
final BuildAppBundleCommand command = BuildAppBundleCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'appbundle',
...?arguments,
'--no-pub',
globals.fs.path.join(target, 'lib', 'main.dart'),
]);
return command;
}
Matcher not(Matcher target){
return isNot(target);
}
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}