mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Powered by the new and amazing `unnecessary_ignore` lint. We're not enabling this lint by default because being able to temporarily use ignores that don't ignore anything is a powerful tool to enable migrations. We should turn this lint on locally periodically, though, and clean up all outdated ignores.
1304 lines
43 KiB
Dart
1304 lines
43 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.
|
|
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/version.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/ios/xcode_debug.dart';
|
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
|
import 'package:flutter_tools/src/macos/xcode.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fake_process_manager.dart';
|
|
|
|
void main() {
|
|
group('Debug project through Xcode', () {
|
|
late MemoryFileSystem fileSystem;
|
|
late BufferLogger logger;
|
|
late FakeProcessManager fakeProcessManager;
|
|
|
|
const String flutterRoot = '/path/to/flutter';
|
|
const String pathToXcodeAutomationScript =
|
|
'$flutterRoot/packages/flutter_tools/bin/xcode_debug.js';
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
logger = BufferLogger.test();
|
|
fakeProcessManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
group('debugApp', () {
|
|
const String pathToXcodeApp = '/Applications/Xcode.app';
|
|
const String deviceId = '0000001234';
|
|
|
|
late Xcode xcode;
|
|
late Directory xcodeproj;
|
|
late Directory xcworkspace;
|
|
late XcodeDebugProject project;
|
|
|
|
setUp(() {
|
|
xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
|
|
xcodeproj = fileSystem.directory('Runner.xcodeproj');
|
|
xcworkspace = fileSystem.directory('Runner.xcworkspace');
|
|
project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'succeeds in opening and debugging with launch options, expectedConfigurationBuildDir, and verbose logging',
|
|
() async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--verbose',
|
|
],
|
|
stdout: '''
|
|
{"status":false,"errorMessage":"Xcode is not running","debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>['open', '-a', pathToXcodeApp, '-g', '-j', '-F', xcworkspace.path],
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--expected-configuration-build-dir',
|
|
'/build/ios/iphoneos',
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]',
|
|
'--verbose',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"running","errorMessage":null}}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
expectedConfigurationBuildDir: '/build/ios/iphoneos',
|
|
verboseLogging: true,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(logger.errorText, isEmpty);
|
|
expect(logger.traceText, contains('Error checking if project opened in Xcode'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(xcodeDebug.startDebugActionProcess, isNull);
|
|
expect(status, true);
|
|
},
|
|
);
|
|
|
|
testWithoutContext(
|
|
'succeeds in opening and debugging without launch options, expectedConfigurationBuildDir, and verbose logging',
|
|
() async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":false,"errorMessage":"Xcode is not running","debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>['open', '-a', pathToXcodeApp, '-g', '-j', '-F', xcworkspace.path],
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
'[]',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"running","errorMessage":null}}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>[],
|
|
);
|
|
|
|
expect(logger.errorText, isEmpty);
|
|
expect(logger.traceText, contains('Error checking if project opened in Xcode'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(xcodeDebug.startDebugActionProcess, isNull);
|
|
expect(status, true);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('fails if project fails to open', () async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":false,"errorMessage":"Xcode is not running","debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>['open', '-a', pathToXcodeApp, '-g', '-j', '-F', xcworkspace.path],
|
|
exception: ProcessException('open', <String>[
|
|
'-a',
|
|
'/non_existent_path',
|
|
'-g',
|
|
'-j',
|
|
'-F',
|
|
xcworkspace.path,
|
|
], 'The application /non_existent_path cannot be opened for an unexpected reason'),
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(
|
|
logger.errorText,
|
|
contains('The application /non_existent_path cannot be opened for an unexpected reason'),
|
|
);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, false);
|
|
});
|
|
|
|
testWithoutContext('fails if osascript errors', () async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":"","debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]',
|
|
],
|
|
exitCode: 1,
|
|
stderr:
|
|
"/flutter/packages/flutter_tools/bin/xcode_debug.js: execution error: Error: ReferenceError: Can't find variable: y (-2700)",
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(logger.errorText, contains('Error executing osascript'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, false);
|
|
});
|
|
|
|
testWithoutContext('fails if osascript output returns false status', () async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]',
|
|
],
|
|
stdout: '''
|
|
{"status":false,"errorMessage":"Unable to find target device.","debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(logger.errorText, contains('Error starting debug session in Xcode'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, false);
|
|
});
|
|
|
|
testWithoutContext('fails if missing debug results', () async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(logger.errorText, contains('Unable to get debug results from response'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, false);
|
|
});
|
|
|
|
testWithoutContext('fails if debug results status is not running', () async {
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'check-workspace-opened',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'debug',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--project-name',
|
|
project.hostAppProjectName,
|
|
'--device-id',
|
|
deviceId,
|
|
'--scheme',
|
|
project.scheme,
|
|
'--skip-building',
|
|
'--launch-args',
|
|
r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"not yet started","errorMessage":null}}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final bool status = await xcodeDebug.debugApp(
|
|
project: project,
|
|
deviceId: deviceId,
|
|
launchArguments: <String>['--enable-dart-profiling', '--trace-allowlist="foo,bar"'],
|
|
);
|
|
|
|
expect(logger.errorText, contains('Unexpected debug results'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, false);
|
|
});
|
|
});
|
|
|
|
group('parse script response', () {
|
|
testWithoutContext('fails if osascript output returns non-json output', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: FakeProcessManager.any(),
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('not json');
|
|
|
|
expect(logger.errorText, contains('osascript returned non-JSON response'));
|
|
expect(response, isNull);
|
|
});
|
|
|
|
testWithoutContext('fails if osascript output returns unexpected json', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: FakeProcessManager.any(),
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('[]');
|
|
|
|
expect(logger.errorText, contains('osascript returned unexpected JSON response'));
|
|
expect(response, isNull);
|
|
});
|
|
|
|
testWithoutContext('fails if osascript output is missing status field', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: FakeProcessManager.any(),
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('{}');
|
|
|
|
expect(logger.errorText, contains('osascript returned unexpected JSON response'));
|
|
expect(response, isNull);
|
|
});
|
|
});
|
|
|
|
group('exit', () {
|
|
const String pathToXcodeApp = '/Applications/Xcode.app';
|
|
|
|
late Directory projectDirectory;
|
|
late Directory xcodeproj;
|
|
late Directory xcworkspace;
|
|
|
|
setUp(() {
|
|
projectDirectory = fileSystem.directory('FlutterApp');
|
|
xcodeproj = projectDirectory.childDirectory('Runner.xcodeproj');
|
|
xcworkspace = projectDirectory.childDirectory('Runner.xcworkspace');
|
|
});
|
|
|
|
testWithoutContext('exits when waiting for debug session to start', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebugProject project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'stop',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
xcodeDebug.startDebugActionProcess = FakeProcess();
|
|
xcodeDebug.currentDebuggingProject = project;
|
|
|
|
expect(xcodeDebug.startDebugActionProcess, isNotNull);
|
|
expect(xcodeDebug.currentDebuggingProject, isNotNull);
|
|
|
|
final bool exitStatus = await xcodeDebug.exit();
|
|
|
|
expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue);
|
|
expect(xcodeDebug.currentDebuggingProject, isNull);
|
|
expect(logger.errorText, isEmpty);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(exitStatus, isTrue);
|
|
});
|
|
|
|
testWithoutContext('exits and deletes temporary directory', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
xcodeproj.createSync(recursive: true);
|
|
xcworkspace.createSync(recursive: true);
|
|
|
|
final XcodeDebugProject project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
isTemporaryProject: true,
|
|
);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'stop',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--close-window',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
xcodeDebug.startDebugActionProcess = FakeProcess();
|
|
xcodeDebug.currentDebuggingProject = project;
|
|
|
|
expect(xcodeDebug.startDebugActionProcess, isNotNull);
|
|
expect(xcodeDebug.currentDebuggingProject, isNotNull);
|
|
expect(projectDirectory.existsSync(), isTrue);
|
|
expect(xcodeproj.existsSync(), isTrue);
|
|
expect(xcworkspace.existsSync(), isTrue);
|
|
|
|
final bool status = await xcodeDebug.exit(skipDelay: true);
|
|
|
|
expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue);
|
|
expect(xcodeDebug.currentDebuggingProject, isNull);
|
|
expect(projectDirectory.existsSync(), isFalse);
|
|
expect(xcodeproj.existsSync(), isFalse);
|
|
expect(xcworkspace.existsSync(), isFalse);
|
|
expect(logger.errorText, isEmpty);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, isTrue);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'prints error message when deleting temporary directory that is nonexistent',
|
|
() async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebugProject project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
isTemporaryProject: true,
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'stop',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--close-window',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
xcodeDebug.startDebugActionProcess = FakeProcess();
|
|
xcodeDebug.currentDebuggingProject = project;
|
|
|
|
expect(xcodeDebug.startDebugActionProcess, isNotNull);
|
|
expect(xcodeDebug.currentDebuggingProject, isNotNull);
|
|
expect(projectDirectory.existsSync(), isFalse);
|
|
expect(xcodeproj.existsSync(), isFalse);
|
|
expect(xcworkspace.existsSync(), isFalse);
|
|
|
|
final bool status = await xcodeDebug.exit(skipDelay: true);
|
|
|
|
expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue);
|
|
expect(xcodeDebug.currentDebuggingProject, isNull);
|
|
expect(projectDirectory.existsSync(), isFalse);
|
|
expect(xcodeproj.existsSync(), isFalse);
|
|
expect(xcworkspace.existsSync(), isFalse);
|
|
expect(logger.errorText, contains('Failed to delete temporary Xcode project'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, isTrue);
|
|
},
|
|
);
|
|
|
|
testWithoutContext('kill Xcode when force exit', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: FakeProcessManager.any(),
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebugProject project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
const FakeCommand(command: <String>['killall', '-9', 'Xcode']),
|
|
]);
|
|
|
|
xcodeDebug.startDebugActionProcess = FakeProcess();
|
|
xcodeDebug.currentDebuggingProject = project;
|
|
|
|
expect(xcodeDebug.startDebugActionProcess, isNotNull);
|
|
expect(xcodeDebug.currentDebuggingProject, isNotNull);
|
|
|
|
final bool exitStatus = await xcodeDebug.exit(force: true);
|
|
|
|
expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue);
|
|
expect(xcodeDebug.currentDebuggingProject, isNull);
|
|
expect(logger.errorText, isEmpty);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(exitStatus, isTrue);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'does not crash when deleting temporary directory that is nonexistent when force exiting',
|
|
() async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: FakeProcessManager.any(),
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
final XcodeDebugProject project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
isTemporaryProject: true,
|
|
);
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: FakeProcessManager.any(),
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
xcodeDebug.startDebugActionProcess = FakeProcess();
|
|
xcodeDebug.currentDebuggingProject = project;
|
|
|
|
expect(xcodeDebug.startDebugActionProcess, isNotNull);
|
|
expect(xcodeDebug.currentDebuggingProject, isNotNull);
|
|
expect(projectDirectory.existsSync(), isFalse);
|
|
expect(xcodeproj.existsSync(), isFalse);
|
|
expect(xcworkspace.existsSync(), isFalse);
|
|
|
|
final bool status = await xcodeDebug.exit(force: true);
|
|
|
|
expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue);
|
|
expect(xcodeDebug.currentDebuggingProject, isNull);
|
|
expect(projectDirectory.existsSync(), isFalse);
|
|
expect(xcodeproj.existsSync(), isFalse);
|
|
expect(xcworkspace.existsSync(), isFalse);
|
|
expect(logger.errorText, isEmpty);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, isTrue);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('stop app', () {
|
|
const String pathToXcodeApp = '/Applications/Xcode.app';
|
|
|
|
late Xcode xcode;
|
|
late Directory xcodeproj;
|
|
late Directory xcworkspace;
|
|
late XcodeDebugProject project;
|
|
|
|
setUp(() {
|
|
xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
xcodeproj = fileSystem.directory('Runner.xcodeproj');
|
|
xcworkspace = fileSystem.directory('Runner.xcworkspace');
|
|
project = XcodeDebugProject(
|
|
scheme: 'Runner',
|
|
xcodeProject: xcodeproj,
|
|
xcodeWorkspace: xcworkspace,
|
|
hostAppProjectName: 'Runner',
|
|
);
|
|
});
|
|
|
|
testWithoutContext('succeeds with all optional flags', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'stop',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--close-window',
|
|
'--prompt-to-save',
|
|
],
|
|
stdout: '''
|
|
{"status":true,"errorMessage":null,"debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final bool status = await xcodeDebug.stopDebuggingApp(
|
|
project: project,
|
|
closeXcode: true,
|
|
promptToSaveOnClose: true,
|
|
);
|
|
|
|
expect(logger.errorText, isEmpty);
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, isTrue);
|
|
});
|
|
|
|
testWithoutContext('fails if osascript output returns false status', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
fakeProcessManager.addCommands(<FakeCommand>[
|
|
FakeCommand(
|
|
command: <String>[
|
|
'xcrun',
|
|
'osascript',
|
|
'-l',
|
|
'JavaScript',
|
|
pathToXcodeAutomationScript,
|
|
'stop',
|
|
'--xcode-path',
|
|
pathToXcodeApp,
|
|
'--project-path',
|
|
project.xcodeProject.path,
|
|
'--workspace-path',
|
|
project.xcodeWorkspace.path,
|
|
'--close-window',
|
|
'--prompt-to-save',
|
|
],
|
|
stdout: '''
|
|
{"status":false,"errorMessage":"Failed to stop app","debugResult":null}
|
|
''',
|
|
),
|
|
]);
|
|
|
|
final bool status = await xcodeDebug.stopDebuggingApp(
|
|
project: project,
|
|
closeXcode: true,
|
|
promptToSaveOnClose: true,
|
|
);
|
|
|
|
expect(logger.errorText, contains('Error stopping app in Xcode'));
|
|
expect(fakeProcessManager, hasNoRemainingExpectations);
|
|
expect(status, isFalse);
|
|
});
|
|
});
|
|
|
|
group('ensureXcodeDebuggerLaunchAction', () {
|
|
late Xcode xcode;
|
|
|
|
setUp(() {
|
|
xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
});
|
|
|
|
testWithoutContext('succeeds', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final File schemeFile = fileSystem.file(
|
|
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
|
);
|
|
schemeFile.createSync(recursive: true);
|
|
schemeFile.writeAsStringSync(validSchemeXml);
|
|
|
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
|
expect(logger.errorText, isEmpty);
|
|
});
|
|
|
|
testWithoutContext('prints error if scheme file not found', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final File schemeFile = fileSystem.file(
|
|
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
|
);
|
|
|
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
|
expect(logger.errorText.contains('Failed to find'), isTrue);
|
|
});
|
|
|
|
testWithoutContext('throws error if launch action is missing debugger info', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final File schemeFile = fileSystem.file(
|
|
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
|
);
|
|
schemeFile.createSync(recursive: true);
|
|
schemeFile.writeAsStringSync(disabledDebugExecutableSchemeXml);
|
|
|
|
expect(
|
|
() => xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile),
|
|
throwsToolExit(message: 'Your Xcode project is not setup to start a debugger.'),
|
|
);
|
|
});
|
|
|
|
testWithoutContext('prints error if unable to find launch action', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final File schemeFile = fileSystem.file(
|
|
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
|
);
|
|
schemeFile.createSync(recursive: true);
|
|
schemeFile.writeAsStringSync('<?xml version="1.0" encoding="UTF-8"?><Scheme></Scheme>');
|
|
|
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
|
expect(logger.errorText.contains('Failed to find LaunchAction for the Scheme'), isTrue);
|
|
});
|
|
|
|
testWithoutContext('prints error if invalid xml', () async {
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: fileSystem,
|
|
);
|
|
|
|
final File schemeFile = fileSystem.file(
|
|
'ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme',
|
|
);
|
|
schemeFile.createSync(recursive: true);
|
|
schemeFile.writeAsStringSync('<?xml version="1.0" encoding="UTF-8"?><Scheme>');
|
|
|
|
xcodeDebug.ensureXcodeDebuggerLaunchAction(schemeFile);
|
|
expect(logger.errorText.contains('Failed to parse'), isTrue);
|
|
});
|
|
});
|
|
});
|
|
|
|
group('Debug project through Xcode with app bundle', () {
|
|
late BufferLogger logger;
|
|
late FakeProcessManager fakeProcessManager;
|
|
late MemoryFileSystem fileSystem;
|
|
|
|
const String flutterRoot = '/path/to/flutter';
|
|
|
|
setUp(() {
|
|
logger = BufferLogger.test();
|
|
fakeProcessManager = FakeProcessManager.empty();
|
|
fileSystem = MemoryFileSystem.test();
|
|
});
|
|
|
|
testUsingContext('creates temporary xcode project', () async {
|
|
final Xcode xcode = setupXcode(
|
|
fakeProcessManager: fakeProcessManager,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
|
|
final XcodeDebug xcodeDebug = XcodeDebug(
|
|
logger: logger,
|
|
processManager: fakeProcessManager,
|
|
xcode: xcode,
|
|
fileSystem: globals.fs,
|
|
);
|
|
|
|
final Directory projectDirectory = globals.fs.systemTempDirectory.createTempSync(
|
|
'flutter_empty_xcode.',
|
|
);
|
|
|
|
try {
|
|
final XcodeDebugProject project = await xcodeDebug.createXcodeProjectWithCustomBundle(
|
|
'/path/to/bundle',
|
|
templateRenderer: globals.templateRenderer,
|
|
projectDestination: projectDirectory,
|
|
);
|
|
|
|
final File schemeFile = projectDirectory
|
|
.childDirectory('Runner.xcodeproj')
|
|
.childDirectory('xcshareddata')
|
|
.childDirectory('xcschemes')
|
|
.childFile('Runner.xcscheme');
|
|
|
|
expect(project.scheme, 'Runner');
|
|
expect(project.xcodeProject.existsSync(), isTrue);
|
|
expect(project.xcodeWorkspace.existsSync(), isTrue);
|
|
expect(project.isTemporaryProject, isTrue);
|
|
expect(projectDirectory.childDirectory('Runner.xcodeproj').existsSync(), isTrue);
|
|
expect(projectDirectory.childDirectory('Runner.xcworkspace').existsSync(), isTrue);
|
|
expect(schemeFile.existsSync(), isTrue);
|
|
expect(schemeFile.readAsStringSync(), contains('FilePath = "/path/to/bundle"'));
|
|
} catch (err) {
|
|
fail(err.toString());
|
|
} finally {
|
|
projectDirectory.deleteSync(recursive: true);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
Xcode setupXcode({
|
|
required FakeProcessManager fakeProcessManager,
|
|
required FileSystem fileSystem,
|
|
required String flutterRoot,
|
|
bool xcodeSelect = true,
|
|
}) {
|
|
fakeProcessManager.addCommand(
|
|
const FakeCommand(
|
|
command: <String>['/usr/bin/xcode-select', '--print-path'],
|
|
stdout: '/Applications/Xcode.app/Contents/Developer',
|
|
),
|
|
);
|
|
|
|
fileSystem
|
|
.file('$flutterRoot/packages/flutter_tools/bin/xcode_debug.js')
|
|
.createSync(recursive: true);
|
|
|
|
final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter.test(
|
|
processManager: FakeProcessManager.any(),
|
|
version: Version(14, 0, 0),
|
|
);
|
|
|
|
return Xcode.test(
|
|
processManager: fakeProcessManager,
|
|
xcodeProjectInterpreter: xcodeProjectInterpreter,
|
|
fileSystem: fileSystem,
|
|
flutterRoot: flutterRoot,
|
|
);
|
|
}
|
|
|
|
class FakeProcess extends Fake implements Process {
|
|
bool killed = false;
|
|
|
|
@override
|
|
bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
|
|
killed = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const String validSchemeXml = '''
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<Scheme
|
|
LastUpgradeVersion = "1510"
|
|
version = "1.3">
|
|
<BuildAction
|
|
parallelizeBuildables = "YES"
|
|
buildImplicitDependencies = "YES">
|
|
</BuildAction>
|
|
<TestAction
|
|
buildConfiguration = "Debug"
|
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
</TestAction>
|
|
<LaunchAction
|
|
buildConfiguration = "Debug"
|
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
launchStyle = "0"
|
|
useCustomWorkingDirectory = "NO"
|
|
ignoresPersistentStateOnLaunch = "NO"
|
|
debugDocumentVersioning = "YES"
|
|
debugServiceExtension = "internal"
|
|
allowLocationSimulation = "YES">
|
|
</LaunchAction>
|
|
<ProfileAction
|
|
buildConfiguration = "Profile"
|
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
savedToolIdentifier = ""
|
|
useCustomWorkingDirectory = "NO"
|
|
debugDocumentVersioning = "YES">
|
|
</ProfileAction>
|
|
<AnalyzeAction
|
|
buildConfiguration = "Debug">
|
|
</AnalyzeAction>
|
|
<ArchiveAction
|
|
buildConfiguration = "Release"
|
|
revealArchiveInOrganizer = "YES">
|
|
</ArchiveAction>
|
|
</Scheme>
|
|
''';
|
|
|
|
const String disabledDebugExecutableSchemeXml = '''
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<Scheme
|
|
LastUpgradeVersion = "1510"
|
|
version = "1.3">
|
|
<BuildAction
|
|
parallelizeBuildables = "YES"
|
|
buildImplicitDependencies = "YES">
|
|
</BuildAction>
|
|
<TestAction
|
|
buildConfiguration = "Debug"
|
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
</TestAction>
|
|
<LaunchAction
|
|
buildConfiguration = "Debug"
|
|
selectedDebuggerIdentifier = ""
|
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
|
launchStyle = "0"
|
|
useCustomWorkingDirectory = "NO"
|
|
ignoresPersistentStateOnLaunch = "NO"
|
|
debugDocumentVersioning = "YES"
|
|
debugServiceExtension = "internal"
|
|
allowLocationSimulation = "YES">
|
|
</LaunchAction>
|
|
<ProfileAction
|
|
buildConfiguration = "Profile"
|
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
savedToolIdentifier = ""
|
|
useCustomWorkingDirectory = "NO"
|
|
debugDocumentVersioning = "YES">
|
|
</ProfileAction>
|
|
<AnalyzeAction
|
|
buildConfiguration = "Debug">
|
|
</AnalyzeAction>
|
|
<ArchiveAction
|
|
buildConfiguration = "Release"
|
|
revealArchiveInOrganizer = "YES">
|
|
</ArchiveAction>
|
|
</Scheme>
|
|
''';
|