| // 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. |
| |
| // @dart = 2.8 |
| |
| import 'dart:async'; |
| |
| import 'package:dds/src/dap/protocol_generated.dart'; |
| import 'package:file/file.dart'; |
| import 'package:flutter_tools/src/cache.dart'; |
| |
| import '../../src/common.dart'; |
| import '../test_data/basic_project.dart'; |
| import '../test_data/compile_error_project.dart'; |
| import '../test_utils.dart'; |
| import 'test_client.dart'; |
| import 'test_support.dart'; |
| |
| void main() { |
| Directory tempDir; |
| /*late*/ DapTestSession dap; |
| final String relativeMainPath = 'lib${fileSystem.path.separator}main.dart'; |
| |
| setUpAll(() { |
| Cache.flutterRoot = getFlutterRoot(); |
| }); |
| |
| setUp(() async { |
| tempDir = createResolvedTempDirectorySync('flutter_adapter_test.'); |
| dap = await DapTestSession.setUp(); |
| }); |
| |
| tearDown(() async { |
| await dap.tearDown(); |
| tryToDelete(tempDir); |
| }); |
| |
| testWithoutContext('can run and terminate a Flutter app in debug mode', () async { |
| final BasicProject _project = BasicProject(); |
| await _project.setUpIn(tempDir); |
| |
| // Once the "topLevelFunction" output arrives, we can terminate the app. |
| unawaited( |
| dap.client.outputEvents |
| .firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')) |
| .whenComplete(() => dap.client.terminate()), |
| ); |
| |
| final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput( |
| launch: () => dap.client |
| .launch( |
| cwd: _project.dir.path, |
| toolArgs: <String>['-d', 'flutter-tester'], |
| ), |
| ); |
| |
| final String output = _uniqueOutputLines(outputEvents); |
| |
| expectLines(output, <Object>[ |
| 'Launching $relativeMainPath on Flutter test device in debug mode...', |
| startsWith('Connecting to VM Service at'), |
| 'topLevelFunction', |
| '', |
| startsWith('Exited'), |
| ]); |
| }); |
| |
| testWithoutContext('can run and terminate a Flutter app in noDebug mode', () async { |
| final BasicProject _project = BasicProject(); |
| await _project.setUpIn(tempDir); |
| |
| // Once the "topLevelFunction" output arrives, we can terminate the app. |
| unawaited( |
| dap.client.outputEvents |
| .firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')) |
| .whenComplete(() => dap.client.terminate()), |
| ); |
| |
| final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput( |
| launch: () => dap.client |
| .launch( |
| cwd: _project.dir.path, |
| noDebug: true, |
| toolArgs: <String>['-d', 'flutter-tester'], |
| ), |
| ); |
| |
| final String output = _uniqueOutputLines(outputEvents); |
| |
| expectLines(output, <Object>[ |
| 'Launching $relativeMainPath on Flutter test device in debug mode...', |
| 'topLevelFunction', |
| '', |
| startsWith('Exited'), |
| ]); |
| }); |
| |
| testWithoutContext('correctly outputs launch errors and terminates', () async { |
| final CompileErrorProject _project = CompileErrorProject(); |
| await _project.setUpIn(tempDir); |
| |
| final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput( |
| launch: () => dap.client |
| .launch( |
| cwd: _project.dir.path, |
| toolArgs: <String>['-d', 'flutter-tester'], |
| ), |
| ); |
| |
| final String output = _uniqueOutputLines(outputEvents); |
| expect(output, contains('this code does not compile')); |
| expect(output, contains('Exception: Failed to build')); |
| expect(output, contains('Exited (1)')); |
| }); |
| |
| testWithoutContext('can hot reload', () async { |
| final BasicProject _project = BasicProject(); |
| await _project.setUpIn(tempDir); |
| |
| // Launch the app and wait for it to print "topLevelFunction". |
| await Future.wait(<Future<Object>>[ |
| dap.client.outputEvents.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')), |
| dap.client.start( |
| launch: () => dap.client.launch( |
| cwd: _project.dir.path, |
| noDebug: true, |
| toolArgs: <String>['-d', 'flutter-tester'], |
| ), |
| ), |
| ], eagerError: true); |
| |
| // Capture the next two output events that we expect to be the Reload |
| // notification and then topLevelFunction being printed again. |
| final Future<List<String>> outputEventsFuture = dap.client.output |
| // But skip any topLevelFunctions that come before the reload. |
| .skipWhile((String output) => output.startsWith('topLevelFunction')) |
| .take(2) |
| .toList(); |
| |
| await dap.client.hotReload(); |
| |
| expectLines( |
| (await outputEventsFuture).join(), |
| <Object>[ |
| startsWith('Reloaded'), |
| 'topLevelFunction', |
| ], |
| ); |
| |
| await dap.client.terminate(); |
| }); |
| |
| testWithoutContext('can hot restart', () async { |
| final BasicProject _project = BasicProject(); |
| await _project.setUpIn(tempDir); |
| |
| // Launch the app and wait for it to print "topLevelFunction". |
| await Future.wait(<Future<Object>>[ |
| dap.client.outputEvents.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')), |
| dap.client.start( |
| launch: () => dap.client.launch( |
| cwd: _project.dir.path, |
| noDebug: true, |
| toolArgs: <String>['-d', 'flutter-tester'], |
| ), |
| ), |
| ], eagerError: true); |
| |
| // Capture the next two output events that we expect to be the Restart |
| // notification and then topLevelFunction being printed again. |
| final Future<List<String>> outputEventsFuture = dap.client.output |
| // But skip any topLevelFunctions that come before the restart. |
| .skipWhile((String output) => output.startsWith('topLevelFunction')) |
| .take(2) |
| .toList(); |
| |
| await dap.client.hotRestart(); |
| |
| expectLines( |
| (await outputEventsFuture).join(), |
| <Object>[ |
| startsWith('Restarted application'), |
| 'topLevelFunction', |
| ], |
| ); |
| |
| await dap.client.terminate(); |
| }); |
| } |
| |
| /// Extracts the output from a set of [OutputEventBody], removing any |
| /// adjacent duplicates and combining into a single string. |
| String _uniqueOutputLines(List<OutputEventBody> outputEvents) { |
| String/*?*/ lastItem; |
| return outputEvents |
| .map((OutputEventBody e) => e.output) |
| .where((String output) { |
| // Skip the item if it's the same as the previous one. |
| final bool isDupe = output == lastItem; |
| lastItem = output; |
| return !isDupe; |
| }) |
| .join(); |
| } |