Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add super trivial smoke test #102

Merged
merged 3 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,50 @@ jobs:
name: ruffle-release-apks
path: app/build/outputs/apk/release/*.apk

android-tests:
name: Android Tests
needs: build-native-libs
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with: # no name set, so all artifacts are downloaded
path: native-libs

- name: Copy native libs
run: |
mkdir app/src/main/jniLibs
cp -r native-libs/*/* app/src/main/jniLibs/

- name: Set up Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

# https://github.blog/changelog/2023-02-23-hardware-accelerated-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedCheck

- name: Upload Test Report
uses: actions/upload-artifact@v3
if: ${{ !cancelled() }} # always run even if the previous step fails
with:
name: junit-test-results
path: '**/build/test-results/test/TEST-*.xml'
retention-days: 1

24 changes: 24 additions & 0 deletions .github/workflows/report_test_results.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Report test results
on:
workflow_run:
workflows: [build]
types: [completed]

permissions:
checks: write

jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Download Test Report
uses: dawidd6/action-download-artifact@v2
with:
name: junit-test-results
workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }}
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
with:
commit: ${{github.event.workflow_run.head_sha}}
report_paths: '**/build/test-results/test/TEST-*.xml'
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:Suppress("UnstableApiUsage")

import com.github.willir.rust.CargoNdkBuildTask

