Unverified Commit b69b2a8c authored by Anna Gringauze's avatar Anna Gringauze Committed by GitHub

Convert expression evaluation exceptions to errors (#54916)

parent bec63a54
...@@ -77,7 +77,8 @@ class WebExpressionCompiler implements ExpressionCompiler { ...@@ -77,7 +77,8 @@ class WebExpressionCompiler implements ExpressionCompiler {
content, compilerOutput.errorCount > 0); content, compilerOutput.errorCount > 0);
} }
throw Exception('Failed to compile $expression'); return ExpressionCompilationResult(
'InternalError: frontend server failed to compile \'$expression\'', true);
} }
} }
......
...@@ -856,7 +856,7 @@ class DefaultResidentCompiler implements ResidentCompiler { ...@@ -856,7 +856,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
} }
Future<CompilerOutput> _compileExpressionToJs(_CompileExpressionToJsRequest request) async { Future<CompilerOutput> _compileExpressionToJs(_CompileExpressionToJsRequest request) async {
_stdoutHandler.reset(suppressCompilerMessages: true, expectSources: false); _stdoutHandler.reset(suppressCompilerMessages: false, expectSources: false);
// 'compile-expression-to-js' should be invoked after compiler has been started, // 'compile-expression-to-js' should be invoked after compiler has been started,
// program was compiled. // program was compiled.
......
...@@ -17,6 +17,7 @@ import 'base/file_system.dart'; ...@@ -17,6 +17,7 @@ import 'base/file_system.dart';
import 'base/io.dart' as io; import 'base/io.dart' as io;
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/signals.dart'; import 'base/signals.dart';
import 'base/terminal.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_info.dart'; import 'build_info.dart';
import 'codegen.dart'; import 'codegen.dart';
...@@ -92,6 +93,9 @@ class FlutterDevice { ...@@ -92,6 +93,9 @@ class FlutterDevice {
// Override the filesystem scheme so that the frontend_server can find // Override the filesystem scheme so that the frontend_server can find
// the generated entrypoint code. // the generated entrypoint code.
fileSystemScheme: 'org-dartlang-app', fileSystemScheme: 'org-dartlang-app',
compilerMessageConsumer:
(String message, {bool emphasis, TerminalColor color, }) =>
globals.printTrace(message),
initializeFromDill: globals.fs.path.join(getBuildDirectory(), 'cache.dill'), initializeFromDill: globals.fs.path.join(getBuildDirectory(), 'cache.dill'),
targetModel: TargetModel.dartdevc, targetModel: TargetModel.dartdevc,
experimentalFlags: experimentalFlags, experimentalFlags: experimentalFlags,
......
// 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.
import 'package:dwds/dwds.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/build_runner/devfs_web.dart';
import 'package:matcher/matcher.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/testbed.dart';
void main() {
Testbed testbed;
setUp(() {
testbed = Testbed();
});
test('WebExpressionCompiler handles successful expression compilation', () => testbed.run(() async {
globals.fs.file('compilerOutput').writeAsStringSync('a');
final ResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.compileExpressionToJs(
any, any, any, any, any, any, any
)).thenAnswer((Invocation invocation) async {
return const CompilerOutput('compilerOutput', 0, <Uri>[]);
});
final ExpressionCompiler expressionCompiler =
WebExpressionCompiler(residentCompiler);
final ExpressionCompilationResult result =
await expressionCompiler.compileExpressionToJs(
null, null, 1, 1, null, null, null, null);
expectResult(result, false, 'a');
}));
test('WebExpressionCompiler handles compilation error', () => testbed.run(() async {
globals.fs.file('compilerOutput').writeAsStringSync('Error: a');
final ResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.compileExpressionToJs(
any, any, any, any, any, any, any
)).thenAnswer((Invocation invocation) async {
return const CompilerOutput('compilerOutput', 1, <Uri>[]);
});
final ExpressionCompiler expressionCompiler =
WebExpressionCompiler(residentCompiler);
final ExpressionCompilationResult result =
await expressionCompiler.compileExpressionToJs(
null, null, 1, 1, null, null, null, null);
expectResult(result, true, 'Error: a');
}));
test('WebExpressionCompiler handles internal error', () => testbed.run(() async {
final ResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.compileExpressionToJs(
any, any, any, any, any, any, any
)).thenAnswer((Invocation invocation) async {
return null;
});
final ExpressionCompiler expressionCompiler =
WebExpressionCompiler(residentCompiler);
final ExpressionCompilationResult result =
await expressionCompiler.compileExpressionToJs(
null, null, 1, 1, null, null, null, 'a');
expectResult(result, true, 'InternalError: frontend server failed to compile \'a\'');
}));
}
void expectResult(ExpressionCompilationResult result, bool isError, String value) {
expect(result,
const TypeMatcher<ExpressionCompilationResult>()
.having((ExpressionCompilationResult instance) => instance.isError, 'isError', isError)
.having((ExpressionCompilationResult instance) =>instance.result, 'result', value));
}
class MockResidentCompiler extends Mock implements ResidentCompiler {}
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:matcher/matcher.dart';
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
...@@ -146,32 +147,51 @@ void batch2() { ...@@ -146,32 +147,51 @@ void batch2() {
} }
Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async { Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
InstanceRef res; ObjRef res;
res = await flutter.evaluateInFrame('"test"'); res = await flutter.evaluateInFrame('"test"');
expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue); expectValueOfType(res, InstanceKind.kString, 'test');
res = await flutter.evaluateInFrame('1'); res = await flutter.evaluateInFrame('1');
expect(res.kind == InstanceKind.kInt && res.valueAsString == 1.toString(), isTrue); expectValueOfType(res, InstanceKind.kInt, 1.toString());
res = await flutter.evaluateInFrame('true'); res = await flutter.evaluateInFrame('true');
expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue); expectValueOfType(res, InstanceKind.kBool, true.toString());
} }
Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async { Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async {
final InstanceRef res = await flutter.evaluateInFrame('new DateTime.now().year'); final ObjRef res = await flutter.evaluateInFrame('new DateTime.now().year');
expect(res.kind == InstanceKind.kInt && res.valueAsString == DateTime.now().year.toString(), isTrue); expectValueOfType(res, InstanceKind.kInt, DateTime.now().year.toString());
} }
Future<void> evaluateComplexReturningExpressions(FlutterTestDriver flutter) async { Future<void> evaluateComplexReturningExpressions(FlutterTestDriver flutter) async {
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
final InstanceRef resp = await flutter.evaluateInFrame('new DateTime.now()'); final ObjRef resp = await flutter.evaluateInFrame('new DateTime.now()');
expect(resp.classRef.name, equals('DateTime')); expectInstanceOfClass(resp, 'DateTime');
// Ensure we got a reasonable approximation. The more accurate we try to // 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 // 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. // 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"'); final InstanceRef res = await flutter.evaluate(resp.id, r'"$year-$month-$day"');
expect(res.valueAsString, equals('${now.year}-${now.month}-${now.day}')); expectValue(res, '${now.year}-${now.month}-${now.day}');
}
void expectInstanceOfClass(ObjRef result, String name) {
expect(result,
const TypeMatcher<InstanceRef>()
.having((InstanceRef instance) => instance.classRef.name, 'resp.classRef.name', name));
}
void expectValueOfType(ObjRef result, String kind, String message) {
expect(result,
const TypeMatcher<InstanceRef>()
.having((InstanceRef instance) => instance.kind, 'kind', kind)
.having((InstanceRef instance) => instance.valueAsString, 'valueAsString', message));
}
void expectValue(ObjRef result, String message) {
expect(result,
const TypeMatcher<InstanceRef>()
.having((InstanceRef instance) => instance.valueAsString, 'valueAsString', message));
} }
void main() { void main() {
......
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:matcher/matcher.dart';
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
...@@ -46,6 +47,14 @@ void batch1() { ...@@ -46,6 +47,14 @@ void batch1() {
); );
} }
test('flutter run expression evaluation - can handle compilation errors', () async {
await initProject();
await _flutter.run(withDebugger: true, chrome: true);
await breakInTopLevelFunction(_flutter);
await evaluateErrorExpressions(_flutter);
await cleanProject();
}, skip: 'CI not setup for web tests'); // https://github.com/flutter/flutter/issues/53779
test('flutter run expression evaluation - can evaluate trivial expressions in top level function', () async { test('flutter run expression evaluation - can evaluate trivial expressions in top level function', () async {
await initProject(); await initProject();
await _flutter.run(withDebugger: true, chrome: true); await _flutter.run(withDebugger: true, chrome: true);
...@@ -77,22 +86,6 @@ void batch1() { ...@@ -77,22 +86,6 @@ void batch1() {
await evaluateComplexExpressions(_flutter); await evaluateComplexExpressions(_flutter);
await cleanProject(); await cleanProject();
}, skip: 'CI not setup for web tests'); // https://github.com/flutter/flutter/issues/53779 }, skip: 'CI not setup for web tests'); // https://github.com/flutter/flutter/issues/53779
test('flutter run expression evaluation - can evaluate expressions returning complex objects in top level function', () async {
await initProject();
await _flutter.run(withDebugger: true, chrome: true);
await breakInTopLevelFunction(_flutter);
await evaluateComplexReturningExpressions(_flutter);
await cleanProject();
}, skip: 'Evaluate on objects is not supported for web yet');
test('flutter run expression evaluation - can evaluate expressions returning complex objects in build method', () async {
await initProject();
await _flutter.run(withDebugger: true, chrome: true);
await breakInBuildMethod(_flutter);
await evaluateComplexReturningExpressions(_flutter);
await cleanProject();
}, skip: 'Evaluate on objects is not supported for web yet');
} }
void batch2() { void batch2() {
...@@ -133,43 +126,43 @@ void batch2() { ...@@ -133,43 +126,43 @@ void batch2() {
await evaluateComplexExpressions(_flutter); await evaluateComplexExpressions(_flutter);
await cleanProject(); await cleanProject();
}, skip: 'CI not setup for web tests'); // https://github.com/flutter/flutter/issues/53779 }, skip: 'CI not setup for web tests'); // https://github.com/flutter/flutter/issues/53779
}
test('flutter test expression evaluation - can evaluate expressions returning complex objects in a test', () async {
await initProject(); Future<void> evaluateErrorExpressions(FlutterTestDriver flutter) async {
await _flutter.run(withDebugger: true, chrome: true, script: _project.testFilePath); final ObjRef res = await flutter.evaluateInFrame('typo');
await breakInMethod(_flutter); expectError(res, 'CompilationError: Getter not found: \'typo\'.\ntypo\n^^^^');
await evaluateComplexReturningExpressions(_flutter);
await cleanProject();
}, skip: 'Evaluate on objects is not supported for web yet');
} }
Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async { Future<void> evaluateTrivialExpressions(FlutterTestDriver flutter) async {
InstanceRef res; ObjRef res;
res = await flutter.evaluateInFrame('"test"'); res = await flutter.evaluateInFrame('"test"');
expect(res.kind == InstanceKind.kString && res.valueAsString == 'test', isTrue); expectInstance(res, InstanceKind.kString, 'test');
res = await flutter.evaluateInFrame('1'); res = await flutter.evaluateInFrame('1');
expect(res.kind == InstanceKind.kDouble && res.valueAsString == 1.toString(), isTrue); expectInstance(res, InstanceKind.kDouble, 1.toString());
res = await flutter.evaluateInFrame('true'); res = await flutter.evaluateInFrame('true');
expect(res.kind == InstanceKind.kBool && res.valueAsString == true.toString(), isTrue); expectInstance(res, InstanceKind.kBool, true.toString());
} }
Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async { Future<void> evaluateComplexExpressions(FlutterTestDriver flutter) async {
final InstanceRef res = await flutter.evaluateInFrame('new DateTime.now().year'); final ObjRef res = await flutter.evaluateInFrame('new DateTime.now().year');
expect(res.kind == InstanceKind.kDouble && res.valueAsString == DateTime.now().year.toString(), isTrue); expectInstance(res, InstanceKind.kDouble, DateTime.now().year.toString());
}
void expectInstance(ObjRef result, String kind, String message) {
expect(result,
const TypeMatcher<InstanceRef>()
.having((InstanceRef instance) => instance.kind, 'kind', kind)
.having((InstanceRef instance) => instance.valueAsString, 'valueAsString', message));
} }
Future<void> evaluateComplexReturningExpressions(FlutterTestDriver flutter) async { void expectError(ObjRef result, String message) {
final DateTime now = DateTime.now(); expect(result,
final InstanceRef resp = await flutter.evaluateInFrame('new DateTime.now()'); const TypeMatcher<ErrorRef>()
expect(resp.classRef.name, equals('DateTime')); .having((ErrorRef instance) => instance.message, 'message', message));
// 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}'));
} }
void main() { void main() {
......
...@@ -291,9 +291,9 @@ abstract class FlutterTestDriver { ...@@ -291,9 +291,9 @@ abstract class FlutterTestDriver {
return waitForNextPause ? waitForPause() : null; return waitForNextPause ? waitForPause() : null;
} }
Future<InstanceRef> evaluateInFrame(String expression) async { Future<ObjRef> evaluateInFrame(String expression) async {
return _timeoutWithMessages<InstanceRef>( return _timeoutWithMessages<ObjRef>(
() async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression) as InstanceRef, () async => await _vmService.evaluateInFrame(await _getFlutterIsolateId(), 0, expression) as ObjRef,
task: 'Evaluating expression ($expression)', task: 'Evaluating expression ($expression)',
); );
} }
......
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