Commit f9c2d7d9 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Introduce a quick way to test across platforms (#8262)

parent c233f382
......@@ -54,7 +54,7 @@ class GalleryAppState extends State<GalleryApp> {
bool _showPerformanceOverlay = false;
bool _checkerboardRasterCacheImages = false;
double _timeDilation = 1.0;
TargetPlatform _platform = defaultTargetPlatform;
TargetPlatform _platform;
Timer _timeDilationTimer;
......@@ -94,7 +94,7 @@ class GalleryAppState extends State<GalleryApp> {
} : null,
onPlatformChanged: (TargetPlatform value) {
setState(() {
_platform = value;
_platform = value == defaultTargetPlatform ? null : value;
});
},
timeDilation: _timeDilation,
......@@ -128,7 +128,7 @@ class GalleryAppState extends State<GalleryApp> {
return new MaterialApp(
title: 'Flutter Gallery',
color: Colors.grey[500],
theme: (_useLightTheme ? _kGalleryLightTheme : _kGalleryDarkTheme).copyWith(platform: _platform),
theme: (_useLightTheme ? _kGalleryLightTheme : _kGalleryDarkTheme).copyWith(platform: _platform ?? defaultTargetPlatform),
showPerformanceOverlay: _showPerformanceOverlay,
checkerboardRasterCacheImages: _checkerboardRasterCacheImages,
routes: _kRoutes,
......
......@@ -11,6 +11,7 @@ import 'package:meta/meta.dart';
import 'assertions.dart';
import 'basic_types.dart';
import 'platform.dart';
/// Signature for service extensions.
///
......@@ -115,6 +116,36 @@ abstract class BindingBase {
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 @@ abstract class BindingBase {
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.
......
......@@ -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 @@ import 'print.dart';
/// 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;
});
......
......@@ -28,19 +28,22 @@ enum TargetPlatform {
/// 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 @@ TargetPlatform get defaultTargetPlatform {
}
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;
......@@ -65,8 +65,23 @@ class TestServiceExtensionsBinding extends BindingBase
}
}
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 @@ void main() {
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 @@ void main() {
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;
......
......@@ -111,26 +111,28 @@ abstract class ResidentRunner {
Status status = logger.startProgress('Taking screenshot...');
File outputFile = getUniqueFile(fs.currentDirectory, 'flutter', 'png');
try {
if (supportsServiceProtocol && isRunningDebug) {
if (vmService != null)
await vmService.vm.refreshViews();
try {
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(false);
} catch (error) {
status.stop();
printError(error);
}
}
try {
await device.takeScreenshot(outputFile);
} finally {
if (supportsServiceProtocol && isRunningDebug) {
try {
if (isRunningDebug)
await currentView.uiIsolate.flutterDebugAllowBanner(true);
} catch (error) {
status.stop();
printError(error);
}
}
}
int sizeKB = (await outputFile.length()) ~/ 1024;
status.stop();
printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
......@@ -140,6 +142,18 @@ abstract class ResidentRunner {
}
}
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 @@ abstract class ResidentRunner {
printHelp(details: true);
return true;
} else if (lower == 'w') {
if (!supportsServiceProtocol)
return true;
if (supportsServiceProtocol) {
await _debugDumpApp();
return true;
}
} else if (lower == 't') {
if (!supportsServiceProtocol)
return true;
if (supportsServiceProtocol) {
await _debugDumpRenderTree();
return true;
}
} else if (lower == 'p') {
if (!supportsServiceProtocol)
return true;
if (supportsServiceProtocol && isRunningDebug) {
await _debugToggleDebugPaintSizeEnabled();
return true;
}
} else if (lower == 's') {
if (!supportsServiceProtocol || !device.supportsScreenshot)
return true;
if (device.supportsScreenshot) {
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 @@ abstract class ResidentRunner {
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".');
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".');
......
......@@ -924,6 +924,18 @@ class Isolate extends ServiceObjectOwner {
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> {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment