Skip to content

Mutating Paint breaks paints #171194

@justinmc

Description

@justinmc

Steps to reproduce

Mutating an instance of Paint will affect the saved calls that are used when matching with paints in a test.

Run the test case given below, which does this:

  1. Draws something to the canvas with a Paint instance.
  2. Later, modifies that Paint instance.
  3. Test that the drawing happened with the unmodified Paint.

Expected results

The test passes. paints sees the correct Paint that was used at the time of the draw.

Actual results

The test fails. paints sees the modified Paint.

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('paints.circle is not affected by mutated colors', (
    WidgetTester tester,
  ) async {
    final Key customPaintKey = UniqueKey();
    Widget buildApp({required bool enabled}) {
      return MaterialApp(
        home: Scaffold(
          body: Center(child: CustomPaint(key: customPaintKey, painter: _MutantPainter())),
        ),
      );
    }

    await tester.pumpWidget(buildApp(enabled: true));

    // Selected and enabled.
    await tester.pumpAndSettle();
    expect(
      Material.of(tester.element(find.byKey(customPaintKey))),
      paints..circle(color: Colors.red),
    );
  });
}

class _MutantPainter extends ChangeNotifier implements CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()..color = Colors.red;

    canvas.drawCircle(Offset.zero, 10.0, paint);

    // Mutate paint after drawing.
    paint.color = Colors.blue;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;

  @override
  bool? hitTest(Offset position) => null;

  @override
  SemanticsBuilderCallback? get semanticsBuilder => null;

  @override
  bool shouldRebuildSemantics(covariant CustomPainter oldDelegate) => true;
}

Screenshots or Video

No response

Logs

Test output
✗ flutter test /usr/local/google/home/jmccandless/Projects/flutter/packages/flutter_test/test/recording_canvas.dart
Resolving dependencies... 
Downloading packages... 
  _fe_analyzer_shared 82.0.0 (84.0.0 available)
  adaptive_breakpoints 0.1.7 (discontinued)
  analyzer 7.4.5 (7.5.0 available)
  archive 3.6.1 (4.0.7 available)
  device_info 2.0.3 (discontinued replaced by device_info_plus)
  ffigen 18.1.0 (19.0.0 available)
  google_mobile_ads 5.1.0 (6.0.0 available)
  googleapis 12.0.0 (14.0.0 available)
  googleapis_auth 1.6.0 (2.0.0 available)
  isolate 2.1.1 (discontinued)
  js 0.7.2 (discontinued)
  material_color_utilities 0.11.1 (0.13.0 available)
  meta 1.16.0 (1.17.0 available)
  metrics_center 1.0.13 (1.0.14 available)
  pedantic 1.11.1 (discontinued replaced by lints)
  petitparser 6.1.0 (7.0.0 available)
  shelf_web_socket 2.0.1 (3.0.0 available)
  webview_flutter 4.9.0 (4.13.0 available)
  webview_flutter_android 3.16.9 (4.7.0 available)
  xml 6.5.0 (6.6.0 available)
Got dependencies!
5 packages are discontinued.
15 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
00:02 +0: RadioListTile respects fillColor in enabled/disabled states                                                                                                                                                                                        
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: Object or closure painting:
          'a circle with MaterialColor(primary value: Color(alpha: 1.0000, red: 0.9569, green:
0.2627, blue: 0.2118, colorSpace: ColorSpace.sRGB))'
  Actual: _RenderInkFeatures:<_RenderInkFeatures#f350f>
   Which: threw the following exception:
          It called drawCircle with a paint whose color, Color(alpha: 1.0000, red: 0.1294, green:
0.5882, blue: 0.9529, colorSpace: ColorSpace.sRGB), was not exactly the expected color
(MaterialColor(primary value: Color(alpha: 1.0000, red: 0.9569, green: 0.2627, blue: 0.2118,
colorSpace: ColorSpace.sRGB))).
          #0      _DrawCommandPaintPredicate.verifyArguments
