blob: 0257f58d87fa2dfbd27721003b0e5223f783a0a7 [file] [view]
# Android Hardware Smoke Test
An integration and compatibility smoke test suite designed to verify visual rendering correctness on Android hardware.
## Prerequisites & Initial Setup
Because this integration test project follows a minimal-boilerplate pattern, standard binary Gradle wrappers and properties are not committed to the repository.
Before compiling or running any connected JUnit/instrumented tests locally for the first time, simply run the standard Flutter project regeneration command in this directory to restore the missing wrappers:
```sh
flutter create --platform=android --no-overwrite .
```
This will cleanly restore the missing wrapper scripts (`gradlew`, `gradlew.bat`) and wrapper configurations without modifying any of the customized build definitions, Java/Kotlin test harnesses, or package sources.
## 1. Overview & Purpose
The primary objective of the `android_hardware_smoke_test` is to provide a **fully self-contained Android instrumented test suite**.
This allows Android hardware manufacturers (OEMs) to run visual regression, performance, and GPU compatibility tests on a precompiled APK directly on their target devices. **The test execution requires no Flutter SDK, Dart CLI commands, or host-side orchestration.** Feedback is reported directly in standard native Android JUnit test reports.
---
## 2. Structural Differences: `android_hardware_smoke_test` vs. `android_engine_test`
While both suites verify rendering correctness, they are architected differently to support different workflows:
| Dimension | `android_engine_test` | `android_hardware_smoke_test` |
| :--- | :--- | :--- |
| **Verification Location** | **Host-Only** (CI PC) | **Dual-Mode**: On-Device (OEM) & On-Host (CI) |
| **Golden Comparison** | Host-side only against Skia Gold. | **OEM Mode**: In-App pixel comparison against bundled assets.<br>**CI Mode**: Host-side comparisons against repository files. |
| **Device Role** | Passive target rendering static views. | Active participant executing on-device JUnit orchestration. |
| **Target Audience** | Core Engine Contributors & CI Shards. | Android Hardware Manufacturers (OEMs) & CI Shards. |
---
## 3. Dual-Mode Architecture
```
┌──────────────────────────────────────┐
│ Test Initiation │
└──────────────────┬───────────────────┘
┌────────────────────────┴────────────────────────┐
▼ ▼
[ On-Device Instrumented ] [ Host-Driven Driver ]
(OEM / Self-Contained Testing) (CI / Host-Driven Testing)
│ │
▼ ▼
native Android JUnit flutter drive host script
(FlutterActivityTest / AndroidJUnit4) (test_driver/driver_test.dart)
│ │
▼ ▼
Native Java Test sends Host driver script connects,
testName over Message Channel and requests testName via
│ driver.requestData()
▼ │
App renders target state & ▼
performs local on-device golden App renders target state and
comparison against bundled assets returns image bytes over channel
│ │
▼ ▼
App replies status back to Host driver script decodes
Java; JUnit reports result image bytes and asserts match
```
### Instrumented On-Device Mode (OEM / Standalone)
* **Orchestration**: Runs purely on the device under Android `AndroidJUnit4` runner.
* **Execution**: Java JUnit code (`FlutterActivityTest.java`) launches the main activity and sends the test payload over a JSON message channel. The app (`lib/main.dart`) renders the widget, performs a local pixel-by-pixel on-device comparison against baseline images bundled within the APK assets, and replies with the status to the Java runner to pass or fail the JUnit assertion.
### Host-Driven Driver Mode (CI / Host-Driven)
* **Orchestration**: Orchestrated by the host PC using `flutter drive`.
* **Execution**: The host script (`test_driver/driver_test.dart`) commands the target app (`integration_test/integration_test_wrapper.dart`) to transition states. The app captures the repaint boundary, base64-encodes it, and streams the bytes back to the host over a WebSocket channel. The host driver decodes the bytes and asserts visual matches against local repository baselines on the host filesystem.
---
## 4. How to Run the Tests
Unlock your connected Android device or emulator and ensure it is active before executing any of these commands.
### A. Running via Host Driver (CI / Host-Driven)
This mode is used to execute visual assertions locally on your PC or in CI pipelines, and to manage the local golden baselines.
* **Command to run the driver test suite**:
```sh
# Execute from the android_hardware_smoke_test root directory
flutter drive -v \
--driver=test_driver/driver_test.dart \
--target=integration_test/integration_test_wrapper.dart \
--no-dds
```
* **Command to capture/update reference golden baselines**:
Running with `UPDATE_GOLDENS=true` writes or overwrites the local PNG baselines under `test_driver/goldens/` on the host.
Because the statically compiled `AndroidManifest.xml` is the single source of truth, the app will automatically self-report its active backend variant. To capture or update the baseline for a specific graphics variant, simply edit `android/app/src/main/AndroidManifest.xml` to set the desired `io.flutter.embedding.android.ImpellerBackend` value, then execute:
```sh
UPDATE_GOLDENS=true flutter drive -v \
--driver=test_driver/driver_test.dart \
--target=integration_test/integration_test_wrapper.dart \
--no-dds
```
---
### B. Running Instrumented Tests (OEM / Self-Contained)
> [!IMPORTANT]
> **Asset Bundling Precondition**:
> Because instrumented tests run completely standalone on the device, they compare pixels against baseline images bundled as read-only assets inside the APK. You **must** first generate the local baselines under `test_driver/goldens/` using the **Host-Driven Driver Mode (with `UPDATE_GOLDENS=true`)** before compiling and building the instrumented APK.
* **Command to compile and run the native JUnit suite**:
```sh
# Execute from the 'android' subdirectory
cd android
./gradlew :app:connectedDebugAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.example.android_hardware_smoke_test.FlutterActivityTest \
-s
```
> [!NOTE]
> **Statically Compiled Single Source of Truth**:
> The app's compiled `AndroidManifest.xml` `<meta-data>` tag is the single source of truth for the graphics backend configuration under **both** Instrumented On-Device Mode (OEM) and Host-Driven Driver Mode (CI).
>
> * **Instrumented On-Device Mode (OEM)**: The native Java JUnit harness (`FlutterActivityTest.java`) reads this value dynamically using the `PackageManager` API and routes it to Dart.
> * **Host-Driven Driver Mode (CI / Host)**: The Dart app queries the native Android embedder via a custom `MethodChannel` to self-discover its compiled backend and self-reports it to the host test script inside its JSON reply payload, completely eliminating the need for environment variables on the host PC.
>
> To switch the active graphics backend manually for local runs, open `android/app/src/main/AndroidManifest.xml` and update the `io.flutter.embedding.android.ImpellerBackend` value:
>
> ```xml
> <!-- Enable Vulkan: -->
> <meta-data android:name="io.flutter.embedding.android.ImpellerBackend" android:value="vulkan" />
>
> <!-- Enable OpenGLES: -->
> <meta-data android:name="io.flutter.embedding.android.ImpellerBackend" android:value="opengles" />
> ```
> [!NOTE]
> **Automated HTML Screenshot Embedding (`embedTestResultImages`)**:
> When running the Gradle command above, a custom Kotlin DSL task named **`embedTestResultImages`** executes automatically once the tests finish.
>
> It performs the following actions seamlessly:
> 1. Prevents Gradle from auto-uninstalling the APKs prematurely (via a `gradle.properties` injection).
> 2. Queries the device sandbox cache to discover all rendered `.png` files dynamically.
> 3. Streams the raw binary images directly onto the host PC using zero-copy ADB piping.
> 4. Dynamically parses the generated HTML reports and injects Alternative `<img>` elements right next to the test outcome table cells.
> 5. Executes a manual `adb uninstall` cleanup to leave the target device perfectly clean.
>
> Once finished, open `app/build/reports/androidTests/connected/debug/index.html` to view the interactive report with all rendering result snapshots embedded natively!
---
### C. Manual Command-Line Debugging (Without Gradle Orchestration)
If you prefer to bypass Gradle entirely for custom debugging, you can manually build, install, and run the instrumentation using raw `adb` shell calls:
1. **Build and Install the packages manually**:
```sh
# Run from the 'android' subdirectory
cd android
./gradlew installDebug installDebugAndroidTest
```
2. **Manually launch the native Android instrumentation test**:
```sh
adb shell am instrument -w \
-e class com.example.android_hardware_smoke_test.FlutterActivityTest \
com.example.android_hardware_smoke_test.test/androidx.test.runner.AndroidJUnitRunner
```
3. **Manually pull the generated snapshot off the device's sandbox**:
Since the app remains installed during raw `adb` runs, you can copy the rendering result files manually:
```sh
adb exec-out "run-as com.example.android_hardware_smoke_test cat cache/results/blueRectangleTest.png" \
> test_driver/results/blueRectangleTest.png
```
---
### D. Running the CI Shard Locally
Since the test suite is registered inside the central repository test orchestrator (`dev/bots/test.dart`), you can execute the full CI runner pipeline locally using standard dev-bot scripts:
```sh
# Run the Vulkan graphics backend shard locally
SHARD=android_hardware_smoke_vulkan_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
# Run the OpenGLES graphics backend shard locally
SHARD=android_hardware_smoke_opengles_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
```
---
## 5. Test Suite Coverage & Technical Rationales
The suite is composed of targeted visual regression test cases designed to exercise distinct GPU graphics pipelines and verify compatibility with driver and hardware configurations:
| Test Case | Rendering Pipeline | Impeller/Hardware Mechanism Exercised | Reference/Source |
| :--- | :--- | :--- | :--- |
| **`blueRectangleTest`** | **Solid Vector Fills** | Standard vector rasterization and layout transformation. | Simple `canvas.drawRect` |
| **`trianglePathTest`** | **Complex Paths** | Path triangulation, rasterization, and hardware anti-aliasing (MSAA). | Simple `canvas.drawPath` |
| **`textTest`** | **Font Rendering** | Text layout (`TextPainter`), glyph caching, shaping, and font atlas rendering. | Simple `TextPainter.paint` |
| **`imageTest`** | **Texture Sampling** | Image decoding, GPU texture uploading, and texture sampler rendering. Uses a 32x32 4-color checkerboard PNG to verify RGB color channel correctness. | Simple `canvas.drawImage` |
| **`advancedBlendTest`** | **Advanced Blending** | Fragment shader blending and framebuffer fetch tile-memory optimizations (e.g. Vulkan subpass inputs, `EXT_shader_framebuffer_fetch` in GLES). Uses `BlendMode.difference`. | Mirrors [animated_advanced_blend.dart](/dev/benchmarks/macrobenchmarks/lib/src/animated_advanced_blend.dart). |
| **`backdropFilterBlurTest`** | **Compositing & Blur** | Offscreen texture allocation, layer downscale/upscale passes, and multi-pass Gaussian blur filter execution. Uses `ImageFilter.blur(sigmaX: 5, sigmaY: 5)`. | Mirrors [backdrop_filter.dart](/dev/benchmarks/macrobenchmarks/lib/src/backdrop_filter.dart). |