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.
|
||||
PATH_add .fvm/flutter_sdk/bin
|
||||
|
||||
PATH_add "$HOME/Android/Sdk/platform-tools"
|
||||
|
||||
# Load variables from .env
|
||||
dotenv_if_exists
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: dart-format
|
||||
name: dart format
|
||||
- id: dart-check
|
||||
name: dart format (autofix) + check-fast (parallel)
|
||||
language: system
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command fvm dart format .'
|
||||
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'
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command scripts/pre_commit_check.sh'
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ tasks:
|
||||
preconditions:
|
||||
- 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"
|
||||
- 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"
|
||||
|
||||
install-hooks:
|
||||
|
||||
@@ -47,10 +47,4 @@ flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
dependencies {}
|
||||
|
||||
+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
|
||||
|
||||
## 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
|
||||
|
||||
Replaced the hand-rolled `TextField`-in-`AppBar` search UI with Flutter's built-in `SearchBar`
|
||||
|
||||
@@ -10,6 +10,24 @@
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
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 {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
@@ -22,6 +40,13 @@
|
||||
# Flutter version manager — needed for host builds (task build-linux, task run)
|
||||
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
|
||||
stalwart-mail
|
||||
|
||||
@@ -37,12 +62,32 @@
|
||||
jq
|
||||
sqlite
|
||||
python3 # used by stalwart-dev/start to pick random ports
|
||||
];
|
||||
]);
|
||||
|
||||
shellHook = ''
|
||||
# Disable Flutter telemetry inside dev shell
|
||||
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 " Analyze : task analyze"
|
||||
echo " Unit tests : task test"
|
||||
|
||||
@@ -220,6 +220,10 @@ void main() {
|
||||
|
||||
// ── 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 pumpUntil(tester, find.text('INBOX'));
|
||||
_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() {
|
||||
return LazyDatabase(() async {
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
final file = File(p.join(dir.path, 'sharedinbox.db'));
|
||||
final file = File(
|
||||
_dbPath ??
|
||||
p.join(
|
||||
(await getApplicationSupportDirectory()).path,
|
||||
'sharedinbox.db',
|
||||
),
|
||||
);
|
||||
return NativeDatabase.createInBackground(
|
||||
file,
|
||||
setup: (db) {
|
||||
|
||||
+4
-1
@@ -1,10 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:sharedinbox/data/db/database.dart';
|
||||
import 'package:sharedinbox/di.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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,3 @@ Git repo should not contain unknown files.
|
||||
Then commit.
|
||||
|
||||
## 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:
|
||||
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
|
||||
# display without requiring a real desktop session. No D-Bus or keyring daemon
|
||||
# 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"
|
||||
|
||||
Reference in New Issue
Block a user