(package:flutter_test/src/mock_canvas.dart:1267:7)
          #1      _CirclePaintPredicate.verifyArguments
(package:flutter_test/src/mock_canvas.dart:1544:11)
          #2      _DrawCommandPaintPredicate.match
(package:flutter_test/src/mock_canvas.dart:1258:5)
          #3      _TestRecordingCanvasPatternMatcher._evaluatePredicates
(package:flutter_test/src/mock_canvas.dart:1170:27)
          #4      _TestRecordingCanvasMatcher.matches
(package:flutter_test/src/mock_canvas.dart:686:16)
          #5      _expect (package:matcher/src/expect/expect.dart:138:30)
          #6      expect (package:matcher/src/expect/expect.dart:56:3)
          #7      expect (package:flutter_test/src/widget_tester.dart:474:18)
          #8      main.<anonymous closure>
(file:///usr/local/google/home/jmccandless/Projects/flutter/packages/flutter_test/test/recording_canvas.dart:25:5)
          <asynchronous suspension>
          #9      testWidgets.<anonymous closure>.<anonymous closure>
(package:flutter_test/src/widget_tester.dart:193:15)
          <asynchronous suspension>
          #10     TestWidgetsFlutterBinding._runTestBody
(package:flutter_test/src/binding.dart:1064:5)
          <asynchronous suspension>
          #11     StackZoneSpecification._registerCallback.<anonymous closure>
(package:stack_trace/src/stack_zone_specification.dart:114:42)
          <asynchronous suspension>
          The complete display list was:
            * save()
            * translate(400.0, 300.0)
            * drawCircle(Offset(0.0, 0.0), 10.0, Paint(Color(alpha: 1.0000, red: 0.1294, green:
0.5882, blue: 0.9529, colorSpace: ColorSpace.sRGB)))
            * restore()

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (file:///usr/local/google/home/jmccandless/Projects/flutter/packages/flutter_test/test/recording_canvas.dart:25:5)
<asynchronous suspension>
#5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:193:15)
<asynchronous suspension>
#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:1064:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

This was caught by the test expectation on the following line:
  file:///usr/local/google/home/jmccandless/Projects/flutter/packages/flutter_test/test/recording_canvas.dart line 25
The test description was:
  RadioListTile respects fillColor in enabled/disabled states
════════════════════════════════════════════════════════════════════════════════════════════════════
00:02 +0 -1: RadioListTile respects fillColor in enabled/disabled states [E]                                                                                                                                                                                 
  Test failed. See exception logs above.
  The test description was: RadioListTile respects fillColor in enabled/disabled states
  

To run this test again: /usr/local/google/home/jmccandless/Projects/flutter/bin/cache/dart-sdk/bin/dart test /usr/local/google/home/jmccandless/Projects/flutter/packages/flutter_test/test/recording_canvas.dart -p vm --plain-name 'RadioListTile respects fillColor in enabled/disabled states'
00:02 +0 -1: Some tests failed.   

Flutter Doctor output

Doctor output
✗ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel [user-branch], 3.33.0-1.0.pre.690, on Debian GNU/Linux rodete 6.12.20-1rodete1-amd64, locale en_US.UTF-8)
    ! Flutter version 3.33.0-1.0.pre.690 on channel [user-branch] at /usr/local/google/home/jmccandless/Projects/flutter
      Currently on an unknown channel. Run `flutter channel` to switch to an official channel.
      If that doesn't fix the issue, reinstall Flutter by following instructions at https://flutter.dev/setup.
    ! Upstream repository [email protected]:justinmc/flutter.git is not a standard remote.
      Set environment variable "FLUTTER_GIT_URL" to [email protected]:justinmc/flutter.git to dismiss this error.
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2023.3)
[✓] VS Code (version 1.100.3)
[✓] Connected device (2 available)
[✓] Network resources

! Doctor found issues in 1 category.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions