rive-flutter
rive-flutter copied to clipboard
Flutter test breaks when using textRun
Description
flutter test breaks when trying to test a widget that uses dynamic texts:
RiveAnimation.asset(
'foo.riv',
fit: BoxFit.contain,
stateMachines: const ['State Machine 1'],
onInit: (artboard) {
final bookTraining = artboard.textRun('bookTraining');
bookTraining.text = 'bookTraining'.i18n;
});
Any test with that widget returns:
Invalid argument(s): Failed to lookup symbol 'init': dlsym(RTLD_DEFAULT, init): symbol not found
When the exception was thrown, this was the stack:
#0 DynamicLibrary.lookup (dart:ffi-patch/ffi_dynamic_library_patch.dart:33:70)
#1 init (package:rive_common/src/rive_text_ffi.dart:528:15)
#2 init (package:rive_common/src/rive_text_ffi.dart)
#3 initFont (package:rive_common/src/rive_text_ffi.dart:837:3)
#4 Font.initialize (package:rive_common/rive_text.dart:469:12)
#5 RiveFile._initTextAndImport (package:rive/src/rive_file.dart:362:20)
#6 RiveFile.asset (package:rive/src/rive_file.dart:392:12)
<asynchronous suspension>
Steps To Reproduce
Steps to reproduce the behavior: Write a test pumping the mentioned widget.
Expected behavior
Test suite should work
Hi @cerealexx , the Rive Font engine won't have initialized at this point. The best way to do this is to manage the loading of the riv binary yourself, and you can also manually call RiveFile.initializeText
You can see an example in our package tests: https://github.com/rive-app/rive/blob/master/packages/rive_flutter/test/goldens/text/golden_text_test.dart
Something like:
/// Loads a Rive file from the assets sub-folder
ByteData loadFile(String filename) {
final file = File(
'./${Directory.current.path.endsWith('/test') ? '' : 'test/'}$filename');
return ByteData.sublistView(file.readAsBytesSync());
}
...
final riveBytes = loadFile('assets/electrified_button_simple.riv');
final file = RiveFile.import(riveBytes);
...
RiveAnimation.direct(file);
Hi @HayesGordon , I was running into the same issue as @cerealexx as I tried to write a simple widget test for a rive widget. I applied the solution you proposed and it actually works. Unfortunately I'm now getting a similar error and still cannot get my test running properly.
My simplified code looks like this:
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:rive/rive.dart';
import 'package:tuktoro_app/config/flutter_gen/assets.gen.dart';
void main() {
testWidgets('Test that rive file has correct interface', (tester) async {
final file = File(Assets.public.images.chickenGame.chicken);
final riveBytes = ByteData.sublistView(file.readAsBytesSync());
final riveFile = RiveFile.import(riveBytes);
final widget = RiveAnimation.direct(riveFile);
await tester.pumpWidget(widget);
});
}
When running this test code I get the following error as soon as the line final riveFile = RiveFile.import(riveBytes)
is executed:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following ArgumentError was thrown running a test:
Invalid argument(s): Failed to lookup symbol 'makeFont': dlsym(RTLD_DEFAULT, makeFont): symbol not
found
When the exception was thrown, this was the stack:
#0 DynamicLibrary.lookup (dart:ffi-patch/ffi_dynamic_library_patch.dart:33:70)
#1 makeFont (package:rive_common/src/rive_text_ffi.dart:507:10)
#2 makeFont (package:rive_common/src/rive_text_ffi.dart)
#3 decodeFont (package:rive_common/src/rive_text_ffi.dart:827:16)
#4 Font.decode (package:rive_common/rive_text.dart:473:12)
#5 FontAsset.parseBytes (package:rive/src/rive_core/assets/font_asset.dart:63:17)
#6 FontAsset.decode (package:rive/src/rive_core/assets/font_asset.dart:59:18)
#7 FileAssetImporter.resolve.<anonymous closure> (package:rive/src/core/importers/file_asset_importer.dart:32:19)
<asynchronous suspension>
(elided 8 frames from dart:async and package:stack_trace)
Do you have any idea what could be the problem and how to solve it? Thanks ahead!
@mklemann90 I'm experiencing a similar issue. Have you found a way to solve it?
@luisredondo so far not, but since it was not top priority in our project, I postponed looking for a solution after I got stuck with this issue.
Hi all, I'm unable to replicate tests breaking. If someone can share a reproducible example in a repository, I can take a look.
But here is an example unit test with files:
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:rive/rive.dart';
extension _TextExtension on Artboard {
TextValueRun? textRun(String name) => component<TextValueRun>(name);
}
ByteData _loadFile(String filename) {
final file = File(
'./${Directory.current.path.endsWith('/test') ? '' : 'test/'}$filename');
return ByteData.sublistView(file.readAsBytesSync());
}
void main() {
late RiveFile riveFile;
setUp(() {
return Future(() async {
final riveBytes = _loadFile('assets/text_run.riv');
await RiveFile.initializeText();
riveFile = RiveFile.import(riveBytes);
});
});
test('Text run updating', () {
final artboard = riveFile.mainArtboard.instance();
final run1 = artboard.textRun('run1')!;
final run2 = artboard.textRun('run2')!;
expect(run1.text, "run1-value");
expect(run2.text, "run2-value");
run1.text = "new value 1";
run2.text = "new value 2";
expect(run1.text, "new value 1");
expect(run2.text, "new value 2");
});
}
text_run.riv.zip text_run.rev.zip
And here is a golden test that updates a text run: https://github.com/rive-app/rive-flutter/blob/master/test/goldens/text/golden_text_test.dart
With output files: https://github.com/rive-app/rive-flutter/tree/master/test/goldens/text/images
Hey @HayesGordon! I created this repository: rive_issue
. There I'm getting:
Invalid argument(s): Failed to lookup symbol 'init': dlsym(RTLD_DEFAULT, init): symbol not found
dart:ffi DynamicLibrary.lookup
package:rive_common/src/rive_text_ffi.dart 527:15 init
package:rive_common/src/rive_text_ffi.dart init
package:rive_common/src/rive_text_ffi.dart 836:3 initFont
package:rive_common/rive_text.dart 471:15 Font.initialize
package:rive/src/rive_file.dart 365:18 RiveFile.initializeText
test/widget_test.dart 23:22 main.<fn>.<fn>
I pasted the exact same code you shared, and added the .riv
and .rev
files you attached.
I'm running with:
Flutter:
Flutter 3.19.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ba39319843 (7 weeks ago) • 2024-03-07 15:22:21 -0600
Engine • revision 2e4ba9c6fb
Tools • Dart 3.3.1 • DevTools 2.31.1
Machine: Apple M1 Pro with Sonoma 14.4.1
Please let me know if you are able to reproduce.
@luisredondo thanks for the repro. This was a mistake on my side, I forgot that Flutter tests need to load in the dylib for macOS.
I'm providing a solution, but to give some context, the Rive editor uses the same underlying rive_common
package, which complicates things slightly. Long term we'd need to revisit this to make it easier. But for now, I recommend generating the libraries yourself.
Here are the steps:
- Copy the attached shell script to the root of your Flutter project
- Make it executable with
chmod +x build_rive_common.sh
. - Run the script normally with
./build_rive_common.sh
. - To force a rebuild use
./build_rive_common.sh -f
(else it will cache based on the rive_common version and if the output directory is empty).
This script will generate a shared_lib/build/bin/debug
folder with libraries needed for testing. You do not need to commit this to source control, but if you want to run Rive tests as part of a Github action, for example, this script will need to be run. Or see our Github action for tests - which does the same thing as the script (but without caching).
EDIT: I only tested this on the latest version of Rive, at the time of writing v0.13.2
build_rive_common.sh
#!/bin/bash
# Default flag for force rebuild set to false
FORCE_REBUILD=false
# Parse command-line options
while getopts "f" opt; do
case $opt in
f)
FORCE_REBUILD=true
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
# Directory where the version file and build outputs will be stored
BUILD_DIR="shared_lib/build/bin/debug/"
# Version file path
VERSION_FILE="rive_common_version.txt"
# Make sure Rive and Rive Common is available
flutter pub get
# Fetch the current rive_common version from pubspec.lock
CURRENT_VERSION=$(dart pub deps -s list | grep rive_common | sed 's/[^0-9.]*//g' | head -n 1)
echo $CURRENT_VERSION
# Check if the version was actually retrieved
if [ -z "$CURRENT_VERSION" ]; then
echo $'\nFailed to retrieve rive_common version. Please check the output of 'dart pub deps -s list'.'
exit 1
fi
# Read the version from disk if the version file exists
if [ -f "$VERSION_FILE" ]; then
SAVED_VERSION=$(cat "$VERSION_FILE")
else
SAVED_VERSION=""
fi
# Check if the build directory is empty
if [ "$(ls -A $BUILD_DIR)" ]; then
BUILD_DIR_EMPTY=false
else
BUILD_DIR_EMPTY=true
fi
# Compare versions and directory status unless force rebuild is triggered
if [ "$FORCE_REBUILD" = false ] && [ "$CURRENT_VERSION" = "$SAVED_VERSION" ] && [ "$BUILD_DIR_EMPTY" = false ]; then
echo $'\nNo rebuild needed. Exiting...\n'
exit 0
fi
# Save the current version to disk
echo $CURRENT_VERSION > $VERSION_FILE
echo ""
echo "Rive Common Version: $CURRENT_VERSION"
echo ""
# Change to the directory where 'rive_common' is located in the local pub cache
pushd ~/.pub-cache/hosted/pub.dev/rive_common-$CURRENT_VERSION
# Run the update_dependencies script with 'force' option
./update_dependencies.sh force
# Change to the directory containing the shared library source
pushd shared_lib
# Build the shared library
./build_shared.sh
# Store the path to the built shared library
RIVE_TEXT_DYLIB=$PWD/build/bin/debug/librive_text.dylib
# Return to the previous directory
popd
# Return to the initial directory
popd
# Create the target directory if it doesn't exist
mkdir -p $BUILD_DIR
# Copy the built shared library to the target directory
cp $RIVE_TEXT_DYLIB $BUILD_DIR
echo $'\nLibrary built and copied successfully.\n'
Is there any long-term solution for this?
A Slightly modified version of the script so it can run on ubuntu-latest as well
#!/bin/bash
# Default flag for force rebuild set to false
FORCE_REBUILD=false
# Parse command-line options
while getopts "f" opt; do
case $opt in
f)
FORCE_REBUILD=true
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
# Directory where the version file and build outputs will be stored
BUILD_DIR="shared_lib/build/bin/debug/"
# Version file path
VERSION_FILE="rive_common_version.txt"
# Current dir
CURRENT_DIR=$(pwd)
# Make sure Rive and Rive Common is available
flutter pub get
# Fetch the current rive_common version from pubspec.lock
CURRENT_VERSION=$(dart pub deps -s list | grep rive_common | sed 's/[^0-9.]*//g' | head -n 1)
echo $CURRENT_VERSION
# Check if the version was actually retrieved
if [ -z "$CURRENT_VERSION" ]; then
echo $'\nFailed to retrieve rive_common version. Please check the output of 'dart pub deps -s list'.'
exit 1
fi
# Read the version from disk if the version file exists
if [ -f "$VERSION_FILE" ]; then
SAVED_VERSION=$(cat "$VERSION_FILE")
else
SAVED_VERSION=""
fi
# Check if the build directory is empty
if [ "$(ls -A $BUILD_DIR)" ]; then
BUILD_DIR_EMPTY=false
else
BUILD_DIR_EMPTY=true
fi
# Compare versions and directory status unless force rebuild is triggered
if [ "$FORCE_REBUILD" = false ] && [ "$CURRENT_VERSION" = "$SAVED_VERSION" ] && [ "$BUILD_DIR_EMPTY" = false ]; then
echo $'\nNo rebuild needed. Exiting...\n'
exit 0
fi
# Save the current version to disk
echo $CURRENT_VERSION > $VERSION_FILE
echo ""
echo "Rive Common Version: $CURRENT_VERSION"
echo ""
# Change to the directory where 'rive_common' is located in the local pub cache
cd $PUB_CACHE/hosted/pub.dev/rive_common-$CURRENT_VERSION || exit 1
# Run the update_dependencies script with 'force' option
./update_dependencies.sh force
# Change to the directory containing the shared library source
cd shared_lib || exit 1
# Build the shared library
./build_shared.sh
# Store the path to the built shared library
if [ -f "$PWD/build/bin/debug/librive_text.so" ]; then
RIVE_TEXT_DYLIB=$PWD/build/bin/debug/librive_text.so
else
RIVE_TEXT_DYLIB=$PWD/build/bin/debug/librive_text.dylib
fi
# Return to the initial directory
cd $CURRENT_DIR || exit 1
# Create the target directory if it doesn't exist
mkdir -p $BUILD_DIR
# Copy the built shared library to the target directory
cp $RIVE_TEXT_DYLIB $BUILD_DIR
echo $'\nLibrary built and copied successfully.\n'
I'm providing a solution, but to give some context, the Rive editor uses the same underlying rive_common package, which complicates things slightly.
Didn't it depend on rive_common
before 0.13.x
too?
Currently we can't update flame_rive
to the latest version of rive due to it not being able to run the tests of the package. And we don't want to maintain a custom build script for the package. :sweat_smile:
@HayesGordon is there any other way forward here from the rive side?
@spydon, it did depend on rive_common
but the libraries were only needed if the Rive file contained Rive Text. We checked the file to see if it required initialization. Every Rive file will now require that library in preparation for an upcoming feature.
We're working on a significant underlying change for the Rive Flutter runtime that will directly impact this. For this new version of the Rive Flutter runtime, we're discussing the best way to approach this issue. Most likely, we will build and package the libraries instead of requiring them to be built.
So, in that new major release of Rive Flutter, we will address this issue.
So, in that new major release of Rive Flutter, we will address this issue.
Super, thanks for the update! :)