flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.66k stars 27.6k forks source link

Widget tests failing due to FormatException when using DefaultAssetBundle #126612

Closed eerive closed 1 year ago

eerive commented 1 year ago

Is there an existing issue for this?

Steps to reproduce

  1. Create a new Flutter project
  2. Add a resource that can be displayed in an Image widget (a random jpg/png inside /assets)
  3. Add the asset to pubspec
  4. In the main.dart, add an Image widget with the resource from step 2 to the Widget tree
  5. Create a widget test and pump a widget with a DefaultAssetBundle included, like:
    MaterialApp(
        home: Material(
          child: DefaultAssetBundle(
            bundle: TestAssetBundleByteData(),
            child: const MyHomePage(
              title: 'test',
            ),
          ),
        ),
      )
  6. Create a TestAssetBundleByteData to apply a bundle that should be used as a default, like:
    class TestAssetBundleByteData extends CachingAssetBundle {
    @override
    Future<ByteData> load(String key) async {
    ByteData bytes = ByteData(12);
    return bytes;
    }
    }
  7. Run the test

Expected results

The test should succeed without crashing.

Actual results

The test will fail with the following error message:

══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════
The following FormatException was thrown while resolving an image:
Message corrupted

When the exception was thrown, this was the stack:
#0      StandardMessageCodec.readValueOfType (package:flutter/src/services/message_codecs.dart:533:16)
#1      StandardMessageCodec.readValue (package:flutter/src/services/message_codecs.dart:478:12)
#2      StandardMessageCodec.decodeMessage (package:flutter/src/services/message_codecs.dart:342:28)
#3      new _AssetManifestBin.fromStandardMessageCodecMessage (package:flutter/src/services/asset_manifest.dart:57:55)
<asynchronous suspension>
<asynchronous suspension>
(elided 6 frames from dart:async and package:stack_trace)

Image provider: ScrollAwareImageProvider<Object>()
Image configuration: ImageConfiguration(bundle: TestAssetBundleLoadRes#99c4b(), devicePixelRatio:
  3.0, locale: en_US, textDirection: TextDirection.ltr, platform: android)
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: Load test asset bundle with a fallback image as resource

Code sample

https://github.com/eerive/flutter-test-asset

Screenshots or Video

Screenshots / Video demonstration~ \-

Logs

Logs ```console Connecting to VM Service at http://127.0.0.1:52356/BNrTT7JnnUg=/ws ══╡ EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE ╞════════════════════════════════════════════════════ The following FormatException was thrown while resolving an image: Message corrupted When the exception was thrown, this was the stack: #0 StandardMessageCodec.readValueOfType (package:flutter/src/services/message_codecs.dart:533:16) #1 StandardMessageCodec.readValue (package:flutter/src/services/message_codecs.dart:478:12) #2 StandardMessageCodec.decodeMessage (package:flutter/src/services/message_codecs.dart:342:28) #3 new _AssetManifestBin.fromStandardMessageCodecMessage (package:flutter/src/services/asset_manifest.dart:57:55) (elided 6 frames from dart:async and package:stack_trace) Image provider: ScrollAwareImageProvider() Image configuration: ImageConfiguration(bundle: TestAssetBundleLoadRes#99c4b(), devicePixelRatio: 3.0, locale: en_US, textDirection: TextDirection.ltr, platform: android) ════════════════════════════════════════════════════════════════════════════════════════════════════ Test failed. See exception logs above. The test description was: Load test asset bundle with a fallback image as resource ```

Flutter Doctor output

Doctor output ```console [✓] Flutter (Channel stable, 3.10.0, on macOS 13.3.1 22E261 darwin-arm64, locale en-DE) • Flutter version 3.10.0 on channel stable at /Users/mdlam/development/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 84a1e904f4 (2 days ago), 2023-05-09 07:41:44 -0700 • Engine revision d44b5a94c9 • Dart version 3.0.0 • DevTools version 2.23.1 [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1) • Android SDK at /Users/mdlam/Library/Android/sdk • Platform android-33, build-tools 33.0.1 • ANDROID_HOME = /Users/mdlam/Library/Android/sdk • Java binary at: /usr/bin/java • Java version OpenJDK Runtime Environment Temurin-20.0.1+9 (build 20.0.1+9) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 14.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 14B47b • CocoaPods version 1.12.1 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio • Android Studio at /Applications/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [!] Android Studio • Android Studio at /Users/mdlam/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/223.8836.35.2231.10023527/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [!] Android Studio • Android Studio at /Users/mdlam/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/223.8836.35.2231.9923731/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [✓] VS Code (version 1.78.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.64.0 [✓] VS Code (version 1.79.0-insider) • VS Code at /Applications/Visual Studio Code - Insiders.app/Contents • Flutter extension version 3.58.0 [✓] Connected device (1 available) • Chrome (web) • chrome • web-javascript • Google Chrome 113.0.5672.92 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 3 categories. ```
andrewkolos commented 1 year ago

Thanks for the great issue description.

The framework uses rootBundle.load for loading all assets, including ones generated by Flutter itself. Specifcally, there is a special asset called the asset manifest. The FormatException you see is thrown because the load override in TestAssetBundleByteData doesn't return data in the format that the framework expects for the asset manifest file. The easiest way to work around this is to 1) extend PlatformAssetBundle instead of CachingAssetBundle and 2) fallback to normal loading behavior by returning super.load(key) when the key is not something you expect to see in your test. See these code examples below:

class TestAssetBundle extends PlatformAssetBundle {
  @override
  Future<ByteData> load(String key) async {
    if (key == 'expected_test_key') {
      return ByteData(12);
    }

    // Unrecognized key, default to normal loading behavior.
    return super.load(key);
  }
}

// Or, if you want to explicitly handle the asset manifest:
class TestAssetBundle extends PlatformAssetBundle {
  @override
  Future<ByteData> load(String key) async {
    // Matches "AssetManifest.json", "AssetManifest.bin", and "AssetManifest.smcbin"
    if (key.startsWith('AssetManifest')) {
      return super.load(key);
    }

    if (key == 'expected_test_key') {
      return ByteData(12);
    }

    throw Exception('Unexpected key: $key');
  }
}

Let me know if this works.

I recognize that this issue highlights how tricky it is to creating test implementations of AssetBundle, and there is certainly potential for improvement. I'll make sure to file another issue before this one is closed.

eerive commented 1 year ago

@andrewkolos Thanks for your response. Your suggested solution does indeed fix all our widget tests. For improvements, it might be a good idea to update the documentation of the DefaultAssetBundle to instead use the PlatformAssetBundle should a rootBundle be accessed. This is basically how we implemented it.

andrewkolos commented 1 year ago

Created #126860 to track progress on making issues like these more preventable and/or more easily-solved in the future.

github-actions[bot] commented 1 year ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.