diff --git a/dev/devicelab/bin/tasks/android_enable_twc.dart b/dev/devicelab/bin/tasks/android_enable_twc.dart new file mode 100644 index 0000000000..95363a7333 --- /dev/null +++ b/dev/devicelab/bin/tasks/android_enable_twc.dart @@ -0,0 +1,15 @@ +// 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:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/track_widget_creation_enabled_task.dart'; + +/// Verify that twc can be enabled/disabled on Android +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(TrackWidgetCreationEnabledTask().task); +} diff --git a/dev/devicelab/bin/tasks/ios_enable_twc.dart b/dev/devicelab/bin/tasks/ios_enable_twc.dart new file mode 100644 index 0000000000..c0380af36c --- /dev/null +++ b/dev/devicelab/bin/tasks/ios_enable_twc.dart @@ -0,0 +1,15 @@ +// 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:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/track_widget_creation_enabled_task.dart'; + +/// Verify that twc can be enabled/disabled on iOS +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(TrackWidgetCreationEnabledTask().task); +} diff --git a/dev/devicelab/bin/tasks/mac_enable_twc.dart b/dev/devicelab/bin/tasks/mac_enable_twc.dart new file mode 100644 index 0000000000..eaf8a52587 --- /dev/null +++ b/dev/devicelab/bin/tasks/mac_enable_twc.dart @@ -0,0 +1,13 @@ +// 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:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/track_widget_creation_enabled_task.dart'; + +/// Verify that twc can be enabled/disabled on macOS +Future main() async { + await task(TrackWidgetCreationEnabledTask('macos').task); +} diff --git a/dev/devicelab/bin/tasks/web_enable_twc.dart b/dev/devicelab/bin/tasks/web_enable_twc.dart new file mode 100644 index 0000000000..7b5862182e --- /dev/null +++ b/dev/devicelab/bin/tasks/web_enable_twc.dart @@ -0,0 +1,13 @@ +// 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:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/track_widget_creation_enabled_task.dart'; + +/// Verify that twc can be enabled/disabled on the web. +Future main() async { + await task(TrackWidgetCreationEnabledTask('chrome').task); +} diff --git a/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart new file mode 100644 index 0000000000..34df13646c --- /dev/null +++ b/dev/devicelab/lib/tasks/track_widget_creation_enabled_task.dart @@ -0,0 +1,130 @@ +// 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:convert'; +import 'dart:io'; + +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service/vm_service_io.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:path/path.dart' as path; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; + +final Directory integrationTestDir = Directory( + path.join(flutterDirectory.path, 'dev/integration_tests/ui'), +); + +/// Verifies that track-widget-creation can be enabled and disabled. +class TrackWidgetCreationEnabledTask { + TrackWidgetCreationEnabledTask([this.deviceIdOverride]); + + String deviceIdOverride; + + Future task() async { + final File file = File(path.join(integrationTestDir.path, 'info')); + if (file.existsSync()) { + file.deleteSync(); + } + bool failed = false; + String message = ''; + if (deviceIdOverride == null) { + final Device device = await devices.workingDevice; + await device.unlock(); + deviceIdOverride = device.deviceId; + } + await inDirectory(integrationTestDir, () async { + section('Running with track-widget-creation enabled'); + final Process runProcess = await startProcess( + path.join(flutterDirectory.path, 'bin', 'flutter'), + flutterCommandArgs('run', [ + '--vmservice-out-file=info', + '--track-widget-creation', + '-v', + '-d', + deviceIdOverride, + path.join('lib/track_widget_creation.dart'), + ]), + environment: { + 'FLUTTER_WEB': 'true', + 'FLUTTER_MACOS': 'true' + } + ); + runProcess.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(print); + final File file = await waitForFile(path.join(integrationTestDir.path, 'info')); + final VmService vmService = await vmServiceConnectUri(file.readAsStringSync()); + final VM vm = await vmService.getVM(); + final Response result = await vmService.callMethod( + 'ext.devicelab.test', + isolateId: vm.isolates.single.id, + ); + if (result.json['result'] != 2) { + message += result.json.toString(); + failed = true; + } + runProcess.stdin.write('q'); + vmService.dispose(); + file.deleteSync(); + await runProcess.exitCode; + }); + + await inDirectory(integrationTestDir, () async { + section('Running with track-widget-creation disabled'); + final Process runProcess = await startProcess( + path.join(flutterDirectory.path, 'bin', 'flutter'), + flutterCommandArgs('run', [ + '--vmservice-out-file=info', + '--no-track-widget-creation', + '-v', + '-d', + deviceIdOverride, + path.join('lib/track_widget_creation.dart'), + ]), + environment: { + 'FLUTTER_WEB': 'true', + 'FLUTTER_MACOS': 'true' + } + ); + runProcess.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(print); + final File file = await waitForFile(path.join(integrationTestDir.path, 'info')); + final VmService vmService = await vmServiceConnectUri(file.readAsStringSync()); + final VM vm = await vmService.getVM(); + final Response result = await vmService.callMethod( + 'ext.devicelab.test', + isolateId: vm.isolates.single.id, + ); + if (result.json['result'] != 1) { + message += result.json.toString(); + failed = true; + } + runProcess.stdin.write('q'); + vmService.dispose(); + file.deleteSync(); + await runProcess.exitCode; + }); + + return failed + ? TaskResult.failure(message) + : TaskResult.success(null); + } +} + +/// Wait for up to 400 seconds for the file to appear. +Future waitForFile(String path) async { + for (int i = 0; i < 20; i += 1) { + final File file = File(path); + print('looking for ${file.path}'); + if (file.existsSync()) { + return file; + } + await Future.delayed(const Duration(seconds: 20)); + } + throw StateError('Did not find vmservice out file after 400 seconds'); +} diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index 94e403c4db..e89c8888b1 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -346,6 +346,13 @@ tasks: stage: devicelab required_agent_capabilities: ["mac/android"] + android_enable_twc: + description: > + Verifies that track-widget-creation can be enabled and disabled. + stage: devicelab + required_agent_capabilities: ["mac/android"] + flaky: true + android_defines_test: description: > Builds an APK with a --dart-define and verifies it can be used as a constant @@ -591,6 +598,20 @@ tasks: stage: devicelab_ios required_agent_capabilities: ["mac/ios"] + ios_enable_twc: + description: > + Verifies that track-widget-creation can be enabled and disabled. + stage: devicelab_ios + required_agent_capabilities: ["mac/ios"] + flaky: true + + mac_enable_twc: + description: > + Verifies that track-widget-creation can be enabled and disabled. + stage: devicelab_ios + required_agent_capabilities: ["mac/ios"] + flaky: true + # TODO(fujino): does not pass on iOS13 https://github.com/flutter/flutter/issues/43029 # system_debug_ios: # description: > @@ -797,6 +818,15 @@ tasks: stage: devicelab required_agent_capabilities: ["linux-vm"] + # TODO(jonahwilliams): This should stay off until + # https://github.com/flutter/flutter/issues/56212 is fixed. + # web_enable_twc: + # description: > + # Verifies that track-widget-creation can be enabled and disabled. + # stage: devicelab + # required_agent_capabilities: ["linux-vm"] + # flaky: true + # run_without_leak_linux: # description: > # Checks that `flutter run` does not leak dart on Linux. diff --git a/dev/integration_tests/ui/lib/track_widget_creation.dart b/dev/integration_tests/ui/lib/track_widget_creation.dart new file mode 100644 index 0000000000..16e8c84c99 --- /dev/null +++ b/dev/integration_tests/ui/lib/track_widget_creation.dart @@ -0,0 +1,18 @@ +// 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:developer'; +import 'package:flutter/widgets.dart'; + +void main() { + final Set widgets = {}; + widgets.add(const Text('same')); + widgets.add(const Text('same')); + + // If track-widget-creation is enabled, the set will have 2 members. + // Otherwise is will only have one. + registerExtension('ext.devicelab.test', (String method, Map params) async { + return ServiceExtensionResponse.result('{"result":${widgets.length}}'); + }); +} diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index bbec915da7..5dc4ee9af5 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter non-plugin UI integration tests. environment: # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: ">=2.8.0 <3.0.0" dependencies: image: 2.1.12 diff --git a/dev/integration_tests/ui/web/index.html b/dev/integration_tests/ui/web/index.html new file mode 100644 index 0000000000..b7944c9e9e --- /dev/null +++ b/dev/integration_tests/ui/web/index.html @@ -0,0 +1,13 @@ + + + + + + web_integration + + + + + diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh index 03d00ede70..6a0820a777 100755 --- a/packages/flutter_tools/bin/macos_assemble.sh +++ b/packages/flutter_tools/bin/macos_assemble.sh @@ -79,6 +79,7 @@ RunCommand "${FLUTTER_ROOT}/bin/flutter" \ -dTreeShakeIcons="${TREE_SHAKE_ICONS}" \ -dDartObfuscation="${DART_OBFUSCATION}" \ -dSplitDebugInfo="${SPLIT_DEBUG_INFO}" \ + -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}" \ --DartDefines="${DART_DEFINES}" \ --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}" \ -dExtraFrontEndOptions="${EXTRA_FRONT_END_OPTIONS}" \ diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 2cc9268cb7..8ac0448e2c 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -922,6 +922,9 @@ abstract class BaseFlutterTask extends DefaultTask { if (extraFrontEndOptions != null) { args "-dExtraFrontEndOptions=${extraFrontEndOptions}" } + if (trackWidgetCreation != null) { + args "-dTrackWidgetCreation=${trackWidgetCreation}" + } if (splitDebugInfo != null) { args "-dSplitDebugInfo=${splitDebugInfo}" } diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart index b851c311fb..282e3de6cf 100644 --- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart @@ -713,6 +713,11 @@ class _ResidentWebRunner extends ResidentWebRunner { } } if (websocketUri != null) { + if (debuggingOptions.vmserviceOutFile != null) { + globals.fs.file(debuggingOptions.vmserviceOutFile) + ..createSync(recursive: true) + ..writeAsStringSync(websocketUri.toString()); + } globals.printStatus('Debug service listening on $websocketUri'); } appStartedCompleter?.complete();