mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[flutter tools] rewrite launch non-prebuilt app tests (#53351)
This commit is contained in:
parent
c5800fe107
commit
2717eb6413
@ -3,11 +3,12 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
@ -15,26 +16,15 @@ import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/create.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/doctor.dart';
|
||||
import 'package:flutter_tools/src/ios/devices.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_deploy.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fakes.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
@ -59,7 +49,6 @@ void main() {
|
||||
mockCache = MockCache();
|
||||
const MapEntry<String, String> dyLdLibEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', '/path/to/libs');
|
||||
when(mockCache.dyLdLibEntry).thenReturn(dyLdLibEntry);
|
||||
mockFileSystem = MockFileSystem();
|
||||
logger = BufferLogger.test();
|
||||
iosDeploy = IOSDeploy(
|
||||
artifacts: mockArtifacts,
|
||||
@ -231,7 +220,6 @@ void main() {
|
||||
forwardedPort = ForwardedPort.withContext(123, 456, mockProcess3);
|
||||
mockArtifacts = MockArtifacts();
|
||||
mockCache = MockCache();
|
||||
mockFileSystem = MockFileSystem();
|
||||
iosDeploy = IOSDeploy(
|
||||
artifacts: mockArtifacts,
|
||||
cache: mockCache,
|
||||
@ -268,271 +256,12 @@ void main() {
|
||||
verify(mockProcess3.kill());
|
||||
});
|
||||
});
|
||||
|
||||
group('startApp', () {
|
||||
MockIOSApp mockApp;
|
||||
MockArtifacts mockArtifacts;
|
||||
MockCache mockCache;
|
||||
MockFileSystem mockFileSystem;
|
||||
MockPlatform mockPlatform;
|
||||
MockProcessManager mockProcessManager;
|
||||
FakeDeviceLogReader mockLogReader;
|
||||
MockPortForwarder mockPortForwarder;
|
||||
MockIMobileDevice mockIMobileDevice;
|
||||
MockIOSDeploy mockIosDeploy;
|
||||
|
||||
Directory tempDir;
|
||||
Directory projectDir;
|
||||
|
||||
const int devicePort = 499;
|
||||
const int hostPort = 42;
|
||||
const String installerPath = '/path/to/ideviceinstaller';
|
||||
const String iosDeployPath = '/path/to/iosdeploy';
|
||||
const String iproxyPath = '/path/to/iproxy';
|
||||
const MapEntry<String, String> libraryEntry = MapEntry<String, String>(
|
||||
'DYLD_LIBRARY_PATH',
|
||||
'/path/to/libraries',
|
||||
);
|
||||
final Map<String, String> env = Map<String, String>.fromEntries(
|
||||
<MapEntry<String, String>>[libraryEntry]
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
Cache.disableLocking();
|
||||
|
||||
mockApp = MockIOSApp();
|
||||
mockArtifacts = MockArtifacts();
|
||||
mockCache = MockCache();
|
||||
when(mockCache.dyLdLibEntry).thenReturn(libraryEntry);
|
||||
mockFileSystem = MockFileSystem();
|
||||
mockPlatform = MockPlatform();
|
||||
when(mockPlatform.isMacOS).thenReturn(true);
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockLogReader = FakeDeviceLogReader();
|
||||
mockPortForwarder = MockPortForwarder();
|
||||
mockIMobileDevice = MockIMobileDevice();
|
||||
mockIosDeploy = MockIOSDeploy();
|
||||
|
||||
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
|
||||
projectDir = tempDir.childDirectory('flutter_project');
|
||||
|
||||
when(
|
||||
mockArtifacts.getArtifactPath(
|
||||
Artifact.ideviceinstaller,
|
||||
platform: anyNamed('platform'),
|
||||
),
|
||||
).thenReturn(installerPath);
|
||||
|
||||
when(
|
||||
mockArtifacts.getArtifactPath(
|
||||
Artifact.iosDeploy,
|
||||
platform: anyNamed('platform'),
|
||||
),
|
||||
).thenReturn(iosDeployPath);
|
||||
|
||||
when(
|
||||
mockArtifacts.getArtifactPath(
|
||||
Artifact.iproxy,
|
||||
platform: anyNamed('platform'),
|
||||
),
|
||||
).thenReturn(iproxyPath);
|
||||
|
||||
when(mockPortForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
|
||||
.thenAnswer((_) async => hostPort);
|
||||
when(mockPortForwarder.forwardedPorts)
|
||||
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
|
||||
when(mockPortForwarder.unforward(any))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
final MemoryFileSystem memoryFileSystem = MemoryFileSystem();
|
||||
when(mockFileSystem.currentDirectory)
|
||||
.thenReturn(memoryFileSystem.currentDirectory);
|
||||
|
||||
const String bundlePath = '/path/to/bundle';
|
||||
final List<String> installArgs = <String>[installerPath, '-i', bundlePath];
|
||||
when(mockApp.deviceBundlePath).thenReturn(bundlePath);
|
||||
final MockDirectory directory = MockDirectory();
|
||||
when(mockFileSystem.directory(bundlePath)).thenReturn(directory);
|
||||
when(directory.existsSync()).thenReturn(true);
|
||||
when(mockProcessManager.run(
|
||||
installArgs,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: env,
|
||||
)).thenAnswer(
|
||||
(_) => Future<ProcessResult>.value(ProcessResult(1, 0, '', ''))
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
mockLogReader.dispose();
|
||||
tryToDelete(tempDir);
|
||||
|
||||
Cache.enableLocking();
|
||||
});
|
||||
|
||||
void testNonPrebuilt(
|
||||
String name, {
|
||||
@required bool showBuildSettingsFlakes,
|
||||
void Function() additionalSetup,
|
||||
void Function() additionalExpectations,
|
||||
}) {
|
||||
testUsingContext('non-prebuilt succeeds in debug mode $name', () async {
|
||||
final Directory targetBuildDir =
|
||||
projectDir.childDirectory('build/ios/iphoneos/Debug-arm64');
|
||||
|
||||
// The -showBuildSettings calls have a timeout and so go through
|
||||
// globals.processManager.start().
|
||||
mockProcessManager.processFactory = flakyProcessFactory(
|
||||
flakes: showBuildSettingsFlakes ? 1 : 0,
|
||||
delay: const Duration(seconds: 62),
|
||||
filter: (List<String> args) => args.contains('-showBuildSettings'),
|
||||
stdout:
|
||||
() => Stream<String>
|
||||
.fromIterable(
|
||||
<String>['TARGET_BUILD_DIR = ${targetBuildDir.path}\n'])
|
||||
.transform(utf8.encoder),
|
||||
);
|
||||
|
||||
// Make all other subcommands succeed.
|
||||
when(mockProcessManager.run(
|
||||
any,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((Invocation inv) {
|
||||
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
|
||||
});
|
||||
|
||||
when(mockProcessManager.run(
|
||||
argThat(contains('find-identity')),
|
||||
environment: anyNamed('environment'),
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
)).thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(
|
||||
1, // pid
|
||||
0, // exitCode
|
||||
'''
|
||||
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
|
||||
2) da4b9237bacccdf19c0760cab7aec4a8359010b0 "iPhone Developer: Profile 2 (2222BBBB22)"
|
||||
3) 5bf1fd927dfb8679496a2e6cf00cbe50c1c87145 "iPhone Developer: Profile 3 (3333CCCC33)"
|
||||
3 valid identities found''',
|
||||
'',
|
||||
)));
|
||||
|
||||
// Deploy works.
|
||||
when(mockIosDeploy.runApp(
|
||||
deviceId: anyNamed('deviceId'),
|
||||
bundlePath: anyNamed('bundlePath'),
|
||||
launchArguments: anyNamed('launchArguments'),
|
||||
)).thenAnswer((_) => Future<int>.value(0));
|
||||
|
||||
// Create a dummy project to avoid mocking out the whole directory
|
||||
// structure expected by device.startApp().
|
||||
Cache.flutterRoot = '../..';
|
||||
final CreateCommand command = CreateCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>[
|
||||
'create',
|
||||
'--no-pub',
|
||||
projectDir.path,
|
||||
]);
|
||||
|
||||
if (additionalSetup != null) {
|
||||
additionalSetup();
|
||||
}
|
||||
|
||||
final IOSApp app = await AbsoluteBuildableIOSApp.fromProject(
|
||||
FlutterProject.fromDirectory(projectDir).ios);
|
||||
final IOSDevice device = IOSDevice(
|
||||
'123',
|
||||
name: 'iPhone 1',
|
||||
sdkVersion: '13.3',
|
||||
artifacts: mockArtifacts,
|
||||
fileSystem: globals.fs,
|
||||
logger: testLogger,
|
||||
platform: macPlatform,
|
||||
iosDeploy: mockIosDeploy,
|
||||
iMobileDevice: iMobileDevice,
|
||||
cpuArchitecture: DarwinArch.arm64,
|
||||
);
|
||||
|
||||
// Pre-create the expected build products.
|
||||
targetBuildDir.createSync(recursive: true);
|
||||
projectDir.childDirectory('build/ios/iphoneos/Runner.app').createSync(recursive: true);
|
||||
|
||||
final Completer<LaunchResult> completer = Completer<LaunchResult>();
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
device.startApp(
|
||||
app,
|
||||
prebuiltApplication: false,
|
||||
debuggingOptions: DebuggingOptions.disabled(const BuildInfo(BuildMode.debug, null, treeShakeIcons: false)),
|
||||
platformArgs: <String, dynamic>{},
|
||||
).then((LaunchResult result) {
|
||||
completer.complete(result);
|
||||
});
|
||||
time.flushMicrotasks();
|
||||
time.elapse(const Duration(seconds: 65));
|
||||
});
|
||||
final LaunchResult launchResult = await completer.future;
|
||||
expect(launchResult.started, isTrue);
|
||||
expect(launchResult.hasObservatory, isFalse);
|
||||
expect(await device.stopApp(mockApp), isFalse);
|
||||
|
||||
if (additionalExpectations != null) {
|
||||
additionalExpectations();
|
||||
}
|
||||
}, overrides: <Type, Generator>{
|
||||
DoctorValidatorsProvider: () => FakeIosDoctorProvider(),
|
||||
IMobileDevice: () => mockIMobileDevice,
|
||||
Platform: () => macPlatform,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
}
|
||||
|
||||
testNonPrebuilt('flaky: false', showBuildSettingsFlakes: false);
|
||||
testNonPrebuilt('flaky: true', showBuildSettingsFlakes: true);
|
||||
testNonPrebuilt('with concurrent build failiure',
|
||||
showBuildSettingsFlakes: false,
|
||||
additionalSetup: () {
|
||||
int callCount = 0;
|
||||
when(mockProcessManager.run(
|
||||
argThat(allOf(
|
||||
contains('xcodebuild'),
|
||||
contains('-configuration'),
|
||||
contains('Debug'),
|
||||
)),
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((Invocation inv) {
|
||||
// Succeed after 2 calls.
|
||||
if (++callCount > 2) {
|
||||
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
|
||||
}
|
||||
// Otherwise fail with the Xcode concurrent error.
|
||||
return Future<ProcessResult>.value(ProcessResult(
|
||||
0,
|
||||
1,
|
||||
'''
|
||||
"/Developer/Xcode/DerivedData/foo/XCBuildData/build.db":
|
||||
database is locked
|
||||
Possibly there are two concurrent builds running in the same filesystem location.
|
||||
''',
|
||||
'',
|
||||
));
|
||||
});
|
||||
},
|
||||
additionalExpectations: () {
|
||||
expect(testLogger.statusText, contains('will retry in 2 seconds'));
|
||||
expect(testLogger.statusText, contains('will retry in 4 seconds'));
|
||||
expect(testLogger.statusText, contains('Xcode build done.'));
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('pollingGetDevices', () {
|
||||
MockXcdevice mockXcdevice;
|
||||
MockArtifacts mockArtifacts;
|
||||
MockCache mockCache;
|
||||
MockFileSystem mockFileSystem;
|
||||
FakeProcessManager fakeProcessManager;
|
||||
Logger logger;
|
||||
IOSDeploy iosDeploy;
|
||||
@ -544,7 +273,6 @@ void main() {
|
||||
mockArtifacts = MockArtifacts();
|
||||
mockCache = MockCache();
|
||||
logger = BufferLogger.test();
|
||||
mockFileSystem = MockFileSystem();
|
||||
mockIosWorkflow = MockIOSWorkflow();
|
||||
fakeProcessManager = FakeProcessManager.any();
|
||||
iosDeploy = IOSDeploy(
|
||||
@ -596,7 +324,7 @@ void main() {
|
||||
iMobileDevice: iMobileDevice,
|
||||
logger: logger,
|
||||
platform: macPlatform,
|
||||
fileSystem: mockFileSystem,
|
||||
fileSystem: MemoryFileSystem.test(),
|
||||
);
|
||||
when(mockXcdevice.getAvailableTetheredIOSDevices())
|
||||
.thenAnswer((Invocation invocation) => Future<List<IOSDevice>>.value(<IOSDevice>[device]));
|
||||
@ -646,48 +374,10 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
class AbsoluteBuildableIOSApp extends BuildableIOSApp {
|
||||
AbsoluteBuildableIOSApp(IosProject project, String projectBundleId) :
|
||||
super(project, projectBundleId);
|
||||
|
||||
static Future<AbsoluteBuildableIOSApp> fromProject(IosProject project) async {
|
||||
final String projectBundleId = await project.productBundleIdentifier;
|
||||
return AbsoluteBuildableIOSApp(project, projectBundleId);
|
||||
}
|
||||
|
||||
@override
|
||||
String get deviceBundlePath =>
|
||||
globals.fs.path.join(project.parent.directory.path, 'build', 'ios', 'iphoneos', name);
|
||||
|
||||
}
|
||||
|
||||
class FakeIosDoctorProvider implements DoctorValidatorsProvider {
|
||||
List<Workflow> _workflows;
|
||||
|
||||
@override
|
||||
List<DoctorValidator> get validators => <DoctorValidator>[];
|
||||
|
||||
@override
|
||||
List<Workflow> get workflows {
|
||||
if (_workflows == null) {
|
||||
_workflows = <Workflow>[];
|
||||
if (globals.iosWorkflow.appliesToHostPlatform) {
|
||||
_workflows.add(globals.iosWorkflow);
|
||||
}
|
||||
}
|
||||
return _workflows;
|
||||
}
|
||||
}
|
||||
|
||||
class MockIOSApp extends Mock implements IOSApp {}
|
||||
class MockArtifacts extends Mock implements Artifacts {}
|
||||
class MockCache extends Mock implements Cache {}
|
||||
class MockDirectory extends Mock implements Directory {}
|
||||
class MockFile extends Mock implements File {}
|
||||
class MockFileSystem extends Mock implements FileSystem {}
|
||||
class MockIMobileDevice extends Mock implements IMobileDevice {}
|
||||
class MockIOSDeploy extends Mock implements IOSDeploy {}
|
||||
class MockIOSWorkflow extends Mock implements IOSWorkflow {}
|
||||
class MockPlatform extends Mock implements Platform {}
|
||||
class MockPortForwarder extends Mock implements DevicePortForwarder {}
|
||||
class MockXcdevice extends Mock implements XCDevice {}
|
||||
|
||||
@ -0,0 +1,300 @@
|
||||
// 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.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/application_package.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/ios/devices.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_deploy.dart';
|
||||
import 'package:flutter_tools/src/ios/mac.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:quiver/testing/async.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/fake_process_manager.dart';
|
||||
|
||||
const List<String> kRunReleaseArgs = <String>[
|
||||
'/usr/bin/env',
|
||||
'xcrun',
|
||||
'xcodebuild',
|
||||
'-configuration',
|
||||
'Release',
|
||||
'-quiet',
|
||||
'-workspace',
|
||||
'Runner.xcworkspace',
|
||||
'-scheme',
|
||||
'Runner',
|
||||
'BUILD_DIR=/build/ios',
|
||||
'-sdk',
|
||||
'iphoneos',
|
||||
'ONLY_ACTIVE_ARCH=YES',
|
||||
'ARCHS=arm64',
|
||||
'FLUTTER_SUPPRESS_ANALYTICS=true',
|
||||
'COMPILER_INDEX_STORE_ENABLE=NO',
|
||||
];
|
||||
|
||||
const String kConcurrentBuildErrorMessage = '''
|
||||
"/Developer/Xcode/DerivedData/foo/XCBuildData/build.db":
|
||||
database is locked
|
||||
Possibly there are two concurrent builds running in the same filesystem location.
|
||||
''';
|
||||
|
||||
final FakePlatform macPlatform = FakePlatform(
|
||||
operatingSystem: 'macos',
|
||||
environment: <String, String>{},
|
||||
);
|
||||
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
FakeProcessManager processManager;
|
||||
BufferLogger logger;
|
||||
|
||||
setUp(() {
|
||||
logger = BufferLogger.test();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[]);
|
||||
});
|
||||
|
||||
testUsingContext('IOSDevice.startApp succeeds in release mode with buildable app', () async {
|
||||
final IOSDevice iosDevice = setUpIOSDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter');
|
||||
|
||||
processManager.addCommand(const FakeCommand(command: kRunReleaseArgs));
|
||||
processManager.addCommand(const FakeCommand(command: <String>[...kRunReleaseArgs, '-showBuildSettings']));
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: <String>[
|
||||
'ios-deploy',
|
||||
'--id',
|
||||
'123',
|
||||
'--bundle',
|
||||
'build/ios/iphoneos/Runner.app',
|
||||
'--no-wifi',
|
||||
'--justlaunch',
|
||||
'--args',
|
||||
const <String>[
|
||||
'--enable-dart-profiling',
|
||||
'--enable-service-port-fallback',
|
||||
'--disable-service-auth-codes',
|
||||
'--observatory-port=53781',
|
||||
].join(' ')
|
||||
])
|
||||
);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
buildableIOSApp,
|
||||
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
|
||||
platformArgs: <String, Object>{},
|
||||
);
|
||||
|
||||
expect(launchResult.started, true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
FileSystem: () => fileSystem,
|
||||
Logger: () => logger,
|
||||
Platform: () => macPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('IOSDevice.startApp succeeds in release mode with buildable '
|
||||
'app with flaky buildSettings call', () async {
|
||||
LaunchResult launchResult;
|
||||
FakeAsync().run((FakeAsync time) {
|
||||
final IOSDevice iosDevice = setUpIOSDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter');
|
||||
|
||||
processManager.addCommand(const FakeCommand(command: kRunReleaseArgs));
|
||||
// The first showBuildSettings call should timeout.
|
||||
processManager.addCommand(
|
||||
const FakeCommand(
|
||||
command: <String>[...kRunReleaseArgs, '-showBuildSettings'],
|
||||
duration: Duration(minutes: 5), // this is longer than the timeout of 1 minute.
|
||||
));
|
||||
// The second call succeedes and is made after the first times out.
|
||||
processManager.addCommand(
|
||||
const FakeCommand(
|
||||
command: <String>[...kRunReleaseArgs, '-showBuildSettings'],
|
||||
exitCode: 0,
|
||||
));
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: <String>[
|
||||
'ios-deploy',
|
||||
'--id',
|
||||
'123',
|
||||
'--bundle',
|
||||
'build/ios/iphoneos/Runner.app',
|
||||
'--no-wifi',
|
||||
'--justlaunch',
|
||||
'--args',
|
||||
const <String>[
|
||||
'--enable-dart-profiling',
|
||||
'--enable-service-port-fallback',
|
||||
'--disable-service-auth-codes',
|
||||
'--observatory-port=53781',
|
||||
].join(' ')
|
||||
])
|
||||
);
|
||||
|
||||
iosDevice.startApp(
|
||||
buildableIOSApp,
|
||||
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
|
||||
platformArgs: <String, Object>{},
|
||||
).then((LaunchResult result) {
|
||||
launchResult = result;
|
||||
});
|
||||
|
||||
// Elapse duration for process timeout.
|
||||
time.flushMicrotasks();
|
||||
time.elapse(const Duration(minutes: 1));
|
||||
|
||||
// Elapse duration for overall process timer.
|
||||
time.flushMicrotasks();
|
||||
time.elapse(const Duration(minutes: 5));
|
||||
|
||||
time.flushTimers();
|
||||
});
|
||||
|
||||
expect(launchResult?.started, true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
FileSystem: () => fileSystem,
|
||||
Logger: () => logger,
|
||||
Platform: () => macPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('IOSDevice.startApp succeeds in release mode with buildable '
|
||||
'app with concurrent build failure', () async {
|
||||
final IOSDevice iosDevice = setUpIOSDevice(
|
||||
fileSystem: fileSystem,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
);
|
||||
setUpIOSProject(fileSystem);
|
||||
final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory);
|
||||
final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter');
|
||||
|
||||
// The first xcrun call should fail with a
|
||||
// concurrent build exception.
|
||||
processManager.addCommand(
|
||||
const FakeCommand(
|
||||
command: kRunReleaseArgs,
|
||||
exitCode: 1,
|
||||
stdout: kConcurrentBuildErrorMessage,
|
||||
));
|
||||
processManager.addCommand(const FakeCommand(command: kRunReleaseArgs));
|
||||
processManager.addCommand(
|
||||
const FakeCommand(
|
||||
command: <String>[...kRunReleaseArgs, '-showBuildSettings'],
|
||||
exitCode: 0,
|
||||
));
|
||||
processManager.addCommand(FakeCommand(
|
||||
command: <String>[
|
||||
'ios-deploy',
|
||||
'--id',
|
||||
'123',
|
||||
'--bundle',
|
||||
'build/ios/iphoneos/Runner.app',
|
||||
'--no-wifi',
|
||||
'--justlaunch',
|
||||
'--args',
|
||||
const <String>[
|
||||
'--enable-dart-profiling',
|
||||
'--enable-service-port-fallback',
|
||||
'--disable-service-auth-codes',
|
||||
'--observatory-port=53781',
|
||||
].join(' ')
|
||||
])
|
||||
);
|
||||
|
||||
final LaunchResult launchResult = await iosDevice.startApp(
|
||||
buildableIOSApp,
|
||||
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
|
||||
platformArgs: <String, Object>{},
|
||||
);
|
||||
|
||||
expect(logger.statusText,
|
||||
contains('Xcode build failed due to concurrent builds, will retry in 2 seconds'));
|
||||
expect(launchResult.started, true);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
FileSystem: () => fileSystem,
|
||||
Logger: () => logger,
|
||||
Platform: () => macPlatform,
|
||||
});
|
||||
}
|
||||
|
||||
void setUpIOSProject(FileSystem fileSystem) {
|
||||
fileSystem.file('pubspec.yaml').createSync();
|
||||
fileSystem.file('.packages').writeAsStringSync('\n');
|
||||
fileSystem.directory('ios').createSync();
|
||||
fileSystem.directory('ios/Runner.xcworkspace').createSync();
|
||||
fileSystem.directory('ios/Runner.xcodeproj').createSync();
|
||||
fileSystem.file('ios/Runner.xcodeproj/project.pbxproj').createSync();
|
||||
// This is the expected output directory.
|
||||
fileSystem.directory('build/ios/iphoneos/Runner.app').createSync(recursive: true);
|
||||
}
|
||||
|
||||
IOSDevice setUpIOSDevice({
|
||||
String sdkVersion = '13.0.1',
|
||||
FileSystem fileSystem,
|
||||
Logger logger,
|
||||
ProcessManager processManager,
|
||||
}) {
|
||||
const MapEntry<String, String> dyldLibraryEntry = MapEntry<String, String>(
|
||||
'DYLD_LIBRARY_PATH',
|
||||
'/path/to/libraries',
|
||||
);
|
||||
final MockCache cache = MockCache();
|
||||
final MockArtifacts artifacts = MockArtifacts();
|
||||
logger ??= BufferLogger.test();
|
||||
when(cache.dyLdLibEntry).thenReturn(dyldLibraryEntry);
|
||||
when(artifacts.getArtifactPath(Artifact.iosDeploy, platform: anyNamed('platform')))
|
||||
.thenReturn('ios-deploy');
|
||||
return IOSDevice('123',
|
||||
name: 'iPhone 1',
|
||||
sdkVersion: sdkVersion,
|
||||
fileSystem: fileSystem ?? MemoryFileSystem.test(),
|
||||
platform: macPlatform,
|
||||
artifacts: artifacts,
|
||||
logger: logger,
|
||||
iosDeploy: IOSDeploy(
|
||||
logger: logger,
|
||||
platform: macPlatform,
|
||||
processManager: processManager ?? FakeProcessManager.any(),
|
||||
artifacts: artifacts,
|
||||
cache: cache,
|
||||
),
|
||||
iMobileDevice: IMobileDevice(
|
||||
logger: logger,
|
||||
processManager: processManager ?? FakeProcessManager.any(),
|
||||
artifacts: artifacts,
|
||||
cache: cache,
|
||||
),
|
||||
cpuArchitecture: DarwinArch.arm64,
|
||||
);
|
||||
}
|
||||
|
||||
class MockArtifacts extends Mock implements Artifacts {}
|
||||
class MockCache extends Mock implements Cache {}
|
||||
Loading…
x
Reference in New Issue
Block a user