// 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:path/path.dart' as path; import '../framework/framework.dart'; import '../framework/task_result.dart'; import '../framework/utils.dart'; TaskFunction dartPluginRegistryTest({ String? deviceIdOverride, Map<String, String>? environment, }) { final Directory tempDir = Directory.systemTemp .createTempSync('flutter_devicelab_dart_plugin_test.'); return () async { try { section('Create implementation plugin'); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--template=plugin', '--org', 'io.flutter.devicelab', '--platforms', 'macos', 'aplugin_platform_implementation', ], environment: environment, ); }); final File pluginMain = File(path.join( tempDir.absolute.path, 'aplugin_platform_implementation', 'lib', 'aplugin_platform_implementation.dart', )); if (!pluginMain.existsSync()) { return TaskResult.failure('${pluginMain.path} does not exist'); } // Patch plugin main dart file. await pluginMain.writeAsString(''' class ApluginPlatformInterfaceMacOS { static void registerWith() { print('ApluginPlatformInterfaceMacOS.registerWith() was called'); } } ''', flush: true); // Patch plugin main pubspec file. final File pluginImplPubspec = File(path.join( tempDir.absolute.path, 'aplugin_platform_implementation', 'pubspec.yaml', )); String pluginImplPubspecContent = await pluginImplPubspec.readAsString(); pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst( ' pluginClass: ApluginPlatformImplementationPlugin', ' pluginClass: ApluginPlatformImplementationPlugin\n' ' dartPluginClass: ApluginPlatformInterfaceMacOS\n', ); pluginImplPubspecContent = pluginImplPubspecContent.replaceFirst( ' platforms:\n', ' implements: aplugin_platform_interface\n' ' platforms:\n'); await pluginImplPubspec.writeAsString(pluginImplPubspecContent, flush: true); section('Create interface plugin'); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--template=plugin', '--org', 'io.flutter.devicelab', '--platforms', 'macos', 'aplugin_platform_interface', ], environment: environment, ); }); final File pluginInterfacePubspec = File(path.join( tempDir.absolute.path, 'aplugin_platform_interface', 'pubspec.yaml', )); String pluginInterfacePubspecContent = await pluginInterfacePubspec.readAsString(); pluginInterfacePubspecContent = pluginInterfacePubspecContent.replaceFirst( ' pluginClass: ApluginPlatformInterfacePlugin', ' default_package: aplugin_platform_implementation\n'); pluginInterfacePubspecContent = pluginInterfacePubspecContent.replaceFirst( 'dependencies:', 'dependencies:\n' ' aplugin_platform_implementation:\n' ' path: ../aplugin_platform_implementation\n'); await pluginInterfacePubspec.writeAsString(pluginInterfacePubspecContent, flush: true); section('Create app'); await inDirectory(tempDir, () async { await flutter( 'create', options: <String>[ '--template=app', '--org', 'io.flutter.devicelab', '--platforms', 'macos', 'app', ], environment: environment, ); }); final File appPubspec = File(path.join( tempDir.absolute.path, 'app', 'pubspec.yaml', )); String appPubspecContent = await appPubspec.readAsString(); appPubspecContent = appPubspecContent.replaceFirst( 'dependencies:', 'dependencies:\n' ' aplugin_platform_interface:\n' ' path: ../aplugin_platform_interface\n'); await appPubspec.writeAsString(appPubspecContent, flush: true); section('Flutter run for macos'); late Process run; await inDirectory(path.join(tempDir.path, 'app'), () async { run = await startProcess( path.join(flutterDirectory.path, 'bin', 'flutter'), flutterCommandArgs('run', <String>['-d', 'macos', '-v']), ); }); Completer<void> registryExecutedCompleter = Completer<void>(); final StreamSubscription<void> stdoutSub = run.stdout .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { if (line.contains('ApluginPlatformInterfaceMacOS.registerWith() was called')) { registryExecutedCompleter.complete(); } print('stdout: $line'); }); final StreamSubscription<void> stderrSub = run.stderr .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { print('stderr: $line'); }); final Future<void> stdoutDone = stdoutSub.asFuture<void>(); final Future<void> stderrDone = stderrSub.asFuture<void>(); Future<void> waitForStreams() { return Future.wait<void>(<Future<void>>[stdoutDone, stderrDone]); } Future<void> waitOrExit(Future<void> future) async { final dynamic result = await Future.any<dynamic>( <Future<dynamic>>[ future, run.exitCode, ], ); if (result is int) { await waitForStreams(); throw 'process exited with code $result'; } } section('Wait for registry execution'); await waitOrExit(registryExecutedCompleter.future); // Hot restart. run.stdin.write('R'); await run.stdin.flush(); await run.stdin.close(); registryExecutedCompleter = Completer<void>(); section('Wait for registry execution after hot restart'); await waitOrExit(registryExecutedCompleter.future); run.kill(); section('Wait for stdout/stderr streams'); await waitForStreams(); unawaited(stdoutSub.cancel()); unawaited(stderrSub.cancel()); return TaskResult.success(null); } finally { rmTree(tempDir); } }; }