Unverified Commit 3456dc62 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

Add failing tests for #17833, #18441 and #18678 (#18560)

parent aa088501
// 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 'dart:async';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:test/test.dart';
import 'package:vm_service_client/vm_service_client.dart';
import '../src/context.dart';
import 'flutter_test_driver.dart';
import 'util.dart';
Directory _tempDir;
FlutterTestDriver _flutter;
void main() {
setUp(() async {
_tempDir = await fs.systemTempDirectory.createTemp('test_app');
await _setupSampleProject();
_flutter = new FlutterTestDriver(_tempDir);
});
tearDown(() async {
try {
await _flutter.stop();
_tempDir?.deleteSync(recursive: true);
_tempDir = null;
} catch (e) {
// Don't fail tests if we failed to clean up temp folder.
}
});
Future<VMIsolate> breakInBuildMethod(FlutterTestDriver flutter) async {
return _flutter.breakAt(
fs.path.join(_tempDir.path, 'lib', 'main.dart'),
9
);
}
Future<VMIsolate> breakInTopLevelFunction(FlutterTestDriver flutter) async {
return _flutter.breakAt(
fs.path.join(_tempDir.path, 'lib', 'main.dart'),
17
);
}
group('FlutterTesterDevice', () {
testUsingContext('can hot reload', () async {
await _flutter.run();
await _flutter.hotReload();
}, skip: true); // https://github.com/flutter/flutter/issues/17833
testUsingContext('can hit breakpoints with file:// prefixes after reload', () async {
await _flutter.run(withDebugger: true);
// Add the breakpoint using a file:// URI.
await _flutter.addBreakpoint(
// Test currently passes with a FS path, but not with file:// URI.
// fs.path.join(_tempDir.path, 'lib', 'main.dart'),
new Uri.file(fs.path.join(_tempDir.path, 'lib', 'main.dart')).toString(),
9
);
await _flutter.hotReload();
// Ensure we hit the breakpoint.
final VMIsolate isolate = await _flutter.waitForBreakpointHit();
expect(isolate.pauseEvent, const isInstanceOf<VMPauseBreakpointEvent>());
}, skip: true); // https://github.com/flutter/flutter/issues/18441
Future<void> evaluateTrivialExpressions() async {
VMInstanceRef res;
res = await _flutter.evaluateExpression('"test"');
expect(res is VMStringInstanceRef && res.value == 'test', isTrue);
res = await _flutter.evaluateExpression('"test"');
expect(res is VMIntInstanceRef && res.value == 1, isTrue);
res = await _flutter.evaluateExpression('"test"');
expect(res is VMBoolInstanceRef && res.value == true, isTrue);
}
Future<void> evaluateComplexExpressions() async {
final VMInstanceRef res = await _flutter.evaluateExpression('new DateTime.now().year');
expect(res is VMIntInstanceRef && res.value == new DateTime.now().year, isTrue);
}
Future<void> evaluateComplexReturningExpressions() async {
final DateTime now = new DateTime.now();
final VMInstanceRef resp = await _flutter.evaluateExpression('new DateTime.now()');
expect(resp.klass.name, equals('DateTime'));
final DateTime value = await resp.getValue();
// 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.
expect('${value.year}-${value.month}-${value.day}',
equals('${now.year}-${now.month}-${now.day}'));
}
testUsingContext('can evaluate trivial expressions in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
await evaluateTrivialExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
testUsingContext('can evaluate trivial expressions in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
await evaluateTrivialExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
testUsingContext('can evaluate complex expressions in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
await evaluateTrivialExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
testUsingContext('can evaluate complex expressions in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
await evaluateComplexExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
testUsingContext('can evaluate expressions returning complex objects in top level function', () async {
await _flutter.run(withDebugger: true);
await breakInTopLevelFunction(_flutter);
await evaluateComplexReturningExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
testUsingContext('can evaluate expressions returning complex objects in build method', () async {
await _flutter.run(withDebugger: true);
await breakInBuildMethod(_flutter);
await evaluateComplexReturningExpressions();
}, skip: true); // https://github.com/flutter/flutter/issues/18678
}, timeout: const Timeout.factor(3));
}
Future<void> _setupSampleProject() async {
writePubspec(_tempDir.path);
writePackages(_tempDir.path);
await getPackages(_tempDir.path);
final String mainPath = fs.path.join(_tempDir.path, 'lib', 'main.dart');
writeFile(mainPath, r'''
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
topLevelFunction();
return new MaterialApp(
title: 'Flutter Demo',
home: new Container(),
);
}
}
topLevelFunction() {
print("test");
}
''');
}
// 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 'dart:async';
import 'dart:convert';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:process/process.dart';
import 'package:vm_service_client/vm_service_client.dart';
import '../src/common.dart';
// Set this to true for debugging to get JSON written to stdout.
const bool _printJsonAndStderr = false;
class FlutterTestDriver {
Directory _projectFolder;
Process _proc;
final StreamController<String> _stdout = new StreamController<String>.broadcast();
final StreamController<String> _stderr = new StreamController<String>.broadcast();
final StringBuffer _errorBuffer = new StringBuffer();
String _currentRunningAppId;
FlutterTestDriver(this._projectFolder);
VMServiceClient vmService;
String get lastErrorInfo => _errorBuffer.toString();
// TODO(dantup): Is there a better way than spawning a proc? This breaks debugging..
// However, there's a lot of logic inside RunCommand that wouldn't be good
// to duplicate here.
Future<void> run({bool withDebugger = false}) async {
_proc = await _runFlutter(_projectFolder);
_transformToLines(_proc.stdout).listen((String line) => _stdout.add(line));
_transformToLines(_proc.stderr).listen((String line) => _stderr.add(line));
// Capture stderr to a buffer so we can show it all if any requests fail.
_stderr.stream.listen(_errorBuffer.writeln);
// This is just debug printing to aid running/debugging tests locally.
if (_printJsonAndStderr) {
_stdout.stream.listen(print);
_stderr.stream.listen(print);
}
// 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');
if (withDebugger) {
final Future<Map<String, dynamic>> debugPort = _waitFor(event: 'app.debugPort');
final String wsUri = (await debugPort)['params']['wsUri'];
vmService = new VMServiceClient.connect(wsUri);
}
// Now await the started event; if it had already happened the future will
// have already completed.
_currentRunningAppId = (await started)['params']['appId'];
}
Future<void> hotReload() async {
if (_currentRunningAppId == null)
throw new Exception('App has not started yet');
final dynamic hotReloadResp = await _sendRequest(
'app.restart',
<String, dynamic>{'appId': _currentRunningAppId, 'fullRestart': false}
);
if (hotReloadResp == null || hotReloadResp['code'] != 0)
throw 'Hot reload request failed\n\n${_errorBuffer.toString()}';
}
Future<int> stop() async {
if (_currentRunningAppId != null) {
await _sendRequest(
'app.stop',
<String, dynamic>{'appId': _currentRunningAppId}
);
}
_currentRunningAppId = null;
return _proc.exitCode;
}
Future<Process> _runFlutter(Directory projectDir) async {
final String flutterBin = fs.path.join(getFlutterRoot(), 'bin', 'flutter');
final List<String> command = <String>[
flutterBin,
'run',
'--machine',
'-d',
'flutter-tester',
'--observatory-port=0',
];
if (_printJsonAndStderr) {
print('Spawning $command in ${projectDir.path}');
}
const ProcessManager _processManager = const LocalProcessManager();
return _processManager.start(
command,
workingDirectory: projectDir.path,
environment: <String, String>{'FLUTTER_TEST': 'true'}
);
}
Future<void> addBreakpoint(String path, int line) async {
final VM vm = await vmService.getVM();
final VMIsolate isolate = await vm.isolates.first.load();
await isolate.addBreakpoint(path, line);
}
Future<VMIsolate> waitForBreakpointHit() async {
final VM vm = await vmService.getVM();
final VMIsolate isolate = await vm.isolates.first.load();
await _withTimeout<void>(
isolate.waitUntilPaused(),
() => 'Isolate did not pause'
);
return isolate.load();
}
Future<VMIsolate> breakAt(String path, int line) async {
await addBreakpoint(path, line);
await hotReload();
return waitForBreakpointHit();
}
Future<VMInstanceRef> evaluateExpression(String expression) async {
final VM vm = await vmService.getVM();
final VMIsolate isolate = await vm.isolates.first.load();
final VMStack stack = await isolate.getStack();
if (stack.frames.isEmpty) {
throw new Exception('Stack is empty; unable to evaluate expression');
}
final VMFrame topFrame = stack.frames.first;
return _withTimeout(
topFrame.evaluate(expression),
() => 'Timed out evaluating expression'
);
}
Future<Map<String, dynamic>> _waitFor({String event, int id}) async {
// Capture output to a buffer so if we don't get the repsonse we want we can show
// the output that did arrive in the timeout errr.
final StringBuffer messages = new StringBuffer();
_stdout.stream.listen(messages.writeln);
_stderr.stream.listen(messages.writeln);
final Completer<Map<String, dynamic>> response = new Completer<Map<String, dynamic>>();
final StreamSubscription<String> sub = _stdout.stream.listen((String line) {
final dynamic json = _parseFlutterResponse(line);
if (json == null) {
return;
} else if (
(event != null && json['event'] == event)
|| (id != null && json['id'] == id)) {
response.complete(json);
}
});
return _withTimeout(
response.future,
() {
if (event != null)
return 'Did not receive expected $event event.\nDid get:\n${messages.toString()}';
else if (id != null)
return 'Did not receive response to request "$id".\nDid get:\n${messages.toString()}';
}
).whenComplete(() => sub.cancel());
}
Map<String, dynamic> _parseFlutterResponse(String line) {
if (line.startsWith('[') && line.endsWith(']')) {
try {
return json.decode(line)[0];
} catch (e) {
// Not valid JSON, so likely some other output that was surrounded by [brackets]
return null;
}
}
return null;
}
int id = 1;
Future<dynamic> _sendRequest(String method, dynamic params) async {
final int requestId = id++;
final Map<String, dynamic> req = <String, dynamic>{
'id': requestId,
'method': method,
'params': params
};
final String jsonEncoded = json.encode(<Map<String, dynamic>>[req]);
if (_printJsonAndStderr) {
print(jsonEncoded);
}
// Set up the response future before we send the request to avoid any
// races.
final Future<Map<String, dynamic>> responseFuture = _waitFor(id: requestId);
_proc.stdin.writeln(jsonEncoded);
final Map<String, dynamic> resp = await responseFuture;
if (resp['error'] != null || resp['result'] == null)
throw 'Unexpected error response: ${resp['error']}\n\n${_errorBuffer.toString()}';
return resp['result'];
}
}
Future<T> _withTimeout<T>(Future<T> f, [
String Function() getDebugMessage,
int timeoutSeconds = 20,
]) {
final Future<T> timeout =
new Future<T>.delayed(new Duration(seconds: timeoutSeconds))
.then((Object _) => throw new Exception(getDebugMessage()));
return Future.any(<Future<T>>[f, timeout]);
}
Stream<String> _transformToLines(Stream<List<int>> byteStream) {
return byteStream.transform(utf8.decoder).transform(const LineSplitter());
}
......@@ -3,19 +3,16 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/process_manager.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/tester/flutter_tester.dart';
import 'package:test/test.dart';
import '../src/common.dart';
import '../src/context.dart';
import 'util.dart';
void main() {
Directory tempDir;
......@@ -52,11 +49,11 @@ void main() {
}
testUsingContext('start', () async {
_writePubspec();
_writePackages();
writePubspec(tempDir.path);
writePackages(tempDir.path);
final String mainPath = fs.path.join('lib', 'main.dart');
_writeFile(mainPath, r'''
writeFile(mainPath, r'''
import 'dart:async';
void main() {
new Timer.periodic(const Duration(milliseconds: 1), (Timer timer) {
......@@ -76,12 +73,12 @@ void main() {
});
testUsingContext('keeps running', () async {
_writePubspec();
_writePackages();
await _getPackages();
writePubspec(tempDir.path);
writePackages(tempDir.path);
await getPackages(tempDir.path);
final String mainPath = fs.path.join('lib', 'main.dart');
_writeFile(mainPath, r'''
writeFile(mainPath, r'''
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
......@@ -109,38 +106,3 @@ class MyApp extends StatelessWidget {
});
});
}
void _writeFile(String path, String content) {
fs.file(path)
..createSync(recursive: true)
..writeAsStringSync(content);
}
void _writePackages() {
_writeFile('.packages', '''
test:${fs.path.join(fs.currentDirectory.path, 'lib')}/
''');
}
void _writePubspec() {
_writeFile('pubspec.yaml', '''
name: test
dependencies:
flutter:
sdk: flutter
''');
}
Future<void> _getPackages() async {
final List<String> command = <String>[
fs.path.join(getFlutterRoot(), 'bin', 'flutter'),
'packages',
'get'
];
final Process process = await processManager.start(command);
final StringBuffer errorOutput = new StringBuffer();
process.stderr.transform(utf8.decoder).listen(errorOutput.write);
final int exitCode = await process.exitCode;
if (exitCode != 0)
throw new Exception('flutter packages get failed: ${errorOutput.toString()}');
}
// 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 'dart:async';
import 'dart:convert';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/process_manager.dart';
import '../src/common.dart';
void writeFile(String path, String content) {
fs.file(path)
..createSync(recursive: true)
..writeAsStringSync(content);
}
void writePackages(String folder) {
writeFile(fs.path.join(folder, '.packages'), '''
test:${fs.path.join(fs.currentDirectory.path, 'lib')}/
''');
}
void writePubspec(String folder) {
writeFile(fs.path.join(folder, 'pubspec.yaml'), '''
name: test
dependencies:
flutter:
sdk: flutter
''');
}
Future<void> getPackages(String folder) async {
final List<String> command = <String>[
fs.path.join(getFlutterRoot(), 'bin', 'flutter'),
'packages',
'get'
];
final Process process = await processManager.start(command, workingDirectory: folder);
final StringBuffer errorOutput = new StringBuffer();
process.stderr.transform(utf8.decoder).listen(errorOutput.write);
final int exitCode = await process.exitCode;
if (exitCode != 0)
throw new Exception(
'flutter packages get failed: ${errorOutput.toString()}');
}
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