// 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 'package:fake_async/fake_async.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/fake_devices.dart'; void main() { group('DeviceManager', () { testWithoutContext('getDevices', () async { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5'); final List devices = [device1, device2, device3]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); expect(await deviceManager.getDevices(), devices); }); testWithoutContext('getDeviceById exact matcher', () async { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5'); final List devices = [device1, device2, device3]; final BufferLogger logger = BufferLogger.test(); // Include different device discoveries: // 1. One that never completes to prove the first exact match is // returned quickly. // 2. One that throws, to prove matches can return when some succeed // and others fail. // 3. A device discoverer that succeeds. final DeviceManager deviceManager = TestDeviceManager( devices, deviceDiscoveryOverrides: [ ThrowingPollingDeviceDiscovery(), LongPollingDeviceDiscovery(), ], logger: logger, ); Future expectDevice(String id, List expected) async { expect(await deviceManager.getDevicesById(id), expected); } await expectDevice('01abfc49119c410e', [device2]); expect(logger.traceText, contains('Ignored error discovering 01abfc49119c410e')); await expectDevice('Nexus 5X', [device2]); expect(logger.traceText, contains('Ignored error discovering Nexus 5X')); await expectDevice('0553790d0a4e726f', [device1]); expect(logger.traceText, contains('Ignored error discovering 0553790d0a4e726f')); }); testWithoutContext('getDeviceById exact matcher with well known ID', () async { final FakeDevice device1 = FakeDevice('Windows', 'windows'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5'); final List devices = [device1, device2, device3]; final BufferLogger logger = BufferLogger.test(); // Because the well known ID will match, no other device discovery will run. final DeviceManager deviceManager = TestDeviceManager( devices, deviceDiscoveryOverrides: [ ThrowingPollingDeviceDiscovery(), LongPollingDeviceDiscovery(), ], logger: logger, wellKnownId: 'windows', ); Future expectDevice(String id, List expected) async { deviceManager.specifiedDeviceId = id; expect(await deviceManager.getDevicesById(id), expected); } await expectDevice('windows', [device1]); expect(logger.traceText, isEmpty); }); testWithoutContext('getDeviceById prefix matcher', () async { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); final FakeDevice device3 = FakeDevice('iPod touch', '82564b38861a9a5'); final List devices = [device1, device2, device3]; final BufferLogger logger = BufferLogger.test(); // Include different device discoveries: // 1. One that throws, to prove matches can return when some succeed // and others fail. // 2. A device discoverer that succeeds. final DeviceManager deviceManager = TestDeviceManager( devices, deviceDiscoveryOverrides: [ThrowingPollingDeviceDiscovery()], logger: logger, ); Future expectDevice(String id, List expected) async { expect(await deviceManager.getDevicesById(id), expected); } await expectDevice('Nexus 5', [device1]); expect(logger.traceText, contains('Ignored error discovering Nexus 5')); await expectDevice('0553790', [device1]); expect(logger.traceText, contains('Ignored error discovering 0553790')); await expectDevice('Nexus', [device1, device2]); expect(logger.traceText, contains('Ignored error discovering Nexus')); }); testWithoutContext('getDeviceById two exact matches, matches on first', () async { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5', '01abfc49119c410e'); final List devices = [device1, device2]; final BufferLogger logger = BufferLogger.test(); final DeviceManager deviceManager = TestDeviceManager(devices, logger: logger); Future expectDevice(String id, List expected) async { expect(await deviceManager.getDevicesById(id), expected); } await expectDevice('Nexus 5', [device1]); }); testWithoutContext('getAllDevices caches', () async { final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery(); final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery( requiresExtendedWirelessDeviceDiscovery: true, ); final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice wirelessDevice = FakeDevice( 'Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); notSupportedDiscoverer.addDevice(attachedDevice); supportedDiscoverer.addDevice(wirelessDevice); final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), deviceDiscoveryOverrides: [notSupportedDiscoverer, supportedDiscoverer], ); expect(await deviceManager.getAllDevices(), [attachedDevice, wirelessDevice]); final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e'); notSupportedDiscoverer.addDevice(newAttachedDevice); final FakeDevice newWirelessDevice = FakeDevice( 'New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); supportedDiscoverer.addDevice(newWirelessDevice); expect(await deviceManager.getAllDevices(), [attachedDevice, wirelessDevice]); }); testWithoutContext('refreshAllDevices does not cache', () async { final FakePollingDeviceDiscovery notSupportedDiscoverer = FakePollingDeviceDiscovery(); final FakePollingDeviceDiscovery supportedDiscoverer = FakePollingDeviceDiscovery( requiresExtendedWirelessDeviceDiscovery: true, ); final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice wirelessDevice = FakeDevice( 'Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); notSupportedDiscoverer.addDevice(attachedDevice); supportedDiscoverer.addDevice(wirelessDevice); final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), deviceDiscoveryOverrides: [notSupportedDiscoverer, supportedDiscoverer], ); expect(await deviceManager.refreshAllDevices(), [attachedDevice, wirelessDevice]); final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e'); notSupportedDiscoverer.addDevice(newAttachedDevice); final FakeDevice newWirelessDevice = FakeDevice( 'New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); supportedDiscoverer.addDevice(newWirelessDevice); expect(await deviceManager.refreshAllDevices(), [ attachedDevice, newAttachedDevice, wirelessDevice, newWirelessDevice, ]); }); testWithoutContext( 'refreshExtendedWirelessDeviceDiscoverers only refreshes discoverers that require extended time', () async { final FakePollingDeviceDiscovery normalDiscoverer = FakePollingDeviceDiscovery(); final FakePollingDeviceDiscovery extendedDiscoverer = FakePollingDeviceDiscovery( requiresExtendedWirelessDeviceDiscovery: true, ); final FakeDevice attachedDevice = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice wirelessDevice = FakeDevice( 'Wireless device', 'wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); normalDiscoverer.addDevice(attachedDevice); extendedDiscoverer.addDevice(wirelessDevice); final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), deviceDiscoveryOverrides: [normalDiscoverer, extendedDiscoverer], ); await deviceManager.refreshExtendedWirelessDeviceDiscoverers(); expect(await deviceManager.getAllDevices(), [attachedDevice, wirelessDevice]); final FakeDevice newAttachedDevice = FakeDevice('Nexus 5X', '01abfc49119c410e'); normalDiscoverer.addDevice(newAttachedDevice); final FakeDevice newWirelessDevice = FakeDevice( 'New wireless device', 'new-wireless-device', connectionInterface: DeviceConnectionInterface.wireless, ); extendedDiscoverer.addDevice(newWirelessDevice); await deviceManager.refreshExtendedWirelessDeviceDiscoverers(); expect(await deviceManager.getAllDevices(), [ attachedDevice, wirelessDevice, newWirelessDevice, ]); }, ); }); testWithoutContext('PollingDeviceDiscovery startPolling', () { FakeAsync().run((FakeAsync time) { final FakePollingDeviceDiscovery pollingDeviceDiscovery = FakePollingDeviceDiscovery(); pollingDeviceDiscovery.startPolling(); // First check should use the default polling timeout // to quickly populate the list. expect(pollingDeviceDiscovery.lastPollingTimeout, isNull); time.elapse(const Duration(milliseconds: 4001)); // Subsequent polling should be much longer. expect(pollingDeviceDiscovery.lastPollingTimeout, const Duration(seconds: 30)); pollingDeviceDiscovery.stopPolling(); }); }); group('Filter devices', () { final FakeDevice ephemeralOne = FakeDevice('ephemeralOne', 'ephemeralOne'); final FakeDevice ephemeralTwo = FakeDevice('ephemeralTwo', 'ephemeralTwo'); final FakeDevice nonEphemeralOne = FakeDevice( 'nonEphemeralOne', 'nonEphemeralOne', ephemeral: false, ); final FakeDevice nonEphemeralTwo = FakeDevice( 'nonEphemeralTwo', 'nonEphemeralTwo', ephemeral: false, ); final FakeDevice unsupported = FakeDevice('unsupported', 'unsupported', isSupported: false); final FakeDevice unsupportedForProject = FakeDevice( 'unsupportedForProject', 'unsupportedForProject', isSupportedForProject: false, ); final FakeDevice webDevice = FakeDevice('webby', 'webby') ..targetPlatform = Future.value(TargetPlatform.web_javascript); final FakeDevice fuchsiaDevice = FakeDevice('fuchsiay', 'fuchsiay') ..targetPlatform = Future.value(TargetPlatform.fuchsia_x64); final FakeDevice unconnectedDevice = FakeDevice( 'ephemeralTwo', 'ephemeralTwo', isConnected: false, ); final FakeDevice wirelessDevice = FakeDevice( 'ephemeralTwo', 'ephemeralTwo', connectionInterface: DeviceConnectionInterface.wireless, ); testUsingContext('chooses ephemeral device', () async { final List devices = [ephemeralOne, nonEphemeralOne, nonEphemeralTwo]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final Device? ephemeralDevice = deviceManager.getSingleEphemeralDevice(devices); expect(ephemeralDevice, ephemeralOne); }, overrides: {FlutterProject: () => FakeFlutterProject()}); testUsingContext( 'returns null when multiple non ephemeral devices are found', () async { final List devices = [ ephemeralOne, ephemeralTwo, nonEphemeralOne, nonEphemeralTwo, ]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final Device? ephemeralDevice = deviceManager.getSingleEphemeralDevice(devices); expect(ephemeralDevice, isNull); }, overrides: {FlutterProject: () => FakeFlutterProject()}, ); testUsingContext( 'return null when hasSpecifiedDeviceId is true', () async { final List devices = [ephemeralOne, nonEphemeralOne, nonEphemeralTwo]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = 'device'; final Device? ephemeralDevice = deviceManager.getSingleEphemeralDevice(devices); expect(ephemeralDevice, isNull); }, overrides: {FlutterProject: () => FakeFlutterProject()}, ); testUsingContext( 'returns null when no ephemeral devices are found', () async { final List devices = [nonEphemeralOne, nonEphemeralTwo]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final Device? ephemeralDevice = deviceManager.getSingleEphemeralDevice(devices); expect(ephemeralDevice, isNull); }, overrides: {FlutterProject: () => FakeFlutterProject()}, ); testWithoutContext('Unsupported devices listed in all devices', () async { final List devices = [unsupported, unsupportedForProject]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final List filtered = await deviceManager.getDevices(); expect(filtered, [unsupported, unsupportedForProject]); }); testUsingContext('Removes unsupported devices', () async { final List devices = [unsupported, unsupportedForProject]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, []); }); testUsingContext( 'Retains devices unsupported by the project if includeDevicesUnsupportedByProject is true', () async { final List devices = [unsupported, unsupportedForProject]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter( supportFilter: deviceManager.deviceSupportFilter( includeDevicesUnsupportedByProject: true, ), ), ); expect(filtered, [unsupportedForProject]); }, ); testUsingContext('Removes web and fuchsia from --all', () async { final List devices = [webDevice, fuchsiaDevice]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = 'all'; final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, []); }); testUsingContext('Removes devices unsupported by the project from --all', () async { final List devices = [ nonEphemeralOne, nonEphemeralTwo, unsupported, unsupportedForProject, ]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = 'all'; final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, [nonEphemeralOne, nonEphemeralTwo]); }); testUsingContext('Returns device with the specified id', () async { final List devices = [nonEphemeralOne, nonEphemeralTwo]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = nonEphemeralOne.id; final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, [nonEphemeralOne]); }); testUsingContext( 'Returns multiple devices when multiple devices matches the specified id', () async { final List devices = [nonEphemeralOne, nonEphemeralTwo]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = 'nonEphemeral'; // This prefix matches both devices final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, [nonEphemeralOne, nonEphemeralTwo]); }, ); testUsingContext('Returns empty when device of specified id is not found', () async { final List devices = [nonEphemeralOne]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); deviceManager.specifiedDeviceId = nonEphemeralTwo.id; final List filtered = await deviceManager.getDevices( filter: DeviceDiscoveryFilter(supportFilter: deviceManager.deviceSupportFilter()), ); expect(filtered, []); }); testWithoutContext( 'uses DeviceDiscoverySupportFilter.isDeviceSupportedForProject instead of device.isSupportedForProject', () async { final List devices = [unsupported, unsupportedForProject]; final TestDeviceManager deviceManager = TestDeviceManager( devices, logger: BufferLogger.test(), ); final TestDeviceDiscoverySupportFilter supportFilter = TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject( flutterProject: FakeFlutterProject(), ); supportFilter.isAlwaysSupportedForProjectOverride = true; final DeviceDiscoveryFilter filter = DeviceDiscoveryFilter(supportFilter: supportFilter); final List filtered = await deviceManager.getDevices(filter: filter); expect(filtered, [unsupportedForProject]); }, ); testUsingContext('Unconnected devices filtered out by default', () async { final List devices = [unconnectedDevice]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final List filtered = await deviceManager.getDevices(); expect(filtered, []); }); testUsingContext('Return unconnected devices when filter allows', () async { final List devices = [unconnectedDevice]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final DeviceDiscoveryFilter filter = DeviceDiscoveryFilter(excludeDisconnected: false); final List filtered = await deviceManager.getDevices(filter: filter); expect(filtered, [unconnectedDevice]); }); testUsingContext('Filter to only include wireless devices', () async { final List devices = [ephemeralOne, wirelessDevice]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final DeviceDiscoveryFilter filter = DeviceDiscoveryFilter( deviceConnectionInterface: DeviceConnectionInterface.wireless, ); final List filtered = await deviceManager.getDevices(filter: filter); expect(filtered, [wirelessDevice]); }); testUsingContext('Filter to only include attached devices', () async { final List devices = [ephemeralOne, wirelessDevice]; final DeviceManager deviceManager = TestDeviceManager(devices, logger: BufferLogger.test()); final DeviceDiscoveryFilter filter = DeviceDiscoveryFilter( deviceConnectionInterface: DeviceConnectionInterface.attached, ); final List filtered = await deviceManager.getDevices(filter: filter); expect(filtered, [ephemeralOne]); }); }); group('Simultaneous device discovery', () { testWithoutContext( 'Run getAllDevices and refreshAllDevices at same time with refreshAllDevices finishing last', () async { FakeAsync().run((FakeAsync time) { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); const Duration timeToGetInitialDevices = Duration(seconds: 1); const Duration timeToRefreshDevices = Duration(seconds: 5); final List initialDevices = [device2]; final List refreshDevices = [device1]; final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), fakeDiscoverer: FakePollingDeviceDiscoveryWithTimeout(>[ initialDevices, refreshDevices, ], timeout: timeToGetInitialDevices), ); // Expect that the cache is set by getOrSetCache process (1 second timeout) // and then later updated by refreshCache process (5 second timeout). // Ending with devices from the refreshCache process. final Future> refreshCache = deviceManager.refreshAllDevices( timeout: timeToRefreshDevices, ); final Future> getOrSetCache = deviceManager.getAllDevices(); // After 1 second, the getAllDevices should be done time.elapse(const Duration(seconds: 1)); expect(getOrSetCache, completion([device2])); // double check values in cache are as expected Future> getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device2])); // After 5 seconds, getOrSetCache should be done time.elapse(const Duration(seconds: 5)); expect(refreshCache, completion([device1])); // double check values in cache are as expected getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device1])); time.flushMicrotasks(); }); }, ); testWithoutContext( 'Run getAllDevices and refreshAllDevices at same time with refreshAllDevices finishing first', () async { fakeAsync((FakeAsync async) { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); const Duration timeToGetInitialDevices = Duration(seconds: 5); const Duration timeToRefreshDevices = Duration(seconds: 1); final List initialDevices = [device2]; final List refreshDevices = [device1]; final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), fakeDiscoverer: FakePollingDeviceDiscoveryWithTimeout(>[ initialDevices, refreshDevices, ], timeout: timeToGetInitialDevices), ); // Expect that the cache is set by refreshCache process (1 second timeout). // Then later when getOrSetCache finishes (5 second timeout), it does not update the cache. // Ending with devices from the refreshCache process. final Future> refreshCache = deviceManager.refreshAllDevices( timeout: timeToRefreshDevices, ); final Future> getOrSetCache = deviceManager.getAllDevices(); // After 1 second, the refreshCache should be done async.elapse(const Duration(seconds: 1)); expect(refreshCache, completion([device2])); // double check values in cache are as expected Future> getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device2])); // After 5 seconds, getOrSetCache should be done async.elapse(const Duration(seconds: 5)); expect(getOrSetCache, completion([device2])); // double check values in cache are as expected getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device2])); async.flushMicrotasks(); }); }, ); testWithoutContext('refreshAllDevices twice', () async { fakeAsync((FakeAsync async) { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); final FakeDevice device2 = FakeDevice('Nexus 5X', '01abfc49119c410e'); const Duration timeToFirstRefresh = Duration(seconds: 1); const Duration timeToSecondRefresh = Duration(seconds: 5); final List firstRefreshDevices = [device2]; final List secondRefreshDevices = [device1]; final TestDeviceManager deviceManager = TestDeviceManager( [], logger: BufferLogger.test(), fakeDiscoverer: FakePollingDeviceDiscoveryWithTimeout(>[ firstRefreshDevices, secondRefreshDevices, ]), ); // Expect that the cache is updated by each refresh in order of completion. final Future> firstRefresh = deviceManager.refreshAllDevices( timeout: timeToFirstRefresh, ); final Future> secondRefresh = deviceManager.refreshAllDevices( timeout: timeToSecondRefresh, ); // After 1 second, the firstRefresh should be done async.elapse(const Duration(seconds: 1)); expect(firstRefresh, completion([device2])); // double check values in cache are as expected Future> getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device2])); // After 5 seconds, secondRefresh should be done async.elapse(const Duration(seconds: 5)); expect(secondRefresh, completion([device1])); // double check values in cache are as expected getFromCache = deviceManager.getAllDevices(); expect(getFromCache, completion([device1])); async.flushMicrotasks(); }); }); }); group('JSON encode devices', () { testWithoutContext('Consistency of JSON representation', () async { expect( // This tests that fakeDevices is a list of tuples where "second" is the // correct JSON representation of the "first". Actual values are irrelevant await Future.wait(fakeDevices.map((FakeDeviceJsonData d) => d.dev.toJson())), fakeDevices.map((FakeDeviceJsonData d) => d.json), ); }); }); group('JSON encode DebuggingOptions', () { testWithoutContext('can preserve the original options', () { final DebuggingOptions original = DebuggingOptions.enabled( BuildInfo.debug, startPaused: true, disableServiceAuthCodes: true, enableDds: false, dartEntrypointArgs: ['a', 'b'], dartFlags: 'c', deviceVmServicePort: 1234, enableImpeller: ImpellerStatus.enabled, enableDartProfiling: false, enableEmbedderApi: true, ); final String jsonString = json.encode(original.toJson()); final Map decoded = castStringKeyedMap(json.decode(jsonString))!; final DebuggingOptions deserialized = DebuggingOptions.fromJson(decoded, BuildInfo.debug); expect(deserialized.startPaused, original.startPaused); expect(deserialized.disableServiceAuthCodes, original.disableServiceAuthCodes); expect(deserialized.enableDds, original.enableDds); expect(deserialized.dartEntrypointArgs, original.dartEntrypointArgs); expect(deserialized.dartFlags, original.dartFlags); expect(deserialized.deviceVmServicePort, original.deviceVmServicePort); expect(deserialized.enableImpeller, original.enableImpeller); expect(deserialized.enableDartProfiling, original.enableDartProfiling); expect(deserialized.enableEmbedderApi, original.enableEmbedderApi); }); }); group('Get iOS launch arguments from DebuggingOptions', () { testWithoutContext( 'Get launch arguments for physical device with debugging enabled with all launch arguments', () { final DebuggingOptions original = DebuggingOptions.enabled( BuildInfo.debug, startPaused: true, disableServiceAuthCodes: true, disablePortPublication: true, dartFlags: '--foo', useTestFonts: true, enableSoftwareRendering: true, skiaDeterministicRendering: true, traceSkia: true, traceAllowlist: 'foo', traceSkiaAllowlist: 'skia.a,skia.b', traceSystrace: true, traceToFile: 'path/to/trace.binpb', endlessTraceBuffer: true, purgePersistentCache: true, verboseSystemLogs: true, enableImpeller: ImpellerStatus.disabled, deviceVmServicePort: 0, hostVmServicePort: 1, ); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, '/test', {'trace-startup': true}, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--disable-service-auth-codes', '--disable-vm-service-publication', '--start-paused', '--dart-flags="--foo"', '--use-test-fonts', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--trace-systrace', '--trace-to-file="path/to/trace.binpb"', '--skia-deterministic-rendering', '--trace-skia', '--trace-allowlist="foo"', '--trace-skia-allowlist="skia.a,skia.b"', '--endless-trace-buffer', '--verbose-logging', '--purge-persistent-cache', '--route=/test', '--trace-startup', '--enable-impeller=false', '--vm-service-port=0', ].join(' '), ); }, ); testWithoutContext( 'Get launch arguments for physical device with debugging enabled with no launch arguments', () { final DebuggingOptions original = DebuggingOptions.enabled(BuildInfo.debug); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, null, {}, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--enable-checked-mode', '--verify-entry-points', ].join(' '), ); }, ); testWithoutContext( 'Get launch arguments for physical CoreDevice with debugging enabled with no launch arguments', () { final DebuggingOptions original = DebuggingOptions.enabled(BuildInfo.debug); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, null, {}, isCoreDevice: true, ); expect(launchArguments.join(' '), ['--enable-dart-profiling'].join(' ')); }, ); testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () { final DebuggingOptions original = DebuggingOptions.enabled(BuildInfo.debug); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, null, {}, interfaceType: DeviceConnectionInterface.wireless, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--enable-checked-mode', '--verify-entry-points', '--vm-service-host=0.0.0.0', ].join(' '), ); }); testWithoutContext('Get launch arguments for physical device with iPv6 network connection', () { final DebuggingOptions original = DebuggingOptions.enabled(BuildInfo.debug, ipv6: true); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, null, {}, interfaceType: DeviceConnectionInterface.wireless, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--enable-checked-mode', '--verify-entry-points', '--vm-service-host=::0', ].join(' '), ); }); testWithoutContext( 'Get launch arguments for physical device with debugging disabled with available launch arguments', () { final DebuggingOptions original = DebuggingOptions.disabled( BuildInfo.debug, traceAllowlist: 'foo', enableImpeller: ImpellerStatus.disabled, ); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, '/test', {'trace-startup': true}, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--trace-allowlist="foo"', '--route=/test', '--trace-startup', '--enable-impeller=false', ].join(' '), ); }, ); testWithoutContext( 'Get launch arguments for simulator device with debugging enabled with all launch arguments', () { final DebuggingOptions original = DebuggingOptions.enabled( BuildInfo.debug, startPaused: true, disableServiceAuthCodes: true, disablePortPublication: true, dartFlags: '--foo', useTestFonts: true, enableSoftwareRendering: true, skiaDeterministicRendering: true, traceSkia: true, traceAllowlist: 'foo', traceSkiaAllowlist: 'skia.a,skia.b', traceSystrace: true, traceToFile: 'path/to/trace.binpb', endlessTraceBuffer: true, purgePersistentCache: true, verboseSystemLogs: true, enableImpeller: ImpellerStatus.disabled, deviceVmServicePort: 0, hostVmServicePort: 1, ); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.simulator, '/test', {'trace-startup': true}, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--disable-service-auth-codes', '--disable-vm-service-publication', '--start-paused', '--dart-flags=--foo', '--use-test-fonts', '--enable-checked-mode', '--verify-entry-points', '--enable-software-rendering', '--trace-systrace', '--trace-to-file="path/to/trace.binpb"', '--skia-deterministic-rendering', '--trace-skia', '--trace-allowlist="foo"', '--trace-skia-allowlist="skia.a,skia.b"', '--endless-trace-buffer', '--verbose-logging', '--purge-persistent-cache', '--route=/test', '--trace-startup', '--enable-impeller=false', '--vm-service-port=1', ].join(' '), ); }, ); testWithoutContext( 'Get launch arguments for simulator device with debugging enabled with no launch arguments', () { final DebuggingOptions original = DebuggingOptions.enabled(BuildInfo.debug); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.simulator, null, {}, ); expect( launchArguments.join(' '), [ '--enable-dart-profiling', '--enable-checked-mode', '--verify-entry-points', ].join(' '), ); }, ); testWithoutContext('No --enable-dart-profiling flag when option is false', () { final DebuggingOptions original = DebuggingOptions.enabled( BuildInfo.debug, enableDartProfiling: false, ); final List launchArguments = original.getIOSLaunchArguments( EnvironmentType.physical, null, {}, ); expect( launchArguments.join(' '), ['--enable-checked-mode', '--verify-entry-points'].join(' '), ); }); }); group('PollingDeviceDiscovery', () { final FakeDevice device1 = FakeDevice('Nexus 5', '0553790d0a4e726f'); testWithoutContext('initial call to devices returns the correct list', () async { final List deviceList = [device1]; final TestPollingDeviceDiscovery testDeviceDiscovery = TestPollingDeviceDiscovery(deviceList); // Call `onAdded` to make sure that calling `onAdded` does not affect the // result of `devices()`. final List addedDevice = []; final List removedDevice = []; testDeviceDiscovery.onAdded.listen(addedDevice.add); testDeviceDiscovery.onRemoved.listen(removedDevice.add); final List devices = await testDeviceDiscovery.devices(); expect(devices.length, 1); expect(devices.first.id, device1.id); }); testWithoutContext('call to devices triggers onAdded', () async { final List deviceList = [device1]; final TestPollingDeviceDiscovery testDeviceDiscovery = TestPollingDeviceDiscovery(deviceList); // Call `onAdded` to make sure that calling `onAdded` does not affect the // result of `devices()`. final List addedDevice = []; final List removedDevice = []; testDeviceDiscovery.onAdded.listen(addedDevice.add); testDeviceDiscovery.onRemoved.listen(removedDevice.add); final List devices = await testDeviceDiscovery.devices(); expect(devices.length, 1); expect(devices.first.id, device1.id); await pumpEventQueue(); expect(addedDevice.length, 1); expect(addedDevice.first.id, device1.id); }); }); } class TestDeviceManager extends DeviceManager { TestDeviceManager( List allDevices, { List? deviceDiscoveryOverrides, required super.logger, String? wellKnownId, FakePollingDeviceDiscovery? fakeDiscoverer, }) : _fakeDeviceDiscoverer = fakeDiscoverer ?? FakePollingDeviceDiscovery(), _deviceDiscoverers = [], super() { if (wellKnownId != null) { _fakeDeviceDiscoverer.wellKnownIds.add(wellKnownId); } _deviceDiscoverers.add(_fakeDeviceDiscoverer); if (deviceDiscoveryOverrides != null) { _deviceDiscoverers.addAll(deviceDiscoveryOverrides); } resetDevices(allDevices); } @override List get deviceDiscoverers => _deviceDiscoverers; final List _deviceDiscoverers; final FakePollingDeviceDiscovery _fakeDeviceDiscoverer; void resetDevices(List allDevices) { _fakeDeviceDiscoverer.setDevices(allDevices); } } class TestDeviceDiscoverySupportFilter extends DeviceDiscoverySupportFilter { TestDeviceDiscoverySupportFilter.excludeDevicesUnsupportedByFlutterOrProject({ required super.flutterProject, }) : super.excludeDevicesUnsupportedByFlutterOrProject(); bool? isAlwaysSupportedForProjectOverride; @override bool isDeviceSupportedForProject(Device device) { return isAlwaysSupportedForProjectOverride ?? super.isDeviceSupportedForProject(device); } } class FakePollingDeviceDiscoveryWithTimeout extends FakePollingDeviceDiscovery { FakePollingDeviceDiscoveryWithTimeout(this._devices, {Duration? timeout}) : defaultTimeout = timeout ?? const Duration(seconds: 2); final List> _devices; int index = 0; Duration defaultTimeout; @override Future> pollingGetDevices({Duration? timeout}) async { timeout ??= defaultTimeout; await Future.delayed(timeout); final List results = _devices[index]; index += 1; return results; } } class FakeFlutterProject extends Fake implements FlutterProject {} class LongPollingDeviceDiscovery extends PollingDeviceDiscovery { LongPollingDeviceDiscovery() : super('forever'); final Completer> _completer = Completer>(); @override Future> pollingGetDevices({Duration? timeout}) async { return _completer.future; } @override Future stopPolling() async { _completer.complete([]); } @override Future dispose() async { _completer.complete([]); } @override bool get supportsPlatform => true; @override bool get canListAnything => true; @override final List wellKnownIds = []; } class ThrowingPollingDeviceDiscovery extends PollingDeviceDiscovery { ThrowingPollingDeviceDiscovery() : super('throw'); @override Future> pollingGetDevices({Duration? timeout}) async { throw const ProcessException('fake-discovery', []); } @override bool get supportsPlatform => true; @override bool get canListAnything => true; @override List get wellKnownIds => []; } class TestPollingDeviceDiscovery extends PollingDeviceDiscovery { TestPollingDeviceDiscovery(this._devices) : super('test'); final List _devices; @override Future> pollingGetDevices({Duration? timeout}) async { return _devices; } @override bool get supportsPlatform => true; @override bool get canListAnything => true; @override List get wellKnownIds => []; }