Introduce a quick way to test across platforms (#8262)
diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 2e070ed..617d707 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart
@@ -11,6 +11,7 @@ import 'assertions.dart'; import 'basic_types.dart'; +import 'platform.dart'; /// Signature for service extensions. /// @@ -115,6 +116,36 @@ name: 'frameworkPresent', callback: () => new Future<Null>.value() ); + assert(() { + registerServiceExtension( + name: 'platformOverride', + callback: (Map<String, String> parameters) async { + if (parameters.containsKey('value')) { + switch (parameters['value']) { + case 'android': + debugDefaultTargetPlatformOverride = TargetPlatform.android; + break; + case 'iOS': + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + break; + case 'fuchsia': + debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; + break; + case 'default': + default: + debugDefaultTargetPlatformOverride = null; + } + await reassembleApplication(); + } + return <String, String>{ + 'value': defaultTargetPlatform + .toString() + .substring('$TargetPlatform.'.length), + }; + } + ); + return true; + }); assert(() { _debugServiceExtensionsRegistered = true; return true; }); } @@ -133,7 +164,6 @@ return new Future<Null>.value(); } - /// Registers a service extension method with the given name (full /// name "ext.flutter.name"), which takes no arguments and returns /// no value.
diff --git a/packages/flutter/lib/src/foundation/debug.dart b/packages/flutter/lib/src/foundation/debug.dart index 8adfc3d..02a8787 100644 --- a/packages/flutter/lib/src/foundation/debug.dart +++ b/packages/flutter/lib/src/foundation/debug.dart
@@ -4,6 +4,7 @@ import 'assertions.dart'; import 'print.dart'; +import 'platform.dart'; /// Returns true if none of the foundation library debug variables have been /// changed. @@ -20,7 +21,8 @@ /// a complete list. bool debugAssertAllFoundationVarsUnset(String reason, { DebugPrintCallback debugPrintOverride: debugPrintThrottled }) { assert(() { - if (debugPrint != debugPrintOverride) + if (debugPrint != debugPrintOverride || + debugDefaultTargetPlatformOverride != null) throw new FlutterError(reason); return true; });
diff --git a/packages/flutter/lib/src/foundation/platform.dart b/packages/flutter/lib/src/foundation/platform.dart index 98224ba..4e86b25 100644 --- a/packages/flutter/lib/src/foundation/platform.dart +++ b/packages/flutter/lib/src/foundation/platform.dart
@@ -28,19 +28,22 @@ /// originally written assuming Android-like behavior, and we added platform /// adaptations for iOS later). Tests can check iOS behavior by using the /// platform override APIs (such as [ThemeData.platform] in the material -/// library). +/// library) or by calling [debugSetDefaultTargetPlatformOverride]. The value +/// can only be explicitly set in debug mode. TargetPlatform get defaultTargetPlatform { TargetPlatform result; if (Platform.isIOS || Platform.isMacOS) { result = TargetPlatform.iOS; } else if (Platform.isAndroid || Platform.isLinux) { result = TargetPlatform.android; - } else if (Platform.operatingSystem == "fuchsia") { + } else if (Platform.operatingSystem == 'fuchsia') { result = TargetPlatform.fuchsia; } assert(() { if (Platform.environment.containsKey('FLUTTER_TEST')) result = TargetPlatform.android; + if (debugDefaultTargetPlatformOverride != null) + result = debugDefaultTargetPlatformOverride; return true; }); if (result == null) { @@ -52,3 +55,12 @@ } return result; } +/// Override the [defaultTargetPlatform]. +/// +/// Setting this to null returns the [defaultTargetPlatform] to its original +/// value (based on the actual current platform). +/// +/// This setter is only available intended for debugging purposes. To change the +/// target platform in release builds, use the platform override APIs (such as +/// [ThemeData.platform] in the material library). +TargetPlatform debugDefaultTargetPlatformOverride;
diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index 3d02b09..9579398 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart
@@ -65,8 +65,23 @@ } } +TestServiceExtensionsBinding binding; + +Future<Map<String, String>> hasReassemble(Future<Map<String, String>> pendingResult) async { + bool completed = false; + pendingResult.whenComplete(() { completed = true; }); + expect(binding.frameScheduled, isFalse); + await binding.flushMicrotasks(); + expect(binding.frameScheduled, isTrue); + expect(completed, isFalse); + binding.doFrame(); + await binding.flushMicrotasks(); + expect(completed, isTrue); + expect(binding.frameScheduled, isFalse); + return pendingResult; +} + void main() { - TestServiceExtensionsBinding binding; List<String> console = <String>[]; test('Service extensions - pretest', () async { @@ -216,6 +231,41 @@ expect(result, <String, String>{}); }); + test('Service extensions - platformOverride', () async { + Map<String, String> result; + + expect(binding.reassembled, 0); + expect(defaultTargetPlatform, TargetPlatform.android); + result = await binding.testExtension('platformOverride', <String, String>{}); + expect(result, <String, String>{'value': 'android'}); + expect(defaultTargetPlatform, TargetPlatform.android); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'iOS'})); + expect(result, <String, String>{'value': 'iOS'}); + expect(binding.reassembled, 1); + expect(defaultTargetPlatform, TargetPlatform.iOS); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'android'})); + expect(result, <String, String>{'value': 'android'}); + expect(binding.reassembled, 2); + expect(defaultTargetPlatform, TargetPlatform.android); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'fuchsia'})); + expect(result, <String, String>{'value': 'fuchsia'}); + expect(binding.reassembled, 3); + expect(defaultTargetPlatform, TargetPlatform.fuchsia); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'default'})); + expect(result, <String, String>{'value': 'android'}); + expect(binding.reassembled, 4); + expect(defaultTargetPlatform, TargetPlatform.android); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'iOS'})); + expect(result, <String, String>{'value': 'iOS'}); + expect(binding.reassembled, 5); + expect(defaultTargetPlatform, TargetPlatform.iOS); + result = await hasReassemble(binding.testExtension('platformOverride', <String, String>{'value': 'bogus'})); + expect(result, <String, String>{'value': 'android'}); + expect(binding.reassembled, 6); + expect(defaultTargetPlatform, TargetPlatform.android); + binding.reassembled = 0; + }); + test('Service extensions - repaintRainbow', () async { Map<String, String> result; Future<Map<String, String>> pendingResult; @@ -329,7 +379,7 @@ test('Service extensions - posttest', () async { // If you add a service extension... TEST IT! :-) // ...then increment this number. - expect(binding.extensions.length, 11); + expect(binding.extensions.length, 12); expect(console, isEmpty); debugPrint = debugPrintThrottled;
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index d207ffd..2450bd5 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -111,24 +111,26 @@ Status status = logger.startProgress('Taking screenshot...'); File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png'); try { - if (vmService != null) - await vmService.vm.refreshViews(); - try { - if (isRunningDebug) + if (supportsServiceProtocol && isRunningDebug) { + if (vmService != null) + await vmService.vm.refreshViews(); + try { await currentView.uiIsolate.flutterDebugAllowBanner(false); - } catch (error) { - status.stop(); - printError(error); + } catch (error) { + status.stop(); + printError(error); + } } try { await device.takeScreenshot(outputFile); } finally { - try { - if (isRunningDebug) + if (supportsServiceProtocol && isRunningDebug) { + try { await currentView.uiIsolate.flutterDebugAllowBanner(true); - } catch (error) { - status.stop(); - printError(error); + } catch (error) { + status.stop(); + printError(error); + } } } int sizeKB = (await outputFile.length()) ~/ 1024; @@ -140,6 +142,18 @@ } } + Future<String> _debugRotatePlatform() async { + if (vmService != null) + await vmService.vm.refreshViews(); + switch (await currentView.uiIsolate.flutterPlatformOverride()) { + case 'iOS': + return await currentView.uiIsolate.flutterPlatformOverride('android'); + case 'android': + default: + return await currentView.uiIsolate.flutterPlatformOverride('iOS'); + } + } + void registerSignalHandlers() { assert(stayResident); ProcessSignal.SIGINT.watch().listen(_cleanUpAndExit); @@ -228,25 +242,31 @@ printHelp(details: true); return true; } else if (lower == 'w') { - if (!supportsServiceProtocol) + if (supportsServiceProtocol) { + await _debugDumpApp(); return true; - await _debugDumpApp(); - return true; + } } else if (lower == 't') { - if (!supportsServiceProtocol) + if (supportsServiceProtocol) { + await _debugDumpRenderTree(); return true; - await _debugDumpRenderTree(); - return true; + } } else if (lower == 'p') { - if (!supportsServiceProtocol) + if (supportsServiceProtocol && isRunningDebug) { + await _debugToggleDebugPaintSizeEnabled(); return true; - await _debugToggleDebugPaintSizeEnabled(); - return true; + } } else if (lower == 's') { - if (!supportsServiceProtocol || !device.supportsScreenshot) + if (device.supportsScreenshot) { + await _screenshot(); return true; - await _screenshot(); - return true; + } + } else if (lower == 'o') { + if (supportsServiceProtocol && isRunningDebug) { + String platform = await _debugRotatePlatform(); + print('Switched operating system to: $platform'); + return true; + } } else if (lower == 'q' || character == AnsiTerminal.KEY_F10) { // F10, exit await stop(); @@ -347,7 +367,10 @@ if (supportsServiceProtocol) { printStatus('To dump the widget hierarchy of the app (debugDumpApp), press "w".'); printStatus('To dump the rendering tree of the app (debugDumpRenderTree), press "t".'); - printStatus('To toggle the display of construction lines (debugPaintSizeEnabled), press "p".'); + if (isRunningDebug) { + printStatus('To toggle the display of construction lines (debugPaintSizeEnabled), press "p".'); + printStatus('To simulate different operating systems, (defaultTargetPlatform), press "o".'); + } } if (device.supportsScreenshot) printStatus('To save a screenshot to flutter.png, press "s".');
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 95a9bb6..b3114d8 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -924,6 +924,18 @@ timeoutFatal: false, ); } + + Future<Null> flutterPlatformOverride([String platform]) async { + Map<String, String> result = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.platformOverride', + params: platform != null ? <String, dynamic>{ 'value': platform } : <String, String>{}, + timeout: const Duration(seconds: 5), + timeoutFatal: false, + ); + if (result != null && result.containsKey('value') && result['value'] is String) + return result['value']; + return 'unknown'; + } } class ServiceMap extends ServiceObject implements Map<String, dynamic> {