mirror of
https://github.com/flutter/packages.git
synced 2025-06-04 10:18:12 +08:00

The mock process runner used in most of the tests had poor handling of stdout/stderr: - By default it would return the `List<int>` output from the mock process, which was never correct since the parent process runner interface always sets encodings, thus the `dynamic` should always be of type `String` - The process for setting output on a `MockProcess` was awkward, since it was based on a `Stream<Lint<int>>`, even though in every case what we actually want to do is just set the full output to a string. - A hack was at some point added (presumably due to the above issues) to bypass that flow at the process runner level, and instead return a `String` set there. That meant there were two ways of setting output (one of which that only worked for one of the ways of running processes) - That hack wasn't updated when the ability to return multiple mock processes instead of a single global mock process was added, so the API was even more confusing, and there was no way to set different output for different processes. This changes the test APIs so that: - `MockProcess` takes stdout and stderr as strings, and internally manages converting them to a `Stream<List<int>>`. - `RecordingProcessRunner` correctly decodes and returns the output streams when constructing a process result. It also removes the resultStdout and resultStderr hacks, as well as the legacy `processToReturn` API, and converts all uses to the new structure, which is both simpler to use, and clearly associates output with specific processes.
407 lines
13 KiB
Dart
407 lines
13 KiB
Dart
// Copyright 2013 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:convert';
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:file/local.dart';
|
|
import 'package:flutter_plugin_tools/src/common/xcode.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import '../mocks.dart';
|
|
import '../util.dart';
|
|
|
|
void main() {
|
|
late RecordingProcessRunner processRunner;
|
|
late Xcode xcode;
|
|
|
|
setUp(() {
|
|
processRunner = RecordingProcessRunner();
|
|
xcode = Xcode(processRunner: processRunner);
|
|
});
|
|
|
|
group('findBestAvailableIphoneSimulator', () {
|
|
test('finds the newest device', () async {
|
|
const String expectedDeviceId = '1E76A0FD-38AC-4537-A989-EA639D7D012A';
|
|
// Note: This uses `dynamic` deliberately, and should not be updated to
|
|
// Object, in order to ensure that the code correctly handles this return
|
|
// type from JSON decoding.
|
|
final Map<String, dynamic> devices = <String, dynamic>{
|
|
'runtimes': <Map<String, dynamic>>[
|
|
<String, dynamic>{
|
|
'bundlePath':
|
|
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime',
|
|
'buildversion': '17A577',
|
|
'runtimeRoot':
|
|
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime/Contents/Resources/RuntimeRoot',
|
|
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-0',
|
|
'version': '13.0',
|
|
'isAvailable': true,
|
|
'name': 'iOS 13.0'
|
|
},
|
|
<String, dynamic>{
|
|
'bundlePath':
|
|
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime',
|
|
'buildversion': '17L255',
|
|
'runtimeRoot':
|
|
'/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.4.simruntime/Contents/Resources/RuntimeRoot',
|
|
'identifier': 'com.apple.CoreSimulator.SimRuntime.iOS-13-4',
|
|
'version': '13.4',
|
|
'isAvailable': true,
|
|
'name': 'iOS 13.4'
|
|
},
|
|
<String, dynamic>{
|
|
'bundlePath':
|
|
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
|
|
'buildversion': '17T531',
|
|
'runtimeRoot':
|
|
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
|
|
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
|
|
'version': '6.2.1',
|
|
'isAvailable': true,
|
|
'name': 'watchOS 6.2'
|
|
}
|
|
],
|
|
'devices': <String, dynamic>{
|
|
'com.apple.CoreSimulator.SimRuntime.iOS-13-4': <Map<String, dynamic>>[
|
|
<String, dynamic>{
|
|
'dataPath':
|
|
'/Users/xxx/Library/Developer/CoreSimulator/Devices/2706BBEB-1E01-403E-A8E9-70E8E5A24774/data',
|
|
'logPath':
|
|
'/Users/xxx/Library/Logs/CoreSimulator/2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
|
'udid': '2706BBEB-1E01-403E-A8E9-70E8E5A24774',
|
|
'isAvailable': true,
|
|
'deviceTypeIdentifier':
|
|
'com.apple.CoreSimulator.SimDeviceType.iPhone-8',
|
|
'state': 'Shutdown',
|
|
'name': 'iPhone 8'
|
|
},
|
|
<String, dynamic>{
|
|
'dataPath':
|
|
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
|
|
'logPath':
|
|
'/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A',
|
|
'udid': expectedDeviceId,
|
|
'isAvailable': true,
|
|
'deviceTypeIdentifier':
|
|
'com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus',
|
|
'state': 'Shutdown',
|
|
'name': 'iPhone 8 Plus'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: jsonEncode(devices)),
|
|
];
|
|
|
|
expect(await xcode.findBestAvailableIphoneSimulator(), expectedDeviceId);
|
|
});
|
|
|
|
test('ignores non-iOS runtimes', () async {
|
|
// Note: This uses `dynamic` deliberately, and should not be updated to
|
|
// Object, in order to ensure that the code correctly handles this return
|
|
// type from JSON decoding.
|
|
final Map<String, dynamic> devices = <String, dynamic>{
|
|
'runtimes': <Map<String, dynamic>>[
|
|
<String, dynamic>{
|
|
'bundlePath':
|
|
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime',
|
|
'buildversion': '17T531',
|
|
'runtimeRoot':
|
|
'/Applications/Xcode_11_7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime/Contents/Resources/RuntimeRoot',
|
|
'identifier': 'com.apple.CoreSimulator.SimRuntime.watchOS-6-2',
|
|
'version': '6.2.1',
|
|
'isAvailable': true,
|
|
'name': 'watchOS 6.2'
|
|
}
|
|
],
|
|
'devices': <String, dynamic>{
|
|
'com.apple.CoreSimulator.SimRuntime.watchOS-6-2':
|
|
<Map<String, dynamic>>[
|
|
<String, dynamic>{
|
|
'dataPath':
|
|
'/Users/xxx/Library/Developer/CoreSimulator/Devices/1E76A0FD-38AC-4537-A989-EA639D7D012A/data',
|
|
'logPath':
|
|
'/Users/xxx/Library/Logs/CoreSimulator/1E76A0FD-38AC-4537-A989-EA639D7D012A',
|
|
'udid': '1E76A0FD-38AC-4537-A989-EA639D7D012A',
|
|
'isAvailable': true,
|
|
'deviceTypeIdentifier':
|
|
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm',
|
|
'state': 'Shutdown',
|
|
'name': 'Apple Watch'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: jsonEncode(devices)),
|
|
];
|
|
|
|
expect(await xcode.findBestAvailableIphoneSimulator(), null);
|
|
});
|
|
|
|
test('returns null if simctl fails', () async {
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(exitCode: 1),
|
|
];
|
|
|
|
expect(await xcode.findBestAvailableIphoneSimulator(), null);
|
|
});
|
|
});
|
|
|
|
group('runXcodeBuild', () {
|
|
test('handles minimal arguments', () async {
|
|
final Directory directory = const LocalFileSystem().currentDirectory;
|
|
|
|
final int exitCode = await xcode.runXcodeBuild(
|
|
directory,
|
|
workspace: 'A.xcworkspace',
|
|
scheme: 'AScheme',
|
|
);
|
|
|
|
expect(exitCode, 0);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
const <String>[
|
|
'xcodebuild',
|
|
'build',
|
|
'-workspace',
|
|
'A.xcworkspace',
|
|
'-scheme',
|
|
'AScheme',
|
|
],
|
|
directory.path),
|
|
]));
|
|
});
|
|
|
|
test('handles all arguments', () async {
|
|
final Directory directory = const LocalFileSystem().currentDirectory;
|
|
|
|
final int exitCode = await xcode.runXcodeBuild(directory,
|
|
actions: <String>['action1', 'action2'],
|
|
workspace: 'A.xcworkspace',
|
|
scheme: 'AScheme',
|
|
configuration: 'Debug',
|
|
extraFlags: <String>['-a', '-b', 'c=d']);
|
|
|
|
expect(exitCode, 0);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
const <String>[
|
|
'xcodebuild',
|
|
'action1',
|
|
'action2',
|
|
'-workspace',
|
|
'A.xcworkspace',
|
|
'-scheme',
|
|
'AScheme',
|
|
'-configuration',
|
|
'Debug',
|
|
'-a',
|
|
'-b',
|
|
'c=d',
|
|
],
|
|
directory.path),
|
|
]));
|
|
});
|
|
|
|
test('returns error codes', () async {
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(exitCode: 1),
|
|
];
|
|
final Directory directory = const LocalFileSystem().currentDirectory;
|
|
|
|
final int exitCode = await xcode.runXcodeBuild(
|
|
directory,
|
|
workspace: 'A.xcworkspace',
|
|
scheme: 'AScheme',
|
|
);
|
|
|
|
expect(exitCode, 1);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
const <String>[
|
|
'xcodebuild',
|
|
'build',
|
|
'-workspace',
|
|
'A.xcworkspace',
|
|
'-scheme',
|
|
'AScheme',
|
|
],
|
|
directory.path),
|
|
]));
|
|
});
|
|
});
|
|
|
|
group('projectHasTarget', () {
|
|
test('returns true when present', () async {
|
|
const String stdout = '''
|
|
{
|
|
"project" : {
|
|
"configurations" : [
|
|
"Debug",
|
|
"Release"
|
|
],
|
|
"name" : "Runner",
|
|
"schemes" : [
|
|
"Runner"
|
|
],
|
|
"targets" : [
|
|
"Runner",
|
|
"RunnerTests",
|
|
"RunnerUITests"
|
|
]
|
|
}
|
|
}''';
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: stdout),
|
|
];
|
|
|
|
final Directory project =
|
|
const LocalFileSystem().directory('/foo.xcodeproj');
|
|
expect(await xcode.projectHasTarget(project, 'RunnerTests'), true);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
<String>[
|
|
'xcodebuild',
|
|
'-list',
|
|
'-json',
|
|
'-project',
|
|
project.path,
|
|
],
|
|
null),
|
|
]));
|
|
});
|
|
|
|
test('returns false when not present', () async {
|
|
const String stdout = '''
|
|
{
|
|
"project" : {
|
|
"configurations" : [
|
|
"Debug",
|
|
"Release"
|
|
],
|
|
"name" : "Runner",
|
|
"schemes" : [
|
|
"Runner"
|
|
],
|
|
"targets" : [
|
|
"Runner",
|
|
"RunnerUITests"
|
|
]
|
|
}
|
|
}''';
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: stdout),
|
|
];
|
|
|
|
final Directory project =
|
|
const LocalFileSystem().directory('/foo.xcodeproj');
|
|
expect(await xcode.projectHasTarget(project, 'RunnerTests'), false);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
<String>[
|
|
'xcodebuild',
|
|
'-list',
|
|
'-json',
|
|
'-project',
|
|
project.path,
|
|
],
|
|
null),
|
|
]));
|
|
});
|
|
|
|
test('returns null for unexpected output', () async {
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: '{}'),
|
|
];
|
|
|
|
final Directory project =
|
|
const LocalFileSystem().directory('/foo.xcodeproj');
|
|
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
<String>[
|
|
'xcodebuild',
|
|
'-list',
|
|
'-json',
|
|
'-project',
|
|
project.path,
|
|
],
|
|
null),
|
|
]));
|
|
});
|
|
|
|
test('returns null for invalid output', () async {
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(stdout: ':)'),
|
|
];
|
|
|
|
final Directory project =
|
|
const LocalFileSystem().directory('/foo.xcodeproj');
|
|
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
<String>[
|
|
'xcodebuild',
|
|
'-list',
|
|
'-json',
|
|
'-project',
|
|
project.path,
|
|
],
|
|
null),
|
|
]));
|
|
});
|
|
|
|
test('returns null for failure', () async {
|
|
processRunner.mockProcessesForExecutable['xcrun'] = <io.Process>[
|
|
MockProcess(exitCode: 1), // xcodebuild -list
|
|
];
|
|
|
|
final Directory project =
|
|
const LocalFileSystem().directory('/foo.xcodeproj');
|
|
expect(await xcode.projectHasTarget(project, 'RunnerTests'), null);
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'xcrun',
|
|
<String>[
|
|
'xcodebuild',
|
|
'-list',
|
|
'-json',
|
|
'-project',
|
|
project.path,
|
|
],
|
|
null),
|
|
]));
|
|
});
|
|
});
|
|
}
|