Refactor Test Driver in preparation for `flutter test` integration tests (#24060)
* Refactor Test Driver in preperation for `flutter test` integration tests
* Fix indent
diff --git a/packages/flutter_tools/test/integration/expression_evaluation_test.dart b/packages/flutter_tools/test/integration/expression_evaluation_test.dart
index eb6bb3c..6610ee6 100644
--- a/packages/flutter_tools/test/integration/expression_evaluation_test.dart
+++ b/packages/flutter_tools/test/integration/expression_evaluation_test.dart
@@ -15,15 +15,15 @@
import 'test_utils.dart';
void main() {
- group('expression evaluation', () {
+ group('flutter run expression evaluation', () {
Directory tempDir;
final BasicProject _project = BasicProject();
- FlutterTestDriver _flutter;
+ FlutterRunTestDriver _flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
- _flutter = FlutterTestDriver(tempDir);
+ _flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
@@ -43,70 +43,70 @@
_project.topLevelFunctionBreakpointLine);
}
- Future<void> evaluateTrivialExpressions() async {
- InstanceRef res;
-
- res = await _flutter.evaluateInFrame('"test"');
- expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue);
-
- res = await _flutter.evaluateInFrame('1');
- expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue);
-
- res = await _flutter.evaluateInFrame('true');
- expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue);
- }
-
- Future<void> evaluateComplexExpressions() async {
- final InstanceRef res = await _flutter.evaluateInFrame('new DateTime.now().year');
- expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue);
- }
-
- Future<void> evaluateComplexReturningExpressions() async {
- final DateTime now = DateTime.now();
- final InstanceRef resp = await _flutter.evaluateInFrame('new DateTime.now()');
- expect(resp.classRef.name, equals('DateTime'));
- // Ensure we got a reasonable approximation. The more accurate we try to
- // make this, the more likely it'll fail due to differences in the time
- // in the remote VM and the local VM at the time the code runs.
- final InstanceRef res = await _flutter.evaluate(resp.id, r'"$year-$month-$day"');
- expect(res.valueAsString,
- equals('${now.year}-${now.month}-${now.day}'));
- }
-
test('can evaluate trivial expressions in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
- await evaluateTrivialExpressions();
+ await evaluateTrivialExpressions(_flutter);
});
test('can evaluate trivial expressions in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
- await evaluateTrivialExpressions();
+ await evaluateTrivialExpressions(_flutter);
});
test('can evaluate complex expressions in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
- await evaluateComplexExpressions();
+ await evaluateComplexExpressions(_flutter);
});
test('can evaluate complex expressions in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
- await evaluateComplexExpressions();
+ await evaluateComplexExpressions(_flutter);
});
test('can evaluate expressions returning complex objects in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
- await evaluateComplexReturningExpressions();
+ await evaluateComplexReturningExpressions(_flutter);
});
test('can evaluate expressions returning complex objects in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
- await evaluateComplexReturningExpressions();
+ await evaluateComplexReturningExpressions(_flutter);
});
}, timeout: const Timeout.factor(6));
}
+
+Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
+ InstanceRef res;
+
+ res = await flutter.evaluateInFrame('"test"');
+ expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue);
+
+ res = await flutter.evaluateInFrame('1');
+ expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue);
+
+ res = await flutter.evaluateInFrame('true');
+ expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue);
+}
+
+Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async {
+ final InstanceRef res = await flutter.evaluateInFrame('new DateTime.now().year');
+ expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue);
+}
+
+Future<void> evaluateComplexReturningExpressions(FlutterTestDriver flutter) async {
+ final DateTime now = DateTime.now();
+ final InstanceRef resp = await flutter.evaluateInFrame('new DateTime.now()');
+ expect(resp.classRef.name, equals('DateTime'));
+ // Ensure we got a reasonable approximation. The more accurate we try to
+ // make this, the more likely it'll fail due to differences in the time
+ // in the remote VM and the local VM at the time the code runs.
+ final InstanceRef res = await flutter.evaluate(resp.id, r'"$year-$month-$day"');
+ expect(res.valueAsString,
+ equals('${now.year}-${now.month}-${now.day}'));
+}
diff --git a/packages/flutter_tools/test/integration/flutter_attach_test.dart b/packages/flutter_tools/test/integration/flutter_attach_test.dart
index 2e21ffa..dff1132 100644
--- a/packages/flutter_tools/test/integration/flutter_attach_test.dart
+++ b/packages/flutter_tools/test/integration/flutter_attach_test.dart
@@ -11,15 +11,15 @@
import 'test_utils.dart';
void main() {
- FlutterTestDriver _flutterRun, _flutterAttach;
+ FlutterRunTestDriver _flutterRun, _flutterAttach;
final BasicProject _project = BasicProject();
Directory tempDir;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
- _flutterRun = FlutterTestDriver(tempDir, logPrefix: 'RUN');
- _flutterAttach = FlutterTestDriver(tempDir, logPrefix: 'ATTACH');
+ _flutterRun = FlutterRunTestDriver(tempDir, logPrefix: 'RUN');
+ _flutterAttach = FlutterRunTestDriver(tempDir, logPrefix: 'ATTACH');
});
tearDown(() async {
@@ -54,7 +54,7 @@
await _flutterRun.run(withDebugger: true);
await _flutterAttach.attach(_flutterRun.vmServicePort);
await _flutterAttach.quit();
- _flutterAttach = FlutterTestDriver(tempDir, logPrefix: 'ATTACH-2');
+ _flutterAttach = FlutterRunTestDriver(tempDir, logPrefix: 'ATTACH-2');
await _flutterAttach.attach(_flutterRun.vmServicePort);
await _flutterAttach.hotReload();
});
diff --git a/packages/flutter_tools/test/integration/flutter_run_test.dart b/packages/flutter_tools/test/integration/flutter_run_test.dart
index 1b31405..70c69d1 100644
--- a/packages/flutter_tools/test/integration/flutter_run_test.dart
+++ b/packages/flutter_tools/test/integration/flutter_run_test.dart
@@ -16,12 +16,12 @@
group('flutter_run', () {
Directory tempDir;
final BasicProject _project = BasicProject();
- FlutterTestDriver _flutter;
+ FlutterRunTestDriver _flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
- _flutter = FlutterTestDriver(tempDir);
+ _flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
diff --git a/packages/flutter_tools/test/integration/hot_reload_test.dart b/packages/flutter_tools/test/integration/hot_reload_test.dart
index 46c3d30..b7e9602 100644
--- a/packages/flutter_tools/test/integration/hot_reload_test.dart
+++ b/packages/flutter_tools/test/integration/hot_reload_test.dart
@@ -17,12 +17,12 @@
group('hot', () {
Directory tempDir;
final HotReloadProject _project = HotReloadProject();
- FlutterTestDriver _flutter;
+ FlutterRunTestDriver _flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
- _flutter = FlutterTestDriver(tempDir);
+ _flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
diff --git a/packages/flutter_tools/test/integration/lifetime_test.dart b/packages/flutter_tools/test/integration/lifetime_test.dart
index aab6d1a..f8faa03 100644
--- a/packages/flutter_tools/test/integration/lifetime_test.dart
+++ b/packages/flutter_tools/test/integration/lifetime_test.dart
@@ -20,13 +20,13 @@
void main() {
group('flutter run', () {
final BasicProject _project = BasicProject();
- FlutterTestDriver _flutter;
+ FlutterRunTestDriver _flutter;
Directory tempDir;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
- _flutter = FlutterTestDriver(tempDir);
+ _flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
diff --git a/packages/flutter_tools/test/integration/test_data/basic_project.dart b/packages/flutter_tools/test/integration/test_data/basic_project.dart
index 4aa8074..b315f05 100644
--- a/packages/flutter_tools/test/integration/test_data/basic_project.dart
+++ b/packages/flutter_tools/test/integration/test_data/basic_project.dart
@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'test_project.dart';
+import 'project.dart';
-class BasicProject extends TestProject {
+class BasicProject extends Project {
@override
final String pubspec = '''
diff --git a/packages/flutter_tools/test/integration/test_data/hot_reload_project.dart b/packages/flutter_tools/test/integration/test_data/hot_reload_project.dart
index 0e99ba3..f4174ed 100644
--- a/packages/flutter_tools/test/integration/test_data/hot_reload_project.dart
+++ b/packages/flutter_tools/test/integration/test_data/hot_reload_project.dart
@@ -5,9 +5,9 @@
import 'package:flutter_tools/src/base/file_system.dart';
import '../test_utils.dart';
-import 'test_project.dart';
+import 'project.dart';
-class HotReloadProject extends TestProject {
+class HotReloadProject extends Project {
@override
final String pubspec = '''
name: test
diff --git a/packages/flutter_tools/test/integration/test_data/test_project.dart b/packages/flutter_tools/test/integration/test_data/project.dart
similarity index 89%
rename from packages/flutter_tools/test/integration/test_data/test_project.dart
rename to packages/flutter_tools/test/integration/test_data/project.dart
index 5428177..e723aff 100644
--- a/packages/flutter_tools/test/integration/test_data/test_project.dart
+++ b/packages/flutter_tools/test/integration/test_data/project.dart
@@ -9,7 +9,7 @@
import '../test_utils.dart';
-abstract class TestProject {
+abstract class Project {
Directory dir;
String get pubspec;
@@ -22,7 +22,9 @@
Future<void> setUpIn(Directory dir) async {
this.dir = dir;
writeFile(fs.path.join(dir.path, 'pubspec.yaml'), pubspec);
- writeFile(fs.path.join(dir.path, 'lib', 'main.dart'), main);
+ if (main != null) {
+ writeFile(fs.path.join(dir.path, 'lib', 'main.dart'), main);
+ }
await getPackages(dir.path);
}
diff --git a/packages/flutter_tools/test/integration/test_driver.dart b/packages/flutter_tools/test/integration/test_driver.dart
index 0a596bf..0778064 100644
--- a/packages/flutter_tools/test/integration/test_driver.dart
+++ b/packages/flutter_tools/test/integration/test_driver.dart
@@ -20,7 +20,7 @@
const Duration appStartTimeout = Duration(seconds: 120);
const Duration quitTimeout = Duration(seconds: 10);
-class FlutterTestDriver {
+abstract class FlutterTestDriver {
FlutterTestDriver(this._projectFolder, {String logPrefix}):
_logPrefix = logPrefix != null ? '$logPrefix: ' : '';
@@ -33,7 +33,6 @@
final StreamController<String> _allMessages = StreamController<String>.broadcast();
final StringBuffer _errorBuffer = StringBuffer();
String _lastResponse;
- String _currentRunningAppId;
Uri _vmServiceWsUri;
bool _hasExited = false;
@@ -54,35 +53,6 @@
return msg;
}
- Future<void> run({
- bool withDebugger = false,
- bool pauseOnExceptions = false,
- File pidFile,
- }) async {
- await _setupProcess(<String>[
- 'run',
- '--machine',
- '-d',
- 'flutter-tester',
- ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
- }
-
- Future<void> attach(
- int port, {
- bool withDebugger = false,
- bool pauseOnExceptions = false,
- File pidFile,
- }) async {
- await _setupProcess(<String>[
- 'attach',
- '--machine',
- '-d',
- 'flutter-tester',
- '--debug-port',
- '$port',
- ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
- }
-
Future<void> _setupProcess(
List<String> args, {
bool withDebugger = false,
@@ -121,110 +91,6 @@
// This is just debug printing to aid running/debugging tests locally.
_stdout.stream.listen(_debugPrint);
_stderr.stream.listen(_debugPrint);
-
- // Stash the PID so that we can terminate the VM more reliably than using
- // _proc.kill() (because _proc is a shell, because `flutter` is a shell
- // script).
- final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
- _procPid = connected['params']['pid'];
-
- // Set this up now, but we don't wait it yet. We want to make sure we don't
- // miss it while waiting for debugPort below.
- final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started',
- timeout: appStartTimeout);
-
- if (withDebugger) {
- final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort',
- timeout: appStartTimeout);
- final String wsUriString = debugPort['params']['wsUri'];
- _vmServiceWsUri = Uri.parse(wsUriString);
- _vmService =
- await vmServiceConnectUri(_vmServiceWsUri.toString());
- _vmService.onSend.listen((String s) => _debugPrint('==> $s'));
- _vmService.onReceive.listen((String s) => _debugPrint('<== $s'));
- await Future.wait(<Future<Success>>[
- _vmService.streamListen('Isolate'),
- _vmService.streamListen('Debug'),
- ]);
-
- // Because we start paused, resume so the app is in a "running" state as
- // expected by tests. Tests will reload/restart as required if they need
- // to hit breakpoints, etc.
- await waitForPause();
- if (pauseOnExceptions) {
- await _vmService.setExceptionPauseMode(await _getFlutterIsolateId(), ExceptionPauseMode.kUnhandled);
- }
- await resume(wait: false);
- }
-
- // Now await the started event; if it had already happened the future will
- // have already completed.
- _currentRunningAppId = (await started)['params']['appId'];
- }
-
- Future<void> hotRestart({bool pause = false}) => _restart(fullRestart: true, pause: pause);
- Future<void> hotReload() => _restart(fullRestart: false);
-
- Future<void> _restart({bool fullRestart = false, bool pause = false}) async {
- if (_currentRunningAppId == null)
- throw Exception('App has not started yet');
-
- final dynamic hotReloadResp = await _sendRequest(
- 'app.restart',
- <String, dynamic>{'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause},
- );
-
- if (hotReloadResp == null || hotReloadResp['code'] != 0)
- _throwErrorResponse('Hot ${fullRestart ? 'restart' : 'reload'} request failed');
- }
-
- Future<int> detach() async {
- if (_vmService != null) {
- _debugPrint('Closing VM service');
- _vmService.dispose();
- }
- if (_currentRunningAppId != null) {
- _debugPrint('Detaching from app');
- await Future.any<void>(<Future<void>>[
- _proc.exitCode,
- _sendRequest(
- 'app.detach',
- <String, dynamic>{'appId': _currentRunningAppId},
- ),
- ]).timeout(
- quitTimeout,
- onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); },
- );
- _currentRunningAppId = null;
- }
- _debugPrint('Waiting for process to end');
- return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
- }
-
- Future<int> stop() async {
- if (_vmService != null) {
- _debugPrint('Closing VM service');
- _vmService.dispose();
- }
- if (_currentRunningAppId != null) {
- _debugPrint('Stopping app');
- await Future.any<void>(<Future<void>>[
- _proc.exitCode,
- _sendRequest(
- 'app.stop',
- <String, dynamic>{'appId': _currentRunningAppId},
- ),
- ]).timeout(
- quitTimeout,
- onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); },
- );
- _currentRunningAppId = null;
- }
- if (_proc != null) {
- _debugPrint('Waiting for process to end');
- return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
- }
- return 0;
}
Future<int> quit() => _killGracefully();
@@ -309,20 +175,6 @@
return wait ? waitForPause() : null;
}
- Future<Isolate> breakAt(Uri uri, int line, {bool restart = false}) async {
- if (restart) {
- // For a hot restart, we need to send the breakpoints after the restart
- // so we need to pause during the restart to avoid races.
- await hotRestart(pause: true);
- await addBreakpoint(uri, line);
- return resume();
- } else {
- await addBreakpoint(uri, line);
- await hotReload();
- return waitForPause();
- }
- }
-
Future<InstanceRef> evaluateInFrame(String expression) async {
return _timeoutWithMessages<InstanceRef>(
() async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression),
@@ -438,6 +290,175 @@
}
return null;
}
+}
+
+class FlutterRunTestDriver extends FlutterTestDriver {
+ FlutterRunTestDriver(Directory _projectFolder, {String logPrefix}):
+ super(_projectFolder, logPrefix: logPrefix);
+
+ String _currentRunningAppId;
+
+ Future<void> run({
+ bool withDebugger = false,
+ bool pauseOnExceptions = false,
+ File pidFile,
+ }) async {
+ await _setupProcess(<String>[
+ 'run',
+ '--machine',
+ '-d',
+ 'flutter-tester',
+ ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
+ }
+
+ Future<void> attach(
+ int port, {
+ bool withDebugger = false,
+ bool pauseOnExceptions = false,
+ File pidFile,
+ }) async {
+ await _setupProcess(<String>[
+ 'attach',
+ '--machine',
+ '-d',
+ 'flutter-tester',
+ '--debug-port',
+ '$port',
+ ], withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
+ }
+
+ @override
+ Future<void> _setupProcess(
+ List<String> args, {
+ bool withDebugger = false,
+ bool pauseOnExceptions = false,
+ File pidFile,
+ }) async {
+ await super._setupProcess(
+ args,
+ withDebugger: withDebugger,
+ pauseOnExceptions: pauseOnExceptions,
+ pidFile: pidFile,
+ );
+
+ // Stash the PID so that we can terminate the VM more reliably than using
+ // _proc.kill() (because _proc is a shell, because `flutter` is a shell
+ // script).
+ final Map<String, dynamic> connected = await _waitFor(event: 'daemon.connected');
+ _procPid = connected['params']['pid'];
+
+ // Set this up now, but we don't wait it yet. We want to make sure we don't
+ // miss it while waiting for debugPort below.
+ final Future<Map<String, dynamic>> started = _waitFor(event: 'app.started',
+ timeout: appStartTimeout);
+
+ if (withDebugger) {
+ final Map<String, dynamic> debugPort = await _waitFor(event: 'app.debugPort',
+ timeout: appStartTimeout);
+ final String wsUriString = debugPort['params']['wsUri'];
+ _vmServiceWsUri = Uri.parse(wsUriString);
+ _vmService =
+ await vmServiceConnectUri(_vmServiceWsUri.toString());
+ _vmService.onSend.listen((String s) => _debugPrint('==> $s'));
+ _vmService.onReceive.listen((String s) => _debugPrint('<== $s'));
+ await Future.wait(<Future<Success>>[
+ _vmService.streamListen('Isolate'),
+ _vmService.streamListen('Debug'),
+ ]);
+
+ // Because we start paused, resume so the app is in a "running" state as
+ // expected by tests. Tests will reload/restart as required if they need
+ // to hit breakpoints, etc.
+ await waitForPause();
+ if (pauseOnExceptions) {
+ await _vmService.setExceptionPauseMode(await _getFlutterIsolateId(), ExceptionPauseMode.kUnhandled);
+ }
+ await resume(wait: false);
+ }
+
+ // Now await the started event; if it had already happened the future will
+ // have already completed.
+ _currentRunningAppId = (await started)['params']['appId'];
+ }
+
+ Future<void> hotRestart({bool pause = false}) => _restart(fullRestart: true, pause: pause);
+ Future<void> hotReload() => _restart(fullRestart: false);
+
+ Future<void> _restart({bool fullRestart = false, bool pause = false}) async {
+ if (_currentRunningAppId == null)
+ throw Exception('App has not started yet');
+
+ final dynamic hotReloadResp = await _sendRequest(
+ 'app.restart',
+ <String, dynamic>{'appId': _currentRunningAppId, 'fullRestart': fullRestart, 'pause': pause},
+ );
+
+ if (hotReloadResp == null || hotReloadResp['code'] != 0)
+ _throwErrorResponse('Hot ${fullRestart ? 'restart' : 'reload'} request failed');
+ }
+
+ Future<int> detach() async {
+ if (_vmService != null) {
+ _debugPrint('Closing VM service');
+ _vmService.dispose();
+ }
+ if (_currentRunningAppId != null) {
+ _debugPrint('Detaching from app');
+ await Future.any<void>(<Future<void>>[
+ _proc.exitCode,
+ _sendRequest(
+ 'app.detach',
+ <String, dynamic>{'appId': _currentRunningAppId},
+ ),
+ ]).timeout(
+ quitTimeout,
+ onTimeout: () { _debugPrint('app.detach did not return within $quitTimeout'); },
+ );
+ _currentRunningAppId = null;
+ }
+ _debugPrint('Waiting for process to end');
+ return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
+ }
+
+ Future<int> stop() async {
+ if (_vmService != null) {
+ _debugPrint('Closing VM service');
+ _vmService.dispose();
+ }
+ if (_currentRunningAppId != null) {
+ _debugPrint('Stopping app');
+ await Future.any<void>(<Future<void>>[
+ _proc.exitCode,
+ _sendRequest(
+ 'app.stop',
+ <String, dynamic>{'appId': _currentRunningAppId},
+ ),
+ ]).timeout(
+ quitTimeout,
+ onTimeout: () { _debugPrint('app.stop did not return within $quitTimeout'); },
+ );
+ _currentRunningAppId = null;
+ }
+ if (_proc != null) {
+ _debugPrint('Waiting for process to end');
+ return _proc.exitCode.timeout(quitTimeout, onTimeout: _killGracefully);
+ }
+ return 0;
+ }
+
+ Future<Isolate> breakAt(Uri uri, int line, { bool restart = false }) async {
+ if (restart) {
+ // For a hot restart, we need to send the breakpoints after the restart
+ // so we need to pause during the restart to avoid races.
+ await hotRestart(pause: true);
+ await addBreakpoint(uri, line);
+ return resume();
+ } else {
+ await addBreakpoint(uri, line);
+ await hotReload();
+ return waitForPause();
+ }
+ }
int id = 1;
Future<dynamic> _sendRequest(String method, dynamic params) async {