plugins {
Expand Down Expand Up @@ -109,6 +111,9 @@ dependencies {
implementation(libs.androidx.games.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.appcompat)
androidTestImplementation(libs.androidx.uiautomator)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
12 changes: 12 additions & 0 deletions app/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="28" />

<instrumentation android:targetPackage="rs.ruffle"
android:name="androidx.test.runner.AndroidJUnitRunner"/>

<application tools:replace="label" android:label="SmokeTest" />
</manifest>
22 changes: 0 additions & 22 deletions app/src/androidTest/java/rs/ruffle/ExampleInstrumentedTest.kt

This file was deleted.

83 changes: 83 additions & 0 deletions app/src/androidTest/java/rs/ruffle/SmokeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package rs.ruffle

import android.R
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import java.io.File
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.notNullValue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

private const val BASIC_SAMPLE_PACKAGE = "rs.ruffle"
private const val LAUNCH_TIMEOUT = 5000L

@RunWith(AndroidJUnit4::class)
class SmokeTest {
private lateinit var device: UiDevice
private lateinit var traceOutput: File
private lateinit var swfFile: File

@Before
fun startMainActivityFromHomeScreen() {
// Initialize UiDevice instance
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

// Start from the home screen
device.pressHome()

// Wait for launcher
val launcherPackage: String = device.launcherPackageName
assertThat(launcherPackage, notNullValue())
device.wait(
Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT
)

// Launch the app
val context = ApplicationProvider.getApplicationContext<Context>()
traceOutput = File.createTempFile("trace", ".txt", context.cacheDir)
swfFile = File.createTempFile("movie", ".swf", context.cacheDir)
val resources = InstrumentationRegistry.getInstrumentation().context.resources
val inStream = resources.openRawResource(
rs.ruffle.test.R.raw.helloflash
)
val bytes = inStream.readBytes()
swfFile.writeBytes(bytes)
val intent = context.packageManager.getLaunchIntentForPackage(
BASIC_SAMPLE_PACKAGE
)?.apply {
component = ComponentName("rs.ruffle", "rs.ruffle.PlayerActivity")
data = Uri.fromFile(swfFile)
putExtra("traceOutput", traceOutput.absolutePath)
// Clear out any previous instances
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
context.startActivity(intent)

// Wait for the app to appear
device.wait(
Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT
)
}

@Test
fun emulatorRunsASwf() {
device.waitForWindowUpdate(null, 1000)
assertThat(device, notNullValue())

val trace = traceOutput.readLines()
assertThat(trace, equalTo(listOf("Hello from Flash!")))
}
}
Binary file added app/src/androidTest/res/raw/helloflash.swf
Binary file not shown.
7 changes: 7 additions & 0 deletions app/src/main/java/rs/ruffle/PlayerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ class PlayerActivity : GameActivity() {
return intent.dataString
}

@Suppress("unused")
// Used by Rust
private val traceOutput: String?
get() {
return intent.getStringExtra("traceOutput")
}

@Suppress("unused")
// Used by Rust
private fun navigateToUrl(url: String?) {
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ gamesActivity = "2.0.2" # Needs to be in sync with android-activity crate
constraintlayout = "2.1.4"
appcompat = "1.6.1"
ktlint = "12.1.0"
uiautomator = "2.3.0"
androidXTestRunner = "1.5.2"
androidXTestRules = "1.5.0"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
Expand All @@ -37,6 +40,9 @@ androidx-navigation-compose = { group = "androidx.navigation", name = "navigatio
androidx-games-activity = { group = "androidx.games", name = "games-activity", version.ref = "gamesActivity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTestRunner" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidXTestRules" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand Down
22 changes: 22 additions & 0 deletions src/java.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use jni::objects::{
use jni::signature::{Primitive, ReturnType};
use jni::JNIEnv;
use ruffle_core::ContextMenuItem;
use std::path::PathBuf;
use std::sync::OnceLock;

/// Handles to various items on the Java `PlayerActivity` class.
Expand All @@ -16,6 +17,7 @@ pub struct JavaInterface {
show_context_menu: JMethodID,
get_swf_bytes: JMethodID,
get_swf_uri: JMethodID,
get_trace_output: JMethodID,
get_loc_on_screen: JMethodID,
}

Expand Down Expand Up @@ -109,6 +111,23 @@ impl JavaInterface {
url
}

pub fn get_trace_output(env: &mut JNIEnv, this: &JObject) -> Option<PathBuf> {
let result = unsafe {
env.call_method_unchecked(this, Self::get().get_trace_output, ReturnType::Object, &[])
};
let object = result
.expect("getTraceOutput() must never throw")
.l()
.unwrap();
if object.is_null() {
return None;
}
let string_object = JString::from(object);
let java_string = unsafe { env.get_string_unchecked(&string_object) };
let url = java_string.unwrap().to_string_lossy().to_string();
Some(url.into())
}

pub fn get_loc_on_screen(env: &mut JNIEnv, this: &JObject) -> (i32, i32) {
let result = unsafe {
env.call_method_unchecked(this, Self::get().get_loc_on_screen, ReturnType::Array, &[])
Expand Down Expand Up @@ -150,6 +169,9 @@ impl JavaInterface {
get_swf_uri: env
.get_method_id(class, "getSwfUri", "()Ljava/lang/String;")
.expect("getSwfUri must exist"),
get_trace_output: env
.get_method_id(class, "getTraceOutput", "()Ljava/lang/String;")
.expect("getTraceOutput must exist"),
get_loc_on_screen: env
.get_method_id(class, "getLocOnScreen", "()[I")
.expect("getLocOnScreen must exist"),
Expand Down
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod java;
mod keycodes;
mod navigator;
mod task;
mod trace;

use custom_event::RuffleEvent;

Expand Down Expand Up @@ -41,6 +42,7 @@ use ruffle_core::{
};

use crate::keycodes::android_keycode_to_ruffle;
use crate::trace::FileLogBackend;
use java::JavaInterface;
use ruffle_render_wgpu::{backend::WgpuRenderBackend, target::SwapChainTarget};

Expand Down Expand Up @@ -78,14 +80,14 @@ fn run(app: AndroidApp) {
};

log::info!("Starting event loop...");
let trace_output;

unsafe {
let vm = JavaVM::from_raw(app.vm_as_ptr() as *mut sys::JavaVM).expect("JVM must exist");
let activity = JObject::from_raw(app.activity_as_ptr() as jobject);
let _ = vm
.get_env()
.unwrap()
.set_rust_field(activity, "eventLoopHandle", sender.clone());
let mut jni_env = vm.get_env().unwrap();
trace_output = JavaInterface::get_trace_output(&mut jni_env, &activity);
let _ = jni_env.set_rust_field(activity, "eventLoopHandle", sender.clone());
}

while !quit {
Expand Down Expand Up @@ -222,6 +224,7 @@ fn run(app: AndroidApp) {
.with_audio(AAudioAudioBackend::new().unwrap())
.with_storage(MemoryStorageBackend::default())
.with_navigator(navigator)
.with_log(FileLogBackend::new(trace_output.as_deref()))
.with_video(
ruffle_video_software::backend::SoftwareVideoBackend::new(),
)
Expand Down
Loading
Loading