blob: f7d76140fd7fd5491ed33af4a56e160e1b752c3c [file] [log] [blame]
// 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();
}