https://github.com/bbqsrc/vampire-rs
Run instrumented Rust integration tests on Android (with JNI compat)
https://github.com/bbqsrc/vampire-rs
android jni rust testing
Last synced: 3 months ago
JSON representation
Run instrumented Rust integration tests on Android (with JNI compat)
- Host: GitHub
- URL: https://github.com/bbqsrc/vampire-rs
- Owner: bbqsrc
- License: other
- Created: 2025-10-01T18:48:19.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-10-07T21:23:34.000Z (9 months ago)
- Last Synced: 2026-02-02T07:50:58.050Z (5 months ago)
- Topics: android, jni, rust, testing
- Language: Rust
- Homepage:
- Size: 105 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# 🧛 Vampire
A minimalist Android test framework for Rust that enables running Rust tests on Android devices through dynamic loading.
[](https://github.com/vampire-rs/vampire)
## Why Vampire?
Named after the [vampire crab](https://en.wikipedia.org/wiki/Geosesarma_dennerle) (*Geosesarma dennerle*), a small purple crustacean native to Java. The name is a double pun: the crab lives in **Java** (the island), and this framework runs tests in **Java** (the Android platform). Like its namesake, Vampire dynamically loads into a host environment to do its work.
## Quick Start
1. **Install dependencies:**
```bash
# Ensure you have Android SDK and NDK
export ANDROID_SDK_ROOT=/path/to/android/sdk
# Or on macOS, SDK is auto-detected from ~/Library/Android/sdk
# Install cargo-ndk
cargo install cargo-ndk
```
2. **Run tests:**
```bash
# Run tests (builds + deploys + runs automatically)
cargo run --bin vampire -- test
# Show detailed test output including stdout/stderr
cargo run --bin vampire -- test --nocapture
# Force rebuild of APK even if already installed
cargo run --bin vampire -- test --force
```
The `vampire` CLI automatically:
- ✅ Builds test library for Android (arm64-v8a)
- ✅ Compiles and builds host APK (only when needed)
- ✅ Installs APK to device (only if not already installed)
- ✅ Deploys native library to app's private storage
- ✅ Runs tests and displays results
## Configuration
### Android Permissions
Specify Android permissions your tests need in `Cargo.toml`:
```toml
[package.metadata.vampire]
permissions = [
"android.permission.INTERNET",
"android.permission.ACCESS_NETWORK_STATE",
"android.permission.WRITE_EXTERNAL_STORAGE"
]
```
These permissions are automatically added to the generated AndroidManifest.xml when building the host APK.
### Maven Dependencies
If your tests require Android libraries (JARs/AARs), specify them using Maven coordinates in a Cargo-style format:
```toml
[package.metadata.vampire.dependencies]
"org.chromium.net:cronet-api" = "141.7340.3"
"androidx.annotation:annotation" = "1.7.0"
```
You can also use the object form for future extensibility:
```toml
[package.metadata.vampire.dependencies]
"org.chromium.net:cronet-api" = { version = "141.7340.3" }
```
Vampire will automatically:
- Download the artifacts and their transitive dependencies from Maven Central and Google Maven
- Resolve version conflicts using Maven's nearest-wins strategy
- Extract classes from AAR files
- Include them in the DEX compilation for the host APK
- Cache downloads in `target/vampire/maven-cache/` for faster builds
This allows your Rust tests to interact with Java classes via JNI.
## Build Script Setup
If you have Java sources that need to be compiled for Android (e.g., JNI callback classes), create a `build.rs` file in your package root:
```rust
fn main() {
vampire_build::configure();
}
```
Add `vampire-build` to your build dependencies in `Cargo.toml`:
```toml
[build-dependencies]
vampire-build = { path = "../vampire/vampire-build" } # or version from crates.io
```
### Java Source Directory
By default, `vampire-build` looks for Java sources in the `java/` directory of your package. Place your `.java` files there:
```
my-package/
├── Cargo.toml
├── build.rs
├── java/
│ └── com/
│ └── example/
│ └── MyCallback.java
└── src/
└── lib.rs
```
### Custom Configuration
For advanced use cases, you can customize the builder:
```rust
fn main() {
vampire_build::Builder::new()
.java_dir("src/main/java") // Custom Java source directory
.target_sdk(33) // Target SDK version (default: 30)
.java_source("path/to/specific/File.java") // Add specific file
.configure();
}
```
### What It Does
`vampire-build` automatically:
- Enables `cfg(vampire)` for conditional compilation
- Detects Android target builds
- Finds and compiles all `.java` files in your Java directory
- Uses Java 8 compatibility for Android
- Converts `.class` files to DEX format (required for Android)
- Outputs `classes.dex` to `OUT_DIR`
- Sets up proper `cargo:rerun-if-changed` triggers
**Note:** DEX generation requires the Android SDK with build-tools installed. Set `ANDROID_SDK_ROOT` or `ANDROID_HOME` environment variable pointing to your SDK location.
## Writing Tests
### Basic Tests
```rust
use vampire;
// IMPORTANT: Re-export JNI_OnLoad so Java can initialize the runtime
pub use vampire::JNI_OnLoad;
#[vampire::test]
fn sync_test() {
assert_eq!(2 + 2, 4);
}
#[vampire::test]
async fn async_test() {
tokio::time::sleep(Duration::from_millis(100)).await;
// Your async test code
}
```
### Expected Failures
Use `should_panic` for tests that are expected to fail:
```rust
#[vampire::test(should_panic)]
fn test_expected_failure() {
panic!("This failure is expected and will count as a pass");
}
```
### Android System Interaction
Access Android system properties and device information:
```rust
use vampire::android;
#[vampire::test]
fn test_android_system() {
// Get Android version
let version = android::get_android_version()
.expect("Should get Android version");
// Access app's private directory
let files_dir = android::get_files_dir()
.expect("Should get files directory");
// Query device information
let model = android::get_device_model();
let is_emulator = android::is_emulator();
// Check system properties
let prop = android::get_system_property("ro.build.version.sdk");
}
```
## Cross-Platform Tests
The `#[vampire::test]` macro works on both Android and native platforms:
```rust
#[vampire::test]
fn test_works_everywhere() {
assert_eq!(2 + 2, 4);
}
```
- **On Android**: Runs through the instrumentation framework
- **On native**: Expands to `#[test]` (or `#[tokio::test]` for async)
Run native tests with `cargo test`, Android tests with `cargo run --bin vampire -- test`.
### Tests in `tests/` Directory
Since Android requires all tests in a single cdylib, you need to explicitly include `tests/` files in your `lib.rs`:
```rust
// In src/lib.rs
#[cfg(vampire)]
#[path = "../tests"]
mod integration_tests {
#[path = "my_test.rs"]
mod my_test;
#[path = "another_test.rs"]
mod another_test;
}
```
The `vampire` CLI automatically sets `--cfg vampire` when building, so these modules are only included when building for Android tests.
## Architecture
- **Host APK**: Minimal instrumentation container that loads test libraries
- **Test Library**: Your Rust tests compiled to `.so` with automatic JNI registration
- **vampire CLI**: Coordinates building, deploying, and running tests
## Components
- `vampire-cli`: Main CLI tool (`cargo run --bin vampire`)
- `vampire-macro`: Proc macro providing `#[vampire::test]`
- `vampire`: Runtime library with JNI utilities and Android system access
- `vampire-example`: Example test suite demonstrating features