| // 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:ui'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'rendering_tester.dart'; |
| |
| void main() { |
| // This test has to be kept separate from object_test.dart because the way |
| // the rendering_test.dart dependency of this test uses the bindings in not |
| // compatible with existing tests in object_test.dart. |
| test('reentrant paint error', () { |
| FlutterErrorDetails errorDetails; |
| final RenderBox root = TestReentrantPaintingErrorRenderBox(); |
| layout(root, onErrors: () { |
| errorDetails = renderer.takeFlutterErrorDetails(); |
| }); |
| pumpFrame(phase: EnginePhase.paint); |
| |
| expect(errorDetails, isNotNull); |
| expect(errorDetails.stack, isNotNull); |
| // Check the ErrorDetails without the stack trace |
| final List<String> lines = errorDetails.toString().split('\n'); |
| // The lines in the middle of the error message contain the stack trace |
| // which will change depending on where the test is run. |
| expect(lines.length, greaterThan(12)); |
| expect( |
| lines.take(12).join('\n'), |
| equalsIgnoringHashCodes( |
| '══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞══════════════════════\n' |
| 'The following assertion was thrown during paint():\n' |
| 'Tried to paint a RenderObject reentrantly.\n' |
| 'The following RenderObject was already being painted when it was painted again:\n' |
| ' TestReentrantPaintingErrorRenderBox#00000:\n' |
| ' parentData: <none>\n' |
| ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
| ' size: Size(100.0, 100.0)\n' |
| 'Since this typically indicates an infinite recursion, it is\n' |
| 'disallowed.\n' |
| '\n' |
| 'When the exception was thrown, this was the stack:' |
| ), |
| ); |
| |
| expect( |
| lines.getRange(lines.length - 8, lines.length).join('\n'), |
| equalsIgnoringHashCodes( |
| 'The following RenderObject was being processed when the exception was fired:\n' |
| ' TestReentrantPaintingErrorRenderBox#00000:\n' |
| ' parentData: <none>\n' |
| ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
| ' size: Size(100.0, 100.0)\n' |
| 'This RenderObject has no descendants.\n' |
| '═════════════════════════════════════════════════════════════════\n' |
| ), |
| ); |
| }); |
| |
| test('needsCompositingBitsUpdate paint error', () { |
| FlutterError flutterError; |
| final RenderBox root = RenderRepaintBoundary(child: RenderSizedBox(const Size(100, 100))); |
| try { |
| layout(root); |
| PaintingContext.repaintCompositedChild(root, debugAlsoPaintedParent: true); |
| } on FlutterError catch (exception) { |
| flutterError = exception; |
| } |
| |
| expect(flutterError, isNotNull); |
| // The lines in the middle of the error message contain the stack trace |
| // which will change depending on where the test is run. |
| expect( |
| flutterError.toStringDeep(), |
| equalsIgnoringHashCodes( |
| 'FlutterError\n' |
| ' Tried to paint a RenderObject before its compositing bits were\n' |
| ' updated.\n' |
| ' The following RenderObject was marked as having dirty compositing bits at the time that it was painted:\n' |
| ' RenderRepaintBoundary#00000 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:\n' |
| ' needs compositing\n' |
| ' parentData: <none>\n' |
| ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
| ' layer: OffsetLayer#00000 DETACHED\n' |
| ' size: Size(800.0, 600.0)\n' |
| ' metrics: 0.0% useful (1 bad vs 0 good)\n' |
| ' diagnosis: insufficient data to draw conclusion (less than five\n' |
| ' repaints)\n' |
| ' A RenderObject that still has dirty compositing bits cannot be\n' |
| ' painted because this indicates that the tree has not yet been\n' |
| ' properly configured for creating the layer tree.\n' |
| ' This usually indicates an error in the Flutter framework itself.\n' |
| ), |
| ); |
| expect( |
| flutterError.diagnostics.singleWhere((DiagnosticsNode node) => node.level == DiagnosticLevel.hint).toString(), |
| 'This usually indicates an error in the Flutter framework itself.', |
| ); |
| }); |
| } |
| |
| class TestReentrantPaintingErrorRenderBox extends RenderBox { |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| // Cause a reentrant painting bug that would show up as a stack overflow if |
| // it was not for debugging checks in RenderObject. |
| context.paintChild(this, offset); |
| } |
| |
| @override |
| void performLayout() { |
| size = const Size(100, 100); |
| } |
| } |