1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'package:flutter/foundation.dart'; |
6 | import 'package:flutter/rendering.dart'; |
7 | import 'package:flutter_test/flutter_test.dart'; |
8 | |
9 | import 'rendering_tester.dart'; |
10 | |
11 | void main() { |
12 | TestRenderingFlutterBinding.ensureInitialized(); |
13 | |
14 | // This test has to be kept separate from object_test.dart because the way |
15 | // the rendering_test.dart dependency of this test uses the bindings in not |
16 | // compatible with existing tests in object_test.dart. |
17 | test('reentrant paint error' , () { |
18 | late FlutterErrorDetails errorDetails; |
19 | final RenderBox root = TestReentrantPaintingErrorRenderBox(); |
20 | layout(root, onErrors: () { |
21 | errorDetails = TestRenderingFlutterBinding.instance.takeFlutterErrorDetails()!; |
22 | }); |
23 | pumpFrame(phase: EnginePhase.paint); |
24 | |
25 | expect(errorDetails, isNotNull); |
26 | expect(errorDetails.stack, isNotNull); |
27 | // Check the ErrorDetails without the stack trace |
28 | final List<String> lines = errorDetails.toString().split('\n' ); |
29 | // The lines in the middle of the error message contain the stack trace |
30 | // which will change depending on where the test is run. |
31 | expect(lines.length, greaterThan(12)); |
32 | expect( |
33 | lines.take(12).join('\n' ), |
34 | equalsIgnoringHashCodes( |
35 | '══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞══════════════════════\n' |
36 | 'The following assertion was thrown during paint():\n' |
37 | 'Tried to paint a RenderObject reentrantly.\n' |
38 | 'The following RenderObject was already being painted when it was painted again:\n' |
39 | ' TestReentrantPaintingErrorRenderBox#00000:\n' |
40 | ' parentData: <none>\n' |
41 | ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
42 | ' size: Size(100.0, 100.0)\n' |
43 | 'Since this typically indicates an infinite recursion, it is\n' |
44 | 'disallowed.\n' |
45 | '\n' |
46 | 'When the exception was thrown, this was the stack:' , |
47 | ), |
48 | ); |
49 | |
50 | expect( |
51 | lines.getRange(lines.length - 8, lines.length).join('\n' ), |
52 | equalsIgnoringHashCodes( |
53 | 'The following RenderObject was being processed when the exception was fired:\n' |
54 | ' TestReentrantPaintingErrorRenderBox#00000:\n' |
55 | ' parentData: <none>\n' |
56 | ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
57 | ' size: Size(100.0, 100.0)\n' |
58 | 'This RenderObject has no descendants.\n' |
59 | '═════════════════════════════════════════════════════════════════\n' , |
60 | ), |
61 | ); |
62 | }); |
63 | |
64 | test('needsCompositingBitsUpdate paint error' , () { |
65 | late FlutterError flutterError; |
66 | final RenderBox root = RenderRepaintBoundary(child: RenderSizedBox(const Size(100, 100))); |
67 | try { |
68 | layout(root); |
69 | PaintingContext.repaintCompositedChild(root, debugAlsoPaintedParent: true); |
70 | } on FlutterError catch (exception) { |
71 | flutterError = exception; |
72 | } |
73 | |
74 | expect(flutterError, isNotNull); |
75 | // The lines in the middle of the error message contain the stack trace |
76 | // which will change depending on where the test is run. |
77 | expect( |
78 | flutterError.toStringDeep(), |
79 | equalsIgnoringHashCodes( |
80 | 'FlutterError\n' |
81 | ' Tried to paint a RenderObject before its compositing bits were\n' |
82 | ' updated.\n' |
83 | ' The following RenderObject was marked as having dirty compositing bits at the time that it was painted:\n' |
84 | ' RenderRepaintBoundary#00000 NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE:\n' |
85 | ' needs compositing\n' |
86 | ' parentData: <none>\n' |
87 | ' constraints: BoxConstraints(w=800.0, h=600.0)\n' |
88 | ' layer: OffsetLayer#00000 DETACHED\n' |
89 | ' size: Size(800.0, 600.0)\n' |
90 | ' metrics: 0.0% useful (1 bad vs 0 good)\n' |
91 | ' diagnosis: insufficient data to draw conclusion (less than five\n' |
92 | ' repaints)\n' |
93 | ' A RenderObject that still has dirty compositing bits cannot be\n' |
94 | ' painted because this indicates that the tree has not yet been\n' |
95 | ' properly configured for creating the layer tree.\n' |
96 | ' This usually indicates an error in the Flutter framework itself.\n' , |
97 | ), |
98 | ); |
99 | expect( |
100 | flutterError.diagnostics.singleWhere((DiagnosticsNode node) => node.level == DiagnosticLevel.hint).toString(), |
101 | 'This usually indicates an error in the Flutter framework itself.' , |
102 | ); |
103 | }); |
104 | } |
105 | |
106 | class TestReentrantPaintingErrorRenderBox extends RenderBox { |
107 | @override |
108 | void paint(PaintingContext context, Offset offset) { |
109 | // Cause a reentrant painting bug that would show up as a stack overflow if |
110 | // it was not for debugging checks in RenderObject. |
111 | context.paintChild(this, offset); |
112 | } |
113 | |
114 | @override |
115 | void performLayout() { |
116 | size = const Size(100, 100); |
117 | } |
118 | } |
119 | |