Unverified Commit 1a048159 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

Add some basic tests for evaluating expressions in `flutter test` (#24513)

* Add some basic tests for evaluating expressions in `flutter test`

* Review tweaks
parent 090c3bcd
......@@ -11,6 +11,7 @@ import 'package:vm_service_lib/vm_service_lib.dart';
import '../src/common.dart';
import 'test_data/basic_project.dart';
import 'test_data/tests_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
......@@ -79,6 +80,47 @@ void main() {
await evaluateComplexReturningExpressions(_flutter);
});
}, timeout: const Timeout.factor(6));
group('flutter test expression evaluation', () {
Directory tempDir;
final TestsProject _project = TestsProject();
FlutterTestTestDriver _flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync();
await _project.setUpIn(tempDir);
_flutter = FlutterTestTestDriver(tempDir);
});
tearDown(() async {
await _flutter.quit();
tryToDelete(tempDir);
});
test('can evaluate trivial expressions in a test', () async {
await _flutter.test(
withDebugger: true,
beforeStart: () => _flutter.addBreakpoint(_project.breakpointUri, _project.breakpointLine),
);
await evaluateTrivialExpressions(_flutter);
});
test('can evaluate complex expressions in a test', () async {
await _flutter.test(
withDebugger: true,
beforeStart: () => _flutter.addBreakpoint(_project.breakpointUri, _project.breakpointLine),
);
await evaluateComplexExpressions(_flutter);
});
test('can evaluate expressions returning complex objects in a test', () async {
await _flutter.test(
withDebugger: true,
beforeStart: () => _flutter.addBreakpoint(_project.breakpointUri, _project.breakpointLine),
);
await evaluateComplexReturningExpressions(_flutter);
});
}, timeout: const Timeout.factor(6));
}
Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
......
// Copyright 2018 The Chromium 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 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import '../test_utils.dart';
import 'project.dart';
class TestsProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
''';
@override
String get main => '// Unused';
final String testContent = r'''
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Hello world test', (WidgetTester tester) async {
expect(true, isTrue); // BREAKPOINT
});
}
''';
@override
Future<void> setUpIn(Directory dir) {
this.dir = dir;
writeFile(testFilePath, testContent);
return super.setUpIn(dir);
}
String get testFilePath => fs.path.join(dir.path, 'test', 'test.dart');
@override
Uri get breakpointUri => Uri.file(testFilePath);
@override
int get breakpointLine => lineContaining(main, '// BREAKPOINT');
}
......@@ -55,6 +55,7 @@ abstract class FlutterTestDriver {
Future<void> _setupProcess(
List<String> args, {
String script,
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
......@@ -66,6 +67,9 @@ abstract class FlutterTestDriver {
if (pidFile != null) {
args.addAll(<String>['--pid-file', pidFile.path]);
}
if (script != null) {
args.add(script);
}
_debugPrint('Spawning flutter $args in ${_projectFolder.path}');
const ProcessManager _processManager = LocalProcessManager();
......@@ -93,6 +97,32 @@ abstract class FlutterTestDriver {
_stderr.stream.listen(_debugPrint);
}
Future<void> connectToVmService({bool pauseOnExceptions = false}) async {
_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'),
]);
// On hot restarts, the isolate ID we have for the Flutter thread will
// exit so we need to invalidate our cached ID.
_vmService.onIsolateEvent.listen((Event event) {
if (event.kind == EventKind.kIsolateExit && event.isolate.id == _flutterIsolateId) {
_flutterIsolateId = null;
}
});
// 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);
}
}
Future<int> quit() => _killGracefully();
Future<int> _killGracefully() async {
......@@ -326,12 +356,14 @@ class FlutterRunTestDriver extends FlutterTestDriver {
@override
Future<void> _setupProcess(
List<String> args, {
String script,
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
}) async {
await super._setupProcess(
args,
script: script,
withDebugger: withDebugger,
pauseOnExceptions: pauseOnExceptions,
pidFile: pidFile,
......@@ -353,30 +385,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
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'),
]);
// On hot restarts, the isolate ID we have for the Flutter thread will
// exit so we need to invalidate our cached ID.
_vmService.onIsolateEvent.listen((Event event) {
if (event.kind == EventKind.kIsolateExit && event.isolate.id == _flutterIsolateId) {
_flutterIsolateId = null;
}
});
// 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 connectToVmService(pauseOnExceptions: pauseOnExceptions);
await resume(wait: false);
}
......@@ -496,6 +505,81 @@ class FlutterRunTestDriver extends FlutterTestDriver {
}
}
class FlutterTestTestDriver extends FlutterTestDriver {
FlutterTestTestDriver(Directory _projectFolder, {String logPrefix}):
super(_projectFolder, logPrefix: logPrefix);
Future<void> test({
String testFile = 'test/test.dart',
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
Future<void> Function() beforeStart,
}) async {
await _setupProcess(<String>[
'test',
'--machine',
'-d',
'flutter-tester'
], script: testFile, withDebugger: withDebugger, pauseOnExceptions: pauseOnExceptions, pidFile: pidFile);
}
@override
Future<void> _setupProcess(
List<String> args, {
String script,
bool withDebugger = false,
bool pauseOnExceptions = false,
File pidFile,
Future<void> Function() beforeStart,
}) async {
await super._setupProcess(
args,
script: script,
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> version = await _waitForJson();
_procPid = version['pid'];
if (withDebugger) {
final Map<String, dynamic> startedProcess = await _waitFor(event: 'test.startedProcess', timeout: appStartTimeout);
final String vmServiceHttpString = startedProcess['params']['observatoryUri'];
_vmServiceWsUri = Uri.parse(vmServiceHttpString).replace(scheme: 'ws', path: '/ws');
await connectToVmService(pauseOnExceptions: pauseOnExceptions);
// Allow us to run code before we start, eg. to set up breakpoints.
if (beforeStart != null) {
await beforeStart();
}
await resume(wait: false);
}
}
Future<Map<String, dynamic>> _waitForJson({
Duration timeout,
}) async {
return _timeoutWithMessages<Map<String, dynamic>>(
() => _stdout.stream.map<Map<String, dynamic>>(_parseJsonResponse).first,
timeout: timeout,
message: 'Did not receive any JSON.',
);
}
Map<String, dynamic> _parseJsonResponse(String line) {
try {
return json.decode(line);
} catch (e) {
// Not valid JSON, so likely some other output.
return null;
}
}
}
Stream<String> transformToLines(Stream<List<int>> byteStream) {
return byteStream.transform<String>(utf8.decoder).transform<String>(const LineSplitter());
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment