fix: Android E2E aliceTile race + bundle deploy-android infra
The Android UI integration test failed at tap(aliceTile) with "0 widgets" even though pumpUntil had just found the tile. On the slow software-rendered emulator the route-pop animation finalises during pumpUntil's trailing 300 ms settle, briefly leaving the tile out of the tree. Re-confirm with a second pumpUntil before the tap. Bundles the previously uncommitted infra changes that make task deploy-android run end-to-end inside nix develop: Linux desktop runtime libs + GL software rendering env in flake.nix, path_provider_android pin to <2.3 to avoid the libdartjni SIGSEGV, deferred DB-path resolution after WidgetsFlutterBinding, +iglx for xvfb-run, platform-tools on PATH, and a single pre-commit script replacing the dart-format / task-check-fast pair. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d2226388d7
commit
2e2b7c3d9f
@@ -12,5 +12,7 @@ use flake
|
|||||||
# This must come after `use flake` so FVM Flutter takes precedence.
|
# This must come after `use flake` so FVM Flutter takes precedence.
|
||||||
PATH_add .fvm/flutter_sdk/bin
|
PATH_add .fvm/flutter_sdk/bin
|
||||||
|
|
||||||
|
PATH_add "$HOME/Android/Sdk/platform-tools"
|
||||||
|
|
||||||
# Load variables from .env
|
# Load variables from .env
|
||||||
dotenv_if_exists
|
dotenv_if_exists
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: dart-format
|
- id: dart-check
|
||||||
name: dart format
|
name: dart format (autofix) + check-fast (parallel)
|
||||||
language: system
|
language: system
|
||||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command fvm dart format .'
|
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command scripts/pre_commit_check.sh'
|
||||||
pass_filenames: false
|
|
||||||
always_run: true
|
|
||||||
- id: task-check
|
|
||||||
name: task check-fast (analyze + unit + widget)
|
|
||||||
language: system
|
|
||||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command task check-fast'
|
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
always_run: true
|
always_run: true
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ tasks:
|
|||||||
preconditions:
|
preconditions:
|
||||||
- sh: command -v clang >/dev/null 2>&1
|
- sh: command -v clang >/dev/null 2>&1
|
||||||
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
||||||
- sh: /usr/bin/pkg-config --exists gtk+-3.0 2>/dev/null
|
- sh: pkg-config --exists gtk+-3.0 2>/dev/null
|
||||||
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
msg: "Linux desktop deps missing. Run: sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev libstdc++-12-dev"
|
||||||
|
|
||||||
install-hooks:
|
install-hooks:
|
||||||
|
|||||||
@@ -47,10 +47,4 @@ flutter {
|
|||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {}
|
||||||
// integration_test is a dev dependency; the Flutter plugin loader adds it as
|
|
||||||
// debugImplementation only, but GeneratedPluginRegistrant.java (in src/main)
|
|
||||||
// references its class in all variants. Make it available for release compilation
|
|
||||||
// without bundling it in the APK.
|
|
||||||
releaseCompileOnly(project(":integration_test"))
|
|
||||||
}
|
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.flutter.plugins.integration_test;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No-op stub used in release builds.
|
||||||
|
* The real IntegrationTestPlugin lives in the integration_test SDK package
|
||||||
|
* which is a dev-only dependency. GeneratedPluginRegistrant references it in
|
||||||
|
* all build variants, so without this stub the release build throws
|
||||||
|
* NoClassDefFoundError at startup, aborting plugin registration entirely.
|
||||||
|
*/
|
||||||
|
public class IntegrationTestPlugin implements FlutterPlugin {
|
||||||
|
@Override
|
||||||
|
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
|
||||||
|
}
|
||||||
@@ -6,6 +6,41 @@ Tasks get moved from next.md to done.md
|
|||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
|
## task deploy-android works end-to-end
|
||||||
|
|
||||||
|
The original "Emulator did not become ready within 120 s" was already resolved in
|
||||||
|
commit `d222638` by running `adb start-server` before booting the AVD; without the
|
||||||
|
adb daemon running first, the emulator can never register as a device.
|
||||||
|
|
||||||
|
Running `task deploy-android` after that surfaced an Android-specific integration-test
|
||||||
|
failure: `aliceTile` had 0 widgets at `tester.tap()` time even though the immediately
|
||||||
|
preceding `pumpUntil(aliceTile)` had just found it. On the slow software-rendered
|
||||||
|
emulator the route-pop animation finalises during `pumpUntil`'s trailing 300 ms settle
|
||||||
|
and the tile is briefly absent right after. Fixed in
|
||||||
|
`integration_test/app_e2e_test.dart` by re-confirming `aliceTile` with a second
|
||||||
|
`pumpUntil` (5 s timeout) before the tap.
|
||||||
|
|
||||||
|
Bundled with a coherent set of pre-existing infrastructure changes that make the full
|
||||||
|
pipeline (Linux + Android UI tests, MobSF scan, APK upload) work in `nix develop`:
|
||||||
|
|
||||||
|
- `flake.nix`: adds Linux desktop runtime libs (gtk3, mesa, libGL, libsecret, …) plus
|
||||||
|
`PKG_CONFIG_PATH`, `LD_LIBRARY_PATH`, `LIBGL_ALWAYS_SOFTWARE=1`, and the libglvnd
|
||||||
|
vendor-dir env vars so `flutter build linux` and `xvfb-run` work without a real GPU.
|
||||||
|
- `pubspec.yaml`: pins `path_provider_android` to `>=2.2.0 <2.3.0` to dodge the SIGSEGV
|
||||||
|
in `libdartjni.so` (FindClassUnchecked) on Android startup with 2.3+.
|
||||||
|
- `lib/main.dart` + `lib/data/db/database.dart`: resolves the DB path during `main()`
|
||||||
|
after `WidgetsFlutterBinding.ensureInitialized()` so the path_provider plugin channel
|
||||||
|
is registered before the first DB access.
|
||||||
|
- `stalwart-dev/integration_ui_test.sh`: passes `-screen 0 1280x720x24 +iglx` to Xvfb
|
||||||
|
so GTK3/Flutter can create a GLX OpenGL context under the virtual framebuffer.
|
||||||
|
- `.envrc`: adds `$HOME/Android/Sdk/platform-tools` to PATH so `adb` resolves outside
|
||||||
|
`nix develop`.
|
||||||
|
- `Taskfile.yml`: drops the `/usr/bin/pkg-config` hardcode in favour of PATH so the
|
||||||
|
nix-provided wrapper is found.
|
||||||
|
- `.pre-commit-config.yaml` + `scripts/pre_commit_check.sh`: consolidates `dart format`
|
||||||
|
and `task check-fast` into a single script invoked by one hook (one `nix develop`
|
||||||
|
startup instead of two).
|
||||||
|
|
||||||
## Replace custom search TextField with Flutter SearchBar
|
## Replace custom search TextField with Flutter SearchBar
|
||||||
|
|
||||||
Replaced the hand-rolled `TextField`-in-`AppBar` search UI with Flutter's built-in `SearchBar`
|
Replaced the hand-rolled `TextField`-in-`AppBar` search UI with Flutter's built-in `SearchBar`
|
||||||
|
|||||||
@@ -10,6 +10,24 @@
|
|||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
|
||||||
|
# All Linux desktop runtime libraries needed by flutter build linux and
|
||||||
|
# the UI integration tests (xvfb-run). Kept as a list so we can reuse
|
||||||
|
# it for both buildInputs and LD_LIBRARY_PATH / PKG_CONFIG_PATH.
|
||||||
|
linuxDesktopLibs = with pkgs; [
|
||||||
|
gtk3
|
||||||
|
libsecret
|
||||||
|
fontconfig
|
||||||
|
libepoxy
|
||||||
|
mesa
|
||||||
|
libGL # libglvnd — vendor-neutral GL/EGL/GLX dispatch layer
|
||||||
|
at-spi2-core
|
||||||
|
glib
|
||||||
|
pango
|
||||||
|
cairo
|
||||||
|
gdk-pixbuf
|
||||||
|
harfbuzz
|
||||||
|
];
|
||||||
in {
|
in {
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
@@ -22,6 +40,13 @@
|
|||||||
# Flutter version manager — needed for host builds (task build-linux, task run)
|
# Flutter version manager — needed for host builds (task build-linux, task run)
|
||||||
fvm
|
fvm
|
||||||
|
|
||||||
|
# Linux desktop build + runtime dependencies (flutter build linux / task run)
|
||||||
|
] ++ linuxDesktopLibs ++ (with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
clang
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
|
||||||
# Local IMAP/SMTP dev server for integration tests
|
# Local IMAP/SMTP dev server for integration tests
|
||||||
stalwart-mail
|
stalwart-mail
|
||||||
|
|
||||||
@@ -37,12 +62,32 @@
|
|||||||
jq
|
jq
|
||||||
sqlite
|
sqlite
|
||||||
python3 # used by stalwart-dev/start to pick random ports
|
python3 # used by stalwart-dev/start to pick random ports
|
||||||
];
|
]);
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
# Disable Flutter telemetry inside dev shell
|
# Disable Flutter telemetry inside dev shell
|
||||||
export FLUTTER_SUPPRESS_ANALYTICS=true
|
export FLUTTER_SUPPRESS_ANALYTICS=true
|
||||||
|
|
||||||
|
# Expose dev headers to cmake's FindPkgConfig.
|
||||||
|
# The nix pkg-config wrapper works in bash but cmake invokes pkg-config
|
||||||
|
# as a subprocess and needs PKG_CONFIG_PATH set explicitly.
|
||||||
|
export PKG_CONFIG_PATH="${pkgs.gtk3.dev}/lib/pkgconfig:${pkgs.glib.dev}/lib/pkgconfig:${pkgs.pango.dev}/lib/pkgconfig:${pkgs.cairo.dev}/lib/pkgconfig:${pkgs.gdk-pixbuf.dev}/lib/pkgconfig:${pkgs.at-spi2-core.dev}/lib/pkgconfig:${pkgs.harfbuzz.dev}/lib/pkgconfig:${pkgs.libsecret}/lib/pkgconfig:${pkgs.fontconfig.dev}/lib/pkgconfig:${pkgs.libepoxy}/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||||
|
|
||||||
|
# Nix ld uses --no-copy-dt-needed-entries (strict mode): transitive shared-lib
|
||||||
|
# deps are not followed automatically, so link them explicitly.
|
||||||
|
export LDFLAGS="-L${pkgs.fontconfig.lib}/lib -lfontconfig $LDFLAGS"
|
||||||
|
|
||||||
|
# Make nix-built runtime libs visible to the dynamic linker so the
|
||||||
|
# Flutter Linux bundle and integration-ui tests can run.
|
||||||
|
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath linuxDesktopLibs}:$LD_LIBRARY_PATH"
|
||||||
|
|
||||||
|
# Wire the libglvnd dispatch to the nix mesa vendor ICDs so GTK/Flutter
|
||||||
|
# can create an OpenGL (EGL + GLX) context under Xvfb without a real GPU.
|
||||||
|
export __EGL_VENDOR_LIBRARY_DIRS="${pkgs.mesa}/share/glvnd/egl_vendor.d"
|
||||||
|
export __GLX_VENDOR_LIBRARY_DIRS="${pkgs.mesa}/lib"
|
||||||
|
export LIBGL_ALWAYS_SOFTWARE=1
|
||||||
|
export MESA_LOADER_DRIVER_OVERRIDE=softpipe
|
||||||
|
|
||||||
echo "SharedInbox Flutter dev environment ready."
|
echo "SharedInbox Flutter dev environment ready."
|
||||||
echo " Analyze : task analyze"
|
echo " Analyze : task analyze"
|
||||||
echo " Unit tests : task test"
|
echo " Unit tests : task test"
|
||||||
|
|||||||
@@ -220,6 +220,10 @@ void main() {
|
|||||||
|
|
||||||
// ── Navigate to mailboxes ──────────────────────────────────────────────
|
// ── Navigate to mailboxes ──────────────────────────────────────────────
|
||||||
_log('navigate to mailboxes');
|
_log('navigate to mailboxes');
|
||||||
|
// On the slow Android emulator (software rendering), aliceTile can be
|
||||||
|
// briefly absent right after pumpUntil's trailing 300ms settle while the
|
||||||
|
// route-pop animation finalises. Re-confirm it's present before tapping.
|
||||||
|
await pumpUntil(tester, aliceTile, timeout: const Duration(seconds: 5));
|
||||||
await tester.tap(aliceTile);
|
await tester.tap(aliceTile);
|
||||||
await pumpUntil(tester, find.text('INBOX'));
|
await pumpUntil(tester, find.text('INBOX'));
|
||||||
_log('mailboxes settled');
|
_log('mailboxes settled');
|
||||||
|
|||||||
@@ -251,10 +251,25 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolved once in main() via initDatabasePath() before runApp().
|
||||||
|
String? _dbPath;
|
||||||
|
|
||||||
|
/// Call after WidgetsFlutterBinding.ensureInitialized() so that the
|
||||||
|
/// path_provider plugin channel is registered before the first DB access.
|
||||||
|
Future<void> initDatabasePath() async {
|
||||||
|
final dir = await getApplicationSupportDirectory();
|
||||||
|
_dbPath = p.join(dir.path, 'sharedinbox.db');
|
||||||
|
}
|
||||||
|
|
||||||
LazyDatabase _openConnection() {
|
LazyDatabase _openConnection() {
|
||||||
return LazyDatabase(() async {
|
return LazyDatabase(() async {
|
||||||
final dir = await getApplicationSupportDirectory();
|
final file = File(
|
||||||
final file = File(p.join(dir.path, 'sharedinbox.db'));
|
_dbPath ??
|
||||||
|
p.join(
|
||||||
|
(await getApplicationSupportDirectory()).path,
|
||||||
|
'sharedinbox.db',
|
||||||
|
),
|
||||||
|
);
|
||||||
return NativeDatabase.createInBackground(
|
return NativeDatabase.createInBackground(
|
||||||
file,
|
file,
|
||||||
setup: (db) {
|
setup: (db) {
|
||||||
|
|||||||
+4
-1
@@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:sharedinbox/data/db/database.dart';
|
||||||
import 'package:sharedinbox/di.dart';
|
import 'package:sharedinbox/di.dart';
|
||||||
import 'package:sharedinbox/ui/router.dart';
|
import 'package:sharedinbox/ui/router.dart';
|
||||||
|
|
||||||
void main({List<Override> overrides = const []}) {
|
void main({List<Override> overrides = const []}) async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await initDatabasePath();
|
||||||
runApp(ProviderScope(overrides: overrides, child: const SharedInboxApp()));
|
runApp(ProviderScope(overrides: overrides, child: const SharedInboxApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,3 @@ Git repo should not contain unknown files.
|
|||||||
Then commit.
|
Then commit.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
fix:
|
|
||||||
|
|
||||||
[6ms] no emulator running — booting AVD sharedinbox_test
|
|
||||||
Emulator did not become ready within 120 s
|
|
||||||
task: Failed to run task "deploy-android": task: Failed to run task "integration-android": exit status 1
|
|
||||||
|
|
||||||
Why does the emulator not start? Where are the logs?
|
|
||||||
|
|
||||||
After that: task deploy-android
|
|
||||||
|
|
||||||
fix, if it fails.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@@ -54,3 +54,9 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
# path_provider_android 2.3+ uses package:jni which crashes on startup
|
||||||
|
# (SIGSEGV in libdartjni.so FindClassUnchecked — JNI env not ready when
|
||||||
|
# the Dart VM first calls into it). Pin to 2.2.x which uses Pigeon instead.
|
||||||
|
path_provider_android: ">=2.2.0 <2.3.0"
|
||||||
|
|||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Single nix develop session: format first (autofix), then check-fast.
|
||||||
|
# Flutter's startup lock prevents true parallelism between dart format and flutter analyze.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)" || exit 1
|
||||||
|
|
||||||
|
fvm dart format .
|
||||||
|
task check-fast
|
||||||
@@ -94,6 +94,9 @@ ts "flutter test start"
|
|||||||
# xvfb-run provides a virtual framebuffer so the Flutter Linux runner has a
|
# xvfb-run provides a virtual framebuffer so the Flutter Linux runner has a
|
||||||
# display without requiring a real desktop session. No D-Bus or keyring daemon
|
# display without requiring a real desktop session. No D-Bus or keyring daemon
|
||||||
# is needed because the integration tests inject an in-memory SecureStorage.
|
# is needed because the integration tests inject an in-memory SecureStorage.
|
||||||
xvfb-run --auto-servernum fvm flutter test integration_test/ -d linux
|
# +iglx enables indirect GLX on Xvfb so Flutter/GTK3 can create an OpenGL context
|
||||||
|
# using mesa's software renderer (LIBGL_ALWAYS_SOFTWARE=1 is set in flake.nix).
|
||||||
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24 +iglx" \
|
||||||
|
fvm flutter test integration_test/ -d linux
|
||||||
|
|
||||||
ts "flutter test done"
|
ts "flutter test done"
|
||||||
|
|||||||
Reference in New Issue
Block a user