| // 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 'package:yaml_edit/yaml_edit.dart'; | |
| import '../framework/browser.dart'; | |
| import '../framework/devices.dart'; | |
| import '../framework/framework.dart'; | |
| import '../framework/task_result.dart'; | |
| import '../framework/utils.dart'; | |
| final Directory _editedFlutterGalleryWorkspaceDir = dir( | |
| path.join(Directory.systemTemp.path, 'gallery_workspace'), | |
| ); | |
| final Directory _editedFlutterGalleryDir = dir( | |
| path.join(_editedFlutterGalleryWorkspaceDir.path, 'edited_flutter_gallery'), | |
| ); | |
| final Directory flutterGalleryDir = dir( | |
| path.join(flutterDirectory.path, 'dev/integration_tests/flutter_gallery'), | |
| ); | |
| const String kInitialStartupTime = 'InitialStartupTime'; | |
| const String kFirstRestartTime = 'FistRestartTime'; | |
| const String kFirstRecompileTime = 'FirstRecompileTime'; | |
| const String kSecondStartupTime = 'SecondStartupTime'; | |
| const String kSecondRestartTime = 'SecondRestartTime'; | |
| const String kWebServerDevice = 'web-server'; | |
| final RegExp servedAtPattern = RegExp('is being served at (.*)'); | |
| int hotRestartCount = 0; | |
| Future<List<int>> launch({required bool isFirstRun}) async { | |
| final options = <String>[ | |
| '--hot', | |
| '-d', | |
| kWebServerDevice, | |
| '--verbose', | |
| '--resident', | |
| '--target=lib/main.dart', | |
| ]; | |
| final Process process = await startFlutter('run', options: options); | |
| final measurements = <int>[]; | |
| final stdoutDone = Completer<void>(); | |
| final stderrDone = Completer<void>(); | |
| final waitForService = Completer<void>(); | |
| final sw = Stopwatch()..start(); | |
| Chrome? chrome; | |
| var restarted = false; | |
| process | |
| ..stdout | |
| .transform<String>(utf8.decoder) | |
| .transform<String>(const LineSplitter()) | |
| .listen( | |
| (String line) async { | |
| if (line.contains(servedAtPattern)) { | |
| final String url = servedAtPattern.firstMatch(line)!.group(1)!; | |
| chrome = await Chrome.launch( | |
| ChromeOptions(url: url, headless: true, silent: true, debugPort: 10000), | |
| onError: (String e) {}, | |
| ); | |
| } | |
| if (line.contains('DevHandler: Debug service listening on')) { | |
| waitForService.complete(); | |
| } | |
| // non-dwds builds do not know when the browser is loaded so keep trying | |
| // until this succeeds. | |
| if (line.contains('Ignoring terminal input')) { | |
| unawaited( | |
| Future<void>.delayed(const Duration(seconds: 1)).then((void _) { | |
| process.stdin.write(restarted ? 'q' : 'r'); | |
| }), | |
| ); | |
| return; | |
| } | |
| if (line.contains('Hot restart')) { | |
| unawaited( | |
| waitForService.future.then((_) { | |
| // measure clean start-up time. | |
| sw.stop(); | |
| measurements.add(sw.elapsedMilliseconds); | |
| sw | |
| ..reset() | |
| ..start(); | |
| process.stdin.write('r'); | |
| }), | |
| ); | |
| return; | |
| } | |
| if (line.contains('Reloaded application')) { | |
| if (hotRestartCount == 0) { | |
| assert(isFirstRun); | |
| measurements.add(sw.elapsedMilliseconds); | |
| // Update the file and reload again. | |
| final File appDartSource = file( | |
| path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'), | |
| ); | |
| appDartSource.writeAsStringSync( | |
| appDartSource.readAsStringSync().replaceFirst( | |
| "'Flutter Gallery'", | |
| "'Updated Flutter Gallery'", | |
| ), | |
| ); | |
| sw | |
| ..reset() | |
| ..start(); | |
| process.stdin.writeln('r'); | |
| ++hotRestartCount; | |
| } else { | |
| restarted = true; | |
| measurements.add(sw.elapsedMilliseconds); | |
| // Quit after second hot restart. | |
| process.stdin.writeln('q'); | |
| } | |
| } | |
| print('stdout: $line'); | |
| }, | |
| onDone: () { | |
| stdoutDone.complete(); | |
| }, | |
| ) | |
| ..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; | |
| chrome?.stop(); | |
| return measurements; | |
| } | |
| TaskFunction createWebDevModeTest() { | |
| deviceOperatingSystem = DeviceOperatingSystem.webServer; | |
| return () async { | |
| final measurements = <String, int>{}; | |
| await inDirectory<void>(flutterDirectory, () async { | |
| rmTree(_editedFlutterGalleryDir); | |
| mkdirs(_editedFlutterGalleryDir); | |
| recursiveCopy(flutterGalleryDir, _editedFlutterGalleryDir); | |
| final String rootPubspec = File(path.join(flutterDirectory.path, 'pubspec.yaml')) | |
| .readAsStringSync(); | |
| final yamlEditor = YamlEditor(rootPubspec); | |
| yamlEditor.update(<String>['workspace'], <String>['edited_flutter_gallery']); | |
| File(path.join(_editedFlutterGalleryDir.parent.path, 'pubspec.yaml')) | |
| .writeAsStringSync(yamlEditor.toString()); | |
| await inDirectory<void>(_editedFlutterGalleryDir, () async { | |
| await flutter('packages', options: <String>['get']); | |
| final List<int> firstMeasurements = await launch(isFirstRun: true); | |
| measurements.addAll(<String, int>{ | |
| kInitialStartupTime: firstMeasurements[0], | |
| kFirstRecompileTime: firstMeasurements[1], | |
| kFirstRestartTime: firstMeasurements[2], | |
| }); | |
| // Start `flutter run` again to make sure it loads from the previous | |
| // state. dev compilers loads up from previously compiled JavaScript. | |
| final List<int> secondMeasurements = await launch(isFirstRun: false); | |
| measurements.addAll(<String, int>{ | |
| kSecondStartupTime: secondMeasurements[0], | |
| kSecondRestartTime: secondMeasurements[1], | |
| }); | |
| }); | |
| }); | |
| if (hotRestartCount != 1) { | |
| return TaskResult.failure(null); | |
| } | |
| return TaskResult.success( | |
| measurements, | |
| benchmarkScoreKeys: <String>[ | |
| kInitialStartupTime, | |
| kFirstRestartTime, | |
| kFirstRecompileTime, | |
| kSecondStartupTime, | |
| kSecondRestartTime, | |
| ], | |
| ); | |
| }; | |
| } |