From 7a10b46ee0c821996e59f477f277c64dcdc96d4a Mon Sep 17 00:00:00 2001 From: Yegor Date: Mon, 26 Oct 2020 15:57:02 -0700 Subject: [PATCH] Print errors in all build modes (#69046) --- dev/bots/test.dart | 11 ++- .../web/lib/framework_stack_trace.dart | 96 +++++++++++++++++++ .../lib/src/foundation/assertions.dart | 28 ++++-- 3 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 dev/integration_tests/web/lib/framework_stack_trace.dart diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 398edde7541..0649506e160 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -776,9 +776,12 @@ Future _runWebUnitTests() async { } Future _runWebIntegrationTests() async { - await _runWebStackTraceTest('profile'); - await _runWebStackTraceTest('release'); + await _runWebStackTraceTest('profile', 'lib/stack_trace.dart'); + await _runWebStackTraceTest('release', 'lib/stack_trace.dart'); + await _runWebStackTraceTest('profile', 'lib/framework_stack_trace.dart'); + await _runWebStackTraceTest('release', 'lib/framework_stack_trace.dart'); await _runWebDebugTest('lib/stack_trace.dart'); + await _runWebDebugTest('lib/framework_stack_trace.dart'); await _runWebDebugTest('lib/web_directory_loading.dart'); await _runWebDebugTest('test/test.dart'); await _runWebDebugTest('lib/null_assert_main.dart', enableNullSafety: true); @@ -807,7 +810,7 @@ Future _runWebIntegrationTests() async { ]); } -Future _runWebStackTraceTest(String buildMode) async { +Future _runWebStackTraceTest(String buildMode, String entrypoint) async { final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web'); final String appBuildDirectory = path.join(testAppDirectory, 'build', 'web'); @@ -824,7 +827,7 @@ Future _runWebStackTraceTest(String buildMode) async { 'web', '--$buildMode', '-t', - 'lib/stack_trace.dart', + entrypoint, ], workingDirectory: testAppDirectory, environment: { diff --git a/dev/integration_tests/web/lib/framework_stack_trace.dart b/dev/integration_tests/web/lib/framework_stack_trace.dart new file mode 100644 index 00000000000..76204098108 --- /dev/null +++ b/dev/integration_tests/web/lib/framework_stack_trace.dart @@ -0,0 +1,96 @@ +// 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 'dart:html' as html; + +import 'package:meta/dart2js.dart'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +// Tests that the framework prints stack traces in all build modes. +// +// Regression test for https://github.com/flutter/flutter/issues/68616. +// +// See also `dev/integration_tests/web/lib/stack_trace.dart` that tests the +// framework's ability to parse stack traces in all build modes. +void main() async { + final StringBuffer errorMessage = StringBuffer(); + debugPrint = (String message, { int wrapWidth }) { + errorMessage.writeln(message); + }; + + runApp(ThrowingWidget()); + + // Let the framework flush error messages. + await Future.delayed(Duration.zero); + + final StringBuffer output = StringBuffer(); + if (_errorMessageFormattedCorrectly(errorMessage.toString())) { + output.writeln('--- TEST SUCCEEDED ---'); + } else { + output.writeln('--- UNEXPECTED ERROR MESSAGE FORMAT ---'); + output.writeln(errorMessage); + output.writeln('--- TEST FAILED ---'); + } + + print(output); + html.HttpRequest.request( + '/test-result', + method: 'POST', + sendData: '$output', + ); +} + +bool _errorMessageFormattedCorrectly(String errorMessage) { + if (!errorMessage.contains('Test error message')) { + return false; + } + + // In release mode symbols are minified. No sense testing the contents of the stack trace. + if (kReleaseMode) { + return true; + } + + const List expectedFunctions = [ + 'topLevelFunction', + 'secondLevelFunction', + 'thirdLevelFunction', + ]; + + return expectedFunctions.every(errorMessage.contains); +} + +class ThrowingWidget extends StatefulWidget { + @override + _ThrowingWidgetState createState() => _ThrowingWidgetState(); +} + +class _ThrowingWidgetState extends State { + @override + void initState() { + super.initState(); + topLevelFunction(); + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +@noInline +void topLevelFunction() { + secondLevelFunction(); +} + +@noInline +void secondLevelFunction() { + thirdLevelFunction(); +} + +@noInline +void thirdLevelFunction() { + throw Exception('Test error message'); +} diff --git a/packages/flutter/lib/src/foundation/assertions.dart b/packages/flutter/lib/src/foundation/assertions.dart index e621f77f0b1..d9330d83cb8 100644 --- a/packages/flutter/lib/src/foundation/assertions.dart +++ b/packages/flutter/lib/src/foundation/assertions.dart @@ -944,22 +944,32 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti static void dumpErrorToConsole(FlutterErrorDetails details, { bool forceReport = false }) { assert(details != null); assert(details.exception != null); - bool reportError = details.silent != true; // could be null + bool isInDebugMode = false; assert(() { // In checked mode, we ignore the "silent" flag. - reportError = true; + isInDebugMode = true; return true; }()); + final bool reportError = isInDebugMode || details.silent != true; // could be null if (!reportError && !forceReport) return; if (_errorCount == 0 || forceReport) { - debugPrint( - TextTreeRenderer( - wrapWidth: wrapWidth, - wrapWidthProperties: wrapWidth, - maxDescendentsTruncatableNode: 5, - ).render(details.toDiagnosticsNode(style: DiagnosticsTreeStyle.error)).trimRight(), - ); + // Diagnostics is only available in debug mode. In profile and release modes fallback to plain print. + if (isInDebugMode) { + debugPrint( + TextTreeRenderer( + wrapWidth: wrapWidth, + wrapWidthProperties: wrapWidth, + maxDescendentsTruncatableNode: 5, + ).render(details.toDiagnosticsNode(style: DiagnosticsTreeStyle.error)).trimRight(), + ); + } else { + debugPrintStack( + stackTrace: details.stack, + label: details.exception.toString(), + maxFrames: 100, + ); + } } else { debugPrint('Another exception was thrown: ${details.summary}'); }