// 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:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter_devicelab/framework/devices.dart'; import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; /// This is a test to validate that Xcode debugging still works now that LLDB is the default. Future main() async { await task(() async { deviceOperatingSystem = DeviceOperatingSystem.ios; return createIosWorkflowTest()(); }); } Future enableLLDBDebugging() async { final int configResult = await exec(path.join(flutterDirectory.path, 'bin', 'flutter'), [ 'config', '--enable-lldb-debugging', ], canFail: true); if (configResult != 0) { print('Failed to enable configuration.'); } } TaskFunction createIosWorkflowTest({String? deviceIdOverride}) { return () async { // Create project const appName = 'ios_workflow_test'; final Directory tempDirectory = dir(Directory.systemTemp.createTempSync().path); await exec(_flutterBin, [ 'create', '--no-pub', appName, ], workingDirectory: tempDirectory.path); final Directory appDirectory = dir(path.join(tempDirectory.path, appName)); // Select device if (deviceIdOverride == null) { final Device device = await devices.workingDevice; await device.unlock(); deviceIdOverride = device.deviceId; } // Test LLDB workflow await enableLLDBDebugging(); final TaskResult lldbResult = await _validateWorkflow( workflow: IosDebugWorkflow.lldb, deviceId: deviceIdOverride!, appDirectoryPath: appDirectory.path, ); if (lldbResult.failed) { return lldbResult; } // TODO(vashworth): Also test Xcode workflow once // https://github.com/flutter/flutter/issues/173573 is fixed. return TaskResult.success(null); }; } Future _validateWorkflow({ required IosDebugWorkflow workflow, required String deviceId, required String appDirectoryPath, }) async { final options = [ '--no-android-gradle-daemon', '--verbose', '--debug', '--no-publish-port', '-d', deviceId, ]; final Process process = await startFlutter( 'run', options: options, workingDirectory: appDirectoryPath, ); Pattern expectedLog; Pattern unexpectedLog; const Pattern xcodeExpectedLog = 'Action result status: not yet started'; final Pattern lldbExpectedLog = RegExp(r'Process .* resuming'); switch (workflow) { case IosDebugWorkflow.xcode: expectedLog = xcodeExpectedLog; unexpectedLog = lldbExpectedLog; case IosDebugWorkflow.lldb: expectedLog = lldbExpectedLog; unexpectedLog = xcodeExpectedLog; } // TODO(vashworth): Update to verify app launched all the way once // https://github.com/flutter/flutter/issues/173365 is fixed. final Pattern finishPattern = RegExp( 'Application launched on the device. Waiting for Dart VM Service url.', ); String? foundUnexpectedLog; String? foundExpectedLog; final StreamSubscription stdoutSubscription = process.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String line) { print('stdout: $line'); if (line.contains(finishPattern)) { process.kill(); } if (line.contains(expectedLog)) { foundExpectedLog = line; } if (line.contains(unexpectedLog)) { foundUnexpectedLog = line; } }); final StreamSubscription stderrSubscription = process.stderr .transform(utf8.decoder) .transform(const LineSplitter()) .listen((String line) => print('stderr: $line')); final int runFlutterResult = await process.exitCode.whenComplete(() { stdoutSubscription.cancel(); stderrSubscription.cancel(); }); if (runFlutterResult != 0) { print('Flutter run returned non-zero exit code: $runFlutterResult.'); return TaskResult.failure('failed'); } if (foundUnexpectedLog != null) { return TaskResult.failure('Unexpected logs found: $foundUnexpectedLog'); } if (foundExpectedLog == null) { return TaskResult.failure('Expected logs not found.'); } return TaskResult.success(null); } enum IosDebugWorkflow { xcode, lldb } final String _flutterBin = path.join(flutterDirectory.path, 'bin', 'flutter');