// 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/devices.dart'; import '../framework/framework.dart'; import '../framework/task_result.dart'; import '../framework/utils.dart'; final Directory _editedFlutterGalleryDir = dir(path.join(Directory.systemTemp.path, 'edited_flutter_gallery')); final Directory flutterGalleryDir = dir(path.join(flutterDirectory.path, 'dev/integration_tests/flutter_gallery')); const String kSourceLine = 'fontSize: (orientation == Orientation.portrait) ? 32.0 : 24.0'; const String kReplacementLine = 'fontSize: (orientation == Orientation.portrait) ? 34.0 : 24.0'; TaskFunction createHotModeTest({String? deviceIdOverride, Map<String, String>? environment}) { // This file is modified during the test and needs to be restored at the end. final File flutterFrameworkSource = file(path.join( flutterDirectory.path, 'packages/flutter/lib/src/widgets/framework.dart', )); final String oldContents = flutterFrameworkSource.readAsStringSync(); return () async { if (deviceIdOverride == null) { final Device device = await devices.workingDevice; await device.unlock(); deviceIdOverride = device.deviceId; } final File benchmarkFile = file(path.join(_editedFlutterGalleryDir.path, 'hot_benchmark.json')); rm(benchmarkFile); final List<String> options = <String>[ '--hot', '-d', deviceIdOverride!, '--benchmark', '--resident', '--no-android-gradle-daemon', '--no-publish-port', '--verbose', '--uninstall-first', ]; int hotReloadCount = 0; late Map<String, dynamic> smallReloadData; late Map<String, dynamic> mediumReloadData; late Map<String, dynamic> largeReloadData; late Map<String, dynamic> freshRestartReloadsData; await inDirectory<void>(flutterDirectory, () async { rmTree(_editedFlutterGalleryDir); mkdirs(_editedFlutterGalleryDir); recursiveCopy(flutterGalleryDir, _editedFlutterGalleryDir); try { await inDirectory<void>(_editedFlutterGalleryDir, () async { smallReloadData = await captureReloadData(options, environment, benchmarkFile, (String line, Process process) { if (!line.contains('Reloaded ')) { return; } if (hotReloadCount == 0) { // Update a file for 2 library invalidation. final File appDartSource = file(path.join( _editedFlutterGalleryDir.path, 'lib/gallery/app.dart', )); appDartSource.writeAsStringSync( appDartSource.readAsStringSync().replaceFirst( "'Flutter Gallery'", "'Updated Flutter Gallery'", )); process.stdin.writeln('r'); hotReloadCount += 1; } else { process.stdin.writeln('q'); } }); mediumReloadData = await captureReloadData(options, environment, benchmarkFile, (String line, Process process) { if (!line.contains('Reloaded ')) { return; } if (hotReloadCount == 1) { // Update a file for ~50 library invalidation. final File appDartSource = file(path.join( _editedFlutterGalleryDir.path, 'lib/demo/calculator/home.dart', )); appDartSource.writeAsStringSync( appDartSource.readAsStringSync().replaceFirst(kSourceLine, kReplacementLine) ); process.stdin.writeln('r'); hotReloadCount += 1; } else { process.stdin.writeln('q'); } }); largeReloadData = await captureReloadData(options, environment, benchmarkFile, (String line, Process process) { if (!line.contains('Reloaded ')) { return; } if (hotReloadCount == 2) { // Trigger a framework invalidation (370 libraries) without modifying the source flutterFrameworkSource.writeAsStringSync( '${flutterFrameworkSource.readAsStringSync()}\n' ); process.stdin.writeln('r'); hotReloadCount += 1; } else { process.stdin.writeln('q'); } }); // Start `flutter run` again to make sure it loads from the previous // state. Frontend loads up from previously generated kernel files. { final Process process = await startProcess( path.join(flutterDirectory.path, 'bin', 'flutter'), flutterCommandArgs('run', options), environment: environment, ); final Completer<void> stdoutDone = Completer<void>(); final Completer<void> stderrDone = Completer<void>(); process.stdout .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { if (line.contains('Reloaded ')) { process.stdin.writeln('q'); } print('stdout: $line'); }, onDone: () { stdoutDone.complete(); }); process.stderr .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { print('stderr: $line'); }, onDone: () { stderrDone.complete(); }); await Future.wait<void>( <Future<void>>[stdoutDone.future, stderrDone.future]); await process.exitCode; freshRestartReloadsData = json.decode(benchmarkFile.readAsStringSync()) as Map<String, dynamic>; } }); } finally { flutterFrameworkSource.writeAsStringSync(oldContents); } }); return TaskResult.success( <String, dynamic> { // ignore: avoid_dynamic_calls 'hotReloadInitialDevFSSyncMilliseconds': smallReloadData['hotReloadInitialDevFSSyncMilliseconds'][0], // ignore: avoid_dynamic_calls 'hotRestartMillisecondsToFrame': smallReloadData['hotRestartMillisecondsToFrame'][0], // ignore: avoid_dynamic_calls 'hotReloadMillisecondsToFrame' : smallReloadData['hotReloadMillisecondsToFrame'][0], // ignore: avoid_dynamic_calls 'hotReloadDevFSSyncMilliseconds': smallReloadData['hotReloadDevFSSyncMilliseconds'][0], // ignore: avoid_dynamic_calls 'hotReloadFlutterReassembleMilliseconds': smallReloadData['hotReloadFlutterReassembleMilliseconds'][0], // ignore: avoid_dynamic_calls 'hotReloadVMReloadMilliseconds': smallReloadData['hotReloadVMReloadMilliseconds'][0], // ignore: avoid_dynamic_calls 'hotReloadMillisecondsToFrameAfterChange' : smallReloadData['hotReloadMillisecondsToFrame'][1], // ignore: avoid_dynamic_calls 'hotReloadDevFSSyncMillisecondsAfterChange': smallReloadData['hotReloadDevFSSyncMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadFlutterReassembleMillisecondsAfterChange': smallReloadData['hotReloadFlutterReassembleMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadVMReloadMillisecondsAfterChange': smallReloadData['hotReloadVMReloadMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadInitialDevFSSyncAfterRelaunchMilliseconds' : freshRestartReloadsData['hotReloadInitialDevFSSyncMilliseconds'][0], // ignore: avoid_dynamic_calls 'hotReloadMillisecondsToFrameAfterMediumChange' : mediumReloadData['hotReloadMillisecondsToFrame'][1], // ignore: avoid_dynamic_calls 'hotReloadDevFSSyncMillisecondsAfterMediumChange': mediumReloadData['hotReloadDevFSSyncMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadFlutterReassembleMillisecondsAfterMediumChange': mediumReloadData['hotReloadFlutterReassembleMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadVMReloadMillisecondsAfterMediumChange': mediumReloadData['hotReloadVMReloadMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadMillisecondsToFrameAfterLargeChange' : largeReloadData['hotReloadMillisecondsToFrame'][1], // ignore: avoid_dynamic_calls 'hotReloadDevFSSyncMillisecondsAfterLargeChange': largeReloadData['hotReloadDevFSSyncMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadFlutterReassembleMillisecondsAfterLargeChange': largeReloadData['hotReloadFlutterReassembleMilliseconds'][1], // ignore: avoid_dynamic_calls 'hotReloadVMReloadMillisecondsAfterLargeChange': largeReloadData['hotReloadVMReloadMilliseconds'][1], }, benchmarkScoreKeys: <String>[ 'hotReloadInitialDevFSSyncMilliseconds', 'hotRestartMillisecondsToFrame', 'hotReloadMillisecondsToFrame', 'hotReloadDevFSSyncMilliseconds', 'hotReloadFlutterReassembleMilliseconds', 'hotReloadVMReloadMilliseconds', 'hotReloadMillisecondsToFrameAfterChange', 'hotReloadDevFSSyncMillisecondsAfterChange', 'hotReloadFlutterReassembleMillisecondsAfterChange', 'hotReloadVMReloadMillisecondsAfterChange', 'hotReloadInitialDevFSSyncAfterRelaunchMilliseconds', 'hotReloadMillisecondsToFrameAfterMediumChange', 'hotReloadDevFSSyncMillisecondsAfterMediumChange', 'hotReloadFlutterReassembleMillisecondsAfterMediumChange', 'hotReloadVMReloadMillisecondsAfterMediumChange', 'hotReloadMillisecondsToFrameAfterLargeChange', 'hotReloadDevFSSyncMillisecondsAfterLargeChange', 'hotReloadFlutterReassembleMillisecondsAfterLargeChange', 'hotReloadVMReloadMillisecondsAfterLargeChange', ], ); }; } Future<Map<String, dynamic>> captureReloadData( List<String> options, Map<String, String>? environment, File benchmarkFile, void Function(String, Process) onLine, ) async { final Process process = await startProcess( path.join(flutterDirectory.path, 'bin', 'flutter'), flutterCommandArgs('run', options), environment: environment, ); final Completer<void> stdoutDone = Completer<void>(); final Completer<void> stderrDone = Completer<void>(); process.stdout .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { onLine(line, process); print('stdout: $line'); }, onDone: stdoutDone.complete); process.stderr .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen( (String line) => print('stderr: $line'), onDone: stderrDone.complete, ); await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]); await process.exitCode; final Map<String, dynamic> result = json.decode(benchmarkFile.readAsStringSync()) as Map<String, dynamic>; benchmarkFile.deleteSync(); return result; }