blob: bb78ae362b31b2078bb8062ac6dc4b7f52a35ae6 [file] [log] [blame]
// Copyright 2020 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:async';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:platform/platform.dart' as platform;
import 'package:process/process.dart';
import 'utils.dart';
/// Closes system dialogs on iOS, e.g. the one about new system update.
///
/// The dialogs often cause test flakiness and performance regressions.
Future<HealthCheckResult> closeIosDialog({
ProcessManager pm = const LocalProcessManager(),
String? deviceId,
platform.Platform pl = const platform.LocalPlatform(),
String infraDialog = 'infra-dialog',
}) async {
Directory dialogDir = dir(path.dirname(Platform.script.path), 'tool', infraDialog);
if (!await dialogDir.exists()) {
dialogDir = dir(Directory.current.path, 'tool', infraDialog);
if (!await dialogDir.exists()) {
fail('Unable to find infra-dialog at $dialogDir');
}
}
// Runs the single XCUITest in infra-dialog.
await inDirectory(dialogDir, () async {
final List<String> command =
'xcrun xcodebuild -project infra-dialog.xcodeproj -scheme infra-dialog -destination -quiet id=$deviceId test'
.split(' ');
// By default the above command relies on automatic code signing, while on devicelab machines
// it should utilize manual code signing as that is more stable. Below overwrites the code
// signing config if one exists in the environment.
if (pl.environment['FLUTTER_XCODE_CODE_SIGN_STYLE'] != null) {
command.add("CODE_SIGN_STYLE=${pl.environment['FLUTTER_XCODE_CODE_SIGN_STYLE']}");
command.add("DEVELOPMENT_TEAM=${pl.environment['FLUTTER_XCODE_DEVELOPMENT_TEAM']}");
command.add("PROVISIONING_PROFILE_SPECIFIER=${pl.environment['FLUTTER_XCODE_PROVISIONING_PROFILE_SPECIFIER']}");
}
final Process proc = await pm.start(command, workingDirectory: dialogDir.path);
final int exitCode = await proc.exitCode;
if (exitCode != 0) {
fail('Command "$command" failed with exit code $exitCode.');
}
});
return HealthCheckResult.success('close iOS dialog');
}
/// Result of a health check for a specific parameter.
class HealthCheckResult {
HealthCheckResult.success(this.name, [this.details]) : succeeded = true;
HealthCheckResult.failure(this.name, this.details) : succeeded = false;
HealthCheckResult.error(this.name, dynamic error, dynamic stackTrace)
: succeeded = false,
details = 'ERROR: $error\n${stackTrace ?? ''}';
final String name;
final bool succeeded;
final String? details;
@override
String toString() {
final StringBuffer buf = StringBuffer(name);
buf.writeln(succeeded ? 'succeeded' : 'failed');
if (details != null && details!.trim().isNotEmpty) {
buf.writeln();
// Indent details by 4 spaces
for (String line in details!.trim().split('\n')) {
buf.writeln(' $line');
}
}
return '$buf';
}
}
/// Check healthiness for discovered devices.
Future<Map<String, Map<String, dynamic>>> healthcheck(Map<String, List<HealthCheckResult>> deviceChecks) async {
final Map<String, Map<String, dynamic>> healthcheckMap = <String, Map<String, dynamic>>{};
if (deviceChecks.isEmpty) {
healthcheckMap[kAttachedDeviceHealthcheckKey] = <String, dynamic>{
'status': false,
'details': kAttachedDeviceHealthcheckValue,
};
} else {
healthcheckMap[kAttachedDeviceHealthcheckKey] = <String, dynamic>{'status': true, 'details': null};
}
for (String deviceID in deviceChecks.keys) {
final List<HealthCheckResult> checks = deviceChecks[deviceID]!;
for (HealthCheckResult healthCheckResult in checks) {
final Map<String, dynamic> healthCheckResultMap = <String, dynamic>{
'status': healthCheckResult.succeeded,
'details': healthCheckResult.details,
};
healthcheckMap[healthCheckResult.name] = healthCheckResultMap;
}
}
return healthcheckMap;
}